From 84cf7fd955912c68f8ebc7e6e7a42e89ea78fd1b Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 25 Dec 2021 23:51:17 +0100 Subject: [PATCH 1/9] Use SHA256 for SSH signing When using the SSH Agent signers we need to use a wrapper to actually allow signing with SHA256. --- algorithms.go | 49 +++++++++++++++++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 19 +++++++++++-------- httpsig.go | 3 +-- 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/algorithms.go b/algorithms.go index 9595941..b30bb0a 100644 --- a/algorithms.go +++ b/algorithms.go @@ -68,9 +68,8 @@ var hashToDef = map[crypto.Hash]struct { // http://www.iana.org/assignments/signature-algorithms // // Note that the forbidden hashes have an invalid 'new' function. - crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, - crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, - // Temporarily enable SHA1 because of issue https://github.com/golang/go/issues/37278 + crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, + crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, crypto.SHA1: {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }}, crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, @@ -192,11 +191,49 @@ func (r *rsaAlgorithm) setSig(b []byte) error { return nil } +// Code from https://github.com/cloudtools/ssh-cert-authority/pull/49/files +// This interface provides a way to reach the exported, but not accessible SignWithOpts() method +// in x/crypto/ssh/agent. Access to this is needed to sign with more secure signing algorithms +type agentKeyringSigner interface { + SignWithOpts(rand io.Reader, data []byte, opts crypto.SignerOpts) (*ssh.Signature, error) +} + +// A struct to wrap an SSH Signer with one that will switch to SHA256 Signatures. +// Replaces the call to Sign() with a call to SignWithOpts using HashFunc() algorithm. +type Sha256Signer struct { + ssh.Signer +} + +func (s Sha256Signer) HashFunc() crypto.Hash { + return crypto.SHA256 +} + +func (s Sha256Signer) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + if aks, ok := s.Signer.(agentKeyringSigner); !ok { + return nil, fmt.Errorf("ssh: can't wrap a non ssh agentKeyringSigner") + } else { + return aks.SignWithOpts(rand, data, s) + } +} + func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { if r.sshSigner != nil { - sshsig, err := r.sshSigner.Sign(rand, sig) - if err != nil { - return nil, err + var ( + sshsig *ssh.Signature + err error + ) + // are we using an SSH Agent + if _, ok := r.sshSigner.(agentKeyringSigner); ok { + signer := Sha256Signer{r.sshSigner} + sshsig, err = signer.Sign(rand, sig) + if err != nil { + return nil, err + } + } else { + sshsig, err = r.sshSigner.(ssh.AlgorithmSigner).SignWithAlgorithm(rand, sig, ssh.SigAlgoRSASHA2256) + if err != nil { + return nil, err + } } return sshsig.Blob, nil diff --git a/go.mod b/go.mod index a407692..1fbe8d0 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/go-fed/httpsig -require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 +require golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 go 1.13 diff --git a/go.sum b/go.sum index 8b7d318..146b2ae 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/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 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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= diff --git a/httpsig.go b/httpsig.go index 17310c8..7735a4b 100644 --- a/httpsig.go +++ b/httpsig.go @@ -234,7 +234,7 @@ func getSSHAlgorithm(pkType string) Algorithm { case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): return ED25519 case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): - return RSA_SHA1 + return RSA_SHA256 } return "" @@ -324,7 +324,6 @@ func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, h } func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) { - var expires, created int64 = 0, 0 if expiresIn != 0 { created = time.Now().Unix() From 7f727c9cf9bfd82538abbc9b30251a34513460ee Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 26 Dec 2021 23:43:59 +0100 Subject: [PATCH 2/9] Move to own fork for now --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1fbe8d0..74913a9 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/go-fed/httpsig +module github.com/42wim/httpsig require golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 From c96a3436fa01562cf320bb58a469c7006ca85e50 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 28 Jan 2023 00:00:10 +0100 Subject: [PATCH 3/9] Update dependencies and modules --- README.md | 26 ++++++++++++++------------ go.mod | 6 ++++-- go.sum | 16 +++++----------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3d4cb06..7bd87ed 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # httpsig +Forked from + > HTTP Signatures made simple [![Build Status][Build-Status-Image]][Build-Status-Url] [![Go Reference][Go-Reference-Image]][Go-Reference-Url] [![Go Report Card][Go-Report-Card-Image]][Go-Report-Card-Url] [![License][License-Image]][License-Url] [![Chat][Chat-Image]][Chat-Url] [![OpenCollective][OpenCollective-Image]][OpenCollective-Url] -`go get github.com/go-fed/httpsig` +`go get github.com/42wim/httpsig` Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). @@ -23,7 +25,7 @@ signing of hash schemes. Its goals are: ## How to use -`import "github.com/go-fed/httpsig"` +`import "github.com/42wim/httpsig"` ### Signing @@ -99,15 +101,15 @@ func verify(r *http.Request) error { constructed on a per-request or per-response basis it should not be a common restriction. -[Build-Status-Image]: https://travis-ci.org/go-fed/httpsig.svg?branch=master -[Build-Status-Url]: https://travis-ci.org/go-fed/httpsig -[Go-Reference-Image]: https://pkg.go.dev/badge/github.com/go-fed/httpsig -[Go-Reference-Url]: https://pkg.go.dev/github.com/go-fed/httpsig -[Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/go-fed/httpsig -[Go-Report-Card-Url]: https://goreportcard.com/report/github.com/go-fed/httpsig -[License-Image]: https://img.shields.io/github/license/go-fed/httpsig?color=blue +[Build-Status-Image]: https://travis-ci.org/42wim/httpsig.svg?branch=master +[Build-Status-Url]: https://travis-ci.org/42wim/httpsig +[Go-Reference-Image]: https://pkg.go.dev/badge/github.com/42wim/httpsig +[Go-Reference-Url]: https://pkg.go.dev/github.com/42wim/httpsig +[Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/42wim/httpsig +[Go-Report-Card-Url]: https://goreportcard.com/report/github.com/42wim/httpsig +[License-Image]: https://img.shields.io/github/license/42wim/httpsig?color=blue [License-Url]: https://opensource.org/licenses/BSD-3-Clause -[Chat-Image]: https://img.shields.io/matrix/go-fed:feneas.org?server_fqdn=matrix.org +[Chat-Image]: https://img.shields.io/matrix/42wim:feneas.org?server_fqdn=matrix.org [Chat-Url]: https://matrix.to/#/!BLOSvIyKTDLIVjRKSc:feneas.org?via=feneas.org&via=matrix.org -[OpenCollective-Image]: https://img.shields.io/opencollective/backers/go-fed-activitypub-labs -[OpenCollective-Url]: https://opencollective.com/go-fed-activitypub-labs +[OpenCollective-Image]: https://img.shields.io/opencollective/backers/42wim-activitypub-labs +[OpenCollective-Url]: https://opencollective.com/42wim-activitypub-labs diff --git a/go.mod b/go.mod index 74913a9..7b31841 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/42wim/httpsig -require golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 +require golang.org/x/crypto v0.5.0 -go 1.13 +require golang.org/x/sys v0.4.0 // indirect + +go 1.18 diff --git a/go.sum b/go.sum index 146b2ae..9a780d0 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,5 @@ -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/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 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= From 553afe30818a46bfbfc913f44c9ff8c2849f3d0c Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 17 Aug 2024 23:35:09 +0200 Subject: [PATCH 4/9] Remove legacy/broken crypto --- algorithms.go | 22 -------------- algorithms_test.go | 75 ---------------------------------------------- httpsig.go | 18 +++++------ 3 files changed, 7 insertions(+), 108 deletions(-) diff --git a/algorithms.go b/algorithms.go index b30bb0a..8972ddc 100644 --- a/algorithms.go +++ b/algorithms.go @@ -5,7 +5,6 @@ import ( "crypto/ecdsa" "crypto/hmac" "crypto/rsa" - "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/subtle" // Use should trigger great care @@ -20,7 +19,6 @@ import ( "golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2s" "golang.org/x/crypto/ed25519" - "golang.org/x/crypto/ripemd160" "golang.org/x/crypto/sha3" "golang.org/x/crypto/ssh" ) @@ -31,15 +29,10 @@ const ( sshPrefix = "ssh" ecdsaPrefix = "ecdsa" ed25519Prefix = "ed25519" - md4String = "md4" - md5String = "md5" - sha1String = "sha1" sha224String = "sha224" sha256String = "sha256" sha384String = "sha384" sha512String = "sha512" - md5sha1String = "md5sha1" - ripemd160String = "ripemd160" sha3_224String = "sha3-224" sha3_256String = "sha3-256" sha3_384String = "sha3-384" @@ -68,15 +61,10 @@ var hashToDef = map[crypto.Hash]struct { // http://www.iana.org/assignments/signature-algorithms // // Note that the forbidden hashes have an invalid 'new' function. - crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, - crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, - crypto.SHA1: {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }}, crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }}, crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }}, - crypto.MD5SHA1: {md5sha1String, func(key []byte) (hash.Hash, error) { return nil, nil }}, - crypto.RIPEMD160: {ripemd160String, func(key []byte) (hash.Hash, error) { return ripemd160.New(), nil }}, crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }}, crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }}, crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }}, @@ -111,16 +99,6 @@ func init() { } func isForbiddenHash(h crypto.Hash) bool { - switch h { - // Not actually cryptographically secure - case crypto.MD4: - fallthrough - case crypto.MD5: - fallthrough - case crypto.MD5SHA1: // shorthand for crypto/tls, not actually implemented - return true - } - // Still cryptographically secure return false } diff --git a/algorithms_test.go b/algorithms_test.go index 5c3a3a3..3cc29b3 100644 --- a/algorithms_test.go +++ b/algorithms_test.go @@ -31,25 +31,6 @@ func TestIsAvailable(t *testing.T) { expected bool expectError bool }{ - { - name: md4String, - algo: md4String, - expected: false, - expectError: true, - }, - { - name: md5String, - algo: md5String, - expected: false, - expectError: true, - }, - { - // TODO: Disable once https://github.com/golang/go/issues/37278 is fixed - name: sha1String, - algo: sha1String, - expected: true, - expectError: false, - }, { name: sha224String, algo: sha224String, @@ -74,18 +55,6 @@ func TestIsAvailable(t *testing.T) { expected: true, expectError: false, }, - { - name: md5sha1String, - algo: md5sha1String, - expected: false, - expectError: true, - }, - { - name: ripemd160String, - algo: ripemd160String, - expected: true, - expectError: false, - }, { name: sha3_224String, algo: sha3_224String, @@ -189,11 +158,6 @@ func TestSignerFromString(t *testing.T) { input: HMAC_SHA512, expectError: true, }, - { - name: "HMAC_RIPEMD160", - input: HMAC_RIPEMD160, - expectError: true, - }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, @@ -284,11 +248,6 @@ func TestSignerFromString(t *testing.T) { input: RSA_SHA512, expectKind: crypto.SHA512, }, - { - name: "RSA_RIPEMD160", - input: RSA_RIPEMD160, - expectKind: crypto.RIPEMD160, - }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, @@ -388,11 +347,6 @@ func TestMACerFromString(t *testing.T) { input: HMAC_SHA512, expectKind: crypto.SHA512, }, - { - name: "HMAC_RIPEMD160", - input: HMAC_RIPEMD160, - expectKind: crypto.RIPEMD160, - }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, @@ -483,11 +437,6 @@ func TestMACerFromString(t *testing.T) { input: RSA_SHA512, expectError: true, }, - { - name: "RSA_RIPEMD160", - input: RSA_RIPEMD160, - expectError: true, - }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, @@ -587,11 +536,6 @@ func TestSignerSigns(t *testing.T) { input: RSA_SHA512, inputCryptoHash: crypto.SHA512, }, - { - name: "RSA_RIPEMD160", - input: RSA_RIPEMD160, - inputCryptoHash: crypto.RIPEMD160, - }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, @@ -738,11 +682,6 @@ func TestSignerVerifies(t *testing.T) { input: RSA_SHA512, inputCryptoHash: crypto.SHA512, }, - { - name: "RSA_RIPEMD160", - input: RSA_RIPEMD160, - inputCryptoHash: crypto.RIPEMD160, - }, } for _, test := range tests { privKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -817,13 +756,6 @@ func TestMACerSigns(t *testing.T) { isHMAC: true, keySize: 128, }, - { - name: "HMAC_RIPEMD160", - input: HMAC_RIPEMD160, - inputCryptoHash: crypto.RIPEMD160, - isHMAC: true, - keySize: 128, - }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, @@ -1016,13 +948,6 @@ func TestMACerEquals(t *testing.T) { isHMAC: true, keySize: 128, }, - { - name: "HMAC_RIPEMD160", - input: HMAC_RIPEMD160, - inputCryptoHash: crypto.RIPEMD160, - isHMAC: true, - keySize: 128, - }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, diff --git a/httpsig.go b/httpsig.go index 7735a4b..c9c38e8 100644 --- a/httpsig.go +++ b/httpsig.go @@ -27,7 +27,6 @@ const ( HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String - HMAC_RIPEMD160 Algorithm = hmacPrefix + "-" + ripemd160String HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String @@ -43,19 +42,16 @@ const ( BLAKE2B_384 Algorithm = blake2b_384String BLAKE2B_512 Algorithm = blake2b_512String // RSA-based algorithms. - RSA_SHA1 Algorithm = rsaPrefix + "-" + sha1String RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String // RSA_SHA256 is the default algorithm. - RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String - RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String - RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String - RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String + RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String + RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String + RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String // ECDSA algorithms - ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String - ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String - ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String - ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String - ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String + ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String + ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String + ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String + ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String // ED25519 algorithms // can only be SHA512 ED25519 Algorithm = ed25519Prefix From 426da5f6b5a085c1688ce7afe9942cbe785cd452 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 17 Aug 2024 23:36:04 +0200 Subject: [PATCH 5/9] Update dependencies --- go.mod | 4 ++-- go.sum | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7b31841..40cc0f9 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/42wim/httpsig -require golang.org/x/crypto v0.5.0 +require golang.org/x/crypto v0.26.0 -require golang.org/x/sys v0.4.0 // indirect +require golang.org/x/sys v0.23.0 // indirect go 1.18 diff --git a/go.sum b/go.sum index 9a780d0..6787297 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= From 5d808adf01f9be62e3d2123684a33c7c172120a2 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 17 Aug 2024 23:40:51 +0200 Subject: [PATCH 6/9] algorithms: fix dropped error (#1) Co-authored-by: Lars Lehtonen --- algorithms.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/algorithms.go b/algorithms.go index 8972ddc..4e5e191 100644 --- a/algorithms.go +++ b/algorithms.go @@ -125,6 +125,9 @@ type hmacAlgorithm struct { func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { hs, err := h.fn(key) + if err != nil { + return nil, err + } if err = setSig(hs, sig); err != nil { return nil, err } From 747b79bd243d47dfd1bac1fee17f92e770a6c148 Mon Sep 17 00:00:00 2001 From: Wim Date: Fri, 2 May 2025 17:45:51 +0200 Subject: [PATCH 7/9] Update dependencies --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 40cc0f9..290983c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/42wim/httpsig -require golang.org/x/crypto v0.26.0 +require golang.org/x/crypto v0.37.0 -require golang.org/x/sys v0.23.0 // indirect +require golang.org/x/sys v0.32.0 // indirect -go 1.18 +go 1.23.0 diff --git a/go.sum b/go.sum index 6787297..bf505dd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +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= From 61d48953018d4609884871bc42407ae32bd1cf8b Mon Sep 17 00:00:00 2001 From: Wim Date: Tue, 17 Mar 2026 20:49:33 +0100 Subject: [PATCH 8/9] Update dependencies --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 290983c..13a5cb4 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/42wim/httpsig -require golang.org/x/crypto v0.37.0 +require golang.org/x/crypto v0.49.0 -require golang.org/x/sys v0.32.0 // indirect +require golang.org/x/sys v0.42.0 // indirect -go 1.23.0 +go 1.25.0 diff --git a/go.sum b/go.sum index bf505dd..e7defb9 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +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= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= From 5498e38e7963cf06444b26f3f7160e76f3ca3d47 Mon Sep 17 00:00:00 2001 From: Wim Date: Thu, 1 Oct 2026 17:00:47 +0200 Subject: [PATCH 9/9] Add support for ecdsa-sha2-nistp256 --- algorithms.go | 29 +++++++- algorithms_test.go | 126 +++++++++++++++++++++++++++-------- go.mod | 6 +- go.sum | 5 ++ httpsig.go | 7 +- httpsig_test.go | 161 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 302 insertions(+), 32 deletions(-) diff --git a/algorithms.go b/algorithms.go index 4e5e191..2743eed 100644 --- a/algorithms.go +++ b/algorithms.go @@ -290,7 +290,8 @@ var _ signer = &ecdsaAlgorithm{} type ecdsaAlgorithm struct { hash.Hash - kind crypto.Hash + kind crypto.Hash + sshSigner ssh.Signer } func (r *ecdsaAlgorithm) setSig(b []byte) error { @@ -310,6 +311,22 @@ type ECDSASignature struct { } func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + if r.sshSigner != nil { + sshsig, err := r.sshSigner.Sign(rand, sig) + if err != nil { + return nil, err + } + // SSH wraps the (r, s) pair as two mpints, convert it to the ASN.1 + // encoding expected by Verify. + var blob struct { + R *big.Int + S *big.Int + } + if err := ssh.Unmarshal(sshsig.Blob, &blob); err != nil { + return nil, err + } + return asn1.Marshal(ECDSASignature{R: blob.R, S: blob.S}) + } defer r.Reset() if err := r.setSig(sig); err != nil { return nil, err @@ -460,6 +477,16 @@ func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { switch { + case strings.HasPrefix(s, ecdsaPrefix): + hash, cHash, err := newAlgorithm(strings.TrimPrefix(s, ecdsaPrefix+"-"), nil) + if err != nil { + return nil, err + } + return &ecdsaAlgorithm{ + Hash: hash, + kind: cHash, + sshSigner: sshSigner, + }, nil case strings.HasPrefix(s, rsaPrefix): return &rsaAlgorithm{ sshSigner: sshSigner, diff --git a/algorithms_test.go b/algorithms_test.go index 3cc29b3..1c54e11 100644 --- a/algorithms_test.go +++ b/algorithms_test.go @@ -2,6 +2,8 @@ package httpsig import ( "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/hmac" "crypto/rand" "crypto/rsa" @@ -298,6 +300,26 @@ func TestSignerFromString(t *testing.T) { input: rsa_BLAKE2B_512, expectKind: crypto.BLAKE2b_512, }, + { + name: "ECDSA_SHA224", + input: ECDSA_SHA224, + expectKind: crypto.SHA224, + }, + { + name: "ECDSA_SHA256", + input: ECDSA_SHA256, + expectKind: crypto.SHA256, + }, + { + name: "ECDSA_SHA384", + input: ECDSA_SHA384, + expectKind: crypto.SHA384, + }, + { + name: "ECDSA_SHA512", + input: ECDSA_SHA512, + expectKind: crypto.SHA512, + }, } for _, test := range tests { s, err := signerFromString(string(test.input)) @@ -511,9 +533,10 @@ func TestMACerFromString(t *testing.T) { func TestSignerSigns(t *testing.T) { tests := []struct { - name string - input Algorithm - inputCryptoHash crypto.Hash + name string + input Algorithm + inputCryptoHash crypto.Hash + // Only the BLAKE2 hashes lack a PKCS#1 v1.5 DigestInfo prefix. expectRSAUnsupported bool }{ { @@ -537,40 +560,34 @@ func TestSignerSigns(t *testing.T) { inputCryptoHash: crypto.SHA512, }, { - name: "rsa_SHA3_224", - input: rsa_SHA3_224, - inputCryptoHash: crypto.SHA3_224, - expectRSAUnsupported: true, + name: "rsa_SHA3_224", + input: rsa_SHA3_224, + inputCryptoHash: crypto.SHA3_224, }, { - name: "rsa_SHA3_256", - input: rsa_SHA3_256, - inputCryptoHash: crypto.SHA3_256, - expectRSAUnsupported: true, + name: "rsa_SHA3_256", + input: rsa_SHA3_256, + inputCryptoHash: crypto.SHA3_256, }, { - name: "rsa_SHA3_384", - input: rsa_SHA3_384, - inputCryptoHash: crypto.SHA3_384, - expectRSAUnsupported: true, + name: "rsa_SHA3_384", + input: rsa_SHA3_384, + inputCryptoHash: crypto.SHA3_384, }, { - name: "rsa_SHA3_512", - input: rsa_SHA3_512, - inputCryptoHash: crypto.SHA3_512, - expectRSAUnsupported: true, + name: "rsa_SHA3_512", + input: rsa_SHA3_512, + inputCryptoHash: crypto.SHA3_512, }, { - name: "rsa_SHA512_224", - input: rsa_SHA512_224, - inputCryptoHash: crypto.SHA512_224, - expectRSAUnsupported: true, + name: "rsa_SHA512_224", + input: rsa_SHA512_224, + inputCryptoHash: crypto.SHA512_224, }, { - name: "rsa_SHA512_256", - input: rsa_SHA512_256, - inputCryptoHash: crypto.SHA512_256, - expectRSAUnsupported: true, + name: "rsa_SHA512_256", + input: rsa_SHA512_256, + inputCryptoHash: crypto.SHA512_256, }, { name: "rsa_BLAKE2S_256", @@ -720,6 +737,61 @@ func TestSignerVerifies(t *testing.T) { } } +// ECDSA signatures are not deterministic, so sign and verify are tested as a +// round trip instead of against a fixed signature. +func TestECDSASignerSignsAndVerifies(t *testing.T) { + tests := []struct { + name string + input Algorithm + curve elliptic.Curve + }{ + { + name: "ECDSA_SHA224", + input: ECDSA_SHA224, + curve: elliptic.P256(), + }, + { + name: "ECDSA_SHA256", + input: ECDSA_SHA256, + curve: elliptic.P256(), + }, + { + name: "ECDSA_SHA384", + input: ECDSA_SHA384, + curve: elliptic.P384(), + }, + { + name: "ECDSA_SHA512", + input: ECDSA_SHA512, + curve: elliptic.P521(), + }, + } + for _, test := range tests { + privKey, err := ecdsa.GenerateKey(test.curve, rand.Reader) + if err != nil { + t.Fatalf("%q: Failed setup: %s", test.name, err) + } + toHash := make([]byte, 65535) + n, err := doNotUseInProdCode.Read(toHash) + if n != len(toHash) { + t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(toHash)) + } else if err != nil { + t.Fatalf("%q: Failed setup: %s", test.name, err) + } + s, err := signerFromString(string(test.input)) + if err != nil { + t.Fatalf("%q: %s", test.name, err) + } + signature, err := s.Sign(rand.Reader, privKey, toHash) + if err != nil { + t.Fatalf("%q: %s", test.name, err) + } + if err := s.Verify(privKey.Public(), toHash, signature); err != nil { + t.Fatalf("%q: %s", test.name, err) + } + } +} + func TestMACerSigns(t *testing.T) { tests := []struct { name string diff --git a/go.mod b/go.mod index 13a5cb4..ae100bb 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/42wim/httpsig -require golang.org/x/crypto v0.49.0 +require golang.org/x/crypto v0.57.0 -require golang.org/x/sys v0.42.0 // indirect +require golang.org/x/sys v0.48.0 // indirect -go 1.25.0 +go 1.26.0 diff --git a/go.sum b/go.sum index e7defb9..c1de29d 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,11 @@ golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= diff --git a/httpsig.go b/httpsig.go index c9c38e8..fcc4e0a 100644 --- a/httpsig.go +++ b/httpsig.go @@ -203,7 +203,8 @@ type SSHSigner interface { } // NewwSSHSigner creates a new Signer using the specified ssh.Signer -// At the moment only ed25519 ssh keys are supported. +// At the moment only ed25519, rsa and ecdsa-sha2-nistp256 ssh keys +// (including their OpenSSH certificate variants) are supported. // The headers specified will be included into the HTTP signatures. // // The Digest will also be calculated on a request's body using the provided @@ -231,6 +232,10 @@ func getSSHAlgorithm(pkType string) Algorithm { return ED25519 case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): return RSA_SHA256 + // Covers both ecdsa-sha2-nistp256 and + // ecdsa-sha2-nistp256-cert-v01@openssh.com. + case strings.HasPrefix(pkType, ssh.KeyAlgoECDSA256): + return ECDSA_SHA256 } return "" diff --git a/httpsig_test.go b/httpsig_test.go index dca48e7..118d26e 100644 --- a/httpsig_test.go +++ b/httpsig_test.go @@ -3,6 +3,8 @@ package httpsig import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" @@ -19,6 +21,7 @@ import ( "testing" "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ssh" ) const ( @@ -63,6 +66,7 @@ type ed25519PubKey struct { var ( privKey *rsa.PrivateKey + privECDSAKey *ecdsa.PrivateKey macKey []byte tests []httpsigTest testSpecRSAPrivateKey *rsa.PrivateKey @@ -77,6 +81,10 @@ func init() { if err != nil { panic(err) } + privECDSAKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(err) @@ -139,6 +147,32 @@ func init() { expectedSignatureAlgorithm: "hs2019", expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", }, + { + name: "ecdsa signature", + prefs: []Algorithm{ECDSA_SHA256}, + digestAlg: DigestSha256, + headers: []string{"Date", "Digest"}, + scheme: Signature, + privKey: privECDSAKey, + pubKey: privECDSAKey.Public(), + pubKeyId: "pubKeyId", + expectedAlgorithm: ECDSA_SHA256, + expectedSignatureAlgorithm: "hs2019", + }, + { + name: "digest on ecdsa signature", + prefs: []Algorithm{ECDSA_SHA256}, + digestAlg: DigestSha256, + headers: []string{"Date", "Digest"}, + body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."), + scheme: Signature, + privKey: privECDSAKey, + pubKey: privECDSAKey.Public(), + pubKeyId: "pubKeyId", + expectedAlgorithm: ECDSA_SHA256, + expectedSignatureAlgorithm: "hs2019", + expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", + }, { name: "hmac signature", prefs: []Algorithm{HMAC_SHA256}, @@ -189,6 +223,18 @@ func init() { expectedAlgorithm: ED25519, expectedSignatureAlgorithm: "hs2019", }, + { + name: "ecdsa authorization", + prefs: []Algorithm{ECDSA_SHA256}, + digestAlg: DigestSha256, + headers: []string{"Date", "Digest"}, + scheme: Authorization, + privKey: privECDSAKey, + pubKey: privECDSAKey.Public(), + pubKeyId: "pubKeyId", + expectedAlgorithm: ECDSA_SHA256, + expectedSignatureAlgorithm: "hs2019", + }, { name: "hmac authorization", prefs: []Algorithm{HMAC_SHA256}, @@ -390,6 +436,121 @@ func TestSignerResponse(t *testing.T) { } } +func TestGetSSHAlgorithm(t *testing.T) { + sshTests := []struct { + pkType string + expected Algorithm + }{ + {ssh.KeyAlgoED25519, ED25519}, + {ssh.CertAlgoED25519v01, ED25519}, + {ssh.KeyAlgoRSA, RSA_SHA256}, + {ssh.CertAlgoRSAv01, RSA_SHA256}, + {ssh.KeyAlgoECDSA256, ECDSA_SHA256}, + {ssh.CertAlgoECDSA256v01, ECDSA_SHA256}, + {ssh.KeyAlgoECDSA384, ""}, + {ssh.KeyAlgoECDSA521, ""}, + {"ssh-dss", ""}, + } + for _, test := range sshTests { + if got := getSSHAlgorithm(test.pkType); got != test.expected { + t.Errorf("%q: got %q, want %q", test.pkType, got, test.expected) + } + } +} + +func TestSSHSignerRequest(t *testing.T) { + pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("error generating ed25519 key: %s", err) + } + sshTests := []struct { + name string + privKey crypto.PrivateKey + pubKey crypto.PublicKey + expectedAlgorithm Algorithm + }{ + { + name: "rsa", + privKey: privKey, + pubKey: privKey.Public(), + expectedAlgorithm: RSA_SHA256, + }, + { + name: "ed25519", + privKey: privEd25519Key, + pubKey: pubEd25519Key, + expectedAlgorithm: ED25519, + }, + { + name: "ecdsa", + privKey: privECDSAKey, + pubKey: privECDSAKey.Public(), + expectedAlgorithm: ECDSA_SHA256, + }, + } + for _, test := range sshTests { + for _, useCert := range []bool{false, true} { + name := test.name + if useCert { + name += " certificate" + } + t.Run(name, func(t *testing.T) { + sshSigner, err := ssh.NewSignerFromKey(test.privKey) + if err != nil { + t.Fatalf("error creating ssh signer: %s", err) + } + if useCert { + sshSigner = newTestCertSigner(t, sshSigner) + } + s, algo, err := NewSSHSigner(sshSigner, DigestSha256, []string{"Date", "Digest"}, Signature, 0) + if err != nil { + t.Fatalf("error creating ssh http signer: %s", err) + } + if algo != test.expectedAlgorithm { + t.Fatalf("got %s, want %s", algo, test.expectedAlgorithm) + } + req, err := http.NewRequest(testMethod, testUrl, nil) + if err != nil { + t.Fatalf("error creating request: %s", err) + } + req.Header.Set("Date", testDate) + req.Header.Set("Digest", testDigest) + if err := s.SignRequest("pubKeyId", req, nil); err != nil { + t.Fatalf("error signing request: %s", err) + } + v, err := NewVerifier(req) + if err != nil { + t.Fatalf("error creating verifier: %s", err) + } + if v.KeyId() != "pubKeyId" { + t.Errorf("KeyId mismatch\nGot: %s\nWant: pubKeyId", v.KeyId()) + } + if err := v.Verify(test.pubKey, algo); err != nil { + t.Errorf("Verification failure: %s", err) + } + }) + } + } +} + +func newTestCertSigner(t *testing.T, s ssh.Signer) ssh.Signer { + t.Helper() + cert := &ssh.Certificate{ + Key: s.PublicKey(), + CertType: ssh.UserCert, + KeyId: "test", + ValidBefore: ssh.CertTimeInfinity, + } + if err := cert.SignCert(rand.Reader, s); err != nil { + t.Fatalf("error signing certificate: %s", err) + } + certSigner, err := ssh.NewCertSigner(cert, s) + if err != nil { + t.Fatalf("error creating certificate signer: %s", err) + } + return certSigner +} + func TestNewSignerRequestMissingHeaders(t *testing.T) { failingTests := []struct { name string