From 56d25ca410703d7bdaf654765dd9384741df8f08 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 12:15:54 -0200 Subject: [PATCH 01/15] new func to decrypt xml response from IDP --- xmlsec.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/xmlsec.go b/xmlsec.go index 484a940..f5b4cd8 100644 --- a/xmlsec.go +++ b/xmlsec.go @@ -27,6 +27,24 @@ func SignResponse(xml string, privateKeyPath string) (string, error) { return sign(xml, privateKeyPath, xmlResponseID) } +// DecryptResponse decrypt a SAML 2.0 xml using his private key +func DecryptResponse(xml string, privateKeyPath string) (string, error) { + return decrypt(xml, privateKeyPath) +} + +func decrypt(xml string, privateKeyPath string) (string, error) { + + tempfile, err := ioutil.TempFile(os.TempDir(), "saml-resp") + ioutil.WriteFile(tempfile.Name(), []byte(xml), 0644) + plainXML, err := exec.Command("xmlsec1", "--decrypt", "--privkey-pem", privateKeyPath, tempfile.Name()).CombinedOutput() + defer deleteTempFile(tempfile.Name()) + if err != nil { + return "", errors.New(err.Error() + " : " + string(plainXML)) + } + + return strings.Trim(string(plainXML), "\n"), nil +} + func sign(xml string, privateKeyPath string, id string) (string, error) { samlXmlsecInput, err := ioutil.TempFile(os.TempDir(), "tmpgs") From 1c72609021c17a433bb3d02a88c59290e3a6cf89 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 12:16:44 -0200 Subject: [PATCH 02/15] add structs for xml encrypted fields --- types.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/types.go b/types.go index d32b091..f732da5 100644 --- a/types.go +++ b/types.go @@ -8,6 +8,7 @@ type AuthnRequest struct { SAML string `xml:"xmlns:saml,attr"` SAMLSIG string `xml:"xmlns:samlsig,attr"` ID string `xml:"ID,attr"` + Destination string `xml:"Destination,attr"` Version string `xml:"Version,attr"` ProtocolBinding string `xml:"ProtocolBinding,attr"` AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr"` @@ -23,7 +24,7 @@ type AuthnRequest struct { type Issuer struct { XMLName xml.Name - SAML string `xml:"xmlns:saml,attr"` + SAML string `xml:"xmlns:saml2,attr"` Url string `xml:",innerxml"` } @@ -68,9 +69,13 @@ type SignatureValue struct { type KeyInfo struct { XMLName xml.Name - X509Data X509Data `xml:",innerxml"` + X509Data X509Data } +type KeyInfoMain struct { + XMLName xml.Name `xml:"KeyInfo"` + EncryptedKey EncryptedKey `xml:"EncryptedKey,omitempty"` +} type CanonicalizationMethod struct { XMLName xml.Name Algorithm string `xml:"Algorithm,attr"` @@ -91,7 +96,7 @@ type SamlsigReference struct { type X509Data struct { XMLName xml.Name - X509Certificate X509Certificate `xml:",innerxml"` + X509Certificate X509Certificate } type Transforms struct { @@ -141,6 +146,8 @@ type Extensions struct { type SPSSODescriptor struct { XMLName xml.Name ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"` + AuthnRequestsSigned string `xml:"AuthnRequestsSigned,attr"` + WantAssertionsSigned string `xml:"WantAssertionsSigned,attr"` SigningKeyDescriptor KeyDescriptor EncryptionKeyDescriptor KeyDescriptor // SingleLogoutService SingleLogoutService `xml:"SingleLogoutService"` @@ -186,14 +193,47 @@ type Response struct { IssueInstant string `xml:"IssueInstant,attr"` InResponseTo string `xml:"InResponseTo,attr"` - Assertion Assertion `xml:"Assertion"` - Signature Signature `xml:"Signature"` - Issuer Issuer `xml:"Issuer"` - Status Status `xml:"Status"` + EncryptedAssertion EncryptedAssertion `xml:"EncryptedAssertion,omitempty"` + Assertion Assertion `xml:"Assertion,omitempty"` + Signature Signature `xml:"Signature"` + Issuer Issuer `xml:"Issuer"` + Status Status `xml:"Status"` originalString string } +type EncryptedAssertion struct { + XMLName xml.Name + EncryptedData EncryptedData + Assertion Assertion `xml:"Assertion` +} + +type EncryptedData struct { + XMLName xml.Name + EncryptionMethod EncryptionMethod + KeyInfo KeyInfoMain `xml:"KeyInfo"` + CipherData CipherData +} + +type EncryptionMethod struct { + XMLName xml.Name + Algorithm string `xml:"Algorithm,attr"` + DigestMethod DigestMethod +} + +type EncryptedKey struct { + XMLName xml.Name + ID string `xml:"Id,attr"` + EncryptionMethod EncryptionMethod + KeyInfo KeyInfo + CipherData CipherData +} + +type CipherData struct { + XMLName xml.Name + CipherValue string `xml:"CipherValue"` +} + type Assertion struct { XMLName xml.Name ID string `xml:"ID,attr"` From 2065132cc91991d50200f71b1b45dcba0a11707a Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 12:20:53 -0200 Subject: [PATCH 03/15] func to decode encryptedResponse --- authnresponse.go | 49 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/authnresponse.go b/authnresponse.go index 52f1f5a..37a5892 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -4,9 +4,8 @@ import ( "encoding/base64" "encoding/xml" "errors" - "time" - "github.com/RobotsAndPencils/go-saml/util" + "time" ) func ParseCompressedEncodedResponse(b64ResponseXML string) (*Response, error) { @@ -31,6 +30,7 @@ func ParseCompressedEncodedResponse(b64ResponseXML string) (*Response, error) { func ParseEncodedResponse(b64ResponseXML string) (*Response, error) { response := Response{} bytesXML, err := base64.StdEncoding.DecodeString(b64ResponseXML) + //dst := string(bytesXML[:]) if err != nil { return nil, err } @@ -38,14 +38,45 @@ func ParseEncodedResponse(b64ResponseXML string) (*Response, error) { if err != nil { return nil, err } - + //fmt.Printf("%+v\n", response) // There is a bug with XML namespaces in Go that's causing XML attributes with colons to not be roundtrip // marshal and unmarshaled so we'll keep the original string around for validation. response.originalString = string(bytesXML) - // fmt.Println(response.originalString) + //fmt.Println(response.originalString) return &response, nil } +func (r *Response) IsEncrypted() bool { + + //Test if exits EncryptedAssertion tag + if r.EncryptedAssertion.EncryptedData.EncryptionMethod.Algorithm == "" { + return false + } else { + return true + } +} + +func (r *Response) Decrypt(privateKeyPath string) (*Response, error) { + s := r.originalString + + if r.IsEncrypted() == false { + return r, errors.New("missing EncryptedAssertion tag on SAML Response, is encrypted?") + + } + plainXML, err := DecryptResponse(s, privateKeyPath) + if err != nil { + return r, err + } + err = xml.Unmarshal([]byte(plainXML), &r) + if err != nil { + return r, err + } + + return r, nil + + //return DecryptResponse(s, privateKeyPath) +} + func (r *Response) Validate(s *ServiceProviderSettings) error { if r.Version != "2.0" { return errors.New("unsupported SAML Version") @@ -307,7 +338,15 @@ func (r *Response) CompressedEncodedSignedString(privateKeyPath string) (string, // GetAttribute by Name or by FriendlyName. Return blank string if not found func (r *Response) GetAttribute(name string) string { - for _, attr := range r.Assertion.AttributeStatement.Attributes { + attrStatement := AttributeStatement{} + + if r.IsEncrypted() { + attrStatement = r.EncryptedAssertion.Assertion.AttributeStatement + } else { + attrStatement = r.Assertion.AttributeStatement + } + + for _, attr := range attrStatement.Attributes { if attr.Name == name || attr.FriendlyName == name { return attr.AttributeValue.Value } From bed439273174d8cd5e282b322d4a4fc04cc94285 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 15:39:27 -0200 Subject: [PATCH 04/15] update docs --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad97627..fb4a4c1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ go-saml ====== -[![Build Status](https://travis-ci.org/RobotsAndPencils/go-saml.svg?branch=master)](https://travis-ci.org/RobotsAndPencils/go-saml) - A just good enough SAML client library written in Go. This library is by no means complete and has been developed to solve several specific integration efforts. However, it's a start, and it would be great to see it evolve into a more fleshed out implemention. @@ -11,7 +9,7 @@ Inspired by the early work of [Matt Baird](https://github.com/mattbaird/gosaml). The library supports: -* generating signed/unsigned AuthnRequests +* generating signed AuthnRequests * validating signed AuthnRequests * generating service provider metadata * generating signed Responses @@ -42,7 +40,9 @@ sp := saml.ServiceProviderSettings{ IDPSSOURL: "http://idp/saml2", IDPSSODescriptorURL: "http://idp/issuer", IDPPublicCertPath: "idpcert.crt", - SPSignRequest: "true", + Id: "entityID", // can be SP hostname without slash + SPSignRequest: true, + IDPSignResponse: true, AssertionConsumerServiceURL: "http://localhost:8000/saml_consume", } sp.Init() @@ -106,6 +106,18 @@ response = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return } + //If is a encrypted response need decode + if response.IsEncrypted(){ + err = response.Decrypt(sp.PrivateKeyPath) + if err != nil { + httpcommon.SendBadRequest(w, "SAMLResponse parse: "+err.Error()) + return + } + } + // Print plain xml + //fmt.Printf(response.String()) + + //... } ``` From 67755d421d92c0cd787a98e596a94fcb358c0d6b Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 15:40:55 -0200 Subject: [PATCH 05/15] update entityDescriptor --- iDPEntityDescriptor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iDPEntityDescriptor.go b/iDPEntityDescriptor.go index afc4a83..93a0a50 100644 --- a/iDPEntityDescriptor.go +++ b/iDPEntityDescriptor.go @@ -13,7 +13,7 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { DS: "http://www.w3.org/2000/09/xmldsig#", XMLNS: "urn:oasis:names:tc:SAML:2.0:metadata", MD: "urn:oasis:names:tc:SAML:2.0:metadata", - EntityId: s.AssertionConsumerServiceURL, + EntityId: s.Id, Extensions: Extensions{ XMLName: xml.Name{ @@ -25,6 +25,8 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { }, SPSSODescriptor: SPSSODescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", + AuthnRequestsSigned: fmt.Sprintf("%t",s.SPSignRequest), + WantAssertionsSigned: fmt.Sprintf("%t",s.IDPSignResponse), SigningKeyDescriptor: KeyDescriptor{ XMLName: xml.Name{ Local: "md:KeyDescriptor", From acc0a7e73b8772335fecd62df65d704126d7453d Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 15:42:47 -0200 Subject: [PATCH 06/15] add IDPSignResponse parameter --- saml.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/saml.go b/saml.go index 4ba8f64..1014914 100644 --- a/saml.go +++ b/saml.go @@ -12,12 +12,13 @@ type ServiceProviderSettings struct { IDPSSODescriptorURL string IDPPublicCertPath string AssertionConsumerServiceURL string - SPSignRequest bool - - hasInit bool - publicCert string - privateKey string - iDPPublicCert string + Id string + SPSignRequest bool + IDPSignResponse bool + hasInit bool + publicCert string + privateKey string + iDPPublicCert string } type IdentityProviderSettings struct { @@ -29,7 +30,7 @@ func (s *ServiceProviderSettings) Init() (err error) { } s.hasInit = true - if s.SPSignRequest { + if s.SPSignRequest { s.publicCert, err = util.LoadCertificate(s.PublicCertPath) if err != nil { panic(err) From e2d518ab49bc93e1b4a7b6859b88056e1d32db64 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 15:46:05 -0200 Subject: [PATCH 07/15] add Destination --- authnrequest.go | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/authnrequest.go b/authnrequest.go index 3c826a6..84b069f 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -84,6 +84,7 @@ func (r *AuthnRequest) Validate(publicCertPath string) error { func (s *ServiceProviderSettings) GetAuthnRequest() *AuthnRequest { r := NewAuthnRequest() r.AssertionConsumerServiceURL = s.AssertionConsumerServiceURL + r.Destination = s.IDPSSOURL r.Issuer.Url = s.IDPSSODescriptorURL r.Signature.KeyInfo.X509Data.X509Certificate.Cert = s.PublicCert() @@ -116,6 +117,7 @@ func NewAuthnRequest() *AuthnRequest { ID: id, ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", Version: "2.0", + Destination: "", // caller must populate ar.AppSettings.Destination, AssertionConsumerServiceURL: "", // caller must populate ar.AppSettings.AssertionConsumerServiceURL, Issuer: Issuer{ XMLName: xml.Name{ @@ -258,22 +260,3 @@ func (r *AuthnRequest) CompressedEncodedSignedString(privateKeyPath string) (str b64XML := base64.StdEncoding.EncodeToString(compressed) return b64XML, nil } - -func (r *AuthnRequest) EncodedString() (string, error) { - saml, err := r.String() - if err != nil { - return "", err - } - b64XML := base64.StdEncoding.EncodeToString([]byte(saml)) - return b64XML, nil -} - -func (r *AuthnRequest) CompressedEncodedString() (string, error) { - saml, err := r.String() - if err != nil { - return "", err - } - compressed := util.Compress([]byte(saml)) - b64XML := base64.StdEncoding.EncodeToString(compressed) - return b64XML, nil -} From 386f382b80fcbca849be2e62d844eb186182cc23 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 15:47:31 -0200 Subject: [PATCH 08/15] change Decrypt return --- authnresponse.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/authnresponse.go b/authnresponse.go index 37a5892..1705ea7 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -56,23 +56,23 @@ func (r *Response) IsEncrypted() bool { } } -func (r *Response) Decrypt(privateKeyPath string) (*Response, error) { +func (r *Response) Decrypt(privateKeyPath string) error{ s := r.originalString if r.IsEncrypted() == false { - return r, errors.New("missing EncryptedAssertion tag on SAML Response, is encrypted?") + return errors.New("missing EncryptedAssertion tag on SAML Response, is encrypted?") } plainXML, err := DecryptResponse(s, privateKeyPath) if err != nil { - return r, err + return err } err = xml.Unmarshal([]byte(plainXML), &r) if err != nil { - return r, err + return err } - return r, nil + return nil //return DecryptResponse(s, privateKeyPath) } From 53138c4729e5c15251a7f2b8148a7bcd8a0b55f7 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 16:00:31 -0200 Subject: [PATCH 09/15] add add Destination --- authnrequest.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/authnrequest.go b/authnrequest.go index 84b069f..a993921 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -260,3 +260,22 @@ func (r *AuthnRequest) CompressedEncodedSignedString(privateKeyPath string) (str b64XML := base64.StdEncoding.EncodeToString(compressed) return b64XML, nil } + +func (r *AuthnRequest) EncodedString() (string, error) { + saml, err := r.String() + if err != nil { + return "", err + } + b64XML := base64.StdEncoding.EncodeToString([]byte(saml)) + return b64XML, nil +} + +func (r *AuthnRequest) CompressedEncodedString() (string, error) { + saml, err := r.String() + if err != nil { + return "", err + } + compressed := util.Compress([]byte(saml)) + b64XML := base64.StdEncoding.EncodeToString(compressed) + return b64XML, nil +} From 341247833982682501870488e9b44cd713e09dce Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 19 Oct 2015 16:50:50 -0200 Subject: [PATCH 10/15] fix missing closing quote --- types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types.go b/types.go index f732da5..23d62fc 100644 --- a/types.go +++ b/types.go @@ -205,7 +205,7 @@ type Response struct { type EncryptedAssertion struct { XMLName xml.Name EncryptedData EncryptedData - Assertion Assertion `xml:"Assertion` + Assertion Assertion `xml:"Assertion"` } type EncryptedData struct { From c05938c90ef8dbb7b52be32bb04d0928d2524a22 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Fri, 6 Nov 2015 16:12:25 -0200 Subject: [PATCH 11/15] add specific return errors --- authnresponse.go | 2 +- saml.go | 25 +++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/authnresponse.go b/authnresponse.go index 1705ea7..3544f55 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -4,7 +4,7 @@ import ( "encoding/base64" "encoding/xml" "errors" - "github.com/RobotsAndPencils/go-saml/util" + "github.com/diego-araujo/go-saml/util" "time" ) diff --git a/saml.go b/saml.go index 1014914..dff748e 100644 --- a/saml.go +++ b/saml.go @@ -1,7 +1,7 @@ package saml -import "github.com/RobotsAndPencils/go-saml/util" - +import "github.com/diego-araujo/go-saml/util" +import stderrors "errors" // ServiceProviderSettings provides settings to configure server acting as a SAML Service Provider. // Expect only one IDP per SP in this configuration. If you need to configure multipe IDPs for an SP // then configure multiple instances of this module @@ -21,6 +21,12 @@ type ServiceProviderSettings struct { iDPPublicCert string } +var ( + ErrPrivkey = stderrors.New("error load private key") + ErrSpPubCert = stderrors.New("error load SP publicCert") + ErrIdpPubCert = stderrors.New("error load IDP publicCert") +) + type IdentityProviderSettings struct { } @@ -33,40 +39,31 @@ func (s *ServiceProviderSettings) Init() (err error) { if s.SPSignRequest { s.publicCert, err = util.LoadCertificate(s.PublicCertPath) if err != nil { - panic(err) + return ErrSpPubCert } s.privateKey, err = util.LoadCertificate(s.PrivateKeyPath) if err != nil { - panic(err) + return ErrPrivkey } } s.iDPPublicCert, err = util.LoadCertificate(s.IDPPublicCertPath) if err != nil { - panic(err) + return ErrIdpPubCert } return nil } func (s *ServiceProviderSettings) PublicCert() string { - if !s.hasInit { - panic("Must call ServiceProviderSettings.Init() first") - } return s.publicCert } func (s *ServiceProviderSettings) PrivateKey() string { - if !s.hasInit { - panic("Must call ServiceProviderSettings.Init() first") - } return s.privateKey } func (s *ServiceProviderSettings) IDPPublicCert() string { - if !s.hasInit { - panic("Must call ServiceProviderSettings.Init() first") - } return s.iDPPublicCert } From 3ec918f3d749ca589077632b239fd50ce20bf97f Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Fri, 4 Dec 2015 16:58:43 -0200 Subject: [PATCH 12/15] adapt to run with shibolleth --- authnrequest.go | 3 ++- authnresponse.go | 44 +++++++++++++++++++------------ iDPEntityDescriptor.go | 59 ++++++++++++++++++++++++++++++++++++------ saml.go | 11 +++++--- types.go | 34 +++++++++++++++++++++--- xmlsec.go | 11 +++++--- 6 files changed, 124 insertions(+), 38 deletions(-) diff --git a/authnrequest.go b/authnrequest.go index a993921..000de8b 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -85,7 +85,8 @@ func (s *ServiceProviderSettings) GetAuthnRequest() *AuthnRequest { r := NewAuthnRequest() r.AssertionConsumerServiceURL = s.AssertionConsumerServiceURL r.Destination = s.IDPSSOURL - r.Issuer.Url = s.IDPSSODescriptorURL + //r.Issuer.Url = s.IDPSSODescriptorURL + r.Issuer.Url = s.Id r.Signature.KeyInfo.X509Data.X509Certificate.Cert = s.PublicCert() return r diff --git a/authnresponse.go b/authnresponse.go index 3544f55..0c12dbe 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -42,7 +42,6 @@ func ParseEncodedResponse(b64ResponseXML string) (*Response, error) { // There is a bug with XML namespaces in Go that's causing XML attributes with colons to not be roundtrip // marshal and unmarshaled so we'll keep the original string around for validation. response.originalString = string(bytesXML) - //fmt.Println(response.originalString) return &response, nil } @@ -58,7 +57,7 @@ func (r *Response) IsEncrypted() bool { func (r *Response) Decrypt(privateKeyPath string) error{ s := r.originalString - + if r.IsEncrypted() == false { return errors.New("missing EncryptedAssertion tag on SAML Response, is encrypted?") @@ -67,14 +66,13 @@ func (r *Response) Decrypt(privateKeyPath string) error{ if err != nil { return err } - err = xml.Unmarshal([]byte(plainXML), &r) + err = xml.Unmarshal([]byte(plainXML), &r) if err != nil { return err } - + + r.originalString = plainXML return nil - - //return DecryptResponse(s, privateKeyPath) } func (r *Response) Validate(s *ServiceProviderSettings) error { @@ -86,24 +84,32 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { return errors.New("missing ID attribute on SAML Response") } - if len(r.Assertion.ID) == 0 { - return errors.New("no Assertions") + assertion := Assertion{} + if r.IsEncrypted() { + assertion = r.EncryptedAssertion.Assertion + } else { + assertion = r.Assertion } + - if len(r.Signature.SignatureValue.Value) == 0 { - return errors.New("no signature") + if len(assertion.ID) == 0 { + return errors.New("no Assertions") } - if r.Destination != s.AssertionConsumerServiceURL { - return errors.New("destination mismath expected: " + s.AssertionConsumerServiceURL + " not " + r.Destination) + if len(assertion.Signature.SignatureValue.Value) == 0 { + return errors.New("no signature") } - if r.Assertion.Subject.SubjectConfirmation.Method != "urn:oasis:names:tc:SAML:2.0:cm:bearer" { - return errors.New("assertion method exception") + if assertion.Subject.SubjectConfirmation.Method != "urn:oasis:names:tc:SAML:2.0:cm:bearer" { + return errors.New("assertion method exception") + } + + if assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient != s.AssertionConsumerServiceURL { + return errors.New("subject recipient mismatch, expected: " + s.AssertionConsumerServiceURL + " not " + assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient) } - if r.Assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient != s.AssertionConsumerServiceURL { - return errors.New("subject recipient mismatch, expected: " + s.AssertionConsumerServiceURL + " not " + r.Assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient) + if r.Destination != s.AssertionConsumerServiceURL { + return errors.New("destination mismath expected: " + s.AssertionConsumerServiceURL + " not " + r.Destination) } err := VerifyResponseSignature(r.originalString, s.IDPPublicCertPath) @@ -112,7 +118,7 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { } //CHECK TIMES - expires := r.Assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter + expires := assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter notOnOrAfter, e := time.Parse(time.RFC3339, expires) if e != nil { return e @@ -308,6 +314,10 @@ func (r *Response) String() (string, error) { return string(b), nil } +func (r *Response) OriginalString() (string) { + return r.originalString +} + func (r *Response) SignedString(privateKeyPath string) (string, error) { s, err := r.String() if err != nil { diff --git a/iDPEntityDescriptor.go b/iDPEntityDescriptor.go index 93a0a50..08ea2a8 100644 --- a/iDPEntityDescriptor.go +++ b/iDPEntityDescriptor.go @@ -22,11 +22,53 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { Alg: "urn:oasis:names:tc:SAML:metadata:algsupport", MDAttr: "urn:oasis:names:tc:SAML:metadata:attribute", MDRPI: "urn:oasis:names:tc:SAML:metadata:rpi", + + UIInfo: UIInfo{ + XMLName: xml.Name{ + Local: "mdui:UIInfo", + }, + MDUI: "urn:oasis:names:tc:SAML:metadata:ui", + DisplayName: UIDisplayName{ + Lang: "en", + Value: "", + }, + Description: UIDescription{ + Lang: "en", + Value: "", + }, + + }, + }, SPSSODescriptor: SPSSODescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", AuthnRequestsSigned: fmt.Sprintf("%t",s.SPSignRequest), WantAssertionsSigned: fmt.Sprintf("%t",s.IDPSignResponse), + + Extensions: Extensions{ + XMLName: xml.Name{ + Local: "md:Extensions", + }, + Alg: "urn:oasis:names:tc:SAML:metadata:algsupport", + MDAttr: "urn:oasis:names:tc:SAML:metadata:attribute", + MDRPI: "urn:oasis:names:tc:SAML:metadata:rpi", + + UIInfo: UIInfo{ + XMLName: xml.Name{ + Local: "mdui:UIInfo", + }, + MDUI: "urn:oasis:names:tc:SAML:metadata:ui", + DisplayName: UIDisplayName{ + Value: s.DisplayName, + Lang: "en", + }, + Description: UIDescription{ + Lang: "en", + Value: s.Description, + }, + }, + + }, SigningKeyDescriptor: KeyDescriptor{ XMLName: xml.Name{ Local: "md:KeyDescriptor", @@ -88,15 +130,16 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", Location: s.AssertionConsumerServiceURL, Index: "0", + Default: true, }, - AssertionConsumerService{ - XMLName: xml.Name{ - Local: "md:AssertionConsumerService", - }, - Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact", - Location: s.AssertionConsumerServiceURL, - Index: "1", - }, + // AssertionConsumerService{ + // XMLName: xml.Name{ + // Local: "md:AssertionConsumerService", + // }, + // Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact", + // Location: s.AssertionConsumerServiceURL, + // Index: "1", + // }, }, }, } diff --git a/saml.go b/saml.go index dff748e..5a4c3ce 100644 --- a/saml.go +++ b/saml.go @@ -2,6 +2,7 @@ package saml import "github.com/diego-araujo/go-saml/util" import stderrors "errors" + // ServiceProviderSettings provides settings to configure server acting as a SAML Service Provider. // Expect only one IDP per SP in this configuration. If you need to configure multipe IDPs for an SP // then configure multiple instances of this module @@ -10,11 +11,13 @@ type ServiceProviderSettings struct { PrivateKeyPath string IDPSSOURL string IDPSSODescriptorURL string + DisplayName string + Description string IDPPublicCertPath string AssertionConsumerServiceURL string Id string - SPSignRequest bool - IDPSignResponse bool + SPSignRequest bool + IDPSignResponse bool hasInit bool publicCert string privateKey string @@ -22,8 +25,8 @@ type ServiceProviderSettings struct { } var ( - ErrPrivkey = stderrors.New("error load private key") - ErrSpPubCert = stderrors.New("error load SP publicCert") + ErrPrivkey = stderrors.New("error load private key") + ErrSpPubCert = stderrors.New("error load SP publicCert") ErrIdpPubCert = stderrors.New("error load IDP publicCert") ) diff --git a/types.go b/types.go index 23d62fc..a14fcd3 100644 --- a/types.go +++ b/types.go @@ -53,6 +53,8 @@ type Signature struct { SignedInfo SignedInfo SignatureValue SignatureValue KeyInfo KeyInfo + DS string `xml:"xmlns:dsig,attr"` + } type SignedInfo struct { @@ -139,10 +141,30 @@ type Extensions struct { Alg string `xml:"xmlns:alg,attr"` MDAttr string `xml:"xmlns:mdattr,attr"` MDRPI string `xml:"xmlns:mdrpi,attr"` + EntityAttributes string `xml:"EntityAttributes,omitempty"` + UIInfo UIInfo +} + +type UIInfo struct { + XMLName xml.Name + DisplayName UIDisplayName + MDUI string `xml:"xmlns:mdui,attr"` + Description UIDescription +} + +type UIDisplayName struct { + XMLName xml.Name `xml:"mdui:DisplayName"` + Lang string `xml:"xml:lang,attr,omitempty"` + Value string `xml:",innerxml"` +} - EntityAttributes string `xml:"EntityAttributes"` +type UIDescription struct { + XMLName xml.Name `xml:"mdui:Description"` + Lang string `xml:"xml:lang,attr,omitempty"` + Value string `xml:",innerxml"` } + type SPSSODescriptor struct { XMLName xml.Name ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"` @@ -152,6 +174,7 @@ type SPSSODescriptor struct { EncryptionKeyDescriptor KeyDescriptor // SingleLogoutService SingleLogoutService `xml:"SingleLogoutService"` AssertionConsumerServices []AssertionConsumerService + Extensions Extensions `xml:"Extensions"` } type EntityAttributes struct { @@ -180,11 +203,12 @@ type AssertionConsumerService struct { Binding string `xml:"Binding,attr"` Location string `xml:"Location,attr"` Index string `xml:"index,attr"` + Default bool `xml:"isDefault,attr,omitempty"` } type Response struct { XMLName xml.Name - SAMLP string `xml:"xmlns:samlp,attr"` + SAMLP string `xml:"xmlns:saml2p,attr"` SAML string `xml:"xmlns:saml,attr"` SAMLSIG string `xml:"xmlns:samlsig,attr"` Destination string `xml:"Destination,attr"` @@ -195,10 +219,9 @@ type Response struct { EncryptedAssertion EncryptedAssertion `xml:"EncryptedAssertion,omitempty"` Assertion Assertion `xml:"Assertion,omitempty"` - Signature Signature `xml:"Signature"` Issuer Issuer `xml:"Issuer"` Status Status `xml:"Status"` - + Signature Signature `xml:"Signature"` originalString string } @@ -246,8 +269,11 @@ type Assertion struct { Subject Subject Conditions Conditions AttributeStatement AttributeStatement + Signature Signature + } + type Conditions struct { XMLName xml.Name NotBefore string `xml:",attr"` diff --git a/xmlsec.go b/xmlsec.go index f5b4cd8..15a0dc6 100644 --- a/xmlsec.go +++ b/xmlsec.go @@ -9,7 +9,8 @@ import ( ) const ( - xmlResponseID = "urn:oasis:names:tc:SAML:2.0:protocol:Response" + //xmlResponseID = "urn:oasis:names:tc:SAML:2.0:protocol:Response" + xmlResponseID = "urn:oasis:names:tc:SAML:2.0:assertion:Assertion" xmlRequestID = "urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest" ) @@ -36,7 +37,7 @@ func decrypt(xml string, privateKeyPath string) (string, error) { tempfile, err := ioutil.TempFile(os.TempDir(), "saml-resp") ioutil.WriteFile(tempfile.Name(), []byte(xml), 0644) - plainXML, err := exec.Command("xmlsec1", "--decrypt", "--privkey-pem", privateKeyPath, tempfile.Name()).CombinedOutput() + plainXML, err := exec.Command("xmlsec1", "--decrypt", "--privkey-pem", privateKeyPath, tempfile.Name()).Output() defer deleteTempFile(tempfile.Name()) if err != nil { return "", errors.New(err.Error() + " : " + string(plainXML)) @@ -107,9 +108,11 @@ func verify(xml string, publicCertPath string, id string) error { defer deleteTempFile(samlXmlsecInput.Name()) //fmt.Println("xmlsec1", "--verify", "--pubkey-cert-pem", publicCertPath, "--id-attr:ID", id, samlXmlsecInput.Name()) - _, err = exec.Command("xmlsec1", "--verify", "--pubkey-cert-pem", publicCertPath, "--id-attr:ID", id, samlXmlsecInput.Name()).CombinedOutput() + cmd := exec.Command("xmlsec1", "--verify", "--pubkey-cert-pem", publicCertPath, "--id-attr:ID", id, samlXmlsecInput.Name()) + output, err := cmd.CombinedOutput() + if err != nil { - return errors.New("error verifing signature: " + err.Error()) + return errors.New("error verifing signature: " + string(output)) } return nil } From 074144d4698d43a0ae098653d07578c04c6d46fd Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Mon, 7 Dec 2015 15:01:31 -0200 Subject: [PATCH 13/15] update docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fb4a4c1..4b68b59 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ sp := saml.ServiceProviderSettings{ IDPSSODescriptorURL: "http://idp/issuer", IDPPublicCertPath: "idpcert.crt", Id: "entityID", // can be SP hostname without slash + DisplayName: "name", //maybe it will be displayed in IDP login page + Description: "desc" //maybe it will be displayed in IDP login page SPSignRequest: true, IDPSignResponse: true, AssertionConsumerServiceURL: "http://localhost:8000/saml_consume", From 83716ce8996d95d70cc5d496b9bd6dfc4b8b64f9 Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Wed, 9 Dec 2015 16:10:30 -0200 Subject: [PATCH 14/15] split validate methods --- authnresponse.go | 86 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/authnresponse.go b/authnresponse.go index 0c12dbe..f695bce 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -75,6 +75,41 @@ func (r *Response) Decrypt(privateKeyPath string) error{ return nil } +func (r *Response) ValidateResponseSignature(s *ServiceProviderSettings) error { + + assertion, err := r.getAssertion() + if err != nil { + return err + } + + if len(assertion.Signature.SignatureValue.Value) == 0 { + return errors.New("no signature") + } + + err = VerifyResponseSignature(r.originalString, s.IDPPublicCertPath) + if err != nil { + return err + } + + return nil +} + +func (r *Response) getAssertion() (Assertion, error) { + + assertion := Assertion{} + + if r.IsEncrypted() { + assertion = r.EncryptedAssertion.Assertion + } else { + assertion = r.Assertion + } + + if len(assertion.ID) == 0 { + return assertion, errors.New("no Assertions") + } + return assertion, nil +} + func (r *Response) Validate(s *ServiceProviderSettings) error { if r.Version != "2.0" { return errors.New("unsupported SAML Version") @@ -84,20 +119,10 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { return errors.New("missing ID attribute on SAML Response") } - assertion := Assertion{} - if r.IsEncrypted() { - assertion = r.EncryptedAssertion.Assertion - } else { - assertion = r.Assertion - } - - - if len(assertion.ID) == 0 { - return errors.New("no Assertions") - } - - if len(assertion.Signature.SignatureValue.Value) == 0 { - return errors.New("no signature") + + assertion, err := r.getAssertion() + if err != nil { + return err } if assertion.Subject.SubjectConfirmation.Method != "urn:oasis:names:tc:SAML:2.0:cm:bearer" { @@ -112,24 +137,29 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { return errors.New("destination mismath expected: " + s.AssertionConsumerServiceURL + " not " + r.Destination) } - err := VerifyResponseSignature(r.originalString, s.IDPPublicCertPath) - if err != nil { - return err - } + return nil +} + + +func (r *Response) ValidateExpiredConfirmation(s *ServiceProviderSettings) error { + + assertion, err := r.getAssertion() + if err != nil { + return err + } //CHECK TIMES - expires := assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter - notOnOrAfter, e := time.Parse(time.RFC3339, expires) - if e != nil { - return e - } - if notOnOrAfter.Before(time.Now()) { - return errors.New("assertion has expired on: " + expires) - } + expires := assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter + notOnOrAfter, e := time.Parse(time.RFC3339, expires) + if e != nil { + return e + } + if notOnOrAfter.Before(time.Now()) { + return errors.New("assertion has expired on: " + expires) + } - return nil + return nil } - func NewSignedResponse() *Response { return &Response{ XMLName: xml.Name{ From 81203d2425377605c5688e55dca1340b614b618d Mon Sep 17 00:00:00 2001 From: Diego Araujo Date: Fri, 11 Dec 2015 08:29:11 -0200 Subject: [PATCH 15/15] ident code --- authnresponse.go | 78 ++++++++++++++++++------------------ iDPEntityDescriptor.go | 89 ++++++++++++++++++++---------------------- types.go | 44 ++++++++++----------- 3 files changed, 101 insertions(+), 110 deletions(-) diff --git a/authnresponse.go b/authnresponse.go index f695bce..442444e 100644 --- a/authnresponse.go +++ b/authnresponse.go @@ -55,9 +55,9 @@ func (r *Response) IsEncrypted() bool { } } -func (r *Response) Decrypt(privateKeyPath string) error{ +func (r *Response) Decrypt(privateKeyPath string) error { s := r.originalString - + if r.IsEncrypted() == false { return errors.New("missing EncryptedAssertion tag on SAML Response, is encrypted?") @@ -66,11 +66,11 @@ func (r *Response) Decrypt(privateKeyPath string) error{ if err != nil { return err } - err = xml.Unmarshal([]byte(plainXML), &r) + err = xml.Unmarshal([]byte(plainXML), &r) if err != nil { return err } - + r.originalString = plainXML return nil } @@ -78,18 +78,18 @@ func (r *Response) Decrypt(privateKeyPath string) error{ func (r *Response) ValidateResponseSignature(s *ServiceProviderSettings) error { assertion, err := r.getAssertion() - if err != nil { - return err - } + if err != nil { + return err + } if len(assertion.Signature.SignatureValue.Value) == 0 { - return errors.New("no signature") - } + return errors.New("no signature") + } err = VerifyResponseSignature(r.originalString, s.IDPPublicCertPath) - if err != nil { - return err - } + if err != nil { + return err + } return nil } @@ -98,15 +98,15 @@ func (r *Response) getAssertion() (Assertion, error) { assertion := Assertion{} - if r.IsEncrypted() { - assertion = r.EncryptedAssertion.Assertion - } else { - assertion = r.Assertion - } + if r.IsEncrypted() { + assertion = r.EncryptedAssertion.Assertion + } else { + assertion = r.Assertion + } if len(assertion.ID) == 0 { - return assertion, errors.New("no Assertions") - } + return assertion, errors.New("no Assertions") + } return assertion, nil } @@ -119,18 +119,17 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { return errors.New("missing ID attribute on SAML Response") } - assertion, err := r.getAssertion() if err != nil { return err } if assertion.Subject.SubjectConfirmation.Method != "urn:oasis:names:tc:SAML:2.0:cm:bearer" { - return errors.New("assertion method exception") - } + return errors.New("assertion method exception") + } - if assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient != s.AssertionConsumerServiceURL { - return errors.New("subject recipient mismatch, expected: " + s.AssertionConsumerServiceURL + " not " + assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient) + if assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient != s.AssertionConsumerServiceURL { + return errors.New("subject recipient mismatch, expected: " + s.AssertionConsumerServiceURL + " not " + assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient) } if r.Destination != s.AssertionConsumerServiceURL { @@ -140,25 +139,24 @@ func (r *Response) Validate(s *ServiceProviderSettings) error { return nil } - func (r *Response) ValidateExpiredConfirmation(s *ServiceProviderSettings) error { - + assertion, err := r.getAssertion() - if err != nil { - return err - } + if err != nil { + return err + } //CHECK TIMES - expires := assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter - notOnOrAfter, e := time.Parse(time.RFC3339, expires) - if e != nil { - return e - } - if notOnOrAfter.Before(time.Now()) { - return errors.New("assertion has expired on: " + expires) - } - - return nil + expires := assertion.Subject.SubjectConfirmation.SubjectConfirmationData.NotOnOrAfter + notOnOrAfter, e := time.Parse(time.RFC3339, expires) + if e != nil { + return e + } + if notOnOrAfter.Before(time.Now()) { + return errors.New("assertion has expired on: " + expires) + } + + return nil } func NewSignedResponse() *Response { return &Response{ @@ -344,7 +342,7 @@ func (r *Response) String() (string, error) { return string(b), nil } -func (r *Response) OriginalString() (string) { +func (r *Response) OriginalString() string { return r.originalString } diff --git a/iDPEntityDescriptor.go b/iDPEntityDescriptor.go index 08ea2a8..211306c 100644 --- a/iDPEntityDescriptor.go +++ b/iDPEntityDescriptor.go @@ -22,52 +22,49 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { Alg: "urn:oasis:names:tc:SAML:metadata:algsupport", MDAttr: "urn:oasis:names:tc:SAML:metadata:attribute", MDRPI: "urn:oasis:names:tc:SAML:metadata:rpi", - - UIInfo: UIInfo{ - XMLName: xml.Name{ - Local: "mdui:UIInfo", - }, - MDUI: "urn:oasis:names:tc:SAML:metadata:ui", - DisplayName: UIDisplayName{ - Lang: "en", - Value: "", - }, - Description: UIDescription{ - Lang: "en", - Value: "", - }, - - }, + UIInfo: UIInfo{ + XMLName: xml.Name{ + Local: "mdui:UIInfo", + }, + MDUI: "urn:oasis:names:tc:SAML:metadata:ui", + DisplayName: UIDisplayName{ + Lang: "en", + Value: "", + }, + Description: UIDescription{ + Lang: "en", + Value: "", + }, + }, }, SPSSODescriptor: SPSSODescriptor{ ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol", - AuthnRequestsSigned: fmt.Sprintf("%t",s.SPSignRequest), - WantAssertionsSigned: fmt.Sprintf("%t",s.IDPSignResponse), - + AuthnRequestsSigned: fmt.Sprintf("%t", s.SPSignRequest), + WantAssertionsSigned: fmt.Sprintf("%t", s.IDPSignResponse), + Extensions: Extensions{ XMLName: xml.Name{ - Local: "md:Extensions", - }, + Local: "md:Extensions", + }, Alg: "urn:oasis:names:tc:SAML:metadata:algsupport", - MDAttr: "urn:oasis:names:tc:SAML:metadata:attribute", - MDRPI: "urn:oasis:names:tc:SAML:metadata:rpi", + MDAttr: "urn:oasis:names:tc:SAML:metadata:attribute", + MDRPI: "urn:oasis:names:tc:SAML:metadata:rpi", UIInfo: UIInfo{ - XMLName: xml.Name{ - Local: "mdui:UIInfo", - }, - MDUI: "urn:oasis:names:tc:SAML:metadata:ui", - DisplayName: UIDisplayName{ - Value: s.DisplayName, - Lang: "en", - }, - Description: UIDescription{ - Lang: "en", - Value: s.Description, - }, - }, - + XMLName: xml.Name{ + Local: "mdui:UIInfo", + }, + MDUI: "urn:oasis:names:tc:SAML:metadata:ui", + DisplayName: UIDisplayName{ + Value: s.DisplayName, + Lang: "en", + }, + Description: UIDescription{ + Lang: "en", + Value: s.Description, + }, + }, }, SigningKeyDescriptor: KeyDescriptor{ XMLName: xml.Name{ @@ -130,16 +127,16 @@ func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error) { Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", Location: s.AssertionConsumerServiceURL, Index: "0", - Default: true, + Default: true, }, - // AssertionConsumerService{ - // XMLName: xml.Name{ - // Local: "md:AssertionConsumerService", - // }, - // Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact", - // Location: s.AssertionConsumerServiceURL, - // Index: "1", - // }, + // AssertionConsumerService{ + // XMLName: xml.Name{ + // Local: "md:AssertionConsumerService", + // }, + // Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact", + // Location: s.AssertionConsumerServiceURL, + // Index: "1", + // }, }, }, } diff --git a/types.go b/types.go index a14fcd3..b02ffb5 100644 --- a/types.go +++ b/types.go @@ -53,8 +53,7 @@ type Signature struct { SignedInfo SignedInfo SignatureValue SignatureValue KeyInfo KeyInfo - DS string `xml:"xmlns:dsig,attr"` - + DS string `xml:"xmlns:dsig,attr"` } type SignedInfo struct { @@ -137,34 +136,33 @@ type EntityDescriptor struct { } type Extensions struct { - XMLName xml.Name - Alg string `xml:"xmlns:alg,attr"` - MDAttr string `xml:"xmlns:mdattr,attr"` - MDRPI string `xml:"xmlns:mdrpi,attr"` + XMLName xml.Name + Alg string `xml:"xmlns:alg,attr"` + MDAttr string `xml:"xmlns:mdattr,attr"` + MDRPI string `xml:"xmlns:mdrpi,attr"` EntityAttributes string `xml:"EntityAttributes,omitempty"` - UIInfo UIInfo + UIInfo UIInfo } type UIInfo struct { - XMLName xml.Name - DisplayName UIDisplayName - MDUI string `xml:"xmlns:mdui,attr"` + XMLName xml.Name + DisplayName UIDisplayName + MDUI string `xml:"xmlns:mdui,attr"` Description UIDescription } type UIDisplayName struct { - XMLName xml.Name `xml:"mdui:DisplayName"` - Lang string `xml:"xml:lang,attr,omitempty"` - Value string `xml:",innerxml"` + XMLName xml.Name `xml:"mdui:DisplayName"` + Lang string `xml:"xml:lang,attr,omitempty"` + Value string `xml:",innerxml"` } type UIDescription struct { - XMLName xml.Name `xml:"mdui:Description"` - Lang string `xml:"xml:lang,attr,omitempty"` - Value string `xml:",innerxml"` + XMLName xml.Name `xml:"mdui:Description"` + Lang string `xml:"xml:lang,attr,omitempty"` + Value string `xml:",innerxml"` } - type SPSSODescriptor struct { XMLName xml.Name ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"` @@ -174,7 +172,7 @@ type SPSSODescriptor struct { EncryptionKeyDescriptor KeyDescriptor // SingleLogoutService SingleLogoutService `xml:"SingleLogoutService"` AssertionConsumerServices []AssertionConsumerService - Extensions Extensions `xml:"Extensions"` + Extensions Extensions `xml:"Extensions"` } type EntityAttributes struct { @@ -203,7 +201,7 @@ type AssertionConsumerService struct { Binding string `xml:"Binding,attr"` Location string `xml:"Location,attr"` Index string `xml:"index,attr"` - Default bool `xml:"isDefault,attr,omitempty"` + Default bool `xml:"isDefault,attr,omitempty"` } type Response struct { @@ -221,8 +219,8 @@ type Response struct { Assertion Assertion `xml:"Assertion,omitempty"` Issuer Issuer `xml:"Issuer"` Status Status `xml:"Status"` - Signature Signature `xml:"Signature"` - originalString string + Signature Signature `xml:"Signature"` + originalString string } type EncryptedAssertion struct { @@ -269,11 +267,9 @@ type Assertion struct { Subject Subject Conditions Conditions AttributeStatement AttributeStatement - Signature Signature - + Signature Signature } - type Conditions struct { XMLName xml.Name NotBefore string `xml:",attr"`