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