@@ -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