Add support for ssh commit signing (#17743)
* Add support for ssh commit signing * Split out ssh verification to separate file * Show ssh key fingerprint on commit page * Update sshsig lib * Make sure we verify against correct namespace * Add ssh public key verification via ssh signatures When adding a public ssh key also validate that this user actually owns the key by signing a token with the private key. * Remove some gpg references and make verify key optional * Fix spaces indentation * Update options/locale/locale_en-US.ini Co-authored-by: Gusted <williamzijl7@hotmail.com> * Update templates/user/settings/keys_ssh.tmpl Co-authored-by: Gusted <williamzijl7@hotmail.com> * Update options/locale/locale_en-US.ini Co-authored-by: Gusted <williamzijl7@hotmail.com> * Update options/locale/locale_en-US.ini Co-authored-by: Gusted <williamzijl7@hotmail.com> * Update models/ssh_key_commit_verification.go Co-authored-by: Gusted <williamzijl7@hotmail.com> * Reword ssh/gpg_key_success message * Change Badsignature to NoKeyFound * Add sign/verify tests * Fix upstream api changes to user_model User * Match exact on SSH signature * Fix code review remarks Co-authored-by: Gusted <williamzijl7@hotmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>tokarchuk/v1.17
parent
f1e85622da
commit
6fe756dc93
@ -0,0 +1,77 @@ |
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package asymkey |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
user_model "code.gitea.io/gitea/models/user" |
||||
"code.gitea.io/gitea/modules/git" |
||||
"code.gitea.io/gitea/modules/log" |
||||
|
||||
"github.com/42wim/sshsig" |
||||
) |
||||
|
||||
// ParseCommitWithSSHSignature check if signature is good against keystore.
|
||||
func ParseCommitWithSSHSignature(c *git.Commit, committer *user_model.User) *CommitVerification { |
||||
// Now try to associate the signature with the committer, if present
|
||||
if committer.ID != 0 { |
||||
keys, err := ListPublicKeys(committer.ID, db.ListOptions{}) |
||||
if err != nil { // Skipping failed to get ssh keys of user
|
||||
log.Error("ListPublicKeys: %v", err) |
||||
return &CommitVerification{ |
||||
CommittingUser: committer, |
||||
Verified: false, |
||||
Reason: "gpg.error.failed_retrieval_gpg_keys", |
||||
} |
||||
} |
||||
|
||||
committerEmailAddresses, err := user_model.GetEmailAddresses(committer.ID) |
||||
if err != nil { |
||||
log.Error("GetEmailAddresses: %v", err) |
||||
} |
||||
|
||||
activated := false |
||||
for _, e := range committerEmailAddresses { |
||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { |
||||
activated = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
for _, k := range keys { |
||||
if k.Verified && activated { |
||||
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) |
||||
if commitVerification != nil { |
||||
return commitVerification |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return &CommitVerification{ |
||||
CommittingUser: committer, |
||||
Verified: false, |
||||
Reason: NoKeyFound, |
||||
} |
||||
} |
||||
|
||||
func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification { |
||||
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil { |
||||
return nil |
||||
} |
||||
|
||||
return &CommitVerification{ // Everything is ok
|
||||
CommittingUser: committer, |
||||
Verified: true, |
||||
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), |
||||
SigningUser: signer, |
||||
SigningSSHKey: k, |
||||
SigningEmail: email, |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package asymkey |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/modules/log" |
||||
"github.com/42wim/sshsig" |
||||
) |
||||
|
||||
// VerifySSHKey marks a SSH key as verified
|
||||
func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string, error) { |
||||
ctx, committer, err := db.TxContext() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
defer committer.Close() |
||||
|
||||
key := new(PublicKey) |
||||
|
||||
has, err := db.GetEngine(ctx).Where("owner_id = ? AND fingerprint = ?", ownerID, fingerprint).Get(key) |
||||
if err != nil { |
||||
return "", err |
||||
} else if !has { |
||||
return "", ErrKeyNotExist{} |
||||
} |
||||
|
||||
if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil { |
||||
log.Error("Unable to validate token signature. Error: %v", err) |
||||
return "", ErrSSHInvalidTokenSignature{ |
||||
Fingerprint: key.Fingerprint, |
||||
} |
||||
} |
||||
|
||||
key.Verified = true |
||||
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
if err := committer.Commit(); err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return key.Fingerprint, nil |
||||
} |
@ -0,0 +1,15 @@ |
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations |
||||
|
||||
import "xorm.io/xorm" |
||||
|
||||
func addSSHKeyIsVerified(x *xorm.Engine) error { |
||||
type PublicKey struct { |
||||
Verified bool `xorm:"NOT NULL DEFAULT false"` |
||||
} |
||||
|
||||
return x.Sync(new(PublicKey)) |
||||
} |
@ -0,0 +1,201 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,82 @@ |
||||
# Armored ssh signatures in go |
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/42wim/sshsig.svg)](https://pkg.go.dev/github.com/42wim/sshsig#section-documentation) |
||||
|
||||
Package sshsig implements signing/verifying armored SSH signatures. |
||||
You can use this package to sign data and verify signatures using your ssh private keys or your ssh agent. |
||||
It gives the same output as using `ssh-keygen`, eg when signing `ssh-keygen -Y sign -f keyfile -n namespace data` |
||||
|
||||
This code is based upon work by <https://github.com/sigstore/rekor> |
||||
|
||||
References: <https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig> |
||||
|
||||
You can find some examples on how to use this library on: <https://pkg.go.dev/github.com/42wim/sshsig#pkg-examples> |
||||
|
||||
## Examples |
||||
|
||||
```golang |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"net" |
||||
"os" |
||||
|
||||
"github.com/42wim/sshsig" |
||||
"golang.org/x/crypto/ssh/agent" |
||||
) |
||||
|
||||
func ExampleSignWithAgent() { |
||||
// This example will panic when you don't have a ssh-agent running. |
||||
conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
ag := agent.NewClient(conn) |
||||
|
||||
// This public key must match in your agent (use `ssh-add -L` to get the public key) |
||||
pubkey := []byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAo3D7CGN01tTYY/dLKXEv8RxRyxa32c51X0uKMhnMab wim@localhost`) |
||||
// |
||||
data := []byte("hello world") |
||||
|
||||
res, err := sshsig.SignWithAgent(pubkey, ag, bytes.NewBuffer(data), "file") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
fmt.Println(string(res)) |
||||
} |
||||
|
||||
func ExampleSign() { |
||||
privkey := []byte(`-----BEGIN OPENSSH PRIVATE KEY----- |
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW |
||||
QyNTUxOQAAACCOjP6i4Pm/pYAAmpAMNZ6xrbHl9RW8xdul6kzIWuKMMAAAAIhoQm34aEJt |
||||
+AAAAAtzc2gtZWQyNTUxOQAAACCOjP6i4Pm/pYAAmpAMNZ6xrbHl9RW8xdul6kzIWuKMMA |
||||
AAAEBfIl93TLj6qHeg37GnPuZ00h8OVv1mzlhy0rhuO4Y0do6M/qLg+b+lgACakAw1nrGt |
||||
seX1FbzF26XqTMha4owwAAAAAAECAwQF |
||||
-----END OPENSSH PRIVATE KEY-----`) |
||||
|
||||
data := []byte("hello world") |
||||
|
||||
res, err := sshsig.Sign(privkey, bytes.NewBuffer(data), "file") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
fmt.Println(string(res)) |
||||
|
||||
// Output: |
||||
// -----BEGIN SSH SIGNATURE----- |
||||
// U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgjoz+ouD5v6WAAJqQDDWesa2x5f |
||||
// UVvMXbpepMyFrijDAAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx |
||||
// OQAAAEBeu9Z+vLxBORysiqEbTzJP0EZKG0/aE5HpTtvimjQS6mHZCAGFg+kimNatBE0Y1j |
||||
// gS4pfD73TlML1SyB5lb/YO |
||||
// -----END SSH SIGNATURE----- |
||||
} |
||||
|
||||
func main() { |
||||
ExampleSign() |
||||
} |
||||
``` |
@ -0,0 +1,11 @@ |
||||
/* |
||||
Package sshsig implements signing/verifying armored SSH signatures. |
||||
You can use this package to sign data and verify signatures using your ssh private keys or your ssh agent. |
||||
It gives the same output as using `ssh-keygen`, eg when signing `ssh-keygen -Y sign -f keyfile -n namespace data` |
||||
|
||||
This code is based upon work by https://github.com/sigstore/rekor
|
||||
|
||||
References: |
||||
- https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig
|
||||
*/ |
||||
package sshsig |
@ -0,0 +1,95 @@ |
||||
// Modified by 42wim
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sshsig |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
|
||||
"github.com/42wim/sshsig/pem" |
||||
|
||||
"golang.org/x/crypto/ssh" |
||||
) |
||||
|
||||
const ( |
||||
pemType = "SSH SIGNATURE" |
||||
) |
||||
|
||||
// Armored returns the signature in an armored format.
|
||||
func Armor(s *ssh.Signature, p ssh.PublicKey, ns string) []byte { |
||||
sig := WrappedSig{ |
||||
Version: 1, |
||||
PublicKey: string(p.Marshal()), |
||||
Namespace: ns, |
||||
HashAlgorithm: defaultHashAlgorithm, |
||||
Signature: string(ssh.Marshal(s)), |
||||
} |
||||
|
||||
copy(sig.MagicHeader[:], magicHeader) |
||||
|
||||
enc := pem.EncodeToMemory(&pem.Block{ |
||||
Type: pemType, |
||||
Bytes: ssh.Marshal(sig), |
||||
}) |
||||
return enc |
||||
} |
||||
|
||||
// Decode parses an armored signature.
|
||||
func Decode(b []byte) (*Signature, error) { |
||||
pemBlock, _ := pem.Decode(b) |
||||
if pemBlock == nil { |
||||
return nil, errors.New("unable to decode pem file") |
||||
} |
||||
|
||||
if pemBlock.Type != pemType { |
||||
return nil, fmt.Errorf("wrong pem block type: %s. Expected SSH-SIGNATURE", pemBlock.Type) |
||||
} |
||||
|
||||
// Now we unmarshal it into the Signature block
|
||||
sig := WrappedSig{} |
||||
if err := ssh.Unmarshal(pemBlock.Bytes, &sig); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if sig.Version != 1 { |
||||
return nil, fmt.Errorf("unsupported signature version: %d", sig.Version) |
||||
} |
||||
if string(sig.MagicHeader[:]) != magicHeader { |
||||
return nil, fmt.Errorf("invalid magic header: %s", sig.MagicHeader[:]) |
||||
} |
||||
if _, ok := supportedHashAlgorithms[sig.HashAlgorithm]; !ok { |
||||
return nil, fmt.Errorf("unsupported hash algorithm: %s", sig.HashAlgorithm) |
||||
} |
||||
|
||||
// Now we can unpack the Signature and PublicKey blocks
|
||||
sshSig := ssh.Signature{} |
||||
if err := ssh.Unmarshal([]byte(sig.Signature), &sshSig); err != nil { |
||||
return nil, err |
||||
} |
||||
// TODO: check the format here (should be rsa-sha512)
|
||||
|
||||
pk, err := ssh.ParsePublicKey([]byte(sig.PublicKey)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &Signature{ |
||||
signature: &sshSig, |
||||
pk: pk, |
||||
hashAlg: sig.HashAlgorithm, |
||||
}, nil |
||||
} |
@ -0,0 +1,7 @@ |
||||
module github.com/42wim/sshsig |
||||
|
||||
go 1.17 |
||||
|
||||
require golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 |
||||
|
||||
require golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect |
@ -0,0 +1,12 @@ |
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= |
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= |
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= |
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= |
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
@ -0,0 +1,346 @@ |
||||
// Modified by 42wim
|
||||
//
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package pem implements the PEM data encoding, which originated in Privacy
|
||||
// Enhanced Mail. The most common use of PEM encoding today is in TLS keys and
|
||||
// certificates. See RFC 1421.
|
||||
package pem |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/base64" |
||||
"errors" |
||||
"io" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
// A Block represents a PEM encoded structure.
|
||||
//
|
||||
// The encoded form is:
|
||||
// -----BEGIN Type-----
|
||||
// Headers
|
||||
// base64-encoded Bytes
|
||||
// -----END Type-----
|
||||
// where Headers is a possibly empty sequence of Key: Value lines.
|
||||
type Block struct { |
||||
Type string // The type, taken from the preamble (i.e. "RSA PRIVATE KEY").
|
||||
Headers map[string]string // Optional headers.
|
||||
Bytes []byte // The decoded bytes of the contents. Typically a DER encoded ASN.1 structure.
|
||||
} |
||||
|
||||
// getLine results the first \r\n or \n delineated line from the given byte
|
||||
// array. The line does not include trailing whitespace or the trailing new
|
||||
// line bytes. The remainder of the byte array (also not including the new line
|
||||
// bytes) is also returned and this will always be smaller than the original
|
||||
// argument.
|
||||
func getLine(data []byte) (line, rest []byte) { |
||||
i := bytes.IndexByte(data, '\n') |
||||
var j int |
||||
if i < 0 { |
||||
i = len(data) |
||||
j = i |
||||
} else { |
||||
j = i + 1 |
||||
if i > 0 && data[i-1] == '\r' { |
||||
i-- |
||||
} |
||||
} |
||||
return bytes.TrimRight(data[0:i], " \t"), data[j:] |
||||
} |
||||
|
||||
// removeSpacesAndTabs returns a copy of its input with all spaces and tabs
|
||||
// removed, if there were any. Otherwise, the input is returned unchanged.
|
||||
//
|
||||
// The base64 decoder already skips newline characters, so we don't need to
|
||||
// filter them out here.
|
||||
func removeSpacesAndTabs(data []byte) []byte { |
||||
if !bytes.ContainsAny(data, " \t") { |
||||
// Fast path; most base64 data within PEM contains newlines, but
|
||||
// no spaces nor tabs. Skip the extra alloc and work.
|
||||
return data |
||||
} |
||||
result := make([]byte, len(data)) |
||||
n := 0 |
||||
|
||||
for _, b := range data { |
||||
if b == ' ' || b == '\t' { |
||||
continue |
||||
} |
||||
result[n] = b |
||||
n++ |
||||
} |
||||
|
||||
return result[0:n] |
||||
} |
||||
|
||||
var ( |
||||
pemStart = []byte("\n-----BEGIN ") |
||||
pemEnd = []byte("\n-----END ") |
||||
pemEndOfLine = []byte("-----") |
||||
) |
||||
|
||||
// Decode will find the next PEM formatted block (certificate, private key
|
||||
// etc) in the input. It returns that block and the remainder of the input. If
|
||||
// no PEM data is found, p is nil and the whole of the input is returned in
|
||||
// rest.
|
||||
func Decode(data []byte) (p *Block, rest []byte) { |
||||
// pemStart begins with a newline. However, at the very beginning of
|
||||
// the byte array, we'll accept the start string without it.
|
||||
rest = data |
||||
if bytes.HasPrefix(data, pemStart[1:]) { |
||||
rest = rest[len(pemStart)-1 : len(data)] |
||||
} else if i := bytes.Index(data, pemStart); i >= 0 { |
||||
rest = rest[i+len(pemStart) : len(data)] |
||||
} else { |
||||
return nil, data |
||||
} |
||||
|
||||
typeLine, rest := getLine(rest) |
||||
if !bytes.HasSuffix(typeLine, pemEndOfLine) { |
||||
return decodeError(data, rest) |
||||
} |
||||
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] |
||||
|
||||
p = &Block{ |
||||
Headers: make(map[string]string), |
||||
Type: string(typeLine), |
||||
} |
||||
|
||||
for { |
||||
// This loop terminates because getLine's second result is
|
||||
// always smaller than its argument.
|
||||
if len(rest) == 0 { |
||||
return nil, data |
||||
} |
||||
line, next := getLine(rest) |
||||
|
||||
i := bytes.IndexByte(line, ':') |
||||
if i == -1 { |
||||
break |
||||
} |
||||
|
||||
// TODO(agl): need to cope with values that spread across lines.
|
||||
key, val := line[:i], line[i+1:] |
||||
key = bytes.TrimSpace(key) |
||||
val = bytes.TrimSpace(val) |
||||
p.Headers[string(key)] = string(val) |
||||
rest = next |
||||
} |
||||
|
||||
var endIndex, endTrailerIndex int |
||||
|
||||
// If there were no headers, the END line might occur
|
||||
// immediately, without a leading newline.
|
||||
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) { |
||||
endIndex = 0 |
||||
endTrailerIndex = len(pemEnd) - 1 |
||||
} else { |
||||
endIndex = bytes.Index(rest, pemEnd) |
||||
endTrailerIndex = endIndex + len(pemEnd) |
||||
} |
||||
|
||||
if endIndex < 0 { |
||||
return decodeError(data, rest) |
||||
} |
||||
|
||||
// After the "-----" of the ending line, there should be the same type
|
||||
// and then a final five dashes.
|
||||
endTrailer := rest[endTrailerIndex:] |
||||
endTrailerLen := len(typeLine) + len(pemEndOfLine) |
||||
if len(endTrailer) < endTrailerLen { |
||||
return decodeError(data, rest) |
||||
} |
||||
|
||||
restOfEndLine := endTrailer[endTrailerLen:] |
||||
endTrailer = endTrailer[:endTrailerLen] |
||||
if !bytes.HasPrefix(endTrailer, typeLine) || |
||||
!bytes.HasSuffix(endTrailer, pemEndOfLine) { |
||||
return decodeError(data, rest) |
||||
} |
||||
|
||||
// The line must end with only whitespace.
|
||||
if s, _ := getLine(restOfEndLine); len(s) != 0 { |
||||
return decodeError(data, rest) |
||||
} |
||||
|
||||
base64Data := removeSpacesAndTabs(rest[:endIndex]) |
||||
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) |
||||
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) |
||||
if err != nil { |
||||
return decodeError(data, rest) |
||||
} |
||||
p.Bytes = p.Bytes[:n] |
||||
|
||||
// the -1 is because we might have only matched pemEnd without the
|
||||
// leading newline if the PEM block was empty.
|
||||
_, rest = getLine(rest[endIndex+len(pemEnd)-1:]) |
||||
|
||||
return |
||||
} |
||||
|
||||
func decodeError(data, rest []byte) (*Block, []byte) { |
||||
// If we get here then we have rejected a likely looking, but
|
||||
// ultimately invalid PEM block. We need to start over from a new
|
||||
// position. We have consumed the preamble line and will have consumed
|
||||
// any lines which could be header lines. However, a valid preamble
|
||||
// line is not a valid header line, therefore we cannot have consumed
|
||||
// the preamble line for the any subsequent block. Thus, we will always
|
||||
// find any valid block, no matter what bytes precede it.
|
||||
//
|
||||
// For example, if the input is
|
||||
//
|
||||
// -----BEGIN MALFORMED BLOCK-----
|
||||
// junk that may look like header lines
|
||||
// or data lines, but no END line
|
||||
//
|
||||
// -----BEGIN ACTUAL BLOCK-----
|
||||
// realdata
|
||||
// -----END ACTUAL BLOCK-----
|
||||
//
|
||||
// we've failed to parse using the first BEGIN line
|
||||
// and now will try again, using the second BEGIN line.
|
||||
p, rest := Decode(rest) |
||||
if p == nil { |
||||
rest = data |
||||
} |
||||
return p, rest |
||||
} |
||||
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L20-L21
|
||||
// Should be on 76 chars, but strangely enough ssh breaks on 70
|
||||
const pemLineLength = 70 |
||||
|
||||
type lineBreaker struct { |
||||
line [pemLineLength]byte |
||||
used int |
||||
out io.Writer |
||||
} |
||||
|
||||
var nl = []byte{'\n'} |
||||
|
||||
func (l *lineBreaker) Write(b []byte) (n int, err error) { |
||||
if l.used+len(b) < pemLineLength { |
||||
copy(l.line[l.used:], b) |
||||
l.used += len(b) |
||||
return len(b), nil |
||||
} |
||||
|
||||
n, err = l.out.Write(l.line[0:l.used]) |
||||
if err != nil { |
||||
return |
||||
} |
||||
excess := pemLineLength - l.used |
||||
l.used = 0 |
||||
|
||||
n, err = l.out.Write(b[0:excess]) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
n, err = l.out.Write(nl) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
return l.Write(b[excess:]) |
||||
} |
||||
|
||||
func (l *lineBreaker) Close() (err error) { |
||||
if l.used > 0 { |
||||
_, err = l.out.Write(l.line[0:l.used]) |
||||
if err != nil { |
||||
return |
||||
} |
||||
_, err = l.out.Write(nl) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func writeHeader(out io.Writer, k, v string) error { |
||||
_, err := out.Write([]byte(k + ": " + v + "\n")) |
||||
return err |
||||
} |
||||
|
||||
// Encode writes the PEM encoding of b to out.
|
||||
func Encode(out io.Writer, b *Block) error { |
||||
// Check for invalid block before writing any output.
|
||||
for k := range b.Headers { |
||||
if strings.Contains(k, ":") { |
||||
return errors.New("pem: cannot encode a header key that contains a colon") |
||||
} |
||||
} |
||||
|
||||
// All errors below are relayed from underlying io.Writer,
|
||||
// so it is now safe to write data.
|
||||
|
||||
if _, err := out.Write(pemStart[1:]); err != nil { |
||||
return err |
||||
} |
||||
if _, err := out.Write([]byte(b.Type + "-----\n")); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(b.Headers) > 0 { |
||||
const procType = "Proc-Type" |
||||
h := make([]string, 0, len(b.Headers)) |
||||
hasProcType := false |
||||
for k := range b.Headers { |
||||
if k == procType { |
||||
hasProcType = true |
||||
continue |
||||
} |
||||
h = append(h, k) |
||||
} |
||||
// The Proc-Type header must be written first.
|
||||
// See RFC 1421, section 4.6.1.1
|
||||
if hasProcType { |
||||
if err := writeHeader(out, procType, b.Headers[procType]); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
// For consistency of output, write other headers sorted by key.
|
||||
sort.Strings(h) |
||||
for _, k := range h { |
||||
if err := writeHeader(out, k, b.Headers[k]); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if _, err := out.Write(nl); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
var breaker lineBreaker |
||||
breaker.out = out |
||||
|
||||
b64 := base64.NewEncoder(base64.StdEncoding, &breaker) |
||||
if _, err := b64.Write(b.Bytes); err != nil { |
||||
return err |
||||
} |
||||
b64.Close() |
||||
breaker.Close() |
||||
|
||||
if _, err := out.Write(pemEnd[1:]); err != nil { |
||||
return err |
||||
} |
||||
_, err := out.Write([]byte(b.Type + "-----\n")) |
||||
return err |
||||
} |
||||
|
||||
// EncodeToMemory returns the PEM encoding of b.
|
||||
//
|
||||
// If b has invalid headers and cannot be encoded,
|
||||
// EncodeToMemory returns nil. If it is important to
|
||||
// report details about this error case, use Encode instead.
|
||||
func EncodeToMemory(b *Block) []byte { |
||||
var buf bytes.Buffer |
||||
if err := Encode(&buf, b); err != nil { |
||||
return nil |
||||
} |
||||
return buf.Bytes() |
||||
} |
@ -0,0 +1,169 @@ |
||||
// Modifications by 42wim
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sshsig |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"crypto/sha256" |
||||
"crypto/sha512" |
||||
"errors" |
||||
"hash" |
||||
"io" |
||||
|
||||
"golang.org/x/crypto/ssh" |
||||
"golang.org/x/crypto/ssh/agent" |
||||
) |
||||
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L81
|
||||
type MessageWrapper struct { |
||||
Namespace string |
||||
Reserved string |
||||
HashAlgorithm string |
||||
Hash string |
||||
} |
||||
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L34
|
||||
type WrappedSig struct { |
||||
MagicHeader [6]byte |
||||
Version uint32 |
||||
PublicKey string |
||||
Namespace string |
||||
Reserved string |
||||
HashAlgorithm string |
||||
Signature string |
||||
} |
||||
|
||||
const ( |
||||
magicHeader = "SSHSIG" |
||||
defaultHashAlgorithm = "sha512" |
||||
) |
||||
|
||||
var supportedHashAlgorithms = map[string]func() hash.Hash{ |
||||
"sha256": sha256.New, |
||||
"sha512": sha512.New, |
||||
} |
||||
|
||||
func wrapData(m io.Reader, ns string) ([]byte, error) { |
||||
hf := sha512.New() |
||||
if _, err := io.Copy(hf, m); err != nil { |
||||
return nil, err |
||||
} |
||||
mh := hf.Sum(nil) |
||||
|
||||
sp := MessageWrapper{ |
||||
Namespace: ns, |
||||
HashAlgorithm: defaultHashAlgorithm, |
||||
Hash: string(mh), |
||||
} |
||||
|
||||
dataMessageWrapper := ssh.Marshal(sp) |
||||
dataMessageWrapper = append([]byte(magicHeader), dataMessageWrapper...) |
||||
|
||||
return dataMessageWrapper, nil |
||||
} |
||||
|
||||
func sign(s ssh.AlgorithmSigner, m io.Reader, ns string) (*ssh.Signature, error) { |
||||
dataMessageWrapper, err := wrapData(m, ns) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// ssh-rsa is not supported for RSA keys:
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L71
|
||||
// We can use the default value of "" for other key types though.
|
||||
algo := "" |
||||
if s.PublicKey().Type() == ssh.KeyAlgoRSA { |
||||
algo = ssh.SigAlgoRSASHA2512 |
||||
} |
||||
|
||||
return s.SignWithAlgorithm(rand.Reader, dataMessageWrapper, algo) |
||||
} |
||||
|
||||
func signAgent(pk ssh.PublicKey, ag agent.Agent, m io.Reader, ns string) (*ssh.Signature, error) { |
||||
dataMessageWrapper, err := wrapData(m, ns) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var sigFlag agent.SignatureFlags |
||||
if pk.Type() == ssh.KeyAlgoRSA { |
||||
sigFlag = agent.SignatureFlagRsaSha512 |
||||
} |
||||
|
||||
agExt, ok := ag.(agent.ExtendedAgent) |
||||
if !ok { |
||||
return nil, errors.New("couldn't cast to ExtendedAgent") |
||||
} |
||||
|
||||
return agExt.SignWithFlags(pk, dataMessageWrapper, sigFlag) |
||||
} |
||||
|
||||
// SignWithAgent asks the ssh Agent to sign the data with the signer matching the given publicKey and returns an armored signature.
|
||||
// The purpose of the namespace value is to specify a unambiguous
|
||||
// interpretation domain for the signature, e.g. file signing.
|
||||
// This prevents cross-protocol attacks caused by signatures
|
||||
// intended for one intended domain being accepted in another.
|
||||
// If empty, the default is "file".
|
||||
// This can be compared with `ssh-keygen -Y sign -f keyfile -n namespace data`
|
||||
func SignWithAgent(publicKey []byte, ag agent.Agent, data io.Reader, namespace string) ([]byte, error) { |
||||
pk, _, _, _, err := ssh.ParseAuthorizedKey(publicKey) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if namespace == "" { |
||||
namespace = defaultNamespace |
||||
} |
||||
|
||||
sig, err := signAgent(pk, ag, data, namespace) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
armored := Armor(sig, pk, namespace) |
||||
return armored, nil |
||||
} |
||||
|
||||
// Sign signs the data with the given private key in PEM format and returns an armored signature.
|
||||
// The purpose of the namespace value is to specify a unambiguous
|
||||
// interpretation domain for the signature, e.g. file signing.
|
||||
// This prevents cross-protocol attacks caused by signatures
|
||||
// intended for one intended domain being accepted in another.
|
||||
// If empty, the default is "file".
|
||||
// This can be compared with `ssh-keygen -Y sign -f keyfile -n namespace data`
|
||||
func Sign(pemBytes []byte, data io.Reader, namespace string) ([]byte, error) { |
||||
s, err := ssh.ParsePrivateKey(pemBytes) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
as, ok := s.(ssh.AlgorithmSigner) |
||||
if !ok { |
||||
return nil, err |
||||
} |
||||
|
||||
if namespace == "" { |
||||
namespace = defaultNamespace |
||||
} |
||||
|
||||
sig, err := sign(as, data, namespace) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
armored := Armor(sig, s.PublicKey(), namespace) |
||||
return armored, nil |
||||
} |
@ -0,0 +1,29 @@ |
||||
// modified by 42wim
|
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sshsig |
||||
|
||||
import ( |
||||
"golang.org/x/crypto/ssh" |
||||
) |
||||
|
||||
type Signature struct { |
||||
signature *ssh.Signature |
||||
pk ssh.PublicKey |
||||
hashAlg string |
||||
} |
||||
|
||||
const defaultNamespace = "file" |
@ -0,0 +1,56 @@ |
||||
//
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sshsig |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"golang.org/x/crypto/ssh" |
||||
) |
||||
|
||||
// Verify verifies the signature of the given data and the armored signature using the given public key and the namespace.
|
||||
// If the namespace is empty, the default namespace (file) is used.
|
||||
func Verify(message io.Reader, armoredSignature []byte, publicKey []byte, namespace string) error { |
||||
if namespace == "" { |
||||
namespace = defaultNamespace |
||||
} |
||||
|
||||
decodedSignature, err := Decode(armoredSignature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
desiredPk, _, _, _, err := ssh.ParseAuthorizedKey(publicKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Hash the message so we can verify it against the signature.
|
||||
h := supportedHashAlgorithms[decodedSignature.hashAlg]() |
||||
if _, err := io.Copy(h, message); err != nil { |
||||
return err |
||||
} |
||||
hm := h.Sum(nil) |
||||
|
||||
toVerify := MessageWrapper{ |
||||
Namespace: namespace, |
||||
HashAlgorithm: decodedSignature.hashAlg, |
||||
Hash: string(hm), |
||||
} |
||||
signedMessage := ssh.Marshal(toVerify) |
||||
signedMessage = append([]byte(magicHeader), signedMessage...) |
||||
return desiredPk.Verify(signedMessage, decodedSignature.signature) |
||||
} |
@ -0,0 +1,14 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package idna |
||||
|
||||
// Transitional processing is disabled by default in Go 1.18.
|
||||
// https://golang.org/issue/47510
|
||||
const transitionalLookup = false |
@ -0,0 +1,12 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
package idna |
||||
|
||||
const transitionalLookup = true |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@ |
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package unix |
||||
|
||||
import "runtime" |
||||
|
||||
// SysvShmCtl performs control operations on the shared memory segment
|
||||
// specified by id.
|
||||
func SysvShmCtl(id, cmd int, desc *SysvShmDesc) (result int, err error) { |
||||
if runtime.GOARCH == "arm" || |
||||
runtime.GOARCH == "mips64" || runtime.GOARCH == "mips64le" { |
||||
cmd |= ipc_64 |
||||
} |
||||
|
||||
return shmctl(id, cmd, desc) |
||||
} |
@ -0,0 +1,61 @@ |
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (darwin && !ios) || linux
|
||||
// +build darwin,!ios linux
|
||||
|
||||
package unix |
||||
|
||||
import ( |
||||
"unsafe" |
||||
|
||||
"golang.org/x/sys/internal/unsafeheader" |
||||
) |
||||
|
||||
// SysvShmAttach attaches the Sysv shared memory segment associated with the
|
||||
// shared memory identifier id.
|
||||
func SysvShmAttach(id int, addr uintptr, flag int) ([]byte, error) { |
||||
addr, errno := shmat(id, addr, flag) |
||||
if errno != nil { |
||||
return nil, errno |
||||
} |
||||
|
||||
// Retrieve the size of the shared memory to enable slice creation
|
||||
var info SysvShmDesc |
||||
|
||||
_, err := SysvShmCtl(id, IPC_STAT, &info) |
||||
if err != nil { |
||||
// release the shared memory if we can't find the size
|
||||
|
||||
// ignoring error from shmdt as there's nothing sensible to return here
|
||||
shmdt(addr) |
||||
return nil, err |
||||
} |
||||
|
||||
// Use unsafe to convert addr into a []byte.
|
||||
// TODO: convert to unsafe.Slice once we can assume Go 1.17
|
||||
var b []byte |
||||
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b)) |
||||
hdr.Data = unsafe.Pointer(addr) |
||||
hdr.Cap = int(info.Segsz) |
||||
hdr.Len = int(info.Segsz) |
||||
return b, nil |
||||
} |
||||
|
||||
// SysvShmDetach unmaps the shared memory slice returned from SysvShmAttach.
|
||||
//
|
||||
// It is not safe to use the slice after calling this function.
|
||||
func SysvShmDetach(data []byte) error { |
||||
if len(data) == 0 { |
||||
return EINVAL |
||||
} |
||||
|
||||
return shmdt(uintptr(unsafe.Pointer(&data[0]))) |
||||
} |
||||
|
||||
// SysvShmGet returns the Sysv shared memory identifier associated with key.
|
||||
// If the IPC_CREAT flag is specified a new segment is created.
|
||||
func SysvShmGet(key, size, flag int) (id int, err error) { |
||||
return shmget(key, size, flag) |
||||
} |
@ -0,0 +1,14 @@ |
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && !ios
|
||||
// +build darwin,!ios
|
||||
|
||||
package unix |
||||
|
||||
// SysvShmCtl performs control operations on the shared memory segment
|
||||
// specified by id.
|
||||
func SysvShmCtl(id, cmd int, desc *SysvShmDesc) (result int, err error) { |
||||
return shmctl(id, cmd, desc) |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue