Skip to content

Commit 9a10555

Browse files
authored
Merge pull request #24 from pressly/update_authnreq_signature
Update AuthnReq signature generation.
2 parents febce40 + c773851 commit 9a10555

File tree

1 file changed

+24
-11
lines changed

1 file changed

+24
-11
lines changed

sp_handlers.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"strings"
1212

1313
"github.com/beevik/etree"
14-
"github.com/pressly/saml/xmlsec"
1514
"github.com/pkg/errors"
15+
"github.com/pressly/saml/xmlsec"
1616
dsig "github.com/russellhaering/goxmldsig"
1717
)
1818

@@ -24,7 +24,7 @@ func (sp *ServiceProvider) SAMLRequest(relayState string) (string, error) {
2424
return "", errors.Wrap(err, "failed to create auth request")
2525
}
2626

27-
buf, err := xml.MarshalIndent(authnRequest, "", "\t")
27+
buf, err := xml.Marshal(authnRequest)
2828
if err != nil {
2929
return "", errors.Wrap(err, "failed to marshal auth request")
3030
}
@@ -93,8 +93,18 @@ func (sp *ServiceProvider) SAMLRequestForm(authnRequest []byte, relayState strin
9393
}
9494

9595
signingContext := dsig.NewDefaultSigningContext(dsig.TLSCertKeyStore(cert))
96+
// CA API Gateway IdP requires the exclusive canonicalization algorithm
97+
//
98+
// From the spec: http: //docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
99+
// 5.4.3 Canonicalization Method
100+
// SAML implementations SHOULD use Exclusive Canonicalization [Excl-C14N], with or without comments,
101+
// both in the <ds:CanonicalizationMethod> element of <ds:SignedInfo>, and as a
102+
// <ds:Transform> algorithm. Use of Exclusive Canonicalization ensures that signatures created over
103+
// SAML messages embedded in an XML context can be verified independent of that context.
104+
signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
96105
signingContext.SetSignatureMethod(CryptoSHA256)
97106

107+
// Build an etree document from the AuthnRequest XML
98108
doc := etree.NewDocument()
99109
err = doc.ReadFromBytes(authnRequest)
100110
if err != nil {
@@ -104,23 +114,26 @@ func (sp *ServiceProvider) SAMLRequestForm(authnRequest []byte, relayState strin
104114
if len(doc.Child) < 1 {
105115
return "", errors.Errorf("expecting at least one child element for authn request")
106116
}
107-
// Review the signing flow
108-
el := doc.Child[0].(*etree.Element)
109-
sig, err := signingContext.ConstructSignature(el, true)
117+
118+
// Signature requires an element, not document
119+
element := doc.Child[0].(*etree.Element)
120+
sig, err := signingContext.ConstructSignature(element, true)
110121
if err != nil {
111122
return "", errors.Wrap(err, "failed to build authn request signature")
112123
}
113124

114-
elCopy := el.Copy()
125+
// Build a new element with the signature included
126+
elementWithSig := element.Copy()
115127
// Following the flow defined in the gosaml2 lib: https://github.com/russellhaering/gosaml2/blob/master/build_request.go#L17
116128
var children []etree.Token
117-
children = append(children, elCopy.Child[1]) // issuer is always first
118-
children = append(children, sig) // next is the signature
119-
children = append(children, elCopy.Child[2:]...) // then all other children
120-
elCopy.Child = children
129+
children = append(children, elementWithSig.Child[0]) // issuer is always first
130+
children = append(children, sig) // next is the signature
131+
children = append(children, elementWithSig.Child[1:]...) // then all other children
132+
elementWithSig.Child = children
121133

134+
// Convert the signed element to a document before marhsalling
122135
doc = etree.NewDocument()
123-
doc.SetRoot(elCopy)
136+
doc.SetRoot(elementWithSig)
124137
if authnRequest, err = doc.WriteToBytes(); err != nil {
125138
return "", errors.Wrap(err, "failed to write xml document to string")
126139
}

0 commit comments

Comments
 (0)