diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | docs/federation/federating_with_gotosocial.md | 2 | ||||
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | internal/federation/authenticate.go | 62 | ||||
| -rw-r--r-- | internal/federation/federatingprotocol_test.go | 2 | ||||
| -rw-r--r-- | internal/gtscontext/context.go | 6 | ||||
| -rw-r--r-- | internal/middleware/signaturecheck.go | 2 | ||||
| -rw-r--r-- | internal/transport/signing.go | 2 | ||||
| -rw-r--r-- | internal/transport/transport.go | 2 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/LICENSE | 29 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/README.md | 101 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/algorithms.go | 532 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/digest.go | 120 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/httpsig.go | 413 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/signing.go | 350 | ||||
| -rw-r--r-- | vendor/github.com/superseriousbusiness/httpsig/verifying.go | 188 | ||||
| -rw-r--r-- | vendor/modules.txt | 3 | 
18 files changed, 1799 insertions, 22 deletions
| @@ -263,7 +263,6 @@ The following open source libraries, frameworks, and tools are used by GoToSocia    - [gin-contrib/gzip](https://github.com/gin-contrib/gzip); Gin gzip middleware. [MIT License](https://spdx.org/licenses/MIT.html).    - [gin-contrib/sessions](https://github.com/gin-contrib/sessions); Gin sessions middleware. [MIT License](https://spdx.org/licenses/MIT.html).    - [gin-gonic/gin](https://github.com/gin-gonic/gin); speedy router engine. [MIT License](https://spdx.org/licenses/MIT.html). -- [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).  - [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).  - [google/wuffs](https://github.com/google/wuffs); png-stripping code. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).  - Go-Playground: @@ -306,6 +305,7 @@ The following open source libraries, frameworks, and tools are used by GoToSocia  - superseriousbusiness:    - [superseriousbusiness/activity](https://github.com/superseriousbusiness/activity) forked from [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).    - [superseriousbusiness/exif-terminator](https://codeberg.org/superseriousbusiness/exif-terminator); EXIF data removal. [GNU AGPL v3 LICENSE](https://spdx.org/licenses/AGPL-3.0-or-later.html). +  - [superseriousbusiness/httpsig](https://github.com/superseriousbusiness/httpsig) forked from [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).    - [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); OAuth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html).  - [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification for Markdown-submitted posts. [MIT License](https://spdx.org/licenses/MIT.html).  - [uber-go/automaxprocs](https://github.com/uber-go/automaxprocs); GOMAXPROCS automation. [MIT License](https://spdx.org/licenses/MIT.html). diff --git a/docs/federation/federating_with_gotosocial.md b/docs/federation/federating_with_gotosocial.md index 3f4458f71..563536556 100644 --- a/docs/federation/federating_with_gotosocial.md +++ b/docs/federation/federating_with_gotosocial.md @@ -10,7 +10,7 @@ GoToSocial will also sign all outgoing `GET` and `POST` requests that it makes t  This behavior is the equivalent of Mastodon's [AUTHORIZED_FETCH / "secure mode"](https://docs.joinmastodon.org/admin/config/#authorized_fetch). -GoToSocial uses the [go-fed/httpsig](https://github.com/go-fed/httpsig) library for signing outgoing requests, and for parsing and validating the signatures of incoming requests. This library strictly follows the [Cavage http signature RFC](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12), which is the same RFC used by other implementations like Mastodon, Pixelfed, Akkoma/Pleroma, etc. (This RFC has since been superceded by the [httpbis http signature RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures), but this is not yet widely implemented.) +GoToSocial uses the [superseriousbusiness/httpsig](https://github.com/superseriousbusiness/httpsign) library (forked from go-fed) for signing outgoing requests, and for parsing and validating the signatures of incoming requests. This library strictly follows the [Cavage http signature RFC](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12), which is the same RFC used by other implementations like Mastodon, Pixelfed, Akkoma/Pleroma, etc. (This RFC has since been superceded by the [httpbis http signature RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures), but this is not yet widely implemented.)  ### Incoming Requests @@ -31,7 +31,6 @@ require (  	github.com/gin-contrib/gzip v0.0.6  	github.com/gin-contrib/sessions v0.0.5  	github.com/gin-gonic/gin v1.9.1 -	github.com/go-fed/httpsig v1.1.0  	github.com/go-playground/form/v4 v4.2.1  	github.com/google/uuid v1.5.0  	github.com/gorilla/feeds v1.1.2 @@ -48,6 +47,7 @@ require (  	github.com/spf13/viper v1.18.2  	github.com/stretchr/testify v1.8.4  	github.com/superseriousbusiness/activity v1.4.0-gts +	github.com/superseriousbusiness/httpsig v1.2.0-SSB  	github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8  	github.com/tdewolff/minify/v2 v2.20.14  	github.com/technologize/otel-go-contrib v1.1.0 @@ -106,6 +106,7 @@ require (  	github.com/gabriel-vasile/mimetype v1.4.2 // indirect  	github.com/gin-contrib/sse v0.1.0 // indirect  	github.com/go-errors/errors v1.4.1 // indirect +	github.com/go-fed/httpsig v1.1.0 // indirect  	github.com/go-jose/go-jose/v3 v3.0.1 // indirect  	github.com/go-logr/logr v1.4.1 // indirect  	github.com/go-logr/stdr v1.2.2 // indirect @@ -489,6 +489,8 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430  github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4=  github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8=  github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB/go.mod h1:ymKGfy9kg4dIdraeZRAdobMS/flzLk3VcRPLpEWOAXg= +github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9f0XVshx2CTDtkaWj0= +github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28=  github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=  github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=  github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go index fe611af8c..6899e5ad3 100644 --- a/internal/federation/authenticate.go +++ b/internal/federation/authenticate.go @@ -28,7 +28,6 @@ import (  	"time"  	"codeberg.org/gruf/go-kv" -	"github.com/go-fed/httpsig"  	"github.com/superseriousbusiness/activity/streams"  	"github.com/superseriousbusiness/gotosocial/internal/ap"  	"github.com/superseriousbusiness/gotosocial/internal/config" @@ -37,6 +36,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/httpsig"  )  var ( @@ -509,24 +509,62 @@ var signingAlgorithms = []httpsig.Algorithm{  	httpsig.ED25519,    // Try ED25519 as a long shot.  } -// verifyAuth verifies auth using generated verifier, according to pubkey and our supported signing algorithms. -func verifyAuth(l *log.Entry, verifier httpsig.Verifier, pubKey *rsa.PublicKey) bool { +// Cheeky type to wrap a signing option with a +// description of that option for logging purposes. +type signingOption struct { +	desc   string                  // Description of this options set. +	sigOpt httpsig.SignatureOption // The options themselves. +} + +var signingOptions = []signingOption{ +	{ +		// Prefer include query params. +		desc: "include query params", +		sigOpt: httpsig.SignatureOption{ +			ExcludeQueryStringFromPathPseudoHeader: false, +		}, +	}, +	{ +		// Fall back to exclude query params. +		desc: "exclude query params", +		sigOpt: httpsig.SignatureOption{ +			ExcludeQueryStringFromPathPseudoHeader: true, +		}, +	}, +} + +// verifyAuth verifies auth using generated verifier, +// according to pubkey, our supported signing algorithms, +// and signature options. The loops in the function are +// arranged in such a way that the most common combos are +// tried first, so that we can hopefully succeed quickly +// without wasting too many CPU cycles. +func verifyAuth( +	l *log.Entry, +	verifier httpsig.VerifierWithOptions, +	pubKey *rsa.PublicKey, +) bool {  	if pubKey == nil {  		return false  	} -	// Loop through all supported algorithms. +	// Loop through supported algorithms.  	for _, algo := range signingAlgorithms { -		// Verify according to pubkey and algo. -		err := verifier.Verify(pubKey, algo) -		if err != nil { -			l.Tracef("authentication NOT PASSED with %s: %v", algo, err) -			continue -		} +		// Loop through signing options. +		for _, opt := range signingOptions { -		l.Tracef("authenticated PASSED with %s", algo) -		return true +			// Try to verify according to this pubkey, +			// algo, and signing options combination. +			err := verifier.VerifyWithOptions(pubKey, algo, opt.sigOpt) +			if err != nil { +				l.Tracef("authentication NOT PASSED with %s (%s): %v", algo, opt.desc, err) +				continue +			} + +			l.Tracef("authenticated PASSED with %s (%s)", algo, opt.desc) +			return true +		}  	}  	return false diff --git a/internal/federation/federatingprotocol_test.go b/internal/federation/federatingprotocol_test.go index 7a8343048..999569c85 100644 --- a/internal/federation/federatingprotocol_test.go +++ b/internal/federation/federatingprotocol_test.go @@ -27,12 +27,12 @@ import (  	"net/url"  	"testing" -	"github.com/go-fed/httpsig"  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/ap"  	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/testrig" +	"github.com/superseriousbusiness/httpsig"  )  type FederatingProtocolTestSuite struct { diff --git a/internal/gtscontext/context.go b/internal/gtscontext/context.go index 46f2899fa..0d5ed5340 100644 --- a/internal/gtscontext/context.go +++ b/internal/gtscontext/context.go @@ -21,8 +21,8 @@ import (  	"context"  	"net/url" -	"github.com/go-fed/httpsig"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/httpsig"  )  // package private context key type. @@ -129,8 +129,8 @@ func SetOtherIRIs(ctx context.Context, iris []*url.URL) context.Context {  // HTTPSignatureVerifier returns an http signature verifier for the current ActivityPub  // request chain. This verifier can be called to authenticate the current request. -func HTTPSignatureVerifier(ctx context.Context) httpsig.Verifier { -	verifier, _ := ctx.Value(httpSigVerifierKey).(httpsig.Verifier) +func HTTPSignatureVerifier(ctx context.Context) httpsig.VerifierWithOptions { +	verifier, _ := ctx.Value(httpSigVerifierKey).(httpsig.VerifierWithOptions)  	return verifier  } diff --git a/internal/middleware/signaturecheck.go b/internal/middleware/signaturecheck.go index 87c7aac01..ea63ec4f0 100644 --- a/internal/middleware/signaturecheck.go +++ b/internal/middleware/signaturecheck.go @@ -26,7 +26,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/log"  	"github.com/gin-gonic/gin" -	"github.com/go-fed/httpsig" +	"github.com/superseriousbusiness/httpsig"  )  const ( diff --git a/internal/transport/signing.go b/internal/transport/signing.go index a9eafeb54..dcd8e206f 100644 --- a/internal/transport/signing.go +++ b/internal/transport/signing.go @@ -18,7 +18,7 @@  package transport  import ( -	"github.com/go-fed/httpsig" +	"github.com/superseriousbusiness/httpsig"  )  var ( diff --git a/internal/transport/transport.go b/internal/transport/transport.go index ac56c73cb..558e187f0 100644 --- a/internal/transport/transport.go +++ b/internal/transport/transport.go @@ -27,10 +27,10 @@ import (  	"sync"  	"time" -	"github.com/go-fed/httpsig"  	"github.com/superseriousbusiness/gotosocial/internal/gtscontext"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/httpclient" +	"github.com/superseriousbusiness/httpsig"  )  // Transport implements the pub.Transport interface with some additional functionality for fetching remote media. diff --git a/vendor/github.com/superseriousbusiness/httpsig/LICENSE b/vendor/github.com/superseriousbusiness/httpsig/LICENSE new file mode 100644 index 000000000..a9e8aefad --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/LICENSE @@ -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. diff --git a/vendor/github.com/superseriousbusiness/httpsig/README.md b/vendor/github.com/superseriousbusiness/httpsig/README.md new file mode 100644 index 000000000..8e45e8d38 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/README.md @@ -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 diff --git a/vendor/github.com/superseriousbusiness/httpsig/algorithms.go b/vendor/github.com/superseriousbusiness/httpsig/algorithms.go new file mode 100644 index 000000000..9595941be --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/algorithms.go @@ -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) +	} +} diff --git a/vendor/github.com/superseriousbusiness/httpsig/digest.go b/vendor/github.com/superseriousbusiness/httpsig/digest.go new file mode 100644 index 000000000..bf9e3a914 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/digest.go @@ -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 +} diff --git a/vendor/github.com/superseriousbusiness/httpsig/httpsig.go b/vendor/github.com/superseriousbusiness/httpsig/httpsig.go new file mode 100644 index 000000000..8864da055 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/httpsig.go @@ -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 +} diff --git a/vendor/github.com/superseriousbusiness/httpsig/signing.go b/vendor/github.com/superseriousbusiness/httpsig/signing.go new file mode 100644 index 000000000..a2fa38c5b --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/signing.go @@ -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 +} diff --git a/vendor/github.com/superseriousbusiness/httpsig/verifying.go b/vendor/github.com/superseriousbusiness/httpsig/verifying.go new file mode 100644 index 000000000..52a142ac1 --- /dev/null +++ b/vendor/github.com/superseriousbusiness/httpsig/verifying.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index de5d99d68..a9dd0bc6e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -676,6 +676,9 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2  # github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB  ## explicit; go 1.12  github.com/superseriousbusiness/go-png-image-structure/v2 +# github.com/superseriousbusiness/httpsig v1.2.0-SSB +## explicit; go 1.21 +github.com/superseriousbusiness/httpsig  # github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8  ## explicit; go 1.13  github.com/superseriousbusiness/oauth2/v4 | 
