Adding upstream version 0.0~git20250502.5100632.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
bdc5bee1be
commit
879cf0a009
16 changed files with 1453 additions and 0 deletions
201
LICENSE
Normal file
201
LICENSE
Normal file
|
@ -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.
|
82
README.md
Normal file
82
README.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Armored ssh signatures in go
|
||||
|
||||
[](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()
|
||||
}
|
||||
```
|
11
doc.go
Normal file
11
doc.go
Normal file
|
@ -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
|
95
encode.go
Normal file
95
encode.go
Normal file
|
@ -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
|
||||
}
|
60
example_test.go
Normal file
60
example_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package sshsig_test
|
||||
|
||||
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-----
|
||||
}
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
|||
module github.com/42wim/sshsig
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require golang.org/x/crypto v0.37.0
|
||||
|
||||
require golang.org/x/sys v0.32.0 // indirect
|
6
go.sum
Normal file
6
go.sum
Normal file
|
@ -0,0 +1,6 @@
|
|||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
346
pem/encode.go
Normal file
346
pem/encode.go
Normal file
|
@ -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()
|
||||
}
|
169
sign.go
Normal file
169
sign.go
Normal file
|
@ -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
|
||||
}
|
332
sign_test.go
Normal file
332
sign_test.go
Normal file
|
@ -0,0 +1,332 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
// Generated with "ssh-keygen -C test@rekor.dev -f id_rsa"
|
||||
sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
sshPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXofkiahE7uavjWvxnwkUF27qMgz7pdTwzSv0XzVG6EtirOv3PDWct4YKoXE9c0EqbxnIfYEKwEextdvB7zkgwczdJSHxf/18jQumLn/FuoCmugVSk1H5Qli/qzwBpaTnOk3WuakGuoYUl8ZAokKKgOKLA0aZJ1WRQ2ZCZggA3EkwNZiY17y9Q6HqdgQcH6XN8aAMADNVJdMAJb33hSRJjjsAPTmzBTishP8lYDoGRSsSE7/8XRBCEV5E4I8mI9GElcZwV/1KJx98mpH8QvMzXM1idFcwPRtt1NTAOshwgUU0Fu1x8lU5RQIa6ZKW36qNQLvLxy/BscC7B/mdLptoDs/ot9NimUXZcgCR1a2Q3o7Wi6jIgcgJcyV10Nba81ol4RdN4qPHnVZIzuo+dBkqwG3CMtB4Rj84+Qi+7zyU01hIPreoxQDXaayiGPBUUIiAlW9gsiuRWJzNnu3cvuWDLVfQIkjh7Wug58z+v2NOJ7IMdyERillhzDcvVHaq14+U= test@rekor.dev
|
||||
`
|
||||
// Generated with "ssh-keygen -C other-test@rekor.dev -f id_rsa"
|
||||
otherSSHPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
otherSSHPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDD9YJJYL1MS8JA7A75PrwS81rc5Ii/VjT6xPy5K/KM/IQ7T6nS3wKD/PLru87GJClTmM3owi2NcCMyZ4KbwYoy3qCT5wOhoB6/9l+O7NIvtFrITGmaEGV6HZfcYouSRcX0AEU1yGVOpIs5mISMOg2lsW/XopPWwToKpbwOPFdCRCT0krrmEsf4HF5Yw0IQlVoKZrhfThomYLvMkCLnIZ55PRIpWoyiFq8X3Q7peJgUJAe7Bc8/Id+hyqC52ZeejPP7oPprEkpkzBCw2ndYq6Y6OXNafEEIIHWXaM3pFqDxonbbvuIwVdHCNMv/yNoSxbgqTKwN/QaNXb+KpuvSrlvRqsNhu/sKsYFH64fTAbP9miDXHmJkA05uFlQukOstUmJ0QxzbsdcFvs8yw0PLsEZhEHXJzR3TLzenyZSq86VZICvGfVacBk7TikCBOtyWuESHhlc6SfZKfzZ67cOlDyKeSiVjgh+eEh9s4h56ahQ2rW05Sq6GjD0YtEzog2J4csE= other-test@rekor.dev
|
||||
`
|
||||
|
||||
// Generated with ssh-keygen -C test@rekor.dev -t ed25519 -f id_ed25519
|
||||
ed25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQAAAJgyRa3cMkWt
|
||||
3AAAAAtzc2gtZWQyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQ
|
||||
AAAED7y4N/DsVnRQiBZNxEWdsJ9RmbranvtQ3X9jnb6gFed0HjnNEfE88W1pvBLdV3otv2
|
||||
8x760gdmPao3lVD5uAt9AAAADnRlc3RAcmVrb3IuZGV2AQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`
|
||||
ed25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHjnNEfE88W1pvBLdV3otv28x760gdmPao3lVD5uAt9 test@rekor.dev
|
||||
`
|
||||
)
|
||||
|
||||
func TestFromOpenSSH(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
pub string
|
||||
priv string
|
||||
}{
|
||||
{
|
||||
name: "rsa",
|
||||
pub: sshPublicKey,
|
||||
priv: sshPrivateKey,
|
||||
},
|
||||
{
|
||||
name: "ed25519",
|
||||
pub: ed25519PublicKey,
|
||||
priv: ed25519PrivateKey,
|
||||
},
|
||||
} {
|
||||
if _, err := exec.LookPath("ssh-keygen"); err != nil {
|
||||
t.Skip("skip TestFromOpenSSH: missing ssh-keygen in PATH")
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt := tt
|
||||
|
||||
// Test that a signature from the cli can validate here.
|
||||
td := t.TempDir()
|
||||
|
||||
data := []byte("hello, ssh world")
|
||||
dataPath := write(t, []byte(data), td, "data")
|
||||
|
||||
privPath := write(t, []byte(tt.priv), td, "id")
|
||||
write(t, []byte(tt.pub), td, "id.pub")
|
||||
|
||||
sigPath := dataPath + ".sig"
|
||||
run(t, nil, "ssh-keygen", "-Y", "sign", "-n", "file", "-f", privPath, dataPath)
|
||||
|
||||
sigBytes, err := ioutil.ReadFile(sigPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Verify(bytes.NewReader(data), sigBytes, []byte(tt.pub), "file"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// It should not verify if we check against another public key
|
||||
if err := Verify(bytes.NewReader(data), sigBytes, []byte(otherSSHPublicKey), "file"); err == nil {
|
||||
t.Error("expected error with incorrect key")
|
||||
}
|
||||
|
||||
// It should not verify if the data is tampered
|
||||
if err := Verify(strings.NewReader("bad data"), sigBytes, []byte(sshPublicKey), "file"); err == nil {
|
||||
t.Error("expected error with incorrect data")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToOpenSSH(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
pub string
|
||||
priv string
|
||||
}{
|
||||
{
|
||||
name: "rsa",
|
||||
pub: sshPublicKey,
|
||||
priv: sshPrivateKey,
|
||||
},
|
||||
{
|
||||
name: "ed25519",
|
||||
pub: ed25519PublicKey,
|
||||
priv: ed25519PrivateKey,
|
||||
},
|
||||
} {
|
||||
if _, err := exec.LookPath("ssh-keygen"); err != nil {
|
||||
t.Skip("skip TestToOpenSSH: missing ssh-keygen in PATH")
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt := tt
|
||||
// Test that a signature from here can validate in the CLI.
|
||||
td := t.TempDir()
|
||||
|
||||
data := []byte("hello, ssh world")
|
||||
write(t, []byte(data), td, "data")
|
||||
|
||||
armored, err := Sign([]byte(tt.priv), bytes.NewReader(data), "file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sigPath := write(t, []byte(armored), td, "oursig")
|
||||
|
||||
// Create an allowed_signers file with two keys to check against.
|
||||
allowedSigner := "test@rekor.dev " + tt.pub + "\n"
|
||||
allowedSigner += "othertest@rekor.dev " + otherSSHPublicKey + "\n"
|
||||
allowedSigners := write(t, []byte(allowedSigner), td, "allowed_signer")
|
||||
|
||||
// We use the correct principal here so it should work.
|
||||
run(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
|
||||
"-I", "test@rekor.dev", "-n", "file", "-s", sigPath)
|
||||
|
||||
// Just to be sure, check against the other public key as well.
|
||||
runErr(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
|
||||
"-I", "othertest@rekor.dev", "-n", "file", "-s", sigPath)
|
||||
|
||||
// It should error if we run it against other data
|
||||
data = []byte("other data!")
|
||||
runErr(t, data, "ssh-keygen", "-Y", "check-novalidate", "-n", "file", "-s", sigPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
data := []byte("my good data to be signed!")
|
||||
|
||||
// Create one extra signature for all the tests.
|
||||
otherSig, err := Sign([]byte(otherSSHPrivateKey), bytes.NewReader(data), "file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
pub string
|
||||
priv string
|
||||
}{
|
||||
{
|
||||
name: "rsa",
|
||||
pub: sshPublicKey,
|
||||
priv: sshPrivateKey,
|
||||
},
|
||||
{
|
||||
name: "ed25519",
|
||||
pub: ed25519PublicKey,
|
||||
priv: ed25519PrivateKey,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt := tt
|
||||
sig, err := Sign([]byte(tt.priv), bytes.NewReader(data), "file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the signature against that data and public key
|
||||
if err := Verify(bytes.NewReader(data), sig, []byte(tt.pub), "file"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Now check it against invalid data.
|
||||
if err := Verify(strings.NewReader("invalid data!"), sig, []byte(tt.pub), "file"); err == nil {
|
||||
t.Error("expected error!")
|
||||
}
|
||||
|
||||
// Now check it against the wrong key.
|
||||
if err := Verify(bytes.NewReader(data), sig, []byte(otherSSHPublicKey), "file"); err == nil {
|
||||
t.Error("expected error!")
|
||||
}
|
||||
|
||||
// Now check it against an invalid signature data.
|
||||
if err := Verify(bytes.NewReader(data), []byte("invalid signature!"), []byte(tt.pub), "file"); err == nil {
|
||||
t.Error("expected error!")
|
||||
}
|
||||
|
||||
// Once more, use the wrong signature and check it against the original (wrong public key)
|
||||
if err := Verify(bytes.NewReader(data), otherSig, []byte(tt.pub), "file"); err == nil {
|
||||
t.Error("expected error!")
|
||||
}
|
||||
// It should work against the correct public key.
|
||||
if err := Verify(bytes.NewReader(data), otherSig, []byte(otherSSHPublicKey), "file"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func write(t *testing.T, d []byte, fp ...string) string {
|
||||
p := filepath.Join(fp...)
|
||||
if err := ioutil.WriteFile(p, d, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func run(t *testing.T, stdin []byte, args ...string) {
|
||||
t.Helper()
|
||||
/* #nosec */
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = bytes.NewReader(stdin)
|
||||
out, err := cmd.CombinedOutput()
|
||||
t.Logf("cmd %v: %s", cmd, string(out))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runErr(t *testing.T, stdin []byte, args ...string) {
|
||||
t.Helper()
|
||||
/* #nosec */
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = bytes.NewReader(stdin)
|
||||
out, err := cmd.CombinedOutput()
|
||||
t.Logf("cmd %v: %s", cmd, string(out))
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
29
sshsig.go
Normal file
29
sshsig.go
Normal file
|
@ -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"
|
1
testdata/hello_world.txt
vendored
Normal file
1
testdata/hello_world.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello, ssh world!
|
19
testdata/hello_world.txt.sig
vendored
Normal file
19
testdata/hello_world.txt.sig
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAM2JmobMfIz+/RFT8RWbF4
|
||||
x69qm8kgPr9ly8cGLGMDzJn4WbTyk141VlXRdogpPWEj78nHsmhijgp14rfJ/tvgwCjl3B
|
||||
MkvTPQfCH5M0tlbpy2LUdo1JP79KOAyssKob93iGPPBnOvg8YlX3HhGi6XZuXovN9m1CG7
|
||||
mons0VYyeaL7/1Vj4iQGBkXH41jVG+rUZC5xYY6lPMzgxHmsljzRiX99rQoAZOwPjVk8Hu
|
||||
Db7qNkjVlAaZ7kftG7/Q40NqLchsw1Q/OYlQqrdSb+rp43YTOdPpR9eOUbasOkG32/GDz7
|
||||
ylS7Q7+7OltRNTakOkFSc/h91h1hcf7/YDreOUp9UKTX7EGdCmjArnHaXnKuZRZ6GgtGsZ
|
||||
aeOKJtgW6Xcaff8ydY4ZdJvQTzlhMPoXuOCKoyDkbKmbkwEAn8D1n9lMUsNmANZsCGi9al
|
||||
jQbxaWbFQ/HeUgiU5U813GwHSe5NPDqV3zYtVqEJqWMGIOhUriYIgp+wayFvx3DIXp7kEG
|
||||
awAAAARmaWxlAAAAAAAAAAZzaGE1MTIAAAGUAAAADHJzYS1zaGEyLTUxMgAAAYCKb6hW7f
|
||||
e+oN2hTje0ymbQbm6RCxvUID+qxA3uXpGaHXXOh2JDkjqTZE0aCbFIhrjLKsl5L4eu2Upn
|
||||
OmJf1og4Wi4qpMaFu1QS5j5OMzimJl/60iP0GklpQvfj/BaP2S2NoelTiHvZcbOUxtwVwX
|
||||
/ya5+MdB21mriZDXL8krEnx9XqHMga6ReCUJ1wN/IeA30xWg9k7acoR9q/UfaTauwbEa67
|
||||
upCfAWLSlQxfTd5LZpw2Re695vXRZnKT3Xf7vXVPhLSLAZBn2zHm+Mk++oy5VGAxjf8+o1
|
||||
yYSGv6xpiBynenZv50WffLyaZ1X1YtZ/WBiiz7lSdeulZAR3iDSWJYUDo7kREb68trygjV
|
||||
a68kEamlIMCDOo2wbWk8Z+yoYyT6qp9EbiWBuqexFonAnvabxOp3kQ4bGDeXkcjIyauhtZ
|
||||
Y92S+qadtwmi+4cLzo/yVEvIeNsA7NOGSpVI+fdOW45R8E1nQLnf8LczXRESVgv//9KMtx
|
||||
QM1YRBRG6BaBhg8=
|
||||
-----END SSH SIGNATURE-----
|
38
testdata/id_rsa
vendored
Normal file
38
testdata/id_rsa
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
1
testdata/id_rsa.pub
vendored
Normal file
1
testdata/id_rsa.pub
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDNiZqGzHyM/v0RU/EVmxeMevapvJID6/ZcvHBixjA8yZ+Fm08pNeNVZV0XaIKT1hI+/Jx7JoYo4KdeK3yf7b4MAo5dwTJL0z0Hwh+TNLZW6cti1HaNST+/SjgMrLCqG/d4hjzwZzr4PGJV9x4Roul2bl6LzfZtQhu5qJ7NFWMnmi+/9VY+IkBgZFx+NY1Rvq1GQucWGOpTzM4MR5rJY80Yl/fa0KAGTsD41ZPB7g2+6jZI1ZQGme5H7Ru/0ONDai3IbMNUPzmJUKq3Um/q6eN2EznT6UfXjlG2rDpBt9vxg8+8pUu0O/uzpbUTU2pDpBUnP4fdYdYXH+/2A63jlKfVCk1+xBnQpowK5x2l5yrmUWehoLRrGWnjiibYFul3Gn3/MnWOGXSb0E85YTD6F7jgiqMg5Gypm5MBAJ/A9Z/ZTFLDZgDWbAhovWpY0G8WlmxUPx3lIIlOVPNdxsB0nuTTw6ld82LVahCaljBiDoVK4mCIKfsGshb8dwyF6e5BBms= test@rekor.dev
|
56
verify.go
Normal file
56
verify.go
Normal 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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue