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