identity_provider.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. package saml
  2. import (
  3. "bytes"
  4. "compress/flate"
  5. "crypto"
  6. "crypto/tls"
  7. "crypto/x509"
  8. "encoding/base64"
  9. "encoding/xml"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "net/http"
  14. "net/url"
  15. "os"
  16. "regexp"
  17. "strconv"
  18. "text/template"
  19. "time"
  20. "github.com/beevik/etree"
  21. "github.com/crewjam/saml/logger"
  22. "github.com/crewjam/saml/xmlenc"
  23. dsig "github.com/russellhaering/goxmldsig"
  24. )
  25. // Session represents a user session. It is returned by the
  26. // SessionProvider implementation's GetSession method. Fields here
  27. // are used to set fields in the SAML assertion.
  28. type Session struct {
  29. ID string
  30. CreateTime time.Time
  31. ExpireTime time.Time
  32. Index string
  33. NameID string
  34. Groups []string
  35. UserName string
  36. UserEmail string
  37. UserCommonName string
  38. UserSurname string
  39. UserGivenName string
  40. }
  41. // SessionProvider is an interface used by IdentityProvider to determine the
  42. // Session associated with a request. For an example implementation, see
  43. // GetSession in the samlidp package.
  44. type SessionProvider interface {
  45. // GetSession returns the remote user session associated with the http.Request.
  46. //
  47. // If (and only if) the request is not associated with a session then GetSession
  48. // must complete the HTTP request and return nil.
  49. GetSession(w http.ResponseWriter, r *http.Request, req *IdpAuthnRequest) *Session
  50. }
  51. // ServiceProviderProvider is an interface used by IdentityProvider to look up
  52. // service provider metadata for a request.
  53. type ServiceProviderProvider interface {
  54. // GetServiceProvider returns the Service Provider metadata for the
  55. // service provider ID, which is typically the service provider's
  56. // metadata URL. If an appropriate service provider cannot be found then
  57. // the returned error must be os.ErrNotExist.
  58. GetServiceProvider(r *http.Request, serviceProviderID string) (*EntityDescriptor, error)
  59. }
  60. // AssertionMaker is an interface used by IdentityProvider to construct the
  61. // assertion for a request. The default implementation is DefaultAssertionMaker,
  62. // which is used if not AssertionMaker is specified.
  63. type AssertionMaker interface {
  64. // MakeAssertion constructs an assertion from session and the request and
  65. // assigns it to req.Assertion.
  66. MakeAssertion(req *IdpAuthnRequest, session *Session) error
  67. }
  68. // IdentityProvider implements the SAML Identity Provider role (IDP).
  69. //
  70. // An identity provider receives SAML assertion requests and responds
  71. // with SAML Assertions.
  72. //
  73. // You must provide a keypair that is used to
  74. // sign assertions.
  75. //
  76. // You must provide an implementation of ServiceProviderProvider which
  77. // returns
  78. //
  79. // You must provide an implementation of the SessionProvider which
  80. // handles the actual authentication (i.e. prompting for a username
  81. // and password).
  82. type IdentityProvider struct {
  83. Key crypto.PrivateKey
  84. Logger logger.Interface
  85. Certificate *x509.Certificate
  86. Intermediates []*x509.Certificate
  87. MetadataURL url.URL
  88. SSOURL url.URL
  89. LogoutURL url.URL
  90. ServiceProviderProvider ServiceProviderProvider
  91. SessionProvider SessionProvider
  92. AssertionMaker AssertionMaker
  93. SignatureMethod string
  94. }
  95. // Metadata returns the metadata structure for this identity provider.
  96. func (idp *IdentityProvider) Metadata() *EntityDescriptor {
  97. certStr := base64.StdEncoding.EncodeToString(idp.Certificate.Raw)
  98. ed := &EntityDescriptor{
  99. EntityID: idp.MetadataURL.String(),
  100. ValidUntil: TimeNow().Add(DefaultValidDuration),
  101. CacheDuration: DefaultValidDuration,
  102. IDPSSODescriptors: []IDPSSODescriptor{
  103. IDPSSODescriptor{
  104. SSODescriptor: SSODescriptor{
  105. RoleDescriptor: RoleDescriptor{
  106. ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
  107. KeyDescriptors: []KeyDescriptor{
  108. {
  109. Use: "signing",
  110. KeyInfo: KeyInfo{
  111. Certificate: certStr,
  112. },
  113. },
  114. {
  115. Use: "encryption",
  116. KeyInfo: KeyInfo{
  117. Certificate: certStr,
  118. },
  119. EncryptionMethods: []EncryptionMethod{
  120. {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc"},
  121. {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes192-cbc"},
  122. {Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"},
  123. {Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"},
  124. },
  125. },
  126. },
  127. },
  128. NameIDFormats: []NameIDFormat{NameIDFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient")},
  129. },
  130. SingleSignOnServices: []Endpoint{
  131. {
  132. Binding: HTTPRedirectBinding,
  133. Location: idp.SSOURL.String(),
  134. },
  135. {
  136. Binding: HTTPPostBinding,
  137. Location: idp.SSOURL.String(),
  138. },
  139. },
  140. },
  141. },
  142. }
  143. if idp.LogoutURL.String() != "" {
  144. ed.IDPSSODescriptors[0].SSODescriptor.SingleLogoutServices = []Endpoint{
  145. {
  146. Binding: HTTPRedirectBinding,
  147. Location: idp.LogoutURL.String(),
  148. },
  149. }
  150. }
  151. return ed
  152. }
  153. // Handler returns an http.Handler that serves the metadata and SSO
  154. // URLs
  155. func (idp *IdentityProvider) Handler() http.Handler {
  156. mux := http.NewServeMux()
  157. mux.HandleFunc(idp.MetadataURL.Path, idp.ServeMetadata)
  158. mux.HandleFunc(idp.SSOURL.Path, idp.ServeSSO)
  159. return mux
  160. }
  161. // ServeMetadata is an http.HandlerFunc that serves the IDP metadata
  162. func (idp *IdentityProvider) ServeMetadata(w http.ResponseWriter, r *http.Request) {
  163. buf, _ := xml.MarshalIndent(idp.Metadata(), "", " ")
  164. w.Header().Set("Content-Type", "application/samlmetadata+xml")
  165. w.Write(buf)
  166. }
  167. // ServeSSO handles SAML auth requests.
  168. //
  169. // When it gets a request for a user that does not have a valid session,
  170. // then it prompts the user via XXX.
  171. //
  172. // If the session already exists, then it produces a SAML assertion and
  173. // returns an HTTP response according to the specified binding. The
  174. // only supported binding right now is the HTTP-POST binding which returns
  175. // an HTML form in the appropriate format with Javascript to automatically
  176. // submit that form the to service provider's Assertion Customer Service
  177. // endpoint.
  178. //
  179. // If the SAML request is invalid or cannot be verified a simple StatusBadRequest
  180. // response is sent.
  181. //
  182. // If the assertion cannot be created or returned, a StatusInternalServerError
  183. // response is sent.
  184. func (idp *IdentityProvider) ServeSSO(w http.ResponseWriter, r *http.Request) {
  185. req, err := NewIdpAuthnRequest(idp, r)
  186. if err != nil {
  187. idp.Logger.Printf("failed to parse request: %s", err)
  188. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  189. return
  190. }
  191. if err := req.Validate(); err != nil {
  192. idp.Logger.Printf("failed to validate request: %s", err)
  193. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  194. return
  195. }
  196. // TODO(ross): we must check that the request ID has not been previously
  197. // issued.
  198. session := idp.SessionProvider.GetSession(w, r, req)
  199. if session == nil {
  200. return
  201. }
  202. assertionMaker := idp.AssertionMaker
  203. if assertionMaker == nil {
  204. assertionMaker = DefaultAssertionMaker{}
  205. }
  206. if err := assertionMaker.MakeAssertion(req, session); err != nil {
  207. idp.Logger.Printf("failed to make assertion: %s", err)
  208. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  209. return
  210. }
  211. if err := req.WriteResponse(w); err != nil {
  212. idp.Logger.Printf("failed to write response: %s", err)
  213. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  214. return
  215. }
  216. }
  217. // ServeIDPInitiated handes an IDP-initiated authorization request. Requests of this
  218. // type require us to know a registered service provider and (optionally) the RelayState
  219. // that will be passed to the application.
  220. func (idp *IdentityProvider) ServeIDPInitiated(w http.ResponseWriter, r *http.Request, serviceProviderID string, relayState string) {
  221. req := &IdpAuthnRequest{
  222. IDP: idp,
  223. HTTPRequest: r,
  224. RelayState: relayState,
  225. Now: TimeNow(),
  226. }
  227. session := idp.SessionProvider.GetSession(w, r, req)
  228. if session == nil {
  229. // If GetSession returns nil, it must have written an HTTP response, per the interface
  230. // (this is probably because it drew a login form or something)
  231. return
  232. }
  233. var err error
  234. req.ServiceProviderMetadata, err = idp.ServiceProviderProvider.GetServiceProvider(r, serviceProviderID)
  235. if err == os.ErrNotExist {
  236. idp.Logger.Printf("cannot find service provider: %s", serviceProviderID)
  237. http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
  238. return
  239. } else if err != nil {
  240. idp.Logger.Printf("cannot find service provider %s: %v", serviceProviderID, err)
  241. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  242. return
  243. }
  244. // find an ACS endpoint that we can use
  245. for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
  246. for _, endpoint := range spssoDescriptor.AssertionConsumerServices {
  247. if endpoint.Binding == HTTPPostBinding {
  248. req.ACSEndpoint = &endpoint
  249. req.SPSSODescriptor = &spssoDescriptor
  250. break
  251. }
  252. }
  253. if req.ACSEndpoint != nil {
  254. break
  255. }
  256. }
  257. if req.ACSEndpoint == nil {
  258. idp.Logger.Printf("saml metadata does not contain an Assertion Customer Service url")
  259. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  260. return
  261. }
  262. assertionMaker := idp.AssertionMaker
  263. if assertionMaker == nil {
  264. assertionMaker = DefaultAssertionMaker{}
  265. }
  266. if err := assertionMaker.MakeAssertion(req, session); err != nil {
  267. idp.Logger.Printf("failed to make assertion: %s", err)
  268. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  269. return
  270. }
  271. if err := req.WriteResponse(w); err != nil {
  272. idp.Logger.Printf("failed to write response: %s", err)
  273. http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  274. return
  275. }
  276. }
  277. // IdpAuthnRequest is used by IdentityProvider to handle a single authentication request.
  278. type IdpAuthnRequest struct {
  279. IDP *IdentityProvider
  280. HTTPRequest *http.Request
  281. RelayState string
  282. RequestBuffer []byte
  283. Request AuthnRequest
  284. ServiceProviderMetadata *EntityDescriptor
  285. SPSSODescriptor *SPSSODescriptor
  286. ACSEndpoint *IndexedEndpoint
  287. Assertion *Assertion
  288. AssertionEl *etree.Element
  289. ResponseEl *etree.Element
  290. Now time.Time
  291. }
  292. // NewIdpAuthnRequest returns a new IdpAuthnRequest for the given HTTP request to the authorization
  293. // service.
  294. func NewIdpAuthnRequest(idp *IdentityProvider, r *http.Request) (*IdpAuthnRequest, error) {
  295. req := &IdpAuthnRequest{
  296. IDP: idp,
  297. HTTPRequest: r,
  298. Now: TimeNow(),
  299. }
  300. switch r.Method {
  301. case "GET":
  302. compressedRequest, err := base64.StdEncoding.DecodeString(r.URL.Query().Get("SAMLRequest"))
  303. if err != nil {
  304. return nil, fmt.Errorf("cannot decode request: %s", err)
  305. }
  306. req.RequestBuffer, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(compressedRequest)))
  307. if err != nil {
  308. return nil, fmt.Errorf("cannot decompress request: %s", err)
  309. }
  310. req.RelayState = r.URL.Query().Get("RelayState")
  311. case "POST":
  312. if err := r.ParseForm(); err != nil {
  313. return nil, err
  314. }
  315. var err error
  316. req.RequestBuffer, err = base64.StdEncoding.DecodeString(r.PostForm.Get("SAMLRequest"))
  317. if err != nil {
  318. return nil, err
  319. }
  320. req.RelayState = r.PostForm.Get("RelayState")
  321. default:
  322. return nil, fmt.Errorf("method not allowed")
  323. }
  324. return req, nil
  325. }
  326. // Validate checks that the authentication request is valid and assigns
  327. // the AuthnRequest and Metadata properties. Returns a non-nil error if the
  328. // request is not valid.
  329. func (req *IdpAuthnRequest) Validate() error {
  330. if err := xml.Unmarshal(req.RequestBuffer, &req.Request); err != nil {
  331. return err
  332. }
  333. // We always have exactly one IDP SSO descriptor
  334. if len(req.IDP.Metadata().IDPSSODescriptors) != 1 {
  335. panic("expected exactly one IDP SSO descriptor in IDP metadata")
  336. }
  337. idpSsoDescriptor := req.IDP.Metadata().IDPSSODescriptors[0]
  338. // TODO(ross): support signed authn requests
  339. // For now we do the safe thing and fail in the case where we think
  340. // requests might be signed.
  341. if idpSsoDescriptor.WantAuthnRequestsSigned != nil && *idpSsoDescriptor.WantAuthnRequestsSigned {
  342. return fmt.Errorf("Authn request signature checking is not currently supported")
  343. }
  344. // In http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf §3.4.5.2
  345. // we get a description of the Destination attribute:
  346. //
  347. // If the message is signed, the Destination XML attribute in the root SAML
  348. // element of the protocol message MUST contain the URL to which the sender
  349. // has instructed the user agent to deliver the message. The recipient MUST
  350. // then verify that the value matches the location at which the message has
  351. // been received.
  352. //
  353. // We require the destination be correct either (a) if signing is enabled or
  354. // (b) if it was provided.
  355. mustHaveDestination := idpSsoDescriptor.WantAuthnRequestsSigned != nil && *idpSsoDescriptor.WantAuthnRequestsSigned
  356. mustHaveDestination = mustHaveDestination || req.Request.Destination != ""
  357. if mustHaveDestination {
  358. if req.Request.Destination != req.IDP.SSOURL.String() {
  359. return fmt.Errorf("expected destination to be %q, not %q", req.IDP.SSOURL.String(), req.Request.Destination)
  360. }
  361. }
  362. if req.Request.IssueInstant.Add(MaxIssueDelay).Before(req.Now) {
  363. return fmt.Errorf("request expired at %s",
  364. req.Request.IssueInstant.Add(MaxIssueDelay))
  365. }
  366. if req.Request.Version != "2.0" {
  367. return fmt.Errorf("expected SAML request version 2.0 got %v", req.Request.Version)
  368. }
  369. // find the service provider
  370. serviceProviderID := req.Request.Issuer.Value
  371. serviceProvider, err := req.IDP.ServiceProviderProvider.GetServiceProvider(req.HTTPRequest, serviceProviderID)
  372. if err == os.ErrNotExist {
  373. return fmt.Errorf("cannot handle request from unknown service provider %s", serviceProviderID)
  374. } else if err != nil {
  375. return fmt.Errorf("cannot find service provider %s: %v", serviceProviderID, err)
  376. }
  377. req.ServiceProviderMetadata = serviceProvider
  378. // Check that the ACS URL matches an ACS endpoint in the SP metadata.
  379. if err := req.getACSEndpoint(); err != nil {
  380. return fmt.Errorf("cannot find assertion consumer service: %v", err)
  381. }
  382. return nil
  383. }
  384. func (req *IdpAuthnRequest) getACSEndpoint() error {
  385. if req.Request.AssertionConsumerServiceIndex != "" {
  386. for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
  387. for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
  388. if strconv.Itoa(spAssertionConsumerService.Index) == req.Request.AssertionConsumerServiceIndex {
  389. req.SPSSODescriptor = &spssoDescriptor
  390. req.ACSEndpoint = &spAssertionConsumerService
  391. return nil
  392. }
  393. }
  394. }
  395. }
  396. if req.Request.AssertionConsumerServiceURL != "" {
  397. for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
  398. for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
  399. if spAssertionConsumerService.Location == req.Request.AssertionConsumerServiceURL {
  400. req.SPSSODescriptor = &spssoDescriptor
  401. req.ACSEndpoint = &spAssertionConsumerService
  402. return nil
  403. }
  404. }
  405. }
  406. }
  407. // Some service providers, like the Microsoft Azure AD service provider, issue
  408. // assertion requests that don't specify an ACS url at all.
  409. if req.Request.AssertionConsumerServiceURL == "" && req.Request.AssertionConsumerServiceIndex == "" {
  410. // find a default ACS binding in the metadata that we can use
  411. for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
  412. for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
  413. if spAssertionConsumerService.IsDefault != nil && *spAssertionConsumerService.IsDefault {
  414. switch spAssertionConsumerService.Binding {
  415. case HTTPPostBinding, HTTPRedirectBinding:
  416. req.SPSSODescriptor = &spssoDescriptor
  417. req.ACSEndpoint = &spAssertionConsumerService
  418. return nil
  419. }
  420. }
  421. }
  422. }
  423. // if we can't find a default, use *any* ACS binding
  424. for _, spssoDescriptor := range req.ServiceProviderMetadata.SPSSODescriptors {
  425. for _, spAssertionConsumerService := range spssoDescriptor.AssertionConsumerServices {
  426. switch spAssertionConsumerService.Binding {
  427. case HTTPPostBinding, HTTPRedirectBinding:
  428. req.SPSSODescriptor = &spssoDescriptor
  429. req.ACSEndpoint = &spAssertionConsumerService
  430. return nil
  431. }
  432. }
  433. }
  434. }
  435. return os.ErrNotExist // no ACS url found or specified
  436. }
  437. // DefaultAssertionMaker produces a SAML assertion for the
  438. // given request and assigns it to req.Assertion.
  439. type DefaultAssertionMaker struct {
  440. }
  441. // MakeAssertion implements AssertionMaker. It produces a SAML assertion from the
  442. // given request and assigns it to req.Assertion.
  443. func (DefaultAssertionMaker) MakeAssertion(req *IdpAuthnRequest, session *Session) error {
  444. attributes := []Attribute{}
  445. var attributeConsumingService *AttributeConsumingService
  446. for _, acs := range req.SPSSODescriptor.AttributeConsumingServices {
  447. if acs.IsDefault != nil && *acs.IsDefault {
  448. attributeConsumingService = &acs
  449. break
  450. }
  451. }
  452. if attributeConsumingService == nil {
  453. for _, acs := range req.SPSSODescriptor.AttributeConsumingServices {
  454. attributeConsumingService = &acs
  455. break
  456. }
  457. }
  458. if attributeConsumingService == nil {
  459. attributeConsumingService = &AttributeConsumingService{}
  460. }
  461. for _, requestedAttribute := range attributeConsumingService.RequestedAttributes {
  462. if requestedAttribute.NameFormat == "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" || requestedAttribute.NameFormat == "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified" {
  463. attrName := requestedAttribute.Name
  464. attrName = regexp.MustCompile("[^A-Za-z0-9]+").ReplaceAllString(attrName, "")
  465. switch attrName {
  466. case "email", "emailaddress":
  467. attributes = append(attributes, Attribute{
  468. FriendlyName: requestedAttribute.FriendlyName,
  469. Name: requestedAttribute.Name,
  470. NameFormat: requestedAttribute.NameFormat,
  471. Values: []AttributeValue{{
  472. Type: "xs:string",
  473. Value: session.UserEmail,
  474. }},
  475. })
  476. case "name", "fullname", "cn", "commonname":
  477. attributes = append(attributes, Attribute{
  478. FriendlyName: requestedAttribute.FriendlyName,
  479. Name: requestedAttribute.Name,
  480. NameFormat: requestedAttribute.NameFormat,
  481. Values: []AttributeValue{{
  482. Type: "xs:string",
  483. Value: session.UserCommonName,
  484. }},
  485. })
  486. case "givenname", "firstname":
  487. attributes = append(attributes, Attribute{
  488. FriendlyName: requestedAttribute.FriendlyName,
  489. Name: requestedAttribute.Name,
  490. NameFormat: requestedAttribute.NameFormat,
  491. Values: []AttributeValue{{
  492. Type: "xs:string",
  493. Value: session.UserGivenName,
  494. }},
  495. })
  496. case "surname", "lastname", "familyname":
  497. attributes = append(attributes, Attribute{
  498. FriendlyName: requestedAttribute.FriendlyName,
  499. Name: requestedAttribute.Name,
  500. NameFormat: requestedAttribute.NameFormat,
  501. Values: []AttributeValue{{
  502. Type: "xs:string",
  503. Value: session.UserSurname,
  504. }},
  505. })
  506. case "uid", "user", "userid":
  507. attributes = append(attributes, Attribute{
  508. FriendlyName: requestedAttribute.FriendlyName,
  509. Name: requestedAttribute.Name,
  510. NameFormat: requestedAttribute.NameFormat,
  511. Values: []AttributeValue{{
  512. Type: "xs:string",
  513. Value: session.UserName,
  514. }},
  515. })
  516. }
  517. }
  518. }
  519. if session.UserName != "" {
  520. attributes = append(attributes, Attribute{
  521. FriendlyName: "uid",
  522. Name: "urn:oid:0.9.2342.19200300.100.1.1",
  523. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  524. Values: []AttributeValue{{
  525. Type: "xs:string",
  526. Value: session.UserName,
  527. }},
  528. })
  529. }
  530. if session.UserEmail != "" {
  531. attributes = append(attributes, Attribute{
  532. FriendlyName: "eduPersonPrincipalName",
  533. Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
  534. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  535. Values: []AttributeValue{{
  536. Type: "xs:string",
  537. Value: session.UserEmail,
  538. }},
  539. })
  540. }
  541. if session.UserSurname != "" {
  542. attributes = append(attributes, Attribute{
  543. FriendlyName: "sn",
  544. Name: "urn:oid:2.5.4.4",
  545. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  546. Values: []AttributeValue{{
  547. Type: "xs:string",
  548. Value: session.UserSurname,
  549. }},
  550. })
  551. }
  552. if session.UserGivenName != "" {
  553. attributes = append(attributes, Attribute{
  554. FriendlyName: "givenName",
  555. Name: "urn:oid:2.5.4.42",
  556. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  557. Values: []AttributeValue{{
  558. Type: "xs:string",
  559. Value: session.UserGivenName,
  560. }},
  561. })
  562. }
  563. if session.UserCommonName != "" {
  564. attributes = append(attributes, Attribute{
  565. FriendlyName: "cn",
  566. Name: "urn:oid:2.5.4.3",
  567. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  568. Values: []AttributeValue{{
  569. Type: "xs:string",
  570. Value: session.UserCommonName,
  571. }},
  572. })
  573. }
  574. if len(session.Groups) != 0 {
  575. groupMemberAttributeValues := []AttributeValue{}
  576. for _, group := range session.Groups {
  577. groupMemberAttributeValues = append(groupMemberAttributeValues, AttributeValue{
  578. Type: "xs:string",
  579. Value: group,
  580. })
  581. }
  582. attributes = append(attributes, Attribute{
  583. FriendlyName: "eduPersonAffiliation",
  584. Name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.1",
  585. NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
  586. Values: groupMemberAttributeValues,
  587. })
  588. }
  589. // allow for some clock skew in the validity period using the
  590. // issuer's apparent clock.
  591. notBefore := req.Now.Add(-1 * MaxClockSkew)
  592. notOnOrAfterAfter := req.Now.Add(MaxIssueDelay)
  593. if notBefore.Before(req.Request.IssueInstant) {
  594. notBefore = req.Request.IssueInstant
  595. notOnOrAfterAfter = notBefore.Add(MaxIssueDelay)
  596. }
  597. req.Assertion = &Assertion{
  598. ID: fmt.Sprintf("id-%x", randomBytes(20)),
  599. IssueInstant: TimeNow(),
  600. Version: "2.0",
  601. Issuer: Issuer{
  602. Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
  603. Value: req.IDP.Metadata().EntityID,
  604. },
  605. Subject: &Subject{
  606. NameID: &NameID{
  607. Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
  608. NameQualifier: req.IDP.Metadata().EntityID,
  609. SPNameQualifier: req.ServiceProviderMetadata.EntityID,
  610. Value: session.NameID,
  611. },
  612. SubjectConfirmations: []SubjectConfirmation{
  613. SubjectConfirmation{
  614. Method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
  615. SubjectConfirmationData: &SubjectConfirmationData{
  616. Address: req.HTTPRequest.RemoteAddr,
  617. InResponseTo: req.Request.ID,
  618. NotOnOrAfter: req.Now.Add(MaxIssueDelay),
  619. Recipient: req.ACSEndpoint.Location,
  620. },
  621. },
  622. },
  623. },
  624. Conditions: &Conditions{
  625. NotBefore: notBefore,
  626. NotOnOrAfter: notOnOrAfterAfter,
  627. AudienceRestrictions: []AudienceRestriction{
  628. AudienceRestriction{
  629. Audience: Audience{Value: req.ServiceProviderMetadata.EntityID},
  630. },
  631. },
  632. },
  633. AuthnStatements: []AuthnStatement{
  634. AuthnStatement{
  635. AuthnInstant: session.CreateTime,
  636. SessionIndex: session.Index,
  637. SubjectLocality: &SubjectLocality{
  638. Address: req.HTTPRequest.RemoteAddr,
  639. },
  640. AuthnContext: AuthnContext{
  641. AuthnContextClassRef: &AuthnContextClassRef{
  642. Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  643. },
  644. },
  645. },
  646. },
  647. AttributeStatements: []AttributeStatement{
  648. AttributeStatement{
  649. Attributes: attributes,
  650. },
  651. },
  652. }
  653. return nil
  654. }
  655. // The Canonicalizer prefix list MUST be empty. Various implementations
  656. // (maybe ours?) do not appear to support non-empty prefix lists in XML C14N.
  657. const canonicalizerPrefixList = ""
  658. // MakeAssertionEl sets `AssertionEl` to a signed, possibly encrypted, version of `Assertion`.
  659. func (req *IdpAuthnRequest) MakeAssertionEl() error {
  660. keyPair := tls.Certificate{
  661. Certificate: [][]byte{req.IDP.Certificate.Raw},
  662. PrivateKey: req.IDP.Key,
  663. Leaf: req.IDP.Certificate,
  664. }
  665. for _, cert := range req.IDP.Intermediates {
  666. keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
  667. }
  668. keyStore := dsig.TLSCertKeyStore(keyPair)
  669. signatureMethod := req.IDP.SignatureMethod
  670. if signatureMethod == "" {
  671. signatureMethod = dsig.RSASHA1SignatureMethod
  672. }
  673. signingContext := dsig.NewDefaultSigningContext(keyStore)
  674. signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList)
  675. if err := signingContext.SetSignatureMethod(signatureMethod); err != nil {
  676. return err
  677. }
  678. assertionEl := req.Assertion.Element()
  679. signedAssertionEl, err := signingContext.SignEnveloped(assertionEl)
  680. if err != nil {
  681. return err
  682. }
  683. sigEl := signedAssertionEl.Child[len(signedAssertionEl.Child)-1]
  684. req.Assertion.Signature = sigEl.(*etree.Element)
  685. signedAssertionEl = req.Assertion.Element()
  686. certBuf, err := req.getSPEncryptionCert()
  687. if err == os.ErrNotExist {
  688. req.AssertionEl = signedAssertionEl
  689. return nil
  690. } else if err != nil {
  691. return err
  692. }
  693. var signedAssertionBuf []byte
  694. {
  695. doc := etree.NewDocument()
  696. doc.SetRoot(signedAssertionEl)
  697. signedAssertionBuf, err = doc.WriteToBytes()
  698. if err != nil {
  699. return err
  700. }
  701. }
  702. encryptor := xmlenc.OAEP()
  703. encryptor.BlockCipher = xmlenc.AES128CBC
  704. encryptor.DigestMethod = &xmlenc.SHA1
  705. encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf)
  706. if err != nil {
  707. return err
  708. }
  709. encryptedDataEl.CreateAttr("Type", "http://www.w3.org/2001/04/xmlenc#Element")
  710. encryptedAssertionEl := etree.NewElement("saml:EncryptedAssertion")
  711. encryptedAssertionEl.AddChild(encryptedDataEl)
  712. req.AssertionEl = encryptedAssertionEl
  713. return nil
  714. }
  715. // WriteResponse writes the `Response` to the http.ResponseWriter. If
  716. // `Response` is not already set, it calls MakeResponse to produce it.
  717. func (req *IdpAuthnRequest) WriteResponse(w http.ResponseWriter) error {
  718. if req.ResponseEl == nil {
  719. if err := req.MakeResponse(); err != nil {
  720. return err
  721. }
  722. }
  723. doc := etree.NewDocument()
  724. doc.SetRoot(req.ResponseEl)
  725. responseBuf, err := doc.WriteToBytes()
  726. if err != nil {
  727. return err
  728. }
  729. // the only supported binding is the HTTP-POST binding
  730. switch req.ACSEndpoint.Binding {
  731. case HTTPPostBinding:
  732. tmpl := template.Must(template.New("saml-post-form").Parse(`<html>` +
  733. `<form method="post" action="{{.URL}}" id="SAMLResponseForm">` +
  734. `<input type="hidden" name="SAMLResponse" value="{{.SAMLResponse}}" />` +
  735. `<input type="hidden" name="RelayState" value="{{.RelayState}}" />` +
  736. `<input id="SAMLSubmitButton" type="submit" value="Continue" />` +
  737. `</form>` +
  738. `<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden';</script>` +
  739. `<script>document.getElementById('SAMLResponseForm').submit();</script>` +
  740. `</html>`))
  741. data := struct {
  742. URL string
  743. SAMLResponse string
  744. RelayState string
  745. }{
  746. URL: req.ACSEndpoint.Location,
  747. SAMLResponse: base64.StdEncoding.EncodeToString(responseBuf),
  748. RelayState: req.RelayState,
  749. }
  750. buf := bytes.NewBuffer(nil)
  751. if err := tmpl.Execute(buf, data); err != nil {
  752. return err
  753. }
  754. if _, err := io.Copy(w, buf); err != nil {
  755. return err
  756. }
  757. return nil
  758. default:
  759. return fmt.Errorf("%s: unsupported binding %s",
  760. req.ServiceProviderMetadata.EntityID,
  761. req.ACSEndpoint.Binding)
  762. }
  763. }
  764. // getSPEncryptionCert returns the certificate which we can use to encrypt things
  765. // to the SP in PEM format, or nil if no such certificate is found.
  766. func (req *IdpAuthnRequest) getSPEncryptionCert() (*x509.Certificate, error) {
  767. certStr := ""
  768. for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors {
  769. if keyDescriptor.Use == "encryption" {
  770. certStr = keyDescriptor.KeyInfo.Certificate
  771. break
  772. }
  773. }
  774. // If there are no certs explicitly labeled for encryption, return the first
  775. // non-empty cert we find.
  776. if certStr == "" {
  777. for _, keyDescriptor := range req.SPSSODescriptor.KeyDescriptors {
  778. if keyDescriptor.Use == "" && keyDescriptor.KeyInfo.Certificate != "" {
  779. certStr = keyDescriptor.KeyInfo.Certificate
  780. break
  781. }
  782. }
  783. }
  784. if certStr == "" {
  785. return nil, os.ErrNotExist
  786. }
  787. // cleanup whitespace and re-encode a PEM
  788. certStr = regexp.MustCompile(`\s+`).ReplaceAllString(certStr, "")
  789. certBytes, err := base64.StdEncoding.DecodeString(certStr)
  790. if err != nil {
  791. return nil, fmt.Errorf("cannot decode certificate base64: %v", err)
  792. }
  793. cert, err := x509.ParseCertificate(certBytes)
  794. if err != nil {
  795. return nil, fmt.Errorf("cannot parse certificate: %v", err)
  796. }
  797. return cert, nil
  798. }
  799. // unmarshalEtreeHack parses `el` and sets values in the structure `v`.
  800. //
  801. // This is a hack -- it first serializes the element, then uses xml.Unmarshal.
  802. func unmarshalEtreeHack(el *etree.Element, v interface{}) error {
  803. doc := etree.NewDocument()
  804. doc.SetRoot(el)
  805. buf, err := doc.WriteToBytes()
  806. if err != nil {
  807. return err
  808. }
  809. return xml.Unmarshal(buf, v)
  810. }
  811. // MakeResponse creates and assigns a new SAML response in ResponseEl. `Assertion` must
  812. // be non-nil. If MakeAssertionEl() has not been called, this function calls it for
  813. // you.
  814. func (req *IdpAuthnRequest) MakeResponse() error {
  815. if req.AssertionEl == nil {
  816. if err := req.MakeAssertionEl(); err != nil {
  817. return err
  818. }
  819. }
  820. response := &Response{
  821. Destination: req.ACSEndpoint.Location,
  822. ID: fmt.Sprintf("id-%x", randomBytes(20)),
  823. InResponseTo: req.Request.ID,
  824. IssueInstant: req.Now,
  825. Version: "2.0",
  826. Issuer: &Issuer{
  827. Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
  828. Value: req.IDP.MetadataURL.String(),
  829. },
  830. Status: Status{
  831. StatusCode: StatusCode{
  832. Value: StatusSuccess,
  833. },
  834. },
  835. }
  836. responseEl := response.Element()
  837. responseEl.AddChild(req.AssertionEl) // AssertionEl either an EncryptedAssertion or Assertion element
  838. // Sign the response element (we've already signed the Assertion element)
  839. {
  840. keyPair := tls.Certificate{
  841. Certificate: [][]byte{req.IDP.Certificate.Raw},
  842. PrivateKey: req.IDP.Key,
  843. Leaf: req.IDP.Certificate,
  844. }
  845. for _, cert := range req.IDP.Intermediates {
  846. keyPair.Certificate = append(keyPair.Certificate, cert.Raw)
  847. }
  848. keyStore := dsig.TLSCertKeyStore(keyPair)
  849. signatureMethod := req.IDP.SignatureMethod
  850. if signatureMethod == "" {
  851. signatureMethod = dsig.RSASHA1SignatureMethod
  852. }
  853. signingContext := dsig.NewDefaultSigningContext(keyStore)
  854. signingContext.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(canonicalizerPrefixList)
  855. if err := signingContext.SetSignatureMethod(signatureMethod); err != nil {
  856. return err
  857. }
  858. signedResponseEl, err := signingContext.SignEnveloped(responseEl)
  859. if err != nil {
  860. return err
  861. }
  862. sigEl := signedResponseEl.ChildElements()[len(signedResponseEl.ChildElements())-1]
  863. response.Signature = sigEl
  864. responseEl = response.Element()
  865. responseEl.AddChild(req.AssertionEl)
  866. }
  867. req.ResponseEl = responseEl
  868. return nil
  869. }