Categorygithub.com/bilcus/saml
modulepackage
0.2.1
Repository: https://github.com/bilcus/saml.git
Documentation: pkg.go.dev

# README

SAML

Package saml contains a partial implementation of the SAML standard in golang. SAML is a standard for identity federation, i.e. either allowing a third party to authenticate your users or allowing third parties to rely on us to authenticate their users.

Introduction

In SAML parlance an Identity Provider (IDP) is a service that knows how to authenticate users. A Service Provider (SP) is a service that delegates authentication to an IDP. If you are building a service where users log in with someone else's credentials, then you are a Service Provider. This package supports implementing both service providers and identity providers.

The core package contains the implementation of SAML. The package samlsp provides helper middleware suitable for use in Service Provider applications. The package samlidp provides a rudimentary IDP service that is useful for testing or as a starting point for other integrations.

Breaking Changes

Note: between version 0.2.0 and the current master include changes to the API that will break your existing code a little.

This change turned some fields from pointers to a single optional struct into the more correct slice of struct, and to pluralize the field name. For example, IDPSSODescriptor *IDPSSODescriptor has become IDPSSODescriptors []IDPSSODescriptor. This more accurately reflects the standard.

The struct Metadata has been renamed to EntityDescriptor. In 0.2.0 and before, every struct derived from the standard has the same name as in the standard, except for Metadata which should always have been called EntityDescriptor.

In various places url.URL is now used where string was used <= version 0.1.0.

In various places where keys and certificates were modeled as string <= version 0.1.0 (what was I thinking?!) they are now modeled as *rsa.PrivateKey, *x509.Certificate, or crypto.PrivateKey as appropriate.

Getting Started as a Service Provider

Let us assume we have a simple web appliation to protect. We'll modify this application so it uses SAML to authenticate users.

package main

import "net/http"

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    app := http.HandlerFunc(hello)
    http.Handle("/hello", app)
    http.ListenAndServe(":8000", nil)
}

Each service provider must have an self-signed X.509 key pair established. You can generate your own with something like this:

openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com"

We will use samlsp.Middleware to wrap the endpoint we want to protect. Middleware provides both an http.Handler to serve the SAML specific URLs and a set of wrappers to require the user to be logged in. We also provide the URL where the service provider can fetch the metadata from the IDP at startup. In our case, we'll use testshib.org, an identity provider designed for testing.

package main

import (
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net/http"
	"net/url"

	"github.com/crewjam/saml/samlsp"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %s!", samlsp.Token(r.Context()).Attributes.Get("cn"))
}

func main() {
	keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key")
	if err != nil {
		panic(err) // TODO handle error
	}
	keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
	if err != nil {
		panic(err) // TODO handle error
	}

	idpMetadataURL, err := url.Parse("https://www.testshib.org/metadata/testshib-providers.xml")
	if err != nil {
		panic(err) // TODO handle error
	}

	rootURL, err := url.Parse("http://localhost:8000")
	if err != nil {
		panic(err) // TODO handle error
	}

	samlSP, _ := samlsp.New(samlsp.Options{
		URL:            *rootURL,
		Key:            keyPair.PrivateKey.(*rsa.PrivateKey),
		Certificate:    keyPair.Leaf,
		IDPMetadataURL: idpMetadataURL,
	})
	app := http.HandlerFunc(hello)
	http.Handle("/hello", samlSP.RequireAccount(app))
	http.Handle("/saml/", samlSP)
	http.ListenAndServe(":8000", nil)
}

Next we'll have to register our service provider with the identiy provider to establish trust from the service provider to the IDP. For testshib.org, you can do something like:

mdpath=saml-test-$USER-$HOST.xml
curl localhost:8000/saml/metadata > $mdpath

Naviate to https://www.testshib.org/register.html and upload the file you fetched.

Now you should be able to authenticate. The flow should look like this:

  1. You browse to localhost:8000/hello

  2. The middleware redirects you to https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO

  3. testshib.org prompts you for a username and password.

  4. testshib.org returns you an HTML document which contains an HTML form setup to POST to localhost:8000/saml/acs. The form is automatically submitted if you have javascript enabled.

  5. The local service validates the response, issues a session cookie, and redirects you to the original URL, localhost:8000/hello.

  6. This time when localhost:8000/hello is requested there is a valid session and so the main content is served.

Getting Started as an Identity Provider

Please see examples/idp/ for a substantially complete example of how to use the library and helpers to be an identity provider.

