Adding upstream version 1.2.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
ed523a11b4
commit
d4d4f346b4
13 changed files with 3867 additions and 0 deletions
901
httpsig_test.go
Normal file
901
httpsig_test.go
Normal file
|
@ -0,0 +1,901 @@
|
|||
package httpsig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
const (
|
||||
testUrl = "foo.net/bar/baz?q=test&r=ok"
|
||||
testUrlPath = "bar/baz"
|
||||
testDate = "Tue, 07 Jun 2014 20:51:35 GMT"
|
||||
testDigest = "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="
|
||||
testMethod = "GET"
|
||||
)
|
||||
|
||||
type httpsigTest struct {
|
||||
name string
|
||||
prefs []Algorithm
|
||||
digestAlg DigestAlgorithm
|
||||
headers []string
|
||||
body []byte
|
||||
scheme SignatureScheme
|
||||
privKey crypto.PrivateKey
|
||||
pubKey crypto.PublicKey
|
||||
pubKeyId string
|
||||
expectedSignatureAlgorithm string
|
||||
expectedAlgorithm Algorithm
|
||||
expectErrorSigningResponse bool
|
||||
expectRequestPath bool
|
||||
expectedDigest string
|
||||
}
|
||||
|
||||
type ed25519PrivKey struct {
|
||||
Version int
|
||||
ObjectIdentifier struct {
|
||||
ObjectIdentifier asn1.ObjectIdentifier
|
||||
}
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type ed25519PubKey struct {
|
||||
OBjectIdentifier struct {
|
||||
ObjectIdentifier asn1.ObjectIdentifier
|
||||
}
|
||||
PublicKey asn1.BitString
|
||||
}
|
||||
|
||||
var (
|
||||
privKey *rsa.PrivateKey
|
||||
macKey []byte
|
||||
tests []httpsigTest
|
||||
testSpecRSAPrivateKey *rsa.PrivateKey
|
||||
testSpecRSAPublicKey *rsa.PublicKey
|
||||
testEd25519PrivateKey ed25519.PrivateKey
|
||||
testEd25519PublicKey ed25519.PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
privKey, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
macKey = make([]byte, 128)
|
||||
err = readFullFromCrypto(macKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tests = []httpsigTest{
|
||||
{
|
||||
name: "rsa signature",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "ed25519 signature",
|
||||
prefs: []Algorithm{ED25519},
|
||||
digestAlg: DigestSha512,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privEd25519Key,
|
||||
pubKey: pubEd25519Key,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: ED25519,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "digest on rsa signature",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."),
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=",
|
||||
},
|
||||
{
|
||||
name: "digest on ed25519 signature",
|
||||
prefs: []Algorithm{ED25519},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
body: []byte("Last night as I lay dreaming This strangest kind of feeling Revealed its secret meaning And now I know..."),
|
||||
scheme: Signature,
|
||||
privKey: privEd25519Key,
|
||||
pubKey: pubEd25519Key,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: ED25519,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectedDigest: "SHA-256=07PJQngqg8+BlomdI6zM7ieOxhINWI+iivJxBDSm3Dg=",
|
||||
},
|
||||
{
|
||||
name: "hmac signature",
|
||||
prefs: []Algorithm{HMAC_SHA256},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: macKey,
|
||||
pubKey: macKey,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: HMAC_SHA256,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "digest on hmac signature",
|
||||
prefs: []Algorithm{HMAC_SHA256},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
body: []byte("I've never ever been to paradise I've never ever seen no angel's eyes You'll never ever let this magic die No matter where you are, you are my lucky star."),
|
||||
scheme: Signature,
|
||||
privKey: macKey,
|
||||
pubKey: macKey,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: HMAC_SHA256,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectedDigest: "SHA-256=d0JoDjbDZRZF7/gUdgrazZCdKCJ9z9uUcMd6n1YKWRU=",
|
||||
},
|
||||
{
|
||||
name: "rsa authorization",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Authorization,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "ed25519 authorization",
|
||||
prefs: []Algorithm{ED25519},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Authorization,
|
||||
privKey: privEd25519Key,
|
||||
pubKey: pubEd25519Key,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: ED25519,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "hmac authorization",
|
||||
prefs: []Algorithm{HMAC_SHA256},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Authorization,
|
||||
privKey: macKey,
|
||||
pubKey: macKey,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: HMAC_SHA256,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "default algo",
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA256,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "default headers",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "different pub key id",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "i write code that sucks",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
},
|
||||
{
|
||||
name: "with request target",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest", RequestTarget},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKey: privKey.Public(),
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectErrorSigningResponse: true,
|
||||
expectRequestPath: true,
|
||||
},
|
||||
}
|
||||
|
||||
testSpecRSAPrivateKey, err = loadPrivateKey([]byte(testSpecPrivateKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testSpecRSAPublicKey, err = loadPublicKey([]byte(testSpecPublicKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testEd25519PrivateKey, err = loadEd25519PrivateKey([]byte(testEd25519PrivateKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testEd25519PublicKey, err = loadEd25519PublicKey([]byte(testEd25519PublicKeyPEM))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func toSignatureParameter(k, v string) string {
|
||||
return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter)
|
||||
}
|
||||
|
||||
func toHeaderSignatureParameters(k string, vals []string) string {
|
||||
if len(vals) == 0 {
|
||||
vals = defaultHeaders
|
||||
}
|
||||
v := strings.Join(vals, headerParameterValueDelim)
|
||||
k = strings.ToLower(k)
|
||||
v = strings.ToLower(v)
|
||||
return fmt.Sprintf("%s%s%s%s%s", k, parameterKVSeparater, parameterValueDelimiter, v, parameterValueDelimiter)
|
||||
}
|
||||
|
||||
func TestSignerRequest(t *testing.T) {
|
||||
testFn := func(t *testing.T, test httpsigTest) {
|
||||
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
if a != test.expectedAlgorithm {
|
||||
t.Fatalf("got %s, want %s", a, test.expectedAlgorithm)
|
||||
}
|
||||
// Test request signing
|
||||
req, err := http.NewRequest(testMethod, testUrl, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
req.Header.Set("Date", testDate)
|
||||
if test.body == nil {
|
||||
req.Header.Set("Digest", testDigest)
|
||||
}
|
||||
err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
vals, ok := req.Header[string(test.scheme)]
|
||||
if !ok {
|
||||
t.Fatalf("not in header %s", test.scheme)
|
||||
}
|
||||
if len(vals) != 1 {
|
||||
t.Fatalf("too many in header %s: %d", test.scheme, len(vals))
|
||||
}
|
||||
if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if !strings.Contains(vals[0], signatureParameter) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter)
|
||||
} else if test.body != nil && req.Header.Get("Digest") != test.expectedDigest {
|
||||
t.Fatalf("%s\ndoes not match\n%s", req.Header.Get("Digest"), test.expectedDigest)
|
||||
}
|
||||
// For schemes with an authScheme, enforce its is present and at the beginning
|
||||
if len(test.scheme.authScheme()) > 0 {
|
||||
if !strings.HasPrefix(vals[0], test.scheme.authScheme()) {
|
||||
t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme())
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testFn(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignerResponse(t *testing.T) {
|
||||
testFn := func(t *testing.T, test httpsigTest) {
|
||||
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
// Test response signing
|
||||
resp := httptest.NewRecorder()
|
||||
resp.HeaderMap.Set("Date", testDate)
|
||||
if test.body == nil {
|
||||
resp.HeaderMap.Set("Digest", testDigest)
|
||||
}
|
||||
err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body)
|
||||
if test.expectErrorSigningResponse {
|
||||
if err != nil {
|
||||
// Skip rest of testing
|
||||
return
|
||||
} else {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
}
|
||||
vals, ok := resp.HeaderMap[string(test.scheme)]
|
||||
if !ok {
|
||||
t.Fatalf("not in header %s", test.scheme)
|
||||
}
|
||||
if len(vals) != 1 {
|
||||
t.Fatalf("too many in header %s: %d", test.scheme, len(vals))
|
||||
}
|
||||
if p := toSignatureParameter(keyIdParameter, test.pubKeyId); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if p := toSignatureParameter(algorithmParameter, string(test.expectedSignatureAlgorithm)); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if p := toHeaderSignatureParameters(headersParameter, test.headers); !strings.Contains(vals[0], p) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], p)
|
||||
} else if !strings.Contains(vals[0], signatureParameter) {
|
||||
t.Fatalf("%s\ndoes not contain\n%s", vals[0], signatureParameter)
|
||||
} else if test.body != nil && resp.Header().Get("Digest") != test.expectedDigest {
|
||||
t.Fatalf("%s\ndoes not match\n%s", resp.Header().Get("Digest"), test.expectedDigest)
|
||||
}
|
||||
// For schemes with an authScheme, enforce its is present and at the beginning
|
||||
if len(test.scheme.authScheme()) > 0 {
|
||||
if !strings.HasPrefix(vals[0], test.scheme.authScheme()) {
|
||||
t.Fatalf("%s\ndoes not start with\n%s", vals[0], test.scheme.authScheme())
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testFn(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSignerRequestMissingHeaders(t *testing.T) {
|
||||
failingTests := []struct {
|
||||
name string
|
||||
prefs []Algorithm
|
||||
digestAlg DigestAlgorithm
|
||||
headers []string
|
||||
scheme SignatureScheme
|
||||
privKey crypto.PrivateKey
|
||||
pubKeyId string
|
||||
expectedAlgorithm Algorithm
|
||||
expectedSignatureAlgorithm string
|
||||
}{
|
||||
{
|
||||
name: "wants digest",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
},
|
||||
}
|
||||
for _, test := range failingTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
if a != test.expectedAlgorithm {
|
||||
t.Fatalf("got %s, want %s", a, test.expectedAlgorithm)
|
||||
}
|
||||
req, err := http.NewRequest(testMethod, testUrl, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
req.Header.Set("Date", testDate)
|
||||
err = s.SignRequest(test.privKey, test.pubKeyId, req, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expect error but got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSignerResponseMissingHeaders(t *testing.T) {
|
||||
failingTests := []struct {
|
||||
name string
|
||||
prefs []Algorithm
|
||||
digestAlg DigestAlgorithm
|
||||
headers []string
|
||||
scheme SignatureScheme
|
||||
privKey crypto.PrivateKey
|
||||
pubKeyId string
|
||||
expectedAlgorithm Algorithm
|
||||
expectErrorSigningResponse bool
|
||||
expectedSignatureAlgorithm string
|
||||
}{
|
||||
{
|
||||
name: "want digest",
|
||||
prefs: []Algorithm{RSA_SHA512},
|
||||
digestAlg: DigestSha256,
|
||||
headers: []string{"Date", "Digest"},
|
||||
scheme: Signature,
|
||||
privKey: privKey,
|
||||
pubKeyId: "pubKeyId",
|
||||
expectedSignatureAlgorithm: "hs2019",
|
||||
expectedAlgorithm: RSA_SHA512,
|
||||
},
|
||||
}
|
||||
for _, test := range failingTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
s, a, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
if a != test.expectedAlgorithm {
|
||||
t.Fatalf("got %s, want %s", a, test.expectedAlgorithm)
|
||||
}
|
||||
resp := httptest.NewRecorder()
|
||||
resp.HeaderMap.Set("Date", testDate)
|
||||
resp.HeaderMap.Set("Digest", testDigest)
|
||||
err = s.SignResponse(test.privKey, test.pubKeyId, resp, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewVerifier(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
// Prepare
|
||||
req, err := http.NewRequest(testMethod, testUrl, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
req.Header.Set("Date", testDate)
|
||||
if test.body == nil {
|
||||
req.Header.Set("Digest", testDigest)
|
||||
}
|
||||
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
err = s.SignRequest(test.privKey, test.pubKeyId, req, test.body)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
// Test verification
|
||||
v, err := NewVerifier(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
if v.KeyId() != test.pubKeyId {
|
||||
t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId)
|
||||
}
|
||||
err = v.Verify(test.pubKey, test.expectedAlgorithm)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewResponseVerifier(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
if test.expectErrorSigningResponse {
|
||||
return
|
||||
}
|
||||
// Prepare
|
||||
resp := httptest.NewRecorder()
|
||||
resp.HeaderMap.Set("Date", testDate)
|
||||
if test.body == nil {
|
||||
resp.HeaderMap.Set("Digest", testDigest)
|
||||
}
|
||||
s, _, err := NewSigner(test.prefs, test.digestAlg, test.headers, test.scheme, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
err = s.SignResponse(test.privKey, test.pubKeyId, resp, test.body)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
// Test verification
|
||||
v, err := NewResponseVerifier(resp.Result())
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
if v.KeyId() != test.pubKeyId {
|
||||
t.Fatalf("got %s, want %s", v.KeyId(), test.pubKeyId)
|
||||
}
|
||||
err = v.Verify(test.pubKey, test.expectedAlgorithm)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test_Signing_HTTP_Messages_AppendixC implement tests from Appendix C
|
||||
// in the http signatures specification:
|
||||
// https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C
|
||||
func Test_Signing_HTTP_Messages_AppendixC(t *testing.T) {
|
||||
specTests := []struct {
|
||||
name string
|
||||
headers []string
|
||||
expectedSignature string
|
||||
}{
|
||||
{
|
||||
name: "C.1. Default Test",
|
||||
headers: []string{},
|
||||
// NOTE: In the Appendix C tests, the following is NOT included:
|
||||
// `headers="date"`
|
||||
// But httpsig will ALWAYS explicitly list the headers used in its
|
||||
// signature. Hence, I have introduced it here.
|
||||
//
|
||||
// NOTE: In verification, if there are no headers listed, the
|
||||
// default headers (date) are indeed used as required by the
|
||||
// specification.
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`,
|
||||
},
|
||||
{
|
||||
name: "C.2. Basic Test",
|
||||
headers: []string{"(request-target)", "host", "date"},
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`,
|
||||
},
|
||||
{
|
||||
name: "C.3. All Headers Test",
|
||||
headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"},
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range specTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody)))
|
||||
if err != nil {
|
||||
t.Fatalf("error creating request: %s", err)
|
||||
}
|
||||
|
||||
r.Header["Date"] = []string{testSpecDate}
|
||||
r.Header["Host"] = []string{r.URL.Host}
|
||||
r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))}
|
||||
r.Header["Content-Type"] = []string{"application/json"}
|
||||
setDigest(r)
|
||||
|
||||
s, _, err := NewSigner([]Algorithm{RSA_SHA256}, DigestSha256, test.headers, Authorization, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating signer: %s", err)
|
||||
}
|
||||
|
||||
if err := s.SignRequest(testSpecRSAPrivateKey, "Test", r, nil); err != nil {
|
||||
t.Fatalf("error signing request: %s", err)
|
||||
}
|
||||
|
||||
expectedAuth := test.expectedSignature
|
||||
gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0])
|
||||
if gotAuth != expectedAuth {
|
||||
t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSigningEd25519(t *testing.T) {
|
||||
specTests := []struct {
|
||||
name string
|
||||
headers []string
|
||||
expectedSignature string
|
||||
}{
|
||||
{
|
||||
name: "Default Test",
|
||||
headers: []string{},
|
||||
// NOTE: In the Appendix C tests, the following is NOT included:
|
||||
// `headers="date"`
|
||||
// But httpsig will ALWAYS explicitly list the headers used in its
|
||||
// signature. Hence, I have introduced it here.
|
||||
//
|
||||
// NOTE: In verification, if there are no headers listed, the
|
||||
// default headers (date) are indeed used as required by the
|
||||
// specification.
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`,
|
||||
},
|
||||
{
|
||||
name: "Basic Test",
|
||||
headers: []string{"(request-target)", "host", "date"},
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`,
|
||||
},
|
||||
{
|
||||
name: "All Headers Test",
|
||||
headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"},
|
||||
expectedSignature: `Authorization: Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range specTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody)))
|
||||
if err != nil {
|
||||
t.Fatalf("error creating request: %s", err)
|
||||
}
|
||||
|
||||
r.Header["Date"] = []string{testSpecDate}
|
||||
r.Header["Host"] = []string{r.URL.Host}
|
||||
r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))}
|
||||
r.Header["Content-Type"] = []string{"application/json"}
|
||||
setDigest(r)
|
||||
|
||||
s, _, err := NewSigner([]Algorithm{ED25519}, DigestSha256, test.headers, Authorization, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating signer: %s", err)
|
||||
}
|
||||
|
||||
if err := s.SignRequest(testEd25519PrivateKey, "Test", r, nil); err != nil {
|
||||
t.Fatalf("error signing request: %s", err)
|
||||
}
|
||||
|
||||
expectedAuth := test.expectedSignature
|
||||
gotAuth := fmt.Sprintf("Authorization: %s", r.Header["Authorization"][0])
|
||||
if gotAuth != expectedAuth {
|
||||
t.Errorf("Signature string mismatch\nGot: %s\nWant: %s", gotAuth, expectedAuth)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test_Verifying_HTTP_Messages_AppendixC implement tests from Appendix C
|
||||
// in the http signatures specification:
|
||||
// https://tools.ietf.org/html/draft-cavage-http-signatures-10#appendix-C
|
||||
func Test_Verifying_HTTP_Messages_AppendixC(t *testing.T) {
|
||||
specTests := []struct {
|
||||
name string
|
||||
headers []string
|
||||
signature string
|
||||
}{
|
||||
{
|
||||
name: "C.1. Default Test",
|
||||
headers: []string{},
|
||||
signature: `Signature keyId="Test",algorithm="rsa-sha256",signature="SjWJWbWN7i0wzBvtPl8rbASWz5xQW6mcJmn+ibttBqtifLN7Sazz6m79cNfwwb8DMJ5cou1s7uEGKKCs+FLEEaDV5lp7q25WqS+lavg7T8hc0GppauB6hbgEKTwblDHYGEtbGmtdHgVCk9SuS13F0hZ8FD0k/5OxEPXe5WozsbM="`,
|
||||
},
|
||||
{
|
||||
name: "C.2. Basic Test",
|
||||
headers: []string{"(request-target)", "host", "date"},
|
||||
signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="`,
|
||||
},
|
||||
{
|
||||
name: "C.3. All Headers Test",
|
||||
headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"},
|
||||
signature: `Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range specTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody)))
|
||||
if err != nil {
|
||||
t.Fatalf("error creating request: %s", err)
|
||||
}
|
||||
|
||||
r.Header["Date"] = []string{testSpecDate}
|
||||
r.Header["Host"] = []string{r.URL.Host}
|
||||
r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))}
|
||||
r.Header["Content-Type"] = []string{"application/json"}
|
||||
setDigest(r)
|
||||
r.Header["Authorization"] = []string{test.signature}
|
||||
|
||||
v, err := NewVerifier(r)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating verifier: %s", err)
|
||||
}
|
||||
|
||||
if "Test" != v.KeyId() {
|
||||
t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId())
|
||||
}
|
||||
if err := v.Verify(testSpecRSAPublicKey, RSA_SHA256); err != nil {
|
||||
t.Errorf("Verification failure: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyingEd25519(t *testing.T) {
|
||||
specTests := []struct {
|
||||
name string
|
||||
headers []string
|
||||
signature string
|
||||
}{
|
||||
{
|
||||
name: "Default Test",
|
||||
headers: []string{},
|
||||
signature: `Signature keyId="Test",algorithm="hs2019",headers="date",signature="6G9bNnUfph4pnl3j8l4UTcSPJVg6r4tM73eWFAn+w4IdIi8yzzZs65QlgM31lAuVCRKlqMzME9VGgMt16nU1AQ=="`,
|
||||
},
|
||||
{
|
||||
name: "Basic Test",
|
||||
headers: []string{"(request-target)", "host", "date"},
|
||||
signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date",signature="upsoNpw5oJTD3lTIQHEnDGWTaKmlT7o2c9Lz3kqy2UTwOEpEop3Sd7F/K2bYD2lQ4AH1HRyvC4/9AcKgNBg1AA=="`,
|
||||
},
|
||||
{
|
||||
name: "All Headers Test",
|
||||
headers: []string{"(request-target)", "host", "date", "content-type", "digest", "content-length"},
|
||||
signature: `Signature keyId="Test",algorithm="hs2019",headers="(request-target) host date content-type digest content-length",signature="UkxhZl0W5/xcuCIP5xOPv4V6rX0TmaV2lmrYYGWauKhdFHihpW80tCqTNFDhyD+nYeGNCRSFRHmDS0bGm0PVAg=="`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range specTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test := test
|
||||
r, err := http.NewRequest("POST", "http://example.com/foo?param=value&pet=dog", bytes.NewBuffer([]byte(testSpecBody)))
|
||||
if err != nil {
|
||||
t.Fatalf("error creating request: %s", err)
|
||||
}
|
||||
|
||||
r.Header["Date"] = []string{testSpecDate}
|
||||
r.Header["Host"] = []string{r.URL.Host}
|
||||
r.Header["Content-Length"] = []string{strconv.Itoa(len(testSpecBody))}
|
||||
r.Header["Content-Type"] = []string{"application/json"}
|
||||
setDigest(r)
|
||||
r.Header["Authorization"] = []string{test.signature}
|
||||
|
||||
v, err := NewVerifier(r)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating verifier: %s", err)
|
||||
}
|
||||
|
||||
if "Test" != v.KeyId() {
|
||||
t.Errorf("KeyId mismatch\nGot: %s\nWant: Test", v.KeyId())
|
||||
}
|
||||
if err := v.Verify(testEd25519PublicKey, ED25519); err != nil {
|
||||
t.Errorf("Verification failure: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func loadPrivateKey(keyData []byte) (*rsa.PrivateKey, error) {
|
||||
pem, _ := pem.Decode(keyData)
|
||||
if pem.Type != "RSA PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("RSA private key is of the wrong type: %s", pem.Type)
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(pem.Bytes)
|
||||
}
|
||||
|
||||
// taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/
|
||||
func loadEd25519PrivateKey(keyData []byte) (ed25519.PrivateKey, error) {
|
||||
var block *pem.Block
|
||||
block, _ = pem.Decode(keyData)
|
||||
|
||||
var asn1PrivKey ed25519PrivKey
|
||||
asn1.Unmarshal(block.Bytes, &asn1PrivKey)
|
||||
|
||||
// [2:] is skipping the byte for TAG and the byte for LEN
|
||||
// see also https://tools.ietf.org/html/draft-ietf-curdle-pkix-10#section-10.3
|
||||
return ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:]), nil
|
||||
}
|
||||
|
||||
func loadPublicKey(keyData []byte) (*rsa.PublicKey, error) {
|
||||
pem, _ := pem.Decode(keyData)
|
||||
if pem.Type != "PUBLIC KEY" {
|
||||
return nil, fmt.Errorf("public key is of the wrong type: %s", pem.Type)
|
||||
}
|
||||
|
||||
key, err := x509.ParsePKIXPublicKey(pem.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key.(*rsa.PublicKey), nil
|
||||
}
|
||||
|
||||
// taken from https://blainsmith.com/articles/signing-jwts-with-gos-crypto-ed25519/
|
||||
func loadEd25519PublicKey(keyData []byte) (ed25519.PublicKey, error) {
|
||||
var block *pem.Block
|
||||
block, _ = pem.Decode(keyData)
|
||||
|
||||
var asn1PubKey ed25519PubKey
|
||||
asn1.Unmarshal(block.Bytes, &asn1PubKey)
|
||||
|
||||
return ed25519.PublicKey(asn1PubKey.PublicKey.Bytes), nil
|
||||
}
|
||||
|
||||
func setDigest(r *http.Request) ([]byte, error) {
|
||||
var bodyBytes []byte
|
||||
if _, ok := r.Header["Digest"]; !ok {
|
||||
body := ""
|
||||
if r.Body != nil {
|
||||
var err error
|
||||
bodyBytes, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading body. %v", err)
|
||||
}
|
||||
|
||||
// And now set a new body, which will simulate the same data we read:
|
||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
|
||||
d := sha256.Sum256([]byte(body))
|
||||
r.Header["Digest"] = []string{fmt.Sprintf("SHA-256=%s", base64.StdEncoding.EncodeToString(d[:]))}
|
||||
}
|
||||
|
||||
return bodyBytes, nil
|
||||
}
|
||||
|
||||
const testSpecBody = `{"hello": "world"}`
|
||||
|
||||
const testSpecDate = `Sun, 05 Jan 2014 21:31:40 GMT`
|
||||
|
||||
const testSpecPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
|
||||
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
|
||||
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
|
||||
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
|
||||
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
|
||||
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
|
||||
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
|
||||
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
|
||||
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
|
||||
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
|
||||
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
|
||||
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
|
||||
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
const testSpecPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
||||
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
||||
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
||||
oYi+1hqp1fIekaxsyQIDAQAB
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
const testEd25519PrivateKeyPEM = `-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIAP+PK4NtdzCe04sbtwBvf9IShlky298SMMBqkCCToHn
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
const testEd25519PublicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAhyP+7zpNCsr7/ipGJjK0zVszTEQ5tooyX3VLAnBSc1c=
|
||||
-----END PUBLIC KEY-----`
|
Loading…
Add table
Add a link
Reference in a new issue