Skip to content

Commit 0ec6ffd

Browse files
committed
Support HTTP Signature verification.
Note that the library includes the deprecated 'algorithm' HTTP Signature parameter for backwards compatibility when creating a signature. However, upon verification it ignores the 'algorithm' deprecated parameter and instead relies on the new behavior of the application knowing which algorithm to use.
1 parent 1ba820e commit 0ec6ffd

4 files changed

Lines changed: 244 additions & 42 deletions

File tree

‎algorithms.go‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func isForbiddenHash(h crypto.Hash) bool {
107107
// signer is an internally public type.
108108
type signer interface {
109109
Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error)
110-
Verify(pub crypto.PublicKey, signature []byte) error
110+
Verify(pub crypto.PublicKey, toHash, signature []byte) error
111111
String() string
112112
}
113113

@@ -182,12 +182,15 @@ func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]
182182
return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil))
183183
}
184184

185-
func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, signature []byte) error {
185+
func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error {
186186
defer r.Reset()
187187
rsaK, ok := pub.(*rsa.PublicKey)
188188
if !ok {
189189
return errors.New("crypto.PublicKey is not *rsa.PublicKey")
190190
}
191+
if err := r.setSig(toHash); err != nil {
192+
return err
193+
}
191194
return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature)
192195
}
193196

‎httpsig.go‎

Lines changed: 97 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
func init() {
2525
// This should guarantee that at runtime the defaultAlgorithm will not
2626
// result in errors when fetching a macer or signer (see algorithms.go)
27-
if ok, err := isAvailable(defaultAlgorithm); err != nil {
27+
if ok, err := isAvailable(string(defaultAlgorithm)); err != nil {
2828
panic(err)
2929
} else if !ok {
3030
panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm))
@@ -33,55 +33,78 @@ func init() {
3333

3434
const (
3535
HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String
36-
HMAC_SHA256 = hmacPrefix + "-" + sha256String
37-
HMAC_SHA384 = hmacPrefix + "-" + sha384String
38-
HMAC_SHA512 = hmacPrefix + "-" + sha512String
39-
HMAC_RIPEMD160 = hmacPrefix + "-" + ripemd160String
40-
HMAC_SHA3_224 = hmacPrefix + "-" + sha3_224String
41-
HMAC_SHA3_256 = hmacPrefix + "-" + sha3_256String
42-
HMAC_SHA3_384 = hmacPrefix + "-" + sha3_384String
43-
HMAC_SHA3_512 = hmacPrefix + "-" + sha3_512String
44-
HMAC_SHA512_224 = hmacPrefix + "-" + sha512_224String
45-
HMAC_SHA512_256 = hmacPrefix + "-" + sha512_256String
46-
HMAC_BLAKE2S_256 = hmacPrefix + "-" + blake2s_256String
47-
HMAC_BLAKE2B_256 = hmacPrefix + "-" + blake2b_256String
48-
HMAC_BLAKE2B_384 = hmacPrefix + "-" + blake2b_384String
49-
HMAC_BLAKE2B_512 = hmacPrefix + "-" + blake2b_512String
50-
BLAKE2S_256 = blake2s_256String
51-
BLAKE2B_256 = blake2b_256String
52-
BLAKE2B_384 = blake2b_384String
53-
BLAKE2B_512 = blake2b_512String
54-
RSA_SHA224 = rsaPrefix + "-" + sha224String
36+
HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String
37+
HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String
38+
HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String
39+
HMAC_RIPEMD160 Algorithm = hmacPrefix + "-" + ripemd160String
40+
HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String
41+
HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String
42+
HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String
43+
HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String
44+
HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String
45+
HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String
46+
HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String
47+
HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String
48+
HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String
49+
HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String
50+
BLAKE2S_256 Algorithm = blake2s_256String
51+
BLAKE2B_256 Algorithm = blake2b_256String
52+
BLAKE2B_384 Algorithm = blake2b_384String
53+
BLAKE2B_512 Algorithm = blake2b_512String
54+
RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String
5555
// RSA_SHA256 is the default algorithm.
56-
RSA_SHA256 = rsaPrefix + "-" + sha256String
57-
RSA_SHA384 = rsaPrefix + "-" + sha384String
58-
RSA_SHA512 = rsaPrefix + "-" + sha512String
59-
RSA_RIPEMD160 = rsaPrefix + "-" + ripemd160String
60-
RSA_SHA3_224 = rsaPrefix + "-" + sha3_224String
61-
RSA_SHA3_256 = rsaPrefix + "-" + sha3_256String
62-
RSA_SHA3_384 = rsaPrefix + "-" + sha3_384String
63-
RSA_SHA3_512 = rsaPrefix + "-" + sha3_512String
64-
RSA_SHA512_224 = rsaPrefix + "-" + sha512_224String
65-
RSA_SHA512_256 = rsaPrefix + "-" + sha512_256String
66-
RSA_BLAKE2S_256 = rsaPrefix + "-" + blake2s_256String
67-
RSA_BLAKE2B_256 = rsaPrefix + "-" + blake2b_256String
68-
RSA_BLAKE2B_384 = rsaPrefix + "-" + blake2b_384String
69-
RSA_BLAKE2B_512 = rsaPrefix + "-" + blake2b_512String
56+
RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String
57+
RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String
58+
RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String
59+
RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String
60+
RSA_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String
61+
RSA_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String
62+
RSA_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String
63+
RSA_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String
64+
RSA_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String
65+
RSA_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String
66+
RSA_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String
67+
RSA_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String
68+
RSA_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String
69+
RSA_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String
7070
)
7171

72-
// HTTP Signatures can be applied to either the "Authorization" or "Signature"
73-
// HTTP header
72+
// HTTP Signatures can be applied to different HTTP headers, depending on the
73+
// expected application behavior.
7474
type SignatureScheme string
7575

7676
const (
77-
Signature SignatureScheme = "Signature"
78-
Authorization = "Authorization"
77+
// Signature will place the HTTP Signature into the 'Signature' HTTP
78+
// header.
79+
Signature SignatureScheme = "Signature"
80+
// Authorization will place the HTTP Signature into the 'Authorization'
81+
// HTTP header.
82+
Authorization SignatureScheme = "Authorization"
7983
)
8084

8185
// Signers will sign HTTP requests or responses based on the algorithms and
8286
// headers selected at creation time.
87+
//
88+
// Note that signatures do set the deprecated 'algorithm' parameter for
89+
// backwards compatibility.
8390
type Signer interface {
91+
// SignRequest signs the request using a private key. The public key id
92+
// is used by the HTTP server to identify which key to use to verify the
93+
// signature.
94+
//
95+
// If the Signer was created using a MAC based algorithm, then the
96+
// private key is expected to be of type []byte. If the Signer was
97+
// created using an RSA based algorithm, then the private key is
98+
// expected to be of type *rsa.PrivateKey.
8499
SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request) error
100+
// SignResponse signs the response using a private key. The public key
101+
// id is used by the HTTP client to identify which key to use to verify
102+
// the signature.
103+
//
104+
// If the Signer was created using a MAC based algorithm, then the
105+
// private key is expected to be of type []byte. If the Signer was
106+
// created using an RSA based algorithm, then the private key is
107+
// expected to be of type *rsa.PrivateKey.
85108
SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter) error
86109
}
87110

