From 5d0e3d9c354906409a514b437d70f7a9db90703a Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Sun, 2 Mar 2025 13:28:38 +0100 Subject: [chore] github.com/superseriousbusiness/httpsig -> codeberg.org/superseriousbusiness/httpsig (#3854) --- .../superseriousbusiness/httpsig/LICENSE | 29 ++ .../superseriousbusiness/httpsig/README.md | 101 ++++ .../superseriousbusiness/httpsig/algorithms.go | 532 +++++++++++++++++++++ .../superseriousbusiness/httpsig/digest.go | 120 +++++ .../superseriousbusiness/httpsig/httpsig.go | 413 ++++++++++++++++ .../superseriousbusiness/httpsig/signing.go | 350 ++++++++++++++ .../superseriousbusiness/httpsig/verifying.go | 188 ++++++++ .../superseriousbusiness/httpsig/LICENSE | 29 -- .../superseriousbusiness/httpsig/README.md | 101 ---- .../superseriousbusiness/httpsig/algorithms.go | 532 --------------------- .../superseriousbusiness/httpsig/digest.go | 120 ----- .../superseriousbusiness/httpsig/httpsig.go | 413 ---------------- .../superseriousbusiness/httpsig/signing.go | 350 -------------- .../superseriousbusiness/httpsig/verifying.go | 188 -------- vendor/modules.txt | 6 +- 15 files changed, 1736 insertions(+), 1736 deletions(-) create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/LICENSE create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/README.md create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/algorithms.go create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/digest.go create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/httpsig.go create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/signing.go create mode 100644 vendor/codeberg.org/superseriousbusiness/httpsig/verifying.go delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/LICENSE delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/README.md delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/algorithms.go delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/digest.go delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/httpsig.go delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/signing.go delete mode 100644 vendor/github.com/superseriousbusiness/httpsig/verifying.go (limited to 'vendor') diff --git a/vendor/codeberg.org/superseriousbusiness/httpsig/LICENSE b/vendor/codeberg.org/superseriousbusiness/httpsig/LICENSE new file mode 100644 index 000000000..a9e8aefad --- /dev/null +++ b/vendor/codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/README.md b/vendor/codeberg.org/superseriousbusiness/httpsig/README.md new file mode 100644 index 000000000..2108ed06c --- /dev/null +++ b/vendor/codeberg.org/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 codeberg.org/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 "codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/algorithms.go b/vendor/codeberg.org/superseriousbusiness/httpsig/algorithms.go new file mode 100644 index 000000000..9595941be --- /dev/null +++ b/vendor/codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/digest.go b/vendor/codeberg.org/superseriousbusiness/httpsig/digest.go new file mode 100644 index 000000000..bf9e3a914 --- /dev/null +++ b/vendor/codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/httpsig.go b/vendor/codeberg.org/superseriousbusiness/httpsig/httpsig.go new file mode 100644 index 000000000..8864da055 --- /dev/null +++ b/vendor/codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/signing.go b/vendor/codeberg.org/superseriousbusiness/httpsig/signing.go new file mode 100644 index 000000000..a2fa38c5b --- /dev/null +++ b/vendor/codeberg.org/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/codeberg.org/superseriousbusiness/httpsig/verifying.go b/vendor/codeberg.org/superseriousbusiness/httpsig/verifying.go new file mode 100644 index 000000000..52a142ac1 --- /dev/null +++ b/vendor/codeberg.org/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/github.com/superseriousbusiness/httpsig/LICENSE b/vendor/github.com/superseriousbusiness/httpsig/LICENSE deleted file mode 100644 index a9e8aefad..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 8e45e8d38..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# 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 deleted file mode 100644 index 9595941be..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/algorithms.go +++ /dev/null @@ -1,532 +0,0 @@ -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 deleted file mode 100644 index bf9e3a914..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/digest.go +++ /dev/null @@ -1,120 +0,0 @@ -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 deleted file mode 100644 index 8864da055..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/httpsig.go +++ /dev/null @@ -1,413 +0,0 @@ -// 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 deleted file mode 100644 index a2fa38c5b..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/signing.go +++ /dev/null @@ -1,350 +0,0 @@ -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 deleted file mode 100644 index 52a142ac1..000000000 --- a/vendor/github.com/superseriousbusiness/httpsig/verifying.go +++ /dev/null @@ -1,188 +0,0 @@ -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 fd573e16e..47b49c650 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -248,6 +248,9 @@ codeberg.org/superseriousbusiness/activity/streams/vocab # codeberg.org/superseriousbusiness/exif-terminator v0.9.1 ## explicit; go 1.21 codeberg.org/superseriousbusiness/exif-terminator +# codeberg.org/superseriousbusiness/httpsig v1.3.0-SSB +## explicit; go 1.21 +codeberg.org/superseriousbusiness/httpsig # github.com/DmitriyVTitov/size v1.5.0 ## explicit; go 1.14 github.com/DmitriyVTitov/size @@ -810,9 +813,6 @@ 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 -- cgit v1.3