saml

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

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

Go to latest
Published: Aug 26, 2017 License: MIT Imports: 15 Imported by: 0

README

go-saml

Build Status

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.

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

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

View Source
var (
	//ErrPEMFormat error with pem format
	ErrPEMFormat = errors.New("Certificate not valid pem format")
	//ErrInvalidSettings settings configuration does not allow for action
	ErrInvalidSettings = errors.New("SAML settings configuration does not permit this action")
	//ErrMissingID missing id attribute
	ErrMissingID = errors.New("Missing ID attribute on SAML Response")
	//ErrUnsupportedVersion saml version not supported
	ErrUnsupportedVersion = errors.New("Unsupported SAML Version")
	//ErrCannotDecode saml document
	ErrCannotDecode = errors.New("Unable to decode and/or decompress message")
)

Functions

func BuildRequestURL

func BuildRequestURL(s Settings, u *url.URL, state string, b64XML string) (string, error)

BuildRequestURL build request url with signature

func GetAuthnRequestURL

func GetAuthnRequestURL(s Settings, state string) (string, error)

GetAuthnRequestURL as SP, generate authentication request url to perform sso

func GetLogoutRequestURL

func GetLogoutRequestURL(s Settings, state string, nameID string, sessionIndex string) (string, error)

GetLogoutRequestURL as SP, generate logout request url to perform slo

func GetRequestSignature

func GetRequestSignature(data string, key string) (string, error)

GetRequestSignature for the request url

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:"saml,attr"`
	IssueInstant       string `xml:"IssueInstant,attr"`
	Issuer             Issuer `xml:"Issuer"`
	Signature          packager.Signature
	Subject            Subject
	Conditions         Conditions
	AttributeStatement AttributeStatement
	AuthnStatement     AuthnStatement `xml:"AuthnStatement,omitempty"`
}

Assertion saml response assertion information

type AssertionConsumerService

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

AssertionConsumerService sso assertion metadata

type Attribute

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

Attribute of subject

type AttributeStatement

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

AttributeStatement TODO needs description

type AttributeValue

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

AttributeValue of subject attribute

type AuthnContextClassRef

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

AuthnContextClassRef authentication context to use for saml interaction

type AuthnRequest

type AuthnRequest struct {
	*RootXML

	XMLName                        xml.Name
	ProtocolBinding                string                 `xml:"ProtocolBinding,attr"`
	AssertionConsumerServiceURL    string                 `xml:"AssertionConsumerServiceURL,attr"`
	AssertionConsumerServiceIndex  int                    `xml:"AssertionConsumerServiceIndex,attr"`
	AttributeConsumingServiceIndex int                    `xml:"AttributeConsumingServiceIndex,attr"`
	NameIDPolicy                   NameIDPolicy           `xml:"NameIDPolicy"`
	IsPassive                      bool                   `xml:"IsPassive,attr"`
	RequestedAuthnContext          *RequestedAuthnContext `xml:"RequestedAuthnContext,omitempty"`
}

AuthnRequest saml authentication request

func ApplyAuthnRequest

func ApplyAuthnRequest(s Settings, r *AuthnRequest) *AuthnRequest

ApplyAuthnRequest returns an authentication request object based on SAML Settings passed in

func NewAuthnRequest

func NewAuthnRequest() *AuthnRequest

NewAuthnRequest get a new authentication request object

func ParseAuthnRequest

func ParseAuthnRequest(s Settings, b64RequestXML string) (*AuthnRequest, error)

ParseAuthnRequest as IDP, parse incoming authentication request

func (*AuthnRequest) CompressedEncodedSignedString

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

CompressedEncodedSignedString get compressed, base64 encoded and xml signed string representation of authentication request

func (*AuthnRequest) CompressedEncodedString

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

CompressedEncodedString get compressed and base64 encoded string representation of authentication request object

func (*AuthnRequest) EncodedSignedString

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

EncodedSignedString get base64 encoded and xml signed string representation of authentication request

func (*AuthnRequest) EncodedString

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

EncodedString get base64 encoded string representation of authentication request object

func (*AuthnRequest) SignedString

func (r *AuthnRequest) SignedString(s *Settings) (string, error)

SignedString get xml signed string representation of authentication request

func (*AuthnRequest) String

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

String get string representation of authentication request

func (*AuthnRequest) Validate

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

Validate authentication request

type AuthnStatement

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

AuthnStatement statement for session information

type CompressionSettings

type CompressionSettings struct {
	Request  bool
	Response bool
}

CompressionSettings to determine if requests and responses should be compressed

type Conditions

type Conditions struct {
	XMLName      xml.Name
	NotBefore    string `xml:",attr"`
	NotOnOrAfter string `xml:",attr"`
}

Conditions of assertion

type Document

type Document interface {
	String() (string, error)
	SignedString(s *Settings) (string, error)
}

Document interface for saml document methods

type EntityAttributes

type EntityAttributes struct {
	XMLName          xml.Name
	SAML             string      `xml:"xmlns:saml,attr"`
	EntityAttributes []Attribute `xml:"Attribute"` // should be array??
}

EntityAttributes TODO needs description

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"`
}

EntityDescriptor saml metadata descriptor

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"`
}

Extensions TODO needs description

type IdentityProviderSettings