@@ -110,7 +133,42 @@ func NewSigner(prefs []Algorithm, headers []string, scheme SignatureScheme) (Sig
110133
return s, defaultAlgorithm, err
111134
}
112135

113-
// TODO: Verification
136+
// Verifier verifies HTTP Signatures.
137+
//
138+
// It will determine which of the supported headers has the parameters
139+
// that define the signature.
140+
//
141+
// Note that verification ignores the deprecated 'algorithm' parameter.
142+
type Verifier interface {
143+
// KeyId gets the public key id that the signature is signed with.
144+
//
145+
// Note that the application is expected to determine the algorithm
146+
// used based on metadata or out-of-band information for this key id.
147+
KeyId() string
148+
// Verify accepts the public key specified by KeyId and returns an
149+
// error if verification fails or if the signature is malformed. The
150+
// algorithm must be the one used to create the signature in order to
151+
// pass verification. The algorithm is determined based on metadata or
152+
// out-of-band information for the key id.
153+
//
154+
// If the signature was created using a MAC based algorithm, then the
155+
// public key is expected to be of type []byte. If the signature was
156+
// created using an RSA based algorithm, then the public key is
157+
// expected to be of type *rsa.PublicKey.
158+
Verify(pKey crypto.PublicKey, algo Algorithm) error
159+
}
160+
161+
func NewVerifier(r *http.Request) (Verifier, error) {
162+
return newVerifier(r.Header, func(h http.Header, toInclude []string) (string, error) {
163+
return signatureString(h, toInclude, addRequestTarget(r))
164+
})
165+
}
166+
167+
func NewResponseVerifier(r *http.Response) (Verifier, error) {
168+
return newVerifier(r.Header, func(h http.Header, toInclude []string) (string, error) {
169+
return signatureString(h, toInclude, requestTargetNotPermitted)
170+
})
171+
}
114172

