Skip to content

Commit 8d61ea6

Browse files
committed
Support signing and verifying the Digest header
1 parent 90d9db9 commit 8d61ea6

5 files changed

Lines changed: 161 additions & 32 deletions

File tree

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ signing of hash schemes. Its goals are:
1313
* Remaining flexible with headers included in the signing string
1414
* Support both HTTP requests and responses
1515
* Explicitly not support known-cryptographically weak algorithms
16+
* Support automatic signing and validating Digest headers
1617

1718
## How to use
1819

@@ -25,14 +26,18 @@ Signing a request or response requires creating a new `Signer` and using it:
2526
```
2627
func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
2728
prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256}
29+
digestAlgorithm := DigestSha256
2830
// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
2931
headersToSign := []string{httpsig.RequestTarget, "date", "digest"}
30-
signer, chosenAlgo, err := httpsig.NewSigner(prefs, headersToSign, httpsig.Signature)
32+
signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature)
3133
if err != nil {
3234
return err
3335
}
36+
// To sign the digest, we need to give the signer a copy of the body...
37+
// ...but it is optional, no digest will be signed if given "nil"
38+
body := ...
3439
// If r were a http.ResponseWriter, call SignResponse instead.
35-
return signer.SignRequest(privateKey, pubKeyId, r)
40+
return signer.SignRequest(privateKey, pubKeyId, r, body)
3641
}
3742
```
3843

@@ -51,7 +56,10 @@ func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) {
5156
// Set headers and such on w
5257
s.mu.Lock()
5358
defer s.mu.Unlock()
54-
err := s.signer.SignResponse(privateKey, pubKeyId, w)
59+
// To sign the digest, we need to give the signer a copy of the response body...
60+
// ...but it is optional, no digest will be signed if given "nil"
61+
body := ...
62+
err := s.signer.SignResponse(privateKey, pubKeyId, w, body)
5563
if err != nil {
5664
...
5765
}
@@ -76,6 +84,7 @@ func verify(r *http.Request) error {
7684
pubKeyId := verifier.KeyId()
7785
var algo httpsig.Algorithm = ...
7886
var pubKey crypto.PublicKey = ...
87+
// The verifier will verify the Digest in addition to the HTTP signature
7988
return verifier.Verify(pubKey, algo)
8089
}
8190
```

digest.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) {
6262
return
6363
}
6464

65+
func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) {
66+
_, ok := r.Header()[digestHeader]
67+
if ok {
68+
err = fmt.Errorf("cannot add Digest: Digest is already set")
69+
return
70+
}
71+
var h hash.Hash
72+
var a DigestAlgorithm
73+
h, a, err = getHash(algo)
74+
if err != nil {
75+
return
76+
}
77+
sum := h.Sum(b)
78+
r.Header().Add(digestHeader,
79+
fmt.Sprintf("%s%s%s",
80+
a,
81+
digestDelim,
82+
base64.StdEncoding.EncodeToString(sum[:])))
83+
return
84+
}
85+
6586
func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) {
6687
d := r.Header.Get(digestHeader)
6788
if len(d) == 0 {

httpsig.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,14 @@ type Signer interface {
107107
// is expected to be of type []byte. If the Signer was created using an
108108
// RSA based algorithm, then the private key is expected to be of type
109109
// *rsa.PrivateKey.
110-
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request) error
110+
//
111+
// A Digest (RFC 3230) will be added to the request. The body provided
112+
// must match the body used in the request, and is allowed to be nil.
113+
// The Digest ensures the request body is not tampered with in flight,
114+
// and if the signer is created to also sign the "Digest" header, the
115+
// HTTP Signature will then ensure both the Digest and body are not both
116+
// modified to maliciously represent different content.
117+
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error
111118
// SignResponse signs the response using a private key. The public key
112119
// id is used by the HTTP client to identify which key to use to verify
113120
// the signature.
@@ -116,7 +123,14 @@ type Signer interface {
116123
// is expected to be of type []byte. If the Signer was created using an
117124
// RSA based algorithm, then the private key is expected to be of type
118125
// *rsa.PrivateKey.
119-
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter) error
126+
//
127+
// A Digest (RFC 3230) will be added to the response. The body provided
128+
// must match the body written in the response, and is allowed to be
129+
// nil. The Digest ensures the response body is not tampered with in
130+
// flight, and if the signer is created to also sign the "Digest"
131+
// header, the HTTP Signature will then ensure both the Digest and body
132+
// are not both modified to maliciously represent different content.
133+
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error
120134
}
121135

122136
// NewSigner creates a new Signer with the provided algorithm preferences to
@@ -125,20 +139,23 @@ type Signer interface {
125139
// algorithms were available, then the default algorithm is used. The headers
126140
// specified will be included into the HTTP signatures.
127141
//
142+
// The Digest will also be calculated on a request's body using the provided
143+
// digest algorithm, if "Digest" is one of the headers listed.
144+
//
128145
// The provided scheme determines which header is populated with the HTTP
129146
// Signature.
130147
//
131148
// An error is returned if an unknown or a known cryptographically insecure
132149
// Algorithm is provided.
133-
func NewSigner(prefs []Algorithm, headers []string, scheme SignatureScheme) (Signer, Algorithm, error) {
150+
func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme) (Signer, Algorithm, error) {
134151
for _, pref := range prefs {
135-
s, err := newSigner(pref, headers, scheme)
152+
s, err := newSigner(pref, dAlgo, headers, scheme)
136153
if err != nil {
137154
continue
138155
}
139156
return s, pref, err
140157
}
141-
s, err := newSigner(defaultAlgorithm, headers, scheme)
158+
s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme)
142159
return s, defaultAlgorithm, err
143160
}
144161

@@ -187,11 +204,12 @@ func NewResponseVerifier(r *http.Response) (Verifier, error) {
187204
})
188205
}
189206

190-
func newSigner(algo Algorithm, headers []string, scheme SignatureScheme) (Signer, error) {
207+
func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme) (Signer, error) {
191208
s, err := signerFromString(string(algo))
192209
if err == nil {
193210
a := &asymmSigner{
194211
s: s,
212+
dAlgo: dAlgo,
195213
headers: headers,
196214
targetHeader: scheme,
197215
prefix: scheme.authScheme(),
@@ -204,6 +222,7 @@ func newSigner(algo Algorithm, headers []string, scheme SignatureScheme) (Signer
204222
}
205223
c := &macSigner{
206224
m: m,
225+
dAlgo: dAlgo,
207226
headers: headers,
208227
targetHeader: scheme,
209228
prefix: scheme.authScheme(),

0 commit comments

Comments
 (0)