diff --git a/.travis.yml b/.travis.yml
index 6ffb3752..d2b67f69 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,6 @@
language: go
-addons:
- apt:
- packages:
- - libxml2-dev
- - libxmlsec1-dev
-
go:
- - 1.6
- 1.7
- 1.8
- tip
diff --git a/identity_provider.go b/identity_provider.go
index 9191eabb..6f5f66e4 100644
--- a/identity_provider.go
+++ b/identity_provider.go
@@ -3,6 +3,7 @@ package saml
import (
"bytes"
"compress/flate"
+ "crypto/tls"
"encoding/base64"
"encoding/pem"
"encoding/xml"
@@ -16,7 +17,9 @@ import (
"text/template"
"time"
- "github.com/crewjam/go-xmlsec"
+ "github.com/beevik/etree"
+ "github.com/crewjam/saml/xmlenc"
+ dsig "github.com/russellhaering/goxmldsig"
)
// Session represents a user session. It is returned by the
@@ -338,7 +341,7 @@ func (req *IdpAuthnRequest) Validate() error {
// MakeAssertion produces a SAML assertion for the
// given request and assigns it to req.Assertion.
func (req *IdpAuthnRequest) MakeAssertion(session *Session) error {
- signatureTemplate := xmlsec.DefaultSignature([]byte(req.IDP.Certificate))
+
attributes := []Attribute{}
if session.UserName != "" {
attributes = append(attributes, Attribute{
@@ -422,7 +425,6 @@ func (req *IdpAuthnRequest) MakeAssertion(session *Session) error {
Format: "XXX",
Value: req.IDP.Metadata().EntityID,
},
- Signature: &signatureTemplate,
Subject: &Subject{
NameID: &NameID{
Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
@@ -470,58 +472,81 @@ func (req *IdpAuthnRequest) MakeAssertion(session *Session) error {
// MarshalAssertion sets `AssertionBuffer` to a signed, encrypted
// version of `Assertion`.
func (req *IdpAuthnRequest) MarshalAssertion() error {
- buf, err := xml.Marshal(req.Assertion)
+ keyPair, err := tls.X509KeyPair([]byte(req.IDP.Certificate), []byte(req.IDP.Key))
if err != nil {
return err
}
+ keyStore := dsig.TLSCertKeyStore(keyPair)
- buf, err = xmlsec.Sign([]byte(req.IDP.Key),
- buf, xmlsec.SignatureOptions{})
- if err != nil {
+ signingContext := dsig.NewDefaultSigningContext(keyStore)
+ if err = signingContext.SetSignatureMethod(dsig.RSASHA1SignatureMethod); err != nil {
return err
}
- buf, err = xmlsec.Encrypt(getSPEncryptionCert(req.ServiceProviderMetadata),
- buf, xmlsec.EncryptOptions{})
+ assertionEl, err := marshalEtreeHack(req.Assertion)
if err != nil {
return err
}
- req.AssertionBuffer = buf
- return nil
-}
+ signedAssertionEl, err := signingContext.SignEnveloped(assertionEl)
+ if err != nil {
+ return err
+ }
-// MakeResponse creates and assigns a new SAML response in Response. `Assertion` must
-// be non-nill. If MarshalAssertion() has not been called, this function calls it for
-// you.
-func (req *IdpAuthnRequest) MakeResponse() error {
- if req.AssertionBuffer == nil {
- if err := req.MarshalAssertion(); err != nil {
+ var signedAssertionBuf []byte
+ {
+ doc := etree.NewDocument()
+ doc.SetRoot(signedAssertionEl)
+ signedAssertionBuf, err = doc.WriteToBytes()
+ if err != nil {
return err
}
}
- req.Response = &Response{
- Destination: req.ACSEndpoint.Location,
- ID: fmt.Sprintf("id-%x", randomBytes(20)),
- InResponseTo: req.Request.ID,
- IssueInstant: TimeNow(),
- Version: "2.0",
- Issuer: &Issuer{
- Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
- Value: req.IDP.MetadataURL,
- },
- Status: &Status{
- StatusCode: StatusCode{
- Value: StatusSuccess,
- },
- },
- EncryptedAssertion: &EncryptedAssertion{
- EncryptedData: req.AssertionBuffer,
- },
+
+ encryptor := xmlenc.OAEP()
+ encryptor.BlockCipher = xmlenc.AES128CBC
+ encryptor.DigestMethod = &xmlenc.SHA1
+ certBuf, err := getSPEncryptionCert(req.ServiceProviderMetadata)
+ if err != nil {
+ return err
+ }
+ encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf)
+ if err != nil {
+ return err
+ }
+ encryptedDataEl.CreateAttr("Type", "http://www.w3.org/2001/04/xmlenc#Element")
+
+ {
+ encryptedAssertionEl := etree.NewElement("saml2:EncryptedAssertion")
+ encryptedAssertionEl.CreateAttr("xmlns:saml2", "urn:oasis:names:tc:SAML:2.0:protocol")
+ encryptedAssertionEl.AddChild(encryptedDataEl)
+ doc := etree.NewDocument()
+ doc.SetRoot(encryptedAssertionEl)
+ req.AssertionBuffer, err = doc.WriteToBytes()
+ if err != nil {
+ return err
+ }
}
return nil
}
+// marshalEtreeHack returns an etree.Element for the value v.
+//
+// This is a hack -- it first users xml.Marshal and then loads the
+// resulting buffer into an etree.
+func marshalEtreeHack(v interface{}) (*etree.Element, error) {
+ buf, err := xml.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+
+ doc := etree.NewDocument()
+ if err := doc.ReadFromBytes(buf); err != nil {
+ return nil, err
+ }
+ return doc.Root(), nil
+}
+
// WriteResponse writes the `Response` to the http.ResponseWriter. If
// `Response` is not already set, it calls MakeResponse to produce it.
func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
@@ -574,7 +599,7 @@ func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
// getSPEncryptionCert returns the certificate which we can use to encrypt things
// to the SP in PEM format, or nil if no such certificate is found.
-func getSPEncryptionCert(sp *Metadata) []byte {
+func getSPEncryptionCert(sp *Metadata) ([]byte, error) {
cert := ""
for _, keyDescriptor := range sp.SPSSODescriptor.KeyDescriptor {
if keyDescriptor.Use == "encryption" {
@@ -595,14 +620,58 @@ func getSPEncryptionCert(sp *Metadata) []byte {
}
if cert == "" {
- return nil
+ return nil, fmt.Errorf("cannot find a certificate for encryption in the service provider SSO descriptor")
}
// cleanup whitespace and re-encode a PEM
- cert = regexp.MustCompile("\\s+").ReplaceAllString(cert, "")
- certBytes, _ := base64.StdEncoding.DecodeString(cert)
- certBytes = pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: certBytes})
- return certBytes
+ cert = regexp.MustCompile(`\s+`).ReplaceAllString(cert, "")
+ certBytes, err := base64.StdEncoding.DecodeString(cert)
+ if err != nil {
+ return nil, err
+ }
+ return certBytes, nil
+}
+
+// unmarshalEtreeHack parses `el` and sets values in the structure `v`.
+//
+// This is a hack -- it first serializes the element, then uses xml.Unmarshal.
+func unmarshalEtreeHack(el *etree.Element, v interface{}) error {
+ doc := etree.NewDocument()
+ doc.SetRoot(el)
+ buf, err := doc.WriteToBytes()
+ if err != nil {
+ return err
+ }
+ return xml.Unmarshal(buf, v)
+}
+
+// MakeResponse creates and assigns a new SAML response in Response. `Assertion` must
+// be non-nill. If MarshalAssertion() has not been called, this function calls it for
+// you.
+func (req *IdpAuthnRequest) MakeResponse() error {
+ if req.AssertionBuffer == nil {
+ if err := req.MarshalAssertion(); err != nil {
+ return err
+ }
+ }
+ req.Response = &Response{
+ Destination: req.ACSEndpoint.Location,
+ ID: fmt.Sprintf("id-%x", randomBytes(20)),
+ InResponseTo: req.Request.ID,
+ IssueInstant: TimeNow(),
+ Version: "2.0",
+ Issuer: &Issuer{
+ Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
+ Value: req.IDP.MetadataURL,
+ },
+ Status: &Status{
+ StatusCode: StatusCode{
+ Value: StatusSuccess,
+ },
+ },
+ EncryptedAssertion: &EncryptedAssertion{
+ EncryptedData: req.AssertionBuffer,
+ },
+ }
+ return nil
}
diff --git a/identity_provider_test.go b/identity_provider_test.go
index 8ae9627b..fcd9b536 100644
--- a/identity_provider_test.go
+++ b/identity_provider_test.go
@@ -4,14 +4,15 @@ import (
"encoding/base64"
"encoding/xml"
"fmt"
+ "math/rand"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"time"
- "github.com/crewjam/go-xmlsec"
"github.com/crewjam/saml/testsaml"
+ "github.com/crewjam/saml/xmlenc"
"github.com/dgrijalva/jwt-go"
. "gopkg.in/check.v1"
)
@@ -35,7 +36,8 @@ func (test *IdentityProviderTest) SetUpTest(c *C) {
return rv
}
jwt.TimeFunc = TimeNow
- RandReader = &testRandomReader{}
+ RandReader = &testRandomReader{} // TODO(ross): remove this and use the below generator
+ xmlenc.RandReader = rand.New(rand.NewSource(0)) // deterministic random numbers for tests
//test.AuthnRequest = `https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO?RelayState=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmkiOiIvIn0.eoUmy2fQduAz--6N82xIOmufY1ZZeRi5x--B7m1pNIY&SAMLRequest=lJJBj9MwEIX%2FSuR7Yzt10sZKIpWtkCotsGqB%2B5BMW4vELp4JsP8et4DYE5Tr%2BPnN957dbGY%2B%2Bz1%2BmZE4%2Bz6NnloxR28DkCPrYUKy3NvD5s2jLXJlLzFw6MMosg0RRnbBPwRP84TxgPGr6%2FHD%2FrEVZ%2BYLWSl1WVXaGJP7UwyfcxckwTQWEnoS2TbtdB6uHn9uuOGSczqgs%2FuUh3i6DmTaenQjyitGIfc4uIg9y8Phnch221a4YVFjpVflcqgM1sUajiWsYGk01KujKVRfJyHRjDtPDJ5bUShdLrReLNX7QtmysrrMK6Pqem3MeqFKq5TInn6lfeX84PypFSL7iJFuwKkN0TU303hPc%2FC7L5G9DnEC%2Frv8OkmxjjepRc%2BOn0X3r14nZBiAoZE%2FwbrmbfLZbZ%2FC6Prn%2F3zgcQzfHiICYys4zii6%2B4E5gieXsBv5kqBr5Msf1%2F0IAAD%2F%2Fw%3D%3D`
//test.SamlResponse = "https://idp.testshib.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"
@@ -443,18 +445,7 @@ func (test *IdentityProviderTest) TestMakeAssertion(c *C) {
Format: "XXX",
Value: "https://idp.example.com/saml/metadata",
},
- Signature: &xmlsec.Signature{
- CanonicalizationMethod: xmlsec.Method{Algorithm: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"},
- SignatureMethod: xmlsec.Method{Algorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1"},
- ReferenceTransforms: []xmlsec.Method{
- {Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"},
- },
- DigestMethod: xmlsec.Method{Algorithm: "http://www.w3.org/2000/09/xmldsig#sha1"},
- DigestValue: "",
- SignatureValue: "",
- KeyName: "",
- X509Certificate: &xmlsec.SignatureX509Data{X509Certificate: "MIIB7zCCAVgCCQDFzbKIp7b3MTANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCR0ExDDAKBgNVBAoMA2ZvbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTEzMTAwMjAwMDg1MVoXDTE0MTAwMjAwMDg1MVowPDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkdBMQwwCgYDVQQKDANmb28xEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1PMHYmhZj308kWLhZVT4vOulqx/9ibm5B86fPWwUKKQ2i12MYtz07tzukPymisTDhQaqyJ8Kqb/6JjhmeMnEOdTvSPmHO8m1ZVveJU6NoKRn/mP/BD7FW52WhbrUXLSeHVSKfWkNk6S4hk9MV9TswTvyRIKvRsw0X/gfnqkroJcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCMMlIO+GNcGekevKgkakpMdAqJfs24maGb90DvTLbRZRD7Xvn1MnVBBS9hzlXiFLYOInXACMW5gcoRFfeTQLSouMM8o57h0uKjfTmuoWHLQLi6hnF+cvCsEFiJZ4AbF+DgmO6TarJ8O05t8zvnOwJlNCASPZRH/JmF8tX0hoHuAQ=="},
- },
+ Signature: nil,
Subject: &Subject{
NameID: &NameID{Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", NameQualifier: "https://idp.example.com/saml/metadata", SPNameQualifier: "https://sp.example.com/saml2/metadata", Value: ""},
SubjectConfirmation: &SubjectConfirmation{
@@ -618,9 +609,7 @@ func (test *IdentityProviderTest) TestMarshalAssertion(c *C) {
err = req.MarshalAssertion()
c.Assert(err, IsNil)
- // TODO(ross): we cannot trivially verify that the assertion was actually marshalled correctly because
- // there is randomness in the xmlsec.Encrypt()
- // c.Assert(string(req.AssertionBuffer), Equals, "XXX")
+ c.Assert(string(req.AssertionBuffer), Equals, "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")
}
func (test *IdentityProviderTest) TestMakeResponse(c *C) {
@@ -694,10 +683,11 @@ func (test *IdentityProviderTest) TestWriteResponse(c *C) {
Response: &Response{ID: "THIS_IS_THE_SAML_RESPONSE"},
}
req.HTTPRequest, _ = http.NewRequest("POST", "http://idp.example.com/saml/sso", nil)
- req.Validate()
+ err := req.Validate()
+ c.Assert(err, IsNil)
w := httptest.NewRecorder()
- err := req.WriteResponse(w)
+ err = req.WriteResponse(w)
c.Assert(err, IsNil)
c.Assert(w.Code, Equals, 200)
c.Assert(string(w.Body.Bytes()), Equals, "
")
diff --git a/samlsp/middleware_test.go b/samlsp/middleware_test.go
index 24b8a39f..b165ea2e 100644
--- a/samlsp/middleware_test.go
+++ b/samlsp/middleware_test.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/dgrijalva/jwt-go"
+ dsig "github.com/russellhaering/goxmldsig"
. "gopkg.in/check.v1"
"github.com/crewjam/saml"
@@ -53,6 +54,7 @@ func (test *MiddlewareTest) SetUpTest(c *C) {
return rv
}
jwt.TimeFunc = saml.TimeNow
+ saml.Clock = dsig.NewFakeClockAt(saml.TimeNow())
saml.RandReader = &testRandomReader{}
test.AuthnRequest = `https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO?RelayState=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmkiOiIvIn0.eoUmy2fQduAz--6N82xIOmufY1ZZeRi5x--B7m1pNIY&SAMLRequest=lJJBj9MwEIX%2FSuR7Yzt10sZKIpWtkCotsGqB%2B5BMW4vELp4JsP8et4DYE5Tr%2BPnN957dbGY%2B%2Bz1%2BmZE4%2Bz6NnloxR28DkCPrYUKy3NvD5s2jLXJlLzFw6MMosg0RRnbBPwRP84TxgPGr6%2FHD%2FrEVZ%2BYLWSl1WVXaGJP7UwyfcxckwTQWEnoS2TbtdB6uHn9uuOGSczqgs%2FuUh3i6DmTaenQjyitGIfc4uIg9y8Phnch221a4YVFjpVflcqgM1sUajiWsYGk01KujKVRfJyHRjDtPDJ5bUShdLrReLNX7QtmysrrMK6Pqem3MeqFKq5TInn6lfeX84PypFSL7iJFuwKkN0TU303hPc%2FC7L5G9DnEC%2Frv8OkmxjjepRc%2BOn0X3r14nZBiAoZE%2FwbrmbfLZbZ%2FC6Prn%2F3zgcQzfHiICYys4zii6%2B4E5gieXsBv5kqBr5Msf1%2F0IAAD%2F%2Fw%3D%3D`
diff --git a/schema.go b/schema.go
index a8e7e251..5d92d68e 100644
--- a/schema.go
+++ b/schema.go
@@ -3,8 +3,6 @@ package saml
import (
"encoding/xml"
"time"
-
- "github.com/crewjam/go-xmlsec"
)
// AuthnRequest represents the SAML object of the same name, a request from a service provider
@@ -24,10 +22,10 @@ type AuthnRequest struct {
// and is typically accompanied by the AssertionConsumerServiceURL attribute.
ProtocolBinding string `xml:",attr"`
- Version string `xml:",attr"`
- Issuer Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
- Signature *xmlsec.Signature `xml:"http://www.w3.org/2000/09/xmldsig# Signature"`
- NameIDPolicy NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"`
+ Version string `xml:",attr"`
+ Issuer Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
+ Signature *Signature `xml:"http://www.w3.org/2000/09/xmldsig# Signature"`
+ NameIDPolicy NameIDPolicy `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"`
}
func (a *AuthnRequest) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
@@ -155,7 +153,7 @@ type Assertion struct {
IssueInstant time.Time `xml:",attr"`
Version string `xml:",attr"`
Issuer *Issuer `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"`
- Signature *xmlsec.Signature
+ Signature *Signature
Subject *Subject
Conditions *Conditions
AuthnStatement *AuthnStatement
diff --git a/service_provider.go b/service_provider.go
index e69eda8a..b41ed0e1 100644
--- a/service_provider.go
+++ b/service_provider.go
@@ -3,9 +3,12 @@ package saml
import (
"bytes"
"compress/flate"
+ "crypto/rsa"
+ "crypto/x509"
"encoding/base64"
"encoding/pem"
"encoding/xml"
+ "errors"
"fmt"
"html/template"
"net/http"
@@ -13,7 +16,9 @@ import (
"regexp"
"time"
- xmlsec "github.com/crewjam/go-xmlsec"
+ "github.com/beevik/etree"
+ "github.com/crewjam/saml/xmlenc"
+ dsig "github.com/russellhaering/goxmldsig"
)
type NameIDFormat string
@@ -170,38 +175,42 @@ func (sp *ServiceProvider) GetSSOBindingLocation(binding string) string {
// getIDPSigningCert returns the certificate which we can use to verify things
// signed by the IDP in PEM format, or nil if no such certificate is found.
-func (sp *ServiceProvider) getIDPSigningCert() []byte {
- cert := ""
-
+func (sp *ServiceProvider) getIDPSigningCert() (*x509.Certificate, error) {
+ certStr := ""
for _, keyDescriptor := range sp.IDPMetadata.IDPSSODescriptor.KeyDescriptor {
if keyDescriptor.Use == "signing" {
- cert = keyDescriptor.KeyInfo.Certificate
+ certStr = keyDescriptor.KeyInfo.Certificate
break
}
}
// If there are no explicitly signing certs, just return the first
// non-empty cert we find.
- if cert == "" {
+ if certStr == "" {
for _, keyDescriptor := range sp.IDPMetadata.IDPSSODescriptor.KeyDescriptor {
if keyDescriptor.Use == "" && keyDescriptor.KeyInfo.Certificate != "" {
- cert = keyDescriptor.KeyInfo.Certificate
+ certStr = keyDescriptor.KeyInfo.Certificate
break
}
}
}
- if cert == "" {
- return nil
+ if certStr == "" {
+ return nil, errors.New("cannot find any signing certificate in the IDP SSO descriptor")
+ }
+
+ // cleanup whitespace
+ certStr = regexp.MustCompile(`\s+`).ReplaceAllString(certStr, "")
+ certBytes, err := base64.StdEncoding.DecodeString(certStr)
+ if err != nil {
+ return nil, err
}
- // cleanup whitespace and re-encode a PEM
- cert = regexp.MustCompile("\\s+").ReplaceAllString(cert, "")
- certBytes, _ := base64.StdEncoding.DecodeString(cert)
- certBytes = pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: certBytes})
- return certBytes
+ parsedCert, err := x509.ParseCertificate(certBytes)
+ if err != nil {
+ return nil, err
+ }
+ return parsedCert, nil
}
// MakeAuthenticationRequest produces a new AuthnRequest object for idpURL.
@@ -385,43 +394,113 @@ func (sp *ServiceProvider) ParseResponse(req *http.Request, possibleRequestIDs [
var assertion *Assertion
if resp.EncryptedAssertion == nil {
- if err := xmlsec.Verify(sp.getIDPSigningCert(), rawResponseBuf,
- xmlsec.SignatureOptions{
- XMLID: []xmlsec.XMLIDOption{{
- ElementName: "Response",
- ElementNamespace: "urn:oasis:names:tc:SAML:2.0:protocol",
- AttributeName: "ID",
- }},
- }); err != nil {
+ cert, err := sp.getIDPSigningCert()
+ if err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+
+ certificateStore := dsig.MemoryX509CertificateStore{
+ Roots: []*x509.Certificate{cert},
+ }
+
+ validationContext := dsig.NewDefaultValidationContext(&certificateStore)
+ validationContext.IdAttribute = "ID"
+ if Clock != nil {
+ validationContext.Clock = Clock
+ }
+
+ doc := etree.NewDocument()
+ if err := doc.ReadFromBytes(rawResponseBuf); err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+
+ // TODO(ross): verify that the namespace is urn:oasis:names:tc:SAML:2.0:protocol
+ responseEl := doc.Root()
+ if responseEl.Tag != "Response" {
+ retErr.PrivateErr = fmt.Errorf("expected to find a response object, not %s", doc.Root().Tag)
+ return nil, retErr
+ }
+
+ el := doc.Root()
+ _, err = validationContext.Validate(el)
+ if err != nil {
retErr.PrivateErr = fmt.Errorf("failed to verify signature on response: %s", err)
return nil, retErr
}
+
assertion = resp.Assertion
}
// decrypt the response
if resp.EncryptedAssertion != nil {
- plaintextAssertion, err := xmlsec.Decrypt([]byte(sp.Key), resp.EncryptedAssertion.EncryptedData)
+ doc := etree.NewDocument()
+ if err := doc.ReadFromBytes(rawResponseBuf); err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+ el := doc.FindElement("//EncryptedAssertion/EncryptedData")
+ var key *rsa.PrivateKey
+ {
+ b, _ := pem.Decode([]byte(sp.Key))
+ if b == nil {
+ retErr.PrivateErr = errors.New("cannot decode key")
+ return nil, retErr
+ }
+ key, err = x509.ParsePKCS1PrivateKey(b.Bytes)
+ if err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+ }
+ plaintextAssertion, err := xmlenc.Decrypt(key, el)
if err != nil {
retErr.PrivateErr = fmt.Errorf("failed to decrypt response: %s", err)
return nil, retErr
}
retErr.Response = string(plaintextAssertion)
- if err := xmlsec.Verify(sp.getIDPSigningCert(), plaintextAssertion,
- xmlsec.SignatureOptions{
- XMLID: []xmlsec.XMLIDOption{{
- ElementName: "Assertion",
- ElementNamespace: "urn:oasis:names:tc:SAML:2.0:assertion",
- AttributeName: "ID",
- }},
- }); err != nil {
- retErr.PrivateErr = fmt.Errorf("failed to verify signature on response: %s", err)
- return nil, retErr
+ {
+ cert, err := sp.getIDPSigningCert()
+ if err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+ certificateStore := dsig.MemoryX509CertificateStore{
+ Roots: []*x509.Certificate{cert},
+ }
+
+ validationContext := dsig.NewDefaultValidationContext(&certificateStore)
+ validationContext.IdAttribute = "ID"
+ if Clock != nil {
+ validationContext.Clock = Clock
+ }
+
+ doc := etree.NewDocument()
+ if err := doc.ReadFromBytes(plaintextAssertion); err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
+
+ // TODO(ross): verify that the namespace is "urn:oasis:names:tc:SAML:2.0:assertion"
+ assertionEl := doc.Root()
+ if assertionEl.Tag != "Assertion" {
+ retErr.PrivateErr = fmt.Errorf("expected an Assertion element, not %s", assertionEl.Tag)
+ return nil, retErr
+ }
+ _, err = validationContext.Validate(assertionEl)
+ if err != nil {
+ retErr.PrivateErr = fmt.Errorf("failed to verify signature on response: %s", err)
+ return nil, retErr
+ }
}
assertion = &Assertion{}
- xml.Unmarshal(plaintextAssertion, assertion)
+ if err := xml.Unmarshal(plaintextAssertion, assertion); err != nil {
+ retErr.PrivateErr = err
+ return nil, retErr
+ }
}
if err := sp.validateAssertion(assertion, possibleRequestIDs, now); err != nil {
diff --git a/service_provider_test.go b/service_provider_test.go
index e94cf1fd..c99199f7 100644
--- a/service_provider_test.go
+++ b/service_provider_test.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/crewjam/saml/testsaml"
+ dsig "github.com/russellhaering/goxmldsig"
. "gopkg.in/check.v1"
)
@@ -51,6 +52,8 @@ func (test *ServiceProviderTest) SetUpTest(c *C) {
rv, _ := time.Parse("Mon Jan 2 15:04:05 MST 2006", "Mon Dec 1 01:57:09 UTC 2015")
return rv
}
+ Clock = dsig.NewFakeClockAt(TimeNow())
+
RandReader = &testRandomReader{}
test.AuthnRequest = `https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO?RelayState=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmkiOiIvIn0.eoUmy2fQduAz--6N82xIOmufY1ZZeRi5x--B7m1pNIY&SAMLRequest=lJJBj9MwEIX%2FSuR7Yzt10sZKIpWtkCotsGqB%2B5BMW4vELp4JsP8et4DYE5Tr%2BPnN957dbGY%2B%2Bz1%2BmZE4%2Bz6NnloxR28DkCPrYUKy3NvD5s2jLXJlLzFw6MMosg0RRnbBPwRP84TxgPGr6%2FHD%2FrEVZ%2BYLWSl1WVXaGJP7UwyfcxckwTQWEnoS2TbtdB6uHn9uuOGSczqgs%2FuUh3i6DmTaenQjyitGIfc4uIg9y8Phnch221a4YVFjpVflcqgM1sUajiWsYGk01KujKVRfJyHRjDtPDJ5bUShdLrReLNX7QtmysrrMK6Pqem3MeqFKq5TInn6lfeX84PypFSL7iJFuwKkN0TU303hPc%2FC7L5G9DnEC%2Frv8OkmxjjepRc%2BOn0X3r14nZBiAoZE%2FwbrmbfLZbZ%2FC6Prn%2F3zgcQzfHiICYys4zii6%2B4E5gieXsBv5kqBr5Msf1%2F0IAAD%2F%2Fw%3D%3D`
@@ -189,6 +192,7 @@ func (test *ServiceProviderTest) TestCanHandleOneloginResponse(c *C) {
rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 17:53:12 UTC 2016")
return rv
}
+ Clock = dsig.NewFakeClockAt(TimeNow())
SamlResponse := `PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJwZnhlZDg4YzQzZC02NTA0LWUxZjEtNWFmMC00MGJlN2YyNzlmYzUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjExWiIgRGVzdGluYXRpb249Imh0dHBzOi8vMjllZTZkMmUubmdyb2suaW8vc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iaWQtZDQwYzE1YzEwNGI1MjY5MWVjY2YwYTJhNWM4YTE1NTk1YmU3NTQyMyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzUwMzk4Mzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+PGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhlZDg4YzQzZC02NTA0LWUxZjEtNWFmMC00MGJlN2YyNzlmYzUiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPlNWQWFRZzh2bW1TUUw2L1lCbVMyeWRLUlA3ST08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+c0JlVFZQMGJab1BSK2JmeUFrVnY2STNDVjdZOFhxbkoycjhmMStXbXIyZ0ZnblJGODVOdnZTUCtyMUJvN250dU9zd080ZkI0Uks0SHlTYnlsZzRiS0hLSDE5WDkxaFZBekpTeXNmbVMvZDV3ZzFDZmlXV3Q1UzJIQTUwOHRoWHVabndHM1h6NktuV0s4a1JkeDFkYytZUldnYUZ5ZDRnTEc5YUJUc1hPWjd2eC83UDRicnpORW00d1A5LzB0dWZ4Rytuc1k2RHB3bkVHQ2psK1ZVS3BnekVxd05OalFxWUZZU0FYRWsrVnQrWDNjMmQwSElyWlF2WW5OaDAyS3h1d1ZCVGhuM01helFOYU54Qy9zeWYza0RRQ1JyWkNZbytZdER1ZHpKVTlwM0EwWVhIVFFjc2RldHNIWlhDTWozbXV2emMwbUVCbHc0TGJjaEttbmJ5Wm1nPT08L2RzOlNpZ25hdHVyZVZhbHVlPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUVDRENDQXZDZ0F3SUJBZ0lVWHVuMDhDc2xMUldTTHFObkRFMU50R0plZmwwd0RRWUpLb1pJaHZjTkFRRUZCUUF3VXpFTE1Ba0dBMVVFQmhNQ1ZWTXhEREFLQmdOVkJBb01BMk4wZFRFVk1CTUdBMVVFQ3d3TVQyNWxURzluYVc0Z1NXUlFNUjh3SFFZRFZRUUREQlpQYm1WTWIyZHBiaUJCWTJOdmRXNTBJRE15TmpFME1CNFhEVEV6TURrek1ERTVNelUwTkZvWERURTRNVEF3TVRFNU16VTBORm93VXpFTE1Ba0dBMVVFQmhNQ1ZWTXhEREFLQmdOVkJBb01BMk4wZFRFVk1CTUdBMVVFQ3d3TVQyNWxURzluYVc0Z1NXUlFNUjh3SFFZRFZRUUREQlpQYm1WTWIyZHBiaUJCWTJOdmRXNTBJRE15TmpFME1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBME9HOFY4bWhvdmtqNHJoR2hqcmJFeFJZYnpLVjJaeGZ2R2ZFR1hHVXZYYzZEcWVqWUVkaFoybUlmQ0RvamhRamswQnl3aWlyQUtNT3QxR051SDdhV0lFNDdEMGV3dEs1eWxFQW03ZVZtb1k0a3hMQ2FXNXdZckMxU3pNbnBlaXRVeHF2c2JuS3ozalVLWUhSZ2dwZnZWajRzaUhEWmVJWmE5YTVyVXZwTW5uYk9vRmlaQ0lFTnBxM1RDMzNpdk9TWmhFTlJUem12bms1R0RvTEh3LzhxQWdRaXlUM0QxeENrU0JiNTRQSGdrUTVScTFvZExNL2hKK0wwanpDVVFINGd4cFdsRUFhYjRLOXM4ZnBCVUJCaDVnbUpDWWk4VWJJbGhxTzhOMm15bnVtMzNCVS92SjNQbmF3VDRZWWtUd1JVeDZZKzNmcG1SQkhxbDRoODNTTWV3SURBUUFCbzRIVE1JSFFNQXdHQTFVZEV3RUIvd1FDTUFBd0hRWURWUjBPQkJZRUZPZkZGakhGajlhNnhwbmdiMTFycmhnTWU5QXJNSUdRQmdOVkhTTUVnWWd3Z1lXQUZPZkZGakhGajlhNnhwbmdiMTFycmhnTWU5QXJvVmVrVlRCVE1Rc3dDUVlEVlFRR0V3SlZVekVNTUFvR0ExVUVDZ3dEWTNSMU1SVXdFd1lEVlFRTERBeFBibVZNYjJkcGJpQkpaRkF4SHpBZEJnTlZCQU1NRms5dVpVeHZaMmx1SUVGalkyOTFiblFnTXpJMk1UU0NGRjdwOVBBckpTMFZraTZqWnd4TlRiUmlYbjVkTUE0R0ExVWREd0VCL3dRRUF3SUhnREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBTWdsbjROUE1RbjhHeXZxOENUUCtjMmU2Q1V6Y3ZSRUtuVGhqeFQ5V2N2VjFaVlhNQk5QbTRjVHFUMzYxRWRMelk1eVdMVVdYZDRBdkZuY2lxQjNNSFlhMm5xVG1udkxnbWhrV2UraGRGb05lNStJQThBeEduK25xVUlTbXlCZUN4dVVVQWJSTXVvd2lBcndISXB6cEV5UklZZFNaUk5GMGR2Z2lQWXlyL01pUFhJY3pwSDVuTGt2YkxwY0FGK1I4Wmg5bndZMGcxSlZ5YzZBQjZqN1lleHVVUVpwSEg0czBWZHgvbldtcmNGZUxaS0NUeGNhaEh2VTUwZTF5S1g1dGhmVmFKcUk4UVE3eFp4eXUwVFRzaWFYMHV3NTFKUE96UHVBUHBoMHo2eG9TOW9ZeHV6WjF5OXNOSEg2a0g4R0ZudlMyTXF5SGlOejBoMFNxL3E2bit3PT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJBZDk0NWFlZGEzOGE1MDhmOGZhYzliYzk2MTNkNTk2NDJjMGQyZDhjYiIgSXNzdWVJbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjExWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzUwMzk4Mzwvc2FtbDpJc3N1ZXI+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnJvc3NAa25kci5vcmc8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTYtMDEtMDVUMTc6NTY6MTFaIiBSZWNpcGllbnQ9Imh0dHBzOi8vMjllZTZkMmUubmdyb2suaW8vc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iaWQtZDQwYzE1YzEwNGI1MjY5MWVjY2YwYTJhNWM4YTE1NTk1YmU3NTQyMyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE2LTAxLTA1VDE3OjUwOjExWiIgTm90T25PckFmdGVyPSIyMDE2LTAxLTA1VDE3OjU2OjExWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovLzI5ZWU2ZDJlLm5ncm9rLmlvL3NhbWwvbWV0YWRhdGE8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE2LTAxLTA1VDE3OjUzOjEwWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNi0wMS0wNlQxNzo1MzoxMVoiIFNlc3Npb25JbmRleD0iX2ViZGNiZTgwLTk1ZmYtMDEzMy1kODcxLTM4Y2EzYTY2MmYxYyI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyIgTmFtZT0iVXNlci5lbWFpbCI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cm9zc0BrbmRyLm9yZzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIiBOYW1lPSJtZW1iZXJPZiI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyIvPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiIE5hbWU9IlVzZXIuTGFzdE5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPktpbmRlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIiBOYW1lPSJQZXJzb25JbW11dGFibGVJRCI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyIvPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiIE5hbWU9IlVzZXIuRmlyc3ROYW1lIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5Sb3NzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+Cgo=`
test.IDPMetadata = `
@@ -311,6 +315,7 @@ func (test *ServiceProviderTest) TestCanHandlePlaintextResponse(c *C) {
rv, _ := time.Parse("Mon Jan 2 15:04:05 UTC 2006", "Tue Jan 5 16:55:39 UTC 2016")
return rv
}
+ Clock = dsig.NewFakeClockAt(TimeNow())
SamlResponse := "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHNhbWwycDpSZXNwb25zZSB4bWxuczpzYW1sMnA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgRGVzdGluYXRpb249Imh0dHBzOi8vMjllZTZkMmUubmdyb2suaW8vc2FtbC9hY3MiIElEPSJfZmMxNDFkYjI4NGViMzA5ODYwNTM1MWJkZTRkOWJlNTkiIEluUmVzcG9uc2VUbz0iaWQtZmQ0MTlhNWFiMDQ3MjY0NTQyN2Y4ZTA3ZDg3YTNhNWRkMGIyZTlhNiIgSXNzdWVJbnN0YW50PSIyMDE2LTAxLTA1VDE2OjU1OjM5LjM0OFoiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3VlciB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+aHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vc2FtbDI/aWRwaWQ9QzAyZGZsMXIxPC9zYW1sMjpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPjxkczpSZWZlcmVuY2UgVVJJPSIjX2ZjMTQxZGIyODRlYjMwOTg2MDUzNTFiZGU0ZDliZTU5Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+bHRNRUJLRzRZNVNLeERScUxHR2xFSGtPd3hla3dQOStybnA2WEtqdkJxVT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+SFBVV0pmYTlqdVdiKy9wZ0YrQklsc2pycE40NkE0RUNiT3hNdXhmWEFRUCtrMU5KMG9EdTJKYk1pZHpmclJBRkRHMjZaNjZWQWtkcwpBRmYwVFgzMWxvVjdaU0tGS0lVY0tuaFlXTHFuUTZLbmRydnJLbzF5UUhzUkdUNzJoVjl3SWdqTFRTZm5FV3QvOEMxaERQQi96R0txClhXZ3VvNFFHYlZUeVBoVVh3eEFzRmxBNjFDdkE5Q1pzU2xpeHBaY2pOVjUyQmMydzI5RUNRNStBcHZGWjVqRU1EN1JiQTVpMzdBbmgKUVBCeVYrZXo4ZU9Yc0hvQlhsR0drTjlDR201MFR6djZ3TW12WkdkT2pKWlhvRWZGUTA4UFJwbE9DQWpxSjM3QnhpWitLZWtUaE1KYgorelowcG1yeWR2V3lONEMzNWcycGVueGw2QUtxYnhMaXlJUkVaZz09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlTdWJqZWN0TmFtZT5TVD1DYWxpZm9ybmlhLEM9VVMsT1U9R29vZ2xlIEZvciBXb3JrLENOPUdvb2dsZSxMPU1vdW50YWluIFZpZXcsTz1Hb29nbGUgSW5jLjwvZHM6WDUwOVN1YmplY3ROYW1lPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJRGREQ0NBbHlnQXdJQkFnSUdBVklTbElsWU1BMEdDU3FHU0liM0RRRUJDd1VBTUhzeEZEQVNCZ05WQkFvVEMwZHZiMmRzWlNCSgpibU11TVJZd0ZBWURWUVFIRXcxTmIzVnVkR0ZwYmlCV2FXVjNNUTh3RFFZRFZRUURFd1pIYjI5bmJHVXhHREFXQmdOVkJBc1REMGR2CmIyZHNaU0JHYjNJZ1YyOXlhekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXdIaGNOTVRZd01UQTEKTVRZeE56UTVXaGNOTWpFd01UQXpNVFl4TnpRNVdqQjdNUlF3RWdZRFZRUUtFd3RIYjI5bmJHVWdTVzVqTGpFV01CUUdBMVVFQnhNTgpUVzkxYm5SaGFXNGdWbWxsZHpFUE1BMEdBMVVFQXhNR1IyOXZaMnhsTVJnd0ZnWURWUVFMRXc5SGIyOW5iR1VnUm05eUlGZHZjbXN4CkN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEEKTUlJQkNnS0NBUUVBbVVmTVVQeEhTWS9aWVo4OGZVR0FsaFVQNE5pN3pqNTR2c3JzUERBNFVoUWlSZUVEUnVuTjFxM09Ic1NoUm9uZwpnZDRMdkE4My9lLzNwbS9WNjBSNnZ5TWZqM1ovSUdXWStlWjk3RUpVdmprdHQrVlJvQWkyNm9lWTlaVzZTODV5YXB2QTNpdWhFd0lRCk9jdVBtMU9xUlEweVE0c1VEK1d0TC9RU21sWXZEUDVUSzFkNndoVGlzTnNLU3FlRlpDYi9zOU9YMDFVZXhXMUJ1RE9MZVZ0MHJDVzEKa1JOY0JCTERtZDRobkRQMFNWcTduTGhORllYajJFYTZXc3lSQUl2Y2hhVUd5K0ltYTJva1htOTVZZTlrbjhlMTE4aS81clJleUtDbQpCbHNrTWtOYUE0S1dLdklRbTNEZGpnT05nRWQwSXZLRXh5THdZN2E1L0pJVXZCaGI5UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBCkE0SUJBUUFVRExNbkhwemZwNFNoZEJxQ3JlVzQ4ZjhyVTk0cTJxTXdyVStXNkRrT3JHSlRBU1ZHUzlSaWIvTUtBaVJZT21xbGFxRVkKTlA1N3BDckUvblJCNUZWZEUrQWxTeC9mUjNraHNRM3pmLzRkWXMyMVN2R2YrT2FzOTlYRWJXZlYwT21QTVltM0lyU0NPQkVWMzF3aAo0MXFSYzVRTG5SK1h1dE5QYlNCTit0bitnaVJDTEdDQkxlODFvVnc0ZlJHUWJna2Q4N3JmTE95M0c2MzBJNnMvSjVmZUZGVVQ4ZDdoCjltcE9lT3FMQ1ByS3BxK3dJM2FEM2xmNG1YcUtJRE5pSEhSb05sNjdBTlB1L04zZk5VMUhwbFZ0dnJvVnBpTnA4N2ZyZ2RsS1RFY2cKUFVrZmJhWUhRR1A2SVMwbHplQ2VEWDB3YWIzcVJvaDcvakp0NS9CUjhJd2Y8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDJwOlN0YXR1cz48c2FtbDJwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbDJwOlN0YXR1cz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iXzllNzY0OTUyZTZhMjYxZTE5NDA5YTM4MjU1ODEwMzNkIiBJc3N1ZUluc3RhbnQ9IjIwMTYtMDEtMDVUMTY6NTU6MzkuMzQ4WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyPmh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL3NhbWwyP2lkcGlkPUMwMmRmbDFyMTwvc2FtbDI6SXNzdWVyPjxzYW1sMjpTdWJqZWN0PjxzYW1sMjpOYW1lSUQ+cm9zc0BvY3RvbGFicy5pbzwvc2FtbDI6TmFtZUlEPjxzYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iaWQtZmQ0MTlhNWFiMDQ3MjY0NTQyN2Y4ZTA3ZDg3YTNhNWRkMGIyZTlhNiIgTm90T25PckFmdGVyPSIyMDE2LTAxLTA1VDE3OjAwOjM5LjM0OFoiIFJlY2lwaWVudD0iaHR0cHM6Ly8yOWVlNmQyZS5uZ3Jvay5pby9zYW1sL2FjcyIvPjwvc2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWwyOlN1YmplY3Q+PHNhbWwyOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE2LTAxLTA1VDE2OjUwOjM5LjM0OFoiIE5vdE9uT3JBZnRlcj0iMjAxNi0wMS0wNVQxNzowMDozOS4zNDhaIj48c2FtbDI6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDI6QXVkaWVuY2U+aHR0cHM6Ly8yOWVlNmQyZS5uZ3Jvay5pby9zYW1sL21ldGFkYXRhPC9zYW1sMjpBdWRpZW5jZT48L3NhbWwyOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sMjpDb25kaXRpb25zPjxzYW1sMjpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWwyOkF0dHJpYnV0ZSBOYW1lPSJwaG9uZSIvPjxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0iYWRkcmVzcyIvPjxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0iam9iVGl0bGUiLz48c2FtbDI6QXR0cmlidXRlIE5hbWU9ImZpcnN0TmFtZSI+PHNhbWwyOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOmFueVR5cGUiPlJvc3M8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjxzYW1sMjpBdHRyaWJ1dGUgTmFtZT0ibGFzdE5hbWUiPjxzYW1sMjpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czphbnlUeXBlIj5LaW5kZXI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sMjpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTYtMDEtMDVUMTY6NTU6MzguMDAwWiIgU2Vzc2lvbkluZGV4PSJfOWU3NjQ5NTJlNmEyNjFlMTk0MDlhMzgyNTU4MTAzM2QiPjxzYW1sMjpBdXRobkNvbnRleHQ+PHNhbWwyOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOnVuc3BlY2lmaWVkPC9zYW1sMjpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWwyOkF1dGhuQ29udGV4dD48L3NhbWwyOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg=="
test.IDPMetadata = `
@@ -591,13 +596,18 @@ func (test *ServiceProviderTest) TestInvalidResponses(c *C) {
s.Key = "invalid"
req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString([]byte(test.SamlResponse)))
_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
- c.Assert(err.(*InvalidResponseError).PrivateErr, ErrorMatches, "failed to decrypt response: .*PEM_read_bio_PrivateKey.*")
+ c.Assert(err.(*InvalidResponseError).PrivateErr, ErrorMatches, "cannot decode key")
s.Key = test.Key
s.IDPMetadata.IDPSSODescriptor.KeyDescriptor[0].KeyInfo.Certificate = "invalid"
req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString([]byte(test.SamlResponse)))
_, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
- c.Assert(err.(*InvalidResponseError).PrivateErr, ErrorMatches, "failed to verify signature on response: .*xmlSecOpenSSLAppKeyLoadMemory.*")
+ c.Assert(err.(*InvalidResponseError).PrivateErr, ErrorMatches, "illegal base64 data at input byte 4")
+
+ s.IDPMetadata.IDPSSODescriptor.KeyDescriptor[0].KeyInfo.Certificate = "aW52YWxpZA=="
+ req.PostForm.Set("SAMLResponse", base64.StdEncoding.EncodeToString([]byte(test.SamlResponse)))
+ _, err = s.ParseResponse(&req, []string{"id-9e61753d64e928af5a7a341a97f420c9"})
+ c.Assert(err.(*InvalidResponseError).PrivateErr, ErrorMatches, "asn1: structure error: tags don't match .*")
}
func (test *ServiceProviderTest) TestInvalidAssertions(c *C) {
diff --git a/signature.go b/signature.go
new file mode 100644
index 00000000..1702f94e
--- /dev/null
+++ b/signature.go
@@ -0,0 +1,69 @@
+package saml
+
+import (
+ "encoding/base64"
+ "encoding/pem"
+ "encoding/xml"
+)
+
+// Method is part of Signature.
+type Method struct {
+ Algorithm string `xml:",attr"`
+}
+
+// Signature is a model for the Signature object specified by XMLDSIG. This is
+// convenience object when constructing XML that you'd like to sign. For example:
+//
+// type Foo struct {
+// Stuff string
+// Signature Signature
+// }
+//
+// f := Foo{Suff: "hello"}
+// f.Signature = DefaultSignature()
+// buf, _ := xml.Marshal(f)
+// buf, _ = Sign(key, buf)
+//
+type Signature struct {
+ XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# Signature"`
+
+ CanonicalizationMethod Method `xml:"SignedInfo>CanonicalizationMethod"`
+ SignatureMethod Method `xml:"SignedInfo>SignatureMethod"`
+ ReferenceTransforms []Method `xml:"SignedInfo>Reference>Transforms>Transform"`
+ DigestMethod Method `xml:"SignedInfo>Reference>DigestMethod"`
+ DigestValue string `xml:"SignedInfo>Reference>DigestValue"`
+ SignatureValue string `xml:"SignatureValue"`
+ KeyName string `xml:"KeyInfo>KeyName,omitempty"`
+ X509Certificate *SignatureX509Data `xml:"KeyInfo>X509Data,omitempty"`
+}
+
+// SignatureX509Data represents the element of
+type SignatureX509Data struct {
+ X509Certificate string `xml:"X509Certificate,omitempty"`
+}
+
+// DefaultSignature returns a Signature struct that uses the default c14n and SHA1 settings.
+func DefaultSignature(pemEncodedPublicKey []byte) Signature {
+ // xmlsec wants the key to be base64-encoded but *not* wrapped with the
+ // PEM flags
+ pemBlock, _ := pem.Decode(pemEncodedPublicKey)
+ certStr := base64.StdEncoding.EncodeToString(pemBlock.Bytes)
+
+ return Signature{
+ CanonicalizationMethod: Method{
+ Algorithm: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
+ },
+ SignatureMethod: Method{
+ Algorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ },
+ ReferenceTransforms: []Method{
+ Method{Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"},
+ },
+ DigestMethod: Method{
+ Algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
+ },
+ X509Certificate: &SignatureX509Data{
+ X509Certificate: certStr,
+ },
+ }
+}
diff --git a/time.go b/time.go
index 4f0ccb53..dd334163 100644
--- a/time.go
+++ b/time.go
@@ -11,7 +11,11 @@ func (m RelaxedTime) MarshalText() ([]byte, error) {
// other applications to handle time resolution finer than a millisecond.
//
// The time MUST be expressed in UTC.
- return []byte(time.Time(m).UTC().Format(timeFormat)), nil
+ return []byte(m.String()), nil
+}
+
+func (m RelaxedTime) String() string {
+ return time.Time(m).UTC().Format(timeFormat)
}
func (m *RelaxedTime) UnmarshalText(text []byte) error {
diff --git a/util.go b/util.go
index 7c7d961c..5c5e2b24 100644
--- a/util.go
+++ b/util.go
@@ -3,12 +3,18 @@ package saml
import (
"crypto/rand"
"time"
+
+ dsig "github.com/russellhaering/goxmldsig"
)
// TimeNow is a function that returns the current time. The default
// value is time.Now, but it can be replaced for testing.
var TimeNow = func() time.Time { return time.Now().UTC() }
+// Clock is assigned to dsig validation and signing contexts if it is
+// not nil, otherwise the default clock is used.
+var Clock *dsig.Clock
+
// RandReader is the io.Reader that produces cryptographically random
// bytes when they are need by the library. The default value is
// rand.Reader, but it can be replaced for testing.