saml

package module
v0.0.0-...-bd022fc Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 7, 2023 License: MIT Imports: 14 Imported by: 0

README

go-saml

Go

*Forked from https://github.com/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 implementing.

Inspired by the early work of Matt Baird.

The library supports:

  • generating signed/unsigned AuthnRequests
  • validating signed AuthnRequests
  • generating service provider metadata
  • generating signed Responses
  • validating signed Responses
Prerequisites:
sudo apt install xmlsec1
Installation
go get github.com/RobotsAndPencils/go-saml

Here's a convenient way to generate a certificate:

curl -sSL https://raw.githubusercontent.com/frntn/x509-san/master/gencert.sh | CRT_CN="mycert"  bash

Usage

Below are samples to show how you might use the library.

Generating Signed AuthnRequests
sp := saml.ServiceProviderSettings{
  PublicCertPath:              "../default.crt",
  PrivateKeyPath:              "../default.key",
  IDPSSOURL:                   "http://idp/saml2",
  IDPSSODescriptorURL:         "http://idp/issuer",
  IDPPublicCertPath:           "idpcert.crt",
  SPSignRequest:               "true",
  AssertionConsumerServiceURL: "http://localhost:8000/saml_consume",
}
sp.Init()

// generate the AuthnRequest and then get a base64 encoded string of the XML
authnRequest := sp.GetAuthnRequest()
b64XML, err := authnRequest.EncodedSignedString(sp.PrivateKeyPath)
if err != nil {
  panic(err)
}

// for convenience, get a URL formed with the SAMLRequest parameter
url, err := saml.GetAuthnRequestURL(sp.IDPSSOURL, b64XML)
if err != nil {
  panic(err)
}

// below is bonus for how you might respond to a request with a form that POSTs to the IdP
data := struct {
  Base64AuthRequest string
  URL               string
}{
  Base64AuthRequest: b64XML,
  URL:               url,
}

t := template.New("saml")
t, err = t.Parse("<html><body style=\"display: none\" onload=\"document.frm.submit()\"><form method=\"post\" name=\"frm\" action=\"{{.URL}}\"><input type=\"hidden\" name=\"SAMLRequest\" value=\"{{.Base64AuthRequest}}\" /><input type=\"submit\" value=\"Submit\" /></form></body></html>")

// how you might respond to a request with the templated form that will auto post
t.Execute(w, data)
Validating a received SAML Response
response = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  encodedXML := r.FormValue("SAMLResponse")

  if encodedXML == "" {
    httpcommon.SendBadRequest(w, "SAMLResponse form value missing")
    return
  }

  response, err := saml.ParseEncodedResponse(encodedXML)
  if err != nil {
    httpcommon.SendBadRequest(w, "SAMLResponse parse: "+err.Error())
    return
  }

  err = response.Validate(&sp)
  if err != nil {
    httpcommon.SendBadRequest(w, "SAMLResponse validation: "+err.Error())
    return
  }

  samlID := response.GetAttribute("uid")
  if samlID == "" {
    httpcommon.SendBadRequest(w, "SAML attribute identifier uid missing")
    return
  }

  //...
}
Service provider metadata
func samlMetadataHandler(sp *saml.ServiceProviderSettings) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		md, err := sp.GetEntityDescriptor()
		if err != nil {
      w.WriteHeader(500)
      w.Write([]byte("Error: " + err.Error()))
			return
		}

		w.Header().Set("Content-Type", "application/xml")
		w.Write([]byte(md))
	})
}
Receiving a authnRequest
b64Request := r.URL.Query().Get("SAMLRequest")
if b64Request == "" {
  w.WriteHeader(400)
  w.Write([]byte("SAMLRequest parameter missing"))
  return
}

defated, err := base64.StdEncoding.DecodeString(b64Request)
if err != nil {
  w.WriteHeader(500)
  w.Write([]byte("Error: " + err.Error()))
  return
}

// enflate and unmarshal
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
io.Copy(&buffer, rdr)
var authnRequest saml.AuthnRequest

err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
  w.WriteHeader(500)
  w.Write([]byte("Error: " + err.Error()))
  return
}

