diff --git a/lib/fixtures/fixtures.go b/lib/fixtures/fixtures.go
index cf86dd07d1959..efec8967ff9e6 100644
--- a/lib/fixtures/fixtures.go
+++ b/lib/fixtures/fixtures.go
@@ -150,6 +150,35 @@ spec:
C7yZIZM0DuazwkaenExrncvPtF6KL7eccudcknNjhRjFD3Yx1nNXgbVRHvVaElm0YxLiLcl8l0Rn
pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwRurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified`
+const SAMLOktaConnectorV2WithPadding = `kind: saml
+version: v2
+metadata:
+ name: OktaSAML
+ namespace: default
+spec:
+ acs: https://localhost:3080/v1/webapi/saml/acs
+ attributes_to_roles:
+ - {name: "groups", value: "Everyone", roles: ["admin"]}
+ entity_descriptor: |
+ MIIDpDCCAoygAwIBAgIGAVvvlUB6MA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
+ A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
+ MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi04MTMzNTQxHDAaBgkqhkiG9w0BCQEW
+ DWluZm9Ab2t0YS5jb20wHhcNMTcwNTA5MjMzODQ3WhcNMjcwNTA5MjMzOTQ3WjCBkjELMAkGA1UE
+ BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
+ BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtODEzMzU0MRwwGgYJ
+ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+ ltQB+ZTGKoaNiWQRZ/bzl9oNmbjFyLiVlDASaYnuv1yBx70/Tzr9VXn0gWkl5yH0zIpzREvR5qM1
+ VAaH3dgNbxTg15f0e5xDk7r5ggS11mX5p8S1Ca9UQmqhRRv7jhMJxHbCy4rFV5jO/uyNQDaMZLPd
+ zFuzpwKaWhy/UCQ3lDmNzxp3Q6T3FULV+fvs7tJp+8p6qfpoGkANGVfs/Jx/kgbbk0JZG2wk4VVl
+ b1rZTZJWQ6hCLwTAsD/WixcUx1BFTXmqoZTYNETATVJQ+bEMCVf8K4hxbH6hEgjoL//AE9zgpa1m
+ uvKwevYBvYZ/+VRy+It3d9mq73AdrG9vchE3qQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQASANAj
+ 8JQdBdKIrrbU6n1egwETwkOUwWyUja/5t+C/RIZPuKP5XmsUhFecbCrML2+M7HG0l5leqyD3u5pS
+ yhyBz99QWZegoRJy05tciuQUCyPrp6zDzl5De3byq5WQ6Ke+uiRb2GFdDNDhLuMlE48aLWyjm4qh
+ 31Q0/wAWJ1zwmrYxu4p/OhZemU7myuSF5tp35rzV3CPRN31d2UcZAwzMUgwKkCE3yT1o+lLskg/k
+ C7yZIZM0DuazwkaenExrncvPtF6KL7eccudcknNjhRjFD3Yx1nNXgbVRHvVaElm0YxLiLcl8l0Rn
+ pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwR
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified`
+
const (
TLSCACertPEM = apifixtures.TLSCACertPEM
TLSCAKeyPEM = apifixtures.TLSCAKeyPEM
diff --git a/lib/services/saml.go b/lib/services/saml.go
index 61a8651ad60b5..b0121584228f3 100644
--- a/lib/services/saml.go
+++ b/lib/services/saml.go
@@ -162,7 +162,13 @@ func CheckSAMLEntityDescriptor(entityDescriptor string) ([]*x509.Certificate, er
for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
for _, samlCert := range kd.KeyInfo.X509Data.X509Certificates {
- certData, err := base64.StdEncoding.DecodeString(strings.TrimSpace(samlCert.Data))
+ // The certificate is base64 encoded and can be split into multiple lines.
+ // Each line can be padded with spaces/tabs, so we need to remove them first
+ // before decoding otherwise we'll get an error.
+ // We need to run this through strings.Fields to remove spaces/tabs
+ // from each line and then join them back with newlines.
+ // The last step isn't strictly necessary, but it makes payload more readable.
+ certData, err := base64.StdEncoding.DecodeString(strings.Join(strings.Fields(samlCert.Data), "\n"))
if err != nil {
return nil, trace.Wrap(err, "failed to decode certificate defined in entity_descriptor")
}
diff --git a/lib/services/saml_test.go b/lib/services/saml_test.go
index 3548b4f549e1f..b83bb3e22d38e 100644
--- a/lib/services/saml_test.go
+++ b/lib/services/saml_test.go
@@ -54,20 +54,25 @@ func TestParseFromMetadata(t *testing.T) {
func TestCheckSAMLEntityDescriptor(t *testing.T) {
t.Parallel()
- input := fixtures.SAMLOktaConnectorV2
-
- decoder := kyaml.NewYAMLOrJSONDecoder(strings.NewReader(input), defaults.LookaheadBufSize)
- var raw UnknownResource
- err := decoder.Decode(&raw)
- require.NoError(t, err)
+ for name, input := range map[string]string{
+ "without certificate padding": fixtures.SAMLOktaConnectorV2,
+ "with certificate padding": fixtures.SAMLOktaConnectorV2WithPadding,
+ } {
+ t.Run(name, func(t *testing.T) {
+ decoder := kyaml.NewYAMLOrJSONDecoder(strings.NewReader(input), defaults.LookaheadBufSize)
+ var raw UnknownResource
+ err := decoder.Decode(&raw)
+ require.NoError(t, err)
- oc, err := UnmarshalSAMLConnector(raw.Raw)
- require.NoError(t, err)
+ oc, err := UnmarshalSAMLConnector(raw.Raw)
+ require.NoError(t, err)
- ed := oc.GetEntityDescriptor()
- certs, err := CheckSAMLEntityDescriptor(ed)
- require.NoError(t, err)
- require.Len(t, certs, 1)
+ ed := oc.GetEntityDescriptor()
+ certs, err := CheckSAMLEntityDescriptor(ed)
+ require.NoError(t, err)
+ require.Len(t, certs, 1)
+ })
+ }
}
func TestValidateRoles(t *testing.T) {