go-experimental.http_signature_test.mustache Maven / Gradle / Ivy
{{>partial_header}}
package {{packageName}}
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
)
// Test RSA private key as published in Appendix C 'Test Values' of
// https://www.ietf.org/id/draft-cavage-http-signatures-12.txt
const rsaTestPrivateKey string = `-----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-----`
func writeTestRsaPemKey(t *testing.T, filePath string) {
err := ioutil.WriteFile(filePath, []byte(rsaTestPrivateKey), 0644)
if err != nil {
t.Fatalf("Error writing private key: %v", err)
}
}
type keyFormat int // The serialization format of the private key.
const (
keyFormatPem keyFormat = iota // Private key is serialized in PEM format.
keyFormatPkcs8Pem // Private key is serialized as PKCS#8 encoded in PEM format.
keyFormatPkcs8Der // Private key is serialized as PKCS#8 encoded in DER format.
)
func writeRandomTestRsaPemKey(t *testing.T, filePath string, bits int, format keyFormat, passphrase string, alg *x509.PEMCipher) {
key, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
t.Fatalf("Error generating RSA private key file: %v", err)
}
var outFile *os.File
outFile, err = os.Create(filePath)
if err != nil {
t.Fatalf("Error creating RSA private key file: %v", err)
}
defer outFile.Close()
var privKeyBytes []byte
switch format {
case keyFormatPem:
if passphrase != "" {
t.Fatalf("Encrypting PKCS#1-encoded private key with passphrase is not supported")
}
privKeyBytes = x509.MarshalPKCS1PrivateKey(key)
case keyFormatPkcs8Pem:
privKeyBytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatalf("Error writing private key: %v", err)
}
case keyFormatPkcs8Der:
if passphrase != "" {
t.Fatalf("Encrypting DER-encoded private key with passphrase is not supported")
}
privKeyBytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatalf("Error writing private key: %v", err)
}
_, err = outFile.Write(privKeyBytes)
if err != nil {
t.Fatalf("Error writing DER-encoded private key: %v", err)
}
default:
t.Fatalf("Unsupported key format: %v", format)
}
switch format {
case keyFormatPem, keyFormatPkcs8Der:
var pemBlock *pem.Block
if passphrase == "" {
pemBlock = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privKeyBytes,
}
} else {
pemBlock, err = x509.EncryptPEMBlock(rand.Reader, "ENCRYPTED PRIVATE KEY", privKeyBytes, []byte(passphrase), *alg)
if err != nil {
t.Fatalf("Error encoding RSA private key: %v", err)
}
}
err = pem.Encode(outFile, pemBlock)
if err != nil {
t.Fatalf("Error encoding RSA private key: %v", err)
}
}
fmt.Printf("Wrote private key '%s'\n", filePath)
}
/*
Commented out because OpenAPITools is configured to use golang 1.8 at build time
x509.MarshalPKCS8PrivateKey is not present.
func writeRandomTestEcdsaPemKey(t *testing.T, filePath string) {
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
t.Fatalf("Error generating ECDSA private key file: %v", err)
}
var outFile *os.File
outFile, err = os.Create(filePath)
if err != nil {
t.Fatalf("Error creating ECDSA private key file: %v", err)
}
defer outFile.Close()
var keybytes []byte
keybytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatalf("Error marshaling ECDSA private key: %v", err)
}
var privateKey = &pem.Block{
Type: "PRIVATE KEY",
Bytes: keybytes,
}
err = pem.Encode(outFile, privateKey)
if err != nil {
t.Fatalf("Error encoding ECDSA private key: %v", err)
}
}
*/
// TestHttpSignaturePrivateKeys creates private keys of various sizes, serialization format,
// clear-text and password encrypted.
// Test unmarshaling of the private key.
func TestHttpSignaturePrivateKeys(t *testing.T) {
var err error
var dir string
dir, err = ioutil.TempDir("", "go-http-sign")
if err != nil {
t.Fatalf("Failed to create temporary directory")
}
defer os.RemoveAll(dir)
pemCiphers := []x509.PEMCipher{
x509.PEMCipherDES,
x509.PEMCipher3DES,
x509.PEMCipherAES128,
x509.PEMCipherAES192,
x509.PEMCipherAES256,
}
// Test RSA private keys with various key lengths.
for _, bits := range []int{1024, 2048, 3072, 4096} {
for _, format := range []keyFormat{keyFormatPem, keyFormatPkcs8Pem, keyFormatPkcs8Der} {
// Generate test private key.
var privateKeyPath string
switch format {
case keyFormatPem, keyFormatPkcs8Pem:
privateKeyPath = "privatekey.pem"
case keyFormatPkcs8Der:
privateKeyPath = "privatekey.der"
default:
t.Fatalf("Unsupported private key format: %v", format)
}
privateKeyPath = filepath.Join(dir, privateKeyPath)
// Generate keys in PEM format.
writeRandomTestRsaPemKey(t, privateKeyPath, bits, format, "", nil)
authConfig := HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: privateKeyPath,
Passphrase: "",
SigningScheme: "hs2019",
SignedHeaders: []string{"Content-Type"},
}
// Create a context with the HTTP signature configuration parameters.
_, err = authConfig.ContextWithValue(context.Background())
if err != nil {
t.Fatalf("Error loading private key '%s': %v", privateKeyPath, err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: privateKeyPath,
Passphrase: "my-secret-passphrase",
SigningScheme: "hs2019",
SignedHeaders: []string{"Content-Type"},
}
switch format {
case keyFormatPem:
// Do nothing. Keys cannot be encrypted when using PKCS#1.
case keyFormatPkcs8Pem:
for _, alg := range pemCiphers {
writeRandomTestRsaPemKey(t, privateKeyPath, bits, format, authConfig.Passphrase, &alg)
_, err := authConfig.ContextWithValue(context.Background())
if err != nil {
t.Fatalf("Error loading private key '%s': %v", privateKeyPath, err)
}
}
}
}
}
/*
Unfortunately, currently the build environment for OpenAPITools is using an old version
of golang that does not support ECDSA keys.
{
privateKeyPath := "privatekey.pem"
authConfig := HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: "hs2019",
SignedHeaders: []string{"Content-Type"},
}
// Generate test private key.
writeRandomTestEcdsaPemKey(t, privateKeyPath)
err := authConfig.LoadPrivateKey(privateKeyPath)
if err != nil {
t.Fatalf("Error loading private key '%s': %v", privateKeyPath, err)
}
}
*/
}
const testHost = "petstore.swagger.io:80"
const testScheme = "http"
func executeHttpSignatureAuth(t *testing.T, authConfig *HttpSignatureAuth, expectSuccess bool) string {
var err error
var dir string
dir, err = ioutil.TempDir("", "go-http-sign")
if err != nil {
t.Fatalf("Failed to create temporary directory")
}
defer os.RemoveAll(dir)
cfg := NewConfiguration()
cfg.AddDefaultHeader("testheader", "testvalue")
cfg.AddDefaultHeader("Content-Type", "application/json")
cfg.Host = testHost
cfg.Scheme = testScheme
apiClient := NewAPIClient(cfg)
privateKeyPath := filepath.Join(dir, "rsa.pem")
writeTestRsaPemKey(t, privateKeyPath)
authConfig.PrivateKeyPath = privateKeyPath
var authCtx context.Context
authCtx, err = authConfig.ContextWithValue(context.Background())
if expectSuccess && err != nil {
t.Fatalf("Error validating HTTP signature configuration: %v", err)
}
if !expectSuccess && err != nil {
// Do not continue. Error is expected.
return ""
}
newPet := (Pet{Id: PtrInt64(12992), Name: "gopher",
PhotoUrls: []string{"http://1.com", "http://2.com"},
Status: PtrString("pending"),
Tags: &[]Tag{Tag{Id: PtrInt64(1), Name: PtrString("tag2")}}})
fmt.Printf("Request with HTTP signature. Scheme: '%s'. Algorithm: '%s'. MaxValidity: %v. Headers: '%v'\n",
authConfig.SigningScheme, authConfig.SigningAlgorithm, authConfig.SignatureMaxValidity, authConfig.SignedHeaders)
r, err2 := apiClient.PetApi.AddPet(authCtx).Pet(newPet).Execute()
if expectSuccess && err2 != nil {
t.Fatalf("Error while adding pet: %v", err2)
}
if !expectSuccess {
if err2 == nil {
t.Fatalf("Error was expected, but no error was generated")
} else {
// Do not continue. Error is expected.
return ""
}
}
if r.StatusCode != 200 {
t.Log(r)
}
_, r, err = apiClient.PetApi.GetPetById(authCtx, 12992).Execute()
if expectSuccess && err != nil {
t.Fatalf("Error while deleting pet by id: %v", err)
}
// The request should look like this:
//
// GET /v2/pet/12992 HTTP/1.1
// Host: petstore.swagger.io:80
// Accept: application/json
// Authorization: Signature keyId="my-key-id",algorithm="hs2019",created=1579033245,headers="(request-target) date host digest content-type",signature="RMJZjVVxzlH02wlxiQgUYDe4QxZaI5IJNIfB2BK8Dhbv3WQ2gw0xyqC+5HiKUmT/cfchhhkUNNsUtiVRnjZmFwtSfYxHfiQvH3KWXlLCMwKGNQC3YzD9lnoWdx0pA5Kxbr0/ygmr3+lTyuN2PJg4IS7Ji/AaKAqIZx7RsHS8Bxw="
// Date: Tue, 14 Jan 2020 06:41:22 GMT
// Digest: SHA-512=z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==
// Testheader: testvalue
// User-Agent: OpenAPI-Generator/1.0.0/go
reqb, _ := httputil.DumpRequest(r.Request, true)
reqt := (string)(reqb)
fmt.Printf("REQUEST:\n%v\n", reqt)
var sb bytes.Buffer
fmt.Fprintf(&sb, `Signature keyId="%s",algorithm="%s",`,
authConfig.KeyId, authConfig.SigningScheme)
if len(authConfig.SignedHeaders) == 0 {
fmt.Fprintf(&sb, `created=[0-9]+,`)
} else {
for _, header := range authConfig.SignedHeaders {
header = strings.ToLower(header)
if header == HttpSignatureParameterCreated {
fmt.Fprintf(&sb, `created=[0-9]+,`)
}
if header == HttpSignatureParameterExpires {
fmt.Fprintf(&sb, `expires=[0-9]+\.[0-9]{3},`)
}
}
}
fmt.Fprintf(&sb, `headers="`)
for i, header := range authConfig.SignedHeaders {
header = strings.ToLower(header)
if i > 0 {
fmt.Fprintf(&sb, " ")
}
fmt.Fprintf(&sb, regexp.QuoteMeta(header))
switch header {
case "date":
if !strings.Contains(reqt, "Date: ") {
t.Errorf("Date header is incorrect")
}
case "digest":
var prefix string
switch authConfig.SigningScheme {
case HttpSigningSchemeRsaSha256:
prefix = "SHA-256="
default:
prefix = "SHA-512="
}
if !strings.Contains(reqt, fmt.Sprintf("Digest: %s", prefix)) {
t.Errorf("Digest header is incorrect")
}
}
}
if len(authConfig.SignedHeaders) == 0 {
fmt.Fprintf(&sb, regexp.QuoteMeta(HttpSignatureParameterCreated))
}
fmt.Fprintf(&sb, `",signature="[a-zA-Z0-9+/]+="`)
re := regexp.MustCompile(sb.String())
actual := r.Request.Header.Get("Authorization")
if !re.MatchString(actual) {
t.Errorf("Authorization header is incorrect. Expected regex\n'%s'\nbut got\n'%s'", sb.String(), actual)
}
validateHttpAuthorizationSignature(t, authConfig, r)
return r.Request.Header.Get("Authorization")
}
var (
// signatureRe is a regular expression to capture the fields from the signature.
signatureRe = regexp.MustCompile(
`Signature keyId="(?P[^"]+)",algorithm="(?P[^"]+)"` +
`(,created=(?P[0-9]+))?(,expires=(?P[0-9.]+))?,headers="(?P[^"]+)",signature="(?P[^"]+)"`)
)
// validateHttpAuthorizationSignature validates the HTTP signature in the HTTP request.
// The signature verification would normally be done by the server.
// Note: this is NOT a complete implementation of the HTTP signature validation. This code is for unit test purpose, do not use
// it for server side implementation.
// In particular, this code does not validate the calculation of the HTTP body digest.
func validateHttpAuthorizationSignature(t *testing.T, authConfig *HttpSignatureAuth, r *http.Response) {
authHeader := r.Request.Header.Get("Authorization")
match := signatureRe.FindStringSubmatch(authHeader)
if len(match) < 3 {
t.Fatalf("Unexpected Authorization header: %s", authHeader)
}
result := make(map[string]string)
for i, name := range signatureRe.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
b64signature := result["signature"]
fmt.Printf("Algorithm: '%s' Headers: '%s' b64signature: '%s'\n", result["algorithm"], result["headers"], b64signature)
var sb bytes.Buffer
fmt.Fprintf(&sb, "%s %s", strings.ToLower(r.Request.Method), r.Request.URL.EscapedPath())
if r.Request.URL.RawQuery != "" {
// The ":path" pseudo-header field includes the path and query parts
// of the target URI (the "path-absolute" production and optionally a
// '?' character followed by the "query" production (see Sections 3.3
// and 3.4 of [RFC3986]
fmt.Fprintf(&sb, "?%s", r.Request.URL.RawQuery)
}
requestTarget := sb.String()
var signedHeaderKvs []string
signedHeaders := strings.Split(result["headers"], " ")
for _, h := range signedHeaders {
var value string
switch h {
case HttpSignatureParameterRequestTarget:
value = requestTarget
case HttpSignatureParameterCreated:
value = result["created"]
case HttpSignatureParameterExpires:
value = result["expires"]
default:
value = r.Request.Header.Get(h)
}
signedHeaderKvs = append(signedHeaderKvs, fmt.Sprintf("%s: %s", h, value))
}
stringToSign := strings.Join(signedHeaderKvs, "\n")
var h crypto.Hash
switch result["algorithm"] {
case HttpSigningSchemeHs2019, HttpSigningSchemeRsaSha512:
h = crypto.SHA512
case HttpSigningSchemeRsaSha256:
h = crypto.SHA256
default:
t.Fatalf("Unexpected signing algorithm: %s", result["algorithm"])
}
msgHash := h.New()
if _, err := msgHash.Write([]byte(stringToSign)); err != nil {
t.Fatalf("Unable to compute hash: %v", err)
}
d := msgHash.Sum(nil)
var pub crypto.PublicKey
var err error
if pub, err = authConfig.GetPublicKey(); err != nil {
t.Fatalf("Unable to get public key: %v", err)
}
if pub == nil {
t.Fatalf("Public key is nil")
}
var signature []byte
if signature, err = base64.StdEncoding.DecodeString(b64signature); err != nil {
t.Fatalf("Failed to decode signature: %v", err)
}
switch publicKey := pub.(type) {
case *rsa.PublicKey:
// It could be PKCS1v15 or PSS signature
var errPKCS1v15, errPSS error
// In a server-side implementation, we wouldn't try to validate both signatures. The specific
// signature algorithm would be derived from the key id. But here we just want to validate for unit test purpose.
errPKCS1v15 = rsa.VerifyPKCS1v15(publicKey, h, d, signature)
errPSS = rsa.VerifyPSS(publicKey, h, d, signature, nil)
if errPKCS1v15 != nil && errPSS != nil {
t.Fatalf("RSA Signature verification failed: %v. %v", errPKCS1v15, errPSS)
}
case *ecdsa.PublicKey:
type ecdsaSignature struct {
R, S *big.Int
}
var lEcdsa ecdsaSignature
if _, err = asn1.Unmarshal(signature, &lEcdsa); err != nil {
t.Fatalf("Unable to parse ECDSA signature: %v", err)
}
if !ecdsa.Verify(publicKey, d, lEcdsa.R, lEcdsa.S) {
t.Fatalf("ECDSA Signature verification failed")
}
default:
t.Fatalf("Unsupported public key: %T", pub)
}
}
func TestHttpSignatureAuth(t *testing.T) {
// Test with 'hs2019' signature scheme, and default signature algorithm (RSA SSA PKCS1.5)
authConfig := HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target.
HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value.
"Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number.
"Date", // The date and time at which the message was originated.
"Content-Type", // The Media type of the body of the request.
"Digest", // A cryptographic digest of the request body.
},
}
executeHttpSignatureAuth(t, &authConfig, true)
// Test with duplicate headers. This is invalid and should be rejected.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"Host", "Date", "Host"},
}
executeHttpSignatureAuth(t, &authConfig, false)
// Test with non-existent header. This is invalid and should be rejected.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"Host", "Date", "Garbage-HeaderDoesNotExist"},
}
executeHttpSignatureAuth(t, &authConfig, false)
// Test with 'Authorization' header in the signed headers. This is invalid and should be rejected.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"Host", "Authorization"},
}
executeHttpSignatureAuth(t, &authConfig, false)
// Specify signature max validity, but '(expires)' parameter is missing. This should cause an error.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignatureMaxValidity: 7 * time.Minute,
}
executeHttpSignatureAuth(t, &authConfig, false)
// Specify invalid signature max validity. This should cause an error.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignatureMaxValidity: -3 * time.Minute,
}
executeHttpSignatureAuth(t, &authConfig, false)
// Specify signature max validity and '(expires)' parameter.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignatureMaxValidity: time.Minute,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target.
HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value.
HttpSignatureParameterExpires, // Time when signature expires.
},
}
executeHttpSignatureAuth(t, &authConfig, true)
// Specify '(expires)' parameter but no signature max validity.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target.
HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value.
HttpSignatureParameterExpires, // Time when signature expires.
},
}
executeHttpSignatureAuth(t, &authConfig, false)
// Test with empty signed headers. The client should automatically add the "(created)" parameter by default.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{},
}
executeHttpSignatureAuth(t, &authConfig, true)
// Test with deprecated RSA-SHA512, some servers may still support it.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeRsaSha512,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target.
HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value.
"Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number.
"Date", // The date and time at which the message was originated.
"Content-Type", // The Media type of the body of the request.
"Digest", // A cryptographic digest of the request body.
},
}
executeHttpSignatureAuth(t, &authConfig, true)
// Test with deprecated RSA-SHA256, some servers may still support it.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeRsaSha256,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget, // The special (request-target) parameter expresses the HTTP request target.
HttpSignatureParameterCreated, // Time when request was signed, formatted as a Unix timestamp integer value.
"Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number.
"Date", // The date and time at which the message was originated.
"Content-Type", // The Media type of the body of the request.
"Digest", // A cryptographic digest of the request body.
},
}
executeHttpSignatureAuth(t, &authConfig, true)
// Test with headers without date. This makes it possible to get a fixed signature, used for unit test purpose.
// This should not be used in production code as it could potentially allow replay attacks.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SigningAlgorithm: HttpSigningAlgorithmRsaPKCS1v15,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget,
"Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number.
"Content-Type", // The Media type of the body of the request.
"Digest", // A cryptographic digest of the request body.
},
}
authorizationHeaderValue := executeHttpSignatureAuth(t, &authConfig, true)
expectedSignature := "sXE2MDeW8os6ywv1oUWaFEPFcSPCEb/msQ/NZGKNA9Emm/e42axaAPojzfkZ9Hacyw/iS/5nH4YIkczMgXu3z5fAwFjumxtf3OxbqvUcQ80wvw2/7B5aQmsF6ZwrCFHZ+L/cj9/bg7L1EGUGtdyDzoRKti4zf9QF/03OsP7QljI="
expectedAuthorizationHeader := fmt.Sprintf(
`Signature keyId="my-key-id",`+
`algorithm="hs2019",headers="(request-target) host content-type digest",`+
`signature="%s"`, expectedSignature)
if authorizationHeaderValue != expectedAuthorizationHeader {
t.Errorf("Authorization header value is incorrect. Got\n'%s'\nbut expected\n'%s'", authorizationHeaderValue, expectedAuthorizationHeader)
}
// Test with PSS signature. The PSS signature creates a new signature every time it is invoked.
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
SigningScheme: HttpSigningSchemeHs2019,
SigningAlgorithm: HttpSigningAlgorithmRsaPSS,
SignedHeaders: []string{
HttpSignatureParameterRequestTarget,
"Host", // The Host request header specifies the domain name of the server, and optionally the TCP port number.
"Content-Type", // The Media type of the body of the request.
"Digest", // A cryptographic digest of the request body.
},
}
authorizationHeaderValue = executeHttpSignatureAuth(t, &authConfig, true)
expectedSignature = `[a-zA-Z0-9+/]+=`
expectedAuthorizationHeader = fmt.Sprintf(
`Signature keyId="my-key-id",`+
`algorithm="hs2019",headers="\(request-target\) host content-type digest",`+
`signature="%s"`, expectedSignature)
re := regexp.MustCompile(expectedAuthorizationHeader)
if !re.MatchString(authorizationHeaderValue) {
t.Errorf("Authorization header value is incorrect. Got\n'%s'\nbut expected\n'%s'", authorizationHeaderValue, expectedAuthorizationHeader)
}
}
func TestInvalidHttpSignatureConfiguration(t *testing.T) {
var err error
var authConfig HttpSignatureAuth
authConfig = HttpSignatureAuth{
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Key ID must be specified") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Private key path must be specified") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: "test.pem",
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Invalid signing scheme") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: "test.pem",
SigningScheme: "garbage",
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Invalid signing scheme") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: "test.pem",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"foo", "bar", "foo"},
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "cannot have duplicate names") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: "test.pem",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"foo", "bar", "Authorization"},
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Signed headers cannot include the 'Authorization' header") {
t.Fatalf("Invalid configuration: %v", err)
}
authConfig = HttpSignatureAuth{
KeyId: "my-key-id",
PrivateKeyPath: "test.pem",
SigningScheme: HttpSigningSchemeHs2019,
SignedHeaders: []string{"foo", "bar"},
SignatureMaxValidity: -7 * time.Minute,
}
_, err = authConfig.ContextWithValue(context.Background())
if err == nil || !strings.Contains(err.Error(), "Signature max validity must be a positive value") {
t.Fatalf("Invalid configuration: %v", err)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy