mattermost-community-enterp.../vendor/github.com/mattermost/gosaml2/saml.go

292 lines
9.1 KiB
Go

// Copyright 2016 Russell Haering et al.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package saml2
import (
"encoding/base64"
"sync"
"time"
"github.com/mattermost/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
dsigtypes "github.com/russellhaering/goxmldsig/types"
)
type ErrSaml struct {
Message string
System error
}
func (serr ErrSaml) Error() string {
if serr.Message != "" {
return serr.Message
}
return "SAML error"
}
type SAMLServiceProvider struct {
IdentityProviderSSOURL string
IdentityProviderSSOBinding string
IdentityProviderSLOURL string
IdentityProviderSLOBinding string
IdentityProviderIssuer string
AssertionConsumerServiceURL string
ServiceProviderSLOURL string
ServiceProviderIssuer string
SignAuthnRequests bool
SignAuthnRequestsAlgorithm string
SignAuthnRequestsCanonicalizer dsig.Canonicalizer
// RequestedAuthnContext allows service providers to require that the identity
// provider use specific authentication mechanisms. Leaving this unset will
// permit the identity provider to choose the auth method. To maximize compatibility
// with identity providers it is recommended to leave this unset.
RequestedAuthnContext *RequestedAuthnContext
AudienceURI string
IDPCertificateStore dsig.X509CertificateStore
SPKeyStore dsig.X509KeyStore // Required encryption key, default signing key
SPSigningKeyStore dsig.X509KeyStore // Optional signing key
NameIdFormat string
ValidateEncryptionCert bool
SkipSignatureValidation bool
AllowMissingAttributes bool
ScopingIDPProviderId string
ScopingIDPProviderName string
Clock *dsig.Clock
// MaximumDecompressedBodySize is the maximum size to which a compressed
// SAML document will be decompressed. If a compresed document is exceeds
// this size during decompression an error will be returned.
MaximumDecompressedBodySize int64
signingContextMu sync.RWMutex
signingContext *dsig.SigningContext
}
// RequestedAuthnContext controls which authentication mechanisms are requested of
// the identity provider. It is generally sufficient to omit this and let the
// identity provider select an authentication mechansim.
type RequestedAuthnContext struct {
// The RequestedAuthnContext comparison policy to use. See the section 3.3.2.2.1
// of the SAML 2.0 specification for details. Constants named AuthnPolicyMatch*
// contain standardized values.
Comparison string
// Contexts will be passed as AuthnContextClassRefs. For example, to force password
// authentication on some identity providers, Contexts should have a value of
// []string{AuthnContextPasswordProtectedTransport}, and Comparison should have a
// value of AuthnPolicyMatchExact.
Contexts []string
}
func (sp *SAMLServiceProvider) Metadata() (*types.EntityDescriptor, error) {
keyDescriptors := make([]types.KeyDescriptor, 0, 2)
if sp.GetSigningKey() != nil {
signingCertBytes, err := sp.GetSigningCertBytes()
if err != nil {
return nil, err
}
keyDescriptors = append(keyDescriptors, types.KeyDescriptor{
Use: "signing",
KeyInfo: dsigtypes.KeyInfo{
X509Data: dsigtypes.X509Data{
X509Certificates: []dsigtypes.X509Certificate{{
Data: base64.StdEncoding.EncodeToString(signingCertBytes),
}},
},
},
})
}
if sp.GetEncryptionKey() != nil {
encryptionCertBytes, err := sp.GetEncryptionCertBytes()
if err != nil {
return nil, err
}
keyDescriptors = append(keyDescriptors, types.KeyDescriptor{
Use: "encryption",
KeyInfo: dsigtypes.KeyInfo{
X509Data: dsigtypes.X509Data{
X509Certificates: []dsigtypes.X509Certificate{{
Data: base64.StdEncoding.EncodeToString(encryptionCertBytes),
}},
},
},
EncryptionMethods: []types.EncryptionMethod{
{Algorithm: types.MethodAES128GCM},
{Algorithm: types.MethodAES192GCM},
{Algorithm: types.MethodAES256GCM},
{Algorithm: types.MethodAES128CBC},
{Algorithm: types.MethodAES256CBC},
},
})
}
return &types.EntityDescriptor{
ValidUntil: time.Now().UTC().Add(time.Hour * 24 * 7), // 7 days
EntityID: sp.ServiceProviderIssuer,
SPSSODescriptor: &types.SPSSODescriptor{
AuthnRequestsSigned: sp.SignAuthnRequests,
WantAssertionsSigned: !sp.SkipSignatureValidation,
ProtocolSupportEnumeration: SAMLProtocolNamespace,
KeyDescriptors: keyDescriptors,
AssertionConsumerServices: []types.IndexedEndpoint{{
Binding: BindingHttpPost,
Location: sp.AssertionConsumerServiceURL,
Index: 1,
}},
},
}, nil
}
func (sp *SAMLServiceProvider) MetadataWithSLO(validityHours int64) (*types.EntityDescriptor, error) {
signingCertBytes, err := sp.GetSigningCertBytes()
if err != nil {
return nil, err
}
encryptionCertBytes, err := sp.GetEncryptionCertBytes()
if err != nil {
return nil, err
}
if validityHours <= 0 {
//By default let's keep it to 7 days.
validityHours = int64(time.Hour * 24 * 7)
}
return &types.EntityDescriptor{
ValidUntil: time.Now().UTC().Add(time.Duration(validityHours)), // default 7 days
EntityID: sp.ServiceProviderIssuer,
SPSSODescriptor: &types.SPSSODescriptor{
AuthnRequestsSigned: sp.SignAuthnRequests,
WantAssertionsSigned: !sp.SkipSignatureValidation,
ProtocolSupportEnumeration: SAMLProtocolNamespace,
KeyDescriptors: []types.KeyDescriptor{
{
Use: "signing",
KeyInfo: dsigtypes.KeyInfo{
X509Data: dsigtypes.X509Data{
X509Certificates: []dsigtypes.X509Certificate{{
Data: base64.StdEncoding.EncodeToString(signingCertBytes),
}},
},
},
},
{
Use: "encryption",
KeyInfo: dsigtypes.KeyInfo{
X509Data: dsigtypes.X509Data{
X509Certificates: []dsigtypes.X509Certificate{{
Data: base64.StdEncoding.EncodeToString(encryptionCertBytes),
}},
},
},
EncryptionMethods: []types.EncryptionMethod{
{Algorithm: types.MethodAES128GCM, DigestMethod: nil},
{Algorithm: types.MethodAES192GCM, DigestMethod: nil},
{Algorithm: types.MethodAES256GCM, DigestMethod: nil},
{Algorithm: types.MethodAES128CBC, DigestMethod: nil},
{Algorithm: types.MethodAES256CBC, DigestMethod: nil},
},
},
},
AssertionConsumerServices: []types.IndexedEndpoint{{
Binding: BindingHttpPost,
Location: sp.AssertionConsumerServiceURL,
Index: 1,
}},
SingleLogoutServices: []types.Endpoint{{
Binding: BindingHttpPost,
Location: sp.ServiceProviderSLOURL,
}},
},
}, nil
}
func (sp *SAMLServiceProvider) GetEncryptionKey() dsig.X509KeyStore {
return sp.SPKeyStore
}
func (sp *SAMLServiceProvider) GetSigningKey() dsig.X509KeyStore {
if sp.SPSigningKeyStore == nil {
return sp.GetEncryptionKey() // Default is signing key is same as encryption key
}
return sp.SPSigningKeyStore
}
func (sp *SAMLServiceProvider) GetEncryptionCertBytes() ([]byte, error) {
if _, encryptionCert, err := sp.GetEncryptionKey().GetKeyPair(); err != nil {
return nil, ErrSaml{Message: "no SP encryption certificate", System: err}
} else if len(encryptionCert) < 1 {
return nil, ErrSaml{Message: "empty SP encryption certificate"}
} else {
return encryptionCert, nil
}
}
func (sp *SAMLServiceProvider) GetSigningCertBytes() ([]byte, error) {
if _, signingCert, err := sp.GetSigningKey().GetKeyPair(); err != nil {
return nil, ErrSaml{Message: "no SP signing certificate", System: err}
} else if len(signingCert) < 1 {
return nil, ErrSaml{Message: "empty SP signing certificate"}
} else {
return signingCert, nil
}
}
func (sp *SAMLServiceProvider) SigningContext() *dsig.SigningContext {
sp.signingContextMu.RLock()
signingContext := sp.signingContext
sp.signingContextMu.RUnlock()
if signingContext != nil {
return signingContext
}
sp.signingContextMu.Lock()
defer sp.signingContextMu.Unlock()
sp.signingContext = dsig.NewDefaultSigningContext(sp.GetSigningKey())
sp.signingContext.SetSignatureMethod(sp.SignAuthnRequestsAlgorithm)
if sp.SignAuthnRequestsCanonicalizer != nil {
sp.signingContext.Canonicalizer = sp.SignAuthnRequestsCanonicalizer
}
return sp.signingContext
}
type ProxyRestriction struct {
Count int
Audience []string
}
type WarningInfo struct {
OneTimeUse bool
ProxyRestriction *ProxyRestriction
NotInAudience bool
InvalidTime bool
}
type AssertionInfo struct {
NameID string
Values Values
WarningInfo *WarningInfo
SessionIndex string
AuthnInstant *time.Time
SessionNotOnOrAfter *time.Time
Assertions []types.Assertion
ResponseSignatureValidated bool
}