type IdentityProviderSettings struct {
	SingleLogoutURL           string
	SingleSignOnURL           string
	SingleSignOnDescriptorURL string
	PublicCertPath            string
	PublicCertString          string
	NameIDFormat              string
	// contains filtered or unexported fields
}

IdentityProviderSettings to configure idp specific settings

type Issuer

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

Issuer request issuer

type KeyDescriptor

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

KeyDescriptor TODO needs description

type LogoutRequest

type LogoutRequest struct {
	*RootXML

	XMLName      xml.Name
	NameID       NameID       `xml:"NameID"`
	SessionIndex SessionIndex `xml:",omitempty"`
}

LogoutRequest saml logout request

func ApplyLogoutRequest

func ApplyLogoutRequest(settings *Settings, r *LogoutRequest, nameID string, sessionIndex string) *LogoutRequest

ApplyLogoutRequest entity as specified by provided parameters

func NewLogoutRequest

func NewLogoutRequest() *LogoutRequest

NewLogoutRequest create new logout request entity

func ParseLogoutRequest

func ParseLogoutRequest(s Settings, b64RequestXML string) (*LogoutRequest, error)

ParseLogoutRequest as IDP, parse incoming logout request

func (*LogoutRequest) SignedString

func (r *LogoutRequest) SignedString(s *Settings) (string, error)

SignedString get xml signed string representation of logout request

func (*LogoutRequest) String

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

String get string representation of logout request

type NameID

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

NameID information

type NameIDPolicy

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

NameIDPolicy policy for saml nameid

type RequestedAuthnContext

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

RequestedAuthnContext requested authentication context

type Response

type Response struct {
	*RootXML

	XMLName      xml.Name
	InResponseTo string    `xml:"InResponseTo,attr"`
	Assertion    Assertion `xml:"Assertion"`
	Status       Status    `xml:"Status"`
}

Response saml responses

func NewAuthnResponse

func NewAuthnResponse() *Response

NewAuthnResponse get new signed response object

func NewLogoutResponse

func NewLogoutResponse() *Response

NewLogoutResponse create new logout response entity

func ParseAuthnResponse

func ParseAuthnResponse(s Settings, b64ResponseXML string) (*Response, error)

ParseAuthnResponse as SP, parse incoming authentication response

func ParseLogoutResponse

func ParseLogoutResponse(s Settings, b64ResponseXML string) (*Response, error)

ParseLogoutResponse as SP, parse incoming logout response

func (*Response) AddAttribute

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

AddAttribute add strong attribute to the Response

func (*Response) CompressedEncodedSignedString

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

CompressedEncodedSignedString get compressed, base64 encoded and xml signed string representation of authentication response object

func (*Response) EncodedSignedString

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

EncodedSignedString get base64 encoded and xml signed string representation of authentication response object

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

GetAttributeValues from attribute name or FriendlyName. Return string slice of values.

func (*Response) SignedString

func (r *Response) SignedString(s *Settings) (string, error)

SignedString get xml signed string representation of response object

func (*Response) String

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

String get string representation of response object

func (*Response) Validate

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

Validate saml response

type RootXML

type RootXML struct {
	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"`
	Destination  string              `xml:"Destination,attr"`
	IssueInstant string              `xml:"IssueInstant,attr"`
	Issuer       Issuer              `xml:"Issuer"`
	Signature    *packager.Signature `xml:"Signature,omitempty"`
	// contains filtered or unexported fields
}

RootXML saml root xml data Although all root xml elements have XMLName, it is not marshalled properly without explicit addition to the type

type SPSSODescriptor

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

SPSSODescriptor TODO needs description

type ServiceProviderSettings

type ServiceProviderSettings struct {
	EntityID                    string
	PublicCertPath              string
	PublicCertString            string
	PrivateKeyPath              string
	PrivateKeyString            string
	AssertionConsumerServiceURL string
	SingleLogoutServiceURL      string
	SignRequest                 bool
	IsPassive                   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.

type SessionIndex

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

SessionIndex request session information

type Settings

type Settings struct {
	SP       ServiceProviderSettings
	IDP      IdentityProviderSettings
	Compress CompressionSettings
	// contains filtered or unexported fields
}

Settings to configure saml properties for one idp and/or one sp. If you need to configure multipe IDPs for an SP then configure multiple instances of this object

func (*Settings) GetEntityDescriptor

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

GetEntityDescriptor get saml entity metadata XML as specified by http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml1x-metadata-cs-01.html

func (*Settings) IDPPublicCert

func (s *Settings) IDPPublicCert() string

IDPPublicCert get loaded idp public certificate in pem format

func (*Settings) Init

func (s *Settings) Init() (err error)

Init settings and load configuration files as needed This will panic on error as SP/IDP fails to load

func (*Settings) SPPrivateKey

func (s *Settings) SPPrivateKey() string

SPPrivateKey get loaded sp private key in pem format

func (*Settings) SPPublicCert

func (s *Settings) SPPublicCert() string

SPPublicCert get loaded sp public certificate data

type SingleLogoutService

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

SingleLogoutService logout service metadata

type Status

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

Status of response

type StatusCode

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

StatusCode TODO needs description

type Subject

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

Subject of assertion

type SubjectConfirmation

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

SubjectConfirmation TODO needs description

type SubjectConfirmationData

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

SubjectConfirmationData TODO needs description

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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