Skip to content

Commit 973e514

Browse files
committed
Implement rsa-sha1 and rsa-sha256 algorithms
1 parent 88528bf commit 973e514

6 files changed

Lines changed: 377 additions & 44 deletions

File tree

algorithm.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
package httpsignatures
22

33
import (
4-
"crypto/sha1"
5-
"crypto/sha256"
4+
"crypto"
5+
"crypto/hmac"
6+
"crypto/rand"
7+
"crypto/rsa"
68
"errors"
7-
"hash"
89
)
910

1011
var (
11-
AlgorithmHmacSha256 = &Algorithm{"hmac-sha256", sha256.New}
12-
AlgorithmHmacSha1 = &Algorithm{"hmac-sha1", sha1.New}
12+
AlgorithmHmacSha256 = &Algorithm{"hmac-sha256", hmacSign(crypto.SHA256), hmacVerify(crypto.SHA256)}
13+
AlgorithmHmacSha1 = &Algorithm{"hmac-sha1", hmacSign(crypto.SHA1), hmacVerify(crypto.SHA1)}
14+
AlgorithmRsaSha256 = &Algorithm{"rsa-sha256", rsaSign(crypto.SHA256), rsaVerify(crypto.SHA256)}
15+
AlgorithmRsaSha1 = &Algorithm{"rsa-sha1", rsaSign(crypto.SHA1), rsaVerify(crypto.SHA1)}
1316

1417
ErrorUnknownAlgorithm = errors.New("Unknown Algorithm")
1518
)
1619

20+
// signFn signs message m using key k.
21+
type signFn func(k interface{}, m []byte) ([]byte, error)
22+
23+
// verifyFn verifies that signature s, for message m was signed by key k.
24+
type verifyFn func(k interface{}, m []byte, s []byte) bool
25+
1726
type Algorithm struct {
1827
name string
19-
hash func() hash.Hash
28+
29+
sign signFn
30+
verify verifyFn
2031
}
2132

2233
func algorithmFromString(name string) (*Algorithm, error) {
@@ -25,7 +36,57 @@ func algorithmFromString(name string) (*Algorithm, error) {
2536
return AlgorithmHmacSha1, nil
2637
case AlgorithmHmacSha256.name:
2738
return AlgorithmHmacSha256, nil
39+
case AlgorithmRsaSha1.name:
40+
return AlgorithmRsaSha1, nil
41+
case AlgorithmRsaSha256.name:
42+
return AlgorithmRsaSha256, nil
2843
}
2944

3045
return nil, ErrorUnknownAlgorithm
3146
}
47+
48+
// hmacSign returns a function that will HMAC sign some message using the given
49+
// hash function.
50+
func hmacSign(h crypto.Hash) signFn {
51+
return func(k interface{}, m []byte) ([]byte, error) {
52+
hash := hmac.New(h.New, []byte(k.(string)))
53+
hash.Write(m)
54+
return hash.Sum(nil), nil
55+
}
56+
}
57+
58+
// hmacVerify returns a function that will verify that the signature signed with
59+
// the given hashfn matches the calculated signature.
60+
func hmacVerify(h crypto.Hash) verifyFn {
61+
sign := hmacSign(h)
62+
return func(k interface{}, m []byte, s []byte) bool {
63+
calculatedSignature, err := sign(k, m)
64+
if err != nil {
65+
return false
66+
}
67+
68+
return hmac.Equal(calculatedSignature, s)
69+
}
70+
}
71+
72+
// rsaSign returns a function that will sign a message with an RSA private key,
73+
// using the given hash function.
74+
func rsaSign(h crypto.Hash) signFn {
75+
return func(k interface{}, m []byte) ([]byte, error) {
76+
hash := h.New()
77+
hash.Write(m)
78+
hashed := hash.Sum(nil)
79+
return rsa.SignPKCS1v15(rand.Reader, k.(*rsa.PrivateKey), h, hashed[:])
80+
}
81+
}
82+
83+
// rsaVerify returns a function that will verify that a message was signed with
84+
// an RSA private key.
85+
func rsaVerify(h crypto.Hash) verifyFn {
86+
return func(k interface{}, m []byte, s []byte) bool {
87+
hash := h.New()
88+
hash.Write(m)
89+
hashed := hash.Sum(nil)
90+
return rsa.VerifyPKCS1v15(k.(*rsa.PublicKey), h, hashed[:], s) == nil
91+
}
92+
}

examples_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,38 @@
11
package httpsignatures_test
22

33
import (
4+
"crypto/rsa"
5+
"crypto/x509"
6+
"encoding/pem"
47
"net/http"
58

69
"github.com/99designs/httpsignatures-go"
710
)
811

12+
const (
13+
ExamplePrivateKey = `-----BEGIN RSA PRIVATE KEY-----
14+
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
15+
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
16+
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
17+
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
18+
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
19+
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
20+
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
21+
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
22+
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
23+
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
24+
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
25+
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
26+
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
27+
-----END RSA PRIVATE KEY-----`
28+
ExamplePublicyKey = `-----BEGIN PUBLIC KEY-----
29+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
30+
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
31+
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
32+
oYi+1hqp1fIekaxsyQIDAQAB
33+
-----END PUBLIC KEY-----`
34+
)
35+
936
func Example_signing() {
1037
r, _ := http.NewRequest("GET", "http://example.com/some-api", nil)
1138

@@ -17,6 +44,20 @@ func Example_signing() {
1744
http.DefaultClient.Do(r)
1845
}
1946

47+
func Example_signingRSA() {
48+
block, _ := pem.Decode([]byte(ExamplePrivateKey))
49+
privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
50+
51+
r, _ := http.NewRequest("GET", "http://example.com/some-api", nil)
52+
53+
// Sign using the 'Signature' header
54+
httpsignatures.DefaultRsaSha256Signer.SignRequestRSA("KeyId", privateKey, r)
55+
// OR Sign using the 'Authorization' header
56+
httpsignatures.DefaultRsaSha256Signer.AuthRequestRSA("KeyId", privateKey, r)
57+
58+
http.DefaultClient.Do(r)
59+
}
60+
2061
func Example_customSigning() {
2162
signer := httpsignatures.NewSigner(
2263
httpsignatures.AlgorithmHmacSha256,
@@ -51,3 +92,28 @@ func Example_verification() {
5192
// request was signed correctly.
5293
}
5394
}
95+
96+
func Example_verificationRSA() {
97+
_ = func(w http.ResponseWriter, r *http.Request) {
98+
sig, err := httpsignatures.FromRequest(r)
99+
if err != nil {
100+
// Probably a malformed header
101+
http.Error(w, "Bad Request", http.StatusBadRequest)
102+
panic(err)
103+
}
104+
105+
// if you have headers that must be signed check
106+
// that they are in sig.Headers
107+
108+
var pemPublicKeyBytes []byte // = lookup using sig.KeyID
109+
block, _ := pem.Decode(pemPublicKeyBytes)
110+
publicKey, _ := x509.ParsePKIXPublicKey(block.Bytes)
111+
112+
if !sig.IsValidRSA(publicKey.(*rsa.PublicKey), r) {
113+
http.Error(w, "Forbidden", http.StatusForbidden)
114+
return
115+
}
116+
117+
// request was signed correctly.
118+
}
119+
}

signature.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
package httpsignatures
44

55
import (
6-
"crypto/hmac"
7-
"crypto/subtle"
6+
"crypto/rsa"
87
"encoding/base64"
98
"errors"
109
"fmt"
@@ -107,21 +106,22 @@ func (s Signature) String() string {
107106
return str
108107
}
109108

110-
func (s Signature) calculateSignature(key string, r *http.Request) (string, error) {
111-
hash := hmac.New(s.Algorithm.hash, []byte(key))
112-
109+
func (s Signature) calculateSignature(key interface{}, r *http.Request) (string, error) {
113110
signingString, err := s.Headers.signingString(r)
114111
if err != nil {
115112
return "", err
116113
}
117114

118-
hash.Write([]byte(signingString))
115+
b, err := s.Algorithm.sign(key, []byte(signingString))
116+
if err != nil {
117+
return "", err
118+
}
119119

120-
return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil
120+
return base64.StdEncoding.EncodeToString(b), nil
121121
}
122122

123123
// Sign this signature using the given key
124-
func (s *Signature) sign(key string, r *http.Request) error {
124+
func (s *Signature) sign(key interface{}, r *http.Request) error {
125125
sig, err := s.calculateSignature(key, r)
126126
if err != nil {
127127
return err
@@ -133,16 +133,32 @@ func (s *Signature) sign(key string, r *http.Request) error {
133133

134134
// IsValid validates this signature for the given key
135135
func (s Signature) IsValid(key string, r *http.Request) bool {
136+
return s.isValid(key, r)
137+
}
138+
139+
// IsValidRSA validates that the request was signed by an RSA private key, using
140+
// the public key for verification. This method should only be called when the
141+
// underlying Algorithm is an RSA backed implementation.
142+
func (s Signature) IsValidRSA(key *rsa.PublicKey, r *http.Request) bool {
143+
return s.isValid(key, r)
144+
}
145+
146+
func (s Signature) isValid(key interface{}, r *http.Request) bool {
136147
if !s.Headers.hasDate() {
137148
return false
138149
}
139150

140-
sig, err := s.calculateSignature(key, r)
151+
signingString, err := s.Headers.signingString(r)
152+
if err != nil {
153+
return false
154+
}
155+
156+
signature, err := base64.StdEncoding.DecodeString(s.Signature)
141157
if err != nil {
142158
return false
143159
}
144160

145-
return subtle.ConstantTimeCompare([]byte(s.Signature), []byte(sig)) == 1
161+
return s.Algorithm.verify(key, []byte(signingString), signature)
146162
}
147163

148164
type HeaderList []string

0 commit comments

Comments
 (0)