mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 19:52:24 -05:00 
			
		
		
		
	[feature] Try HTTP signature validation with and without query params for incoming requests (#2591)
* [feature] Verify signatures both with + without query params * Bump to tagged version
This commit is contained in:
		
					parent
					
						
							
								c675d47a8c
							
						
					
				
			
			
				commit
				
					
						b614d33c40
					
				
			
		
					 18 changed files with 1799 additions and 22 deletions
				
			
		
							
								
								
									
										29
									
								
								vendor/github.com/superseriousbusiness/httpsig/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/superseriousbusiness/httpsig/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| BSD 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. | ||||
							
								
								
									
										101
									
								
								vendor/github.com/superseriousbusiness/httpsig/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/superseriousbusiness/httpsig/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| # httpsig | ||||
| 
 | ||||
| **THIS IS A FORK OF https://github.com/go-fed/httpsig, WHICH WAS NO LONGER MAINTAINED. THANK YOU TO [cjslep](https://github.com/cjslep) FOR ALL YOUR HARD WORK!** | ||||
| 
 | ||||
| > HTTP Signatures made simple | ||||
| 
 | ||||
| `go get github.com/superseriousbusiness/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/superseriousbusiness/httpsig"` | ||||
| 
 | ||||
| ### Signing | ||||
| 
 | ||||
| Signing a request or response requires creating a new `Signer` and using it: | ||||
| 
 | ||||
| ```go | ||||
| 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: | ||||
| 
 | ||||
| ```go | ||||
| 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`: | ||||
| 
 | ||||
| ```go | ||||
| 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. | ||||
| 
 | ||||
| [License-Image]: https://img.shields.io/github/license/go-fed/httpsig?color=blue | ||||
| [License-Url]: https://opensource.org/licenses/BSD-3-Clause | ||||
							
								
								
									
										532
									
								
								vendor/github.com/superseriousbusiness/httpsig/algorithms.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								vendor/github.com/superseriousbusiness/httpsig/algorithms.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,532 @@ | |||
| package 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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										120
									
								
								vendor/github.com/superseriousbusiness/httpsig/digest.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/superseriousbusiness/httpsig/digest.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,120 @@ | |||
| package 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 | ||||
| } | ||||
							
								
								
									
										413
									
								
								vendor/github.com/superseriousbusiness/httpsig/httpsig.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								vendor/github.com/superseriousbusiness/httpsig/httpsig.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,413 @@ | |||
| // 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 "" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type SignatureOption struct { | ||||
| 	// ExcludeQueryStringFromPathPseudoHeader omits the query parameters from the | ||||
| 	// `:path` pseudo-header in the HTTP signature. | ||||
| 	// | ||||
| 	// The query string is optional in the `:path` pseudo-header. | ||||
| 	// https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1-2.4.1 | ||||
| 	ExcludeQueryStringFromPathPseudoHeader bool | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| type SignerWithOptions interface { | ||||
| 	Signer | ||||
| 
 | ||||
| 	// SignRequestWithOptions 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. | ||||
| 	SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error | ||||
| 	// SignResponseWithOptions 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. | ||||
| 	SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, opts SignatureOption) 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) (SignerWithOptions, 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 | ||||
| } | ||||
| 
 | ||||
| type VerifierWithOptions interface { | ||||
| 	Verifier | ||||
| 
 | ||||
| 	VerifyWithOptions(pKey crypto.PublicKey, algo Algorithm, opts SignatureOption) 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) (VerifierWithOptions, 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, opts SignatureOption) (string, error) { | ||||
| 		return signatureString(h, toInclude, addRequestTarget(r, opts), 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, _ SignatureOption) (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: %s", algo, err) | ||||
| 	} | ||||
| 
 | ||||
| 	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) (SignerWithOptions, 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: %s", algo, err) | ||||
| 	} | ||||
| 	c := &macSigner{ | ||||
| 		m:            m, | ||||
| 		dAlgo:        dAlgo, | ||||
| 		headers:      headers, | ||||
| 		targetHeader: scheme, | ||||
| 		prefix:       scheme.authScheme(), | ||||
| 		created:      created, | ||||
| 		expires:      expires, | ||||
| 	} | ||||
| 	return c, nil | ||||
| } | ||||
							
								
								
									
										350
									
								
								vendor/github.com/superseriousbusiness/httpsig/signing.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								vendor/github.com/superseriousbusiness/httpsig/signing.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,350 @@ | |||
| package 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 _ SignerWithOptions = &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 { | ||||
| 	return m.SignRequestWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||
| } | ||||
| 
 | ||||
| func (m *macSigner) SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error { | ||||
| 	if body != nil { | ||||
| 		err := addDigest(r, m.dAlgo, body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	s, err := m.signatureString(r, opts) | ||||
| 	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 { | ||||
| 	return m.SignResponseWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||
| } | ||||
| 
 | ||||
| func (m *macSigner) SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, _ SignatureOption) 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, opts SignatureOption) (string, error) { | ||||
| 	return signatureString(r.Header, m.headers, addRequestTarget(r, opts), 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 _ SignerWithOptions = &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 { | ||||
| 	return a.SignRequestWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||
| } | ||||
| 
 | ||||
| func (a *asymmSigner) SignRequestWithOptions(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte, opts SignatureOption) error { | ||||
| 	if body != nil { | ||||
| 		err := addDigest(r, a.dAlgo, body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	s, err := a.signatureString(r, opts) | ||||
| 	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 { | ||||
| 	return a.SignResponseWithOptions(pKey, pubKeyId, r, body, SignatureOption{}) | ||||
| } | ||||
| 
 | ||||
| func (a *asymmSigner) SignResponseWithOptions(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte, _ SignatureOption) 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, opts SignatureOption) (string, error) { | ||||
| 	return signatureString(r.Header, a.headers, addRequestTarget(r, opts), 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, opts SignatureOption) 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 !opts.ExcludeQueryStringFromPathPseudoHeader && 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 | ||||
| } | ||||
							
								
								
									
										188
									
								
								vendor/github.com/superseriousbusiness/httpsig/verifying.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								vendor/github.com/superseriousbusiness/httpsig/verifying.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,188 @@ | |||
| package httpsig | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| var _ VerifierWithOptions = &verifier{} | ||||
| 
 | ||||
| type verifier struct { | ||||
| 	header      http.Header | ||||
| 	kId         string | ||||
| 	signature   string | ||||
| 	created     int64 | ||||
| 	expires     int64 | ||||
| 	headers     []string | ||||
| 	sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (string, error) | ||||
| } | ||||
| 
 | ||||
| func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (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 { | ||||
| 	return v.VerifyWithOptions(pKey, algo, SignatureOption{}) | ||||
| } | ||||
| 
 | ||||
| func (v *verifier) VerifyWithOptions(pKey crypto.PublicKey, algo Algorithm, opts SignatureOption) error { | ||||
| 	s, err := signerFromString(string(algo)) | ||||
| 	if err == nil { | ||||
| 		return v.asymmVerify(s, pKey, opts) | ||||
| 	} | ||||
| 	m, err := macerFromString(string(algo)) | ||||
| 	if err == nil { | ||||
| 		return v.macVerify(m, pKey, opts) | ||||
| 	} | ||||
| 	return fmt.Errorf("no crypto implementation available for %q: %s", algo, err) | ||||
| } | ||||
| 
 | ||||
| func (v *verifier) macVerify(m macer, pKey crypto.PublicKey, opts SignatureOption) 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, opts) | ||||
| 	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, opts SignatureOption) error { | ||||
| 	toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires, opts) | ||||
| 	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 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue