diff --git a/algorithm.go b/algorithm.go index 1a9b260..2e940a0 100644 --- a/algorithm.go +++ b/algorithm.go @@ -1,15 +1,24 @@ package httpsignatures import ( + "crypto" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" "crypto/sha1" "crypto/sha256" + "crypto/x509" + "encoding/pem" "errors" + "fmt" "hash" ) var ( - AlgorithmHmacSha256 = &Algorithm{"hmac-sha256", sha256.New} - AlgorithmHmacSha1 = &Algorithm{"hmac-sha1", sha1.New} + AlgorithmHmacSha256 = &Algorithm{"hmac-sha256", sha256.New, hmacSign} + AlgorithmHmacSha1 = &Algorithm{"hmac-sha1", sha1.New, hmacSign} + AlgorithmRsaSha1 = &Algorithm{"rsa-sha1", sha1.New, rsaSha1Sign} + AlgorithmRsaSha256 = &Algorithm{"rsa-sha256", sha256.New, rsaSha256Sign} ErrorUnknownAlgorithm = errors.New("Unknown Algorithm") ) @@ -17,6 +26,7 @@ var ( type Algorithm struct { name string hash func() hash.Hash + sign func(hashFunc func() hash.Hash, key string, signingString string) ([]byte, error) } func algorithmFromString(name string) (*Algorithm, error) { @@ -25,7 +35,62 @@ func algorithmFromString(name string) (*Algorithm, error) { return AlgorithmHmacSha1, nil case AlgorithmHmacSha256.name: return AlgorithmHmacSha256, nil + case AlgorithmRsaSha1.name: + return AlgorithmRsaSha1, nil + case AlgorithmRsaSha256.name: + return AlgorithmRsaSha256, nil } - return nil, ErrorUnknownAlgorithm } + +func hmacSign(hashFunc func() hash.Hash, key string, signingString string) ([]byte, error) { + hash := hmac.New(hashFunc, []byte(key)) + hash.Write([]byte(signingString)) + return hash.Sum(nil), nil +} + +func rsaSha1Sign(hashFunc func() hash.Hash, key string, signingString string) ([]byte, error) { + return rsaSign(hashFunc, key, signingString, crypto.SHA1) +} + +func rsaSha256Sign(hashFunc func() hash.Hash, key string, signingString string) ([]byte, error) { + return rsaSign(hashFunc, key, signingString, crypto.SHA256) +} + +func rsaSign(hashFunc func() hash.Hash, key string, signingString string, hashType crypto.Hash) ([]byte, error) { + private_key, err := parsePrivateKey([]byte(key[:])) + if err != nil { + return nil, err + } + hash := hashFunc() + hash.Write([]byte(signingString)) + d := hash.Sum(nil) + singed_hash, err := rsa.SignPKCS1v15(rand.Reader, private_key, hashType, d) + if err != nil { + return nil, err + } + return singed_hash, nil +} + +func parsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + switch block.Type { + case "RSA PRIVATE KEY": + rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa, nil + case "PRIVATE KEY": + rsaz, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsaz.(*rsa.PrivateKey), nil + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} diff --git a/signature.go b/signature.go index 53c38bc..2388519 100644 --- a/signature.go +++ b/signature.go @@ -3,7 +3,6 @@ package httpsignatures import ( - "crypto/hmac" "encoding/base64" "errors" "fmt" @@ -107,16 +106,16 @@ func (s Signature) String() string { } func (s Signature) calculateSignature(key string, r *http.Request) (string, error) { - hash := hmac.New(s.Algorithm.hash, []byte(key)) signingString, err := s.Headers.signingString(r) if err != nil { return "", err } - - hash.Write([]byte(signingString)) - - return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil + signature, err := s.Algorithm.sign(s.Algorithm.hash, key, signingString) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(signature), nil } // Sign this signature using the given key diff --git a/signature_test.go b/signature_test.go index d813fa8..a46e70e 100644 --- a/signature_test.go +++ b/signature_test.go @@ -13,6 +13,33 @@ const ( TEST_KEY = "SomethingRandom" TEST_DATE = "Thu, 05 Jan 2012 21:31:40 GMT" TEST_KEY_ID = "Test" + RSA_TEST_KEY = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAptqthxnWUoN6J/9RhspO9uKmsYUTT150rGyLln/Keq0E4vam +7Pj3B6Xh3reGbyjSz0NQkfLftalRKqNse0TH5TLGMYXz5mtxm+jZkuTqE73wgRDF +3jW0gCoFGy0qKXckunvKOpwkNVDuMZZ1DLgLbUGi1XTVpH8r4hHWsVfrupzJnfo4 +ptAGEPlH9VRoYZd5sNg3OIxQaUYtNZr3dpywzwXwPF1VvWCw1K9cENZhkFS5CW/l +PuZXCFOPDgO1hTJMLWE+aZ7TElYOH/i+1/4igBdPn0G0awBR+OoGF0wN4JpUXDSQ +D9F6nyFRDKaxx5aFYdme+QrzQTyYNPQ18hjf2wIDAQABAoIBABcw68+MWsqrNY5b +oWQ/uEv+YrbnzTBJ66OPjrNDXcxBQh2dtMPZMtSgTM2c6pWGsg5Wx9sRS+C/AOYR +QuG7RKFptjxp5uWO54KJEbymDpbh3ozB3Q6unkD2FjGZzHNo+PTmgcw1qZ6zeffw +dqJm7keoSM6sZ4lul5XbbuDFXKFaMMbz1VRVVdFnAvmKQZfwd6/noeKiuyMyG3g8 +v7VikhsEA3pX7uOVDs2vbj5Pq5lDYZ22k0FD0BS6uPUyzrhDyn+nF2I2GWS8PePX +xRsrw05BgGLi5mcI4XPpxLY53T4yc1RYBKvmtGJsZ26/QeOo/ER7ogd8hV/EwjuM +S30PNbECgYEA/kQklql7ri7+qG6HZ07avD2DTQqC9LIOXc6WsoBIRV13geKL/H9a +uXSrxlLbkWYqt8yd2zzVvo+0mbm+Nixe2huPmawbQjsQoLA0hSC7+g8djboDz91Q +SHsHTDYzNUw6SDqRsypQpUJIOQvvwxodcPlW8RaHW4JAafXPMygkTjkCgYEAp/3x +9ClD+4MTinYLVdRfY4j9r4TjkI2sXf4nfD06fnQcSVsZWD6AqGjqmjfJA+f6w+lu +KHo1NJonlLzac4qOScGCTgCrSbbSPUq8IgaZk4ufqnIek6TH/wPEaY7ZLVb59Wam +Z0+yrpqDqVMf2uUJyiTlFdajfqtHx9Oo5vvnnrMCgYAqd7Msvs37f7nk4+EVriP2 +gMenXHQW7o5buJ+O3MI1Y7EMLox29cZvZz8xdrFZjZjg7foHnheNJm9hpZZRcgO9 +phDL9+TtoPPcAtIi0h7TWybyfvkYBLzd/j5vyjWvVzX8zlt7czvY/kMV1BqNmZUF +Q3/z8HFXJWAg0n9y6ed2cQKBgQCROs/+heI4wIOXMx/vjo78jMTMBXV6VZBLHdpi +5Mf55EVEAZaynC476Z/PvSRx1Q4ManSKV8RBend3daDhPEpwZvNQnfF2469zv3VP +cSc5z/4zqz7V4yHnTAl0PENyl/u19I0tSVAu9HOYYb1rTpCdCjJmI83qRwbiMRCW +x/XgUwKBgBd+kQkrjhhn/vbhG1EtHgAgszyuMCl+nXlvWGrIP24nSfDxH8UBjhhg +LvbPDIsyREqu3KgddzwdUgCu1PX7adl3mEJXfg62QhqU707+eo6OAGkyUJWDofyi +46RZixuecH3thF1BnNNdYi0QI0UTLkZgpKWGE9mXTp4xxzHg0sfs +-----END RSA PRIVATE KEY-----` ) func TestCreateSignatureFromAuthorizationHeader(t *testing.T) { diff --git a/signer.go b/signer.go index dce5f51..07e94bb 100644 --- a/signer.go +++ b/signer.go @@ -20,6 +20,14 @@ var ( // DefaultSha256Signer will sign requests with the url and date using the SHA256 algorithm. // Users are encouraged to create their own signer with the headers they require. DefaultSha256Signer = NewSigner(AlgorithmHmacSha256, RequestTarget, "date") + + // DefaultSha1Signer will sign requests with the url and date using the SHA1 algorithm. + // Users are encouraged to create their own signer with the headers they require. + DefaultRsaSha1Signer = NewSigner(AlgorithmRsaSha1, RequestTarget, "date") + + // DefaultRsaSha256Signer will sign requests with the url and date using the SHA256 algorithm. + // Users are encouraged to create their own signer with the headers they require. + DefaultRsaSha256Signer = NewSigner(AlgorithmRsaSha256, RequestTarget, "date") ) func NewSigner(algorithm *Algorithm, headers ...string) *Signer { @@ -61,7 +69,7 @@ func (s Signer) AuthRequest(id, key string, r *http.Request) error { func (s Signer) buildSignature(id, key string, r *http.Request) (*Signature, error) { if r.Header.Get("date") == "" { - r.Header.Set("date", time.Now().Format(time.RFC1123)) + r.Header.Set("date", time.Now().UTC().Format(time.RFC1123)) } sig := &Signature{ diff --git a/signer_test.go b/signer_test.go index b891928..36d9a04 100644 --- a/signer_test.go +++ b/signer_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSignSha1(t *testing.T) { +func TestSignHmacSha1(t *testing.T) { r := &http.Request{ Header: http.Header{ "Date": []string{"Thu, 05 Jan 2012 21:31:40 GMT"}, @@ -30,7 +30,7 @@ func TestSignSha1(t *testing.T) { ) } -func TestSignSha256(t *testing.T) { +func TestSignHmacSha256(t *testing.T) { r := &http.Request{ Header: http.Header{ "Date": []string{"Thu, 05 Jan 2012 21:31:40 GMT"}, @@ -53,6 +53,52 @@ func TestSignSha256(t *testing.T) { ) } +func TestSignRsaSha1(t *testing.T) { + r := &http.Request{ + Header: http.Header{ + "Date": []string{"Thu, 05 Jan 2012 21:31:40 GMT"}, + }, + } + + err := DefaultRsaSha1Signer.SignRequest(TEST_KEY_ID, RSA_TEST_KEY, r) + assert.Nil(t, err) + + s, err := FromRequest(r) + assert.Nil(t, err) + + assert.Equal(t, TEST_KEY_ID, s.KeyID) + assert.Equal(t, DefaultRsaSha1Signer.algorithm, s.Algorithm) + assert.Equal(t, DefaultRsaSha1Signer.headers, s.Headers) + + assert.Equal(t, + "GvZYUHYZQJ3aYh9FulOG2TQj10ix3wWZ08bXbIxkPXrxDkW55b7yZSbY38HxzLMA9+Nso4xuduQi0eJSDZxadCOs8GHEV/hXyVVX1xUF4Yw8tiWYeu8bRkdWworRP5/L+Xl3g7AFwfKPRWWe6MlY7Vqi8oxt2q2rd3Z+35q4LWDgcvblu0Q5mv8IbtfTP0Z4ncwnQRWRGoe8nVP1v66Thook68eNHszmPRINgTrSDwQbl5jQWvkQv0vznBlj9yxGa3XVO+CoL5r896YrMTrE8JRhj7NHZ1vqOZUIRIK6xgfzjWz0geTUrXS/WIT3hvHLPBMzE8TGaZtlVGycMzD/9g==", + s.Signature, + ) +} + +func TestSignRsaSha256(t *testing.T) { + r := &http.Request{ + Header: http.Header{ + "Date": []string{"Thu, 05 Jan 2012 21:31:40 GMT"}, + }, + } + + err := DefaultRsaSha256Signer.SignRequest(TEST_KEY_ID, RSA_TEST_KEY, r) + assert.Nil(t, err) + + s, err := FromRequest(r) + assert.Nil(t, err) + + assert.Equal(t, TEST_KEY_ID, s.KeyID) + assert.Equal(t, DefaultRsaSha256Signer.algorithm, s.Algorithm) + assert.Equal(t, DefaultRsaSha256Signer.headers, s.Headers) + + assert.Equal(t, + "eVyldHIP+4DotKk28VzSXv7q9ZK0HHcTorxHr0aBsyKYElOUMbISOLbaEOJrOsycH7d7NYr3J985ugGOx5mDzamDy3LyjpCK63tZkawqxdEJA2YZE4Ccu1zX8mlutHCMId6/hM8t4tW86La0lxPo6v7Q9mxwkbZn22lVy4qfjOVEiUrSN6phFIWhwi5/3AhhiMtqnD0Lm3iQDB1YKKBCUPmdrC8PTZGTIha3c7NRRnyVxqOUt16EzHDA8QEZ7TmxnIfv3+v5/sC4mCgW8cW/lo3uiBlCF8mtV6MW7H1NIiGcLrHmCA4PaxoSCLHAZRqKlzvA7/TbffG9T8/JOR3hVg==", + s.Signature, + ) +} + func TestSignWithMissingDateHeader(t *testing.T) { r := &http.Request{Header: http.Header{}}