Support

The SAML standard is huge and complex with many dark corners and strange, unused features. This package implements the most commonly used subset of these features required to provide a single sign on experience. The package supports at least the subset of SAML known as interoperable SAML.

This package supports the Web SSO profile. Message flows from the service provider to the IDP are supported using the HTTP Redirect binding and the HTTP POST binding. Message flows from the IDP to the service provider are supported via the HTTP POST binding.

The package supports signed and encrypted SAML assertions. It does not support signed or encrypted requests.

RelayState

The RelayState parameter allows you to pass user state information across the authentication flow. The most common use for this is to allow a user to request a deep link into your site, be redirected through the SAML login flow, and upon successful completion, be directed to the originaly requested link, rather than the root.

Unfortunately, RelayState is less useful than it could be. Firstly, it is not authenticated, so anything you supply must be signed to avoid XSS or CSRF. Secondly, it is limited to 80 bytes in length, which precludes signing. (See section 3.6.3.1 of SAMLProfiles.)

References

The SAML specification is a collection of PDFs (sadly):

TestShib is a testing ground for SAML service and identity providers.

Security Issues

Please do not report security issues in the issue tracker. Rather, please contact me directly at [email protected] (PGP Key 78B6038B3B9DFB88).

# Packages

This is an example that implements a bitly-esque short link service.
No description provided by the author
Package samlidp a rudimentary SAML identity provider suitable for testing or as a starting point for a more complex service.
Package samlsp provides helpers that can be used to protect web services using SAML.
No description provided by the author
Package xmlenc is a partial implementation of the xmlenc standard as described in https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html.

# Functions

NewIdpAuthnRequest returns a new IdpAuthnRequest for the given HTTP request to the authorization service.

# Constants

DefaultCacheDuration is how long we ask the IDP to cache the SP metadata.
DefaultValidDuration is how long we assert that the SP metadata is valid.
Name ID formats.
MaxIssueDelay is the longest allowed time between when a SAML assertion is issued by the IDP and the time it is received by ParseResponse.
Name ID formats.
StatusAuthnFailed means the responding provider was unable to successfully authenticate the principal.
StatusInvalidAttrNameOrValue means Unexpected or invalid content was encountered within a <saml:Attribute> or <saml:AttributeValue> element.
StatusInvalidNameIDPolicy means the responding provider cannot or will not support the requested name identifier policy.
StatusNoAuthnContext means the specified authentication context requirements cannot be met by the responder.
StatusNoAvailableIDP is used by an intermediary to indicate that none of the supported identity provider <Loc> elements in an <IDPList> can be resolved or that none of the supported identity providers are available.
StatusNoPassive means Indicates the responding provider cannot authenticate the principal passively, as has been requested.
StatusNoSupportedIDP is used by an intermediary to indicate that none of the identity providers in an <IDPList> are supported by the intermediary.
StatusPartialLogout is used by a session authority to indicate to a session participant that it was not able to propagate logout to all other session participants.
StatusProxyCountExceeded means Indicates that a responding provider cannot authenticate the principal directly and is not permitted to proxy the request further.
StatusRequestDenied means the SAML responder or SAML authority is able to process the request but has chosen not to respond.
StatusRequester means the request could not be performed due to an error on the part of the requester.
StatusRequestUnsupported means the SAML responder or SAML authority does not support the request.
StatusRequestVersionDeprecated means the SAML responder cannot process any requests with the protocol version specified in the request.
StatusRequestVersionTooHigh means the SAML responder cannot process the request because the protocol version specified in the request message is a major upgrade from the highest protocol version supported by the responder.
StatusRequestVersionTooLow means the SAML responder cannot process the request because the protocol version specified in the request message is too low.
StatusResourceNotRecognized means the resource value provided in the request message is invalid or unrecognized.
StatusResponder means the request could not be performed due to an error on the part of the SAML responder or SAML authority.
StatusTooManyResponses means the response message would contain more elements than the SAML responder is able to return.
StatusUnknownAttrProfile means an entity that has no knowledge of a particular attribute profile has been presented with an attribute means drawn from that profile.
StatusUnknownPrincipal means the responding provider does not recognize the principal specified or implied by the request.
StatusUnsupportedBinding means the SAML responder cannot properly fulfill the request using the protocol binding specified in the request.
StatusVersionMismatch means the SAML responder could not process the request because the version of the request message was incorrect.
Name ID formats.
Name ID formats.

# Variables

Clock is assigned to dsig validation and signing contexts if it is not nil, otherwise the default clock is used.
HTTPPostBinding is the official URN for the HTTP-POST binding (transport).
HTTPRedirectBinding is the official URN for the HTTP-Redirect binding (transport).
MaxClockSkew allows for leeway for clock skew between the IDP and SP when validating assertions.
Metadata as been renamed to EntityDescriptor This change was made to be consistent with the rest of the API which uses names from the SAML specification for types.
RandReader is the io.Reader that produces cryptographically random bytes when they are need by the library.
StatusSuccess means the request succeeded.
TimeNow is a function that returns the current time.

# Structs

AffiliationDescriptor represents the SAML AffiliationDescriptor object.
Assertion represents the SAML element Assertion.
AssertionAttribute represents an attribute of the user extracted from a SAML Assertion.
Attribute represents the SAML element Attribute.
AttributeAuthorityDescriptor represents the SAML AttributeAuthorityDescriptor object.
AttributeConsumingService represents the SAML AttributeConsumingService object.
AttributeStatement represents the SAML element AttributeStatement.
AttributeValue represents the SAML element AttributeValue.
Audience represents the SAML element Audience.
AudienceRestriction represents the SAML element AudienceRestriction.
AuthnAuthorityDescriptor represents the SAML AuthnAuthorityDescriptor object.
AuthnContext represents the SAML element AuthnContext.
AuthnContextClassRef represents the SAML element AuthnContextClassRef.
AuthnRequest represents the SAML object of the same name, a request from a service provider to authenticate a user.
AuthnStatement represents the SAML element AuthnStatement.
Conditions represents the SAML element Conditions.
ContactPerson represents the SAML element ContactPerson.
DefaultAssertionMaker produces a SAML assertion for the given request and assigns it to req.Assertion.
EncryptionMethod represents the XMLSEC object of the same name.
Endpoint represents the SAML EndpointType object.
EntitiesDescriptor represents the SAML object of the same name.
EntityDescriptor represents the SAML EntityDescriptor object.
IdentityProvider implements the SAML Identity Provider role (IDP).
IdpAuthnRequest is used by IdentityProvider to handle a single authentication request.
IDPSSODescriptor represents the SAML IDPSSODescriptorType object.
IndexedEndpoint represents the SAML IndexedEndpointType object.
InvalidResponseError is the error produced by ParseResponse when it fails.
Issuer represents the SAML object of the same name.
KeyDescriptor represents the XMLSEC object of the same name.
KeyInfo represents the XMLSEC object of the same name TODO(ross): revisit xmldsig and make this type more complete.
LocalizedName represents the SAML type localizedNameType.
LocalizedURI represents the SAML type localizedURIType.
NameID represents the SAML element NameID.
NameIDPolicy represents the SAML object of the same name.
OneTimeUse represents the SAML element OneTimeUse.
Organization represents the SAML Organization object.
PDPDescriptor represents the SAML PDPDescriptor object.
ProxyRestriction represents the SAML element ProxyRestriction.
RequestedAttribute represents the SAML RequestedAttribute object.
Response represents the SAML object of the same name.
RoleDescriptor represents the SAML element RoleDescriptor.
ServiceProvider implements SAML Service provider.
Session represents a user session.
SPSSODescriptor represents the SAML SPSSODescriptorType object.
SSODescriptor represents the SAML complex type SSODescriptor See http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf §2.4.2.
Status represents the SAML object of the same name.
StatusCode represents the SAML object of the same name.
StatusDetail represents the SAML element StatusDetail.
StatusMessage represents the SAML element StatusMessage.
Subject represents the SAML element Subject.
SubjectConfirmation represents the SAML element SubjectConfirmation.
SubjectConfirmationData represents the SAML element SubjectConfirmationData.
SubjectLocality represents the SAML element SubjectLocality.

# Interfaces

AssertionMaker is an interface used by IdentityProvider to construct the assertion for a request.
ServiceProviderProvider is an interface used by IdentityProvider to look up service provider metadata for a request.
SessionProvider is an interface used by IdentityProvider to determine the Session associated with a request.

# Type aliases

AssertionAttributes is a list of AssertionAttribute.
Duration is a time.Duration that uses the xsd:duration format for text marshalling and unmarshalling.
NameIDFormat is the format of the id.
RelaxedTime is a version of time.Time that supports the time format found in SAML documents.