if authnRequest.Issuer.Url != issuerURL {
  w.WriteHeader(500)
  w.Write([]byte("unauthorized issuer "+authnRequest.Issuer.Url))
  return
}

Creating a SAML Response (if acting as an IdP)
issuer := "http://localhost:8000/saml"
authnResponse := saml.NewSignedResponse()
authnResponse.Issuer.Url = issuer
authnResponse.Assertion.Issuer.Url = issuer
authnResponse.Signature.KeyInfo.X509Data.X509Certificate.Cert = stringValueOfCert
authnResponse.Assertion.Subject.NameID.Value = userIdThatYouAuthenticated
authnResponse.AddAttribute("uid", userIdThatYouAuthenticated)
authnResponse.AddAttribute("email", "someone@domain")
authnResponse.Assertion.Subject.SubjectConfirmation.SubjectConfirmationData.InResponseTo = authnRequestIdRespondingTo
authnResponse.InResponseTo = authnRequestIdRespondingTo
authnResponse.Assertion.Subject.SubjectConfirmation.SubjectConfirmationData.Recipient = issuer

// signed XML string
signed, err := authnResponse.SignedString("/path/to/private.key")

// or signed base64 encoded XML string
b64XML, err := authnResponse.EncodedSignedString("/path/to/private.key")

Contributing

Would love any contributions you having including better documentation, tests, or more robust functionality.

git clone git@github.com:RobotsAndPencils/go-saml.git
make init
make test
Contact

Made with ❤ by Robots & Pencils (@robotsNpencils)

Maintainers

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetAuthnRequestURL

func GetAuthnRequestURL(baseURL string, b64XML string, state string) (string, error)

GetAuthnRequestURL generate a URL for the AuthnRequest to the IdP with the SAMLRequst parameter encoded

func SignRequest

func SignRequest(xml string, privateKeyPath string) (string, error)

SignRequest sign a SAML 2.0 AuthnRequest `privateKeyPath` must be a path on the filesystem, xmlsec1 is run out of process through `exec`

func SignResponse

func SignResponse(xml string, privateKeyPath string) (string, error)

SignResponse sign a SAML 2.0 Response `privateKeyPath` must be a path on the filesystem, xmlsec1 is run out of process through `exec`

func VerifyRequestSignature

func VerifyRequestSignature(xml string, publicCertPath string) error

VerifyRequestSignature verify signature of a SAML 2.0 AuthnRequest document `publicCertPath` must be a path on the filesystem, xmlsec1 is run out of process through `exec`

func VerifyResponseSignature

func VerifyResponseSignature(xml string, publicCertPath string) error

VerifyResponseSignature verify signature of a SAML 2.0 Response document `publicCertPath` must be a path on the filesystem, xmlsec1 is run out of process through `exec`

func VerifyResponseSignatureCert

func VerifyResponseSignatureCert(xml string, cert string) error

VerifyResponseSignatureCert verify signature of a SAML 2.0 Response document `cert` must be the raw cert, xmlsec1 is run out of process through `exec`

Types

type Assertion

type Assertion struct {
	XMLName            xml.Name
	ID                 string `xml:"ID,attr"`
	Version            string `xml:"Version,attr"`
	XS                 string `xml:"xmlns:xs,attr"`
	XSI                string `xml:"xmlns:xsi,attr"`
	SAML               string `xml:"xmlns:saml,attr"`
	IssueInstant       string `xml:"IssueInstant,attr"`
	Issuer             Issuer `xml:"Issuer"`
	Subject            Subject
	Conditions         Conditions
	AuthnStatements    []AuthnStatement `xml:"AuthnStatement,omitempty"`
	AttributeStatement AttributeStatement
}

type AssertionConsumerService

type AssertionConsumerService struct {
	XMLName  xml.Name
	Binding  string `xml:"Binding,attr"`
	Location string `xml:"Location,attr"`
	Index    string `xml:"index,attr"`
}

type Attribute

type Attribute struct {
	XMLName         xml.Name
	Name            string           `xml:",attr"`
	FriendlyName    string           `xml:",attr,omitempty"`
	NameFormat      string           `xml:",attr"`
	AttributeValues []AttributeValue `xml:"AttributeValue"`
}

