crewjam/saml

repository·main·Indexed 22 days ago

https://github.com/crewjam/saml

A Go implementation of the SAML standard for identity federation, supporting both Service Providers (SP) via the samlsp package and Identity Providers (IDP). It implements interoperable SAML, including Web SSO Profile, HTTP Redirect and POST bindings, and signed/encrypted assertions. Key components include the IdentityProvider struct, SessionProvider and ServiceProviderProvider interfaces for authentication and metadata lookup, and a Duration type for ISO 8601 XML marshaling.

Tokens
11.5K
Snippets
30
Records
44
Agent score
78%

What's inside crewjam-saml

  1. SAML Implementation Capabilities

    main

    The saml package implements a subset of the SAML standard known as interoperable SAML.

    Supported Profiles and Bindings:

    • Web SSO Profile: Supported.
    • SP to IDP flows: Supported via HTTP Redirect and HTTP POST bindings.
    • IDP to SP flows: Supported via HTTP POST binding.
    • Assertions: The package can produce signed SAML assertions and validate both signed and encrypted SAML assertions.
  2. Understand RelayState in SAML

    main

    The RelayState parameter is used to pass user state information across the authentication flow. This is typically used to implement 'deep linking', allowing a user to be redirected back to a specific URL after successful authentication instead of just the application root.

    Security Warnings:

    • RelayState is not authenticated. Any data supplied in it must be signed by your application to prevent XSS or CSRF attacks.
    • It is limited to 80 bytes in length, which makes it difficult to include signatures within the parameter itself.
  3. Register your Service Provider with an Identity Provider

    main

    After running your Service Provider application, you must register it with the Identity Provider (IDP) to establish trust.

    1. Expose your SP's metadata by fetching it from your local /saml/metadata endpoint:
      mdpath=saml-test-$USER-$HOST.xml
      curl localhost:8000/saml/metadata > $mdpath
    2. Upload the resulting XML file to your IDP's management interface (e.g., uploading to samltest.id/upload.php).
    mdpath=saml-test-$USER-$HOST.xml
    curl localhost:8000/saml/metadata > $mdpath
  4. Implement a Service Provider with samlsp

    main

    To implement a Service Provider (SP) that delegates authentication to an Identity Provider (IDP), use the samlsp package.

    Prerequisites

    1. X.509 Key Pair: You must have a self-signed X.509 key pair for the SP. You can generate one using OpenSSL:
      openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com"
    2. IDP Metadata: You need the metadata URL of the Identity Provider to fetch its configuration at startup.

    Implementation Steps

    1. Load your X.509 certificate and private key.
    2. Use samlsp.FetchMetadata to retrieve the IDP's metadata.
    3. Initialize samlsp.New with samlsp.Options containing your URL, Key, Certificate, and IDPMetadata.
    4. Use samlSP.RequireAccount(handler) to wrap protected endpoints. This ensures the user is authenticated before accessing the handler.
    5. Register the samlSP instance itself at the /saml/ path to handle SAML-specific protocol requests (like ACS and metadata).

    Accessing User Attributes

    You can retrieve user attributes (like displayName) from the request context using samlsp.AttributeFromContext(r.Context(), "attribute_name").

    // ... imports
    
    func hello(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName"))
    }
    
    func main() {
    	keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key")
    	if err != nil {
    		panic(err)
    	}
    	keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
    	if err != nil {
    		panic(err)
    	}
    
    	idpMetadataURL, err := url.Parse("https://samltest.id/saml/idp")
    	if err != nil {
    		panic(err)
    	}
    	idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient, *idpMetadataURL)
    	if err != nil {
    		panic(err)
    	}
    
    	rootURL, err := url.Parse("http://localhost:8000")
    	if err != nil {
    		panic(err)
    	}
    
    	samlSP, _ := samlsp.New(samlsp.Options{
    		URL:            *rootURL,
    		Key:            keyPair.PrivateKey.(*rsa.PrivateKey),
    		Certificate:    keyPair.Leaf,
    		IDPMetadata: idpMetadata,
    	})
    
    	app := http.HandlerFunc(hello)
    	http.Handle("/hello", samlSP.RequireAccount(app))
    	http.Handle("/saml/", samlSP)
    	http.ListenAndServe(":8000", nil)
    }
  5. Understand the IdpAuthnRequest lifecycle

    main

    The IdpAuthnRequest struct manages the state of a single SAML authentication request during the IDP lifecycle.

    Lifecycle Steps:

    1. Creation: NewIdpAuthnRequest(idp, r) parses the incoming GET or POST request, decodes the SAMLRequest (handling Base64 and decompression), and extracts the RelayState.
    2. Validation: req.Validate() verifies the XML structure, checks the Destination attribute, ensures the request hasn't expired, and verifies the issuer against the ServiceProviderProvider.
    3. Assertion Construction: req.MakeAssertion(session) (or via an AssertionMaker) populates the req.Assertion field with user data.
    4. Encryption/Signing: req.MakeAssertionEl() signs the assertion and, if the SP's metadata contains an encryption certificate, encrypts the assertion.
    5. Response Generation: req.MakeResponse() wraps the assertion in a SAML Response element and signs the response.
    6. Transmission: req.WriteResponse(w) or req.PostBinding() prepares the final HTML form (using the HTTP-POST binding) to automatically submit the response to the SP's ACS endpoint.
  6. Endpoint and IndexedEndpoint

    main

    These types represent SAML endpoints used for protocol bindings.

    Endpoint

    Used for services like SingleLogoutService or ManageNameIDService. It includes:

    • Binding: The SAML binding URN.
    • Location: The URI of the endpoint.
    • ResponseLocation: (Optional) The location where responses should be sent.

    Validation: During unmarshaling, the Location and ResponseLocation are validated to ensure they use http or https schemes for known SAML bindings.

    IndexedEndpoint

    Used for services like AssertionConsumerService or ArtifactResolutionService. It includes:

    • Index: An integer index.
    • IsDefault: (Optional) Whether this is the default endpoint.
    • Binding, Location, ResponseLocation: Same as Endpoint.
    type Endpoint struct {
    	Binding          string `xml:"Binding,attr"`
    	Location         string `xml:"Location,attr"`
    	ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
    }
    
    type IndexedEndpoint struct {
    	Binding          string  `xml:"Binding,attr"`
    	Location         string  `xml:"Location,attr"`
    	ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"`
    	Index            int     `xml:"index,attr"`
    	IsDefault        *bool   `xml:"isDefault,attr"`
    }
  7. Configure the ServiceProvider for SAML

    main

    The ServiceProvider struct is the central component for implementing a SAML Service Provider. It manages identity provider (IdP) metadata, local keys for signing/encryption, and endpoint URLs.

    Key configuration fields include:

    • EntityID: The unique identifier for your SP.
    • Key: A crypto.Signer (typically *rsa.PrivateKey or *ecdsa.PrivateKey) used to sign requests.
    • Certificate: The public part of your Key.
    • IDPMetadata: An *EntityDescriptor containing the IdP's configuration.
    • AcsURL: The Assertion Consumer Service endpoint where the IdP sends responses.
    • SloURL: The Single Logout endpoint.
    • SignatureMethod: The algorithm used for signing (e.g., dsig.RSASHA256SignatureMethod).
    • AuthnNameIDFormat: The preferred NameIDFormat for authentication requests.
    sp := &saml.ServiceProvider{
        EntityID: "https://example.com/saml/metadata",
        Key: myPrivateKey,
        Certificate: myCertificate,
        MetadataURL: myMetadataURL,
        AcsURL: myAcsURL,
        SloURL: mySloURL,
        IDPMetadata: idpMetadata,
        SignatureMethod: dsig.RSASHA256SignatureMethod,
    }
  8. Represent SAML AuthnContext in XML

    main

    The AuthnContext type describes the context of the authentication (e.g., how the user was authenticated). It primarily uses AuthnContextClassRef to specify the authentication method class.

    authnContext := &saml.AuthnContext{
    	AuthnContextClassRef: &saml.AuthnContextClassRef{
    		Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
    	},
    }
    // Convert to etree.Element
    el := authnContext.Element()
  9. Configure signing context for IdpAuthnRequest

    main

    When processing an IdpAuthnRequest, a signing context is used to sign the response. The signing process uses the Identity Provider's (IDP) certificate chain and private key.

    Key behaviors:

    • Certificate Chain: The context is built using req.IDP.Certificate.Raw followed by any provided req.IDP.Intermediates.
    • Signer Selection: If req.IDP.Signer is explicitly provided, it is used to create the dsig.SigningContext. Otherwise, the context is created using req.IDP.Key and the certificate chain via a dsig.TLSCertKeyStore.
    • Signature Method: If req.IDP.SignatureMethod is empty, it defaults to dsig.RSASHA1SignatureMethod.
    • Canonicalization: The signing context is configured to use dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList for XML canonicalization.
    // Note: signingContext is an internal method of IdpAuthnRequest used to prepare the dsig.SigningContext
    // based on the IDP configuration (Certificate, Intermediates, Signer, Key, and SignatureMethod).
  10. Represent SAML ProxyRestriction in XML

    main

    The ProxyRestriction type is used to restrict the number of times a SAML assertion can be used by a proxy and the audiences for which it is valid.

    count := 1
    restriction := &saml.ProxyRestriction{
    	Count:     &count,
    	Audiences: []saml.Audience{{ /* ... */ }},
    }
    // Convert to etree.Element
    el := restriction.Element()
  11. Represent SAML SubjectLocality in XML

    main

    The SubjectLocality type provides information about the location of the subject, such as their IP address or DNS name.

    locality := &saml.SubjectLocality{
    	Address: "192.168.1.1",
    	DNSName: "host.example.com",
    }
    // Convert to etree.Element
    el := locality.Element()