Skip to content

Commit 73942bd

Browse files
authored
feat: idp cert fingerprint and actual idp cert (crewjam#551)
1 parent 22adf0f commit 73942bd

File tree

1 file changed

+115
-3
lines changed

1 file changed

+115
-3
lines changed

service_provider.go

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"compress/flate"
66
"context"
77
"crypto/rsa"
8+
"crypto/sha256"
9+
"crypto/sha512"
810
"crypto/tls"
911
"crypto/x509"
1012
"encoding/base64"
@@ -91,6 +93,18 @@ type ServiceProvider struct {
9193
// IDPMetadata is the metadata from the identity provider.
9294
IDPMetadata *EntityDescriptor
9395

96+
// IDPCertificateFingerprint is fingerprint of the idp public certificate. If this field is specified,
97+
// IDPCertificateFingerprintAlgorithm must also be specified, and IDPCertificate must not be specified.
98+
IDPCertificateFingerprint *string
99+
// IDPCertificateFingerprintAlgorithm is fingerprint algorithm used to obtain fingerprint of the idp public
100+
// certificate.
101+
// If this field is specified, IDPCertificateFingerprint must also be specified, and IDPCertificate must not be specified.
102+
IDPCertificateFingerprintAlgorithm *string
103+
104+
// IDPCertificate to use as idp public certificate. If this field is specified, IDPCertificateFingerprint and
105+
// IDPCertificateFingerprintAlgorithm must not be specified.
106+
IDPCertificate *string
107+
94108
// AuthnNameIDFormat is the format used in the NameIDPolicy for
95109
// authentication requests
96110
AuthnNameIDFormat NameIDFormat
@@ -378,6 +392,85 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) {
378392
return certs, nil
379393
}
380394

395+
func (sp *ServiceProvider) getCertBasedOnFingerprint(el *etree.Element) ([]*x509.Certificate, error) {
396+
x509CertEl := el.FindElement("./Signature/KeyInfo/X509Data/X509Certificate")
397+
if x509CertEl == nil {
398+
return nil, fmt.Errorf("cannot validate signature on %s: no certificate present", el.Tag)
399+
}
400+
if len(x509CertEl.Child) != 1 {
401+
return nil, fmt.Errorf("cannot validate signature on %s: x509 cert el child len != 1: %d", el.Tag, len(x509CertEl.Child))
402+
}
403+
404+
x509CertElCharData, ok := x509CertEl.Child[0].(*etree.CharData)
405+
if !ok {
406+
return nil, fmt.Errorf("cannot validate signature on %s: x509 cert el first child not char data: %T", el.Tag, x509CertEl.Child[0])
407+
}
408+
409+
cert, err := parseCert(x509CertElCharData.Data)
410+
if err != nil {
411+
return nil, fmt.Errorf("cannot validate signature on %s: %w", el.Tag, err)
412+
}
413+
414+
finP, err := fingerprint(cert, *sp.IDPCertificateFingerprintAlgorithm)
415+
if err != nil {
416+
return nil, fmt.Errorf("cannot validate signature on %s: %w", el.Tag, err)
417+
}
418+
419+
if *sp.IDPCertificateFingerprint != finP {
420+
return nil, fmt.Errorf("cannot validate signature on %s: fingerprint mismatch", el.Tag)
421+
}
422+
423+
return []*x509.Certificate{cert}, nil
424+
425+
}
426+
427+
func parseCert(x509Data string) (*x509.Certificate, error) {
428+
// cleanup whitespace
429+
regex := regexp.MustCompile(`\s+`)
430+
certStr := regex.ReplaceAllString(x509Data, "")
431+
certBytes, err := base64.StdEncoding.DecodeString(certStr)
432+
if err != nil {
433+
return nil, fmt.Errorf("parse cert, cannot base64 decode cert string: %w", err)
434+
}
435+
436+
parsedCert, err := x509.ParseCertificate(certBytes)
437+
if err != nil {
438+
return nil, fmt.Errorf("parse cert, cannot parse certificate: %w", err)
439+
}
440+
441+
return parsedCert, nil
442+
}
443+
444+
func fingerprint(cert *x509.Certificate, fingerprintAlgorithm string) (string, error) {
445+
switch fingerprintAlgorithm {
446+
case "http://www.w3.org/2001/04/xmlenc#sha256":
447+
fp := sha256.Sum256(cert.Raw)
448+
return fingerprintFormat(fp[:])
449+
case "http://www.w3.org/2001/04/xmlenc#sha512":
450+
fp := sha512.Sum512(cert.Raw)
451+
return fingerprintFormat(fp[:])
452+
default:
453+
return "", fmt.Errorf("fingerprint, unknown algorithm: %s", fingerprintAlgorithm)
454+
}
455+
}
456+
457+
func fingerprintFormat(fp []byte) (string, error) {
458+
var buf bytes.Buffer
459+
for i, f := range fp {
460+
if i > 0 {
461+
_, err := fmt.Fprintf(&buf, ":")
462+
if err != nil {
463+
return "", fmt.Errorf("fingerprint format, print ':': %w", err)
464+
}
465+
}
466+
_, err := fmt.Fprintf(&buf, "%02X", f)
467+
if err != nil {
468+
return "", fmt.Errorf("fingerprint format, print bytes: %w", err)
469+
}
470+
}
471+
return buf.String(), nil
472+
}
473+
381474
// MakeArtifactResolveRequest produces a new ArtifactResolve object to send to the idp's Artifact resolver
382475
func (sp *ServiceProvider) MakeArtifactResolveRequest(artifactID string) (*ArtifactResolve, error) {
383476
req := ArtifactResolve{
@@ -1104,9 +1197,28 @@ func (sp *ServiceProvider) validateSignature(el *etree.Element) error {
11041197
return errSignatureElementNotPresent
11051198
}
11061199

1107-
certs, err := sp.getIDPSigningCerts()
1108-
if err != nil {
1109-
return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err)
1200+
var certs []*x509.Certificate
1201+
if sp.IDPMetadata != nil && sp.IDPCertificateFingerprint == nil && sp.IDPCertificateFingerprintAlgorithm == nil && sp.IDPCertificate == nil {
1202+
certs, err = sp.getIDPSigningCerts()
1203+
if err != nil {
1204+
return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err)
1205+
}
1206+
}
1207+
if sp.IDPMetadata != nil && sp.IDPCertificateFingerprint != nil && sp.IDPCertificateFingerprintAlgorithm != nil && sp.IDPCertificate == nil {
1208+
certs, err = sp.getCertBasedOnFingerprint(el)
1209+
if err != nil {
1210+
return fmt.Errorf("cannot validate signature on %s: %v", el.Tag, err)
1211+
}
1212+
}
1213+
if sp.IDPMetadata != nil && sp.IDPCertificateFingerprint == nil && sp.IDPCertificateFingerprintAlgorithm == nil && sp.IDPCertificate != nil {
1214+
cert, err := parseCert(*sp.IDPCertificate)
1215+
if err != nil {
1216+
return fmt.Errorf("cannot validate signature on %s: %w", el.Tag, err)
1217+
}
1218+
certs = append(certs, cert)
1219+
}
1220+
if len(certs) == 0 {
1221+
return fmt.Errorf("cannot validate signature on %s: saml config not set up properly, specify either idp metadata url, fingerprints or actual certificate", el.Tag)
11101222
}
11111223

11121224
certificateStore := dsig.MemoryX509CertificateStore{

0 commit comments

Comments
 (0)