1package saml2
2
3import (
4	"encoding/base64"
5	"sync"
6	"time"
7
8	"github.com/mattermost/gosaml2/types"
9	dsig "github.com/russellhaering/goxmldsig"
10	dsigtypes "github.com/russellhaering/goxmldsig/types"
11)
12
13type ErrSaml struct {
14	Message string
15	System  error
16}
17
18func (serr ErrSaml) Error() string {
19	if serr.Message != "" {
20		return serr.Message
21	}
22	return "SAML error"
23}
24
25type SAMLServiceProvider struct {
26	IdentityProviderSSOURL string
27	IdentityProviderIssuer string
28
29	AssertionConsumerServiceURL string
30	ServiceProviderIssuer       string
31
32	SignAuthnRequests              bool
33	SignAuthnRequestsAlgorithm     string
34	SignAuthnRequestsCanonicalizer dsig.Canonicalizer
35
36	// RequestedAuthnContext allows service providers to require that the identity
37	// provider use specific authentication mechanisms. Leaving this unset will
38	// permit the identity provider to choose the auth method. To maximize compatibility
39	// with identity providers it is recommended to leave this unset.
40	RequestedAuthnContext   *RequestedAuthnContext
41	AudienceURI             string
42	IDPCertificateStore     dsig.X509CertificateStore
43	SPKeyStore              dsig.X509KeyStore // Required encryption key, default signing key
44	SPSigningKeyStore       dsig.X509KeyStore // Optional signing key
45	NameIdFormat            string
46	ValidateEncryptionCert  bool
47	SkipSignatureValidation bool
48	AllowMissingAttributes  bool
49	ScopingIDPProviderId    string
50	ScopingIDPProviderName  string
51	Clock                   *dsig.Clock
52	signingContextMu        sync.RWMutex
53	signingContext          *dsig.SigningContext
54}
55
56// RequestedAuthnContext controls which authentication mechanisms are requested of
57// the identity provider. It is generally sufficient to omit this and let the
58// identity provider select an authentication mechansim.
59type RequestedAuthnContext struct {
60	// The RequestedAuthnContext comparison policy to use. See the section 3.3.2.2.1
61	// of the SAML 2.0 specification for details. Constants named AuthnPolicyMatch*
62	// contain standardized values.
63	Comparison string
64
65	// Contexts will be passed as AuthnContextClassRefs. For example, to force password
66	// authentication on some identity providers, Contexts should have a value of
67	// []string{AuthnContextPasswordProtectedTransport}, and Comparison should have a
68	// value of AuthnPolicyMatchExact.
69	Contexts []string
70}
71
72func (sp *SAMLServiceProvider) Metadata() (*types.EntityDescriptor, error) {
73	signingCertBytes, err := sp.GetSigningCertBytes()
74	if err != nil {
75		return nil, err
76	}
77	encryptionCertBytes, err := sp.GetEncryptionCertBytes()
78	if err != nil {
79		return nil, err
80	}
81	return &types.EntityDescriptor{
82		ValidUntil: time.Now().UTC().Add(time.Hour * 24 * 7), // 7 days
83		EntityID:   sp.ServiceProviderIssuer,
84		SPSSODescriptor: &types.SPSSODescriptor{
85			AuthnRequestsSigned:        sp.SignAuthnRequests,
86			WantAssertionsSigned:       !sp.SkipSignatureValidation,
87			ProtocolSupportEnumeration: SAMLProtocolNamespace,
88			KeyDescriptors: []types.KeyDescriptor{
89				{
90					Use: "signing",
91					KeyInfo: dsigtypes.KeyInfo{
92						X509Data: dsigtypes.X509Data{
93							X509Certificates: []dsigtypes.X509Certificate{dsigtypes.X509Certificate{
94								Data: base64.StdEncoding.EncodeToString(signingCertBytes),
95							}},
96						},
97					},
98				},
99				{
100					Use: "encryption",
101					KeyInfo: dsigtypes.KeyInfo{
102						X509Data: dsigtypes.X509Data{
103							X509Certificates: []dsigtypes.X509Certificate{dsigtypes.X509Certificate{
104								Data: base64.StdEncoding.EncodeToString(encryptionCertBytes),
105							}},
106						},
107					},
108					EncryptionMethods: []types.EncryptionMethod{
109						{Algorithm: types.MethodAES128GCM},
110						{Algorithm: types.MethodAES128CBC},
111						{Algorithm: types.MethodAES256CBC},
112					},
113				},
114			},
115			AssertionConsumerServices: []types.IndexedEndpoint{{
116				Binding:  BindingHttpPost,
117				Location: sp.AssertionConsumerServiceURL,
118				Index:    1,
119			}},
120		},
121	}, nil
122}
123
124func (sp *SAMLServiceProvider) GetEncryptionKey() dsig.X509KeyStore {
125	return sp.SPKeyStore
126}
127
128func (sp *SAMLServiceProvider) GetSigningKey() dsig.X509KeyStore {
129	if sp.SPSigningKeyStore == nil {
130		return sp.GetEncryptionKey() // Default is signing key is same as encryption key
131	}
132	return sp.SPSigningKeyStore
133}
134
135func (sp *SAMLServiceProvider) GetEncryptionCertBytes() ([]byte, error) {
136	if _, encryptionCert, err := sp.GetEncryptionKey().GetKeyPair(); err != nil {
137		return nil, ErrSaml{Message: "no SP encryption certificate", System: err}
138	} else if len(encryptionCert) < 1 {
139		return nil, ErrSaml{Message: "empty SP encryption certificate"}
140	} else {
141		return encryptionCert, nil
142	}
143}
144
145func (sp *SAMLServiceProvider) GetSigningCertBytes() ([]byte, error) {
146	if _, signingCert, err := sp.GetSigningKey().GetKeyPair(); err != nil {
147		return nil, ErrSaml{Message: "no SP signing certificate", System: err}
148	} else if len(signingCert) < 1 {
149		return nil, ErrSaml{Message: "empty SP signing certificate"}
150	} else {
151		return signingCert, nil
152	}
153}
154
155func (sp *SAMLServiceProvider) SigningContext() *dsig.SigningContext {
156	sp.signingContextMu.RLock()
157	signingContext := sp.signingContext
158	sp.signingContextMu.RUnlock()
159
160	if signingContext != nil {
161		return signingContext
162	}
163
164	sp.signingContextMu.Lock()
165	defer sp.signingContextMu.Unlock()
166
167	sp.signingContext = dsig.NewDefaultSigningContext(sp.GetSigningKey())
168	sp.signingContext.SetSignatureMethod(sp.SignAuthnRequestsAlgorithm)
169	if sp.SignAuthnRequestsCanonicalizer != nil {
170		sp.signingContext.Canonicalizer = sp.SignAuthnRequestsCanonicalizer
171	}
172
173	return sp.signingContext
174}
175
176type ProxyRestriction struct {
177	Count    int
178	Audience []string
179}
180
181type WarningInfo struct {
182	OneTimeUse       bool
183	ProxyRestriction *ProxyRestriction
184	NotInAudience    bool
185	InvalidTime      bool
186}
187
188type AssertionInfo struct {
189	NameID                     string
190	Values                     Values
191	WarningInfo                *WarningInfo
192	AuthnInstant               *time.Time
193	SessionNotOnOrAfter        *time.Time
194	Assertions                 []types.Assertion
195	ResponseSignatureValidated bool
196}
197