Skip to content

Commit ca22123

Browse files
committed
feat: Bring up to date with spec; Tests restructure
1 parent 395021c commit ca22123

10 files changed

Lines changed: 734 additions & 172 deletions

File tree

.github/workflows/go.yml

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,46 @@ name: Go
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main]
66
pull_request:
7-
branches: [ main ]
7+
branches: [main]
88

99
jobs:
10-
1110
build:
1211
runs-on: ubuntu-latest
1312
steps:
14-
- uses: actions/checkout@v3.5.3
13+
- uses: actions/checkout@v3.5.3
14+
15+
- name: Set up Go
16+
uses: actions/setup-go@v4
17+
with:
18+
go-version: 1.18
1519

16-
- name: Set up Go
17-
uses: actions/setup-go@v4
18-
with:
19-
go-version: 1.18
20+
- name: Build
21+
run: go build -v ./...
2022

21-
- name: Build
22-
run: go build -v ./...
23+
- name: Test
24+
run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
2325

24-
- name: Test
25-
run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
26+
- name: Upload coverage reports to Codecov
27+
uses: codecov/codecov-action@v3
28+
env:
29+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2630

27-
- name: Upload coverage to Codecov
28-
run: bash <(curl -s https://codecov.io/bash)
31+
- name: Bump version and push tag
32+
id: tag_version
33+
uses: mathieudutour/github-tag-action@v6.1
34+
if: github.ref == 'refs/heads/main'
35+
with:
36+
github_token: ${{ secrets.GITHUB_TOKEN }}
37+
38+
- name: Create a GitHub release
39+
uses: ncipollo/release-action@v1
40+
if: github.ref == 'refs/heads/main'
41+
with:
42+
tag: ${{ steps.tag_version.outputs.new_tag }}
43+
name: Release ${{ steps.tag_version.outputs.new_tag }}
44+
body: ${{ steps.tag_version.outputs.changelog }}
2945

3046
golangci:
3147
name: lint

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
<p align="center"><i>Standards-based HTTP request signing and verification for <a href="https://golang.org">Go</a></i></p>
88

99
<div align="center">
10-
<a href="https://pkg.go.dev/github.com/jbowes/httpsig"><img src="https://pkg.go.dev/badge/github.com/jbowes/httpsig.svg" alt="Go Reference"></a>
10+
<a href="https://pkg.go.dev/github.com/offblocks/httpsig"><img src="https://pkg.go.dev/badge/github.com/offblocks/httpsig.svg" alt="Go Reference"></a>
1111
<img alt="Alpha Quality" src="https://img.shields.io/badge/status-ALPHA-orange.svg" >
12-
<a href="https://github.com/jbowes/httpsig/actions/workflows/go.yml"><img alt="Build Status" src="https://github.com/jbowes/httpsig/actions/workflows/go.yml/badge.svg?branch=main"></a>
12+
<a href="https://github.com/offblocks/go-httpsig/actions/workflows/go.yml"><img alt="Build Status" src="https://github.com/offblocks/go-httpsig/actions/workflows/go.yml/badge.svg?branch=main"></a>
1313
<a href="./LICENSE"><img alt="BSD license" src="https://img.shields.io/badge/license-BSD-blue.svg"></a>
14-
<a href="https://codecov.io/gh/jbowes/httpsig"><img alt="codecov" src="https://img.shields.io/codecov/c/github/jbowes/httpsig.svg"></a>
15-
<a href="https://goreportcard.com/report/github.com/jbowes/httpsig"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/jbowes/httpsig"></a>
14+
<a href="https://codecov.io/gh/offblocks/go-httpsig"><img alt="codecov" src="https://img.shields.io/codecov/c/github/offblocks/go-httpsig.svg"></a>
15+
<a href="https://goreportcard.com/report/github.com/offblocks/go-httpsig"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/offblocks/go-httpsig"></a>
1616
</div><br /><br />
1717

1818
## Introduction
@@ -106,9 +106,11 @@ computation is based on version `05` of [Digest Headers][dighdr]
106106
| create multiple signatures || | |
107107
| verify from multiple signatures || | |
108108
| `rsa-pss-sha512` || | |
109-
| `rsa-v1_5-sha256` | | | |
109+
| `rsa-v1_5-sha256` | | | |
110110
| `hmac-sha256` || | |
111111
| `ecdsa-p256-sha256` || | |
112+
| `ecdsa-p384-sha384` || | |
113+
| `ed25519` || | |
112114
| custom signature formats | || `eddsa` is not part of the spec, so custom support here would be nice! |
113115
| JSON Web Signatures | || JWS doesn't support any additional algs, but it is part of the spec |
114116
| 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]
117119
| response digests | || Tricky to support for signature use according to the spec |
118120
| multiple digests | || |
119121
| digest: `sha-256` | || |
120-
| digest: `sha-512` | || |
122+
| digest: `sha-512` | || |
121123
| digest: `md5` | || Deprecated in the spec. Unlikely to be supported. |
122124
| digest: `sha` | || Deprecated in the spec. Unlikely to be supported. |
123125
| digest: `unixsum` | || |
124126
| digest: `unixcksum` | || |
125127
| digest: `id-sha-512` | || |
126-
| digest: `id-sha-256` | | | `id-*` digests are more resilient for `content-encoding` support |
128+
| digest: `id-sha-256` | || |
127129
| custom digest formats | || |
128130

129131
## Contributing
@@ -151,7 +153,7 @@ I would love your help!
151153
[dighdr]: https://datatracker.ietf.org/doc/draft-ietf-httpbis-digest-headers/
152154
[myblog]: https://repl.ca/modern-webhook-signatures/
153155

154-
[godoc]: https://pkg.go.dev/github.com/jbowes/httpsig
156+
[godoc]: https://pkg.go.dev/github.com/offblocks/httpsig
155157
[issues]: ./issues
156158
[bug]: ./issues/new?labels=bug
157159
[enhancement]: ./issues/new?labels=enhancement

canonicalize.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ type signatureParams struct {
9999
alg string
100100
created time.Time
101101
expires *time.Time
102-
nonce string
102+
nonce *string
103103
}
104104

105105
func (sp *signatureParams) canonicalize() string {
@@ -118,6 +118,10 @@ func (sp *signatureParams) canonicalize() string {
118118
o += fmt.Sprintf(";keyid=\"%s\"", sp.keyID)
119119
}
120120

121+
if sp.nonce != nil {
122+
o += fmt.Sprintf(";nonce=\"%s\"", *sp.nonce)
123+
}
124+
121125
if sp.alg != "" {
122126
o += fmt.Sprintf(";alg=\"%s\"", sp.alg)
123127
}
@@ -168,7 +172,8 @@ func parseSignatureInput(in string) (*signatureParams, error) {
168172
case "keyid":
169173
sp.keyID = strings.Trim(paramParts[1], `"`)
170174
case "nonce":
171-
sp.nonce = strings.Trim(paramParts[1], `"`)
175+
nonce := strings.Trim(paramParts[1], `"`)
176+
sp.nonce = &nonce
172177
case "created":
173178
i, err := strconv.ParseInt(paramParts[1], 10, 64)
174179
if err != nil {

digest.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
package httpsig
66

77
import (
8-
"crypto/sha256"
8+
"crypto/sha512"
99
"crypto/subtle"
1010
"encoding/base64"
1111
"fmt"
@@ -17,9 +17,9 @@ import (
1717
// TODO: support more algorithms, and maybe do its own package.
1818

1919
func calcDigest(in []byte) string {
20-
dig := sha256.Sum256(in)
20+
dig := sha512.Sum512(in)
2121

22-
return fmt.Sprintf("id-sha256=%s", base64.StdEncoding.EncodeToString(dig[:]))
22+
return fmt.Sprintf("sha-512=:%s:", base64.StdEncoding.EncodeToString(dig[:]))
2323
}
2424

2525
func verifyDigest(in []byte, dig string) bool {

example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"net/http"
1111
"time"
1212

13-
"github.com/Gh0u1L5/httpsig"
13+
"github.com/offblocks/httpsig"
1414
)
1515

1616
const secret = "support-your-local-cat-bonnet-store"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/Gh0u1L5/httpsig
1+
module github.com/offblocks/httpsig
22

33
go 1.18

httpsig.go

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package httpsig
77
import (
88
"bytes"
99
"crypto/ecdsa"
10+
"crypto/ed25519"
1011
"crypto/rsa"
1112
"errors"
1213
"io"
@@ -27,14 +28,11 @@ func sliceHas(haystack []string, needle string) bool {
2728
}
2829

2930
type Signer struct {
30-
signer
31+
*signer
3132
}
3233

3334
func NewSigner(opts ...signOption) *Signer {
34-
s := signer{
35-
keys: make(map[string]sigHolder),
36-
nowFunc: time.Now,
37-
}
35+
s := signer{}
3836

3937
for _, o := range opts {
4038
o.configureSign(&s)
@@ -47,13 +45,13 @@ func NewSigner(opts ...signOption) *Signer {
4745
// TODO: normalize headers? lowercase & de-dupe
4846

4947
// specialty components and digest first, for aesthetics
50-
for _, comp := range []string{"digest", "@query", "@path", "@method"} {
48+
for _, comp := range []string{"content-digest", "@query", "@path", "@method"} {
5149
if !sliceHas(s.headers, comp) {
5250
s.headers = append([]string{comp}, s.headers...)
5351
}
5452
}
5553

56-
return &Signer{s}
54+
return &Signer{&s}
5755
}
5856

5957
func (s *Signer) Sign(r *http.Request) error {
@@ -72,7 +70,7 @@ func (s *Signer) Sign(r *http.Request) error {
7270

7371
// Always set a digest (for now)
7472
// TODO: we could skip setting digest on an empty body if content-length is included in the sig
75-
r.Header.Set("Digest", calcDigest(b.Bytes()))
73+
r.Header.Set("Content-Digest", calcDigest(b.Bytes()))
7674

7775
msg := messageFromRequest(r)
7876
hdr, err := s.signer.Sign(msg)
@@ -131,9 +129,9 @@ func (v *Verifier) Verify(r *http.Request) (keyID string, err error) {
131129
}
132130
}
133131

134-
// Check the digest if set. We only support id-sha-256 for now.
132+
// Check the digest if set. We only support sha-512 for now.
135133
// TODO: option to require this?
136-
if dig := r.Header.Get("Digest"); dig != "" {
134+
if dig := r.Header.Get("Content-Digest"); dig != "" {
137135
if !verifyDigest(b.Bytes(), dig) {
138136
return keyID, errors.New("digest mismatch")
139137
}
@@ -233,11 +231,27 @@ func WithVerifyingKeyResolver(resolver VerifyingKeyResolver) verifyOption {
233231
}
234232
}
235233

234+
// WithSignRsaPkcs1v15Sha256 adds signing using `rsa-v1_5-sha256` with the given private key
235+
// using the given key id.
236+
func WithSignRsaPkcs1v15Sha256(keyID string, pk *rsa.PrivateKey) signOption {
237+
return &optImpl{
238+
s: func(s *signer) { s.keys.Store(keyID, signRsaPkcs1v15Sha256(pk)) },
239+
}
240+
}
241+
242+
// WithVerifyRsaPkcs1v15Sha256 adds signature verification using `rsa-v1_5-sha256` with the
243+
// given public key using the given key id.
244+
func WithVerifyRsaPkcs1v15Sha256(keyID string, pk *rsa.PublicKey) verifyOption {
245+
return &optImpl{
246+
v: func(v *verifier) { v.keys.Store(keyID, verifyRsaPkcs1v15Sha256(pk)) },
247+
}
248+
}
249+
236250
// WithSignRsaPssSha512 adds signing using `rsa-pss-sha512` with the given private key
237251
// using the given key id.
238252
func WithSignRsaPssSha512(keyID string, pk *rsa.PrivateKey) signOption {
239253
return &optImpl{
240-
s: func(s *signer) { s.keys[keyID] = signRsaPssSha512(pk) },
254+
s: func(s *signer) { s.keys.Store(keyID, signRsaPssSha512(pk)) },
241255
}
242256
}
243257

@@ -253,7 +267,7 @@ func WithVerifyRsaPssSha512(keyID string, pk *rsa.PublicKey) verifyOption {
253267
// using the given key id.
254268
func WithSignEcdsaP256Sha256(keyID string, pk *ecdsa.PrivateKey) signOption {
255269
return &optImpl{
256-
s: func(s *signer) { s.keys[keyID] = signEccP256(pk) },
270+
s: func(s *signer) { s.keys.Store(keyID, signEccP256(pk)) },
257271
}
258272
}
259273

@@ -265,11 +279,43 @@ func WithVerifyEcdsaP256Sha256(keyID string, pk *ecdsa.PublicKey) verifyOption {
265279
}
266280
}
267281

282+
// WithSignEcdsaP384Sha384 adds signing using `ecdsa-p384-sha384` with the given private key
283+
// using the given key id.
284+
func WithSignEcdsaP384Sha384(keyID string, pk *ecdsa.PrivateKey) signOption {
285+
return &optImpl{
286+
s: func(s *signer) { s.keys.Store(keyID, signEccP384(pk)) },
287+
}
288+
}
289+
290+
// WithVerifyEcdsaP384Sha384 adds signature verification using `ecdsa-p384-sha384` with the
291+
// given public key using the given key id.
292+
func WithVerifyEcdsaP384Sha384(keyID string, pk *ecdsa.PublicKey) verifyOption {
293+
return &optImpl{
294+
v: func(v *verifier) { v.keys.Store(keyID, verifyEccP384(pk)) },
295+
}
296+
}
297+
298+
// WithSignEd25519 adds signing using `ed25519` with the given private key
299+
// using the given key id.
300+
func WithSignEd25519(keyID string, pk *ed25519.PrivateKey) signOption {
301+
return &optImpl{
302+
s: func(s *signer) { s.keys.Store(keyID, signEd25519(pk)) },
303+
}
304+
}
305+
306+
// WithVerifyEd25519 adds signature verification using `ed25519` with the
307+
// given public key using the given key id.
308+
func WithVerifyEd25519(keyID string, pk *ed25519.PublicKey) verifyOption {
309+
return &optImpl{
310+
v: func(v *verifier) { v.keys.Store(keyID, verifyEd25519(pk)) },
311+
}
312+
}
313+
268314
// WithHmacSha256 adds signing or signature verification using `hmac-sha256` with the
269315
// given shared secret using the given key id.
270316
func WithHmacSha256(keyID string, secret []byte) signOrVerifyOption {
271317
return &optImpl{
272-
s: func(s *signer) { s.keys[keyID] = signHmacSha256(secret) },
318+
s: func(s *signer) { s.keys.Store(keyID, signHmacSha256(secret)) },
273319
v: func(v *verifier) { v.keys.Store(keyID, verifyHmacSha256(secret)) },
274320
}
275321
}

0 commit comments

Comments
 (0)