type AttributeStatement

type AttributeStatement struct {
	XMLName    xml.Name
	Attributes []Attribute `xml:"Attribute"`
}

type AttributeValue

type AttributeValue struct {
	XMLName xml.Name
	Type    string `xml:"xsi:type,attr"`
	Value   string `xml:",innerxml"`
}

type Audience

type Audience struct {
	XMLName xml.Name
	Value   string `xml:",innerxml"`
}

type AudienceRestriction

type AudienceRestriction struct {
	XMLName   xml.Name
	Audiences []Audience `xml:"Audience"`
}

type AuthnContext

type AuthnContext struct {
	XMLName              xml.Name
	AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"`
}

type AuthnContextClassRef

type AuthnContextClassRef struct {
	XMLName   xml.Name
	SAML      string `xml:"xmlns:saml,attr"`
	Transport string `xml:",innerxml"`
}

type AuthnRequest

type AuthnRequest struct {
	XMLName                        xml.Name
	SAMLP                          string                `xml:"xmlns:samlp,attr"`
	SAML                           string                `xml:"xmlns:saml,attr"`
	SAMLSIG                        string                `xml:"xmlns:samlsig,attr,omitempty"`
	ID                             string                `xml:"ID,attr"`
	Version                        string                `xml:"Version,attr"`
	ProtocolBinding                string                `xml:"ProtocolBinding,attr"`
	AssertionConsumerServiceURL    string                `xml:"AssertionConsumerServiceURL,attr"`
	Destination                    string                `xml:"Destination,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"`
	Signature                      *Signature            `xml:"Signature,omitempty"`
	// contains filtered or unexported fields
}

func NewAuthnRequest

func NewAuthnRequest() *AuthnRequest

func ParseCompressedEncodedRequest

func ParseCompressedEncodedRequest(b64RequestXML string) (*AuthnRequest, error)

func ParseEncodedRequest

func ParseEncodedRequest(b64RequestXML string) (*AuthnRequest, error)

func (*AuthnRequest) CompressedEncodedSignedString

func (r *AuthnRequest) CompressedEncodedSignedString(privateKeyPath string) (string, error)

func (*AuthnRequest) CompressedEncodedString

func (r *AuthnRequest) CompressedEncodedString() (string, error)

func (*AuthnRequest) EncodedSignedString

func (r *AuthnRequest) EncodedSignedString(privateKeyPath string) (string, error)

GetAuthnRequestURL generate a URL for the AuthnRequest to the IdP with the SAMLRequst parameter encoded

func (*AuthnRequest) EncodedString

func (r *AuthnRequest) EncodedString() (string, error)

func (*AuthnRequest) SignedString

func (r *AuthnRequest) SignedString(privateKeyPath string) (string, error)

func (*AuthnRequest) String

func (r *AuthnRequest) String() (string, error)

func (*AuthnRequest) Validate

func (r *AuthnRequest) Validate(publicCertPath string) error

type AuthnStatement

type AuthnStatement struct {
	XMLName             xml.Name
	AuthnInstant        string       `xml:",attr"`
	SessionNotOnOrAfter string       `xml:",attr,omitempty"`
	SessionIndex        string       `xml:",attr,omitempty"`
	AuthnContext        AuthnContext `xml:"AuthnContext"`
}

type CanonicalizationMethod

type CanonicalizationMethod struct {
	XMLName   xml.Name
	Algorithm string `xml:"Algorithm,attr"`
}

type Conditions

type Conditions struct {
	XMLName              xml.Name
	NotBefore            string                `xml:",attr"`
	NotOnOrAfter         string                `xml:",attr"`
	AudienceRestrictions []AudienceRestriction `xml:"AudienceRestriction,omitempty"`
}

type DigestMethod

type DigestMethod struct {
	XMLName   xml.Name
	Algorithm string `xml:"Algorithm,attr"`
}

type DigestValue

type DigestValue struct {
	XMLName xml.Name
}

type EntityAttributes

type EntityAttributes struct {
	XMLName xml.Name
	SAML    string `xml:"xmlns:saml,attr"`

	EntityAttributes []Attribute `xml:"Attribute"` // should be array??
}