115173
func newSigner(algo Algorithm, headers []string, scheme SignatureScheme) (Signer, error) {
116174
s, err := signerFromString(string(algo))

‎signing.go‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const (
3636

3737
var defaultHeaders = []string{dateHeader}
3838

39+
var _ Signer = &macSigner{}
40+
3941
type macSigner struct {
4042
m macer
4143
headers []string
@@ -89,6 +91,8 @@ func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, erro
8991
return signatureString(r.Header(), m.headers, requestTargetNotPermitted)
9092
}
9193

94+
var _ Signer = &asymmSigner{}
95+
9296
type asymmSigner struct {
9397
s signer
9498
headers []string
@@ -150,7 +154,8 @@ func setSignatureHeader(h http.Header, targetHeader, pubKeyId, algo, enc string,
150154
b.WriteString(pubKeyId)
151155
b.WriteString(parameterValueDelimiter)
152156
b.WriteString(parameterSeparater)
153-
// Algorithm
157+
// Algorithm (deprecated)
158+
// TODO: Remove this.
154159
b.WriteString(algorithmParameter)
155160
b.WriteString(parameterKVSeparater)
156161
b.WriteString(parameterValueDelimiter)

‎verifying.go‎

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package httpsig
2+
3+
import (
4+
"crypto"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
var _ Verifier = &verifier{}
11+
12+
type verifier struct {
13+
header http.Header
14+
kId string
15+
signature string
16+
headers []string
17+
sigStringFn func(http.Header, []string) (string, error)
18+
}
19+
20+
func newVerifier(h http.Header, sigStringFn func(http.Header, []string) (string, error)) (*verifier, error) {
21+
s, err := getSignatureScheme(h)
22+
if err != nil {
23+
return nil, err
24+
}
25+
kId, sig, headers, err := getSignatureComponents(s)
26+
if err != nil {
27+
return nil, err
28+
}
29+
return &verifier{
30+
header: h,
31+
kId: kId,
32+
signature: sig,
33+
headers: headers,
34+
sigStringFn: sigStringFn,
35+
}, nil
36+
}
37+
38+
func (v *verifier) KeyId() string {
39+
return v.kId
40+
}
41+
42+
func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error {
43+
s, err := signerFromString(string(algo))
44+
if err == nil {
45+
return v.asymmVerify(s, pKey)
46+
}
47+
m, err := macerFromString(string(algo))
48+
if err == nil {
49+
return v.macVerify(m, pKey)
50+
}
51+
return fmt.Errorf("no crypto implementation available for %q", algo)
52+
}
53+
54+
func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error {
55+
key, ok := pKey.([]byte)
56+
if !ok {
57+
return fmt.Errorf("public key for MAC verifying must be of type []byte")
58+
}
59+
actualMAC, err := v.sigStringFn(v.header, v.headers)
60+
if err != nil {
61+
return err
62+
}
63+
ok, err = m.Equal([]byte(v.signature), []byte(actualMAC), key)
64+
if err != nil {
65+
return err
66+
} else if !ok {
67+
return fmt.Errorf("invalid http signature")
68+
}
69+
return nil
70+
}
71+
72+
func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error {
73+
toHash, err := v.sigStringFn(v.header, v.headers)
74+
if err != nil {
75+
return err
76+
}
77+
err = s.Verify(pKey, []byte(toHash), []byte(v.signature))
78+
if err != nil {
79+
return err
80+
}
81+
return nil
82+
}
83+
84+
func getSignatureScheme(h http.Header) (string, error) {
85+
s := h.Get(string(Signature))
86+
sigHasAll := strings.Contains(s, keyIdParameter) ||
87+
strings.Contains(s, headersParameter) ||
88+
strings.Contains(s, signatureParameter)
89+
a := h.Get(string(Authorization))
90+
authHasAll := strings.Contains(a, keyIdParameter) ||
91+
strings.Contains(a, headersParameter) ||
92+
strings.Contains(a, signatureParameter)
93+
if sigHasAll && authHasAll {
94+
return "", fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization)
95+
} else if !sigHasAll && !authHasAll {
96+
return "", fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization)
97+
} else if sigHasAll {
98+
return s, nil
99+
} else { // authHasAll
100+
return a, nil
101+
}
102+
}
103+
104+
func getSignatureComponents(s string) (kId, sig string, headers []string, err error) {
105+
params := strings.Split(s, parameterSeparater)
106+
for _, p := range params {
107+
kv := strings.Split(p, parameterKVSeparater)
108+
if len(kv) != 2 {
109+
err = fmt.Errorf("malformed http signature parameter: %q", p)
110+
return
111+
}
112+
k := kv[0]
113+
v := strings.Trim(kv[1], parameterValueDelimiter)
114+
switch k {
115+
case keyIdParameter:
116+
kId = v
117+
case algorithmParameter:
118+
// Deprecated, ignore
119+
case headersParameter:
120+
headers = strings.Split(v, headerParameterValueDelim)
121+
case signatureParameter:
122+
sig = v
123+
default:
124+
err = fmt.Errorf("unrecognized http signature parameter %q", k)
125+
return
126+
}
127+
}
128+
if len(kId) == 0 {
129+
err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter)
130+
} else if len(sig) == 0 {
131+
err = fmt.Errorf("missing %q parameter in http signature", signatureParameter)
132+
} else if len(headers) == 0 { // Optional
133+
headers = defaultHeaders
134+
}
135+
return
136+
}

0 commit comments

Comments
 (0)