From 697eaafb0c8511925602e18342654481920ab7fd Mon Sep 17 00:00:00 2001 From: Owen Marshall Date: Wed, 9 Dec 2015 13:36:23 -0500 Subject: [PATCH 1/4] Add a link to testshib.org --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4967a94..828b6ce 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ For instance, New Relic allows you to configure a saml provider (https://newreli Ping Identity has a nice video for SAML here: https://www.pingidentity.com/resource-center/Introduction-to-SAML-Video.cfm +Shibboleth provides a test SAML IdP and SP endpoint at https://www.testshib.org/ + Installation ------------ From 38ad58af0003af7b7eb3bb2fe3ab1a71261ee724 Mon Sep 17 00:00:00 2001 From: Owen Marshall Date: Wed, 9 Dec 2015 13:37:20 -0500 Subject: [PATCH 2/4] Make GetRequest take an enum. for binding, use DEFLATE in GetURL --- authnrequest.go | 57 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/authnrequest.go b/authnrequest.go index 904bae2..9932d81 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -15,8 +15,11 @@ package saml import ( + "bytes" + "compress/flate" "encoding/base64" "encoding/xml" + "errors" "fmt" "github.com/nu7hatch/gouuid" "io/ioutil" @@ -27,6 +30,11 @@ import ( "time" ) +const ( + BindingPOST = iota + BindingRedirect +) + func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSettings) *AuthorizationRequest { myIdUUID, err := uuid.NewV4() if err != nil { @@ -41,7 +49,17 @@ func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSet // GetRequest returns a string formatted XML document that represents the SAML document // TODO: parameterize more parts of the request -func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) { +func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { + bindings := map[int]string{ + BindingPOST: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post", + BindingRedirect: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", + } + + bind, ok := bindings[binding] + if !ok { + return "", errors.New("invalid binding type requested") + } + d := AuthnRequest{ XMLName: xml.Name{ Local: "samlp:AuthnRequest", @@ -49,7 +67,7 @@ func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) { SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol", SAML: "urn:oasis:names:tc:SAML:2.0:assertion", ID: ar.Id, - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + ProtocolBinding: bind, Version: "2.0", AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL, Issuer: Issuer{ @@ -88,13 +106,7 @@ func (ar AuthorizationRequest) GetRequest(base64Encode bool) (string, error) { } xmlAuthnRequest := fmt.Sprintf("\n%s", b) - - if base64Encode { - data := []byte(xmlAuthnRequest) - return base64.StdEncoding.EncodeToString(data), nil - } else { - return string(xmlAuthnRequest), nil - } + return string(xmlAuthnRequest), nil } // GetSignedRequest returns a string formatted XML document that represents the SAML document @@ -268,13 +280,36 @@ func (ar AuthorizationRequest) GetRequestUrl() (string, error) { if err != nil { return "", err } - base64EncodedUTF8SamlRequest, err := ar.GetRequest(true) + + request, err := ar.GetRequest(BindingRedirect) if err != nil { return "", err } + // Deflate + var deflated bytes.Buffer + + fw, err := flate.NewWriter(&deflated, -1) + if err != nil { + return "", err + } + + _, err = fw.Write([]byte(request)) + if err != nil { + return "", err + } + + err = fw.Close() + if err != nil { + return "", err + } + + // base64 + b64str := base64.StdEncoding.EncodeToString(deflated.Bytes()) + + // urlencode q := u.Query() - q.Add("SAMLRequest", base64EncodedUTF8SamlRequest) + q.Add("SAMLRequest", b64str) u.RawQuery = q.Encode() return u.String(), nil From cbea3807cbf385f3d776e414116c971eeaa81d83 Mon Sep 17 00:00:00 2001 From: Owen Marshall Date: Wed, 9 Dec 2015 14:32:21 -0500 Subject: [PATCH 3/4] bugfixes * GetRequestUrl(int) now takes a int of the binding and doesn't assume Redirect. * BindingPOST was changed to capitalize POST * ACSIndex is now omitempty --- authnrequest.go | 6 +++--- authnrequestInterface.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/authnrequest.go b/authnrequest.go index 9932d81..e2b8f29 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -51,7 +51,7 @@ func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSet // TODO: parameterize more parts of the request func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { bindings := map[int]string{ - BindingPOST: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post", + BindingPOST: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", BindingRedirect: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", } @@ -275,13 +275,13 @@ func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert st // String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" + // AuthRequest.getRidOfCRLF(URLEncoder.encode(authReq.getRequest(AuthRequest.base64),"UTF-8")); -func (ar AuthorizationRequest) GetRequestUrl() (string, error) { +func (ar AuthorizationRequest) GetRequestUrl(binding int) (string, error) { u, err := url.Parse(ar.AccountSettings.IDP_SSO_Target_URL) if err != nil { return "", err } - request, err := ar.GetRequest(BindingRedirect) + request, err := ar.GetRequest(binding) if err != nil { return "", err } diff --git a/authnrequestInterface.go b/authnrequestInterface.go index 96d4234..3b642ef 100644 --- a/authnrequestInterface.go +++ b/authnrequestInterface.go @@ -35,7 +35,7 @@ type AuthnRequest struct { ProtocolBinding string `xml:"ProtocolBinding,attr"` AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr,omitempty"` IssueInstant string `xml:"IssueInstant,attr"` - AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr"` + AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr,omitemtpy"` AssertionConsumerServiceIndex int `xml:"AssertionConsumerServiceIndex,attr,omitempty"` Issuer Issuer `xml:"Issuer"` NameIDPolicy NameIDPolicy `xml:"NameIDPolicy"` From 76a26a08ca6a42e260a76d6181bb2fd2ce684140 Mon Sep 17 00:00:00 2001 From: Owen Marshall Date: Wed, 9 Dec 2015 15:20:09 -0500 Subject: [PATCH 4/4] Move AuthnSignedRequest fields -> parameterized req --- authnrequest.go | 81 +++++++++++++++------------------------- authnrequestInterface.go | 20 ++-------- 2 files changed, 35 insertions(+), 66 deletions(-) diff --git a/authnrequest.go b/authnrequest.go index e2b8f29..1a16ec1 100644 --- a/authnrequest.go +++ b/authnrequest.go @@ -47,9 +47,7 @@ func NewAuthorizationRequest(appSettings AppSettings, accountSettings AccountSet return &AuthorizationRequest{AccountSettings: accountSettings, AppSettings: appSettings, Id: "_" + myIdUUID.String(), IssueInstant: t} } -// GetRequest returns a string formatted XML document that represents the SAML document -// TODO: parameterize more parts of the request -func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { +func (ar AuthorizationRequest) BuildRequest(binding int) (AuthnRequest, error) { bindings := map[int]string{ BindingPOST: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", BindingRedirect: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @@ -57,7 +55,7 @@ func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { bind, ok := bindings[binding] if !ok { - return "", errors.New("invalid binding type requested") + return AuthnRequest{}, errors.New("invalid binding type requested") } d := AuthnRequest{ @@ -100,6 +98,18 @@ func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { }, }, } + + return d, nil +} + +// GetRequest returns a string formatted XML document that represents the SAML document +// TODO: parameterize more parts of the request +func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { + d, err := ar.BuildRequest(binding) + if err != nil { + return "", err + } + b, err := xml.MarshalIndent(d, "", " ") if err != nil { return "", err @@ -111,51 +121,20 @@ func (ar AuthorizationRequest) GetRequest(binding int) (string, error) { // GetSignedRequest returns a string formatted XML document that represents the SAML document // TODO: parameterize more parts of the request -func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert string, privateCert string) (string, error) { +func (ar AuthorizationRequest) GetSignedRequest(binding int, publicCert string, privateCert string) (string, error) { cert, err := LoadCertificate(publicCert) if err != nil { return "", err } + base, err := ar.BuildRequest(binding) + if err != nil { + return "", err + } + d := AuthnSignedRequest{ - XMLName: xml.Name{ - Local: "samlp:AuthnRequest", - }, - SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol", - SAML: "urn:oasis:names:tc:SAML:2.0:assertion", - SAMLSIG: "http://www.w3.org/2000/09/xmldsig#", - ID: ar.Id, - ProtocolBinding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - Version: "2.0", - AssertionConsumerServiceURL: ar.AppSettings.AssertionConsumerServiceURL, - Issuer: Issuer{ - XMLName: xml.Name{ - Local: "saml:Issuer", - }, - Url: ar.AppSettings.Issuer, - }, - IssueInstant: ar.IssueInstant, - NameIDPolicy: NameIDPolicy{ - XMLName: xml.Name{ - Local: "samlp:NameIDPolicy", - }, - AllowCreate: true, - Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", - }, - RequestedAuthnContext: RequestedAuthnContext{ - XMLName: xml.Name{ - Local: "samlp:RequestedAuthnContext", - }, - SAMLP: "urn:oasis:names:tc:SAML:2.0:protocol", - Comparison: "exact", - }, - AuthnContextClassRef: AuthnContextClassRef{ - XMLName: xml.Name{ - Local: "saml:AuthnContextClassRef", - }, - SAML: "urn:oasis:names:tc:SAML:2.0:assertion", - Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - }, + AuthnRequest: base, + SAMLSIG: "http://www.w3.org/2000/09/xmldsig#", Signature: Signature{ XMLName: xml.Name{ Local: "samlsig:Signature", @@ -228,7 +207,15 @@ func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert st }, }, }, + AuthnContextClassRef: AuthnContextClassRef{ + XMLName: xml.Name{ + Local: "saml:AuthnContextClassRef", + }, + SAML: "urn:oasis:names:tc:SAML:2.0:assertion", + Transport: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + }, } + b, err := xml.MarshalIndent(d, "", " ") if err != nil { return "", err @@ -264,13 +251,7 @@ func (ar AuthorizationRequest) GetSignedRequest(base64Encode bool, publicCert st return "", err } samlSignedRequestXml := strings.Trim(string(samlSignedRequest), "\n") - - if base64Encode { - data := []byte(samlSignedRequestXml) - return base64.StdEncoding.EncodeToString(data), nil - } else { - return string(samlSignedRequestXml), nil - } + return string(samlSignedRequestXml), nil } // String reqString = accSettings.getIdp_sso_target_url()+"?SAMLRequest=" + diff --git a/authnrequestInterface.go b/authnrequestInterface.go index 3b642ef..d008569 100644 --- a/authnrequestInterface.go +++ b/authnrequestInterface.go @@ -43,22 +43,10 @@ type AuthnRequest struct { } type AuthnSignedRequest struct { - XMLName xml.Name - SAMLP string `xml:"xmlns:samlp,attr"` - SAML string `xml:"xmlns:saml,attr"` - SAMLSIG string `xml:"xmlns:samlsig,attr"` - ID string `xml:"ID,attr"` - Version string `xml:"Version,attr"` - ProtocolBinding string `xml:"ProtocolBinding,attr"` - AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr"` - IssueInstant string `xml:"IssueInstant,attr"` - AssertionConsumerServiceIndex int `xml:"AssertionConsumerServiceIndex,attr"` - AttributeConsumingServiceIndex int `xml:"AttributeConsumingServiceIndex,attr"` - Issuer Issuer `xml:"Issuer"` - NameIDPolicy NameIDPolicy `xml:"NameIDPolicy"` - RequestedAuthnContext RequestedAuthnContext `xml:"RequestedAuthnContext"` - AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"` - Signature Signature `xml:"Signature"` + AuthnRequest + AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"` + Signature Signature `xml:"Signature"` + SAMLSIG string `xml:"xmlns:samlsig,attr"` } type Issuer struct {