├── .travis.yml ├── LICENSE ├── README.md ├── algorithms.go ├── algorithms_test.go ├── digest.go ├── digest_test.go ├── go.mod ├── go.sum ├── httpsig.go ├── httpsig_test.go ├── signing.go └── verifying.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.15 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, go-fed 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # httpsig 2 | 3 | > HTTP Signatures made simple 4 | 5 | [![Build Status][Build-Status-Image]][Build-Status-Url] [![Go Reference][Go-Reference-Image]][Go-Reference-Url] 6 | [![Go Report Card][Go-Report-Card-Image]][Go-Report-Card-Url] [![License][License-Image]][License-Url] 7 | [![Chat][Chat-Image]][Chat-Url] [![OpenCollective][OpenCollective-Image]][OpenCollective-Url] 8 | 9 | `go get github.com/go-fed/httpsig` 10 | 11 | Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). 12 | 13 | Supports many different combinations of MAC, HMAC signing of hash, or RSA 14 | signing of hash schemes. Its goals are: 15 | 16 | * Have a very simple interface for signing and validating 17 | * Support a variety of signing algorithms and combinations 18 | * Support setting either headers (`Authorization` or `Signature`) 19 | * Remaining flexible with headers included in the signing string 20 | * Support both HTTP requests and responses 21 | * Explicitly not support known-cryptographically weak algorithms 22 | * Support automatic signing and validating Digest headers 23 | 24 | ## How to use 25 | 26 | `import "github.com/go-fed/httpsig"` 27 | 28 | ### Signing 29 | 30 | Signing a request or response requires creating a new `Signer` and using it: 31 | 32 | ``` 33 | func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { 34 | prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} 35 | digestAlgorithm := DigestSha256 36 | // The "Date" and "Digest" headers must already be set on r, as well as r.URL. 37 | headersToSign := []string{httpsig.RequestTarget, "date", "digest"} 38 | signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) 39 | if err != nil { 40 | return err 41 | } 42 | // To sign the digest, we need to give the signer a copy of the body... 43 | // ...but it is optional, no digest will be signed if given "nil" 44 | body := ... 45 | // If r were a http.ResponseWriter, call SignResponse instead. 46 | return signer.SignRequest(privateKey, pubKeyId, r, body) 47 | } 48 | ``` 49 | 50 | `Signer`s are not safe for concurrent use by goroutines, so be sure to guard 51 | access: 52 | 53 | ``` 54 | type server struct { 55 | signer httpsig.Signer 56 | mu *sync.Mutex 57 | } 58 | 59 | func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { 60 | privateKey := ... 61 | pubKeyId := ... 62 | // Set headers and such on w 63 | s.mu.Lock() 64 | defer s.mu.Unlock() 65 | // To sign the digest, we need to give the signer a copy of the response body... 66 | // ...but it is optional, no digest will be signed if given "nil" 67 | body := ... 68 | err := s.signer.SignResponse(privateKey, pubKeyId, w, body) 69 | if err != nil { 70 | ... 71 | } 72 | ... 73 | } 74 | ``` 75 | 76 | The `pubKeyId` will be used at verification time. 77 | 78 | ### Verifying 79 | 80 | Verifying requires an application to use the `pubKeyId` to both retrieve the key 81 | needed for verification as well as determine the algorithm to use. Use a 82 | `Verifier`: 83 | 84 | ``` 85 | func verify(r *http.Request) error { 86 | verifier, err := httpsig.NewVerifier(r) 87 | if err != nil { 88 | return err 89 | } 90 | pubKeyId := verifier.KeyId() 91 | var algo httpsig.Algorithm = ... 92 | var pubKey crypto.PublicKey = ... 93 | // The verifier will verify the Digest in addition to the HTTP signature 94 | return verifier.Verify(pubKey, algo) 95 | } 96 | ``` 97 | 98 | `Verifier`s are not safe for concurrent use by goroutines, but since they are 99 | constructed on a per-request or per-response basis it should not be a common 100 | restriction. 101 | 102 | [Build-Status-Image]: https://travis-ci.org/go-fed/httpsig.svg?branch=master 103 | [Build-Status-Url]: https://travis-ci.org/go-fed/httpsig 104 | [Go-Reference-Image]: https://pkg.go.dev/badge/github.com/go-fed/httpsig 105 | [Go-Reference-Url]: https://pkg.go.dev/github.com/go-fed/httpsig 106 | [Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/go-fed/httpsig 107 | [Go-Report-Card-Url]: https://goreportcard.com/report/github.com/go-fed/httpsig 108 | [License-Image]: https://img.shields.io/github/license/go-fed/httpsig?color=blue 109 | [License-Url]: https://opensource.org/licenses/BSD-3-Clause 110 | [Chat-Image]: https://img.shields.io/matrix/go-fed:feneas.org?server_fqdn=matrix.org 111 | [Chat-Url]: https://matrix.to/#/!BLOSvIyKTDLIVjRKSc:feneas.org?via=feneas.org&via=matrix.org 112 | [OpenCollective-Image]: https://img.shields.io/opencollective/backers/go-fed-activitypub-labs 113 | [OpenCollective-Url]: https://opencollective.com/go-fed-activitypub-labs 114 | -------------------------------------------------------------------------------- /algorithms.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/hmac" 7 | "crypto/rsa" 8 | "crypto/sha1" 9 | "crypto/sha256" 10 | "crypto/sha512" 11 | "crypto/subtle" // Use should trigger great care 12 | "encoding/asn1" 13 | "errors" 14 | "fmt" 15 | "hash" 16 | "io" 17 | "math/big" 18 | "strings" 19 | 20 | "golang.org/x/crypto/blake2b" 21 | "golang.org/x/crypto/blake2s" 22 | "golang.org/x/crypto/ed25519" 23 | "golang.org/x/crypto/ripemd160" 24 | "golang.org/x/crypto/sha3" 25 | "golang.org/x/crypto/ssh" 26 | ) 27 | 28 | const ( 29 | hmacPrefix = "hmac" 30 | rsaPrefix = "rsa" 31 | sshPrefix = "ssh" 32 | ecdsaPrefix = "ecdsa" 33 | ed25519Prefix = "ed25519" 34 | md4String = "md4" 35 | md5String = "md5" 36 | sha1String = "sha1" 37 | sha224String = "sha224" 38 | sha256String = "sha256" 39 | sha384String = "sha384" 40 | sha512String = "sha512" 41 | md5sha1String = "md5sha1" 42 | ripemd160String = "ripemd160" 43 | sha3_224String = "sha3-224" 44 | sha3_256String = "sha3-256" 45 | sha3_384String = "sha3-384" 46 | sha3_512String = "sha3-512" 47 | sha512_224String = "sha512-224" 48 | sha512_256String = "sha512-256" 49 | blake2s_256String = "blake2s-256" 50 | blake2b_256String = "blake2b-256" 51 | blake2b_384String = "blake2b-384" 52 | blake2b_512String = "blake2b-512" 53 | ) 54 | 55 | var blake2Algorithms = map[crypto.Hash]bool{ 56 | crypto.BLAKE2s_256: true, 57 | crypto.BLAKE2b_256: true, 58 | crypto.BLAKE2b_384: true, 59 | crypto.BLAKE2b_512: true, 60 | } 61 | 62 | var hashToDef = map[crypto.Hash]struct { 63 | name string 64 | new func(key []byte) (hash.Hash, error) // Only MACers will accept a key 65 | }{ 66 | // Which standard names these? 67 | // The spec lists the following as a canonical reference, which is dead: 68 | // http://www.iana.org/assignments/signature-algorithms 69 | // 70 | // Note that the forbidden hashes have an invalid 'new' function. 71 | crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, 72 | crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, 73 | // Temporarily enable SHA1 because of issue https://github.com/golang/go/issues/37278 74 | crypto.SHA1: {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }}, 75 | crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, 76 | crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, 77 | crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }}, 78 | crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }}, 79 | crypto.MD5SHA1: {md5sha1String, func(key []byte) (hash.Hash, error) { return nil, nil }}, 80 | crypto.RIPEMD160: {ripemd160String, func(key []byte) (hash.Hash, error) { return ripemd160.New(), nil }}, 81 | crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }}, 82 | crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }}, 83 | crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }}, 84 | crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }}, 85 | crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }}, 86 | crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }}, 87 | crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }}, 88 | crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }}, 89 | crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }}, 90 | crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }}, 91 | } 92 | 93 | var stringToHash map[string]crypto.Hash 94 | 95 | const ( 96 | defaultAlgorithm = RSA_SHA256 97 | defaultAlgorithmHashing = sha256String 98 | ) 99 | 100 | func init() { 101 | stringToHash = make(map[string]crypto.Hash, len(hashToDef)) 102 | for k, v := range hashToDef { 103 | stringToHash[v.name] = k 104 | } 105 | // This should guarantee that at runtime the defaultAlgorithm will not 106 | // result in errors when fetching a macer or signer (see algorithms.go) 107 | if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil { 108 | panic(err) 109 | } else if !ok { 110 | panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm)) 111 | } 112 | } 113 | 114 | func isForbiddenHash(h crypto.Hash) bool { 115 | switch h { 116 | // Not actually cryptographically secure 117 | case crypto.MD4: 118 | fallthrough 119 | case crypto.MD5: 120 | fallthrough 121 | case crypto.MD5SHA1: // shorthand for crypto/tls, not actually implemented 122 | return true 123 | } 124 | // Still cryptographically secure 125 | return false 126 | } 127 | 128 | // signer is an internally public type. 129 | type signer interface { 130 | Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) 131 | Verify(pub crypto.PublicKey, toHash, signature []byte) error 132 | String() string 133 | } 134 | 135 | // macer is an internally public type. 136 | type macer interface { 137 | Sign(sig, key []byte) ([]byte, error) 138 | Equal(sig, actualMAC, key []byte) (bool, error) 139 | String() string 140 | } 141 | 142 | var _ macer = &hmacAlgorithm{} 143 | 144 | type hmacAlgorithm struct { 145 | fn func(key []byte) (hash.Hash, error) 146 | kind crypto.Hash 147 | } 148 | 149 | func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { 150 | hs, err := h.fn(key) 151 | if err = setSig(hs, sig); err != nil { 152 | return nil, err 153 | } 154 | return hs.Sum(nil), nil 155 | } 156 | 157 | func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { 158 | hs, err := h.fn(key) 159 | if err != nil { 160 | return false, err 161 | } 162 | defer hs.Reset() 163 | err = setSig(hs, sig) 164 | if err != nil { 165 | return false, err 166 | } 167 | expected := hs.Sum(nil) 168 | return hmac.Equal(actualMAC, expected), nil 169 | } 170 | 171 | func (h *hmacAlgorithm) String() string { 172 | return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name) 173 | } 174 | 175 | var _ signer = &rsaAlgorithm{} 176 | 177 | type rsaAlgorithm struct { 178 | hash.Hash 179 | kind crypto.Hash 180 | sshSigner ssh.Signer 181 | } 182 | 183 | func (r *rsaAlgorithm) setSig(b []byte) error { 184 | n, err := r.Write(b) 185 | if err != nil { 186 | r.Reset() 187 | return err 188 | } else if n != len(b) { 189 | r.Reset() 190 | return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) 191 | } 192 | return nil 193 | } 194 | 195 | func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { 196 | if r.sshSigner != nil { 197 | sshsig, err := r.sshSigner.Sign(rand, sig) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | return sshsig.Blob, nil 203 | } 204 | defer r.Reset() 205 | 206 | if err := r.setSig(sig); err != nil { 207 | return nil, err 208 | } 209 | rsaK, ok := p.(*rsa.PrivateKey) 210 | if !ok { 211 | return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey") 212 | } 213 | return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil)) 214 | } 215 | 216 | func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { 217 | defer r.Reset() 218 | rsaK, ok := pub.(*rsa.PublicKey) 219 | if !ok { 220 | return errors.New("crypto.PublicKey is not *rsa.PublicKey") 221 | } 222 | if err := r.setSig(toHash); err != nil { 223 | return err 224 | } 225 | return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature) 226 | } 227 | 228 | func (r *rsaAlgorithm) String() string { 229 | return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name) 230 | } 231 | 232 | var _ signer = &ed25519Algorithm{} 233 | 234 | type ed25519Algorithm struct { 235 | sshSigner ssh.Signer 236 | } 237 | 238 | func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { 239 | if r.sshSigner != nil { 240 | sshsig, err := r.sshSigner.Sign(rand, sig) 241 | if err != nil { 242 | return nil, err 243 | } 244 | 245 | return sshsig.Blob, nil 246 | } 247 | ed25519K, ok := p.(ed25519.PrivateKey) 248 | if !ok { 249 | return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey") 250 | } 251 | return ed25519.Sign(ed25519K, sig), nil 252 | } 253 | 254 | func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { 255 | ed25519K, ok := pub.(ed25519.PublicKey) 256 | if !ok { 257 | return errors.New("crypto.PublicKey is not ed25519.PublicKey") 258 | } 259 | 260 | if ed25519.Verify(ed25519K, toHash, signature) { 261 | return nil 262 | } 263 | 264 | return errors.New("ed25519 verify failed") 265 | } 266 | 267 | func (r *ed25519Algorithm) String() string { 268 | return fmt.Sprintf("%s", ed25519Prefix) 269 | } 270 | 271 | var _ signer = &ecdsaAlgorithm{} 272 | 273 | type ecdsaAlgorithm struct { 274 | hash.Hash 275 | kind crypto.Hash 276 | } 277 | 278 | func (r *ecdsaAlgorithm) setSig(b []byte) error { 279 | n, err := r.Write(b) 280 | if err != nil { 281 | r.Reset() 282 | return err 283 | } else if n != len(b) { 284 | r.Reset() 285 | return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) 286 | } 287 | return nil 288 | } 289 | 290 | type ECDSASignature struct { 291 | R, S *big.Int 292 | } 293 | 294 | func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { 295 | defer r.Reset() 296 | if err := r.setSig(sig); err != nil { 297 | return nil, err 298 | } 299 | ecdsaK, ok := p.(*ecdsa.PrivateKey) 300 | if !ok { 301 | return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey") 302 | } 303 | R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil)) 304 | if err != nil { 305 | return nil, err 306 | } 307 | 308 | signature := ECDSASignature{R: R, S: S} 309 | bytes, err := asn1.Marshal(signature) 310 | 311 | return bytes, err 312 | } 313 | 314 | func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { 315 | defer r.Reset() 316 | ecdsaK, ok := pub.(*ecdsa.PublicKey) 317 | if !ok { 318 | return errors.New("crypto.PublicKey is not *ecdsa.PublicKey") 319 | } 320 | if err := r.setSig(toHash); err != nil { 321 | return err 322 | } 323 | 324 | sig := new(ECDSASignature) 325 | _, err := asn1.Unmarshal(signature, sig) 326 | if err != nil { 327 | return err 328 | } 329 | 330 | if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) { 331 | return nil 332 | } else { 333 | return errors.New("Invalid signature") 334 | } 335 | } 336 | 337 | func (r *ecdsaAlgorithm) String() string { 338 | return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name) 339 | } 340 | 341 | var _ macer = &blakeMacAlgorithm{} 342 | 343 | type blakeMacAlgorithm struct { 344 | fn func(key []byte) (hash.Hash, error) 345 | kind crypto.Hash 346 | } 347 | 348 | func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) { 349 | hs, err := r.fn(key) 350 | if err != nil { 351 | return nil, err 352 | } 353 | if err = setSig(hs, sig); err != nil { 354 | return nil, err 355 | } 356 | return hs.Sum(nil), nil 357 | } 358 | 359 | func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { 360 | hs, err := r.fn(key) 361 | if err != nil { 362 | return false, err 363 | } 364 | defer hs.Reset() 365 | err = setSig(hs, sig) 366 | if err != nil { 367 | return false, err 368 | } 369 | expected := hs.Sum(nil) 370 | return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil 371 | } 372 | 373 | func (r *blakeMacAlgorithm) String() string { 374 | return fmt.Sprintf("%s", hashToDef[r.kind].name) 375 | } 376 | 377 | func setSig(a hash.Hash, b []byte) error { 378 | n, err := a.Write(b) 379 | if err != nil { 380 | a.Reset() 381 | return err 382 | } else if n != len(b) { 383 | a.Reset() 384 | return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) 385 | } 386 | return nil 387 | } 388 | 389 | // IsSupportedHttpSigAlgorithm returns true if the string is supported by this 390 | // library, is not a hash known to be weak, and is supported by the hardware. 391 | func IsSupportedHttpSigAlgorithm(algo string) bool { 392 | a, err := isAvailable(algo) 393 | return a && err == nil 394 | } 395 | 396 | // isAvailable is an internally public function 397 | func isAvailable(algo string) (bool, error) { 398 | c, ok := stringToHash[algo] 399 | if !ok { 400 | return false, fmt.Errorf("no match for %q", algo) 401 | } 402 | if isForbiddenHash(c) { 403 | return false, fmt.Errorf("forbidden hash type in %q", algo) 404 | } 405 | return c.Available(), nil 406 | } 407 | 408 | func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) { 409 | ok := false 410 | c, ok = stringToHash[algo] 411 | if !ok { 412 | e = fmt.Errorf("no match for %q", algo) 413 | return 414 | } 415 | if isForbiddenHash(c) { 416 | e = fmt.Errorf("forbidden hash type in %q", algo) 417 | return 418 | } 419 | algoDef, ok := hashToDef[c] 420 | if !ok { 421 | e = fmt.Errorf("have crypto.Hash %v but no definition", c) 422 | return 423 | } 424 | fn = func(key []byte) (hash.Hash, error) { 425 | h, err := algoDef.new(key) 426 | if err != nil { 427 | return nil, err 428 | } 429 | return h, nil 430 | } 431 | return 432 | } 433 | 434 | func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { 435 | fn, c, err := newAlgorithmConstructor(algo) 436 | if err != nil { 437 | return nil, c, err 438 | } 439 | h, err := fn(key) 440 | return h, c, err 441 | } 442 | 443 | func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { 444 | switch { 445 | case strings.HasPrefix(s, rsaPrefix): 446 | return &rsaAlgorithm{ 447 | sshSigner: sshSigner, 448 | }, nil 449 | case strings.HasPrefix(s, ed25519Prefix): 450 | return &ed25519Algorithm{ 451 | sshSigner: sshSigner, 452 | }, nil 453 | default: 454 | return nil, fmt.Errorf("no signer matching %q", s) 455 | } 456 | } 457 | 458 | // signerFromString is an internally public method constructor 459 | func signerFromString(s string) (signer, error) { 460 | s = strings.ToLower(s) 461 | isEcdsa := false 462 | isEd25519 := false 463 | var algo string = "" 464 | if strings.HasPrefix(s, ecdsaPrefix) { 465 | algo = strings.TrimPrefix(s, ecdsaPrefix+"-") 466 | isEcdsa = true 467 | } else if strings.HasPrefix(s, rsaPrefix) { 468 | algo = strings.TrimPrefix(s, rsaPrefix+"-") 469 | } else if strings.HasPrefix(s, ed25519Prefix) { 470 | isEd25519 = true 471 | algo = "sha512" 472 | } else { 473 | return nil, fmt.Errorf("no signer matching %q", s) 474 | } 475 | hash, cHash, err := newAlgorithm(algo, nil) 476 | if err != nil { 477 | return nil, err 478 | } 479 | if isEd25519 { 480 | return &ed25519Algorithm{}, nil 481 | } 482 | if isEcdsa { 483 | return &ecdsaAlgorithm{ 484 | Hash: hash, 485 | kind: cHash, 486 | }, nil 487 | } 488 | return &rsaAlgorithm{ 489 | Hash: hash, 490 | kind: cHash, 491 | }, nil 492 | } 493 | 494 | // macerFromString is an internally public method constructor 495 | func macerFromString(s string) (macer, error) { 496 | s = strings.ToLower(s) 497 | if strings.HasPrefix(s, hmacPrefix) { 498 | algo := strings.TrimPrefix(s, hmacPrefix+"-") 499 | hashFn, cHash, err := newAlgorithmConstructor(algo) 500 | if err != nil { 501 | return nil, err 502 | } 503 | // Ensure below does not panic 504 | _, err = hashFn(nil) 505 | if err != nil { 506 | return nil, err 507 | } 508 | return &hmacAlgorithm{ 509 | fn: func(key []byte) (hash.Hash, error) { 510 | return hmac.New(func() hash.Hash { 511 | h, e := hashFn(nil) 512 | if e != nil { 513 | panic(e) 514 | } 515 | return h 516 | }, key), nil 517 | }, 518 | kind: cHash, 519 | }, nil 520 | } else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] { 521 | hashFn, cHash, err := newAlgorithmConstructor(s) 522 | if err != nil { 523 | return nil, err 524 | } 525 | return &blakeMacAlgorithm{ 526 | fn: hashFn, 527 | kind: cHash, 528 | }, nil 529 | } else { 530 | return nil, fmt.Errorf("no MACer matching %q", s) 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /algorithms_test.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "crypto" 5 | "crypto/hmac" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "hash" 9 | doNotUseInProdCode "math/rand" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func readFullFromCrypto(b []byte) error { 15 | n := len(b) 16 | t := 0 17 | for t < n { 18 | d, err := rand.Reader.Read(b[t:]) 19 | if d == 0 && err != nil { 20 | return err 21 | } 22 | t += d 23 | } 24 | return nil 25 | } 26 | 27 | func TestIsAvailable(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | algo string 31 | expected bool 32 | expectError bool 33 | }{ 34 | { 35 | name: md4String, 36 | algo: md4String, 37 | expected: false, 38 | expectError: true, 39 | }, 40 | { 41 | name: md5String, 42 | algo: md5String, 43 | expected: false, 44 | expectError: true, 45 | }, 46 | { 47 | // TODO: Disable once https://github.com/golang/go/issues/37278 is fixed 48 | name: sha1String, 49 | algo: sha1String, 50 | expected: true, 51 | expectError: false, 52 | }, 53 | { 54 | name: sha224String, 55 | algo: sha224String, 56 | expected: true, 57 | expectError: false, 58 | }, 59 | { 60 | name: sha256String, 61 | algo: sha256String, 62 | expected: true, 63 | expectError: false, 64 | }, 65 | { 66 | name: sha384String, 67 | algo: sha384String, 68 | expected: true, 69 | expectError: false, 70 | }, 71 | { 72 | name: sha512String, 73 | algo: sha512String, 74 | expected: true, 75 | expectError: false, 76 | }, 77 | { 78 | name: md5sha1String, 79 | algo: md5sha1String, 80 | expected: false, 81 | expectError: true, 82 | }, 83 | { 84 | name: ripemd160String, 85 | algo: ripemd160String, 86 | expected: true, 87 | expectError: false, 88 | }, 89 | { 90 | name: sha3_224String, 91 | algo: sha3_224String, 92 | expected: true, 93 | expectError: false, 94 | }, 95 | { 96 | name: sha3_256String, 97 | algo: sha3_256String, 98 | expected: true, 99 | expectError: false, 100 | }, 101 | { 102 | name: sha3_384String, 103 | algo: sha3_384String, 104 | expected: true, 105 | expectError: false, 106 | }, 107 | { 108 | name: sha3_512String, 109 | algo: sha3_512String, 110 | expected: true, 111 | expectError: false, 112 | }, 113 | { 114 | name: sha512_224String, 115 | algo: sha512_224String, 116 | expected: true, 117 | expectError: false, 118 | }, 119 | { 120 | name: sha512_256String, 121 | algo: sha512_256String, 122 | expected: true, 123 | expectError: false, 124 | }, 125 | { 126 | name: blake2s_256String, 127 | algo: blake2s_256String, 128 | expected: true, 129 | expectError: false, 130 | }, 131 | { 132 | name: blake2b_256String, 133 | algo: blake2b_256String, 134 | expected: true, 135 | expectError: false, 136 | }, 137 | { 138 | name: blake2b_384String, 139 | algo: blake2b_384String, 140 | expected: true, 141 | expectError: false, 142 | }, 143 | { 144 | name: blake2b_512String, 145 | algo: blake2b_512String, 146 | expected: true, 147 | expectError: false, 148 | }, 149 | } 150 | for _, test := range tests { 151 | got, err := isAvailable(test.algo) 152 | gotErr := err != nil 153 | if got != test.expected { 154 | t.Fatalf("%q: got %v, want %v", test.name, got, test.expected) 155 | } else if gotErr != test.expectError { 156 | if test.expectError { 157 | t.Fatalf("%q: expected error, got: %s", test.name, err) 158 | } else { 159 | t.Fatalf("%q: expected no error, got: %s", test.name, err) 160 | } 161 | } 162 | } 163 | } 164 | 165 | func TestSignerFromString(t *testing.T) { 166 | tests := []struct { 167 | name string 168 | input Algorithm 169 | expectKind crypto.Hash 170 | expectError bool 171 | }{ 172 | { 173 | name: "HMAC_SHA224", 174 | input: HMAC_SHA224, 175 | expectError: true, 176 | }, 177 | { 178 | name: "HMAC_SHA256", 179 | input: HMAC_SHA256, 180 | expectError: true, 181 | }, 182 | { 183 | name: "HMAC_SHA384", 184 | input: HMAC_SHA384, 185 | expectError: true, 186 | }, 187 | { 188 | name: "HMAC_SHA512", 189 | input: HMAC_SHA512, 190 | expectError: true, 191 | }, 192 | { 193 | name: "HMAC_RIPEMD160", 194 | input: HMAC_RIPEMD160, 195 | expectError: true, 196 | }, 197 | { 198 | name: "HMAC_SHA3_224", 199 | input: HMAC_SHA3_224, 200 | expectError: true, 201 | }, 202 | { 203 | name: "HMAC_SHA3_256", 204 | input: HMAC_SHA3_256, 205 | expectError: true, 206 | }, 207 | { 208 | name: "HMAC_SHA3_384", 209 | input: HMAC_SHA3_384, 210 | expectError: true, 211 | }, 212 | { 213 | name: "HMAC_SHA3_512", 214 | input: HMAC_SHA3_512, 215 | expectError: true, 216 | }, 217 | { 218 | name: "HMAC_SHA512_224", 219 | input: HMAC_SHA512_224, 220 | expectError: true, 221 | }, 222 | { 223 | name: "HMAC_SHA512_256", 224 | input: HMAC_SHA512_256, 225 | expectError: true, 226 | }, 227 | { 228 | name: "HMAC_BLAKE2S_256", 229 | input: HMAC_BLAKE2S_256, 230 | expectError: true, 231 | }, 232 | { 233 | name: "HMAC_BLAKE2B_256", 234 | input: HMAC_BLAKE2B_256, 235 | expectError: true, 236 | }, 237 | { 238 | name: "HMAC_BLAKE2B_384", 239 | input: HMAC_BLAKE2B_384, 240 | expectError: true, 241 | }, 242 | { 243 | name: "HMAC_BLAKE2B_512", 244 | input: HMAC_BLAKE2B_512, 245 | expectError: true, 246 | }, 247 | { 248 | name: "BLAKE2S_256", 249 | input: BLAKE2S_256, 250 | expectError: true, 251 | }, 252 | { 253 | name: "BLAKE2B_256", 254 | input: BLAKE2B_256, 255 | expectError: true, 256 | }, 257 | { 258 | name: "BLAKE2B_384", 259 | input: BLAKE2B_384, 260 | expectError: true, 261 | }, 262 | { 263 | name: "BLAKE2B_512", 264 | input: BLAKE2B_512, 265 | expectError: true, 266 | }, 267 | { 268 | name: "RSA_SHA224", 269 | input: RSA_SHA224, 270 | expectKind: crypto.SHA224, 271 | }, 272 | { 273 | name: "RSA_SHA256", 274 | input: RSA_SHA256, 275 | expectKind: crypto.SHA256, 276 | }, 277 | { 278 | name: "RSA_SHA384", 279 | input: RSA_SHA384, 280 | expectKind: crypto.SHA384, 281 | }, 282 | { 283 | name: "RSA_SHA512", 284 | input: RSA_SHA512, 285 | expectKind: crypto.SHA512, 286 | }, 287 | { 288 | name: "RSA_RIPEMD160", 289 | input: RSA_RIPEMD160, 290 | expectKind: crypto.RIPEMD160, 291 | }, 292 | { 293 | name: "rsa_SHA3_224", 294 | input: rsa_SHA3_224, 295 | expectKind: crypto.SHA3_224, 296 | }, 297 | { 298 | name: "rsa_SHA3_256", 299 | input: rsa_SHA3_256, 300 | expectKind: crypto.SHA3_256, 301 | }, 302 | { 303 | name: "rsa_SHA3_384", 304 | input: rsa_SHA3_384, 305 | expectKind: crypto.SHA3_384, 306 | }, 307 | { 308 | name: "rsa_SHA3_512", 309 | input: rsa_SHA3_512, 310 | expectKind: crypto.SHA3_512, 311 | }, 312 | { 313 | name: "rsa_SHA512_224", 314 | input: rsa_SHA512_224, 315 | expectKind: crypto.SHA512_224, 316 | }, 317 | { 318 | name: "rsa_SHA512_256", 319 | input: rsa_SHA512_256, 320 | expectKind: crypto.SHA512_256, 321 | }, 322 | { 323 | name: "rsa_BLAKE2S_256", 324 | input: rsa_BLAKE2S_256, 325 | expectKind: crypto.BLAKE2s_256, 326 | }, 327 | { 328 | name: "rsa_BLAKE2B_256", 329 | input: rsa_BLAKE2B_256, 330 | expectKind: crypto.BLAKE2b_256, 331 | }, 332 | { 333 | name: "rsa_BLAKE2B_384", 334 | input: rsa_BLAKE2B_384, 335 | expectKind: crypto.BLAKE2b_384, 336 | }, 337 | { 338 | name: "rsa_BLAKE2B_512", 339 | input: rsa_BLAKE2B_512, 340 | expectKind: crypto.BLAKE2b_512, 341 | }, 342 | } 343 | for _, test := range tests { 344 | s, err := signerFromString(string(test.input)) 345 | hasErr := err != nil 346 | if hasErr != test.expectError { 347 | if test.expectError { 348 | t.Fatalf("%q: expected error, got: %s", test.name, err) 349 | } else { 350 | t.Fatalf("%q: expected no error, got: %s", test.name, err) 351 | } 352 | } else if err == nil { 353 | want, ok := hashToDef[test.expectKind] 354 | if !ok { 355 | t.Fatalf("%q: Bad test setup, cannot find %q", test.name, test.expectKind) 356 | } 357 | if !strings.HasSuffix(s.String(), want.name) { 358 | t.Fatalf("%q: expected suffix %q, got %q", test.name, want.name, s.String()) 359 | } 360 | } 361 | } 362 | } 363 | 364 | func TestMACerFromString(t *testing.T) { 365 | tests := []struct { 366 | name string 367 | input Algorithm 368 | expectKind crypto.Hash 369 | expectError bool 370 | }{ 371 | { 372 | name: "HMAC_SHA224", 373 | input: HMAC_SHA224, 374 | expectKind: crypto.SHA224, 375 | }, 376 | { 377 | name: "HMAC_SHA256", 378 | input: HMAC_SHA256, 379 | expectKind: crypto.SHA256, 380 | }, 381 | { 382 | name: "HMAC_SHA384", 383 | input: HMAC_SHA384, 384 | expectKind: crypto.SHA384, 385 | }, 386 | { 387 | name: "HMAC_SHA512", 388 | input: HMAC_SHA512, 389 | expectKind: crypto.SHA512, 390 | }, 391 | { 392 | name: "HMAC_RIPEMD160", 393 | input: HMAC_RIPEMD160, 394 | expectKind: crypto.RIPEMD160, 395 | }, 396 | { 397 | name: "HMAC_SHA3_224", 398 | input: HMAC_SHA3_224, 399 | expectKind: crypto.SHA3_224, 400 | }, 401 | { 402 | name: "HMAC_SHA3_256", 403 | input: HMAC_SHA3_256, 404 | expectKind: crypto.SHA3_256, 405 | }, 406 | { 407 | name: "HMAC_SHA3_384", 408 | input: HMAC_SHA3_384, 409 | expectKind: crypto.SHA3_384, 410 | }, 411 | { 412 | name: "HMAC_SHA3_512", 413 | input: HMAC_SHA3_512, 414 | expectKind: crypto.SHA3_512, 415 | }, 416 | { 417 | name: "HMAC_SHA512_224", 418 | input: HMAC_SHA512_224, 419 | expectKind: crypto.SHA512_224, 420 | }, 421 | { 422 | name: "HMAC_SHA512_256", 423 | input: HMAC_SHA512_256, 424 | expectKind: crypto.SHA512_256, 425 | }, 426 | { 427 | name: "HMAC_BLAKE2S_256", 428 | input: HMAC_BLAKE2S_256, 429 | expectKind: crypto.BLAKE2s_256, 430 | }, 431 | { 432 | name: "HMAC_BLAKE2B_256", 433 | input: HMAC_BLAKE2B_256, 434 | expectKind: crypto.BLAKE2b_256, 435 | }, 436 | { 437 | name: "HMAC_BLAKE2B_384", 438 | input: HMAC_BLAKE2B_384, 439 | expectKind: crypto.BLAKE2b_384, 440 | }, 441 | { 442 | name: "HMAC_BLAKE2B_512", 443 | input: HMAC_BLAKE2B_512, 444 | expectKind: crypto.BLAKE2b_512, 445 | }, 446 | { 447 | name: "BLAKE2S_256", 448 | input: BLAKE2S_256, 449 | expectKind: crypto.BLAKE2s_256, 450 | }, 451 | { 452 | name: "BLAKE2B_256", 453 | input: BLAKE2B_256, 454 | expectKind: crypto.BLAKE2b_256, 455 | }, 456 | { 457 | name: "BLAKE2B_384", 458 | input: BLAKE2B_384, 459 | expectKind: crypto.BLAKE2b_384, 460 | }, 461 | { 462 | name: "BLAKE2B_512", 463 | input: BLAKE2B_512, 464 | expectKind: crypto.BLAKE2b_512, 465 | }, 466 | { 467 | name: "RSA_SHA224", 468 | input: RSA_SHA224, 469 | expectError: true, 470 | }, 471 | { 472 | name: "RSA_SHA256", 473 | input: RSA_SHA256, 474 | expectError: true, 475 | }, 476 | { 477 | name: "RSA_SHA384", 478 | input: RSA_SHA384, 479 | expectError: true, 480 | }, 481 | { 482 | name: "RSA_SHA512", 483 | input: RSA_SHA512, 484 | expectError: true, 485 | }, 486 | { 487 | name: "RSA_RIPEMD160", 488 | input: RSA_RIPEMD160, 489 | expectError: true, 490 | }, 491 | { 492 | name: "rsa_SHA3_224", 493 | input: rsa_SHA3_224, 494 | expectError: true, 495 | }, 496 | { 497 | name: "rsa_SHA3_256", 498 | input: rsa_SHA3_256, 499 | expectError: true, 500 | }, 501 | { 502 | name: "rsa_SHA3_384", 503 | input: rsa_SHA3_384, 504 | expectError: true, 505 | }, 506 | { 507 | name: "rsa_SHA3_512", 508 | input: rsa_SHA3_512, 509 | expectError: true, 510 | }, 511 | { 512 | name: "rsa_SHA512_224", 513 | input: rsa_SHA512_224, 514 | expectError: true, 515 | }, 516 | { 517 | name: "rsa_SHA512_256", 518 | input: rsa_SHA512_256, 519 | expectError: true, 520 | }, 521 | { 522 | name: "rsa_BLAKE2S_256", 523 | input: rsa_BLAKE2S_256, 524 | expectError: true, 525 | }, 526 | { 527 | name: "rsa_BLAKE2B_256", 528 | input: rsa_BLAKE2B_256, 529 | expectError: true, 530 | }, 531 | { 532 | name: "rsa_BLAKE2B_384", 533 | input: rsa_BLAKE2B_384, 534 | expectError: true, 535 | }, 536 | { 537 | name: "rsa_BLAKE2B_512", 538 | input: rsa_BLAKE2B_512, 539 | expectError: true, 540 | }, 541 | } 542 | for _, test := range tests { 543 | m, err := macerFromString(string(test.input)) 544 | hasErr := err != nil 545 | if hasErr != test.expectError { 546 | if test.expectError { 547 | t.Fatalf("%q: expected error, got: %s", test.name, err) 548 | } else { 549 | t.Fatalf("%q: expected no error, got: %s", test.name, err) 550 | } 551 | } else if err == nil { 552 | want, ok := hashToDef[test.expectKind] 553 | if !ok { 554 | t.Fatalf("%q: Bad test setup, cannot find %q", test.name, test.expectKind) 555 | } 556 | if !strings.HasSuffix(m.String(), want.name) { 557 | t.Fatalf("%q: expected suffix %q, got %q", test.name, want.name, m.String()) 558 | } 559 | } 560 | } 561 | } 562 | 563 | func TestSignerSigns(t *testing.T) { 564 | tests := []struct { 565 | name string 566 | input Algorithm 567 | inputCryptoHash crypto.Hash 568 | expectRSAUnsupported bool 569 | }{ 570 | { 571 | name: "RSA_SHA224", 572 | input: RSA_SHA224, 573 | inputCryptoHash: crypto.SHA224, 574 | }, 575 | { 576 | name: "RSA_SHA256", 577 | input: RSA_SHA256, 578 | inputCryptoHash: crypto.SHA256, 579 | }, 580 | { 581 | name: "RSA_SHA384", 582 | input: RSA_SHA384, 583 | inputCryptoHash: crypto.SHA384, 584 | }, 585 | { 586 | name: "RSA_SHA512", 587 | input: RSA_SHA512, 588 | inputCryptoHash: crypto.SHA512, 589 | }, 590 | { 591 | name: "RSA_RIPEMD160", 592 | input: RSA_RIPEMD160, 593 | inputCryptoHash: crypto.RIPEMD160, 594 | }, 595 | { 596 | name: "rsa_SHA3_224", 597 | input: rsa_SHA3_224, 598 | inputCryptoHash: crypto.SHA3_224, 599 | expectRSAUnsupported: true, 600 | }, 601 | { 602 | name: "rsa_SHA3_256", 603 | input: rsa_SHA3_256, 604 | inputCryptoHash: crypto.SHA3_256, 605 | expectRSAUnsupported: true, 606 | }, 607 | { 608 | name: "rsa_SHA3_384", 609 | input: rsa_SHA3_384, 610 | inputCryptoHash: crypto.SHA3_384, 611 | expectRSAUnsupported: true, 612 | }, 613 | { 614 | name: "rsa_SHA3_512", 615 | input: rsa_SHA3_512, 616 | inputCryptoHash: crypto.SHA3_512, 617 | expectRSAUnsupported: true, 618 | }, 619 | { 620 | name: "rsa_SHA512_224", 621 | input: rsa_SHA512_224, 622 | inputCryptoHash: crypto.SHA512_224, 623 | expectRSAUnsupported: true, 624 | }, 625 | { 626 | name: "rsa_SHA512_256", 627 | input: rsa_SHA512_256, 628 | inputCryptoHash: crypto.SHA512_256, 629 | expectRSAUnsupported: true, 630 | }, 631 | { 632 | name: "rsa_BLAKE2S_256", 633 | input: rsa_BLAKE2S_256, 634 | inputCryptoHash: crypto.BLAKE2s_256, 635 | expectRSAUnsupported: true, 636 | }, 637 | { 638 | name: "rsa_BLAKE2B_256", 639 | input: rsa_BLAKE2B_256, 640 | inputCryptoHash: crypto.BLAKE2b_256, 641 | expectRSAUnsupported: true, 642 | }, 643 | { 644 | name: "rsa_BLAKE2B_384", 645 | input: rsa_BLAKE2B_384, 646 | inputCryptoHash: crypto.BLAKE2b_384, 647 | expectRSAUnsupported: true, 648 | }, 649 | { 650 | name: "rsa_BLAKE2B_512", 651 | input: rsa_BLAKE2B_512, 652 | inputCryptoHash: crypto.BLAKE2b_512, 653 | expectRSAUnsupported: true, 654 | }, 655 | } 656 | for _, test := range tests { 657 | privKey, err := rsa.GenerateKey(rand.Reader, 2048) 658 | if err != nil { 659 | t.Fatalf("%q: Failed setup: %s", test.name, err) 660 | } 661 | sig := make([]byte, 65535) 662 | n, err := doNotUseInProdCode.Read(sig) 663 | if n != len(sig) { 664 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 665 | } else if err != nil { 666 | t.Fatalf("%q: Failed setup: %s", test.name, err) 667 | } 668 | s, err := signerFromString(string(test.input)) 669 | if err != nil { 670 | t.Fatalf("%q: %s", test.name, err) 671 | } 672 | seed := doNotUseInProdCode.Int63() 673 | doNotUseThisKindOfRandInProdCodeTest := doNotUseInProdCode.New(doNotUseInProdCode.NewSource(seed)) 674 | doNotUseThisKindOfRandInProdCodeTestVerify := doNotUseInProdCode.New(doNotUseInProdCode.NewSource(seed)) 675 | actual, err := s.Sign(doNotUseThisKindOfRandInProdCodeTest, privKey, sig) 676 | hasErr := err != nil 677 | if test.expectRSAUnsupported != hasErr { 678 | if test.expectRSAUnsupported { 679 | t.Fatalf("%q: expected error, got: %s", test.name, err) 680 | } else { 681 | t.Fatalf("%q: expected no error, got: %s", test.name, err) 682 | } 683 | } else if !test.expectRSAUnsupported && err != nil { 684 | t.Fatalf("%q: %s", test.name, err) 685 | } else if test.expectRSAUnsupported { 686 | // Skip further testing -- just need to verify it is 687 | // unsupported. 688 | continue 689 | } 690 | testHash, err := hashToDef[test.inputCryptoHash].new(nil) 691 | if err != nil { 692 | t.Fatalf("%q: %s", test.name, err) 693 | } 694 | n, err = testHash.Write(sig) 695 | if n != len(sig) { 696 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 697 | } else if err != nil { 698 | t.Fatalf("%q: Failed setup: %s", test.name, err) 699 | } 700 | want, err := rsa.SignPKCS1v15(doNotUseThisKindOfRandInProdCodeTestVerify, privKey, test.inputCryptoHash, testHash.Sum(nil)) 701 | if err != nil { 702 | t.Fatalf("%q: %s", test.name, err) 703 | } 704 | if len(actual) != len(want) { 705 | t.Fatalf("%q: len actual (%d) != len want (%d)", test.name, len(actual), len(want)) 706 | } 707 | for i, v := range actual { 708 | if v != want[i] { 709 | t.Fatalf("%q: difference beginning at index %d:\nwant: %v\nactual: %v", test.name, i, want, actual) 710 | } 711 | } 712 | } 713 | } 714 | 715 | func TestSignerVerifies(t *testing.T) { 716 | tests := []struct { 717 | name string 718 | input Algorithm 719 | inputCryptoHash crypto.Hash 720 | }{ 721 | { 722 | name: "RSA_SHA224", 723 | input: RSA_SHA224, 724 | inputCryptoHash: crypto.SHA224, 725 | }, 726 | { 727 | name: "RSA_SHA256", 728 | input: RSA_SHA256, 729 | inputCryptoHash: crypto.SHA256, 730 | }, 731 | { 732 | name: "RSA_SHA384", 733 | input: RSA_SHA384, 734 | inputCryptoHash: crypto.SHA384, 735 | }, 736 | { 737 | name: "RSA_SHA512", 738 | input: RSA_SHA512, 739 | inputCryptoHash: crypto.SHA512, 740 | }, 741 | { 742 | name: "RSA_RIPEMD160", 743 | input: RSA_RIPEMD160, 744 | inputCryptoHash: crypto.RIPEMD160, 745 | }, 746 | } 747 | for _, test := range tests { 748 | privKey, err := rsa.GenerateKey(rand.Reader, 2048) 749 | if err != nil { 750 | t.Fatalf("%q: Failed setup: %s", test.name, err) 751 | } 752 | toHash := make([]byte, 65535) 753 | n, err := doNotUseInProdCode.Read(toHash) 754 | if n != len(toHash) { 755 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(toHash)) 756 | } else if err != nil { 757 | t.Fatalf("%q: Failed setup: %s", test.name, err) 758 | } 759 | testHash, err := hashToDef[test.inputCryptoHash].new(nil) 760 | if err != nil { 761 | t.Fatalf("%q: %s", test.name, err) 762 | } 763 | n, err = testHash.Write(toHash) 764 | if n != len(toHash) { 765 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(toHash)) 766 | } else if err != nil { 767 | t.Fatalf("%q: Failed setup: %s", test.name, err) 768 | } 769 | signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, test.inputCryptoHash, testHash.Sum(nil)) 770 | if err != nil { 771 | t.Fatalf("%q: %s", test.name, err) 772 | } 773 | s, err := signerFromString(string(test.input)) 774 | if err != nil { 775 | t.Fatalf("%q: %s", test.name, err) 776 | } 777 | err = s.Verify(privKey.Public(), toHash, signature) 778 | if err != nil { 779 | t.Fatalf("%q: %s", test.name, err) 780 | } 781 | } 782 | } 783 | 784 | func TestMACerSigns(t *testing.T) { 785 | tests := []struct { 786 | name string 787 | input Algorithm 788 | inputCryptoHash crypto.Hash 789 | isHMAC bool 790 | keySize int 791 | }{ 792 | { 793 | name: "HMAC_SHA224", 794 | input: HMAC_SHA224, 795 | inputCryptoHash: crypto.SHA224, 796 | isHMAC: true, 797 | keySize: 128, 798 | }, 799 | { 800 | name: "HMAC_SHA256", 801 | input: HMAC_SHA256, 802 | inputCryptoHash: crypto.SHA256, 803 | isHMAC: true, 804 | keySize: 128, 805 | }, 806 | { 807 | name: "HMAC_SHA384", 808 | input: HMAC_SHA384, 809 | inputCryptoHash: crypto.SHA384, 810 | isHMAC: true, 811 | keySize: 128, 812 | }, 813 | { 814 | name: "HMAC_SHA512", 815 | input: HMAC_SHA512, 816 | inputCryptoHash: crypto.SHA512, 817 | isHMAC: true, 818 | keySize: 128, 819 | }, 820 | { 821 | name: "HMAC_RIPEMD160", 822 | input: HMAC_RIPEMD160, 823 | inputCryptoHash: crypto.RIPEMD160, 824 | isHMAC: true, 825 | keySize: 128, 826 | }, 827 | { 828 | name: "HMAC_SHA3_224", 829 | input: HMAC_SHA3_224, 830 | inputCryptoHash: crypto.SHA3_224, 831 | isHMAC: true, 832 | keySize: 128, 833 | }, 834 | { 835 | name: "HMAC_SHA3_256", 836 | input: HMAC_SHA3_256, 837 | inputCryptoHash: crypto.SHA3_256, 838 | isHMAC: true, 839 | keySize: 128, 840 | }, 841 | { 842 | name: "HMAC_SHA3_384", 843 | input: HMAC_SHA3_384, 844 | inputCryptoHash: crypto.SHA3_384, 845 | isHMAC: true, 846 | keySize: 128, 847 | }, 848 | { 849 | name: "HMAC_SHA3_512", 850 | input: HMAC_SHA3_512, 851 | inputCryptoHash: crypto.SHA3_512, 852 | isHMAC: true, 853 | keySize: 128, 854 | }, 855 | { 856 | name: "HMAC_SHA512_224", 857 | input: HMAC_SHA512_224, 858 | inputCryptoHash: crypto.SHA512_224, 859 | isHMAC: true, 860 | keySize: 128, 861 | }, 862 | { 863 | name: "HMAC_SHA512_256", 864 | input: HMAC_SHA512_256, 865 | inputCryptoHash: crypto.SHA512_256, 866 | isHMAC: true, 867 | keySize: 128, 868 | }, 869 | { 870 | name: "HMAC_BLAKE2S_256", 871 | input: HMAC_BLAKE2S_256, 872 | inputCryptoHash: crypto.BLAKE2s_256, 873 | isHMAC: true, 874 | keySize: 128, 875 | }, 876 | { 877 | name: "HMAC_BLAKE2B_256", 878 | input: HMAC_BLAKE2B_256, 879 | inputCryptoHash: crypto.BLAKE2b_256, 880 | isHMAC: true, 881 | keySize: 128, 882 | }, 883 | { 884 | name: "HMAC_BLAKE2B_384", 885 | input: HMAC_BLAKE2B_384, 886 | inputCryptoHash: crypto.BLAKE2b_384, 887 | isHMAC: true, 888 | keySize: 128, 889 | }, 890 | { 891 | name: "HMAC_BLAKE2B_512", 892 | input: HMAC_BLAKE2B_512, 893 | inputCryptoHash: crypto.BLAKE2b_512, 894 | isHMAC: true, 895 | keySize: 128, 896 | }, 897 | { 898 | name: "BLAKE2S_256", 899 | input: BLAKE2S_256, 900 | inputCryptoHash: crypto.BLAKE2s_256, 901 | keySize: 32, 902 | }, 903 | { 904 | name: "BLAKE2B_256", 905 | input: BLAKE2B_256, 906 | inputCryptoHash: crypto.BLAKE2b_256, 907 | keySize: 64, 908 | }, 909 | { 910 | name: "BLAKE2B_384", 911 | input: BLAKE2B_384, 912 | inputCryptoHash: crypto.BLAKE2b_384, 913 | keySize: 64, 914 | }, 915 | { 916 | name: "BLAKE2B_512", 917 | input: BLAKE2B_512, 918 | inputCryptoHash: crypto.BLAKE2b_512, 919 | keySize: 64, 920 | }, 921 | } 922 | for _, test := range tests { 923 | privKey := make([]byte, test.keySize) 924 | err := readFullFromCrypto(privKey) 925 | if err != nil { 926 | t.Fatalf("%q: Failed setup: %s", test.name, err) 927 | } 928 | sig := make([]byte, 65535) 929 | n, err := doNotUseInProdCode.Read(sig) 930 | if n != len(sig) { 931 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 932 | } else if err != nil { 933 | t.Fatalf("%q: Failed setup: %s", test.name, err) 934 | } 935 | m, err := macerFromString(string(test.input)) 936 | if err != nil { 937 | t.Fatalf("%q: %s", test.name, err) 938 | } 939 | actual, err := m.Sign(sig, privKey) 940 | if err != nil { 941 | t.Fatalf("%q: %s", test.name, err) 942 | } 943 | var want []byte 944 | if test.isHMAC { 945 | hmacHash := hmac.New(func() hash.Hash { 946 | testHash, err := hashToDef[test.inputCryptoHash].new(nil) 947 | if err != nil { 948 | t.Fatalf("%q: %s", test.name, err) 949 | } 950 | return testHash 951 | }, privKey) 952 | n, err = hmacHash.Write(sig) 953 | if n != len(sig) { 954 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 955 | } else if err != nil { 956 | t.Fatalf("%q: Failed setup: %s", test.name, err) 957 | } 958 | want = hmacHash.Sum(nil) 959 | } else { 960 | testHash, err := hashToDef[test.inputCryptoHash].new(privKey) 961 | if err != nil { 962 | t.Fatalf("%q: %s", test.name, err) 963 | } 964 | n, err = testHash.Write(sig) 965 | if n != len(sig) { 966 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 967 | } else if err != nil { 968 | t.Fatalf("%q: Failed setup: %s", test.name, err) 969 | } 970 | want = testHash.Sum(nil) 971 | } 972 | if len(actual) != len(want) { 973 | t.Fatalf("%q: len actual (%d) != len want (%d)", test.name, len(actual), len(want)) 974 | } 975 | for i, v := range actual { 976 | if v != want[i] { 977 | t.Fatalf("%q: difference beginning at index %d:\nwant: %v\nactual: %v", test.name, i, want, actual) 978 | } 979 | } 980 | } 981 | } 982 | 983 | func TestMACerEquals(t *testing.T) { 984 | tests := []struct { 985 | name string 986 | input Algorithm 987 | inputCryptoHash crypto.Hash 988 | isHMAC bool 989 | keySize int 990 | }{ 991 | { 992 | name: "HMAC_SHA224", 993 | input: HMAC_SHA224, 994 | inputCryptoHash: crypto.SHA224, 995 | isHMAC: true, 996 | keySize: 128, 997 | }, 998 | { 999 | name: "HMAC_SHA256", 1000 | input: HMAC_SHA256, 1001 | inputCryptoHash: crypto.SHA256, 1002 | isHMAC: true, 1003 | keySize: 128, 1004 | }, 1005 | { 1006 | name: "HMAC_SHA384", 1007 | input: HMAC_SHA384, 1008 | inputCryptoHash: crypto.SHA384, 1009 | isHMAC: true, 1010 | keySize: 128, 1011 | }, 1012 | { 1013 | name: "HMAC_SHA512", 1014 | input: HMAC_SHA512, 1015 | inputCryptoHash: crypto.SHA512, 1016 | isHMAC: true, 1017 | keySize: 128, 1018 | }, 1019 | { 1020 | name: "HMAC_RIPEMD160", 1021 | input: HMAC_RIPEMD160, 1022 | inputCryptoHash: crypto.RIPEMD160, 1023 | isHMAC: true, 1024 | keySize: 128, 1025 | }, 1026 | { 1027 | name: "HMAC_SHA3_224", 1028 | input: HMAC_SHA3_224, 1029 | inputCryptoHash: crypto.SHA3_224, 1030 | isHMAC: true, 1031 | keySize: 128, 1032 | }, 1033 | { 1034 | name: "HMAC_SHA3_256", 1035 | input: HMAC_SHA3_256, 1036 | inputCryptoHash: crypto.SHA3_256, 1037 | isHMAC: true, 1038 | keySize: 128, 1039 | }, 1040 | { 1041 | name: "HMAC_SHA3_384", 1042 | input: HMAC_SHA3_384, 1043 | inputCryptoHash: crypto.SHA3_384, 1044 | isHMAC: true, 1045 | keySize: 128, 1046 | }, 1047 | { 1048 | name: "HMAC_SHA3_512", 1049 | input: HMAC_SHA3_512, 1050 | inputCryptoHash: crypto.SHA3_512, 1051 | isHMAC: true, 1052 | keySize: 128, 1053 | }, 1054 | { 1055 | name: "HMAC_SHA512_224", 1056 | input: HMAC_SHA512_224, 1057 | inputCryptoHash: crypto.SHA512_224, 1058 | isHMAC: true, 1059 | keySize: 128, 1060 | }, 1061 | { 1062 | name: "HMAC_SHA512_256", 1063 | input: HMAC_SHA512_256, 1064 | inputCryptoHash: crypto.SHA512_256, 1065 | isHMAC: true, 1066 | keySize: 128, 1067 | }, 1068 | { 1069 | name: "HMAC_BLAKE2S_256", 1070 | input: HMAC_BLAKE2S_256, 1071 | inputCryptoHash: crypto.BLAKE2s_256, 1072 | isHMAC: true, 1073 | keySize: 128, 1074 | }, 1075 | { 1076 | name: "HMAC_BLAKE2B_256", 1077 | input: HMAC_BLAKE2B_256, 1078 | inputCryptoHash: crypto.BLAKE2b_256, 1079 | isHMAC: true, 1080 | keySize: 128, 1081 | }, 1082 | { 1083 | name: "HMAC_BLAKE2B_384", 1084 | input: HMAC_BLAKE2B_384, 1085 | inputCryptoHash: crypto.BLAKE2b_384, 1086 | isHMAC: true, 1087 | keySize: 128, 1088 | }, 1089 | { 1090 | name: "HMAC_BLAKE2B_512", 1091 | input: HMAC_BLAKE2B_512, 1092 | inputCryptoHash: crypto.BLAKE2b_512, 1093 | isHMAC: true, 1094 | keySize: 128, 1095 | }, 1096 | { 1097 | name: "BLAKE2S_256", 1098 | input: BLAKE2S_256, 1099 | inputCryptoHash: crypto.BLAKE2s_256, 1100 | keySize: 32, 1101 | }, 1102 | { 1103 | name: "BLAKE2B_256", 1104 | input: BLAKE2B_256, 1105 | inputCryptoHash: crypto.BLAKE2b_256, 1106 | keySize: 64, 1107 | }, 1108 | { 1109 | name: "BLAKE2B_384", 1110 | input: BLAKE2B_384, 1111 | inputCryptoHash: crypto.BLAKE2b_384, 1112 | keySize: 64, 1113 | }, 1114 | { 1115 | name: "BLAKE2B_512", 1116 | input: BLAKE2B_512, 1117 | inputCryptoHash: crypto.BLAKE2b_512, 1118 | keySize: 64, 1119 | }, 1120 | } 1121 | for _, test := range tests { 1122 | privKey := make([]byte, test.keySize) 1123 | err := readFullFromCrypto(privKey) 1124 | if err != nil { 1125 | t.Fatalf("%q: Failed setup: %s", test.name, err) 1126 | } 1127 | sig := make([]byte, 65535) 1128 | n, err := doNotUseInProdCode.Read(sig) 1129 | if n != len(sig) { 1130 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 1131 | } else if err != nil { 1132 | t.Fatalf("%q: Failed setup: %s", test.name, err) 1133 | } 1134 | var actual []byte 1135 | if test.isHMAC { 1136 | hmacHash := hmac.New(func() hash.Hash { 1137 | testHash, err := hashToDef[test.inputCryptoHash].new(nil) 1138 | if err != nil { 1139 | t.Fatalf("%q: %s", test.name, err) 1140 | } 1141 | return testHash 1142 | }, privKey) 1143 | n, err = hmacHash.Write(sig) 1144 | if n != len(sig) { 1145 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 1146 | } else if err != nil { 1147 | t.Fatalf("%q: Failed setup: %s", test.name, err) 1148 | } 1149 | actual = hmacHash.Sum(nil) 1150 | } else { 1151 | testHash, err := hashToDef[test.inputCryptoHash].new(privKey) 1152 | if err != nil { 1153 | t.Fatalf("%q: %s", test.name, err) 1154 | } 1155 | n, err = testHash.Write(sig) 1156 | if n != len(sig) { 1157 | t.Fatalf("%q: Failed setup: %d bytes != %d bytes", test.name, n, len(sig)) 1158 | } else if err != nil { 1159 | t.Fatalf("%q: Failed setup: %s", test.name, err) 1160 | } 1161 | actual = testHash.Sum(nil) 1162 | } 1163 | m, err := macerFromString(string(test.input)) 1164 | if err != nil { 1165 | t.Fatalf("%q: %s", test.name, err) 1166 | } 1167 | equal, err := m.Equal(sig, actual, privKey) 1168 | if err != nil { 1169 | t.Fatalf("%q: %s", test.name, err) 1170 | } else if !equal { 1171 | t.Fatalf("%q: signature is not verified", test.name) 1172 | } 1173 | } 1174 | } 1175 | -------------------------------------------------------------------------------- /digest.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "encoding/base64" 7 | "fmt" 8 | "hash" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | type DigestAlgorithm string 14 | 15 | const ( 16 | DigestSha256 DigestAlgorithm = "SHA-256" 17 | DigestSha512 = "SHA-512" 18 | ) 19 | 20 | var digestToDef = map[DigestAlgorithm]crypto.Hash{ 21 | DigestSha256: crypto.SHA256, 22 | DigestSha512: crypto.SHA512, 23 | } 24 | 25 | // IsSupportedDigestAlgorithm returns true if hte string is supported by this 26 | // library, is not a hash known to be weak, and is supported by the hardware. 27 | func IsSupportedDigestAlgorithm(algo string) bool { 28 | uc := DigestAlgorithm(strings.ToUpper(algo)) 29 | c, ok := digestToDef[uc] 30 | return ok && c.Available() 31 | } 32 | 33 | func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) { 34 | upper := DigestAlgorithm(strings.ToUpper(string(alg))) 35 | c, ok := digestToDef[upper] 36 | if !ok { 37 | err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg) 38 | } else if !c.Available() { 39 | err = fmt.Errorf("unavailable Digest algorithm: %s", alg) 40 | } else { 41 | h = c.New() 42 | toUse = upper 43 | } 44 | return 45 | } 46 | 47 | const ( 48 | digestHeader = "Digest" 49 | digestDelim = "=" 50 | ) 51 | 52 | func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) { 53 | _, ok := r.Header[digestHeader] 54 | if ok { 55 | err = fmt.Errorf("cannot add Digest: Digest is already set") 56 | return 57 | } 58 | var h hash.Hash 59 | var a DigestAlgorithm 60 | h, a, err = getHash(algo) 61 | if err != nil { 62 | return 63 | } 64 | h.Write(b) 65 | sum := h.Sum(nil) 66 | r.Header.Add(digestHeader, 67 | fmt.Sprintf("%s%s%s", 68 | a, 69 | digestDelim, 70 | base64.StdEncoding.EncodeToString(sum[:]))) 71 | return 72 | } 73 | 74 | func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) { 75 | _, ok := r.Header()[digestHeader] 76 | if ok { 77 | err = fmt.Errorf("cannot add Digest: Digest is already set") 78 | return 79 | } 80 | var h hash.Hash 81 | var a DigestAlgorithm 82 | h, a, err = getHash(algo) 83 | if err != nil { 84 | return 85 | } 86 | h.Write(b) 87 | sum := h.Sum(nil) 88 | r.Header().Add(digestHeader, 89 | fmt.Sprintf("%s%s%s", 90 | a, 91 | digestDelim, 92 | base64.StdEncoding.EncodeToString(sum[:]))) 93 | return 94 | } 95 | 96 | func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) { 97 | d := r.Header.Get(digestHeader) 98 | if len(d) == 0 { 99 | err = fmt.Errorf("cannot verify Digest: request has no Digest header") 100 | return 101 | } 102 | elem := strings.SplitN(d, digestDelim, 2) 103 | if len(elem) != 2 { 104 | err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d) 105 | return 106 | } 107 | var h hash.Hash 108 | h, _, err = getHash(DigestAlgorithm(elem[0])) 109 | if err != nil { 110 | return 111 | } 112 | h.Write(body.Bytes()) 113 | sum := h.Sum(nil) 114 | encSum := base64.StdEncoding.EncodeToString(sum[:]) 115 | if encSum != elem[1] { 116 | err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body") 117 | return 118 | } 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /digest_test.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestAddDigest(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | r func() *http.Request 13 | algo DigestAlgorithm 14 | body []byte 15 | expectedDigest string 16 | expectError bool 17 | }{ 18 | { 19 | name: "adds sha256 digest", 20 | r: func() *http.Request { 21 | r, _ := http.NewRequest("POST", "example.com", nil) 22 | return r 23 | }, 24 | algo: "SHA-256", 25 | body: []byte("johnny grab your gun"), 26 | expectedDigest: "SHA-256=RYiuVuVdRpU+BWcNUUg3sf0EbJjQ9LDj9tUqR546hhk=", 27 | }, 28 | { 29 | name: "adds sha512 digest", 30 | r: func() *http.Request { 31 | r, _ := http.NewRequest("POST", "example.com", nil) 32 | return r 33 | }, 34 | algo: "SHA-512", 35 | body: []byte("yours is the drill that will pierce the heavens"), 36 | expectedDigest: "SHA-512=bM0eBRnZkuiOTsejYNb/UpvFozde+Do1ZqlXfRTS39aGmoEzoXBpjmIIuznPslc3kaprUtI/VXH8/5HsD+thGg==", 37 | }, 38 | { 39 | name: "digest already set", 40 | r: func() *http.Request { 41 | r, _ := http.NewRequest("POST", "example.com", nil) 42 | r.Header.Set("Digest", "oops") 43 | return r 44 | }, 45 | algo: "SHA-512", 46 | body: []byte("did bob ewell fall on his knife"), 47 | expectError: true, 48 | }, 49 | { 50 | name: "unknown/unsupported digest algorithm", 51 | r: func() *http.Request { 52 | r, _ := http.NewRequest("POST", "example.com", nil) 53 | return r 54 | }, 55 | algo: "MD5", 56 | body: []byte("two times Cuchulainn almost drowned"), 57 | expectError: true, 58 | }, 59 | } 60 | for _, test := range tests { 61 | t.Run(test.name, func(t *testing.T) { 62 | test := test 63 | req := test.r() 64 | err := addDigest(req, test.algo, test.body) 65 | gotErr := err != nil 66 | if gotErr != test.expectError { 67 | if test.expectError { 68 | t.Fatalf("expected error, got: %s", err) 69 | } else { 70 | t.Fatalf("expected no error, got: %s", err) 71 | } 72 | } else if !gotErr { 73 | d := req.Header.Get("Digest") 74 | if d != test.expectedDigest { 75 | t.Fatalf("unexpected digest: want %s, got %s", test.expectedDigest, d) 76 | } 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestVerifyDigest(t *testing.T) { 83 | tests := []struct { 84 | name string 85 | r func() *http.Request 86 | body []byte 87 | expectError bool 88 | }{ 89 | { 90 | name: "verify sha256", 91 | r: func() *http.Request { 92 | r, _ := http.NewRequest("POST", "example.com", nil) 93 | r.Header.Set("Digest", "SHA-256=RYiuVuVdRpU+BWcNUUg3sf0EbJjQ9LDj9tUqR546hhk=") 94 | return r 95 | }, 96 | body: []byte("johnny grab your gun"), 97 | }, 98 | { 99 | name: "verify sha512", 100 | r: func() *http.Request { 101 | r, _ := http.NewRequest("POST", "example.com", nil) 102 | r.Header.Set("Digest", "SHA-512=bM0eBRnZkuiOTsejYNb/UpvFozde+Do1ZqlXfRTS39aGmoEzoXBpjmIIuznPslc3kaprUtI/VXH8/5HsD+thGg==") 103 | return r 104 | }, 105 | body: []byte("yours is the drill that will pierce the heavens"), 106 | }, 107 | { 108 | name: "no digest header", 109 | r: func() *http.Request { 110 | r, _ := http.NewRequest("POST", "example.com", nil) 111 | return r 112 | }, 113 | body: []byte("Yuji's gender is blue"), 114 | expectError: true, 115 | }, 116 | { 117 | name: "malformed digest", 118 | r: func() *http.Request { 119 | r, _ := http.NewRequest("POST", "example.com", nil) 120 | r.Header.Set("Digest", "SHA-256am9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==") 121 | return r 122 | }, 123 | body: []byte("Tochee and Ozzie BFFs forever"), 124 | expectError: true, 125 | }, 126 | { 127 | name: "unsupported/unknown algo", 128 | r: func() *http.Request { 129 | r, _ := http.NewRequest("POST", "example.com", nil) 130 | r.Header.Set("Digest", "MD5=poo") 131 | return r 132 | }, 133 | body: []byte("what is a man? a miserable pile of secrets"), 134 | expectError: true, 135 | }, 136 | { 137 | name: "bad digest", 138 | r: func() *http.Request { 139 | r, _ := http.NewRequest("POST", "example.com", nil) 140 | r.Header.Set("Digest", "SHA-256=bm9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==") 141 | return r 142 | }, 143 | body: []byte("johnny grab your gun"), 144 | expectError: true, 145 | }, 146 | } 147 | for _, test := range tests { 148 | t.Run(test.name, func(t *testing.T) { 149 | test := test 150 | req := test.r() 151 | buf := bytes.NewBuffer(test.body) 152 | err := verifyDigest(req, buf) 153 | gotErr := err != nil 154 | if gotErr != test.expectError { 155 | if test.expectError { 156 | t.Fatalf("expected error, got: %s", err) 157 | } else { 158 | t.Fatalf("expected no error, got: %s", err) 159 | } 160 | } 161 | }) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-fed/httpsig 2 | 3 | require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 4 | 5 | go 1.13 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 3 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 4 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 5 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 6 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 7 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 8 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 9 | -------------------------------------------------------------------------------- /httpsig.go: -------------------------------------------------------------------------------- 1 | // Implements HTTP request and response signing and verification. Supports the 2 | // major MAC and asymmetric key signature algorithms. It has several safety 3 | // restrictions: One, none of the widely known non-cryptographically safe 4 | // algorithms are permitted; Two, the RSA SHA256 algorithms must be available in 5 | // the binary (and it should, barring export restrictions); Finally, the library 6 | // assumes either the 'Authorizationn' or 'Signature' headers are to be set (but 7 | // not both). 8 | package httpsig 9 | 10 | import ( 11 | "crypto" 12 | "fmt" 13 | "net/http" 14 | "strings" 15 | "time" 16 | 17 | "golang.org/x/crypto/ssh" 18 | ) 19 | 20 | // Algorithm specifies a cryptography secure algorithm for signing HTTP requests 21 | // and responses. 22 | type Algorithm string 23 | 24 | const ( 25 | // MAC-based algoirthms. 26 | HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String 27 | HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String 28 | HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String 29 | HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String 30 | HMAC_RIPEMD160 Algorithm = hmacPrefix + "-" + ripemd160String 31 | HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String 32 | HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String 33 | HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String 34 | HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String 35 | HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String 36 | HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String 37 | HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String 38 | HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String 39 | HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String 40 | HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String 41 | BLAKE2S_256 Algorithm = blake2s_256String 42 | BLAKE2B_256 Algorithm = blake2b_256String 43 | BLAKE2B_384 Algorithm = blake2b_384String 44 | BLAKE2B_512 Algorithm = blake2b_512String 45 | // RSA-based algorithms. 46 | RSA_SHA1 Algorithm = rsaPrefix + "-" + sha1String 47 | RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String 48 | // RSA_SHA256 is the default algorithm. 49 | RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String 50 | RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String 51 | RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String 52 | RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String 53 | // ECDSA algorithms 54 | ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String 55 | ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String 56 | ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String 57 | ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String 58 | ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String 59 | // ED25519 algorithms 60 | // can only be SHA512 61 | ED25519 Algorithm = ed25519Prefix 62 | 63 | // Just because you can glue things together, doesn't mean they will 64 | // work. The following options are not supported. 65 | rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String 66 | rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String 67 | rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String 68 | rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String 69 | rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String 70 | rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String 71 | rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String 72 | rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String 73 | rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String 74 | rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String 75 | ) 76 | 77 | // HTTP Signatures can be applied to different HTTP headers, depending on the 78 | // expected application behavior. 79 | type SignatureScheme string 80 | 81 | const ( 82 | // Signature will place the HTTP Signature into the 'Signature' HTTP 83 | // header. 84 | Signature SignatureScheme = "Signature" 85 | // Authorization will place the HTTP Signature into the 'Authorization' 86 | // HTTP header. 87 | Authorization SignatureScheme = "Authorization" 88 | ) 89 | 90 | const ( 91 | // The HTTP Signatures specification uses the "Signature" auth-scheme 92 | // for the Authorization header. This is coincidentally named, but not 93 | // semantically the same, as the "Signature" HTTP header value. 94 | signatureAuthScheme = "Signature" 95 | ) 96 | 97 | // There are subtle differences to the values in the header. The Authorization 98 | // header has an 'auth-scheme' value that must be prefixed to the rest of the 99 | // key and values. 100 | func (s SignatureScheme) authScheme() string { 101 | switch s { 102 | case Authorization: 103 | return signatureAuthScheme 104 | default: 105 | return "" 106 | } 107 | } 108 | 109 | // Signers will sign HTTP requests or responses based on the algorithms and 110 | // headers selected at creation time. 111 | // 112 | // Signers are not safe to use between multiple goroutines. 113 | // 114 | // Note that signatures do set the deprecated 'algorithm' parameter for 115 | // backwards compatibility. 116 | type Signer interface { 117 | // SignRequest signs the request using a private key. The public key id 118 | // is used by the HTTP server to identify which key to use to verify the 119 | // signature. 120 | // 121 | // If the Signer was created using a MAC based algorithm, then the key 122 | // is expected to be of type []byte. If the Signer was created using an 123 | // RSA based algorithm, then the private key is expected to be of type 124 | // *rsa.PrivateKey. 125 | // 126 | // A Digest (RFC 3230) will be added to the request. The body provided 127 | // must match the body used in the request, and is allowed to be nil. 128 | // The Digest ensures the request body is not tampered with in flight, 129 | // and if the signer is created to also sign the "Digest" header, the 130 | // HTTP Signature will then ensure both the Digest and body are not both 131 | // modified to maliciously represent different content. 132 | SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error 133 | // SignResponse signs the response using a private key. The public key 134 | // id is used by the HTTP client to identify which key to use to verify 135 | // the signature. 136 | // 137 | // If the Signer was created using a MAC based algorithm, then the key 138 | // is expected to be of type []byte. If the Signer was created using an 139 | // RSA based algorithm, then the private key is expected to be of type 140 | // *rsa.PrivateKey. 141 | // 142 | // A Digest (RFC 3230) will be added to the response. The body provided 143 | // must match the body written in the response, and is allowed to be 144 | // nil. The Digest ensures the response body is not tampered with in 145 | // flight, and if the signer is created to also sign the "Digest" 146 | // header, the HTTP Signature will then ensure both the Digest and body 147 | // are not both modified to maliciously represent different content. 148 | SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error 149 | } 150 | 151 | // NewSigner creates a new Signer with the provided algorithm preferences to 152 | // make HTTP signatures. Only the first available algorithm will be used, which 153 | // is returned by this function along with the Signer. If none of the preferred 154 | // algorithms were available, then the default algorithm is used. The headers 155 | // specified will be included into the HTTP signatures. 156 | // 157 | // The Digest will also be calculated on a request's body using the provided 158 | // digest algorithm, if "Digest" is one of the headers listed. 159 | // 160 | // The provided scheme determines which header is populated with the HTTP 161 | // Signature. 162 | // 163 | // An error is returned if an unknown or a known cryptographically insecure 164 | // Algorithm is provided. 165 | func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) { 166 | for _, pref := range prefs { 167 | s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn) 168 | if err != nil { 169 | continue 170 | } 171 | return s, pref, err 172 | } 173 | s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn) 174 | return s, defaultAlgorithm, err 175 | } 176 | 177 | // Signers will sign HTTP requests or responses based on the algorithms and 178 | // headers selected at creation time. 179 | // 180 | // Signers are not safe to use between multiple goroutines. 181 | // 182 | // Note that signatures do set the deprecated 'algorithm' parameter for 183 | // backwards compatibility. 184 | type SSHSigner interface { 185 | // SignRequest signs the request using ssh.Signer. 186 | // The public key id is used by the HTTP server to identify which key to use 187 | // to verify the signature. 188 | // 189 | // A Digest (RFC 3230) will be added to the request. The body provided 190 | // must match the body used in the request, and is allowed to be nil. 191 | // The Digest ensures the request body is not tampered with in flight, 192 | // and if the signer is created to also sign the "Digest" header, the 193 | // HTTP Signature will then ensure both the Digest and body are not both 194 | // modified to maliciously represent different content. 195 | SignRequest(pubKeyId string, r *http.Request, body []byte) error 196 | // SignResponse signs the response using ssh.Signer. The public key 197 | // id is used by the HTTP client to identify which key to use to verify 198 | // the signature. 199 | // 200 | // A Digest (RFC 3230) will be added to the response. The body provided 201 | // must match the body written in the response, and is allowed to be 202 | // nil. The Digest ensures the response body is not tampered with in 203 | // flight, and if the signer is created to also sign the "Digest" 204 | // header, the HTTP Signature will then ensure both the Digest and body 205 | // are not both modified to maliciously represent different content. 206 | SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error 207 | } 208 | 209 | // NewwSSHSigner creates a new Signer using the specified ssh.Signer 210 | // At the moment only ed25519 ssh keys are supported. 211 | // The headers specified will be included into the HTTP signatures. 212 | // 213 | // The Digest will also be calculated on a request's body using the provided 214 | // digest algorithm, if "Digest" is one of the headers listed. 215 | // 216 | // The provided scheme determines which header is populated with the HTTP 217 | // Signature. 218 | func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) { 219 | sshAlgo := getSSHAlgorithm(s.PublicKey().Type()) 220 | if sshAlgo == "" { 221 | return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type()) 222 | } 223 | 224 | signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn) 225 | if err != nil { 226 | return nil, "", err 227 | } 228 | 229 | return signer, sshAlgo, nil 230 | } 231 | 232 | func getSSHAlgorithm(pkType string) Algorithm { 233 | switch { 234 | case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): 235 | return ED25519 236 | case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): 237 | return RSA_SHA1 238 | } 239 | 240 | return "" 241 | } 242 | 243 | // Verifier verifies HTTP Signatures. 244 | // 245 | // It will determine which of the supported headers has the parameters 246 | // that define the signature. 247 | // 248 | // Verifiers are not safe to use between multiple goroutines. 249 | // 250 | // Note that verification ignores the deprecated 'algorithm' parameter. 251 | type Verifier interface { 252 | // KeyId gets the public key id that the signature is signed with. 253 | // 254 | // Note that the application is expected to determine the algorithm 255 | // used based on metadata or out-of-band information for this key id. 256 | KeyId() string 257 | // Verify accepts the public key specified by KeyId and returns an 258 | // error if verification fails or if the signature is malformed. The 259 | // algorithm must be the one used to create the signature in order to 260 | // pass verification. The algorithm is determined based on metadata or 261 | // out-of-band information for the key id. 262 | // 263 | // If the signature was created using a MAC based algorithm, then the 264 | // key is expected to be of type []byte. If the signature was created 265 | // using an RSA based algorithm, then the public key is expected to be 266 | // of type *rsa.PublicKey. 267 | Verify(pKey crypto.PublicKey, algo Algorithm) error 268 | } 269 | 270 | const ( 271 | // host is treated specially because golang may not include it in the 272 | // request header map on the server side of a request. 273 | hostHeader = "Host" 274 | ) 275 | 276 | // NewVerifier verifies the given request. It returns an error if the HTTP 277 | // Signature parameters are not present in any headers, are present in more than 278 | // one header, are malformed, or are missing required parameters. It ignores 279 | // unknown HTTP Signature parameters. 280 | func NewVerifier(r *http.Request) (Verifier, error) { 281 | h := r.Header 282 | if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader { 283 | h[hostHeader] = []string{r.Host} 284 | } 285 | return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { 286 | return signatureString(h, toInclude, addRequestTarget(r), created, expires) 287 | }) 288 | } 289 | 290 | // NewResponseVerifier verifies the given response. It returns errors under the 291 | // same conditions as NewVerifier. 292 | func NewResponseVerifier(r *http.Response) (Verifier, error) { 293 | return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { 294 | return signatureString(h, toInclude, requestTargetNotPermitted, created, expires) 295 | }) 296 | } 297 | 298 | func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) { 299 | var expires, created int64 = 0, 0 300 | 301 | if expiresIn != 0 { 302 | created = time.Now().Unix() 303 | expires = created + expiresIn 304 | } 305 | 306 | s, err := signerFromSSHSigner(sshSigner, string(algo)) 307 | if err != nil { 308 | return nil, fmt.Errorf("no crypto implementation available for ssh algo %q: %s", algo, err) 309 | } 310 | 311 | a := &asymmSSHSigner{ 312 | asymmSigner: &asymmSigner{ 313 | s: s, 314 | dAlgo: dAlgo, 315 | headers: headers, 316 | targetHeader: scheme, 317 | prefix: scheme.authScheme(), 318 | created: created, 319 | expires: expires, 320 | }, 321 | } 322 | 323 | return a, nil 324 | } 325 | 326 | func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) { 327 | 328 | var expires, created int64 = 0, 0 329 | if expiresIn != 0 { 330 | created = time.Now().Unix() 331 | expires = created + expiresIn 332 | } 333 | 334 | s, err := signerFromString(string(algo)) 335 | if err == nil { 336 | a := &asymmSigner{ 337 | s: s, 338 | dAlgo: dAlgo, 339 | headers: headers, 340 | targetHeader: scheme, 341 | prefix: scheme.authScheme(), 342 | created: created, 343 | expires: expires, 344 | } 345 | return a, nil 346 | } 347 | m, err := macerFromString(string(algo)) 348 | if err != nil { 349 | return nil, fmt.Errorf("no crypto implementation available for %q: %s", algo, err) 350 | } 351 | c := &macSigner{ 352 | m: m, 353 | dAlgo: dAlgo, 354 | headers: headers, 355 | targetHeader: scheme, 356 | prefix: scheme.authScheme(), 357 | created: created, 358 | expires: expires, 359 | } 360 | return c, nil 361 | } 362 | -------------------------------------------------------------------------------- /httpsig_test.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha256" 9 | "crypto/x509" 10 | "encoding/asn1" 11 | "encoding/base64" 12 | "encoding/pem" 13 | "fmt" 14 | "io/ioutil" 15 | "net/http" 16 | "net/http/httptest" 17 | "strconv" 18 | "strings" 19 | "testing" 20 | 21 | "golang.org/x/crypto/ed25519" 22 | ) 23 | 24 | const ( 25 | testUrl = "foo.net/bar/baz?q=test&r=ok" 26 | testUrlPath = "bar/baz" 27 | testDate = "Tue, 07 Jun 2014 20:51:35 GMT" 28 | testDigest = "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=" 29 | testMethod = "GET" 30 | ) 31 | 32 | type httpsigTest struct { 33 | name string 34 | prefs []Algorithm 35 | digestAlg DigestAlgorithm 36 | headers []string 37 | body []byte 38 | scheme SignatureScheme 39 | privKey crypto.PrivateKey 40 | pubKey crypto.PublicKey 41 | pubKeyId string 42 | expectedSignatureAlgorithm string 43 | expectedAlgorithm Algorithm 44 | expectErrorSigningResponse bool 45 | expectRequestPath bool 46 | expectedDigest string 47 | } 48 | 49 | type ed25519PrivKey struct { 50 | Version int 51 | ObjectIdentifier struct { 52 | ObjectIdentifier asn1.ObjectIdentifier 53 | } 54 | PrivateKey []byte 55 | } 56 | 57 | type ed25519PubKey struct { 58 | OBjectIdentifier struct { 59 | ObjectIdentifier asn1.ObjectIdentifier 60 | } 61 | PublicKey asn1.BitString 62 | } 63 | 64 | var ( 65 | privKey *rsa.PrivateKey 66 | macKey []byte 67 | tests []httpsigTest 68 | testSpecRSAPrivateKey *rsa.PrivateKey 69 | testSpecRSAPublicKey *rsa.PublicKey 70 | testEd25519PrivateKey ed25519.PrivateKey 71 | testEd25519PublicKey ed25519.PublicKey 72 | ) 73 | 74 | func init() { 75 | var err error 76 | privKey, err = rsa.GenerateKey(rand.Reader, 2048) 77 | if err != nil { 78 | panic(err) 79 | } 80 | pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) 81 | if err != nil { 82 | panic(err) 83 | } 84 | macKey = make([]byte, 128) 85 | err = readFullFromCrypto(macKey) 86 | if err != nil { 87 | panic(err) 88 | } 89 | tests = []httpsigTest{ 90 | { 91 | name: "rsa signature", 92 | prefs: []Algorithm{RSA_SHA512}, 93 | digestAlg: DigestSha256, 94 | headers: []string{"Date", "Digest"}, 95 | scheme: Signature, 96 | privKey: privKey, 97 | pubKey: privKey.Public(), 98 | pubKeyId: "pubKeyId", 99 | expectedAlgorithm: RSA_SHA512, 100 | expectedSignatureAlgorithm: "hs2019", 101 | }, 102 | { 103 | name: "ed25519 signature", 104 | prefs: []Algorithm{ED25519}, 105 | digestAlg: DigestSha512, 106 | headers: []string{"Date", "Digest"}, 107 | scheme: Signature, 108 | privKey: privEd25519Key, 109 | pubKey: pubEd25519Key, 110 | pubKeyId: "pubKeyId", 111 | expectedAlgorithm: ED25519, 112 | expectedSignatureAlgorithm: "hs2019", 113 | }, 114 | { 115 | name: "digest on rsa signature", 116 | prefs: []Algorithm{RSA_SHA512}, 117 | digestAlg: DigestSha256, 118 | headers: []string{"Date", "Digest"}, 119 | body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."), 120 | scheme: Signature, 121 | privKey: privKey, 122 | pubKey: privKey.Public(), 123 | pubKeyId: "pubKeyId", 124 | expectedAlgorithm: RSA_SHA512, 125 | expectedSignatureAlgorithm: "hs2019", 126 | expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", 127 | }, 128 | { 129 | name: "digest on ed25519 signature", 130 | prefs: []Algorithm{ED25519}, 131 | digestAlg: DigestSha256, 132 | headers: []string{"Date", "Digest"}, 133 | body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."), 134 | scheme: Signature, 135 | privKey: privEd25519Key, 136 | pubKey: pubEd25519Key, 137 | pubKeyId: "pubKeyId", 138 | expectedAlgorithm: ED25519, 139 | expectedSignatureAlgorithm: "hs2019", 140 | expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=", 141 | }, 142 | { 143 | name: "hmac signature", 144 | prefs: []Algorithm{HMAC_SHA256}, 145 | digestAlg: DigestSha256, 146 | headers: []string{"Date", "Digest"}, 147 | scheme: Signature, 148 | privKey: macKey, 149 | pubKey: macKey, 150 | pubKeyId: "pubKeyId", 151 | expectedAlgorithm: HMAC_SHA256, 152 | expectedSignatureAlgorithm: "hs2019", 153 | }, 154 | { 155 | name: "digest on hmac signature", 156 | prefs: []Algorithm{HMAC_SHA256}, 157 | digestAlg: DigestSha256, 158 | headers: []string{"Date", "Digest"}, 159 | body: []byte("I've never ever been to paradise I've never ever seen no angel's eyes You'll never ever let this magic die No matter where you are, you are my lucky star."), 160 | scheme: Signature, 161 | privKey: macKey, 162 | pubKey: macKey, 163 | pubKeyId: "pubKeyId", 164 | expectedAlgorithm: HMAC_SHA256, 165 | expectedSignatureAlgorithm: "hs2019", 166 | expectedDigest: "SHA-256=d0JoDjbDZRZF7/gUdgrazZCdKCJ9z9uUcMd6n1YKWRU=", 167 | }, 168 | { 169 | name: "rsa authorization", 170 | prefs: []Algorithm{RSA_SHA512}, 171 | digestAlg: DigestSha256, 172 | headers: []string{"Date", "Digest"}, 173 | scheme: Authorization, 174 | privKey: privKey, 175 | pubKey: privKey.Public(), 176 | pubKeyId: "pubKeyId", 177 | expectedAlgorithm: RSA_SHA512, 178 | expectedSignatureAlgorithm: "hs2019", 179 | }, 180 | { 181 | name: "ed25519 authorization", 182 | prefs: []Algorithm{ED25519}, 183 | digestAlg: DigestSha256, 184 | headers: []string{"Date", "Digest"}, 185 | scheme: Authorization, 186 | privKey: privEd25519Key, 187 | pubKey: pubEd25519Key, 188 | pubKeyId: "pubKeyId", 189 | expectedAlgorithm: ED25519, 190 | expectedSignatureAlgorithm: "hs2019", 191 | }, 192 | { 193 | name: "hmac authorization", 194 | prefs: []Algorithm{HMAC_SHA256}, 195 | digestAlg: DigestSha256, 196 | headers: []string{"Date", "Digest"}, 197 | scheme: Authorization, 198 | privKey: macKey, 199 | pubKey: macKey, 200 | pubKeyId: "pubKeyId", 201 | expectedAlgorithm: HMAC_SHA256, 202 | expectedSignatureAlgorithm: "hs2019", 203 | }, 204 | { 205 | name: "default algo", 206 | digestAlg: DigestSha256, 207 | headers: []string{"Date", "Digest"}, 208 | scheme: Signature, 209 | privKey: privKey, 210 | pubKey: privKey.Public(), 211 | pubKeyId: "pubKeyId", 212 | expectedAlgorithm: RSA_SHA256, 213 | expectedSignatureAlgorithm: "hs2019", 214 | }, 215 | { 216 | name: "default headers", 217 | prefs: []Algorithm{RSA_SHA512}, 218 | digestAlg: DigestSha256, 219 | scheme: Signature, 220 | privKey: privKey, 221 | pubKey: privKey.Public(), 222 | pubKeyId: "pubKeyId", 223 | expectedAlgorithm: RSA_SHA512, 224 | expectedSignatureAlgorithm: "hs2019", 225 | }, 226 | { 227 | name: "different pub key id", 228 | prefs: []Algorithm{RSA_SHA512}, 229 | digestAlg: DigestSha256, 230 | headers: []string{"Date", "Digest"}, 231 | scheme: Signature, 232 | privKey: privKey, 233 | pubKey: privKey.Public(), 234 | pubKeyId: "i write code that sucks", 235 | expectedAlgorithm: RSA_SHA512, 236 | expectedSignatureAlgorithm: "hs2019", 237 | }, 238 | { 239 | name: "with request target", 240 | prefs: []Algorithm{RSA_SHA512}, 241 | digestAlg: DigestSha256, 242 | headers: []string{"Date", "Digest", RequestTarget}, 243 | scheme: Signature, 244 | privKey: privKey, 245 | pubKey: privKey.Public(), 246 | pubKeyId: "pubKeyId", 247 | expectedAlgorithm: RSA_SHA512, 248 | expectedSignatureAlgorithm: "hs2019", 249 | expectErrorSigningResponse: true, 250 | expectRequestPath: true, 251 | }, 252 | } 253 | 254 | testSpecRSAPrivateKey, err = loadPrivateKey([]byte(testSpecPrivateKeyPEM)) 255 | if err != nil { 256 | panic(err) 257 | } 258 | 259 | testSpecRSAPublicKey, err = loadPublicKey([]byte(testSpecPublicKeyPEM)) 260 | if err != nil { 261 | panic(err) 262 | } 263 | 264 | testEd25519PrivateKey, err = loadEd25519PrivateKey([]byte(testEd25519PrivateKeyPEM)) 265 | if err != nil { 266 | panic(err) 267 | } 268 | 269 | testEd25519PublicKey, err = loadEd25519PublicKey([]byte(testEd25519PublicKeyPEM)) 270 | if err != nil { 271 | panic(err) 272 | } 273 | } 274 | 275 | func toSignatureParameter(k, v string) string { 276 | return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter) 277 | } 278 | 279 | func toHeaderSignatureParameters(k string, vals []string) string { 280 | if len(vals) == 0 { 281 | vals = defaultHeaders 282 | } 283 | v := strings.Join(vals, headerParameterValueDelim) 284 | k = strings.ToLower(k) 285 | v = strings.ToLower(v) 286 | return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter) 287 | } 288 | 289 | func TestSignerRequest(t *testing.T) { 290 | testFn := func(t *testing.T, test httpsigTest) { 291 | s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 292 | if err != nil { 293 | t.Fatalf("%s", err) 294 | } 295 | if a != test.expectedAlgorithm { 296 | t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) 297 | } 298 | // Test request signing 299 | req, err := http.NewRequest(testMethod, testUrl, nil) 300 | if err != nil { 301 | t.Fatalf("%s", err) 302 | } 303 | req.Header.Set("Date", testDate) 304 | if test.body == nil { 305 | req.Header.Set("Digest", testDigest) 306 | } 307 | err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body) 308 | if err != nil { 309 | t.Fatalf("%s", err) 310 | } 311 | vals, ok := req.Header[string(test.scheme)] 312 | if !ok { 313 | t.Fatalf("not in header %s", test.scheme) 314 | } 315 | if len(vals) != 1 { 316 | t.Fatalf("too many in header %s: %d", test.scheme, len(vals)) 317 | } 318 | if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) { 319 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 320 | } else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) { 321 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 322 | } else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) { 323 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 324 | } else if !strings.Contains(vals[0], signatureParameter) { 325 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter) 326 | } else if test.body != nil && req.Header.Get("Digest") != test.expectedDigest { 327 | t.Fatalf("%s\ndoes not match\n%s", req.Header.Get("Digest"), test.expectedDigest) 328 | } 329 | // For schemes with an authScheme, enforce its is present and at the beginning 330 | if len(test.scheme.authScheme()) > 0 { 331 | if !strings.HasPrefix(vals[0], test.scheme.authScheme()) { 332 | t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme()) 333 | } 334 | } 335 | } 336 | for _, test := range tests { 337 | t.Run(test.name, func(t *testing.T) { 338 | testFn(t, test) 339 | }) 340 | } 341 | } 342 | 343 | func TestSignerResponse(t *testing.T) { 344 | testFn := func(t *testing.T, test httpsigTest) { 345 | s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 346 | // Test response signing 347 | resp := httptest.NewRecorder() 348 | resp.HeaderMap.Set("Date", testDate) 349 | if test.body == nil { 350 | resp.HeaderMap.Set("Digest", testDigest) 351 | } 352 | err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body) 353 | if test.expectErrorSigningResponse { 354 | if err != nil { 355 | // Skip rest of testing 356 | return 357 | } else { 358 | t.Fatalf("expected error, got nil") 359 | } 360 | } 361 | vals, ok := resp.HeaderMap[string(test.scheme)] 362 | if !ok { 363 | t.Fatalf("not in header %s", test.scheme) 364 | } 365 | if len(vals) != 1 { 366 | t.Fatalf("too many in header %s: %d", test.scheme, len(vals)) 367 | } 368 | if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) { 369 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 370 | } else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) { 371 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 372 | } else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) { 373 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], p) 374 | } else if !strings.Contains(vals[0], signatureParameter) { 375 | t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter) 376 | } else if test.body != nil && resp.Header().Get("Digest") != test.expectedDigest { 377 | t.Fatalf("%s\ndoes not match\n%s", resp.Header().Get("Digest"), test.expectedDigest) 378 | } 379 | // For schemes with an authScheme, enforce its is present and at the beginning 380 | if len(test.scheme.authScheme()) > 0 { 381 | if !strings.HasPrefix(vals[0], test.scheme.authScheme()) { 382 | t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme()) 383 | } 384 | } 385 | } 386 | for _, test := range tests { 387 | t.Run(test.name, func(t *testing.T) { 388 | testFn(t, test) 389 | }) 390 | } 391 | } 392 | 393 | func TestNewSignerRequestMissingHeaders(t *testing.T) { 394 | failingTests := []struct { 395 | name string 396 | prefs []Algorithm 397 | digestAlg DigestAlgorithm 398 | headers []string 399 | scheme SignatureScheme 400 | privKey crypto.PrivateKey 401 | pubKeyId string 402 | expectedAlgorithm Algorithm 403 | expectedSignatureAlgorithm string 404 | }{ 405 | { 406 | name: "wants digest", 407 | prefs: []Algorithm{RSA_SHA512}, 408 | digestAlg: DigestSha256, 409 | headers: []string{"Date", "Digest"}, 410 | scheme: Signature, 411 | privKey: privKey, 412 | pubKeyId: "pubKeyId", 413 | expectedSignatureAlgorithm: "hs2019", 414 | expectedAlgorithm: RSA_SHA512, 415 | }, 416 | } 417 | for _, test := range failingTests { 418 | t.Run(test.name, func(t *testing.T) { 419 | test := test 420 | s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 421 | if err != nil { 422 | t.Fatalf("%s", err) 423 | } 424 | if a != test.expectedAlgorithm { 425 | t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) 426 | } 427 | req, err := http.NewRequest(testMethod, testUrl, nil) 428 | if err != nil { 429 | t.Fatalf("%s", err) 430 | } 431 | req.Header.Set("Date", testDate) 432 | err = s.SignRequest(test.privKey, test.pubKeyId, req, nil) 433 | if err == nil { 434 | t.Fatalf("expect error but got nil") 435 | } 436 | }) 437 | } 438 | } 439 | 440 | func TestNewSignerResponseMissingHeaders(t *testing.T) { 441 | failingTests := []struct { 442 | name string 443 | prefs []Algorithm 444 | digestAlg DigestAlgorithm 445 | headers []string 446 | scheme SignatureScheme 447 | privKey crypto.PrivateKey 448 | pubKeyId string 449 | expectedAlgorithm Algorithm 450 | expectErrorSigningResponse bool 451 | expectedSignatureAlgorithm string 452 | }{ 453 | { 454 | name: "want digest", 455 | prefs: []Algorithm{RSA_SHA512}, 456 | digestAlg: DigestSha256, 457 | headers: []string{"Date", "Digest"}, 458 | scheme: Signature, 459 | privKey: privKey, 460 | pubKeyId: "pubKeyId", 461 | expectedSignatureAlgorithm: "hs2019", 462 | expectedAlgorithm: RSA_SHA512, 463 | }, 464 | } 465 | for _, test := range failingTests { 466 | t.Run(test.name, func(t *testing.T) { 467 | test := test 468 | s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 469 | if err != nil { 470 | t.Fatalf("%s", err) 471 | } 472 | if a != test.expectedAlgorithm { 473 | t.Fatalf("got %s, want %s", a, test.expectedAlgorithm) 474 | } 475 | resp := httptest.NewRecorder() 476 | resp.HeaderMap.Set("Date", testDate) 477 | resp.HeaderMap.Set("Digest", testDigest) 478 | err = s.SignResponse(test.privKey, test.pubKeyId, resp, nil) 479 | if err != nil { 480 | t.Fatalf("expected error, got nil") 481 | } 482 | }) 483 | } 484 | } 485 | 486 | func TestNewVerifier(t *testing.T) { 487 | for _, test := range tests { 488 | t.Run(test.name, func(t *testing.T) { 489 | test := test 490 | // Prepare 491 | req, err := http.NewRequest(testMethod, testUrl, nil) 492 | if err != nil { 493 | t.Fatalf("%s", err) 494 | } 495 | req.Header.Set("Date", testDate) 496 | if test.body == nil { 497 | req.Header.Set("Digest", testDigest) 498 | } 499 | s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 500 | if err != nil { 501 | t.Fatalf("%s", err) 502 | } 503 | err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body) 504 | if err != nil { 505 | t.Fatalf("%s", err) 506 | } 507 | // Test verification 508 | v, err := NewVerifier(req) 509 | if err != nil { 510 | t.Fatalf("%s", err) 511 | } 512 | if v.KeyId() != test.pubKeyId { 513 | t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId) 514 | } 515 | err = v.Verify(test.pubKey, test.expectedAlgorithm) 516 | if err != nil { 517 | t.Fatalf("%s", err) 518 | } 519 | }) 520 | } 521 | } 522 | 523 | func TestNewResponseVerifier(t *testing.T) { 524 | for _, test := range tests { 525 | t.Run(test.name, func(t *testing.T) { 526 | test := test 527 | if test.expectErrorSigningResponse { 528 | return 529 | } 530 | // Prepare 531 | resp := httptest.NewRecorder() 532 | resp.HeaderMap.Set("Date", testDate) 533 | if test.body == nil { 534 | resp.HeaderMap.Set("Digest", testDigest) 535 | } 536 | s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0) 537 | if err != nil { 538 | t.Fatalf("%s", err) 539 | } 540 | err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body) 541 | if err != nil { 542 | t.Fatalf("%s", err) 543 | } 544 | // Test verification 545 | v, err := NewResponseVerifier(resp.Result()) 546 | if err != nil { 547 | t.Fatalf("%s", err) 548 | } 549 | if v.KeyId() != test.pubKeyId { 550 | t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId) 551 | } 552 | err = v.Verify(test.pubKey, test.expectedAlgorithm) 553 | if err != nil { 554 | t.Fatalf("%s", err) 555 | } 556 | }) 557 | } 558 | } 559 | 560 | // Test_Signing_HTTP_Messages_AppendixC implement tests from Appendix C 561 | // in the http signatures specification: 562 | // https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C 563 | func Test_Signing_HTTP_Messages_AppendixC(t *testing.T) { 564 | specTests := []struct { 565 | name string 566 | headers []string 567 | expectedSignature string 568 | }{ 569 | { 570 | name: "C.1. Default Test", 571 | headers: []string{}, 572 | // NOTE: In the Appendix C tests, the following is NOT included: 573 | // `headers="date"` 574 | // But httpsig will ALWAYS explicitly list the headers used in its 575 | // signature. Hence, I have introduced it here. 576 | // 577 | // NOTE: In verification, if there are no headers listed, the 578 | // default headers (date) are indeed used as required by the 579 | // specification. 580 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`, 581 | }, 582 | { 583 | name: "C.2. Basic Test", 584 | headers: []string{"(request-target)", "host", "date"}, 585 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`, 586 | }, 587 | { 588 | name: "C.3. All Headers Test", 589 | headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, 590 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`, 591 | }, 592 | } 593 | 594 | for _, test := range specTests { 595 | t.Run(test.name, func(t *testing.T) { 596 | test := test 597 | r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) 598 | if err != nil { 599 | t.Fatalf("error creating request: %s", err) 600 | } 601 | 602 | r.Header["Date"] = []string{testSpecDate} 603 | r.Header["Host"] = []string{r.URL.Host} 604 | r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} 605 | r.Header["Content-Type"] = []string{"application/json"} 606 | setDigest(r) 607 | 608 | s, _, err := NewSigner([]Algorithm{RSA_SHA256}, DigestSha256, test.headers, Authorization, 0) 609 | if err != nil { 610 | t.Fatalf("error creating signer: %s", err) 611 | } 612 | 613 | if err := s.SignRequest(testSpecRSAPrivateKey, "Test", r, nil); err != nil { 614 | t.Fatalf("error signing request: %s", err) 615 | } 616 | 617 | expectedAuth := test.expectedSignature 618 | gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0]) 619 | if gotAuth != expectedAuth { 620 | t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth) 621 | } 622 | }) 623 | } 624 | } 625 | 626 | func TestSigningEd25519(t *testing.T) { 627 | specTests := []struct { 628 | name string 629 | headers []string 630 | expectedSignature string 631 | }{ 632 | { 633 | name: "Default Test", 634 | headers: []string{}, 635 | // NOTE: In the Appendix C tests, the following is NOT included: 636 | // `headers="date"` 637 | // But httpsig will ALWAYS explicitly list the headers used in its 638 | // signature. Hence, I have introduced it here. 639 | // 640 | // NOTE: In verification, if there are no headers listed, the 641 | // default headers (date) are indeed used as required by the 642 | // specification. 643 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`, 644 | }, 645 | { 646 | name: "Basic Test", 647 | headers: []string{"(request-target)", "host", "date"}, 648 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`, 649 | }, 650 | { 651 | name: "All Headers Test", 652 | headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, 653 | expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`, 654 | }, 655 | } 656 | 657 | for _, test := range specTests { 658 | t.Run(test.name, func(t *testing.T) { 659 | test := test 660 | r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) 661 | if err != nil { 662 | t.Fatalf("error creating request: %s", err) 663 | } 664 | 665 | r.Header["Date"] = []string{testSpecDate} 666 | r.Header["Host"] = []string{r.URL.Host} 667 | r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} 668 | r.Header["Content-Type"] = []string{"application/json"} 669 | setDigest(r) 670 | 671 | s, _, err := NewSigner([]Algorithm{ED25519}, DigestSha256, test.headers, Authorization, 0) 672 | if err != nil { 673 | t.Fatalf("error creating signer: %s", err) 674 | } 675 | 676 | if err := s.SignRequest(testEd25519PrivateKey, "Test", r, nil); err != nil { 677 | t.Fatalf("error signing request: %s", err) 678 | } 679 | 680 | expectedAuth := test.expectedSignature 681 | gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0]) 682 | if gotAuth != expectedAuth { 683 | t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth) 684 | } 685 | }) 686 | } 687 | } 688 | 689 | // Test_Verifying_HTTP_Messages_AppendixC implement tests from Appendix C 690 | // in the http signatures specification: 691 | // https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C 692 | func Test_Verifying_HTTP_Messages_AppendixC(t *testing.T) { 693 | specTests := []struct { 694 | name string 695 | headers []string 696 | signature string 697 | }{ 698 | { 699 | name: "C.1. Default Test", 700 | headers: []string{}, 701 | signature: `Signature keyId="Test",algorithm="rsa-sha256",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`, 702 | }, 703 | { 704 | name: "C.2. Basic Test", 705 | headers: []string{"(request-target)", "host", "date"}, 706 | signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`, 707 | }, 708 | { 709 | name: "C.3. All Headers Test", 710 | headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, 711 | signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`, 712 | }, 713 | } 714 | 715 | for _, test := range specTests { 716 | t.Run(test.name, func(t *testing.T) { 717 | test := test 718 | r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) 719 | if err != nil { 720 | t.Fatalf("error creating request: %s", err) 721 | } 722 | 723 | r.Header["Date"] = []string{testSpecDate} 724 | r.Header["Host"] = []string{r.URL.Host} 725 | r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} 726 | r.Header["Content-Type"] = []string{"application/json"} 727 | setDigest(r) 728 | r.Header["Authorization"] = []string{test.signature} 729 | 730 | v, err := NewVerifier(r) 731 | if err != nil { 732 | t.Fatalf("error creating verifier: %s", err) 733 | } 734 | 735 | if "Test" != v.KeyId() { 736 | t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId()) 737 | } 738 | if err := v.Verify(testSpecRSAPublicKey, RSA_SHA256); err != nil { 739 | t.Errorf("Verification failure: %s", err) 740 | } 741 | }) 742 | } 743 | } 744 | 745 | func TestVerifyingEd25519(t *testing.T) { 746 | specTests := []struct { 747 | name string 748 | headers []string 749 | signature string 750 | }{ 751 | { 752 | name: "Default Test", 753 | headers: []string{}, 754 | signature: `Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`, 755 | }, 756 | { 757 | name: "Basic Test", 758 | headers: []string{"(request-target)", "host", "date"}, 759 | signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`, 760 | }, 761 | { 762 | name: "All Headers Test", 763 | headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"}, 764 | signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`, 765 | }, 766 | } 767 | 768 | for _, test := range specTests { 769 | t.Run(test.name, func(t *testing.T) { 770 | test := test 771 | r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody))) 772 | if err != nil { 773 | t.Fatalf("error creating request: %s", err) 774 | } 775 | 776 | r.Header["Date"] = []string{testSpecDate} 777 | r.Header["Host"] = []string{r.URL.Host} 778 | r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))} 779 | r.Header["Content-Type"] = []string{"application/json"} 780 | setDigest(r) 781 | r.Header["Authorization"] = []string{test.signature} 782 | 783 | v, err := NewVerifier(r) 784 | if err != nil { 785 | t.Fatalf("error creating verifier: %s", err) 786 | } 787 | 788 | if "Test" != v.KeyId() { 789 | t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId()) 790 | } 791 | if err := v.Verify(testEd25519PublicKey, ED25519); err != nil { 792 | t.Errorf("Verification failure: %s", err) 793 | } 794 | }) 795 | } 796 | } 797 | 798 | func loadPrivateKey(keyData []byte) (*rsa.PrivateKey, error) { 799 | pem, _ := pem.Decode(keyData) 800 | if pem.Type != "RSA PRIVATE KEY" { 801 | return nil, fmt.Errorf("RSA private key is of the wrong type: %s", pem.Type) 802 | } 803 | 804 | return x509.ParsePKCS1PrivateKey(pem.Bytes) 805 | } 806 | 807 | // taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/ 808 | func loadEd25519PrivateKey(keyData []byte) (ed25519.PrivateKey, error) { 809 | var block *pem.Block 810 | block, _ = pem.Decode(keyData) 811 | 812 | var asn1PrivKey ed25519PrivKey 813 | asn1.Unmarshal(block.Bytes, &asn1PrivKey) 814 | 815 | // [2:] is skipping the byte for TAG and the byte for LEN 816 | // see also https://tools.ietf.org/html/draft-ietf-curdle-pkix-10#section-10.3 817 | return ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:]), nil 818 | } 819 | 820 | func loadPublicKey(keyData []byte) (*rsa.PublicKey, error) { 821 | pem, _ := pem.Decode(keyData) 822 | if pem.Type != "PUBLIC KEY" { 823 | return nil, fmt.Errorf("public key is of the wrong type: %s", pem.Type) 824 | } 825 | 826 | key, err := x509.ParsePKIXPublicKey(pem.Bytes) 827 | if err != nil { 828 | return nil, err 829 | } 830 | 831 | return key.(*rsa.PublicKey), nil 832 | } 833 | 834 | // taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/ 835 | func loadEd25519PublicKey(keyData []byte) (ed25519.PublicKey, error) { 836 | var block *pem.Block 837 | block, _ = pem.Decode(keyData) 838 | 839 | var asn1PubKey ed25519PubKey 840 | asn1.Unmarshal(block.Bytes, &asn1PubKey) 841 | 842 | return ed25519.PublicKey(asn1PubKey.PublicKey.Bytes), nil 843 | } 844 | 845 | func setDigest(r *http.Request) ([]byte, error) { 846 | var bodyBytes []byte 847 | if _, ok := r.Header["Digest"]; !ok { 848 | body := "" 849 | if r.Body != nil { 850 | var err error 851 | bodyBytes, err = ioutil.ReadAll(r.Body) 852 | if err != nil { 853 | return nil, fmt.Errorf("error reading body. %v", err) 854 | } 855 | 856 | // And now set a new body, which will simulate the same data we read: 857 | r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) 858 | body = string(bodyBytes) 859 | } 860 | 861 | d := sha256.Sum256([]byte(body)) 862 | r.Header["Digest"] = []string{fmt.Sprintf("SHA-256=%s", base64.StdEncoding.EncodeToString(d[:]))} 863 | } 864 | 865 | return bodyBytes, nil 866 | } 867 | 868 | const testSpecBody = `{"hello": "world"}` 869 | 870 | const testSpecDate = `Sun, 05 Jan 2014 21:31:40 GMT` 871 | 872 | const testSpecPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY----- 873 | MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF 874 | NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F 875 | UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB 876 | AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA 877 | QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK 878 | kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg 879 | f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u 880 | 412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc 881 | mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 882 | kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA 883 | gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW 884 | G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI 885 | 7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== 886 | -----END RSA PRIVATE KEY-----` 887 | 888 | const testSpecPublicKeyPEM = `-----BEGIN PUBLIC KEY----- 889 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 890 | 6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 891 | Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw 892 | oYi+1hqp1fIekaxsyQIDAQAB 893 | -----END PUBLIC KEY-----` 894 | 895 | const testEd25519PrivateKeyPEM = `-----BEGIN PRIVATE KEY----- 896 | MC4CAQAwBQYDK2VwBCIEIAP+PK4NtdzCe04sbtwBvf9IShlky298SMMBqkCCToHn 897 | -----END PRIVATE KEY-----` 898 | 899 | const testEd25519PublicKeyPEM = `-----BEGIN PUBLIC KEY----- 900 | MCowBQYDK2VwAyEAhyP+7zpNCsr7/ipGJjK0zVszTEQ5tooyX3VLAnBSc1c= 901 | -----END PUBLIC KEY-----` 902 | -------------------------------------------------------------------------------- /signing.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "fmt" 9 | "net/http" 10 | "net/textproto" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | // Signature Parameters 17 | keyIdParameter = "keyId" 18 | algorithmParameter = "algorithm" 19 | headersParameter = "headers" 20 | signatureParameter = "signature" 21 | prefixSeparater = " " 22 | parameterKVSeparater = "=" 23 | parameterValueDelimiter = "\"" 24 | parameterSeparater = "," 25 | headerParameterValueDelim = " " 26 | // RequestTarget specifies to include the http request method and 27 | // entire URI in the signature. Pass it as a header to NewSigner. 28 | RequestTarget = "(request-target)" 29 | createdKey = "created" 30 | expiresKey = "expires" 31 | dateHeader = "date" 32 | 33 | // Signature String Construction 34 | headerFieldDelimiter = ": " 35 | headersDelimiter = "\n" 36 | headerValueDelimiter = ", " 37 | requestTargetSeparator = " " 38 | ) 39 | 40 | var defaultHeaders = []string{dateHeader} 41 | 42 | var _ Signer = &macSigner{} 43 | 44 | type macSigner struct { 45 | m macer 46 | makeDigest bool 47 | dAlgo DigestAlgorithm 48 | headers []string 49 | targetHeader SignatureScheme 50 | prefix string 51 | created int64 52 | expires int64 53 | } 54 | 55 | func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { 56 | if body != nil { 57 | err := addDigest(r, m.dAlgo, body) 58 | if err != nil { 59 | return err 60 | } 61 | } 62 | s, err := m.signatureString(r) 63 | if err != nil { 64 | return err 65 | } 66 | enc, err := m.signSignature(pKey, s) 67 | if err != nil { 68 | return err 69 | } 70 | setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) 71 | return nil 72 | } 73 | 74 | func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { 75 | if body != nil { 76 | err := addDigestResponse(r, m.dAlgo, body) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | s, err := m.signatureStringResponse(r) 82 | if err != nil { 83 | return err 84 | } 85 | enc, err := m.signSignature(pKey, s) 86 | if err != nil { 87 | return err 88 | } 89 | setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) 90 | return nil 91 | } 92 | 93 | func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { 94 | pKeyBytes, ok := pKey.([]byte) 95 | if !ok { 96 | return "", fmt.Errorf("private key for MAC signing must be of type []byte") 97 | } 98 | sig, err := m.m.Sign([]byte(s), pKeyBytes) 99 | if err != nil { 100 | return "", err 101 | } 102 | enc := base64.StdEncoding.EncodeToString(sig) 103 | return enc, nil 104 | } 105 | 106 | func (m *macSigner) signatureString(r *http.Request) (string, error) { 107 | return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires) 108 | } 109 | 110 | func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { 111 | return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires) 112 | } 113 | 114 | var _ Signer = &asymmSigner{} 115 | 116 | type asymmSigner struct { 117 | s signer 118 | makeDigest bool 119 | dAlgo DigestAlgorithm 120 | headers []string 121 | targetHeader SignatureScheme 122 | prefix string 123 | created int64 124 | expires int64 125 | } 126 | 127 | func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { 128 | if body != nil { 129 | err := addDigest(r, a.dAlgo, body) 130 | if err != nil { 131 | return err 132 | } 133 | } 134 | s, err := a.signatureString(r) 135 | if err != nil { 136 | return err 137 | } 138 | enc, err := a.signSignature(pKey, s) 139 | if err != nil { 140 | return err 141 | } 142 | setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) 143 | return nil 144 | } 145 | 146 | func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { 147 | if body != nil { 148 | err := addDigestResponse(r, a.dAlgo, body) 149 | if err != nil { 150 | return err 151 | } 152 | } 153 | s, err := a.signatureStringResponse(r) 154 | if err != nil { 155 | return err 156 | } 157 | enc, err := a.signSignature(pKey, s) 158 | if err != nil { 159 | return err 160 | } 161 | setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) 162 | return nil 163 | } 164 | 165 | func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { 166 | sig, err := a.s.Sign(rand.Reader, pKey, []byte(s)) 167 | if err != nil { 168 | return "", err 169 | } 170 | enc := base64.StdEncoding.EncodeToString(sig) 171 | return enc, nil 172 | } 173 | 174 | func (a *asymmSigner) signatureString(r *http.Request) (string, error) { 175 | return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires) 176 | } 177 | 178 | func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { 179 | return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires) 180 | } 181 | 182 | var _ SSHSigner = &asymmSSHSigner{} 183 | 184 | type asymmSSHSigner struct { 185 | *asymmSigner 186 | } 187 | 188 | func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error { 189 | return a.asymmSigner.SignRequest(nil, pubKeyId, r, body) 190 | } 191 | 192 | func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error { 193 | return a.asymmSigner.SignResponse(nil, pubKeyId, r, body) 194 | } 195 | 196 | func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) { 197 | if len(headers) == 0 { 198 | headers = defaultHeaders 199 | } 200 | var b bytes.Buffer 201 | // KeyId 202 | b.WriteString(prefix) 203 | if len(prefix) > 0 { 204 | b.WriteString(prefixSeparater) 205 | } 206 | b.WriteString(keyIdParameter) 207 | b.WriteString(parameterKVSeparater) 208 | b.WriteString(parameterValueDelimiter) 209 | b.WriteString(pubKeyId) 210 | b.WriteString(parameterValueDelimiter) 211 | b.WriteString(parameterSeparater) 212 | // Algorithm 213 | b.WriteString(algorithmParameter) 214 | b.WriteString(parameterKVSeparater) 215 | b.WriteString(parameterValueDelimiter) 216 | b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft 217 | b.WriteString(parameterValueDelimiter) 218 | b.WriteString(parameterSeparater) 219 | 220 | hasCreated := false 221 | hasExpires := false 222 | for _, h := range headers { 223 | val := strings.ToLower(h) 224 | if val == "("+createdKey+")" { 225 | hasCreated = true 226 | } else if val == "("+expiresKey+")" { 227 | hasExpires = true 228 | } 229 | } 230 | 231 | // Created 232 | if hasCreated == true { 233 | b.WriteString(createdKey) 234 | b.WriteString(parameterKVSeparater) 235 | b.WriteString(strconv.FormatInt(created, 10)) 236 | b.WriteString(parameterSeparater) 237 | } 238 | 239 | // Expires 240 | if hasExpires == true { 241 | b.WriteString(expiresKey) 242 | b.WriteString(parameterKVSeparater) 243 | b.WriteString(strconv.FormatInt(expires, 10)) 244 | b.WriteString(parameterSeparater) 245 | } 246 | 247 | // Headers 248 | b.WriteString(headersParameter) 249 | b.WriteString(parameterKVSeparater) 250 | b.WriteString(parameterValueDelimiter) 251 | for i, h := range headers { 252 | b.WriteString(strings.ToLower(h)) 253 | if i != len(headers)-1 { 254 | b.WriteString(headerParameterValueDelim) 255 | } 256 | } 257 | b.WriteString(parameterValueDelimiter) 258 | b.WriteString(parameterSeparater) 259 | // Signature 260 | b.WriteString(signatureParameter) 261 | b.WriteString(parameterKVSeparater) 262 | b.WriteString(parameterValueDelimiter) 263 | b.WriteString(enc) 264 | b.WriteString(parameterValueDelimiter) 265 | h.Add(targetHeader, b.String()) 266 | } 267 | 268 | func requestTargetNotPermitted(b *bytes.Buffer) error { 269 | return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget) 270 | } 271 | 272 | func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error { 273 | return func(b *bytes.Buffer) error { 274 | b.WriteString(RequestTarget) 275 | b.WriteString(headerFieldDelimiter) 276 | b.WriteString(strings.ToLower(r.Method)) 277 | b.WriteString(requestTargetSeparator) 278 | b.WriteString(r.URL.Path) 279 | 280 | if r.URL.RawQuery != "" { 281 | b.WriteString("?") 282 | b.WriteString(r.URL.RawQuery) 283 | } 284 | 285 | return nil 286 | } 287 | } 288 | 289 | func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) { 290 | if len(include) == 0 { 291 | include = defaultHeaders 292 | } 293 | var b bytes.Buffer 294 | for n, i := range include { 295 | i := strings.ToLower(i) 296 | if i == RequestTarget { 297 | err := requestTargetFn(&b) 298 | if err != nil { 299 | return "", err 300 | } 301 | } else if i == "("+expiresKey+")" { 302 | if expires == 0 { 303 | return "", fmt.Errorf("missing expires value") 304 | } 305 | b.WriteString(i) 306 | b.WriteString(headerFieldDelimiter) 307 | b.WriteString(strconv.FormatInt(expires, 10)) 308 | } else if i == "("+createdKey+")" { 309 | if created == 0 { 310 | return "", fmt.Errorf("missing created value") 311 | } 312 | b.WriteString(i) 313 | b.WriteString(headerFieldDelimiter) 314 | b.WriteString(strconv.FormatInt(created, 10)) 315 | } else { 316 | hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)] 317 | if !ok { 318 | return "", fmt.Errorf("missing header %q", i) 319 | } 320 | b.WriteString(i) 321 | b.WriteString(headerFieldDelimiter) 322 | for i, v := range hv { 323 | b.WriteString(strings.TrimSpace(v)) 324 | if i < len(hv)-1 { 325 | b.WriteString(headerValueDelimiter) 326 | } 327 | } 328 | } 329 | if n < len(include)-1 { 330 | b.WriteString(headersDelimiter) 331 | } 332 | } 333 | return b.String(), nil 334 | } 335 | -------------------------------------------------------------------------------- /verifying.go: -------------------------------------------------------------------------------- 1 | package httpsig 2 | 3 | import ( 4 | "crypto" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | var _ Verifier = &verifier{} 15 | 16 | type verifier struct { 17 | header http.Header 18 | kId string 19 | signature string 20 | created int64 21 | expires int64 22 | headers []string 23 | sigStringFn func(http.Header, []string, int64, int64) (string, error) 24 | } 25 | 26 | func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) { 27 | scheme, s, err := getSignatureScheme(h) 28 | if err != nil { 29 | return nil, err 30 | } 31 | kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) 32 | if created != 0 { 33 | //check if created is not in the future, we assume a maximum clock offset of 10 seconds 34 | now := time.Now().Unix() 35 | if created-now > 10 { 36 | return nil, errors.New("created is in the future") 37 | } 38 | } 39 | if expires != 0 { 40 | //check if expires is in the past, we assume a maximum clock offset of 10 seconds 41 | now := time.Now().Unix() 42 | if now-expires > 10 { 43 | return nil, errors.New("signature expired") 44 | } 45 | } 46 | if err != nil { 47 | return nil, err 48 | } 49 | return &verifier{ 50 | header: h, 51 | kId: kId, 52 | signature: sig, 53 | created: created, 54 | expires: expires, 55 | headers: headers, 56 | sigStringFn: sigStringFn, 57 | }, nil 58 | } 59 | 60 | func (v *verifier) KeyId() string { 61 | return v.kId 62 | } 63 | 64 | func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { 65 | s, err := signerFromString(string(algo)) 66 | if err == nil { 67 | return v.asymmVerify(s, pKey) 68 | } 69 | m, err := macerFromString(string(algo)) 70 | if err == nil { 71 | return v.macVerify(m, pKey) 72 | } 73 | return fmt.Errorf("no crypto implementation available for %q: %s", algo, err) 74 | } 75 | 76 | func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error { 77 | key, ok := pKey.([]byte) 78 | if !ok { 79 | return fmt.Errorf("public key for MAC verifying must be of type []byte") 80 | } 81 | signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) 82 | if err != nil { 83 | return err 84 | } 85 | actualMAC, err := base64.StdEncoding.DecodeString(v.signature) 86 | if err != nil { 87 | return err 88 | } 89 | ok, err = m.Equal([]byte(signature), actualMAC, key) 90 | if err != nil { 91 | return err 92 | } else if !ok { 93 | return fmt.Errorf("invalid http signature") 94 | } 95 | return nil 96 | } 97 | 98 | func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error { 99 | toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) 100 | if err != nil { 101 | return err 102 | } 103 | signature, err := base64.StdEncoding.DecodeString(v.signature) 104 | if err != nil { 105 | return err 106 | } 107 | err = s.Verify(pKey, []byte(toHash), signature) 108 | if err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { 115 | s := h.Get(string(Signature)) 116 | sigHasAll := strings.Contains(s, keyIdParameter) || 117 | strings.Contains(s, headersParameter) || 118 | strings.Contains(s, signatureParameter) 119 | a := h.Get(string(Authorization)) 120 | authHasAll := strings.Contains(a, keyIdParameter) || 121 | strings.Contains(a, headersParameter) || 122 | strings.Contains(a, signatureParameter) 123 | if sigHasAll && authHasAll { 124 | err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) 125 | return 126 | } else if !sigHasAll && !authHasAll { 127 | err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) 128 | return 129 | } else if sigHasAll { 130 | val = s 131 | scheme = Signature 132 | return 133 | } else { // authHasAll 134 | val = a 135 | scheme = Authorization 136 | return 137 | } 138 | } 139 | 140 | func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { 141 | if as := scheme.authScheme(); len(as) > 0 { 142 | s = strings.TrimPrefix(s, as+prefixSeparater) 143 | } 144 | params := strings.Split(s, parameterSeparater) 145 | for _, p := range params { 146 | kv := strings.SplitN(p, parameterKVSeparater, 2) 147 | if len(kv) != 2 { 148 | err = fmt.Errorf("malformed http signature parameter: %v", kv) 149 | return 150 | } 151 | k := kv[0] 152 | v := strings.Trim(kv[1], parameterValueDelimiter) 153 | switch k { 154 | case keyIdParameter: 155 | kId = v 156 | case createdKey: 157 | created, err = strconv.ParseInt(v, 10, 64) 158 | if err != nil { 159 | return 160 | } 161 | case expiresKey: 162 | expires, err = strconv.ParseInt(v, 10, 64) 163 | if err != nil { 164 | return 165 | } 166 | case algorithmParameter: 167 | // Deprecated, ignore 168 | case headersParameter: 169 | headers = strings.Split(v, headerParameterValueDelim) 170 | case signatureParameter: 171 | sig = v 172 | default: 173 | // Ignore unrecognized parameters 174 | } 175 | } 176 | if len(kId) == 0 { 177 | err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) 178 | } else if len(sig) == 0 { 179 | err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) 180 | } else if len(headers) == 0 { // Optional 181 | headers = defaultHeaders 182 | } 183 | return 184 | } 185 | --------------------------------------------------------------------------------