|
5 | 5 | "compress/flate"
|
6 | 6 | "context"
|
7 | 7 | "crypto/rsa"
|
| 8 | + "crypto/sha256" |
| 9 | + "crypto/sha512" |
8 | 10 | "crypto/tls"
|
9 | 11 | "crypto/x509"
|
10 | 12 | "encoding/base64"
|
@@ -91,6 +93,18 @@ type ServiceProvider struct {
|
91 | 93 | // IDPMetadata is the metadata from the identity provider.
|
92 | 94 | IDPMetadata *EntityDescriptor
|
93 | 95 |
|
| 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 | + |
94 | 108 | // AuthnNameIDFormat is the format used in the NameIDPolicy for
|
95 | 109 | // authentication requests
|
96 | 110 | AuthnNameIDFormat NameIDFormat
|
@@ -378,6 +392,85 @@ func (sp *ServiceProvider) getIDPSigningCerts() ([]*x509.Certificate, error) {
|
378 | 392 | return certs, nil
|
379 | 393 | }
|
380 | 394 |
|
| 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 | + |
381 | 474 | // MakeArtifactResolveRequest produces a new ArtifactResolve object to send to the idp's Artifact resolver
|
382 | 475 | func (sp *ServiceProvider) MakeArtifactResolveRequest(artifactID string) (*ArtifactResolve, error) {
|
383 | 476 | req := ArtifactResolve{
|
@@ -1104,9 +1197,28 @@ func (sp *ServiceProvider) validateSignature(el *etree.Element) error {
|
1104 | 1197 | return errSignatureElementNotPresent
|
1105 | 1198 | }
|
1106 | 1199 |
|
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) |
1110 | 1222 | }
|
1111 | 1223 |
|
1112 | 1224 | certificateStore := dsig.MemoryX509CertificateStore{
|
|
0 commit comments