Browse Source
* [feature] Verify signatures both with + without query params * Bump to tagged versionpull/2592/head
18 changed files with 1799 additions and 22 deletions
@ -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. |
||||
@ -0,0 +1,101 @@
|
||||
# httpsig |
||||
|
||||
**THIS IS A FORK OF https://github.com/go-fed/httpsig, WHICH WAS NO LONGER MAINTAINED. THANK YOU TO [cjslep](https://github.com/cjslep) FOR ALL YOUR HARD WORK!** |
||||
|
||||
> HTTP Signatures made simple |
||||
|
||||
`go get github.com/superseriousbusiness/httpsig` |
||||
|
||||
Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). |
||||
|
||||
Supports many different combinations of MAC, HMAC signing of hash, or RSA |
||||
signing of hash schemes. Its goals are: |
||||
|
||||
* Have a very simple interface for signing and validating |
||||
* Support a variety of signing algorithms and combinations |
||||
* Support setting either headers (`Authorization` or `Signature`) |
||||
* Remaining flexible with headers included in the signing string |
||||
* Support both HTTP requests and responses |
||||
* Explicitly not support known-cryptographically weak algorithms |
||||
* Support automatic signing and validating Digest headers |
||||
|
||||
## How to use |
||||
|
||||
`import "github.com/superseriousbusiness/httpsig"` |
||||
|
||||
### Signing |
||||
|
||||
Signing a request or response requires creating a new `Signer` and using it: |
||||
|
||||
```go |
||||
func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { |
||||
prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} |
||||
digestAlgorithm := DigestSha256 |
||||
// The "Date" and "Digest" headers must already be set on r, as well as r.URL. |
||||
headersToSign := []string{httpsig.RequestTarget, "date", "digest"} |
||||
signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// To sign the digest, we need to give the signer a copy of the body... |
||||
// ...but it is optional, no digest will be signed if given "nil" |
||||
body := ... |
||||
// If r were a http.ResponseWriter, call SignResponse instead. |
||||
return signer.SignRequest(privateKey, pubKeyId, r, body) |
||||
} |
||||
``` |
||||
|
||||
`Signer`s are not safe for concurrent use by goroutines, so be sure to guard |
||||
access: |
||||
|
||||
```go |
||||
type server struct { |
||||
signer httpsig.Signer |
||||
mu *sync.Mutex |
||||
} |
||||
|
||||
func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { |
||||
privateKey := ... |
||||
pubKeyId := ... |
||||
// Set headers and such on w |
||||
s.mu.Lock() |
||||
defer s.mu.Unlock() |
||||
// To sign the digest, we need to give the signer a copy of the response body... |
||||
// ...but it is optional, no digest will be signed if given "nil" |
||||
body := ... |
||||
err := s.signer.SignResponse(privateKey, pubKeyId, w, body) |
||||
if err != nil { |
||||
... |
||||
} |
||||
... |
||||
} |
||||
``` |
||||
|
||||
The `pubKeyId` will be used at verification time. |
||||
|
||||
### Verifying |
||||
|
||||
Verifying requires an application to use the `pubKeyId` to both retrieve the key |
||||
needed for verification as well as determine the algorithm to use. Use a |
||||
`Verifier`: |
||||
|
||||
```go |
||||
func verify(r *http.Request) error { |
||||
verifier, err := httpsig.NewVerifier(r) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
pubKeyId := verifier.KeyId() |
||||
var algo httpsig.Algorithm = ... |
||||
var pubKey crypto.PublicKey = ... |
||||
// The verifier will verify the Digest in addition to the HTTP signature |
||||
return verifier.Verify(pubKey, algo) |
||||
} |
||||
``` |
||||
|
||||
`Verifier`s are not safe for concurrent use by goroutines, but since they are |
||||
constructed on a per-request or per-response basis it should not be a common |
||||
restriction. |
||||
|
||||
[License-Image]: https://img.shields.io/github/license/go-fed/httpsig?color=blue |
||||
[License-Url]: https://opensource.org/licenses/BSD-3-Clause |
||||
@ -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) |
||||
} |
||||
} |
||||
@ -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 |
||||
} |
||||
@ -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 |
||||
} |
||||
@ -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 |
||||
} |
||||
@ -0,0 +1,188 @@
|
||||
package httpsig |
||||
|
||||
import ( |
||||
"crypto" |
||||
"encoding/base64" |
||||
"errors" |
||||
"fmt" |
||||
"net/http" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
var _ VerifierWithOptions = &verifier{} |
||||
|
||||
type verifier struct { |
||||
header http.Header |
||||
kId string |
||||
signature string |
||||
created int64 |
||||
expires int64 |
||||
headers []string |
||||
sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (string, error) |
||||
} |
||||
|
||||
func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64, SignatureOption) (string, error)) (*verifier, error) { |
||||
scheme, s, err := getSignatureScheme(h) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) |
||||
if created != 0 { |
||||
//check if created is not in the future, we assume a maximum clock offset of 10 seconds
|
||||
now := time.Now().Unix() |
||||
if created-now > 10 { |
||||
return nil, errors.New("created is in the future") |
||||
} |
||||
} |
||||
if expires != 0 { |
||||
//check if expires is in the past, we assume a maximum clock offset of 10 seconds
|
||||
now := time.Now().Unix() |
||||
if now-expires > 10 { |
||||
return nil, errors.New("signature expired") |
||||
} |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &verifier{ |
||||
header: h, |
||||
kId: kId, |
||||
signature: sig, |
||||
created: created, |
||||
expires: expires, |
||||
headers: headers, |
||||
sigStringFn: sigStringFn, |
||||
}, nil |
||||
} |
||||
|
||||
func (v *verifier) KeyId() string { |
||||
return v.kId |
||||
} |
||||
|
||||
func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { |
||||
return v.VerifyWithOptions(pKey, algo, SignatureOption{}) |
||||
} |
||||
|
||||
func (v *verifier) VerifyWithOptions(pKey crypto.PublicKey, algo Algorithm, opts SignatureOption) error { |
||||
s, err := signerFromString(string(algo)) |
||||
if err == nil { |
||||
return v.asymmVerify(s, pKey, opts) |
||||
} |
||||
m, err := macerFromString(string(algo)) |
||||
if err == nil { |
||||
return v.macVerify(m, pKey, opts) |
||||
} |
||||
return fmt.Errorf("no crypto implementation available for %q: %s", algo, err) |
||||
} |
||||
|
||||
func (v *verifier) macVerify(m macer, pKey crypto.PublicKey, opts SignatureOption) error { |
||||
key, ok := pKey.([]byte) |
||||
if !ok { |
||||
return fmt.Errorf("public key for MAC verifying must be of type []byte") |
||||
} |
||||
signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires, opts) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
actualMAC, err := base64.StdEncoding.DecodeString(v.signature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ok, err = m.Equal([]byte(signature), actualMAC, key) |
||||
if err != nil { |
||||
return err |
||||
} else if !ok { |
||||
return fmt.Errorf("invalid http signature") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey, opts SignatureOption) error { |
||||
toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires, opts) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
signature, err := base64.StdEncoding.DecodeString(v.signature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
err = s.Verify(pKey, []byte(toHash), signature) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { |
||||
s := h.Get(string(Signature)) |
||||
sigHasAll := strings.Contains(s, keyIdParameter) || |
||||
strings.Contains(s, headersParameter) || |
||||
strings.Contains(s, signatureParameter) |
||||
a := h.Get(string(Authorization)) |
||||
authHasAll := strings.Contains(a, keyIdParameter) || |
||||
strings.Contains(a, headersParameter) || |
||||
strings.Contains(a, signatureParameter) |
||||
if sigHasAll && authHasAll { |
||||
err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) |
||||
return |
||||
} else if !sigHasAll && !authHasAll { |
||||
err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) |
||||
return |
||||
} else if sigHasAll { |
||||
val = s |
||||
scheme = Signature |
||||
return |
||||
} else { // authHasAll
|
||||
val = a |
||||
scheme = Authorization |
||||
return |
||||
} |
||||
} |
||||
|
||||
func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { |
||||
if as := scheme.authScheme(); len(as) > 0 { |
||||
s = strings.TrimPrefix(s, as+prefixSeparater) |
||||
} |
||||
params := strings.Split(s, parameterSeparater) |
||||
for _, p := range params { |
||||
kv := strings.SplitN(p, parameterKVSeparater, 2) |
||||
if len(kv) != 2 { |
||||
err = fmt.Errorf("malformed http signature parameter: %v", kv) |
||||
return |
||||
} |
||||
k := kv[0] |
||||
v := strings.Trim(kv[1], parameterValueDelimiter) |
||||
switch k { |
||||
case keyIdParameter: |
||||
kId = v |
||||
case createdKey: |
||||
created, err = strconv.ParseInt(v, 10, 64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
case expiresKey: |
||||
expires, err = strconv.ParseInt(v, 10, 64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
case algorithmParameter: |
||||
// Deprecated, ignore
|
||||
case headersParameter: |
||||
headers = strings.Split(v, headerParameterValueDelim) |
||||
case signatureParameter: |
||||
sig = v |
||||
default: |
||||
// Ignore unrecognized parameters
|
||||
} |
||||
} |
||||
if len(kId) == 0 { |
||||
err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) |
||||
} else if len(sig) == 0 { |
||||
err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) |
||||
} else if len(headers) == 0 { // Optional
|
||||
headers = defaultHeaders |
||||
} |
||||
return |
||||
} |
||||
Loading…
Reference in new issue