package identity import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "math/big" "testing" "time" "github.com/beevik/etree" "github.com/crewjam/saml" dsig "github.com/russellhaering/goxmldsig" ) func TestValidateSAMLMetadataRequiresLiveSigningCertificate(t *testing.T) { now := time.Unix(1_800_000_000, 0) certificate := makeTestCertificate(t, now.Add(-time.Hour), now.Add(time.Hour)) metadata := testIDPMetadata(certificate) if err := validateSAMLMetadata(context.Background(), metadata, true, now); err != nil { t.Fatalf("valid metadata rejected: %v", err) } metadata.IDPSSODescriptors[0].KeyDescriptors = nil if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil { t.Fatal("metadata without signing certificate was accepted") } metadata = testIDPMetadata(makeTestCertificate(t, now.Add(-2*time.Hour), now.Add(-time.Hour))) if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil { t.Fatal("expired signing certificate was accepted") } metadata = testIDPMetadata(certificate) metadata.IDPSSODescriptors[0].SingleSignOnServices = nil if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil { t.Fatal("metadata without redirect SSO endpoint was accepted") } } func TestSAMLEntityIDAndAttributes(t *testing.T) { if got, err := validateSAMLEntityID(" urn:example:gateway "); err != nil || got != "urn:example:gateway" { t.Fatalf("valid URN entity ID rejected: %q %v", got, err) } for _, raw := range []string{"", "relative", "https://user:secret@example.com/sp", "urn:example:sp#fragment"} { if _, err := validateSAMLEntityID(raw); err == nil { t.Fatalf("invalid entity ID accepted: %q", raw) } } assertion := &saml.Assertion{AttributeStatements: []saml.AttributeStatement{{Attributes: []saml.Attribute{{ FriendlyName: "mail", Name: "urn:oid:0.9.2342.19200300.100.1.3", Values: []saml.AttributeValue{{Value: " user@example.com "}}, }}}}} if got := samlAttribute(assertion, "mail"); got != "user@example.com" { t.Fatalf("unexpected friendly-name attribute %q", got) } if got := samlAttribute(assertion, "urn:oid:0.9.2342.19200300.100.1.3"); got != "user@example.com" { t.Fatalf("unexpected named attribute %q", got) } } func TestSAMLSignatureAlgorithmPolicyRejectsSHA1(t *testing.T) { document := etree.NewDocument() if err := document.ReadFromString(``); err != nil { t.Fatal(err) } if err := validateSAMLSignatureAlgorithms(document.Root()); err != nil { t.Fatalf("SHA-256 signature policy rejected: %v", err) } document.Root().FindElement("./Signature/SignedInfo/SignatureMethod").SelectAttr("Algorithm").Value = dsig.RSASHA1SignatureMethod if err := validateSAMLSignatureAlgorithms(document.Root()); err == nil { t.Fatal("SHA-1 signature algorithm was accepted") } document.Root().FindElement("./Signature/SignedInfo/SignatureMethod").SelectAttr("Algorithm").Value = dsig.RSASHA256SignatureMethod document.Root().FindElement("./Signature/SignedInfo/Reference/DigestMethod").SelectAttr("Algorithm").Value = "http://www.w3.org/2000/09/xmldsig#sha1" if err := validateSAMLSignatureAlgorithms(document.Root()); err == nil { t.Fatal("SHA-1 digest algorithm was accepted") } } func testIDPMetadata(certificate *x509.Certificate) *saml.EntityDescriptor { return &saml.EntityDescriptor{ EntityID: "https://idp.example/metadata", IDPSSODescriptors: []saml.IDPSSODescriptor{{ SSODescriptor: saml.SSODescriptor{RoleDescriptor: saml.RoleDescriptor{KeyDescriptors: []saml.KeyDescriptor{{ Use: "signing", KeyInfo: saml.KeyInfo{X509Data: saml.X509Data{X509Certificates: []saml.X509Certificate{{Data: base64.StdEncoding.EncodeToString(certificate.Raw)}}}}, }}}}, SingleSignOnServices: []saml.Endpoint{{Binding: saml.HTTPRedirectBinding, Location: "http://127.0.0.1:9091/sso"}}, }}, } } func makeTestCertificate(t *testing.T, notBefore, notAfter time.Time) *x509.Certificate { t.Helper() key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } template := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "SAML test"}, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageDigitalSignature, } der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) if err != nil { t.Fatal(err) } certificate, err := x509.ParseCertificate(der) if err != nil { t.Fatal(err) } return certificate }