summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--docs/federation/federating_with_gotosocial.md2
-rw-r--r--go.mod3
-rw-r--r--go.sum2
-rw-r--r--internal/federation/authenticate.go62
-rw-r--r--internal/federation/federatingprotocol_test.go2
-rw-r--r--internal/gtscontext/context.go6
-rw-r--r--internal/middleware/signaturecheck.go2
-rw-r--r--internal/transport/signing.go2
-rw-r--r--internal/transport/transport.go2
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/LICENSE29
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/README.md101
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/algorithms.go532
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/digest.go120
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/httpsig.go413
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/signing.go350
-rw-r--r--vendor/github.com/superseriousbusiness/httpsig/verifying.go188
-rw-r--r--vendor/modules.txt3
18 files changed, 1799 insertions, 22 deletions
diff --git a/README.md b/README.md
index c25e21cb9..42dd89fe8 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/go.mod b/go.mod
index 82ab66209..5afb0cb45 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 12fc8916b..306599665 100644
--- a/go.sum
+++ b/go.sum
@@ -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