From 0d01ec80d1cc8f8ac4b1dc60a97c6143b9f16b81 Mon Sep 17 00:00:00 2001 From: charredlot Date: Sat, 15 Jun 2019 22:16:42 -0700 Subject: [PATCH 1/3] Support validation of ECDSA signatures - Use x509.Certificate.CheckSignature in validate to support ECDSA signatures - Add map for signature methods to x509 signature algorithms - Add ECDSA and RSA-SHA384 signature methods - Add ECDSA validate test --- validate.go | 19 ++----------------- validate_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- xml_constants.go | 28 ++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/validate.go b/validate.go index 55feb39..1bee70c 100644 --- a/validate.go +++ b/validate.go @@ -2,7 +2,6 @@ package dsig import ( "bytes" - "crypto/rsa" "crypto/x509" "encoding/base64" "errors" @@ -205,26 +204,12 @@ func (ctx *ValidationContext) verifySignedInfo(sig *types.Signature, canonicaliz return err } - signatureAlgorithm, ok := signatureMethodsByIdentifier[signatureMethodId] + algo, ok := x509SignatureAlgorithmByIdentifier[signatureMethodId] if !ok { return errors.New("Unknown signature method: " + signatureMethodId) } - hash := signatureAlgorithm.New() - _, err = hash.Write(canonical) - if err != nil { - return err - } - - hashed := hash.Sum(nil) - - pubKey, ok := cert.PublicKey.(*rsa.PublicKey) - if !ok { - return errors.New("Invalid public key") - } - - // Verify that the private key matching the public key from the cert was what was used to sign the 'SignedInfo' and produce the 'SignatureValue' - err = rsa.VerifyPKCS1v15(pubKey, signatureAlgorithm, hashed[:], decodedSignature) + err = cert.CheckSignature(algo, canonical, decodedSignature) if err != nil { return err } diff --git a/validate_test.go b/validate_test.go index 8048600..fdd9a16 100644 --- a/validate_test.go +++ b/validate_test.go @@ -126,6 +126,32 @@ yy7YHlSiVX13QH2XTu/iQQ== -----END CERTIFICATE----- ` +const ecdsaResponse = `https://localhost/saml/acs/Uh15pBqpaLb8KW9EnUCSsw1D3UN6IE7cM6c69fwy1xQ=MEUCIAwuDhyvbhNE7vfS9oqsGwdao/E8EJSK1mQ8gIEIIOQBAiEAud5l0TQru0m291/XzWvdBJ71HN/hOknOnKXqM7OwXrU=MIIB3jCCAYSgAwIBAgITC3mzvAn7vitNgC2KTnea8hlp8jAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDYxMzAwNTYwMVoXDTIxMDYxMjAwNTYwMVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEIa9GeZw9TVMAv7Vnn3bz0DdQstQTIHkSnYfKw6QObxRZJoWvDRcvv2zblCki5FuqTbYqUNeDIQEsKwTJRHUCKjUzBRMB0GA1UdDgQWBBRDic4JRcFytcfX1QkFlsOJVUdrTzAfBgNVHSMEGDAWgBRDic4JRcFytcfX1QkFlsOJVUdrTzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDF2He80OqZJCe8Fjo0BlS5UsRJ3tChy/ZbmkE2DUaFjgIgKpLzRwr21VdekDagOpZj8ENzJ9YC5w+BwffTRwfkyLE=urn:extrahop:saml:hopcloud:ra:idpFfMcWntKHiIB8bpyFayq1nK5wtcCHMpCUnowv7/0dBQ=MEQCIFXVoJmVBLb+zJKDwnIBUA+Mdp0ww0689pvIDPktROS1AiAimmnSUjzMMflVUJvngeyJta33wVMMObIxcEDNesco5A==MIIB3jCCAYSgAwIBAgITC3mzvAn7vitNgC2KTnea8hlp8jAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDYxMzAwNTYwMVoXDTIxMDYxMjAwNTYwMVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEIa9GeZw9TVMAv7Vnn3bz0DdQstQTIHkSnYfKw6QObxRZJoWvDRcvv2zblCki5FuqTbYqUNeDIQEsKwTJRHUCKjUzBRMB0GA1UdDgQWBBRDic4JRcFytcfX1QkFlsOJVUdrTzAfBgNVHSMEGDAWgBRDic4JRcFytcfX1QkFlsOJVUdrTzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDF2He80OqZJCe8Fjo0BlS5UsRJ3tChy/ZbmkE2DUaFjgIgKpLzRwr21VdekDagOpZj8ENzJ9YC5w+BwffTRwfkyLE=woofurn:oasis:names:tc:SAML:2.0:ac:classes:unspecifiedreservedreservedboop@example.com` + +const ecdsaCert = ` +-----BEGIN CERTIFICATE----- +MIIB3jCCAYSgAwIBAgITC3mzvAn7vitNgC2KTnea8hlp8jAKBggqhkjOPQQDAjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDYxMzAwNTYwMVoXDTIxMDYxMjAw +NTYwMVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNV +BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABEIa9GeZw9TVMAv7Vnn3bz0DdQstQTIHkSnYfKw6QObxRZJoWvDRcvv2 +zblCki5FuqTbYqUNeDIQEsKwTJRHUCKjUzBRMB0GA1UdDgQWBBRDic4JRcFytcfX +1QkFlsOJVUdrTzAfBgNVHSMEGDAWgBRDic4JRcFytcfX1QkFlsOJVUdrTzAPBgNV +HRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDF2He80OqZJCe8Fjo0BlS5 +UsRJ3tChy/ZbmkE2DUaFjgIgKpLzRwr21VdekDagOpZj8ENzJ9YC5w+BwffTRwfk +yLE= +-----END CERTIFICATE----- +` + +const ecdsaKey = ` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILnLofyDaFeGyDutTFYuWY0u5IVmny1spzfJbCixceI7oAoGCCqGSM49 +AwEHoUQDQgAEQhr0Z5nD1NUwC/tWefdvPQN1Cy1BMgeRKdh8rDpA5vFFkmha8NFy ++/bNuUKSLkW6pNtipQ14MhASwrBMlEdQIg== +-----END EC PRIVATE KEY----- +` + func TestDigest(t *testing.T) { canonicalizer := MakeC14N10ExclusiveCanonicalizerWithPrefixList("") doc := etree.NewDocument() @@ -190,9 +216,13 @@ func TestValidateWithEmptySignatureReference(t *testing.T) { require.NotEmpty(t, reference) require.Empty(t, reference.SelectAttr(URIAttr).Value) - block, _ := pem.Decode([]byte(oktaCert)) + testValidateDoc(t, doc, oktaCert) +} + +func testValidateDoc(t *testing.T, doc *etree.Document, certPEM string) { + block, _ := pem.Decode([]byte(certPEM)) cert, err := x509.ParseCertificate(block.Bytes) - require.NoError(t, err, "couldn't parse okta cert pem block") + require.NoError(t, err, "couldn't parse cert pem block") certStore := MemoryX509CertificateStore{ Roots: []*x509.Certificate{cert}, @@ -203,3 +233,14 @@ func TestValidateWithEmptySignatureReference(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, el) } + +func TestValidateECDSA(t *testing.T) { + doc := etree.NewDocument() + err := doc.ReadFromBytes([]byte(ecdsaResponse)) + require.NoError(t, err) + + sig := doc.FindElement("//" + SignatureTag) + require.NotEmpty(t, sig) + + testValidateDoc(t, doc, ecdsaCert) +} diff --git a/xml_constants.go b/xml_constants.go index c4b815b..357e83f 100644 --- a/xml_constants.go +++ b/xml_constants.go @@ -1,6 +1,9 @@ package dsig -import "crypto" +import ( + "crypto" + "crypto/x509" +) const ( DefaultPrefix = "ds" @@ -39,9 +42,14 @@ func (id AlgorithmID) String() string { } const ( - RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" + RSASHA1SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + RSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" + RSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" + RSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" + ECDSASHA1SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1" + ECDSASHA256SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" + ECDSASHA384SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384" + ECDSASHA512SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512" ) //Well-known signature algorithms @@ -59,6 +67,7 @@ const ( var digestAlgorithmIdentifiers = map[crypto.Hash]string{ crypto.SHA1: "http://www.w3.org/2000/09/xmldsig#sha1", crypto.SHA256: "http://www.w3.org/2001/04/xmlenc#sha256", + crypto.SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384", crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512", } @@ -79,3 +88,14 @@ var signatureMethodIdentifiers = map[crypto.Hash]string{ crypto.SHA256: RSASHA256SignatureMethod, crypto.SHA512: RSASHA512SignatureMethod, } + +var x509SignatureAlgorithmByIdentifier = map[string]x509.SignatureAlgorithm{ + RSASHA1SignatureMethod: x509.SHA1WithRSA, + RSASHA256SignatureMethod: x509.SHA256WithRSA, + RSASHA384SignatureMethod: x509.SHA384WithRSA, + RSASHA512SignatureMethod: x509.SHA512WithRSA, + ECDSASHA1SignatureMethod: x509.ECDSAWithSHA1, + ECDSASHA256SignatureMethod: x509.ECDSAWithSHA256, + ECDSASHA384SignatureMethod: x509.ECDSAWithSHA384, + ECDSASHA512SignatureMethod: x509.ECDSAWithSHA512, +} From d2ec034c9d856687be45981f7749f058644b0305 Mon Sep 17 00:00:00 2001 From: charredlot Date: Mon, 17 Jun 2019 16:21:34 -0700 Subject: [PATCH 2/3] SigningContext: use a crypto.Signer if available - Add crypto.Signer and certs fields to SigningContext - Add function NewSigningContext to create a SigningContext that uses crypto.Signer and a certs slice - To preserve backwards compatibility, if the SigningContext has a non-nil X509KeyStore, it will behave the same as before. - Add the logic to use the crypto.Signer to sign and include the certs - Add test case for NewSigningContext with RSA --- sign.go | 93 +++++++++++++++++++++++++++++++++++++++++----------- sign_test.go | 10 ++++++ 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/sign.go b/sign.go index e2c2852..d53a8cd 100644 --- a/sign.go +++ b/sign.go @@ -15,11 +15,19 @@ import ( ) type SigningContext struct { - Hash crypto.Hash - KeyStore X509KeyStore + Hash crypto.Hash + + // This field will be nil and unused if the SigningContext is created with + // NewSigningContext + KeyStore X509KeyStore + IdAttribute string Prefix string Canonicalizer Canonicalizer + + // KeyStore is mutually exclusive with signer and certs + signer crypto.Signer + certs [][]byte } func NewDefaultSigningContext(ks X509KeyStore) *SigningContext { @@ -32,6 +40,27 @@ func NewDefaultSigningContext(ks X509KeyStore) *SigningContext { } } +// NewSigningContext creates a new signing context with the given signer and certificate chain. +// Note that e.g. rsa.PrivateKey implements the crypto.Signer interface. +// The certificate chain is a slice of ASN.1 DER-encoded X.509 certificates. +// A SigningContext created with this function should not use the KeyStore field. +// It will return error if passed a nil crypto.Signer +func NewSigningContext(signer crypto.Signer, certs [][]byte) (*SigningContext, error) { + if signer == nil { + return nil, errors.New("signer cannot be nil for NewSigningContext") + } + ctx := &SigningContext{ + Hash: crypto.SHA256, + IdAttribute: DefaultIdAttr, + Prefix: DefaultPrefix, + Canonicalizer: MakeC14N11Canonicalizer(), + + signer: signer, + certs: certs, + } + return ctx, nil +} + func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error { hash, ok := signatureMethodsByIdentifier[algorithmID] if !ok { @@ -58,6 +87,46 @@ func (ctx *SigningContext) digest(el *etree.Element) ([]byte, error) { return hash.Sum(nil), nil } +func (ctx *SigningContext) signDigest(digest []byte) ([]byte, error) { + if ctx.KeyStore != nil { + key, _, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest) + if err != nil { + return nil, err + } + + return rawSignature, nil + } else { + rawSignature, err := ctx.signer.Sign(rand.Reader, digest, ctx.Hash) + if err != nil { + return nil, err + } + + return rawSignature, nil + } +} + +func (ctx *SigningContext) getCerts() ([][]byte, error) { + if ctx.KeyStore != nil { + if cs, ok := ctx.KeyStore.(X509ChainStore); ok { + return cs.GetChain() + } + + _, cert, err := ctx.KeyStore.GetKeyPair() + if err != nil { + return nil, err + } + + return [][]byte{cert}, nil + } else { + return ctx.certs, nil + } +} + func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool) (*etree.Element, error) { digestAlgorithmIdentifier := ctx.GetDigestAlgorithmIdentifier() if digestAlgorithmIdentifier == "" { @@ -171,20 +240,12 @@ func (ctx *SigningContext) ConstructSignature(el *etree.Element, enveloped bool) return nil, err } - key, cert, err := ctx.KeyStore.GetKeyPair() + rawSignature, err := ctx.signDigest(digest) if err != nil { return nil, err } - certs := [][]byte{cert} - if cs, ok := ctx.KeyStore.(X509ChainStore); ok { - certs, err = cs.GetChain() - if err != nil { - return nil, err - } - } - - rawSignature, err := rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest) + certs, err := ctx.getCerts() if err != nil { return nil, err } @@ -246,11 +307,5 @@ func (ctx *SigningContext) SignString(content string) ([]byte, error) { } digest := hash.Sum(nil) - var signature []byte - if key, _, err := ctx.KeyStore.GetKeyPair(); err != nil { - return nil, fmt.Errorf("unable to fetch key for signing: %v", err) - } else if signature, err = rsa.SignPKCS1v15(rand.Reader, key, ctx.Hash, digest); err != nil { - return nil, fmt.Errorf("error signing: %v", err) - } - return signature, nil + return ctx.signDigest(digest) } diff --git a/sign_test.go b/sign_test.go index 56baa2a..f460ad8 100644 --- a/sign_test.go +++ b/sign_test.go @@ -12,7 +12,17 @@ import ( func TestSign(t *testing.T) { randomKeyStore := RandomKeyStoreForTest() ctx := NewDefaultSigningContext(randomKeyStore) + testSignWithContext(t, ctx) +} + +func TestNewSigningContext(t *testing.T) { + randomKeyStore := RandomKeyStoreForTest().(*MemoryX509KeyStore) + ctx, err := NewSigningContext(randomKeyStore.privateKey, [][]byte{randomKeyStore.cert}) + require.NoError(t, err) + testSignWithContext(t, ctx) +} +func testSignWithContext(t *testing.T, ctx *SigningContext) { authnRequest := &etree.Element{ Space: "samlp", Tag: "AuthnRequest", From 37ab28b5e73908b6dd791bf71a6f8fa0e1f7a1a5 Mon Sep 17 00:00:00 2001 From: charredlot Date: Mon, 17 Jun 2019 16:36:24 -0700 Subject: [PATCH 3/3] SigningContext: Support ECDSA signatures - Change signatureMethodIdentifiers map from crypto.Hash to nested map with x509.PublicKeyAlgorithm and crypto.Hash - Change SetSignatureMethod and GetSignatureMethodIdentifier accordingly - Add check in SetSignatureMethod against the public key algorithm. - Add tests for SetSignatureMethod and ECDSA signing --- sign.go | 30 +++++++++++++++++++++++++++--- sign_test.go | 47 +++++++++++++++++++++++++++++++++++++++++------ xml_constants.go | 33 ++++++++++++++++++++++++++------- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/sign.go b/sign.go index d53a8cd..787a3e2 100644 --- a/sign.go +++ b/sign.go @@ -2,10 +2,12 @@ package dsig import ( "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" _ "crypto/sha1" _ "crypto/sha256" + "crypto/x509" "encoding/base64" "errors" "fmt" @@ -61,13 +63,33 @@ func NewSigningContext(signer crypto.Signer, certs [][]byte) (*SigningContext, e return ctx, nil } +func (ctx *SigningContext) getPublicKeyAlgorithm() x509.PublicKeyAlgorithm { + if ctx.KeyStore != nil { + return x509.RSA + } else { + switch ctx.signer.Public().(type) { + case *ecdsa.PublicKey: + return x509.ECDSA + case *rsa.PublicKey: + return x509.RSA + } + } + + return x509.UnknownPublicKeyAlgorithm +} + func (ctx *SigningContext) SetSignatureMethod(algorithmID string) error { - hash, ok := signatureMethodsByIdentifier[algorithmID] + info, ok := signatureMethodByIdentifiers[algorithmID] if !ok { return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID) } - ctx.Hash = hash + algo := ctx.getPublicKeyAlgorithm() + if info.PublicKeyAlgorithm != algo { + return fmt.Errorf("SignatureMethod %s is incompatible with %s key", algorithmID, algo) + } + + ctx.Hash = info.Hash return nil } @@ -282,7 +304,9 @@ func (ctx *SigningContext) SignEnveloped(el *etree.Element) (*etree.Element, err } func (ctx *SigningContext) GetSignatureMethodIdentifier() string { - if ident, ok := signatureMethodIdentifiers[ctx.Hash]; ok { + algo := ctx.getPublicKeyAlgorithm() + + if ident, ok := signatureMethodIdentifiers[algo][ctx.Hash]; ok { return ident } return "" diff --git a/sign_test.go b/sign_test.go index f460ad8..4b34fe1 100644 --- a/sign_test.go +++ b/sign_test.go @@ -2,6 +2,7 @@ package dsig import ( "crypto" + "crypto/tls" "encoding/base64" "testing" @@ -12,24 +13,24 @@ import ( func TestSign(t *testing.T) { randomKeyStore := RandomKeyStoreForTest() ctx := NewDefaultSigningContext(randomKeyStore) - testSignWithContext(t, ctx) + testSignWithContext(t, ctx, RSASHA256SignatureMethod, crypto.SHA256) } func TestNewSigningContext(t *testing.T) { randomKeyStore := RandomKeyStoreForTest().(*MemoryX509KeyStore) ctx, err := NewSigningContext(randomKeyStore.privateKey, [][]byte{randomKeyStore.cert}) require.NoError(t, err) - testSignWithContext(t, ctx) + testSignWithContext(t, ctx, RSASHA256SignatureMethod, crypto.SHA256) } -func testSignWithContext(t *testing.T, ctx *SigningContext) { +func testSignWithContext(t *testing.T, ctx *SigningContext, sigMethodID string, digestAlgo crypto.Hash) { authnRequest := &etree.Element{ Space: "samlp", Tag: "AuthnRequest", } id := "_97e34c50-65ec-4132-8b39-02933960a96a" authnRequest.CreateAttr("ID", id) - hash := crypto.SHA256.New() + hash := digestAlgo.New() canonicalized, err := ctx.Canonicalizer.Canonicalize(authnRequest) require.NoError(t, err) @@ -59,7 +60,7 @@ func testSignWithContext(t *testing.T, ctx *SigningContext) { signatureMethodAttr := signatureMethodElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, signatureMethodAttr) - require.Equal(t, "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", signatureMethodAttr.Value) + require.Equal(t, sigMethodID, signatureMethodAttr.Value) referenceElement := signedInfo.FindElement("//" + ReferenceTag) require.NotEmpty(t, referenceElement) @@ -83,7 +84,7 @@ func testSignWithContext(t *testing.T, ctx *SigningContext) { digestMethodAttr := digestMethodElement.SelectAttr(AlgorithmAttr) require.NotEmpty(t, digestMethodElement) - require.Equal(t, "http://www.w3.org/2001/04/xmlenc#sha256", digestMethodAttr.Value) + require.Equal(t, digestAlgorithmIdentifiers[digestAlgo], digestMethodAttr.Value) digestValueElement := referenceElement.FindElement("//" + DigestValueTag) require.NotEmpty(t, digestValueElement) @@ -147,3 +148,37 @@ func TestSignNonDefaultID(t *testing.T) { refURI := ref.SelectAttrValue("URI", "") require.Equal(t, refURI, "#"+id) } + +func TestIncompatibleSignatureMethods(t *testing.T) { + // RSA + randomKeyStore := RandomKeyStoreForTest().(*MemoryX509KeyStore) + ctx, err := NewSigningContext(randomKeyStore.privateKey, [][]byte{randomKeyStore.cert}) + require.NoError(t, err) + + err = ctx.SetSignatureMethod(ECDSASHA512SignatureMethod) + require.Error(t, err) + + // ECDSA + testECDSACert, err := tls.X509KeyPair([]byte(ecdsaCert), []byte(ecdsaKey)) + require.NoError(t, err) + + ctx, err = NewSigningContext(testECDSACert.PrivateKey.(crypto.Signer), testECDSACert.Certificate) + require.NoError(t, err) + + err = ctx.SetSignatureMethod(RSASHA1SignatureMethod) + require.Error(t, err) +} + +func TestSignWithECDSA(t *testing.T) { + cert, err := tls.X509KeyPair([]byte(ecdsaCert), []byte(ecdsaKey)) + require.NoError(t, err) + + ctx, err := NewSigningContext(cert.PrivateKey.(crypto.Signer), cert.Certificate) + require.NoError(t, err) + + method := ECDSASHA512SignatureMethod + err = ctx.SetSignatureMethod(method) + require.NoError(t, err) + + testSignWithContext(t, ctx, method, crypto.SHA512) +} diff --git a/xml_constants.go b/xml_constants.go index 357e83f..9955172 100644 --- a/xml_constants.go +++ b/xml_constants.go @@ -71,22 +71,41 @@ var digestAlgorithmIdentifiers = map[crypto.Hash]string{ crypto.SHA512: "http://www.w3.org/2001/04/xmlenc#sha512", } +type signatureMethodInfo struct { + PublicKeyAlgorithm x509.PublicKeyAlgorithm + Hash crypto.Hash +} + var digestAlgorithmsByIdentifier = map[string]crypto.Hash{} -var signatureMethodsByIdentifier = map[string]crypto.Hash{} +var signatureMethodByIdentifiers = map[string]signatureMethodInfo{} func init() { for hash, id := range digestAlgorithmIdentifiers { digestAlgorithmsByIdentifier[id] = hash } - for hash, id := range signatureMethodIdentifiers { - signatureMethodsByIdentifier[id] = hash + for algo, hashToMethod := range signatureMethodIdentifiers { + for hash, method := range hashToMethod { + signatureMethodByIdentifiers[method] = signatureMethodInfo{ + PublicKeyAlgorithm: algo, + Hash: hash, + } + } } } -var signatureMethodIdentifiers = map[crypto.Hash]string{ - crypto.SHA1: RSASHA1SignatureMethod, - crypto.SHA256: RSASHA256SignatureMethod, - crypto.SHA512: RSASHA512SignatureMethod, +var signatureMethodIdentifiers = map[x509.PublicKeyAlgorithm]map[crypto.Hash]string{ + x509.RSA: map[crypto.Hash]string{ + crypto.SHA1: RSASHA1SignatureMethod, + crypto.SHA256: RSASHA256SignatureMethod, + crypto.SHA384: RSASHA384SignatureMethod, + crypto.SHA512: RSASHA512SignatureMethod, + }, + x509.ECDSA: map[crypto.Hash]string{ + crypto.SHA1: ECDSASHA1SignatureMethod, + crypto.SHA256: ECDSASHA256SignatureMethod, + crypto.SHA384: ECDSASHA384SignatureMethod, + crypto.SHA512: ECDSASHA512SignatureMethod, + }, } var x509SignatureAlgorithmByIdentifier = map[string]x509.SignatureAlgorithm{