Skip to content

Commit

Permalink
feat: Bring up to date with spec; Tests restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderTar committed Jan 9, 2024
1 parent 395021c commit 71551cc
Show file tree
Hide file tree
Showing 10 changed files with 734 additions and 172 deletions.
44 changes: 30 additions & 14 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<p align="center"><i>Standards-based HTTP request signing and verification for <a href="https://golang.org">Go</a></i></p>

<div align="center">
<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>
<a href="https://pkg.go.dev/github.com/offblocks/go-httpsig"><img src="https://pkg.go.dev/badge/github.com/offblocks/go-httpsig.svg" alt="Go Reference"></a>
<img alt="Alpha Quality" src="https://img.shields.io/badge/status-ALPHA-orange.svg" >
<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>
<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>
<a href="./LICENSE"><img alt="BSD license" src="https://img.shields.io/badge/license-BSD-blue.svg"></a>
<a href="https://codecov.io/gh/jbowes/httpsig"><img alt="codecov" src="https://img.shields.io/codecov/c/github/jbowes/httpsig.svg"></a>
<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>
<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>
<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>
</div><br /><br />

## Introduction
Expand Down Expand Up @@ -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. |
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions canonicalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type signatureParams struct {
alg string
created time.Time
expires *time.Time
nonce string
nonce *string
}

func (sp *signatureParams) canonicalize() string {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package httpsig

import (
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"fmt"
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"net/http"
"time"

"github.com/Gh0u1L5/httpsig"
"github.com/offblocks/httpsig"
)

const secret = "support-your-local-cat-bonnet-store"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/Gh0u1L5/httpsig
module github.com/offblocks/httpsig

go 1.18
72 changes: 59 additions & 13 deletions httpsig.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package httpsig
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"io"
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)) },
}
}

Expand All @@ -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)) },
}
}

Expand All @@ -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)) },
}
}

0 comments on commit 71551cc

Please sign in to comment.