New Upstream Release - golang-github-russellhaering-goxmldsig

Ready changes

Summary

Merged new upstream version: 1.3.0 (was: 1.2.0).

Resulting package

Built on 2023-04-02T02:37 (took 5m21s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-russellhaering-goxmldsig-dev

Lintian Result

Diff

diff --git a/README.md b/README.md
index a775887..c572e8c 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,19 @@ $ go get github.com/russellhaering/goxmldsig
 
 ## Usage
 
+Include the [`types.Signature`](https://pkg.go.dev/github.com/russellhaering/goxmldsig/types#Signature) struct from this package in your application messages.
+
+```go
+import (
+    sigtypes "github.com/russellhaering/goxmldsig/types"
+)
+
+type AppHdr struct {
+    ...
+    Signature *sigtypes.Signature
+}
+```
+
 ### Signing
 
 ```go
diff --git a/debian/changelog b/debian/changelog
index cec9120..9f6e9ce 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-russellhaering-goxmldsig (1.3.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 02 Apr 2023 02:32:48 -0000
+
 golang-github-russellhaering-goxmldsig (1.2.0-1) unstable; urgency=medium
 
   * Team upload
diff --git a/etreeutils/canonicalize.go b/etreeutils/canonicalize.go
index 8437fe4..82ceb0a 100644
--- a/etreeutils/canonicalize.go
+++ b/etreeutils/canonicalize.go
@@ -16,7 +16,8 @@ func TransformExcC14n(el *etree.Element, inclusiveNamespacesPrefixList string, c
 		prefixSet[prefix] = struct{}{}
 	}
 
-	err := transformExcC14n(DefaultNSContext, DefaultNSContext, el, prefixSet, comments)
+	ctx := NewDefaultNSContext()
+	err := transformExcC14n(ctx, ctx, el, prefixSet, comments)
 	if err != nil {
 		return err
 	}
@@ -31,7 +32,7 @@ func transformExcC14n(ctx, declared NSContext, el *etree.Element, inclusiveNames
 	}
 
 	visiblyUtilizedPrefixes := map[string]struct{}{
-		el.Space: struct{}{},
+		el.Space: {},
 	}
 
 	filteredAttrs := []etree.Attr{}
diff --git a/etreeutils/namespace.go b/etreeutils/namespace.go
index baf1124..6a5be03 100644
--- a/etreeutils/namespace.go
+++ b/etreeutils/namespace.go
@@ -2,9 +2,7 @@ package etreeutils
 
 import (
 	"errors"
-
 	"fmt"
-
 	"sort"
 
 	"github.com/beevik/etree"
@@ -19,20 +17,25 @@ const (
 	XMLNSNamespace = "http://www.w3.org/2000/xmlns/"
 )
 
-var (
-	DefaultNSContext = NSContext{
+func NewDefaultNSContext() NSContext {
+	defaultLimit := 1000
+	return NSContext{
 		prefixes: map[string]string{
 			defaultPrefix: XMLNamespace,
 			xmlPrefix:     XMLNamespace,
 			xmlnsPrefix:   XMLNSNamespace,
 		},
+		limit: &defaultLimit,
 	}
+}
 
+var (
 	EmptyNSContext = NSContext{}
 
 	ErrReservedNamespace       = errors.New("disallowed declaration of reserved namespace")
 	ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
 	ErrTraversalHalted         = errors.New("traversal halted")
+	ErrTraversalLimit          = errors.New("traversal limit reached")
 )
 
 type ErrUndeclaredNSPrefix struct {
@@ -45,6 +48,17 @@ func (e ErrUndeclaredNSPrefix) Error() string {
 
 type NSContext struct {
 	prefixes map[string]string
+	limit    *int
+}
+
+// CheckLimit checks the traversal limit before calling the handler function
+func (ctx NSContext) CheckLimit() error {
+	if *ctx.limit <= 0 {
+		return ErrTraversalLimit
+	}
+	*ctx.limit--
+
+	return nil
 }
 
 func (ctx NSContext) Copy() NSContext {
@@ -53,7 +67,7 @@ func (ctx NSContext) Copy() NSContext {
 		prefixes[k] = v
 	}
 
-	return NSContext{prefixes: prefixes}
+	return NSContext{prefixes: prefixes, limit: ctx.limit}
 }
 
 func (ctx NSContext) declare(prefix, namespace string) etree.Attr {
@@ -140,7 +154,12 @@ type NSIterHandler func(NSContext, *etree.Element) error
 // NSTraverse traverses an element tree, invoking the passed handler for each element
 // in the tree.
 func NSTraverse(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
-	ctx, err := ctx.SubContext(el)
+	err := ctx.CheckLimit()
+	if err != nil {
+		return err
+	}
+
+	ctx, err = ctx.SubContext(el)
 	if err != nil {
 		return err
 	}
@@ -223,7 +242,7 @@ func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) {
 // NSSelectOne behaves identically to NSSelectOneCtx, but uses DefaultNSContext as the
 // surrounding context.
 func NSSelectOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
-	return NSSelectOneCtx(DefaultNSContext, el, namespace, tag)
+	return NSSelectOneCtx(NewDefaultNSContext(), el, namespace, tag)
 }
 
 // NSSelectOneCtx conducts a depth-first search for an element with the specified namespace
@@ -243,7 +262,6 @@ func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*e
 
 		return ErrTraversalHalted
 	})
-
 	if err != nil {
 		return nil, err
 	}
@@ -254,7 +272,7 @@ func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*e
 // NSFindIterate behaves identically to NSFindIterateCtx, but uses DefaultNSContext
 // as the surrounding context.
 func NSFindIterate(el *etree.Element, namespace, tag string, handle NSIterHandler) error {
-	return NSFindIterateCtx(DefaultNSContext, el, namespace, tag, handle)
+	return NSFindIterateCtx(NewDefaultNSContext(), el, namespace, tag, handle)
 }
 
 // NSFindIterateCtx conducts a depth-first traversal searching for elements with the
@@ -294,7 +312,7 @@ func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, h
 // NSFindOne behaves identically to NSFindOneCtx, but uses DefaultNSContext for
 // context.
 func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
-	return NSFindOneCtx(DefaultNSContext, el, namespace, tag)
+	return NSFindOneCtx(NewDefaultNSContext(), el, namespace, tag)
 }
 
 // NSFindOneCtx conducts a depth-first search for the specified element. If such an element
@@ -306,7 +324,6 @@ func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etr
 		found = el
 		return ErrTraversalHalted
 	})
-
 	if err != nil {
 		return nil, err
 	}
@@ -325,6 +342,11 @@ func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) e
 
 	// Iterate the child elements.
 	for _, child := range el.ChildElements() {
+		err := ctx.CheckLimit()
+		if err != nil {
+			return err
+		}
+
 		err = handle(ctx, child)
 		if err != nil {
 			return err
@@ -368,7 +390,7 @@ func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag s
 // NSFindOneChild behaves identically to NSFindOneChildCtx, but uses
 // DefaultNSContext for context.
 func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) {
-	return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag)
+	return NSFindOneChildCtx(NewDefaultNSContext(), el, namespace, tag)
 }
 
 // NSFindOneCtx conducts a depth-first search for the specified element. If such an
@@ -394,11 +416,10 @@ func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string)
 func NSBuildParentContext(el *etree.Element) (NSContext, error) {
 	parent := el.Parent()
 	if parent == nil {
-		return DefaultNSContext, nil
+		return NewDefaultNSContext(), nil
 	}
 
 	ctx, err := NSBuildParentContext(parent)
-
 	if err != nil {
 		return ctx, err
 	}
diff --git a/sign.go b/sign.go
index 2be34b7..cc77f79 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"
@@ -15,11 +17,18 @@ import (
 )
 
 type SigningContext struct {
-	Hash          crypto.Hash
+	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,13 +41,54 @@ 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) 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 := signatureMethodsByIdentifier[algorithmID]
 	if !ok {
-		return fmt.Errorf("Unknown SignatureMethod: %s", algorithmID)
+		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
 }
@@ -58,6 +108,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 == "" {
@@ -97,7 +187,6 @@ func (ctx *SigningContext) constructSignedInfo(el *etree.Element, enveloped bool
 		reference.CreateAttr(URIAttr, "#"+dataId)
 	}
 
-
 	// /SignedInfo/Reference/Transforms
 	transforms := ctx.createNamespacedElement(reference, TransformsTag)
 	if enveloped {
@@ -172,20 +261,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
 	}
@@ -222,7 +303,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 ""
@@ -247,11 +330,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 febfec6..b0042ae 100644
--- a/sign_test.go
+++ b/sign_test.go
@@ -2,6 +2,7 @@ package dsig
 
 import (
 	"crypto"
+	"crypto/tls"
 	"encoding/base64"
 	"testing"
 
@@ -12,14 +13,24 @@ import (
 func TestSign(t *testing.T) {
 	randomKeyStore := RandomKeyStoreForTest()
 	ctx := NewDefaultSigningContext(randomKeyStore)
+	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, RSASHA256SignatureMethod, crypto.SHA256)
+}
 
+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)
 
@@ -49,7 +60,7 @@ func TestSign(t *testing.T) {
 
 	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)
@@ -73,7 +84,7 @@ func TestSign(t *testing.T) {
 
 	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)
@@ -126,3 +137,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/validate.go b/validate.go
index 2c65ca1..45e3013 100644
--- a/validate.go
+++ b/validate.go
@@ -2,7 +2,6 @@ package dsig
 
 import (
 	"bytes"
-	"crypto/rsa"
 	"crypto/x509"
 	"encoding/base64"
 	"errors"
@@ -213,26 +212,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 7516376..ad27891 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -127,6 +127,32 @@ yy7YHlSiVX13QH2XTu/iQQ==
 -----END CERTIFICATE-----
 `
 
+const ecdsaResponse = `<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="id-e65dcbd76bd33f51c51137855d499382ffcbd235" Version="2.0" IssueInstant="2019-06-14T21:16:16.206Z"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://localhost/saml/acs/</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/><ds:Reference URI="#id-e65dcbd76bd33f51c51137855d499382ffcbd235"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>Uh15pBqpaLb8KW9EnUCSsw1D3UN6IE7cM6c69fwy1xQ=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>MEUCIAwuDhyvbhNE7vfS9oqsGwdao/E8EJSK1mQ8gIEIIOQBAiEAud5l0TQru0m291/XzWvdBJ71HN/hOknOnKXqM7OwXrU=</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIB3jCCAYSgAwIBAgITC3mzvAn7vitNgC2KTnea8hlp8jAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDYxMzAwNTYwMVoXDTIxMDYxMjAwNTYwMVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEIa9GeZw9TVMAv7Vnn3bz0DdQstQTIHkSnYfKw6QObxRZJoWvDRcvv2zblCki5FuqTbYqUNeDIQEsKwTJRHUCKjUzBRMB0GA1UdDgQWBBRDic4JRcFytcfX1QkFlsOJVUdrTzAfBgNVHSMEGDAWgBRDic4JRcFytcfX1QkFlsOJVUdrTzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDF2He80OqZJCe8Fjo0BlS5UsRJ3tChy/ZbmkE2DUaFjgIgKpLzRwr21VdekDagOpZj8ENzJ9YC5w+BwffTRwfkyLE=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="beepboopmeow" IssueInstant="2019-06-14T21:16:16.206Z" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">urn:extrahop:saml:hopcloud:ra:idp</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/><ds:Reference URI="#beepboopmeow"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>FfMcWntKHiIB8bpyFayq1nK5wtcCHMpCUnowv7/0dBQ=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>MEQCIFXVoJmVBLb+zJKDwnIBUA+Mdp0ww0689pvIDPktROS1AiAimmnSUjzMMflVUJvngeyJta33wVMMObIxcEDNesco5A==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIB3jCCAYSgAwIBAgITC3mzvAn7vitNgC2KTnea8hlp8jAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE5MDYxMzAwNTYwMVoXDTIxMDYxMjAwNTYwMVowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEIa9GeZw9TVMAv7Vnn3bz0DdQstQTIHkSnYfKw6QObxRZJoWvDRcvv2zblCki5FuqTbYqUNeDIQEsKwTJRHUCKjUzBRMB0GA1UdDgQWBBRDic4JRcFytcfX1QkFlsOJVUdrTzAfBgNVHSMEGDAWgBRDic4JRcFytcfX1QkFlsOJVUdrTzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQDF2He80OqZJCe8Fjo0BlS5UsRJ3tChy/ZbmkE2DUaFjgIgKpLzRwr21VdekDagOpZj8ENzJ9YC5w+BwffTRwfkyLE=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">woof</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2019-06-14T21:17:46.206Z"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2019-06-14T21:13:16.206Z" NotOnOrAfter="2019-06-14T21:17:46.206Z"><saml:AudienceRestriction><saml:Audience></saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2019-06-14T21:16:16.206Z" SessionIndex="beepboopmeow"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"><saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">reserved</saml:AttributeValue></saml:Attribute><saml:Attribute Name="urn:oid:2.5.4.4" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"><saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">reserved</saml:AttributeValue></saml:Attribute><saml:Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"><saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">boop@example.com</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>`
+
+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()
@@ -191,20 +217,36 @@ 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},
 	}
 	vc := NewDefaultValidationContext(&certStore)