type EntityDescriptor

type EntityDescriptor struct {
	XMLName  xml.Name
	DS       string `xml:"xmlns:ds,attr"`
	XMLNS    string `xml:"xmlns,attr"`
	MD       string `xml:"xmlns:md,attr"`
	EntityId string `xml:"entityID,attr"`

	Extensions      Extensions      `xml:"Extensions"`
	SPSSODescriptor SPSSODescriptor `xml:"SPSSODescriptor"`
}

type Extensions

type Extensions struct {
	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"`
}

type IdentityProviderSettings

type IdentityProviderSettings struct {
}

type Issuer

type Issuer struct {
	XMLName xml.Name
	SAML    string `xml:"xmlns:saml,attr"`
	Url     string `xml:",innerxml"`
}

type KeyDescriptor

type KeyDescriptor struct {
	XMLName xml.Name
	Use     string  `xml:"use,attr"`
	KeyInfo KeyInfo `xml:"KeyInfo"`
}

type KeyInfo

type KeyInfo struct {
	XMLName  xml.Name
	X509Data X509Data `xml:",innerxml"`
}

type NameID

type NameID struct {
	XMLName         xml.Name
	Format          string `xml:",attr"`
	SPNameQualifier string `xml:",attr,omitempty"`
	Value           string `xml:",innerxml"`
}

type NameIDPolicy

type NameIDPolicy struct {
	XMLName     xml.Name
	AllowCreate bool   `xml:"AllowCreate,attr"`
	Format      string `xml:"Format,attr"`
}

type RequestedAuthnContext

type RequestedAuthnContext struct {
	XMLName              xml.Name
	SAMLP                string               `xml:"xmlns:samlp,attr"`
	Comparison           string               `xml:"Comparison,attr"`
	AuthnContextClassRef AuthnContextClassRef `xml:"AuthnContextClassRef"`
}

type Response

type Response struct {
	XMLName      xml.Name
	SAMLP        string `xml:"xmlns:samlp,attr"`
	SAML         string `xml:"xmlns:saml,attr"`
	SAMLSIG      string `xml:"xmlns:samlsig,attr"`
	Destination  string `xml:"Destination,attr"`
	ID           string `xml:"ID,attr"`
	Version      string `xml:"Version,attr"`
	IssueInstant string `xml:"IssueInstant,attr"`
	InResponseTo string `xml:"InResponseTo,attr"`

	Issuer    Issuer    `xml:"Issuer"`
	Signature Signature `xml:"Signature"`
	Status    Status    `xml:"Status"`
	Assertion Assertion `xml:"Assertion"`
	// contains filtered or unexported fields
}

func NewSignedResponse

func NewSignedResponse() *Response

func ParseCompressedEncodedResponse

func ParseCompressedEncodedResponse(b64ResponseXML string) (*Response, error)

func ParseEncodedResponse

func ParseEncodedResponse(b64ResponseXML string) (*Response, error)

func (*Response) AddAttribute

func (r *Response) AddAttribute(name, value string)

AddAttribute add strong attribute to the Response

func (*Response) AddAudienceRestriction

func (r *Response) AddAudienceRestriction(value string)

func (*Response) AddAuthnStatement

func (r *Response) AddAuthnStatement(transport string, sessionIndex string)

func (*Response) CompressedEncodedSignedString

func (r *Response) CompressedEncodedSignedString(privateKeyPath string) (string, error)

func (*Response) EncodedSignedString

func (r *Response) EncodedSignedString(privateKeyPath string) (string, error)

func (*Response) GetAttribute

func (r *Response) GetAttribute(name string) string

GetAttribute by Name or by FriendlyName. Return blank string if not found

func (*Response) GetAttributeValues

func (r *Response) GetAttributeValues(name string) []string

func (*Response) SignedString

func (r *Response) SignedString(privateKeyPath string) (string, error)

func (*Response) String

func (r *Response) String() (string, error)

func (*Response) Validate

func (r *Response) Validate(s *ServiceProviderSettings) error

type SPSSODescriptor

