diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9a9ba21..d1ef01a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,30 +2,46 @@ name: Go on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: - build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v3.5.3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.18 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.18 + - name: Build + run: go build -v ./... - - name: Build - run: go build -v ./... + - name: Test + run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic - - name: Test - run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Upload coverage to Codecov - run: bash <(curl -s https://codecov.io/bash) + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.1 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + if: github.ref == 'refs/heads/main' + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} golangci: name: lint diff --git a/README.md b/README.md index e4d6a7c..983d97d 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@

Standards-based HTTP request signing and verification for Go

- Go Reference + Go Reference Alpha Quality - Build Status + Build Status BSD license - codecov - Go Report Card + codecov + Go Report Card


## Introduction @@ -106,9 +106,11 @@ computation is based on version `05` of [Digest Headers][dighdr] | create multiple signatures | ✅ | | | | verify from multiple signatures | ✅ | | | | `rsa-pss-sha512` | ✅ | | | -| `rsa-v1_5-sha256` | | ❌ | | +| `rsa-v1_5-sha256` | ✅ | | | | `hmac-sha256` | ✅ | | | | `ecdsa-p256-sha256` | ✅ | | | +| `ecdsa-p384-sha384` | ✅ | | | +| `ed25519` | ✅ | | | | custom signature formats | | ❌ | `eddsa` is not part of the spec, so custom support here would be nice! | | JSON Web Signatures | | ❌ | JWS doesn't support any additional algs, but it is part of the spec | | Signature-Input as trailer | | ❌ | Trailers can be dropped. accept for verification only. | @@ -117,13 +119,13 @@ computation is based on version `05` of [Digest Headers][dighdr] | response digests | | ❌ | Tricky to support for signature use according to the spec | | multiple digests | | ❌ | | | digest: `sha-256` | | ❌ | | -| digest: `sha-512` | | ❌ | | +| digest: `sha-512` | ✅ | ❌ | | | digest: `md5` | | ❌ | Deprecated in the spec. Unlikely to be supported. | | digest: `sha` | | ❌ | Deprecated in the spec. Unlikely to be supported. | | digest: `unixsum` | | ❌ | | | digest: `unixcksum` | | ❌ | | | digest: `id-sha-512` | | ❌ | | -| digest: `id-sha-256` | ✅ | | `id-*` digests are more resilient for `content-encoding` support | +| digest: `id-sha-256` | | ❌ | | | custom digest formats | | ❌ | | ## Contributing @@ -151,7 +153,7 @@ I would love your help! [dighdr]: https://datatracker.ietf.org/doc/draft-ietf-httpbis-digest-headers/ [myblog]: https://repl.ca/modern-webhook-signatures/ -[godoc]: https://pkg.go.dev/github.com/jbowes/httpsig +[godoc]: https://pkg.go.dev/github.com/offblocks/httpsig [issues]: ./issues [bug]: ./issues/new?labels=bug [enhancement]: ./issues/new?labels=enhancement diff --git a/canonicalize.go b/canonicalize.go index 2d9df8b..28c8d47 100644 --- a/canonicalize.go +++ b/canonicalize.go @@ -99,7 +99,7 @@ type signatureParams struct { alg string created time.Time expires *time.Time - nonce string + nonce *string } func (sp *signatureParams) canonicalize() string { @@ -118,6 +118,10 @@ func (sp *signatureParams) canonicalize() string { o += fmt.Sprintf(";keyid=\"%s\"", sp.keyID) } + if sp.nonce != nil { + o += fmt.Sprintf(";nonce=\"%s\"", *sp.nonce) + } + if sp.alg != "" { o += fmt.Sprintf(";alg=\"%s\"", sp.alg) } @@ -168,7 +172,8 @@ func parseSignatureInput(in string) (*signatureParams, error) { case "keyid": sp.keyID = strings.Trim(paramParts[1], `"`) case "nonce": - sp.nonce = strings.Trim(paramParts[1], `"`) + nonce := strings.Trim(paramParts[1], `"`) + sp.nonce = &nonce case "created": i, err := strconv.ParseInt(paramParts[1], 10, 64) if err != nil { diff --git a/digest.go b/digest.go index 9992c1e..ba30110 100644 --- a/digest.go +++ b/digest.go @@ -5,7 +5,7 @@ package httpsig import ( - "crypto/sha256" + "crypto/sha512" "crypto/subtle" "encoding/base64" "fmt" @@ -17,9 +17,9 @@ import ( // TODO: support more algorithms, and maybe do its own package. func calcDigest(in []byte) string { - dig := sha256.Sum256(in) + dig := sha512.Sum512(in) - return fmt.Sprintf("id-sha256=%s", base64.StdEncoding.EncodeToString(dig[:])) + return fmt.Sprintf("sha-512=:%s:", base64.StdEncoding.EncodeToString(dig[:])) } func verifyDigest(in []byte, dig string) bool { diff --git a/example_test.go b/example_test.go index 4fbb70c..3092cf6 100644 --- a/example_test.go +++ b/example_test.go @@ -10,7 +10,7 @@ import ( "net/http" "time" - "github.com/Gh0u1L5/httpsig" + "github.com/offblocks/httpsig" ) const secret = "support-your-local-cat-bonnet-store" diff --git a/go.mod b/go.mod index 725e64d..0f8637f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/Gh0u1L5/httpsig +module github.com/offblocks/httpsig go 1.18 diff --git a/httpsig.go b/httpsig.go index b625de1..ef4a83d 100644 --- a/httpsig.go +++ b/httpsig.go @@ -7,6 +7,7 @@ package httpsig import ( "bytes" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "errors" "io" @@ -27,14 +28,11 @@ func sliceHas(haystack []string, needle string) bool { } type Signer struct { - signer + *signer } func NewSigner(opts ...signOption) *Signer { - s := signer{ - keys: make(map[string]sigHolder), - nowFunc: time.Now, - } + s := signer{} for _, o := range opts { o.configureSign(&s) @@ -47,13 +45,13 @@ func NewSigner(opts ...signOption) *Signer { // TODO: normalize headers? lowercase & de-dupe // specialty components and digest first, for aesthetics - for _, comp := range []string{"digest", "@query", "@path", "@method"} { + for _, comp := range []string{"content-digest", "@query", "@path", "@method"} { if !sliceHas(s.headers, comp) { s.headers = append([]string{comp}, s.headers...) } } - return &Signer{s} + return &Signer{&s} } func (s *Signer) Sign(r *http.Request) error { @@ -72,7 +70,7 @@ func (s *Signer) Sign(r *http.Request) error { // Always set a digest (for now) // TODO: we could skip setting digest on an empty body if content-length is included in the sig - r.Header.Set("Digest", calcDigest(b.Bytes())) + r.Header.Set("Content-Digest", calcDigest(b.Bytes())) msg := messageFromRequest(r) hdr, err := s.signer.Sign(msg) @@ -131,9 +129,9 @@ func (v *Verifier) Verify(r *http.Request) (keyID string, err error) { } } - // Check the digest if set. We only support id-sha-256 for now. + // Check the digest if set. We only support sha-512 for now. // TODO: option to require this? - if dig := r.Header.Get("Digest"); dig != "" { + if dig := r.Header.Get("Content-Digest"); dig != "" { if !verifyDigest(b.Bytes(), dig) { return keyID, errors.New("digest mismatch") } @@ -233,11 +231,27 @@ func WithVerifyingKeyResolver(resolver VerifyingKeyResolver) verifyOption { } } +// WithSignRsaPkcs1v15Sha256 adds signing using `rsa-v1_5-sha256` with the given private key +// using the given key id. +func WithSignRsaPkcs1v15Sha256(keyID string, pk *rsa.PrivateKey) signOption { + return &optImpl{ + s: func(s *signer) { s.keys.Store(keyID, signRsaPkcs1v15Sha256(pk)) }, + } +} + +// WithVerifyRsaPkcs1v15Sha256 adds signature verification using `rsa-v1_5-sha256` with the +// given public key using the given key id. +func WithVerifyRsaPkcs1v15Sha256(keyID string, pk *rsa.PublicKey) verifyOption { + return &optImpl{ + v: func(v *verifier) { v.keys.Store(keyID, verifyRsaPkcs1v15Sha256(pk)) }, + } +} + // WithSignRsaPssSha512 adds signing using `rsa-pss-sha512` with the given private key // using the given key id. func WithSignRsaPssSha512(keyID string, pk *rsa.PrivateKey) signOption { return &optImpl{ - s: func(s *signer) { s.keys[keyID] = signRsaPssSha512(pk) }, + s: func(s *signer) { s.keys.Store(keyID, signRsaPssSha512(pk)) }, } } @@ -253,7 +267,7 @@ func WithVerifyRsaPssSha512(keyID string, pk *rsa.PublicKey) verifyOption { // using the given key id. func WithSignEcdsaP256Sha256(keyID string, pk *ecdsa.PrivateKey) signOption { return &optImpl{ - s: func(s *signer) { s.keys[keyID] = signEccP256(pk) }, + s: func(s *signer) { s.keys.Store(keyID, signEccP256(pk)) }, } } @@ -265,11 +279,43 @@ func WithVerifyEcdsaP256Sha256(keyID string, pk *ecdsa.PublicKey) verifyOption { } } +// WithSignEcdsaP384Sha384 adds signing using `ecdsa-p384-sha384` with the given private key +// using the given key id. +func WithSignEcdsaP384Sha384(keyID string, pk *ecdsa.PrivateKey) signOption { + return &optImpl{ + s: func(s *signer) { s.keys.Store(keyID, signEccP384(pk)) }, + } +} + +// WithVerifyEcdsaP384Sha384 adds signature verification using `ecdsa-p384-sha384` with the +// given public key using the given key id. +func WithVerifyEcdsaP384Sha384(keyID string, pk *ecdsa.PublicKey) verifyOption { + return &optImpl{ + v: func(v *verifier) { v.keys.Store(keyID, verifyEccP384(pk)) }, + } +} + +// WithSignEd25519 adds signing using `ed25519` with the given private key +// using the given key id. +func WithSignEd25519(keyID string, pk *ed25519.PrivateKey) signOption { + return &optImpl{ + s: func(s *signer) { s.keys.Store(keyID, signEd25519(pk)) }, + } +} + +// WithVerifyEd25519 adds signature verification using `ed25519` with the +// given public key using the given key id. +func WithVerifyEd25519(keyID string, pk *ed25519.PublicKey) verifyOption { + return &optImpl{ + v: func(v *verifier) { v.keys.Store(keyID, verifyEd25519(pk)) }, + } +} + // WithHmacSha256 adds signing or signature verification using `hmac-sha256` with the // given shared secret using the given key id. func WithHmacSha256(keyID string, secret []byte) signOrVerifyOption { return &optImpl{ - s: func(s *signer) { s.keys[keyID] = signHmacSha256(secret) }, + s: func(s *signer) { s.keys.Store(keyID, signHmacSha256(secret)) }, v: func(v *verifier) { v.keys.Store(keyID, verifyHmacSha256(secret)) }, } } diff --git a/sign.go b/sign.go index 6364741..06928b4 100644 --- a/sign.go +++ b/sign.go @@ -8,34 +8,51 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/hmac" "crypto/rand" "crypto/rsa" "crypto/sha256" + "crypto/sha512" "encoding/base64" "fmt" "io" "net/http" "strings" + "sync" "time" ) type sigImpl struct { w io.Writer - sign func() []byte + sign func() ([]byte, error) } type sigHolder struct { alg string + nonce func() *string signer func() sigImpl } type signer struct { headers []string - keys map[string]sigHolder + keys sync.Map - // For testing - nowFunc func() time.Time + created *time.Time + expires *time.Time + nonce *string +} + +func withCreated(t time.Time) signOption { + return &optImpl{ + s: func(s *signer) { s.created = &t }, + } +} + +func withNonce(nonce string) signOption { + return &optImpl{ + s: func(s *signer) { s.nonce = &nonce }, + } } func (s *signer) Sign(msg *message) (http.Header, error) { @@ -73,33 +90,47 @@ func (s *signer) Sign(msg *message) (http.Header, error) { items = append(items, h) } - now := s.nowFunc() + var now time.Time + if s.created != nil { + now = *s.created + } else { + now = time.Now() + } sps := make(map[string]string) sigs := make(map[string]string) i := 1 // 1 indexed icky - for k, si := range s.keys { + s.keys.Range(func(k, si any) bool { sp := &signatureParams{ items: items, - keyID: k, + keyID: k.(string), created: now, - alg: si.alg, + expires: s.expires, + nonce: s.nonce, + alg: si.(sigHolder).alg, } sps[fmt.Sprintf("sig%d", i)] = sp.canonicalize() - signer := si.signer() + signer := si.(sigHolder).signer() if _, err := signer.w.Write(b.Bytes()); err != nil { - return nil, err + panic(err) } if err := canonicalizeSignatureParams(signer.w, sp); err != nil { - return nil, err + panic(err) + } + + signed, err := signer.sign() + if err != nil { + panic(err) } - sigs[fmt.Sprintf("sig%d", i)] = base64.StdEncoding.EncodeToString(signer.sign()) + sigs[fmt.Sprintf("sig%d", i)] = base64.StdEncoding.EncodeToString(signed) i++ - } + + return true + }) // for each configured key id, // canonicalize signing options appended to byte slice @@ -125,17 +156,39 @@ func (s *signer) Sign(msg *message) (http.Header, error) { func signRsaPssSha512(pk *rsa.PrivateKey) sigHolder { return sigHolder{ alg: "rsa-pss-sha512", + nonce: func() *string { + n := nonce() + return &n + }, signer: func() sigImpl { - h := sha256.New() + h := sha512.New() return sigImpl{ w: h, - sign: func() []byte { + sign: func() ([]byte, error) { b := h.Sum(nil) + return rsa.SignPSS(rand.Reader, pk, crypto.SHA512, b, nil) + }, + } + }, + } +} + +func signRsaPkcs1v15Sha256(pk *rsa.PrivateKey) sigHolder { + return sigHolder{ + alg: "rsa-v1_5-sha256", + nonce: func() *string { + n := nonce() + return &n + }, + signer: func() sigImpl { + h := sha256.New() - // TODO: might have to deal with this error :) - sig, _ := rsa.SignPSS(rand.Reader, pk, crypto.SHA512, b, nil) - return sig + return sigImpl{ + w: h, + sign: func() ([]byte, error) { + b := h.Sum(nil) + return rsa.SignPKCS1v15(rand.Reader, pk, crypto.SHA512, b) }, } }, @@ -145,17 +198,60 @@ func signRsaPssSha512(pk *rsa.PrivateKey) sigHolder { func signEccP256(pk *ecdsa.PrivateKey) sigHolder { return sigHolder{ alg: "ecdsa-p256-sha256", + nonce: func() *string { + n := nonce() + return &n + }, signer: func() sigImpl { h := sha256.New() return sigImpl{ w: h, - sign: func() []byte { + sign: func() ([]byte, error) { + b := h.Sum(nil) + return ecdsa.SignASN1(rand.Reader, pk, b) + }, + } + }, + } +} + +func signEccP384(pk *ecdsa.PrivateKey) sigHolder { + return sigHolder{ + alg: "ecdsa-p384-sha384", + nonce: func() *string { + n := nonce() + return &n + }, + signer: func() sigImpl { + h := sha512.New384() + + return sigImpl{ + w: h, + sign: func() ([]byte, error) { b := h.Sum(nil) + return ecdsa.SignASN1(rand.Reader, pk, b) + }, + } + }, + } +} + +func signEd25519(pk *ed25519.PrivateKey) sigHolder { + return sigHolder{ + alg: "ed25519", + nonce: func() *string { + n := nonce() + return &n + }, + signer: func() sigImpl { + var h bytes.Buffer - // TODO: might have to deal with this error :) - sig, _ := ecdsa.SignASN1(rand.Reader, pk, b) - return sig + return sigImpl{ + w: &h, + sign: func() ([]byte, error) { + b := h.Bytes() + return ed25519.Sign(*pk, b), nil }, } }, @@ -170,8 +266,17 @@ func signHmacSha256(secret []byte) sigHolder { return sigImpl{ w: h, - sign: func() []byte { return h.Sum(nil) }, + sign: func() ([]byte, error) { return h.Sum(nil), nil }, } }, } } + +func nonce() string { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + panic(err) + } + + return base64.URLEncoding.EncodeToString(b) +} diff --git a/standard_test.go b/standard_test.go index a322ce2..718d1aa 100644 --- a/standard_test.go +++ b/standard_test.go @@ -5,8 +5,12 @@ package httpsig import ( + "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/pem" "net/http" @@ -36,42 +40,54 @@ func testReq() *message { "Host": []string{"example.com"}, "Date": []string{"Tue, 20 Apr 2021 02:07:55 GMT"}, "Content-Type": []string{"application/json"}, - "Digest": []string{"SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="}, + "Content-Digest": []string{"sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:"}, "Content-Length": []string{"18"}, }, } } -func TestSign_B_2_5(t *testing.T) { - k, err := base64.StdEncoding.DecodeString(testSharedSecret) - if err != nil { - panic("could not decode test shared secret") +func TestSign_RSA_PSS_SHA_512_Minimal_B_2_1(t *testing.T) { + block, _ := pem.Decode([]byte(testKeyRSAPSS)) + if block == nil { + panic("could not decode test private key pem") } - s := &signer{ - headers: []string{"@authority", "date", "content-type"}, - keys: map[string]sigHolder{ - "test-shared-secret": signHmacSha256(k), - }, + // taken from crypto/x509/pkcs8.go + type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte + // optional attributes omitted. + } + var privKey pkcs8 + if _, err := asn1.Unmarshal(block.Bytes, &privKey); err != nil { + panic("could not decode test private key pem") + } - nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, + pk, err := x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + if err != nil { + panic("could not decode test private key: " + err.Error()) } + s := signer{} + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-rsa-pss", signRsaPssSha512(pk)) + hdr, err := s.Sign(testReq()) if err != nil { t.Error("signing failed:", err) } - if hdr.Get("Signature-Input") != `sig1=("@authority" "date" "content-type");created=1618884475;keyid="test-shared-secret"` { + if hdr.Get("Signature-Input") != `sig1=();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd";alg="rsa-pss-sha512"` { t.Error("signature input did not match. Got:", hdr.Get("Signature-Input")) } - if hdr.Get("Signature") != `sig1=:fN3AMNGbx0V/cIEKkZOvLOoC3InI+lM2+gTv22x3ia8=:` { - t.Error("signature did not match. Got:", hdr.Get("Signature")) - } + // can't verify signature as it is randomised } -func TestVerify_B_2_1(t *testing.T) { +func TestVerify_RSA_PSS_SHA_512_Minimal_B_2_1(t *testing.T) { block, _ := pem.Decode([]byte(testKeyRSAPSSPub)) if block == nil { panic("could not decode test public key pem") @@ -90,8 +106,8 @@ func TestVerify_B_2_1(t *testing.T) { v.keys.Store("test-key-rsa-pss", verifyRsaPssSha512(pk)) req := testReq() - req.Header.Set("Signature-Input", `sig1=();created=1618884475;keyid="test-key-rsa-pss";alg="rsa-pss-sha512"`) - req.Header.Set("Signature", `sig1=:HWP69ZNiom9Obu1KIdqPPcu/C1a5ZUMBbqS/xwJECV8bhIQVmEAAAzz8LQPvtP1iFSxxluDO1KE9b8L+O64LEOvhwYdDctV5+E39Jy1eJiD7nYREBgxTpdUfzTO+Trath0vZdTylFlxK4H3l3s/cuFhnOCxmFYgEa+cw+StBRgY1JtafSFwNcZgLxVwialuH5VnqJS4JN8PHD91XLfkjMscTo4jmVMpFd3iLVe0hqVFl7MDt6TMkwIyVFnEZ7B/VIQofdShO+C/7MuupCSLVjQz5xA+Zs6Hw+W9ESD/6BuGs6LF1TcKLxW+5K+2zvDY/Cia34HNpRW5io7Iv9/b7iQ==:`) + req.Header.Set("Signature-Input", `sig1=();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd"`) + req.Header.Set("Signature", `sig1=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:`) _, err = v.Verify(req) if err != nil { @@ -99,8 +115,111 @@ func TestVerify_B_2_1(t *testing.T) { } } -func TestVerify_B_2_2(t *testing.T) { - // TODO: key parsing is duplicated +func TestRoundtrip_RSA_PSS_SHA_512_Minimal_B_2_1(t *testing.T) { + blockPrivate, _ := pem.Decode([]byte(testKeyRSAPSS)) + if blockPrivate == nil { + panic("could not decode test private key pem") + } + + // taken from crypto/x509/pkcs8.go + type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte + // optional attributes omitted. + } + var privKey pkcs8 + if _, err := asn1.Unmarshal(blockPrivate.Bytes, &privKey); err != nil { + panic("could not decode test private key pem") + } + + pk, err := x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + if err != nil { + panic("could not decode test private key: " + err.Error()) + } + + s := signer{} + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-rsa-pss", signRsaPssSha512(pk)) + + req := testReq() + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + + blockPub, _ := pem.Decode([]byte(testKeyRSAPSSPub)) + if blockPub == nil { + panic("could not decode test public key pem") + } + + pki, err := x509.ParsePKIXPublicKey(blockPub.Bytes) + if err != nil { + panic("could not decode test public key: " + err.Error()) + } + + pubk := pki.(*rsa.PublicKey) + + v := &verifier{ + nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, + } + v.keys.Store("test-key-rsa-pss", verifyRsaPssSha512(pubk)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + + _, err = v.Verify(req) + if err != nil { + t.Error("verification failed:", err) + } +} + +func TestSign_RSA_PSS_SHA_512_Selective_B_2_2(t *testing.T) { + block, _ := pem.Decode([]byte(testKeyRSAPSS)) + if block == nil { + panic("could not decode test private key pem") + } + + // taken from crypto/x509/pkcs8.go + type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte + // optional attributes omitted. + } + var privKey pkcs8 + if _, err := asn1.Unmarshal(block.Bytes, &privKey); err != nil { + panic("could not decode test private key pem") + } + + pk, err := x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + if err != nil { + panic("could not decode test private key: " + err.Error()) + } + + s := signer{ + headers: []string{"@authority", "content-digest"}, + } + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-rsa-pss", signRsaPssSha512(pk)) + + hdr, err := s.Sign(testReq()) + if err != nil { + t.Error("signing failed:", err) + } + + if hdr.Get("Signature-Input") != `sig1=("@authority" "content-digest");created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd";alg="rsa-pss-sha512"` { + t.Error("signature input did not match. Got:", hdr.Get("Signature-Input")) + } + + // can't verify signature as it is randomised +} + +func TestVerify_RSA_PSS_SHA_512_Selective_B_2_2(t *testing.T) { block, _ := pem.Decode([]byte(testKeyRSAPSSPub)) if block == nil { panic("could not decode test public key pem") @@ -114,13 +233,13 @@ func TestVerify_B_2_2(t *testing.T) { pk := pki.(*rsa.PublicKey) v := &verifier{ - nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, + nowFunc: func() time.Time { return time.Unix(1618884473, 0) }, } v.keys.Store("test-key-rsa-pss", verifyRsaPssSha512(pk)) req := testReq() - req.Header.Set("Signature-Input", `sig1=("@authority" content-type");created=1618884475;keyid="test-key-rsa-pss"`) - req.Header.Set("Signature", `sig1=:ik+OtGmM/kFqENDf9Plm8AmPtqtC7C9a+zYSaxr58b/E6h81ghJS3PcH+m1asiMp8yvccnO/RfaexnqanVB3C72WRNZN7skPTJmUVmoIeqZncdP2mlfxlLP6UbkrgYsk91NS6nwkKC6RRgLhBFqzP42oq8D2336OiQPDAo/04SxZt4Wx9nDGuy2SfZJUhsJqZyEWRk4204x7YEB3VxDAAlVgGt8ewilWbIKKTOKp3ymUeQIwptqYwv0l8mN404PPzRBTpB7+HpClyK4CNp+SVv46+6sHMfJU4taz10s/NoYRmYCGXyadzYYDj0BYnFdERB6NblI/AOWFGl5Axhhmjg==:`) + req.Header.Set("Signature-Input", `sig1=("@authority" "content-digest");created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd";alg="rsa-pss-sha512"`) + req.Header.Set("Signature", `sig1=:e7vSoRHcw4hxLAp129Qdxui1KTgTnI8LM8/gNK7PZJwWm/HCcz+Mxwrzs97fNVCeiu0XPjtPdUcc5mz6/rD644aj0FpvSZzRhlP3KLBU8QMCI80m8blQhDBQeVR/XX9CGLD9BSgWPmd9J4FOf1b/giseT6dbxof1gVvZbHBVPurIGVyht7kNDUTLzxPEFlm7hQBKz0U5UCuqm4Fxw1jRaFm5WhWHwU1A3iqgf7QqE1HT+bCn/MCPl9KstKY5XgKDnJjGA0+qDfFrsNpii1hx/GNsAPWfcJnc7NjASfXtkyItr1e0Wqk2c2gpejiTxW7Qu9mYUmODBiCDn75rK9hSyQ==:`) _, err = v.Verify(req) if err != nil { @@ -128,9 +247,43 @@ func TestVerify_B_2_2(t *testing.T) { } } -func TestVerify_B_2_3(t *testing.T) { - t.Skip("not working as of draft 06 changes") - // TODO: key parsing is duplicated +func TestRoundtrip_RSA_PSS_SHA_512_Selective_B_2_2(t *testing.T) { + blockPrivate, _ := pem.Decode([]byte(testKeyRSAPSS)) + if blockPrivate == nil { + panic("could not decode test private key pem") + } + + // taken from crypto/x509/pkcs8.go + type pkcs8 struct { + Version int + Algo pkix.AlgorithmIdentifier + PrivateKey []byte + // optional attributes omitted. + } + var privKey pkcs8 + if _, err := asn1.Unmarshal(blockPrivate.Bytes, &privKey); err != nil { + panic("could not decode test private key pem") + } + + pk, err := x509.ParsePKCS1PrivateKey(privKey.PrivateKey) + if err != nil { + panic("could not decode test private key: " + err.Error()) + } + + s := signer{ + headers: []string{"@authority", "content-digest"}, + } + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-rsa-pss", signRsaPssSha512(pk)) + + req := testReq() + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + block, _ := pem.Decode([]byte(testKeyRSAPSSPub)) if block == nil { panic("could not decode test public key pem") @@ -141,54 +294,199 @@ func TestVerify_B_2_3(t *testing.T) { panic("could not decode test public key: " + err.Error()) } - pk := pki.(*rsa.PublicKey) + pkpub := pki.(*rsa.PublicKey) v := &verifier{ - nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, + nowFunc: func() time.Time { return time.Unix(1618884473, 0) }, + } + v.keys.Store("test-key-rsa-pss", verifyRsaPssSha512(pkpub)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + + _, err = v.Verify(req) + if err != nil { + t.Error("verification failed:", err) + } +} + +func TestRoundtrip_ECDSA_P256_SHA256(t *testing.T) { + blockPrivate, _ := pem.Decode([]byte(testKeyECCP256)) + if blockPrivate == nil { + panic("could not decode test private key pem") + } + + pk, err := x509.ParseECPrivateKey(blockPrivate.Bytes) + if err != nil { + panic("could not decode test private key: " + err.Error()) + } + + s := signer{} + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-ecc-p256", signEccP256(pk)) + + req := testReq() + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + + block, _ := pem.Decode([]byte(testKeyECCP256Pub)) + if block == nil { + panic("could not decode test public key pem") + } + + pki, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic("could not decode test public key: " + err.Error()) + } + + pkpub := pki.(*ecdsa.PublicKey) + + v := &verifier{ + nowFunc: func() time.Time { return time.Unix(1618884473, 0) }, + } + v.keys.Store("test-key-ecc-p256", verifyEccP256(pkpub)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + + _, err = v.Verify(req) + if err != nil { + t.Error("verification failed:", err) + } +} + +func TestRoundtrip_ECDSA_P384_SHA384(t *testing.T) { + blockPrivate, _ := pem.Decode([]byte(testKeyECCP384)) + if blockPrivate == nil { + panic("could not decode test private key pem") + } + + pk, err := x509.ParseECPrivateKey(blockPrivate.Bytes) + if err != nil { + panic("could not decode test private key: " + err.Error()) } - v.keys.Store("test-key-rsa-pss", verifyRsaPssSha512(pk)) + + s := signer{} + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-ecc-p384", signEccP384(pk)) req := testReq() - req.Header.Set("Signature-Input", `sig1=("date" "@method" "@path" "@query" "@authority" "content-type" "digest" "content-length");created=1618884475;keyid="test-key-rsa-pss"`) - req.Header.Set("Signature", `sig1=:JuJnJMFGD4HMysAGsfOY6N5ZTZUknsQUdClNG51VezDgPUOW03QMe74vbIdndKwW1BBrHOHR3NzKGYZJ7X3ur23FMCdANe4VmKb3Rc1Q/5YxOO8p7KoyfVa4uUcMk5jB9KAn1M1MbgBnqwZkRWsbv8ocCqrnD85Kavr73lx51k1/gU8w673WT/oBtxPtAn1eFjUyIKyA+XD7kYph82I+ahvm0pSgDPagu917SlqUjeaQaNnlZzO03Iy1RZ5XpgbNeDLCqSLuZFVID80EohC2CQ1cL5svjslrlCNstd2JCLmhjL7xV3NYXerLim4bqUQGRgDwNJRnqobpS6C1NBns/Q==:`) + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + + block, _ := pem.Decode([]byte(testKeyECCP384Pub)) + if block == nil { + panic("could not decode test public key pem") + } + + pki, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic("could not decode test public key: " + err.Error()) + } + + pkpub := pki.(*ecdsa.PublicKey) + + v := &verifier{ + nowFunc: func() time.Time { return time.Unix(1618884473, 0) }, + } + v.keys.Store("test-key-ecc-p384", verifyEccP384(pkpub)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + _, err = v.Verify(req) if err != nil { t.Error("verification failed:", err) } } -func TestVerify_B_2_4(t *testing.T) { - t.Skip("not working yet") - /* - block, _ := pem.Decode([]byte(testKeyECCP256Pub)) - if block == nil { - panic("could not decode test public key pem") - } - - pk, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - panic("could not decode test public key: " + err.Error()) - } - - v := &verifier{ - keys: map[string]verHolder{ - "test-key-ecc-p256": verifyEccP256(pk.(*ecdsa.PublicKey)), - }, - - nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, - } - - req := testReq() - req.Header.Set("Signature-Input", `sig1=("content-type" "digest" "content-length");created=1618884475;keyid="test-key-ecc-p256"`) - req.Header.Set("Signature", `sig1=:n8RKXkj0iseWDmC6PNSQ1GX2R9650v+lhbb6rTGoSrSSx18zmn6fPOtBx48/WffYLO0n1RHHf9scvNGAgGq52Q==:`) - err = v.Verify(req) - if err != nil { - t.Error("verification failed:", err) - } - */ +func TestRoundtrip_ED25519(t *testing.T) { + blockPrivate, _ := pem.Decode([]byte(testKeyEd25519)) + if blockPrivate == nil { + panic("could not decode test private key pem") + } + + pki, err := x509.ParsePKCS8PrivateKey(blockPrivate.Bytes) + if err != nil { + panic("could not decode test private key: " + err.Error()) + } + + pk := pki.(ed25519.PrivateKey) + + s := signer{} + withCreated(time.Unix(1618884473, 0)).configureSign(&s) + withNonce("b3k2pp5k7z-50gnwp.yemd").configureSign(&s) + + s.keys.Store("test-key-ed25519", signEd25519(&pk)) + + req := testReq() + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + + block, _ := pem.Decode([]byte(testKeyEd25519Pub)) + if block == nil { + panic("could not decode test public key pem") + } + + pkpubi, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic("could not decode test public key: " + err.Error()) + } + + pkpub := pkpubi.(ed25519.PublicKey) + + v := &verifier{ + nowFunc: func() time.Time { return time.Unix(1618884473, 0) }, + } + v.keys.Store("test-key-ed25519", verifyEd25519(&pkpub)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + + _, err = v.Verify(req) + if err != nil { + t.Error("verification failed:", err) + } +} + +func TestSign_HMAC_SHA_256_B_2_5(t *testing.T) { + k, err := base64.StdEncoding.DecodeString(testSharedSecret) + if err != nil { + panic("could not decode test shared secret") + } + + s := signer{ + headers: []string{"@authority", "date", "content-type"}, + } + withCreated(time.Unix(1618884475, 0)).configureSign(&s) + + s.keys.Store("test-shared-secret", signHmacSha256(k)) + + hdr, err := s.Sign(testReq()) + if err != nil { + t.Error("signing failed:", err) + } + + if hdr.Get("Signature-Input") != `sig1=("@authority" "date" "content-type");created=1618884475;keyid="test-shared-secret";alg="hmac-sha256"` { + t.Error("signature input did not match. Got:", hdr.Get("Signature-Input")) + } + + if hdr.Get("Signature") != `sig1=:Ss67se+mIHEQhqCYpEpp521HLd+2KuQyXRtHr1RfIRk=:` { + t.Error("signature did not match. Got:", hdr.Get("Signature")) + } } -func TestVerify_B_2_5(t *testing.T) { +func TestVerify_HMAC_SHA_256_B_2_5(t *testing.T) { k, err := base64.StdEncoding.DecodeString(testSharedSecret) if err != nil { panic("could not decode test shared secret") @@ -209,39 +507,52 @@ func TestVerify_B_2_5(t *testing.T) { } } +func TestRoundtrip_HMAC_SHA_256_B_2_5(t *testing.T) { + k, err := base64.StdEncoding.DecodeString(testSharedSecret) + if err != nil { + panic("could not decode test shared secret") + } + + s := signer{ + headers: []string{"@authority", "date", "content-type"}, + } + withCreated(time.Unix(1618884475, 0)).configureSign(&s) + + s.keys.Store("test-shared-secret", signHmacSha256(k)) + + req := testReq() + hdr, err := s.Sign(req) + if err != nil { + t.Error("signing failed:", err) + } + + v := &verifier{ + nowFunc: func() time.Time { return time.Unix(1618884475, 0) }, + } + v.keys.Store("test-shared-secret", verifyHmacSha256(k)) + + req.Header.Set("Signature-Input", hdr["Signature-Input"][0]) + req.Header.Set("Signature", hdr["Signature"][0]) + + _, err = v.Verify(req) + if err != nil { + t.Error("verification failed:", err) + } +} + // The following keypairs are taken from the Draft Standard, so we may recreate the examples in tests. // If your robot scans this repo and says it's leaking keys I will be mildly amused. -/* - -var testKeyRSA = ` ------BEGIN RSA PRIVATE KEY----- -MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR874L8cnZxKzAGwd7v36APp7Pv6Q2jdsP -BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbhgajzrw3MOEt8uA5txSKobBpKDeBLOsd -JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYRjTSd/QBwVW9OwNFhekro3RtlinV0a75 -jfZgkne/YiktSvLG34lw2zqXBDTC5NHROUqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI -lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ -SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQABAoIBAG/JZuSWdoVHbi56 -vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPjxuKoWaKO1cBQlE1pSWp/cKncYgD5WxE -CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoHsirK6oNcEnHveydfzJL5934egm6p8DW -+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA -yRXtK+JGxZpImTq+NHvEWWCu09SCq0r838ceQI55SvzmTkwqtC+8AT2zFviMZkKR -Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7O+6t3wBsQ1sLptoSgX3QblELY5asI0J -YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIAKiTm9o6psPlc8CrLI9CH0UbuaA2JCOM -cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9m5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw -DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFtxKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1 -mwJ5AL0pYF0G7x81prlARURwHo0Yf52kEw1dxpx+JXER7hQRWQki5/NsUEtv+8RT -qn2m6qte5DXLyn83b1qRscSdnCCwKtKWUug5q2ZbwVOCJCtmRwmnP131lWRYfj67 -B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2nsN0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv -9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcWjOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn -f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKYVYNHahNFEw5TsAcQWDLRpkGybBcxqZo -81YCqlqidwfeO5YtlO7etx1xLyqa2NsCeG9A86UjG+aeNnXEIDk1PDK+EuiThIUa -/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQYgTImOlfW6/GuYIxKYgEKCFHFqJATAG -IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8CxaEMQoevxAta0ssMK3w6UsDtvUvYvF22m -qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLgsnrtUPZx+z2Ep2x0xc5orneB5fGyF1P -WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTxcjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ -EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRDs15M38eG2cYwB1PZpDHScDnDA0= ------END RSA PRIVATE KEY----- +var testKeyRSAPSSPub = ` +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4tmm3r20Wd/PbqvP1s2 ++QEtvpuRaV8Yq40gjUR8y2Rjxa6dpG2GXHbPfvMs8ct+Lh1GH45x28Rw3Ry53mm+ +oAXjyQ86OnDkZ5N8lYbggD4O3w6M6pAvLkhk95AndTrifbIFPNU8PPMO7OyrFAHq +gDsznjPFmTOtCEcN2Z1FpWgchwuYLPL+Wokqltd11nqqzi+bJ9cvSKADYdUAAN5W +Utzdpiy6LbTgSxP7ociU4Tn0g5I6aDZJ7A8Lzo0KSyZYoA485mqcO0GVAdVw9lq4 +aOT9v6d+nb4bnNkQVklLQ3fVAvJm+xdDOp9LCNCN48V2pnDOkFV6+U9nV5oyc6XI +2wIDAQAB +-----END PUBLIC KEY----- ` var testKeyRSAPSS = ` @@ -274,21 +585,14 @@ S7Fnk6ZVVVHsxjtaHy1uJGFlaZzKR4AGNaUTOJMs6NadzCmGPAxNQQOCqoUjn4XR rOjr9w349JooGXhOxbu8nOxX -----END PRIVATE KEY----- ` -*/ -var testKeyRSAPSSPub = ` +var testKeyECCP256Pub = ` -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4tmm3r20Wd/PbqvP1s2 -+QEtvpuRaV8Yq40gjUR8y2Rjxa6dpG2GXHbPfvMs8ct+Lh1GH45x28Rw3Ry53mm+ -oAXjyQ86OnDkZ5N8lYbggD4O3w6M6pAvLkhk95AndTrifbIFPNU8PPMO7OyrFAHq -gDsznjPFmTOtCEcN2Z1FpWgchwuYLPL+Wokqltd11nqqzi+bJ9cvSKADYdUAAN5W -Utzdpiy6LbTgSxP7ociU4Tn0g5I6aDZJ7A8Lzo0KSyZYoA485mqcO0GVAdVw9lq4 -aOT9v6d+nb4bnNkQVklLQ3fVAvJm+xdDOp9LCNCN48V2pnDOkFV6+U9nV5oyc6XI -2wIDAQAB +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqIVYZVLCrPZHGHjP17CTW0/+D9Lf +w0EkjqF7xB4FivAxzic30tMM4GF+hR6Dxh71Z50VGGdldkkDXZCnTNnoXQ== -----END PUBLIC KEY----- - ` +` -/* var testKeyECCP256 = ` -----BEGIN EC PRIVATE KEY----- MHcCAQEEIFKbhfNZfpDsW43+0+JjUr9K+bTeuxopu653+hBaXGA7oAoGCCqGSM49 @@ -297,12 +601,33 @@ AwEHoUQDQgAEqIVYZVLCrPZHGHjP17CTW0/+D9Lfw0EkjqF7xB4FivAxzic30tMM -----END EC PRIVATE KEY----- ` -var testKeyECCP256Pub = ` +var testKeyECCP384Pub = ` -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqIVYZVLCrPZHGHjP17CTW0/+D9Lf -w0EkjqF7xB4FivAxzic30tMM4GF+hR6Dxh71Z50VGGdldkkDXZCnTNnoXQ== +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUWosCCtXp4eJkinU2XaDGSrrSfMynZkI +EELa7Ratog6SrkIFD9nowhLoxc3Px4zAwxQzD8j5Th+vCtswq7ExACNiM6kM9974 +mF1l1Ll2Pn19pJCE2SutyxcMeAr4Lrgi +-----END PUBLIC KEY----- +` + +var testKeyECCP384 = ` +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDDayXurkt5pieok3TsD5CdPvrUgljTE8n5o9M1bapc8yMz94WCAiQZb +TXi9MwOv4TWgBwYFK4EEACKhZANiAARRaiwIK1enh4mSKdTZdoMZKutJ8zKdmQgQ +QtrtFq2iDpKuQgUP2ejCEujFzc/HjMDDFDMPyPlOH68K2zCrsTEAI2IzqQz33viY +XWXUuXY+fX2kkITZK63LFwx4CvguuCI= +-----END EC PRIVATE KEY----- +` + +var testKeyEd25519Pub = ` +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs= -----END PUBLIC KEY----- ` -*/ + +var testKeyEd25519 = ` +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF +-----END PRIVATE KEY----- +` var testSharedSecret = `uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ==` diff --git a/verify.go b/verify.go index 57c1e46..a7a2765 100644 --- a/verify.go +++ b/verify.go @@ -8,6 +8,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/hmac" "crypto/rsa" "crypto/sha256" @@ -32,7 +33,7 @@ type verHolder struct { } type verifier struct { - keys sync.Map // map[string]verHolder + keys sync.Map resolver VerifyingKeyResolver // For testing @@ -233,6 +234,24 @@ func verifyRsaPssSha512(pk *rsa.PublicKey) verHolder { } } +func verifyRsaPkcs1v15Sha256(pk *rsa.PublicKey) verHolder { + return verHolder{ + alg: "rsa-v1_5-sha256", + verifier: func() verImpl { + h := sha256.New() + + return verImpl{ + w: h, + verify: func(s []byte) error { + b := h.Sum(nil) + + return rsa.VerifyPKCS1v15(pk, crypto.SHA512, b, s) + }, + } + }, + } +} + func verifyEccP256(pk *ecdsa.PublicKey) verHolder { return verHolder{ alg: "ecdsa-p256-sha256", @@ -255,6 +274,50 @@ func verifyEccP256(pk *ecdsa.PublicKey) verHolder { } } +func verifyEccP384(pk *ecdsa.PublicKey) verHolder { + return verHolder{ + alg: "ecdsa-p384-sha384", + verifier: func() verImpl { + h := sha512.New384() + + return verImpl{ + w: h, + verify: func(s []byte) error { + b := h.Sum(nil) + + if !ecdsa.VerifyASN1(pk, b, s) { + return errInvalidSignature + } + + return nil + }, + } + }, + } +} + +func verifyEd25519(pk *ed25519.PublicKey) verHolder { + return verHolder{ + alg: "ed25519", + verifier: func() verImpl { + var h bytes.Buffer + + return verImpl{ + w: &h, + verify: func(s []byte) error { + b := h.Bytes() + + if !ed25519.Verify(*pk, b, s) { + return errInvalidSignature + } + + return nil + }, + } + }, + } +} + func verifyHmacSha256(secret []byte) verHolder { return verHolder{ alg: "hmac-sha256",