pax_global_header00006660000000000000000000000064137023214000014502gustar00rootroot0000000000000052 comment=812070f75b672b9a5bc97f48dfc50496d7ace991 httpsig-1.1.0/000077500000000000000000000000001370232140000131635ustar00rootroot00000000000000httpsig-1.1.0/LICENSE000066400000000000000000000027421370232140000141750ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2018, go-fed All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. httpsig-1.1.0/README.md000066400000000000000000000055141370232140000144470ustar00rootroot00000000000000# httpsig `go get github.com/go-fed/httpsig` Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). Supports many different combinations of MAC, HMAC signing of hash, or RSA signing of hash schemes. Its goals are: * Have a very simple interface for signing and validating * Support a variety of signing algorithms and combinations * Support setting either headers (`Authorization` or `Signature`) * Remaining flexible with headers included in the signing string * Support both HTTP requests and responses * Explicitly not support known-cryptographically weak algorithms * Support automatic signing and validating Digest headers ## How to use `import "github.com/go-fed/httpsig"` ### Signing Signing a request or response requires creating a new `Signer` and using it: ``` func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} digestAlgorithm := DigestSha256 // The "Date" and "Digest" headers must already be set on r, as well as r.URL. headersToSign := []string{httpsig.RequestTarget, "date", "digest"} signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) if err != nil { return err } // To sign the digest, we need to give the signer a copy of the body... // ...but it is optional, no digest will be signed if given "nil" body := ... // If r were a http.ResponseWriter, call SignResponse instead. return signer.SignRequest(privateKey, pubKeyId, r, body) } ``` `Signer`s are not safe for concurrent use by goroutines, so be sure to guard access: ``` type server struct { signer httpsig.Signer mu *sync.Mutex } func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { privateKey := ... pubKeyId := ... // Set headers and such on w s.mu.Lock() defer s.mu.Unlock() // To sign the digest, we need to give the signer a copy of the response body... // ...but it is optional, no digest will be signed if given "nil" body := ... err := s.signer.SignResponse(privateKey, pubKeyId, w, body) if err != nil { ... } ... } ``` The `pubKeyId` will be used at verification time. ### Verifying Verifying requires an application to use the `pubKeyId` to both retrieve the key needed for verification as well as determine the algorithm to use. Use a `Verifier`: ``` func verify(r *http.Request) error { verifier, err := httpsig.NewVerifier(r) if err != nil { return err } pubKeyId := verifier.KeyId() var algo httpsig.Algorithm = ... var pubKey crypto.PublicKey = ... // The verifier will verify the Digest in addition to the HTTP signature return verifier.Verify(pubKey, algo) } ``` `Verifier`s are not safe for concurrent use by goroutines, but since they are constructed on a per-request or per-response basis it should not be a common restriction. httpsig-1.1.0/algorithms.go000066400000000000000000000332401370232140000156650ustar00rootroot00000000000000package httpsig import ( "crypto" "crypto/ecdsa" "crypto/hmac" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/subtle" // Use should trigger great care "encoding/asn1" "errors" "fmt" "hash" "io" "math/big" "strings" "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" ) const ( hmacPrefix = "hmac" rsaPrefix = "rsa" 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" sha3_512String = "sha3-512" sha512_224String = "sha512-224" sha512_256String = "sha512-256" blake2s_256String = "blake2s-256" blake2b_256String = "blake2b-256" blake2b_384String = "blake2b-384" blake2b_512String = "blake2b-512" ) var blake2Algorithms = map[crypto.Hash]bool{ crypto.BLAKE2s_256: true, crypto.BLAKE2b_256: true, crypto.BLAKE2b_384: true, crypto.BLAKE2b_512: true, } var hashToDef = map[crypto.Hash]struct { name string new func(key []byte) (hash.Hash, error) // Only MACers will accept a key }{ // Which standard names these? // The spec lists the following as a canonical reference, which is dead: // 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.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 }}, crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }}, crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }}, crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }}, crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }}, crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }}, crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }}, crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }}, } var stringToHash map[string]crypto.Hash const ( defaultAlgorithm = RSA_SHA256 defaultAlgorithmHashing = sha256String ) func init() { stringToHash = make(map[string]crypto.Hash, len(hashToDef)) for k, v := range hashToDef { stringToHash[v.name] = k } // This should guarantee that at runtime the defaultAlgorithm will not // result in errors when fetching a macer or signer (see algorithms.go) if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil { panic(err) } else if !ok { panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm)) } } 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 } // signer is an internally public type. type signer interface { Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) Verify(pub crypto.PublicKey, toHash, signature []byte) error String() string } // macer is an internally public type. type macer interface { Sign(sig, key []byte) ([]byte, error) Equal(sig, actualMAC, key []byte) (bool, error) String() string } var _ macer = &hmacAlgorithm{} type hmacAlgorithm struct { fn func(key []byte) (hash.Hash, error) kind crypto.Hash } func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { hs, err := h.fn(key) if err = setSig(hs, sig); err != nil { return nil, err } return hs.Sum(nil), nil } func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { hs, err := h.fn(key) if err != nil { return false, err } defer hs.Reset() err = setSig(hs, sig) if err != nil { return false, err } expected := hs.Sum(nil) return hmac.Equal(actualMAC, expected), nil } func (h *hmacAlgorithm) String() string { return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name) } var _ signer = &rsaAlgorithm{} type rsaAlgorithm struct { hash.Hash kind crypto.Hash sshSigner ssh.Signer } func (r *rsaAlgorithm) setSig(b []byte) error { n, err := r.Write(b) if err != nil { r.Reset() return err } else if n != len(b) { r.Reset() return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) } return nil } 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 } return sshsig.Blob, nil } defer r.Reset() if err := r.setSig(sig); err != nil { return nil, err } rsaK, ok := p.(*rsa.PrivateKey) if !ok { return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey") } return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil)) } func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { defer r.Reset() rsaK, ok := pub.(*rsa.PublicKey) if !ok { return errors.New("crypto.PublicKey is not *rsa.PublicKey") } if err := r.setSig(toHash); err != nil { return err } return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature) } func (r *rsaAlgorithm) String() string { return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name) } var _ signer = &ed25519Algorithm{} type ed25519Algorithm struct { sshSigner ssh.Signer } func (r *ed25519Algorithm) 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 } return sshsig.Blob, nil } ed25519K, ok := p.(ed25519.PrivateKey) if !ok { return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey") } return ed25519.Sign(ed25519K, sig), nil } func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { ed25519K, ok := pub.(ed25519.PublicKey) if !ok { return errors.New("crypto.PublicKey is not ed25519.PublicKey") } if ed25519.Verify(ed25519K, toHash, signature) { return nil } return errors.New("ed25519 verify failed") } func (r *ed25519Algorithm) String() string { return fmt.Sprintf("%s", ed25519Prefix) } var _ signer = &ecdsaAlgorithm{} type ecdsaAlgorithm struct { hash.Hash kind crypto.Hash } func (r *ecdsaAlgorithm) setSig(b []byte) error { n, err := r.Write(b) if err != nil { r.Reset() return err } else if n != len(b) { r.Reset() return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) } return nil } type ECDSASignature struct { R, S *big.Int } func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { defer r.Reset() if err := r.setSig(sig); err != nil { return nil, err } ecdsaK, ok := p.(*ecdsa.PrivateKey) if !ok { return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey") } R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil)) if err != nil { return nil, err } signature := ECDSASignature{R: R, S: S} bytes, err := asn1.Marshal(signature) return bytes, err } func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { defer r.Reset() ecdsaK, ok := pub.(*ecdsa.PublicKey) if !ok { return errors.New("crypto.PublicKey is not *ecdsa.PublicKey") } if err := r.setSig(toHash); err != nil { return err } sig := new(ECDSASignature) _, err := asn1.Unmarshal(signature, sig) if err != nil { return err } if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) { return nil } else { return errors.New("Invalid signature") } } func (r *ecdsaAlgorithm) String() string { return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name) } var _ macer = &blakeMacAlgorithm{} type blakeMacAlgorithm struct { fn func(key []byte) (hash.Hash, error) kind crypto.Hash } func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) { hs, err := r.fn(key) if err != nil { return nil, err } if err = setSig(hs, sig); err != nil { return nil, err } return hs.Sum(nil), nil } func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { hs, err := r.fn(key) if err != nil { return false, err } defer hs.Reset() err = setSig(hs, sig) if err != nil { return false, err } expected := hs.Sum(nil) return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil } func (r *blakeMacAlgorithm) String() string { return fmt.Sprintf("%s", hashToDef[r.kind].name) } func setSig(a hash.Hash, b []byte) error { n, err := a.Write(b) if err != nil { a.Reset() return err } else if n != len(b) { a.Reset() return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) } return nil } // IsSupportedHttpSigAlgorithm returns true if the string is supported by this // library, is not a hash known to be weak, and is supported by the hardware. func IsSupportedHttpSigAlgorithm(algo string) bool { a, err := isAvailable(algo) return a && err == nil } // isAvailable is an internally public function func isAvailable(algo string) (bool, error) { c, ok := stringToHash[algo] if !ok { return false, fmt.Errorf("no match for %q", algo) } if isForbiddenHash(c) { return false, fmt.Errorf("forbidden hash type in %q", algo) } return c.Available(), nil } func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) { ok := false c, ok = stringToHash[algo] if !ok { e = fmt.Errorf("no match for %q", algo) return } if isForbiddenHash(c) { e = fmt.Errorf("forbidden hash type in %q", algo) return } algoDef, ok := hashToDef[c] if !ok { e = fmt.Errorf("have crypto.Hash %v but no definition", c) return } fn = func(key []byte) (hash.Hash, error) { h, err := algoDef.new(key) if err != nil { return nil, err } return h, nil } return } func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { fn, c, err := newAlgorithmConstructor(algo) if err != nil { return nil, c, err } h, err := fn(key) return h, c, err } func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { switch { case strings.HasPrefix(s, rsaPrefix): return &rsaAlgorithm{ sshSigner: sshSigner, }, nil case strings.HasPrefix(s, ed25519Prefix): return &ed25519Algorithm{ sshSigner: sshSigner, }, nil default: return nil, fmt.Errorf("no signer matching %q", s) } } // signerFromString is an internally public method constructor func signerFromString(s string) (signer, error) { s = strings.ToLower(s) isEcdsa := false isEd25519 := false var algo string = "" if strings.HasPrefix(s, ecdsaPrefix) { algo = strings.TrimPrefix(s, ecdsaPrefix+"-") isEcdsa = true } else if strings.HasPrefix(s, rsaPrefix) { algo = strings.TrimPrefix(s, rsaPrefix+"-") } else if strings.HasPrefix(s, ed25519Prefix) { isEd25519 = true algo = "sha512" } else { return nil, fmt.Errorf("no signer matching %q", s) } hash, cHash, err := newAlgorithm(algo, nil) if err != nil { return nil, err } if isEd25519 { return &ed25519Algorithm{}, nil } if isEcdsa { return &ecdsaAlgorithm{ Hash: hash, kind: cHash, }, nil } return &rsaAlgorithm{ Hash: hash, kind: cHash, }, nil } // macerFromString is an internally public method constructor func macerFromString(s string) (macer, error) { s = strings.ToLower(s) if strings.HasPrefix(s, hmacPrefix) { algo := strings.TrimPrefix(s, hmacPrefix+"-") hashFn, cHash, err := newAlgorithmConstructor(algo) if err != nil { return nil, err } // Ensure below does not panic _, err = hashFn(nil) if err != nil { return nil, err } return &hmacAlgorithm{ fn: func(key []byte) (hash.Hash, error) { return hmac.New(func() hash.Hash { h, e := hashFn(nil) if e != nil { panic(e) } return h }, key), nil }, kind: cHash, }, nil } else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] { hashFn, cHash, err := newAlgorithmConstructor(s) if err != nil { return nil, err } return &blakeMacAlgorithm{ fn: hashFn, kind: cHash, }, nil } else { return nil, fmt.Errorf("no MACer matching %q", s) } } httpsig-1.1.0/algorithms_test.go000066400000000000000000000662621370232140000167360ustar00rootroot00000000000000package httpsig import ( "crypto" "crypto/hmac" "crypto/rand" "crypto/rsa" "hash" doNotUseInProdCode "math/rand" "strings" "testing" ) func readFullFromCrypto(b []byte) error { n := len(b) t := 0 for t < n { d, err := rand.Reader.Read(b[t:]) if d == 0 && err != nil { return err } t += d } return nil } func TestIsAvailable(t *testing.T) { tests := []struct { name string algo string expected bool expectError bool }{ { name: md4String, algo: md4String, expected: false, expectError: true, }, { name: md5String, algo: md5String, expected: false, expectError: true, }, { name: sha1String, algo: sha1String, expected: false, expectError: true, }, { name: sha224String, algo: sha224String, expected: true, expectError: false, }, { name: sha256String, algo: sha256String, expected: true, expectError: false, }, { name: sha384String, algo: sha384String, expected: true, expectError: false, }, { name: sha512String, algo: sha512String, 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, expected: true, expectError: false, }, { name: sha3_256String, algo: sha3_256String, expected: true, expectError: false, }, { name: sha3_384String, algo: sha3_384String, expected: true, expectError: false, }, { name: sha3_512String, algo: sha3_512String, expected: true, expectError: false, }, { name: sha512_224String, algo: sha512_224String, expected: true, expectError: false, }, { name: sha512_256String, algo: sha512_256String, expected: true, expectError: false, }, { name: blake2s_256String, algo: blake2s_256String, expected: true, expectError: false, }, { name: blake2b_256String, algo: blake2b_256String, expected: true, expectError: false, }, { name: blake2b_384String, algo: blake2b_384String, expected: true, expectError: false, }, { name: blake2b_512String, algo: blake2b_512String, expected: true, expectError: false, }, } for _, test := range tests { got, err := isAvailable(test.algo) gotErr := err != nil if got != test.expected { t.Fatalf("%q: got %v, want %v", test.name, got, test.expected) } else if gotErr != test.expectError { if test.expectError { t.Fatalf("%q: expected error, got: %s", test.name, err) } else { t.Fatalf("%q: expected no error, got: %s", test.name, err) } } } } func TestSignerFromString(t *testing.T) { tests := []struct { name string input Algorithm expectKind crypto.Hash expectError bool }{ { name: "HMAC_SHA224", input: HMAC_SHA224, expectError: true, }, { name: "HMAC_SHA256", input: HMAC_SHA256, expectError: true, }, { name: "HMAC_SHA384", input: HMAC_SHA384, expectError: true, }, { name: "HMAC_SHA512", input: HMAC_SHA512, expectError: true, }, { name: "HMAC_RIPEMD160", input: HMAC_RIPEMD160, expectError: true, }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, expectError: true, }, { name: "HMAC_SHA3_256", input: HMAC_SHA3_256, expectError: true, }, { name: "HMAC_SHA3_384", input: HMAC_SHA3_384, expectError: true, }, { name: "HMAC_SHA3_512", input: HMAC_SHA3_512, expectError: true, }, { name: "HMAC_SHA512_224", input: HMAC_SHA512_224, expectError: true, }, { name: "HMAC_SHA512_256", input: HMAC_SHA512_256, expectError: true, }, { name: "HMAC_BLAKE2S_256", input: HMAC_BLAKE2S_256, expectError: true, }, { name: "HMAC_BLAKE2B_256", input: HMAC_BLAKE2B_256, expectError: true, }, { name: "HMAC_BLAKE2B_384", input: HMAC_BLAKE2B_384, expectError: true, }, { name: "HMAC_BLAKE2B_512", input: HMAC_BLAKE2B_512, expectError: true, }, { name: "BLAKE2S_256", input: BLAKE2S_256, expectError: true, }, { name: "BLAKE2B_256", input: BLAKE2B_256, expectError: true, }, { name: "BLAKE2B_384", input: BLAKE2B_384, expectError: true, }, { name: "BLAKE2B_512", input: BLAKE2B_512, expectError: true, }, { name: "RSA_SHA224", input: RSA_SHA224, expectKind: crypto.SHA224, }, { name: "RSA_SHA256", input: RSA_SHA256, expectKind: crypto.SHA256, }, { name: "RSA_SHA384", input: RSA_SHA384, expectKind: crypto.SHA384, }, { name: "RSA_SHA512", input: RSA_SHA512, expectKind: crypto.SHA512, }, { name: "RSA_RIPEMD160", input: RSA_RIPEMD160, expectKind: crypto.RIPEMD160, }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, expectKind: crypto.SHA3_224, }, { name: "rsa_SHA3_256", input: rsa_SHA3_256, expectKind: crypto.SHA3_256, }, { name: "rsa_SHA3_384", input: rsa_SHA3_384, expectKind: crypto.SHA3_384, }, { name: "rsa_SHA3_512", input: rsa_SHA3_512, expectKind: crypto.SHA3_512, }, { name: "rsa_SHA512_224", input: rsa_SHA512_224, expectKind: crypto.SHA512_224, }, { name: "rsa_SHA512_256", input: rsa_SHA512_256, expectKind: crypto.SHA512_256, }, { name: "rsa_BLAKE2S_256", input: rsa_BLAKE2S_256, expectKind: crypto.BLAKE2s_256, }, { name: "rsa_BLAKE2B_256", input: rsa_BLAKE2B_256, expectKind: crypto.BLAKE2b_256, }, { name: "rsa_BLAKE2B_384", input: rsa_BLAKE2B_384, expectKind: crypto.BLAKE2b_384, }, { name: "rsa_BLAKE2B_512", input: rsa_BLAKE2B_512, expectKind: crypto.BLAKE2b_512, }, } for _, test := range tests { s, err := signerFromString(string(test.input)) hasErr := err != nil if hasErr != test.expectError { if test.expectError { t.Fatalf("%q: expected error, got: %s", test.name, err) } else { t.Fatalf("%q: expected no error, got: %s", test.name, err) } } else if err == nil { want, ok := hashToDef[test.expectKind] if !ok { t.Fatalf("%q: Bad test setup, cannot find %q", test.name, test.expectKind) } if !strings.HasSuffix(s.String(), want.name) { t.Fatalf("%q: expected suffix %q, got %q", test.name, want.name, s.String()) } } } } func TestMACerFromString(t *testing.T) { tests := []struct { name string input Algorithm expectKind crypto.Hash expectError bool }{ { name: "HMAC_SHA224", input: HMAC_SHA224, expectKind: crypto.SHA224, }, { name: "HMAC_SHA256", input: HMAC_SHA256, expectKind: crypto.SHA256, }, { name: "HMAC_SHA384", input: HMAC_SHA384, expectKind: crypto.SHA384, }, { name: "HMAC_SHA512", input: HMAC_SHA512, expectKind: crypto.SHA512, }, { name: "HMAC_RIPEMD160", input: HMAC_RIPEMD160, expectKind: crypto.RIPEMD160, }, { name: "HMAC_SHA3_224", input: HMAC_SHA3_224, expectKind: crypto.SHA3_224, }, { name: "HMAC_SHA3_256", input: HMAC_SHA3_256, expectKind: crypto.SHA3_256, }, { name: "HMAC_SHA3_384", input: HMAC_SHA3_384, expectKind: crypto.SHA3_384, }, { name: "HMAC_SHA3_512", input: HMAC_SHA3_512, expectKind: crypto.SHA3_512, }, { name: "HMAC_SHA512_224", input: HMAC_SHA512_224, expectKind: crypto.SHA512_224, }, { name: "HMAC_SHA512_256", input: HMAC_SHA512_256, expectKind: crypto.SHA512_256, }, { name: "HMAC_BLAKE2S_256", input: HMAC_BLAKE2S_256, expectKind: crypto.BLAKE2s_256, }, { name: "HMAC_BLAKE2B_256", input: HMAC_BLAKE2B_256, expectKind: crypto.BLAKE2b_256, }, { name: "HMAC_BLAKE2B_384", input: HMAC_BLAKE2B_384, expectKind: crypto.BLAKE2b_384, }, { name: "HMAC_BLAKE2B_512", input: HMAC_BLAKE2B_512, expectKind: crypto.BLAKE2b_512, }, { name: "BLAKE2S_256", input: BLAKE2S_256, expectKind: crypto.BLAKE2s_256, }, { name: "BLAKE2B_256", input: BLAKE2B_256, expectKind: crypto.BLAKE2b_256, }, { name: "BLAKE2B_384", input: BLAKE2B_384, expectKind: crypto.BLAKE2b_384, }, { name: "BLAKE2B_512", input: BLAKE2B_512, expectKind: crypto.BLAKE2b_512, }, { name: "RSA_SHA224", input: RSA_SHA224, expectError: true, }, { name: "RSA_SHA256", input: RSA_SHA256, expectError: true, }, { name: "RSA_SHA384", input: RSA_SHA384, expectError: true, }, { name: "RSA_SHA512", input: RSA_SHA512, expectError: true, }, { name: "RSA_RIPEMD160", input: RSA_RIPEMD160, expectError: true, }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, expectError: true, }, { name: "rsa_SHA3_256", input: rsa_SHA3_256, expectError: true, }, { name: "rsa_SHA3_384", input: rsa_SHA3_384, expectError: true, }, { name: "rsa_SHA3_512", input: rsa_SHA3_512, expectError: true, }, { name: "rsa_SHA512_224", input: rsa_SHA512_224, expectError: true, }, { name: "rsa_SHA512_256", input: rsa_SHA512_256, expectError: true, }, { name: "rsa_BLAKE2S_256", input: rsa_BLAKE2S_256, expectError: true, }, { name: "rsa_BLAKE2B_256", input: rsa_BLAKE2B_256, expectError: true, }, { name: "rsa_BLAKE2B_384", input: rsa_BLAKE2B_384, expectError: true, }, { name: "rsa_BLAKE2B_512", input: rsa_BLAKE2B_512, expectError: true, }, } for _, test := range tests { m, err := macerFromString(string(test.input)) hasErr := err != nil if hasErr != test.expectError { if test.expectError { t.Fatalf("%q: expected error, got: %s", test.name, err) } else { t.Fatalf("%q: expected no error, got: %s", test.name, err) } } else if err == nil { want, ok := hashToDef[test.expectKind] if !ok { t.Fatalf("%q: Bad test setup, cannot find %q", test.name, test.expectKind) } if !strings.HasSuffix(m.String(), want.name) { t.Fatalf("%q: expected suffix %q, got %q", test.name, want.name, m.String()) } } } } func TestSignerSigns(t *testing.T) { tests := []struct { name string input Algorithm inputCryptoHash crypto.Hash expectRSAUnsupported bool }{ { name: "RSA_SHA224", input: RSA_SHA224, inputCryptoHash: crypto.SHA224, }, { name: "RSA_SHA256", input: RSA_SHA256, inputCryptoHash: crypto.SHA256, }, { name: "RSA_SHA384", input: RSA_SHA384, inputCryptoHash: crypto.SHA384, }, { name: "RSA_SHA512", input: RSA_SHA512, inputCryptoHash: crypto.SHA512, }, { name: "RSA_RIPEMD160", input: RSA_RIPEMD160, inputCryptoHash: crypto.RIPEMD160, }, { name: "rsa_SHA3_224", input: rsa_SHA3_224, inputCryptoHash: crypto.SHA3_224, expectRSAUnsupported: true, }, { name: "rsa_SHA3_256", input: rsa_SHA3_256, inputCryptoHash: crypto.SHA3_256, expectRSAUnsupported: true, }, { name: "rsa_SHA3_384", input: rsa_SHA3_384, inputCryptoHash: crypto.SHA3_384, expectRSAUnsupported: true, }, { name: "rsa_SHA3_512", input: rsa_SHA3_512, inputCryptoHash: crypto.SHA3_512, expectRSAUnsupported: true, }, { name: "rsa_SHA512_224", input: rsa_SHA512_224, inputCryptoHash: crypto.SHA512_224, expectRSAUnsupported: true, }, { name: "rsa_SHA512_256", input: rsa_SHA512_256, inputCryptoHash: crypto.SHA512_256, expectRSAUnsupported: true, }, { name: "rsa_BLAKE2S_256", input: rsa_BLAKE2S_256, inputCryptoHash: crypto.BLAKE2s_256, expectRSAUnsupported: true, }, { name: "rsa_BLAKE2B_256", input: rsa_BLAKE2B_256, inputCryptoHash: crypto.BLAKE2b_256, expectRSAUnsupported: true, }, { name: "rsa_BLAKE2B_384", input: rsa_BLAKE2B_384, inputCryptoHash: crypto.BLAKE2b_384, expectRSAUnsupported: true, }, { name: "rsa_BLAKE2B_512", input: rsa_BLAKE2B_512, inputCryptoHash: crypto.BLAKE2b_512, expectRSAUnsupported: true, }, } for _, test := range tests { privKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } sig := make([]byte, 65535) n, err := doNotUseInProdCode.Read(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } 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) } seed := doNotUseInProdCode.Int63() doNotUseThisKindOfRandInProdCodeTest := doNotUseInProdCode.New(doNotUseInProdCode.NewSource(seed)) doNotUseThisKindOfRandInProdCodeTestVerify := doNotUseInProdCode.New(doNotUseInProdCode.NewSource(seed)) actual, err := s.Sign(doNotUseThisKindOfRandInProdCodeTest, privKey, sig) hasErr := err != nil if test.expectRSAUnsupported != hasErr { if test.expectRSAUnsupported { t.Fatalf("%q: expected error, got: %s", test.name, err) } else { t.Fatalf("%q: expected no error, got: %s", test.name, err) } } else if !test.expectRSAUnsupported && err != nil { t.Fatalf("%q: %s", test.name, err) } else if test.expectRSAUnsupported { // Skip further testing -- just need to verify it is // unsupported. continue } testHash, err := hashToDef[test.inputCryptoHash].new(nil) if err != nil { t.Fatalf("%q: %s", test.name, err) } n, err = testHash.Write(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } want, err := rsa.SignPKCS1v15(doNotUseThisKindOfRandInProdCodeTestVerify, privKey, test.inputCryptoHash, testHash.Sum(nil)) if err != nil { t.Fatalf("%q: %s", test.name, err) } if len(actual) != len(want) { t.Fatalf("%q: len actual (%d) != len want (%d)", test.name, len(actual), len(want)) } for i, v := range actual { if v != want[i] { t.Fatalf("%q: difference beginning at index %d:\nwant: %v\nactual: %v", test.name, i, want, actual) } } } } func TestSignerVerifies(t *testing.T) { tests := []struct { name string input Algorithm inputCryptoHash crypto.Hash }{ { name: "RSA_SHA224", input: RSA_SHA224, inputCryptoHash: crypto.SHA224, }, { name: "RSA_SHA256", input: RSA_SHA256, inputCryptoHash: crypto.SHA256, }, { name: "RSA_SHA384", input: RSA_SHA384, inputCryptoHash: crypto.SHA384, }, { name: "RSA_SHA512", 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) 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) } testHash, err := hashToDef[test.inputCryptoHash].new(nil) if err != nil { t.Fatalf("%q: %s", test.name, err) } n, err = testHash.Write(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) } signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, test.inputCryptoHash, testHash.Sum(nil)) if err != nil { t.Fatalf("%q: %s", test.name, err) } s, err := signerFromString(string(test.input)) if err != nil { t.Fatalf("%q: %s", test.name, err) } err = s.Verify(privKey.Public(), toHash, signature) if err != nil { t.Fatalf("%q: %s", test.name, err) } } } func TestMACerSigns(t *testing.T) { tests := []struct { name string input Algorithm inputCryptoHash crypto.Hash isHMAC bool keySize int }{ { name: "HMAC_SHA224", input: HMAC_SHA224, inputCryptoHash: crypto.SHA224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA256", input: HMAC_SHA256, inputCryptoHash: crypto.SHA256, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA384", input: HMAC_SHA384, inputCryptoHash: crypto.SHA384, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512", input: HMAC_SHA512, inputCryptoHash: crypto.SHA512, 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, inputCryptoHash: crypto.SHA3_224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_256", input: HMAC_SHA3_256, inputCryptoHash: crypto.SHA3_256, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_384", input: HMAC_SHA3_384, inputCryptoHash: crypto.SHA3_384, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_512", input: HMAC_SHA3_512, inputCryptoHash: crypto.SHA3_512, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512_224", input: HMAC_SHA512_224, inputCryptoHash: crypto.SHA512_224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512_256", input: HMAC_SHA512_256, inputCryptoHash: crypto.SHA512_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2S_256", input: HMAC_BLAKE2S_256, inputCryptoHash: crypto.BLAKE2s_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_256", input: HMAC_BLAKE2B_256, inputCryptoHash: crypto.BLAKE2b_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_384", input: HMAC_BLAKE2B_384, inputCryptoHash: crypto.BLAKE2b_384, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_512", input: HMAC_BLAKE2B_512, inputCryptoHash: crypto.BLAKE2b_512, isHMAC: true, keySize: 128, }, { name: "BLAKE2S_256", input: BLAKE2S_256, inputCryptoHash: crypto.BLAKE2s_256, keySize: 32, }, { name: "BLAKE2B_256", input: BLAKE2B_256, inputCryptoHash: crypto.BLAKE2b_256, keySize: 64, }, { name: "BLAKE2B_384", input: BLAKE2B_384, inputCryptoHash: crypto.BLAKE2b_384, keySize: 64, }, { name: "BLAKE2B_512", input: BLAKE2B_512, inputCryptoHash: crypto.BLAKE2b_512, keySize: 64, }, } for _, test := range tests { privKey := make([]byte, test.keySize) err := readFullFromCrypto(privKey) if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } sig := make([]byte, 65535) n, err := doNotUseInProdCode.Read(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } m, err := macerFromString(string(test.input)) if err != nil { t.Fatalf("%q: %s", test.name, err) } actual, err := m.Sign(sig, privKey) if err != nil { t.Fatalf("%q: %s", test.name, err) } var want []byte if test.isHMAC { hmacHash := hmac.New(func() hash.Hash { testHash, err := hashToDef[test.inputCryptoHash].new(nil) if err != nil { t.Fatalf("%q: %s", test.name, err) } return testHash }, privKey) n, err = hmacHash.Write(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } want = hmacHash.Sum(nil) } else { testHash, err := hashToDef[test.inputCryptoHash].new(privKey) if err != nil { t.Fatalf("%q: %s", test.name, err) } n, err = testHash.Write(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } want = testHash.Sum(nil) } if len(actual) != len(want) { t.Fatalf("%q: len actual (%d) != len want (%d)", test.name, len(actual), len(want)) } for i, v := range actual { if v != want[i] { t.Fatalf("%q: difference beginning at index %d:\nwant: %v\nactual: %v", test.name, i, want, actual) } } } } func TestMACerEquals(t *testing.T) { tests := []struct { name string input Algorithm inputCryptoHash crypto.Hash isHMAC bool keySize int }{ { name: "HMAC_SHA224", input: HMAC_SHA224, inputCryptoHash: crypto.SHA224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA256", input: HMAC_SHA256, inputCryptoHash: crypto.SHA256, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA384", input: HMAC_SHA384, inputCryptoHash: crypto.SHA384, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512", input: HMAC_SHA512, inputCryptoHash: crypto.SHA512, 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, inputCryptoHash: crypto.SHA3_224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_256", input: HMAC_SHA3_256, inputCryptoHash: crypto.SHA3_256, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_384", input: HMAC_SHA3_384, inputCryptoHash: crypto.SHA3_384, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA3_512", input: HMAC_SHA3_512, inputCryptoHash: crypto.SHA3_512, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512_224", input: HMAC_SHA512_224, inputCryptoHash: crypto.SHA512_224, isHMAC: true, keySize: 128, }, { name: "HMAC_SHA512_256", input: HMAC_SHA512_256, inputCryptoHash: crypto.SHA512_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2S_256", input: HMAC_BLAKE2S_256, inputCryptoHash: crypto.BLAKE2s_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_256", input: HMAC_BLAKE2B_256, inputCryptoHash: crypto.BLAKE2b_256, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_384", input: HMAC_BLAKE2B_384, inputCryptoHash: crypto.BLAKE2b_384, isHMAC: true, keySize: 128, }, { name: "HMAC_BLAKE2B_512", input: HMAC_BLAKE2B_512, inputCryptoHash: crypto.BLAKE2b_512, isHMAC: true, keySize: 128, }, { name: "BLAKE2S_256", input: BLAKE2S_256, inputCryptoHash: crypto.BLAKE2s_256, keySize: 32, }, { name: "BLAKE2B_256", input: BLAKE2B_256, inputCryptoHash: crypto.BLAKE2b_256, keySize: 64, }, { name: "BLAKE2B_384", input: BLAKE2B_384, inputCryptoHash: crypto.BLAKE2b_384, keySize: 64, }, { name: "BLAKE2B_512", input: BLAKE2B_512, inputCryptoHash: crypto.BLAKE2b_512, keySize: 64, }, } for _, test := range tests { privKey := make([]byte, test.keySize) err := readFullFromCrypto(privKey) if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } sig := make([]byte, 65535) n, err := doNotUseInProdCode.Read(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } var actual []byte if test.isHMAC { hmacHash := hmac.New(func() hash.Hash { testHash, err := hashToDef[test.inputCryptoHash].new(nil) if err != nil { t.Fatalf("%q: %s", test.name, err) } return testHash }, privKey) n, err = hmacHash.Write(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } actual = hmacHash.Sum(nil) } else { testHash, err := hashToDef[test.inputCryptoHash].new(privKey) if err != nil { t.Fatalf("%q: %s", test.name, err) } n, err = testHash.Write(sig) if n != len(sig) { t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) } else if err != nil { t.Fatalf("%q: Failed setup: %s", test.name, err) } actual = testHash.Sum(nil) } m, err := macerFromString(string(test.input)) if err != nil { t.Fatalf("%q: %s", test.name, err) } equal, err := m.Equal(sig, actual, privKey) if err != nil { t.Fatalf("%q: %s", test.name, err) } else if !equal { t.Fatalf("%q: signature is not verified", test.name) } } } httpsig-1.1.0/digest.go000066400000000000000000000052331370232140000147740ustar00rootroot00000000000000package httpsig import ( "bytes" "crypto" "encoding/base64" "fmt" "hash" "net/http" "strings" ) type DigestAlgorithm string const ( DigestSha256 DigestAlgorithm = "SHA-256" DigestSha512 = "SHA-512" ) var digestToDef = map[DigestAlgorithm]crypto.Hash{ DigestSha256: crypto.SHA256, DigestSha512: crypto.SHA512, } // IsSupportedDigestAlgorithm returns true if hte string is supported by this // library, is not a hash known to be weak, and is supported by the hardware. func IsSupportedDigestAlgorithm(algo string) bool { uc := DigestAlgorithm(strings.ToUpper(algo)) c, ok := digestToDef[uc] return ok && c.Available() } func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) { upper := DigestAlgorithm(strings.ToUpper(string(alg))) c, ok := digestToDef[upper] if !ok { err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg) } else if !c.Available() { err = fmt.Errorf("unavailable Digest algorithm: %s", alg) } else { h = c.New() toUse = upper } return } const ( digestHeader = "Digest" digestDelim = "=" ) func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) { _, ok := r.Header[digestHeader] if ok { err = fmt.Errorf("cannot add Digest: Digest is already set") return } var h hash.Hash var a DigestAlgorithm h, a, err = getHash(algo) if err != nil { return } h.Write(b) sum := h.Sum(nil) r.Header.Add(digestHeader, fmt.Sprintf("%s%s%s", a, digestDelim, base64.StdEncoding.EncodeToString(sum[:]))) return } func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) { _, ok := r.Header()[digestHeader] if ok { err = fmt.Errorf("cannot add Digest: Digest is already set") return } var h hash.Hash var a DigestAlgorithm h, a, err = getHash(algo) if err != nil { return } h.Write(b) sum := h.Sum(nil) r.Header().Add(digestHeader, fmt.Sprintf("%s%s%s", a, digestDelim, base64.StdEncoding.EncodeToString(sum[:]))) return } func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) { d := r.Header.Get(digestHeader) if len(d) == 0 { err = fmt.Errorf("cannot verify Digest: request has no Digest header") return } elem := strings.SplitN(d, digestDelim, 2) if len(elem) != 2 { err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d) return } var h hash.Hash h, _, err = getHash(DigestAlgorithm(elem[0])) if err != nil { return } h.Write(body.Bytes()) sum := h.Sum(nil) encSum := base64.StdEncoding.EncodeToString(sum[:]) if encSum != elem[1] { err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body") return } return } httpsig-1.1.0/digest_test.go000066400000000000000000000103721370232140000160330ustar00rootroot00000000000000package httpsig import ( "bytes" "net/http" "testing" ) func TestAddDigest(t *testing.T) { tests := []struct { name string r func() *http.Request algo DigestAlgorithm body []byte expectedDigest string expectError bool }{ { name: "adds sha256 digest", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) return r }, algo: "SHA-256", body: []byte("johnny grab your gun"), expectedDigest: "SHA-256=RYiuVuVdRpU+BWcNUUg3sf0EbJjQ9LDj9tUqR546hhk=", }, { name: "adds sha512 digest", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) return r }, algo: "SHA-512", body: []byte("yours is the drill that will pierce the heavens"), expectedDigest: "SHA-512=bM0eBRnZkuiOTsejYNb/UpvFozde+Do1ZqlXfRTS39aGmoEzoXBpjmIIuznPslc3kaprUtI/VXH8/5HsD+thGg==", }, { name: "digest already set", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "oops") return r }, algo: "SHA-512", body: []byte("did bob ewell fall on his knife"), expectError: true, }, { name: "unknown/unsupported digest algorithm", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) return r }, algo: "MD5", body: []byte("two times Cuchulainn almost drowned"), expectError: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { test := test req := test.r() err := addDigest(req, test.algo, test.body) gotErr := err != nil if gotErr != test.expectError { if test.expectError { t.Fatalf("expected error, got: %s", err) } else { t.Fatalf("expected no error, got: %s", err) } } else if !gotErr { d := req.Header.Get("Digest") if d != test.expectedDigest { t.Fatalf("unexpected digest: want %s, got %s", test.expectedDigest, d) } } }) } } func TestVerifyDigest(t *testing.T) { tests := []struct { name string r func() *http.Request body []byte expectError bool }{ { name: "verify sha256", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "SHA-256=RYiuVuVdRpU+BWcNUUg3sf0EbJjQ9LDj9tUqR546hhk=") return r }, body: []byte("johnny grab your gun"), }, { name: "verify sha512", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "SHA-512=bM0eBRnZkuiOTsejYNb/UpvFozde+Do1ZqlXfRTS39aGmoEzoXBpjmIIuznPslc3kaprUtI/VXH8/5HsD+thGg==") return r }, body: []byte("yours is the drill that will pierce the heavens"), }, { name: "no digest header", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) return r }, body: []byte("Yuji's gender is blue"), expectError: true, }, { name: "malformed digest", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "SHA-256am9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==") return r }, body: []byte("Tochee and Ozzie BFFs forever"), expectError: true, }, { name: "unsupported/unknown algo", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "MD5=poo") return r }, body: []byte("what is a man? a miserable pile of secrets"), expectError: true, }, { name: "bad digest", r: func() *http.Request { r, _ := http.NewRequest("POST", "example.com", nil) r.Header.Set("Digest", "SHA-256=bm9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==") return r }, body: []byte("johnny grab your gun"), expectError: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { test := test req := test.r() buf := bytes.NewBuffer(test.body) err := verifyDigest(req, buf) gotErr := err != nil if gotErr != test.expectError { if test.expectError { t.Fatalf("expected error, got: %s", err) } else { t.Fatalf("expected no error, got: %s", err) } } }) } } httpsig-1.1.0/go.mod000066400000000000000000000001521370232140000142670ustar00rootroot00000000000000module github.com/go-fed/httpsig require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 go 1.13 httpsig-1.1.0/go.sum000066400000000000000000000014701370232140000143200ustar00rootroot00000000000000golang.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= httpsig-1.1.0/httpsig.go000066400000000000000000000341431370232140000152010ustar00rootroot00000000000000// Implements HTTP request and response signing and verification. Supports the // major MAC and asymmetric key signature algorithms. It has several safety // restrictions: One, none of the widely known non-cryptographically safe // algorithms are permitted; Two, the RSA SHA256 algorithms must be available in // the binary (and it should, barring export restrictions); Finally, the library // assumes either the 'Authorizationn' or 'Signature' headers are to be set (but // not both). package httpsig import ( "crypto" "fmt" "net/http" "strings" "time" "golang.org/x/crypto/ssh" ) // Algorithm specifies a cryptography secure algorithm for signing HTTP requests // and responses. type Algorithm string const ( // MAC-based algoirthms. HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String 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 HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String BLAKE2S_256 Algorithm = blake2s_256String BLAKE2B_256 Algorithm = blake2b_256String 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 // 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 // ED25519 algorithms // can only be SHA512 ED25519 Algorithm = ed25519Prefix // Just because you can glue things together, doesn't mean they will // work. The following options are not supported. rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String ) // HTTP Signatures can be applied to different HTTP headers, depending on the // expected application behavior. type SignatureScheme string const ( // Signature will place the HTTP Signature into the 'Signature' HTTP // header. Signature SignatureScheme = "Signature" // Authorization will place the HTTP Signature into the 'Authorization' // HTTP header. Authorization SignatureScheme = "Authorization" ) const ( // The HTTP Signatures specification uses the "Signature" auth-scheme // for the Authorization header. This is coincidentally named, but not // semantically the same, as the "Signature" HTTP header value. signatureAuthScheme = "Signature" ) // There are subtle differences to the values in the header. The Authorization // header has an 'auth-scheme' value that must be prefixed to the rest of the // key and values. func (s SignatureScheme) authScheme() string { switch s { case Authorization: return signatureAuthScheme default: return "" } } // Signers will sign HTTP requests or responses based on the algorithms and // headers selected at creation time. // // Signers are not safe to use between multiple goroutines. // // Note that signatures do set the deprecated 'algorithm' parameter for // backwards compatibility. type Signer interface { // SignRequest signs the request using a private key. The public key id // is used by the HTTP server to identify which key to use to verify the // signature. // // If the Signer was created using a MAC based algorithm, then the key // is expected to be of type []byte. If the Signer was created using an // RSA based algorithm, then the private key is expected to be of type // *rsa.PrivateKey. // // A Digest (RFC 3230) will be added to the request. The body provided // must match the body used in the request, and is allowed to be nil. // The Digest ensures the request body is not tampered with in flight, // and if the signer is created to also sign the "Digest" header, the // HTTP Signature will then ensure both the Digest and body are not both // modified to maliciously represent different content. SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error // SignResponse signs the response using a private key. The public key // id is used by the HTTP client to identify which key to use to verify // the signature. // // If the Signer was created using a MAC based algorithm, then the key // is expected to be of type []byte. If the Signer was created using an // RSA based algorithm, then the private key is expected to be of type // *rsa.PrivateKey. // // A Digest (RFC 3230) will be added to the response. The body provided // must match the body written in the response, and is allowed to be // nil. The Digest ensures the response body is not tampered with in // flight, and if the signer is created to also sign the "Digest" // header, the HTTP Signature will then ensure both the Digest and body // are not both modified to maliciously represent different content. SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error } // NewSigner creates a new Signer with the provided algorithm preferences to // make HTTP signatures. Only the first available algorithm will be used, which // is returned by this function along with the Signer. If none of the preferred // algorithms were available, then the default algorithm is used. The headers // specified will be included into the HTTP signatures. // // The Digest will also be calculated on a request's body using the provided // digest algorithm, if "Digest" is one of the headers listed. // // The provided scheme determines which header is populated with the HTTP // Signature. // // An error is returned if an unknown or a known cryptographically insecure // Algorithm is provided. func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) { for _, pref := range prefs { s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn) if err != nil { continue } return s, pref, err } s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn) return s, defaultAlgorithm, err } // Signers will sign HTTP requests or responses based on the algorithms and // headers selected at creation time. // // Signers are not safe to use between multiple goroutines. // // Note that signatures do set the deprecated 'algorithm' parameter for // backwards compatibility. type SSHSigner interface { // SignRequest signs the request using ssh.Signer. // The public key id is used by the HTTP server to identify which key to use // to verify the signature. // // A Digest (RFC 3230) will be added to the request. The body provided // must match the body used in the request, and is allowed to be nil. // The Digest ensures the request body is not tampered with in flight, // and if the signer is created to also sign the "Digest" header, the // HTTP Signature will then ensure both the Digest and body are not both // modified to maliciously represent different content. SignRequest(pubKeyId string, r *http.Request, body []byte) error // SignResponse signs the response using ssh.Signer. The public key // id is used by the HTTP client to identify which key to use to verify // the signature. // // A Digest (RFC 3230) will be added to the response. The body provided // must match the body written in the response, and is allowed to be // nil. The Digest ensures the response body is not tampered with in // flight, and if the signer is created to also sign the "Digest" // header, the HTTP Signature will then ensure both the Digest and body // are not both modified to maliciously represent different content. SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error } // NewwSSHSigner creates a new Signer using the specified ssh.Signer // At the moment only ed25519 ssh keys 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 // digest algorithm, if "Digest" is one of the headers listed. // // The provided scheme determines which header is populated with the HTTP // Signature. func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) { sshAlgo := getSSHAlgorithm(s.PublicKey().Type()) if sshAlgo == "" { return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type()) } signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn) if err != nil { return nil, "", err } return signer, sshAlgo, nil } func getSSHAlgorithm(pkType string) Algorithm { switch { case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): return ED25519 case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): return RSA_SHA1 } return "" } // Verifier verifies HTTP Signatures. // // It will determine which of the supported headers has the parameters // that define the signature. // // Verifiers are not safe to use between multiple goroutines. // // Note that verification ignores the deprecated 'algorithm' parameter. type Verifier interface { // KeyId gets the public key id that the signature is signed with. // // Note that the application is expected to determine the algorithm // used based on metadata or out-of-band information for this key id. KeyId() string // Verify accepts the public key specified by KeyId and returns an // error if verification fails or if the signature is malformed. The // algorithm must be the one used to create the signature in order to // pass verification. The algorithm is determined based on metadata or // out-of-band information for the key id. // // If the signature was created using a MAC based algorithm, then the // key is expected to be of type []byte. If the signature was created // using an RSA based algorithm, then the public key is expected to be // of type *rsa.PublicKey. Verify(pKey crypto.PublicKey, algo Algorithm) error } const ( // host is treated specially because golang may not include it in the // request header map on the server side of a request. hostHeader = "Host" ) // NewVerifier verifies the given request. It returns an error if the HTTP // Signature parameters are not present in any headers, are present in more than // one header, are malformed, or are missing required parameters. It ignores // unknown HTTP Signature parameters. func NewVerifier(r *http.Request) (Verifier, error) { h := r.Header if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader { h[hostHeader] = []string{r.Host} } return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { return signatureString(h, toInclude, addRequestTarget(r), created, expires) }) } // NewResponseVerifier verifies the given response. It returns errors under the // same conditions as NewVerifier. func NewResponseVerifier(r *http.Response) (Verifier, error) { return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { return signatureString(h, toInclude, requestTargetNotPermitted, created, expires) }) } func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) { var expires, created int64 = 0, 0 if expiresIn != 0 { created = time.Now().Unix() expires = created + expiresIn } s, err := signerFromSSHSigner(sshSigner, string(algo)) if err != nil { return nil, fmt.Errorf("no crypto implementation available for ssh algo %q", algo) } a := &asymmSSHSigner{ asymmSigner: &asymmSigner{ s: s, dAlgo: dAlgo, headers: headers, targetHeader: scheme, prefix: scheme.authScheme(), created: created, expires: expires, }, } return a, nil } 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() expires = created + expiresIn } s, err := signerFromString(string(algo)) if err == nil { a := &asymmSigner{ s: s, dAlgo: dAlgo, headers: headers, targetHeader: scheme, prefix: scheme.authScheme(), created: created, expires: expires, } return a, nil } m, err := macerFromString(string(algo)) if err != nil { return nil, fmt.Errorf("no crypto implementation available for %q", algo) } c := &macSigner{ m: m, dAlgo: dAlgo, headers: headers, targetHeader: scheme, prefix: scheme.authScheme(), created: created, expires: expires, } return c, nil } httpsig-1.1.0/httpsig_test.go000066400000000000000000000772261370232140000162510ustar00rootroot00000000000000package httpsig import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/pem" "fmt" "io/ioutil" "net/http" "net/http/httptest" "strconv" "strings" "testing" "golang.org/x/crypto/ed25519" ) const ( testUrl = "foo.net/bar/baz?q=test&r=ok" testUrlPath = "bar/baz" testDate = "Tue, 07 Jun 2014 20:51:35 GMT" testDigest = "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=" testMethod = "GET" ) type httpsigTest struct { name string prefs []Algorithm digestAlg DigestAlgorithm headers []string body []byte scheme SignatureScheme privKey crypto.PrivateKey pubKey crypto.PublicKey pubKeyId string expectedSignatureAlgorithm string expectedAlgorithm Algorithm expectErrorSigningResponse bool expectRequestPath bool expectedDigest string } type ed25519PrivKey struct { Version int ObjectIdentifier struct { ObjectIdentifier asn1.ObjectIdentifier } PrivateKey []byte } type ed25519PubKey struct { OBjectIdentifier struct { ObjectIdentifier asn1.ObjectIdentifier } PublicKey asn1.BitString } var ( privKey *rsa.PrivateKey macKey []byte tests []httpsigTest testSpecRSAPrivateKey *rsa.PrivateKey testSpecRSAPublicKey *rsa.PublicKey testEd25519PrivateKey ed25519.PrivateKey testEd25519PublicKey ed25519.PublicKey ) func init() { var err error privKey, err = rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) } pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) if err != nil { panic(err) } macKey = make([]byte, 128) err = readFullFromCrypto(macKey) if err != nil { panic(err) } tests = []httpsigTest{ { name: "rsa signature", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", }, { name: "ed25519 signature", prefs: []Algorithm{ED25519}, digestAlg: DigestSha512, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privEd25519Key, pubKey: pubEd25519Key, pubKeyId: "pubKeyId", expectedAlgorithm: ED25519, expectedSignatureAlgorithm: "hs2019", }, { name: "digest on rsa signature", prefs: []Algorithm{RSA_SHA512}, 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: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", }, { name: "digest on ed25519 signature", prefs: []Algorithm{ED25519}, 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: privEd25519Key, pubKey: pubEd25519Key, pubKeyId: "pubKeyId", expectedAlgorithm: ED25519, expectedSignatureAlgorithm: "hs2019", expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", }, { name: "hmac signature", prefs: []Algorithm{HMAC_SHA256}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: macKey, pubKey: macKey, pubKeyId: "pubKeyId", expectedAlgorithm: HMAC_SHA256, expectedSignatureAlgorithm: "hs2019", }, { name: "digest on hmac signature", prefs: []Algorithm{HMAC_SHA256}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, body: []byte("I've never ever been to paradise I've never ever seen no angel's eyes You'll never ever let this magic die No matter where you are, you are my lucky star."), scheme: Signature, privKey: macKey, pubKey: macKey, pubKeyId: "pubKeyId", expectedAlgorithm: HMAC_SHA256, expectedSignatureAlgorithm: "hs2019", expectedDigest: "SHA-256=d0JoDjbDZRZF7/gUdgrazZCdKCJ9z9uUcMd6n1YKWRU=", }, { name: "rsa authorization", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Authorization, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", }, { name: "ed25519 authorization", prefs: []Algorithm{ED25519}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Authorization, privKey: privEd25519Key, pubKey: pubEd25519Key, pubKeyId: "pubKeyId", expectedAlgorithm: ED25519, expectedSignatureAlgorithm: "hs2019", }, { name: "hmac authorization", prefs: []Algorithm{HMAC_SHA256}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Authorization, privKey: macKey, pubKey: macKey, pubKeyId: "pubKeyId", expectedAlgorithm: HMAC_SHA256, expectedSignatureAlgorithm: "hs2019", }, { name: "default algo", digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA256, expectedSignatureAlgorithm: "hs2019", }, { name: "default headers", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, scheme: Signature, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", }, { name: "different pub key id", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "i write code that sucks", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", }, { name: "with request target", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest", RequestTarget}, scheme: Signature, privKey: privKey, pubKey: privKey.Public(), pubKeyId: "pubKeyId", expectedAlgorithm: RSA_SHA512, expectedSignatureAlgorithm: "hs2019", expectErrorSigningResponse: true, expectRequestPath: true, }, } testSpecRSAPrivateKey, err = loadPrivateKey([]byte(testSpecPrivateKeyPEM)) if err != nil { panic(err) } testSpecRSAPublicKey, err = loadPublicKey([]byte(testSpecPublicKeyPEM)) if err != nil { panic(err) } testEd25519PrivateKey, err = loadEd25519PrivateKey([]byte(testEd25519PrivateKeyPEM)) if err != nil { panic(err) } testEd25519PublicKey, err = loadEd25519PublicKey([]byte(testEd25519PublicKeyPEM)) if err != nil { panic(err) } } func toSignatureParameter(k, v string) string { return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter) } func toHeaderSignatureParameters(k string, vals []string) string { if len(vals) == 0 { vals = defaultHeaders } v := strings.Join(vals, headerParameterValueDelim) k = strings.ToLower(k) v = strings.ToLower(v) return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter) } func TestSignerRequest(t *testing.T) { testFn := func(t *testing.T, test httpsigTest) { s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) if err != nil { t.Fatalf("%s", err) } if a != test.expectedAlgorithm { t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) } // Test request signing req, err := http.NewRequest(testMethod, testUrl, nil) if err != nil { t.Fatalf("%s", err) } req.Header.Set("Date", testDate) if test.body == nil { req.Header.Set("Digest", testDigest) } err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body) if err != nil { t.Fatalf("%s", err) } vals, ok := req.Header[string(test.scheme)] if !ok { t.Fatalf("not in header %s", test.scheme) } if len(vals) != 1 { t.Fatalf("too many in header %s: %d", test.scheme, len(vals)) } if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if !strings.Contains(vals[0], signatureParameter) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter) } else if test.body != nil && req.Header.Get("Digest") != test.expectedDigest { t.Fatalf("%s\ndoes not match\n%s", req.Header.Get("Digest"), test.expectedDigest) } // For schemes with an authScheme, enforce its is present and at the beginning if len(test.scheme.authScheme()) > 0 { if !strings.HasPrefix(vals[0], test.scheme.authScheme()) { t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme()) } } } for _, test := range tests { t.Run(test.name, func(t *testing.T) { testFn(t, test) }) } } func TestSignerResponse(t *testing.T) { testFn := func(t *testing.T, test httpsigTest) { s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) // Test response signing resp := httptest.NewRecorder() resp.HeaderMap.Set("Date", testDate) if test.body == nil { resp.HeaderMap.Set("Digest", testDigest) } err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body) if test.expectErrorSigningResponse { if err != nil { // Skip rest of testing return } else { t.Fatalf("expected error, got nil") } } vals, ok := resp.HeaderMap[string(test.scheme)] if !ok { t.Fatalf("not in header %s", test.scheme) } if len(vals) != 1 { t.Fatalf("too many in header %s: %d", test.scheme, len(vals)) } if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) } else if !strings.Contains(vals[0], signatureParameter) { t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter) } else if test.body != nil && resp.Header().Get("Digest") != test.expectedDigest { t.Fatalf("%s\ndoes not match\n%s", resp.Header().Get("Digest"), test.expectedDigest) } // For schemes with an authScheme, enforce its is present and at the beginning if len(test.scheme.authScheme()) > 0 { if !strings.HasPrefix(vals[0], test.scheme.authScheme()) { t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme()) } } } for _, test := range tests { t.Run(test.name, func(t *testing.T) { testFn(t, test) }) } } func TestNewSignerRequestMissingHeaders(t *testing.T) { failingTests := []struct { name string prefs []Algorithm digestAlg DigestAlgorithm headers []string scheme SignatureScheme privKey crypto.PrivateKey pubKeyId string expectedAlgorithm Algorithm expectedSignatureAlgorithm string }{ { name: "wants digest", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privKey, pubKeyId: "pubKeyId", expectedSignatureAlgorithm: "hs2019", expectedAlgorithm: RSA_SHA512, }, } for _, test := range failingTests { t.Run(test.name, func(t *testing.T) { test := test s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) if err != nil { t.Fatalf("%s", err) } if a != test.expectedAlgorithm { t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) } req, err := http.NewRequest(testMethod, testUrl, nil) if err != nil { t.Fatalf("%s", err) } req.Header.Set("Date", testDate) err = s.SignRequest(test.privKey, test.pubKeyId, req, nil) if err == nil { t.Fatalf("expect error but got nil") } }) } } func TestNewSignerResponseMissingHeaders(t *testing.T) { failingTests := []struct { name string prefs []Algorithm digestAlg DigestAlgorithm headers []string scheme SignatureScheme privKey crypto.PrivateKey pubKeyId string expectedAlgorithm Algorithm expectErrorSigningResponse bool expectedSignatureAlgorithm string }{ { name: "want digest", prefs: []Algorithm{RSA_SHA512}, digestAlg: DigestSha256, headers: []string{"Date", "Digest"}, scheme: Signature, privKey: privKey, pubKeyId: "pubKeyId", expectedSignatureAlgorithm: "hs2019", expectedAlgorithm: RSA_SHA512, }, } for _, test := range failingTests { t.Run(test.name, func(t *testing.T) { test := test s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) if err != nil { t.Fatalf("%s", err) } if a != test.expectedAlgorithm { t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) } resp := httptest.NewRecorder() resp.HeaderMap.Set("Date", testDate) resp.HeaderMap.Set("Digest", testDigest) err = s.SignResponse(test.privKey, test.pubKeyId, resp, nil) if err != nil { t.Fatalf("expected error, got nil") } }) } } func TestNewVerifier(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { test := test // Prepare req, err := http.NewRequest(testMethod, testUrl, nil) if err != nil { t.Fatalf("%s", err) } req.Header.Set("Date", testDate) if test.body == nil { req.Header.Set("Digest", testDigest) } s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) if err != nil { t.Fatalf("%s", err) } err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body) if err != nil { t.Fatalf("%s", err) } // Test verification v, err := NewVerifier(req) if err != nil { t.Fatalf("%s", err) } if v.KeyId() != test.pubKeyId { t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId) } err = v.Verify(test.pubKey, test.expectedAlgorithm) if err != nil { t.Fatalf("%s", err) } }) } } func TestNewResponseVerifier(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { test := test if test.expectErrorSigningResponse { return } // Prepare resp := httptest.NewRecorder() resp.HeaderMap.Set("Date", testDate) if test.body == nil { resp.HeaderMap.Set("Digest", testDigest) } s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) if err != nil { t.Fatalf("%s", err) } err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body) if err != nil { t.Fatalf("%s", err) } // Test verification v, err := NewResponseVerifier(resp.Result()) if err != nil { t.Fatalf("%s", err) } if v.KeyId() != test.pubKeyId { t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId) } err = v.Verify(test.pubKey, test.expectedAlgorithm) if err != nil { t.Fatalf("%s", err) } }) } } // Test_Signing_HTTP_Messages_AppendixC implement tests from Appendix C // in the http signatures specification: // https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C func Test_Signing_HTTP_Messages_AppendixC(t *testing.T) { specTests := []struct { name string headers []string expectedSignature string }{ { name: "C.1. Default Test", headers: []string{}, // NOTE: In the Appendix C tests, the following is NOT included: // `headers="date"` // But httpsig will ALWAYS explicitly list the headers used in its // signature. Hence, I have introduced it here. // // NOTE: In verification, if there are no headers listed, the // default headers (date) are indeed used as required by the // specification. expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`, }, { name: "C.2. Basic Test", headers: []string{"(request-target)", "host", "date"}, expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`, }, { name: "C.3. All Headers Test", headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`, }, } for _, test := range specTests { t.Run(test.name, func(t *testing.T) { test := test r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) if err != nil { t.Fatalf("error creating request: %s", err) } r.Header["Date"] = []string{testSpecDate} r.Header["Host"] = []string{r.URL.Host} r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} r.Header["Content-Type"] = []string{"application/json"} setDigest(r) s, _, err := NewSigner([]Algorithm{RSA_SHA256}, DigestSha256, test.headers, Authorization, 0) if err != nil { t.Fatalf("error creating signer: %s", err) } if err := s.SignRequest(testSpecRSAPrivateKey, "Test", r, nil); err != nil { t.Fatalf("error signing request: %s", err) } expectedAuth := test.expectedSignature gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0]) if gotAuth != expectedAuth { t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth) } }) } } func TestSigningEd25519(t *testing.T) { specTests := []struct { name string headers []string expectedSignature string }{ { name: "Default Test", headers: []string{}, // NOTE: In the Appendix C tests, the following is NOT included: // `headers="date"` // But httpsig will ALWAYS explicitly list the headers used in its // signature. Hence, I have introduced it here. // // NOTE: In verification, if there are no headers listed, the // default headers (date) are indeed used as required by the // specification. expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`, }, { name: "Basic Test", headers: []string{"(request-target)", "host", "date"}, expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`, }, { name: "All Headers Test", headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`, }, } for _, test := range specTests { t.Run(test.name, func(t *testing.T) { test := test r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) if err != nil { t.Fatalf("error creating request: %s", err) } r.Header["Date"] = []string{testSpecDate} r.Header["Host"] = []string{r.URL.Host} r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} r.Header["Content-Type"] = []string{"application/json"} setDigest(r) s, _, err := NewSigner([]Algorithm{ED25519}, DigestSha256, test.headers, Authorization, 0) if err != nil { t.Fatalf("error creating signer: %s", err) } if err := s.SignRequest(testEd25519PrivateKey, "Test", r, nil); err != nil { t.Fatalf("error signing request: %s", err) } expectedAuth := test.expectedSignature gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0]) if gotAuth != expectedAuth { t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth) } }) } } // Test_Verifying_HTTP_Messages_AppendixC implement tests from Appendix C // in the http signatures specification: // https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C func Test_Verifying_HTTP_Messages_AppendixC(t *testing.T) { specTests := []struct { name string headers []string signature string }{ { name: "C.1. Default Test", headers: []string{}, signature: `Signature keyId="Test",algorithm="rsa-sha256",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`, }, { name: "C.2. Basic Test", headers: []string{"(request-target)", "host", "date"}, signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`, }, { name: "C.3. All Headers Test", headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`, }, } for _, test := range specTests { t.Run(test.name, func(t *testing.T) { test := test r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) if err != nil { t.Fatalf("error creating request: %s", err) } r.Header["Date"] = []string{testSpecDate} r.Header["Host"] = []string{r.URL.Host} r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} r.Header["Content-Type"] = []string{"application/json"} setDigest(r) r.Header["Authorization"] = []string{test.signature} v, err := NewVerifier(r) if err != nil { t.Fatalf("error creating verifier: %s", err) } if "Test" != v.KeyId() { t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId()) } if err := v.Verify(testSpecRSAPublicKey, RSA_SHA256); err != nil { t.Errorf("Verification failure: %s", err) } }) } } func TestVerifyingEd25519(t *testing.T) { specTests := []struct { name string headers []string signature string }{ { name: "Default Test", headers: []string{}, signature: `Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`, }, { name: "Basic Test", headers: []string{"(request-target)", "host", "date"}, signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`, }, { name: "All Headers Test", headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`, }, } for _, test := range specTests { t.Run(test.name, func(t *testing.T) { test := test r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) if err != nil { t.Fatalf("error creating request: %s", err) } r.Header["Date"] = []string{testSpecDate} r.Header["Host"] = []string{r.URL.Host} r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} r.Header["Content-Type"] = []string{"application/json"} setDigest(r) r.Header["Authorization"] = []string{test.signature} v, err := NewVerifier(r) if err != nil { t.Fatalf("error creating verifier: %s", err) } if "Test" != v.KeyId() { t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId()) } if err := v.Verify(testEd25519PublicKey, ED25519); err != nil { t.Errorf("Verification failure: %s", err) } }) } } func loadPrivateKey(keyData []byte) (*rsa.PrivateKey, error) { pem, _ := pem.Decode(keyData) if pem.Type != "RSA PRIVATE KEY" { return nil, fmt.Errorf("RSA private key is of the wrong type: %s", pem.Type) } return x509.ParsePKCS1PrivateKey(pem.Bytes) } // taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/ func loadEd25519PrivateKey(keyData []byte) (ed25519.PrivateKey, error) { var block *pem.Block block, _ = pem.Decode(keyData) var asn1PrivKey ed25519PrivKey asn1.Unmarshal(block.Bytes, &asn1PrivKey) // [2:] is skipping the byte for TAG and the byte for LEN // see also https://tools.ietf.org/html/draft-ietf-curdle-pkix-10#section-10.3 return ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:]), nil } func loadPublicKey(keyData []byte) (*rsa.PublicKey, error) { pem, _ := pem.Decode(keyData) if pem.Type != "PUBLIC KEY" { return nil, fmt.Errorf("public key is of the wrong type: %s", pem.Type) } key, err := x509.ParsePKIXPublicKey(pem.Bytes) if err != nil { return nil, err } return key.(*rsa.PublicKey), nil } // taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/ func loadEd25519PublicKey(keyData []byte) (ed25519.PublicKey, error) { var block *pem.Block block, _ = pem.Decode(keyData) var asn1PubKey ed25519PubKey asn1.Unmarshal(block.Bytes, &asn1PubKey) return ed25519.PublicKey(asn1PubKey.PublicKey.Bytes), nil } func setDigest(r *http.Request) ([]byte, error) { var bodyBytes []byte if _, ok := r.Header["Digest"]; !ok { body := "" if r.Body != nil { var err error bodyBytes, err = ioutil.ReadAll(r.Body) if err != nil { return nil, fmt.Errorf("error reading body. %v", err) } // And now set a new body, which will simulate the same data we read: r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) body = string(bodyBytes) } d := sha256.Sum256([]byte(body)) r.Header["Digest"] = []string{fmt.Sprintf("SHA-256=%s", base64.StdEncoding.EncodeToString(d[:]))} } return bodyBytes, nil } const testSpecBody = `{"hello": "world"}` const testSpecDate = `Sun, 05 Jan 2014 21:31:40 GMT` const testSpecPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u 412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI 7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== -----END RSA PRIVATE KEY-----` const testSpecPublicKeyPEM = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw oYi+1hqp1fIekaxsyQIDAQAB -----END PUBLIC KEY-----` const testEd25519PrivateKeyPEM = `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIAP+PK4NtdzCe04sbtwBvf9IShlky298SMMBqkCCToHn -----END PRIVATE KEY-----` const testEd25519PublicKeyPEM = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAhyP+7zpNCsr7/ipGJjK0zVszTEQ5tooyX3VLAnBSc1c= -----END PUBLIC KEY-----` httpsig-1.1.0/signing.go000066400000000000000000000212631370232140000151540ustar00rootroot00000000000000package httpsig import ( "bytes" "crypto" "crypto/rand" "encoding/base64" "fmt" "net/http" "net/textproto" "strconv" "strings" ) const ( // Signature Parameters keyIdParameter = "keyId" algorithmParameter = "algorithm" headersParameter = "headers" signatureParameter = "signature" prefixSeparater = " " parameterKVSeparater = "=" parameterValueDelimiter = "\"" parameterSeparater = "," headerParameterValueDelim = " " // RequestTarget specifies to include the http request method and // entire URI in the signature. Pass it as a header to NewSigner. RequestTarget = "(request-target)" createdKey = "created" expiresKey = "expires" dateHeader = "date" // Signature String Construction headerFieldDelimiter = ": " headersDelimiter = "\n" headerValueDelimiter = ", " requestTargetSeparator = " " ) var defaultHeaders = []string{dateHeader} var _ Signer = &macSigner{} type macSigner struct { m macer makeDigest bool dAlgo DigestAlgorithm headers []string targetHeader SignatureScheme prefix string created int64 expires int64 } func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { if body != nil { err := addDigest(r, m.dAlgo, body) if err != nil { return err } } s, err := m.signatureString(r) if err != nil { return err } enc, err := m.signSignature(pKey, s) if err != nil { return err } setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) return nil } func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { if body != nil { err := addDigestResponse(r, m.dAlgo, body) if err != nil { return err } } s, err := m.signatureStringResponse(r) if err != nil { return err } enc, err := m.signSignature(pKey, s) if err != nil { return err } setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) return nil } func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { pKeyBytes, ok := pKey.([]byte) if !ok { return "", fmt.Errorf("private key for MAC signing must be of type []byte") } sig, err := m.m.Sign([]byte(s), pKeyBytes) if err != nil { return "", err } enc := base64.StdEncoding.EncodeToString(sig) return enc, nil } func (m *macSigner) signatureString(r *http.Request) (string, error) { return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires) } func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires) } var _ Signer = &asymmSigner{} type asymmSigner struct { s signer makeDigest bool dAlgo DigestAlgorithm headers []string targetHeader SignatureScheme prefix string created int64 expires int64 } func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { if body != nil { err := addDigest(r, a.dAlgo, body) if err != nil { return err } } s, err := a.signatureString(r) if err != nil { return err } enc, err := a.signSignature(pKey, s) if err != nil { return err } setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) return nil } func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { if body != nil { err := addDigestResponse(r, a.dAlgo, body) if err != nil { return err } } s, err := a.signatureStringResponse(r) if err != nil { return err } enc, err := a.signSignature(pKey, s) if err != nil { return err } setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) return nil } func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { sig, err := a.s.Sign(rand.Reader, pKey, []byte(s)) if err != nil { return "", err } enc := base64.StdEncoding.EncodeToString(sig) return enc, nil } func (a *asymmSigner) signatureString(r *http.Request) (string, error) { return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires) } func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires) } var _ SSHSigner = &asymmSSHSigner{} type asymmSSHSigner struct { *asymmSigner } func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error { return a.asymmSigner.SignRequest(nil, pubKeyId, r, body) } func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error { return a.asymmSigner.SignResponse(nil, pubKeyId, r, body) } func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) { if len(headers) == 0 { headers = defaultHeaders } var b bytes.Buffer // KeyId b.WriteString(prefix) if len(prefix) > 0 { b.WriteString(prefixSeparater) } b.WriteString(keyIdParameter) b.WriteString(parameterKVSeparater) b.WriteString(parameterValueDelimiter) b.WriteString(pubKeyId) b.WriteString(parameterValueDelimiter) b.WriteString(parameterSeparater) // Algorithm b.WriteString(algorithmParameter) b.WriteString(parameterKVSeparater) b.WriteString(parameterValueDelimiter) b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft b.WriteString(parameterValueDelimiter) b.WriteString(parameterSeparater) hasCreated := false hasExpires := false for _, h := range headers { val := strings.ToLower(h) if val == "("+createdKey+")" { hasCreated = true } else if val == "("+expiresKey+")" { hasExpires = true } } // Created if hasCreated == true { b.WriteString(createdKey) b.WriteString(parameterKVSeparater) b.WriteString(strconv.FormatInt(created, 10)) b.WriteString(parameterSeparater) } // Expires if hasExpires == true { b.WriteString(expiresKey) b.WriteString(parameterKVSeparater) b.WriteString(strconv.FormatInt(expires, 10)) b.WriteString(parameterSeparater) } // Headers b.WriteString(headersParameter) b.WriteString(parameterKVSeparater) b.WriteString(parameterValueDelimiter) for i, h := range headers { b.WriteString(strings.ToLower(h)) if i != len(headers)-1 { b.WriteString(headerParameterValueDelim) } } b.WriteString(parameterValueDelimiter) b.WriteString(parameterSeparater) // Signature b.WriteString(signatureParameter) b.WriteString(parameterKVSeparater) b.WriteString(parameterValueDelimiter) b.WriteString(enc) b.WriteString(parameterValueDelimiter) h.Add(targetHeader, b.String()) } func requestTargetNotPermitted(b *bytes.Buffer) error { return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget) } func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error { return func(b *bytes.Buffer) error { b.WriteString(RequestTarget) b.WriteString(headerFieldDelimiter) b.WriteString(strings.ToLower(r.Method)) b.WriteString(requestTargetSeparator) b.WriteString(r.URL.Path) if r.URL.RawQuery != "" { b.WriteString("?") b.WriteString(r.URL.RawQuery) } return nil } } func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) { if len(include) == 0 { include = defaultHeaders } var b bytes.Buffer for n, i := range include { i := strings.ToLower(i) if i == RequestTarget { err := requestTargetFn(&b) if err != nil { return "", err } } else if i == "("+expiresKey+")" { if expires == 0 { return "", fmt.Errorf("missing expires value") } b.WriteString(i) b.WriteString(headerFieldDelimiter) b.WriteString(strconv.FormatInt(expires, 10)) } else if i == "("+createdKey+")" { if created == 0 { return "", fmt.Errorf("missing created value") } b.WriteString(i) b.WriteString(headerFieldDelimiter) b.WriteString(strconv.FormatInt(created, 10)) } else { hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)] if !ok { return "", fmt.Errorf("missing header %q", i) } b.WriteString(i) b.WriteString(headerFieldDelimiter) for i, v := range hv { b.WriteString(strings.TrimSpace(v)) if i < len(hv)-1 { b.WriteString(headerValueDelimiter) } } } if n < len(include)-1 { b.WriteString(headersDelimiter) } } return b.String(), nil } httpsig-1.1.0/verifying.go000066400000000000000000000112101370232140000155070ustar00rootroot00000000000000package httpsig import ( "crypto" "encoding/base64" "errors" "fmt" "net/http" "strconv" "strings" "time" ) var _ Verifier = &verifier{} type verifier struct { header http.Header kId string signature string created int64 expires int64 headers []string sigStringFn func(http.Header, []string, int64, int64) (string, error) } func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) { scheme, s, err := getSignatureScheme(h) if err != nil { return nil, err } kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) if created != 0 { //check if created is not in the future, we assume a maximum clock offset of 10 seconds now := time.Now().Unix() if created-now > 10 { return nil, errors.New("created is in the future") } } if expires != 0 { //check if expires is in the past, we assume a maximum clock offset of 10 seconds now := time.Now().Unix() if now-expires > 10 { return nil, errors.New("signature expired") } } if err != nil { return nil, err } return &verifier{ header: h, kId: kId, signature: sig, created: created, expires: expires, headers: headers, sigStringFn: sigStringFn, }, nil } func (v *verifier) KeyId() string { return v.kId } func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { s, err := signerFromString(string(algo)) if err == nil { return v.asymmVerify(s, pKey) } m, err := macerFromString(string(algo)) if err == nil { return v.macVerify(m, pKey) } return fmt.Errorf("no crypto implementation available for %q", algo) } func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error { key, ok := pKey.([]byte) if !ok { return fmt.Errorf("public key for MAC verifying must be of type []byte") } signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) if err != nil { return err } actualMAC, err := base64.StdEncoding.DecodeString(v.signature) if err != nil { return err } ok, err = m.Equal([]byte(signature), actualMAC, key) if err != nil { return err } else if !ok { return fmt.Errorf("invalid http signature") } return nil } func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error { toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) if err != nil { return err } signature, err := base64.StdEncoding.DecodeString(v.signature) if err != nil { return err } err = s.Verify(pKey, []byte(toHash), signature) if err != nil { return err } return nil } func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { s := h.Get(string(Signature)) sigHasAll := strings.Contains(s, keyIdParameter) || strings.Contains(s, headersParameter) || strings.Contains(s, signatureParameter) a := h.Get(string(Authorization)) authHasAll := strings.Contains(a, keyIdParameter) || strings.Contains(a, headersParameter) || strings.Contains(a, signatureParameter) if sigHasAll && authHasAll { err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) return } else if !sigHasAll && !authHasAll { err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) return } else if sigHasAll { val = s scheme = Signature return } else { // authHasAll val = a scheme = Authorization return } } func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { if as := scheme.authScheme(); len(as) > 0 { s = strings.TrimPrefix(s, as+prefixSeparater) } params := strings.Split(s, parameterSeparater) for _, p := range params { kv := strings.SplitN(p, parameterKVSeparater, 2) if len(kv) != 2 { err = fmt.Errorf("malformed http signature parameter: %v", kv) return } k := kv[0] v := strings.Trim(kv[1], parameterValueDelimiter) switch k { case keyIdParameter: kId = v case createdKey: created, err = strconv.ParseInt(v, 10, 64) if err != nil { return } case expiresKey: expires, err = strconv.ParseInt(v, 10, 64) if err != nil { return } case algorithmParameter: // Deprecated, ignore case headersParameter: headers = strings.Split(v, headerParameterValueDelim) case signatureParameter: sig = v default: // Ignore unrecognized parameters } } if len(kId) == 0 { err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) } else if len(sig) == 0 { err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) } else if len(headers) == 0 { // Optional headers = defaultHeaders } return }