+	vc.Clock = NewFakeClockAt(cert.NotBefore)
 
 	el, err := vc.Validate(doc.Root())
 	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)
+}
+
 const (
 	validateCert = `
 -----BEGIN CERTIFICATE-----
diff --git a/xml_constants.go b/xml_constants.go
index d2b98e2..0526062 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,12 +42,17 @@ 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
+// Well-known signature algorithms
 const (
 	// Supported canonicalization algorithms
 	CanonicalXML10ExclusiveAlgorithmId             AlgorithmID = "http://www.w3.org/2001/10/xml-exc-c14n#"
@@ -62,23 +70,54 @@ 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",
 }
 
+type signatureMethodInfo struct {
+	PublicKeyAlgorithm x509.PublicKeyAlgorithm
+	Hash               crypto.Hash
+}
+
 var digestAlgorithmsByIdentifier = map[string]crypto.Hash{}
-var signatureMethodsByIdentifier = map[string]crypto.Hash{}
+var signatureMethodsByIdentifier = 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 {
+			signatureMethodsByIdentifier[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: {
+		crypto.SHA1:   RSASHA1SignatureMethod,
+		crypto.SHA256: RSASHA256SignatureMethod,
+		crypto.SHA384: RSASHA384SignatureMethod,
+		crypto.SHA512: RSASHA512SignatureMethod,
+	},
+	x509.ECDSA: {
+		crypto.SHA1:   ECDSASHA1SignatureMethod,
+		crypto.SHA256: ECDSASHA256SignatureMethod,
+		crypto.SHA384: ECDSASHA384SignatureMethod,
+		crypto.SHA512: ECDSASHA512SignatureMethod,
+	},
+}
+
+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,
 }

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details