type SPSSODescriptor struct {
	XMLName                    xml.Name
	ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
	SigningKeyDescriptor       KeyDescriptor
	EncryptionKeyDescriptor    KeyDescriptor
	// SingleLogoutService        SingleLogoutService `xml:"SingleLogoutService"`
	AssertionConsumerServices []AssertionConsumerService
}

type SPSSODescriptors

type SPSSODescriptors struct {
}

type SamlsigReference

type SamlsigReference struct {
	XMLName      xml.Name
	URI          string       `xml:"URI,attr"`
	Transforms   Transforms   `xml:",innerxml"`
	DigestMethod DigestMethod `xml:",innerxml"`
	DigestValue  DigestValue  `xml:",innerxml"`
}

type ServiceProviderSettings

type ServiceProviderSettings struct {
	PublicCertPath              string
	RawPublicCert               string
	PrivateKeyPath              string
	RawPrivateKey               string
	IDPSSOURL                   string
	IDPSSODescriptorURL         string
	IDPPublicCertPath           string
	RawIDPPublicCert            string
	AssertionConsumerServiceURL string
	SPSignRequest               bool
	// contains filtered or unexported fields
}

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 multiple IDPs for an SP then configure multiple instances of this module

func (*ServiceProviderSettings) GetAuthnRequest

func (s *ServiceProviderSettings) GetAuthnRequest() (*AuthnRequest, error)

GetSignedAuthnRequest returns a singed XML document that represents a AuthnRequest SAML document

func (*ServiceProviderSettings) GetEntityDescriptor

func (s *ServiceProviderSettings) GetEntityDescriptor() (string, error)

func (*ServiceProviderSettings) IDPPublicCert

func (s *ServiceProviderSettings) IDPPublicCert() (string, error)

func (*ServiceProviderSettings) Init

func (s *ServiceProviderSettings) Init() error

func (*ServiceProviderSettings) PrivateKey

func (s *ServiceProviderSettings) PrivateKey() (string, error)

func (*ServiceProviderSettings) PublicCert

func (s *ServiceProviderSettings) PublicCert() (string, error)

type Signature

type Signature struct {
	XMLName        xml.Name
	Id             string `xml:"Id,attr"`
	SignedInfo     SignedInfo
	SignatureValue SignatureValue
	KeyInfo        KeyInfo
}

type SignatureMethod

type SignatureMethod struct {
	XMLName   xml.Name
	Algorithm string `xml:"Algorithm,attr"`
}

type SignatureValue

type SignatureValue struct {
	XMLName xml.Name
	Value   string `xml:",innerxml"`
}

type SignedInfo

type SignedInfo struct {
	XMLName                xml.Name
	CanonicalizationMethod CanonicalizationMethod
	SignatureMethod        SignatureMethod
	SamlsigReference       SamlsigReference
}

type SingleLogoutService

type SingleLogoutService struct {
	Binding  string `xml:"Binding,attr"`
	Location string `xml:"Location,attr"`
}

type Status

type Status struct {
	XMLName    xml.Name
	StatusCode StatusCode `xml:"StatusCode"`
}

type StatusCode

type StatusCode struct {
	XMLName xml.Name
	Value   string `xml:",attr"`
}

type Subject

type Subject struct {
	XMLName             xml.Name
	NameID              NameID
	SubjectConfirmation SubjectConfirmation
}

type SubjectConfirmation

type SubjectConfirmation struct {
	XMLName                 xml.Name
	Method                  string `xml:",attr"`
	SubjectConfirmationData SubjectConfirmationData
}

type SubjectConfirmationData

type SubjectConfirmationData struct {
	XMLName      xml.Name
	InResponseTo string `xml:",attr"`
	NotOnOrAfter string `xml:",attr"`
	Recipient    string `xml:",attr"`
}

type Transform

type Transform struct {
	XMLName   xml.Name
	Algorithm string `xml:"Algorithm,attr"`
}

type Transforms

type Transforms struct {
	XMLName   xml.Name
	Transform []Transform
}

type X509Certificate

type X509Certificate struct {
	XMLName xml.Name
	Cert    string `xml:",innerxml"`
}

type X509Data

type X509Data struct {
	XMLName         xml.Name
	X509Certificate X509Certificate `xml:",innerxml"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL