From e68af03753f0e109e00c9f312f96e699faa1c811 Mon Sep 17 00:00:00 2001 From: Grant Spence Date: Tue, 3 Dec 2024 21:30:35 -0500 Subject: [PATCH] OCPBUGS-45290: Reject All CA-Signed Certs Using SHA1 Previously, only SHA1 leaf certs were rejected. However, in 4.16, any SHA1 cert that is CA-signed (not self-signed) is unsupported. This led to cases were routes with SHA1 intermediate CA certs were accepted, but HAProxy rejects them. Self-signed SHA1 certificates (i.e. root CA) remain supported since they are not subject to verification. This update ensures all route certs, including the server, CA, and destination CA certs, are inspected, and any SHA1 cert that is not self-signed is rejected. --- pkg/router/routeapihelpers/validation.go | 50 ++- pkg/router/routeapihelpers/validation_test.go | 405 ++++++++++++++++-- 2 files changed, 417 insertions(+), 38 deletions(-) diff --git a/pkg/router/routeapihelpers/validation.go b/pkg/router/routeapihelpers/validation.go index 2f105d3e8..7d338f471 100644 --- a/pkg/router/routeapihelpers/validation.go +++ b/pkg/router/routeapihelpers/validation.go @@ -205,6 +205,11 @@ func ExtendedValidateRoute(route *routev1.Route) field.ErrorList { } else { tlsConfig.CACertificate = string(data) } + // HAProxy will fail to start if intermediate CA certs use unsupported signature algorithms. + // However, root CAs can still use unsupported algorithms since they are self-signed. + if err := validateCertSignatureAlgorithms(certs); err != nil { + result = append(result, field.Invalid(tlsFieldPath.Child("caCertificate"), "redacted ca certificate data", err.Error())) + } } verifyOptions = &x509.VerifyOptions{ @@ -254,7 +259,7 @@ func ExtendedValidateRoute(route *routev1.Route) field.ErrorList { } if len(tlsConfig.DestinationCACertificate) > 0 { - if _, err := cert.ParseCertsPEM([]byte(tlsConfig.DestinationCACertificate)); err != nil { + if certs, err := cert.ParseCertsPEM([]byte(tlsConfig.DestinationCACertificate)); err != nil { errmsg := fmt.Sprintf("failed to parse destination CA certificate: %v", err) result = append(result, field.Invalid(tlsFieldPath.Child("destinationCACertificate"), "redacted destination ca certificate data", errmsg)) } else { @@ -263,6 +268,11 @@ func ExtendedValidateRoute(route *routev1.Route) field.ErrorList { } else { tlsConfig.DestinationCACertificate = string(data) } + // Unsupported destinationCACertificates algorithms won't prevent HAProxy from starting. + // However, HAProxy will quietly refuse to use them at runtime. Rejecting here improves UX. + if err := validateCertSignatureAlgorithms(certs); err != nil { + result = append(result, field.Invalid(tlsFieldPath.Child("destinationCACertificate"), "redacted ca certificate data", err.Error())) + } } } @@ -353,6 +363,35 @@ func validateInsecureEdgeTerminationPolicy(tls *routev1.TLSConfig, fldPath *fiel return nil } +// isSelfSignedCert determines if a provided certificate is self-signed. +func isSelfSignedCert(cert *x509.Certificate) bool { + // Verify the certificate's signature using its own public key. + err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature) + return err == nil +} + +// validateCertSignatureAlgorithms checks if the certificate list has any certs that use a +// signature algorithm that the router no longer supports. If an unsupported cert is present, HAProxy +// may refuse to start (server & CA certs) or may start but reject connections (destination CA certs). +func validateCertSignatureAlgorithms(certs []*x509.Certificate) error { + for _, cert := range certs { + // Verify the signature algorithms only for certs signed by a CA. + // Self-signed certificates are not subject to validation, so their signature algorithm is not used. + // It's important that we do NOT reject self-signed certificates, as many root CAs still utilize SHA1. + if !isSelfSignedCert(cert) { + switch certs[0].SignatureAlgorithm { + case x509.SHA1WithRSA, x509.ECDSAWithSHA1: + return fmt.Errorf("router does not support CA-signed certs using SHA1") + case x509.MD5WithRSA: + return fmt.Errorf("router does not support CA-signed certs using MD5") + default: + // Acceptable algorithm + } + } + } + return nil +} + // validateCertificatePEM checks if a certificate PEM is valid and // optionally verifies the certificate using the options. func validateCertificatePEM(certPEM string, options *x509.VerifyOptions) ([]*x509.Certificate, error) { @@ -366,13 +405,8 @@ func validateCertificatePEM(certPEM string, options *x509.VerifyOptions) ([]*x50 } // Reject any unsupported cert algorithms as HaProxy will refuse to start with them. - switch certs[0].SignatureAlgorithm { - case x509.SHA1WithRSA, x509.ECDSAWithSHA1: - return certs, fmt.Errorf("router does not support certs using SHA1") - case x509.MD5WithRSA: - return certs, fmt.Errorf("router does not support certs using MD5") - default: - // Acceptable algorithm + if err := validateCertSignatureAlgorithms(certs); err != nil { + return certs, err } if options != nil { diff --git a/pkg/router/routeapihelpers/validation_test.go b/pkg/router/routeapihelpers/validation_test.go index 652ab5622..b9b08be63 100644 --- a/pkg/router/routeapihelpers/validation_test.go +++ b/pkg/router/routeapihelpers/validation_test.go @@ -2,6 +2,10 @@ package routeapihelpers import ( "bytes" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" "reflect" "testing" @@ -928,49 +932,283 @@ IrRGZJwgzmWX+NzqK9H3AyFk5p9oBuzmulVoJyKFzs1eN4ZIn25ifP8hP+uJHOTE jZrtwVw4rGVb/qM= -----END PRIVATE KEY-----` - // openssl req -x509 -sha1 -newkey rsa:1024 -days 3650 -keyout exampleca.key -out exampleca.crt -addext "keyUsage=cRLSign, digitalSignature, keyCertSign" -addext "extendedKeyUsage=serverAuth, clientAuth" -nodes -subj '/C=US/ST=SC/L=Default City/O=Default Company Ltd/OU=Test CA/CN=www.exampleca.com/emailAddress=example@example.com' + // openssl req -x509 -sha1 -newkey rsa:1024 -days 3650 -keyout testCertificateRsaSha1CA.key -out testCertificateRsaSha1CA.crt -addext "keyUsage=cRLSign, digitalSignature, keyCertSign" -addext "extendedKeyUsage=serverAuth, clientAuth" -nodes -subj '/C=US/ST=SC/L=Default City/O=Default Company Ltd/OU=Test CA/CN=www.exampleca.com/emailAddress=example@example.com' + // + // Key = testCertificateRsaSha1CAKey + // CA = self-signed + testCertificateRsaSha1CA = `-----BEGIN CERTIFICATE----- +MIIDTDCCArWgAwIBAgIUESnhsJLBoYVoOfqUpoJcxIjpr9IwDQYJKoZIhvcNAQEF +BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRAwDgYDVQQLDAdU +ZXN0IENBMRowGAYDVQQDDBF3d3cuZXhhbXBsZWNhLmNvbTEiMCAGCSqGSIb3DQEJ +ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDEyMTIwMTA3MTVaFw0zNDEyMTAw +MTA3MTVaMIGhMQswCQYDVQQGEwJVUzELMAkGA1UECAwCU0MxFTATBgNVBAcMDERl +ZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UE +CwwHVGVzdCBDQTEaMBgGA1UEAwwRd3d3LmV4YW1wbGVjYS5jb20xIjAgBgkqhkiG +9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBANiNMnmpMORg+X9TujvAXx1ysM9SzuYLX5SKhxq9SiSqKE+YZjxpkf2E +vBKraxgKIBEHrGpn5CX2YKycT0Tio6G98/8O/xyDAqdHIE5PCD9srz5INtw5Vx9u +LbtSOPwzLoN6qQIH31rdXShdkKVKDegsKgPaRPBlY1O43sXgkCahAgMBAAGjfzB9 +MB0GA1UdDgQWBBRcxFzhkQELDqWRGp2Hjnb+PHSDYzAfBgNVHSMEGDAWgBRcxFzh +kQELDqWRGp2Hjnb+PHSDYzAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEA +u1CFt+f41DhmsOWkaT7SLBR8ODmdq91ta8vYP+L3Ws2fZ2tUNH/DX/lofR90GXA3 +L5W8aWhQYdk+S7zuCFmt18QFjRXX0szbLawGRA+t4zQy/AeOIVnmrlKSs6rQ4I+e +yuoyUfLE8+ULl92NZbj3pHKnWLddD7uVK2GYHr/P8kQ= +-----END CERTIFICATE----- +` + // Key is not used, but keep here for reference for signing new certs if needed. + testCertificateRsaSha1CAKey = `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANiNMnmpMORg+X9T +ujvAXx1ysM9SzuYLX5SKhxq9SiSqKE+YZjxpkf2EvBKraxgKIBEHrGpn5CX2YKyc +T0Tio6G98/8O/xyDAqdHIE5PCD9srz5INtw5Vx9uLbtSOPwzLoN6qQIH31rdXShd +kKVKDegsKgPaRPBlY1O43sXgkCahAgMBAAECgYAxSVGnpv5dvESM2j2Uw9/iD+x2 +A17btNL4N98wEs0BM0khdIowTcbQcJltll41hnht59UyEps2mLDAGINiJkMfbWyD +uKuy1Lmo/+QE4hTZ7VSIoznmpQr4XjytHmSVP5JYBSQIG/uCSg2OoMwjwnXLO6rO +NbYX2392upZZm135UQJBAPzubZkElK19qU0hbMwWfgJE2OwYuo6lS/3x/l40sgHq +NbkurL8W/NIF5v+/X9DOCYbUqp8E0DtLZPmXebACDoMCQQDbLccxv9erBBq2+YNJ +P2ZYzHbwSrj98NLMwdMkutbbHd2521DSXbaT0mdb2QT3MpdK0PT98JnJccI53vHQ +ua0LAkEAoLFGVjIv121/s24p9hvQINbmzlEDrX7dIdCuH+HwugC38xfxTlJne3Oe +iBto33sXWF8iq3beaN2EoIIZILadywJAD+K7g0GSUhTUEtr2xwJPWrRHEpd33P/t +Z2XM9eaM2AjMH0JkEzszlnczgpayI3CJQqTufNFJdC5Ik4UzJZuvjQJBAM8cYMDt +tO6ylsZ2JWKlnsFVW0Nsx696Y3dLygymVLlU607/a7QP9Lakf9XwI8dSmZDIuW9l +w0VeEQOmXrayLUM= +-----END PRIVATE KEY----- +` + // openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha1.key -out testCertificateRsaSha1.csr -subj '/CN=www.example.com/ST=SC/C=US/emailAddress=example@example.com/O=Example/OU=Example' - // openssl x509 -req -days 3650 -sha1 -in testCertificateRsaSha1.csr -CA exampleca.crt -CAcreateserial -CAkey exampleca.key -extensions ext -extfile <(echo $'[ext]\nbasicConstraints = CA:FALSE') -out testCertificateRsaSha1.crt + // openssl x509 -req -days 3650 -sha1 -in testCertificateRsaSha1.csr -CA testCertificateRsaSha1CA.crt -CAcreateserial -CAkey testCertificateRsaSha1CA.key -extensions ext -extfile <(echo $'[ext]\nbasicConstraints = CA:FALSE') -out testCertificateRsaSha1.crt // // Key = testCertificateRsaSha1Key - // CA = Unknown + // CA = testCertificateRsaSha1CA testCertificateRsaSha1 = `-----BEGIN CERTIFICATE----- -MIIC9DCCAl2gAwIBAgIUTWv/Z/7lOkdCELulnNZOP4azjHowDQYJKoZIhvcNAQEF +MIIC9DCCAl2gAwIBAgIUaTcUc8Cz/ZVnUotUfvexgWLIiUAwDQYJKoZIhvcNAQEF BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMRGVmYXVs dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRAwDgYDVQQLDAdU ZXN0IENBMRowGAYDVQQDDBF3d3cuZXhhbXBsZWNhLmNvbTEiMCAGCSqGSIb3DQEJ -ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDAxMTAxOTU2MDhaFw0zNDAxMDcx -OTU2MDhaMHwxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTELMAkGA1UECAwCU0Mx +ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDEyMTIwMTE5MzFaFw0zNDEyMTAw +MTE5MzFaMHwxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTELMAkGA1UECAwCU0Mx CzAJBgNVBAYTAlVTMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUuY29t MRAwDgYDVQQKDAdFeGFtcGxlMRAwDgYDVQQLDAdFeGFtcGxlMIGfMA0GCSqGSIb3 -DQEBAQUAA4GNADCBiQKBgQC4hsxewdQOk5goI9bdkR1urJnbu7TeZdDtPz0Mi976 -1guAxNPQO98t0X/Bhs7toZz/zIG4vQZfXaV2IU1ry7pQ64I8bTPXQ/Kpt8zW3zng -dPeIJqVujKPybIL/teHJ1Bw4c4x1ZMpAGoZ6s750tQy1zP7WRqStJv2G9l3OQLFu -AQIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBS6uwvwYLV5u4TX9ZFMBpQe -hW4YKjAfBgNVHSMEGDAWgBRQlTo+l7rGlVRX5myTzXIHBN587jANBgkqhkiG9w0B -AQUFAAOBgQB+1bS0s6SpuCuMFFMpeBcE7WX//AGU/ZcRfO60ithV6NQ9OnN3djfS -H+ZeW3QEaQVMM0PIOuMO22/9AN6UVs8IxSuSkrfBOQ+PY/3169b6rpGl44/ZTx6B -O+c5wkkhnmy4+T6KnjQE5aO1VKBp3Ocl8PyIBqLLV52pZWUuytGlqA== +DQEBAQUAA4GNADCBiQKBgQDOg5xJj2j1L/bMeCEzq4L+lQNX3A/xpGq2cVL1FfoM +9+ZhUhREIN0PhBnnt1+xPGc9IqoBN8NzmyoGUfrnQGuAlXLHc8RV4Cve+ms6YXYZ +j2YBI1fmgkie7BbnaVzZZYmD9YPSicUpu67x9kpp4O6CTpkdLSgWf1EmrGz/2ynS +HQIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQpXIYiyu06TdXTMxWEL6/C ++E2YiTAfBgNVHSMEGDAWgBRcxFzhkQELDqWRGp2Hjnb+PHSDYzANBgkqhkiG9w0B +AQUFAAOBgQAQ7dEL3vRXWn41lDAjnhHi72DEHfpUazpW9zAJz63IDTWJNKP2h0Ab +xyHCryReB4oxwiFgFzLHAaknudoK8d3ceBL/ZLDlGy0KskxwW0Re3zNixYEFoWgx +Yzh+Fin/QXlJs3xtJqlHfaeo2AX9X5C1MDhAg22Ybt4w91OA88U5jg== -----END CERTIFICATE----- ` - testCertificateRsaSha1Key = ` ------BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALiGzF7B1A6TmCgj -1t2RHW6smdu7tN5l0O0/PQyL3vrWC4DE09A73y3Rf8GGzu2hnP/Mgbi9Bl9dpXYh -TWvLulDrgjxtM9dD8qm3zNbfOeB094gmpW6Mo/Jsgv+14cnUHDhzjHVkykAahnqz -vnS1DLXM/tZGpK0m/Yb2Xc5AsW4BAgMBAAECgYAWaNBzBYkSSBxna4rRl6kCYtXA -mLgrdiP8W/y3BFmNDueQuNacaFj/QH0KbKu+sizV5+ktHU+jz0Sj5wF3AOPccRtJ -QcGxr66f1uVPeBQfO27ac8b5UYwIFCu4gJ9IQp86INARuO4U5UR2o7sJ8rUpmf2M -p2JUQwKXjO0qDyDcQQJBAODWqTkdr0Av2vAOZe6SOfmr+u/2shAWPTg8uc1Y08Ng -1Fh0o7vqkOQ6Amtw4o5lE0RE0LlPSnxhpl28sT0gwUkCQQDSGdtIk77rh+WqNjYZ -GWhKBA2H8w0jo37Wz1aGyv/Yt6LC/LgOdOcadu4xSIgG+Al9JHdzLx7iWvNdIjD6 -l/75AkEA1szdwL5WVnkhrmPjCAhVMO0YALbrqKjGdfq1+7OYJDlWxOcyIe5X3GJ7 -O1AOccGopXkk+1UAMVJNUZJata6cWQJBAIEvhubsecNHL09mwALU3YxNS6ihKR4V -xML+gBynq4Ms/vZYADBbb1KVeEZza7ilQOhiyNPZUGssM2G7yVP8q7kCQHFCAgmO -redbrtiWNunEy1hVHOJD6ALriPz2i1W51NMbrPV2kOy9GpV/p3oby3GmXHs+Zlo6 -bBbOLhI7o+VlGaM= + testCertificateRsaSha1Key = `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM6DnEmPaPUv9sx4 +ITOrgv6VA1fcD/GkarZxUvUV+gz35mFSFEQg3Q+EGee3X7E8Zz0iqgE3w3ObKgZR ++udAa4CVcsdzxFXgK976azphdhmPZgEjV+aCSJ7sFudpXNlliYP1g9KJxSm7rvH2 +Smng7oJOmR0tKBZ/USasbP/bKdIdAgMBAAECgYEAh3dR1/cY1G1oKWxL60cAoNtC +3Clg1BQUZCUmU9rcshETsJdU7/PWzszK6XMidHK5DiNk/XOE5JrOEGNKgNODMCIC +06vUJ8joXSMbRMvpPmRuRwf2P1OeUv7nXig6iKPFI0u6zCsfYaXBIf4C3lcssXc+ +Ra6hH9CPU59DNhQUQ0kCQQDt/Z27n1gSpa612FZPDEtoMy/mZHjG9sc1WIO07Kqd +kElQ2k6S/VYxoSfVmG8Oj6yK13hs/Z3Kr5MJvjl+jTEvAkEA3iQ56aR2t1Xe1Qle +oMtI5ZuH8a+C97RF5T/FSKahAXaVsCSCDxRCLI7GDDqqdFxUhT5khBmqCaLZlv6J +0RxmcwJAYpmAkAskYhVinNRUbcuaMkGCxuE5aLU1M1TIvFyRE1aECYtool1zKHys +FEJjQJUl1yAONJmelirHsHGvQE8e4QJBAKldt2XixbysVNPaa/Jua2rcNT7Y0SLo +qG3MPC9TE+iYsDH2885pZLayOF90jydeifZ5BowNQS5NolZURWFQpO8CQAxHqB79 +1Z8mnatEkb0pJaw/CXngfbCGKCn7h7X5+Pup4nOpnnGmwmQtG6A5YRPsvBaY22/3 +uQgaIwjsQArmlhA= -----END PRIVATE KEY-----` + // openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha1SameSubjIssuer.key -out testCertificateRsaSha1SameSubjIssuer.csr -subj '/C=US/ST=SC/L=Default City/O=Default Company Ltd/OU=Test CA/CN=www.exampleca.com/emailAddress=example@example.com' + // openssl x509 -req -days 3650 -sha1 -in testCertificateRsaSha1SameSubjIssuer.csr -CA testCertificateRsaSha1CA.crt -CAcreateserial -CAkey testCertificateRsaSha1CA.key -extensions ext -extfile <(echo $'[ext]\nbasicConstraints = CA:FALSE') -out testCertificateRsaSha1SameSubjIssuer.crt + // + // Key = testCertificateRsaSha1Key + // CA = testCertificateRsaSha1CA + // + // This key intentionally has the same subject as testCertificateRsaSha1CA. + testCertificateRsaSha1SameSubjIssuer = `-----BEGIN CERTIFICATE----- +MIIDGjCCAoOgAwIBAgIUaTcUc8Cz/ZVnUotUfvexgWLIiT0wDQYJKoZIhvcNAQEF +BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRAwDgYDVQQLDAdU +ZXN0IENBMRowGAYDVQQDDBF3d3cuZXhhbXBsZWNhLmNvbTEiMCAGCSqGSIb3DQEJ +ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDEyMTIwMTEyNTNaFw0zNDEyMTAw +MTEyNTNaMIGhMQswCQYDVQQGEwJVUzELMAkGA1UECAwCU0MxFTATBgNVBAcMDERl +ZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UE +CwwHVGVzdCBDQTEaMBgGA1UEAwwRd3d3LmV4YW1wbGVjYS5jb20xIjAgBgkqhkiG +9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBALyHvdCqBhQZHVipNfRPbvYVHlfPG1q32fkYVcEUyKjQXrd9R31UogjH +LxhxEb2cxBi63yxIkJD1fnQXMLtubgvF++AcyYGK5/rSpcgHpcYF0x6vsXCJMvO8 +QN5rPJZX/gk75Ci26uq15K25LmOYeEJs+IIsUiRNUAEOtTufeuOPAgMBAAGjTTBL +MAkGA1UdEwQCMAAwHQYDVR0OBBYEFLE4C5n+g9n6hd/HbMctz0/9Da3wMB8GA1Ud +IwQYMBaAFFzEXOGRAQsOpZEanYeOdv48dINjMA0GCSqGSIb3DQEBBQUAA4GBAFrY +Ovoj9VOUFcGdrZ45fqCYxemjMWbgkjqE0HwvxwkdKrVEjOkemlcpKD6wzUx7EYs1 +fWHd+vvn9mQLvUENhpCTr+9yS+z9m6m+q6xUYGp9G9rMIpwz9M/zTYrJp1pKqcU7 +GeVqRN168ouWj/FfR+ubOcq3tji1qQA3mzRog5nz +-----END CERTIFICATE----- +` + testCertificateRsaSha1SameSubjIssuerKey = ` +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALyHvdCqBhQZHVip +NfRPbvYVHlfPG1q32fkYVcEUyKjQXrd9R31UogjHLxhxEb2cxBi63yxIkJD1fnQX +MLtubgvF++AcyYGK5/rSpcgHpcYF0x6vsXCJMvO8QN5rPJZX/gk75Ci26uq15K25 +LmOYeEJs+IIsUiRNUAEOtTufeuOPAgMBAAECgYB4B8A84pL+JsM9WHYGdrBBsk5g +P3a9+kGnyuuGA3KBsDAtiHCEheanyhDc8dgGrZFX4VoHOqf38qSwyrb3Dia29nIl +mzrWFa+xRy8r5FQw+BUw0SnOhC59RyCU7zLvUzVvqcv+/DRv7vRT96Fbe654ql3W +4sk0A6znwpmIGFcUSQJBAN5VLVrxq1qF058yZfiJ1PJDtMjBaMNrkBiNMT01Uvby +iDemGPXgX2eAtSB80sNEkHdSwjHCNNfuOLUD9BtPrdUCQQDZFDJVqSzJgAVBJ4ry +zlVEFx+ZoKu5G/T3E6ojUqh1hsJO5ODosqjh86BFdvvxz8ciRGdpB568VRRjuNvE +WanTAkAYnoP0MxiPYIxLb5A9Ej4jSX4GUOxh31JIdbIDHhl+wOJ2jwzqhRrrYiQs +YcYQ21HH9MEOM3wYgQeEe9iXAZ61AkAft/vC2H1a1AHwiz6aS9vZnydW40s0OQmK +MK1ji+hhg9dQf9D9L13N5jM88y3NH3cRYr1Zc2uWSTg5egFip1dRAkEAui5QR6OH ++H6PjQWeW5sq2XWJ3rMepfHfkEWOOOyjSpUN832LyWfJJYQVBwySVXCkbPqbRscG +h8zPSgGSPt9UIg== +-----END PRIVATE KEY----- +` + + // openssl req -x509 -newkey rsa:2048 -days 3650 -sha1 -keyout testCertificateRsaSha1SelfSigned.key -nodes -subj '/CN=www.example.com/ST=SC/C=US/emailAddress=example@example.com/O=Example/OU=Example' -addext "basicConstraints=CA:FALSE" -out testCertificateRsaSha1SelfSigned.crt + // + // Key = testCertificateRsaSha1SelfSignedKey + // CA = self-signed + testCertificateRsaSha1SelfSigned = `-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgIUYnuOhBfzAKuCC2fUAmVMR7+C1jEwDQYJKoZIhvcNAQEF +BQAwfDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMQswCQYDVQQIDAJTQzELMAkG +A1UEBhMCVVMxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20xEDAO +BgNVBAoMB0V4YW1wbGUxEDAOBgNVBAsMB0V4YW1wbGUwHhcNMjQxMjA1MTc1MjM0 +WhcNMzQxMjAzMTc1MjM0WjB8MRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20xCzAJ +BgNVBAgMAlNDMQswCQYDVQQGEwJVUzEiMCAGCSqGSIb3DQEJARYTZXhhbXBsZUBl +eGFtcGxlLmNvbTEQMA4GA1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANyrQEMLrd9QY0ZH8GbDENHh +qDrEuaib1Xy+M8qdRQbWZBRYDLQQrveDOfp7oPT5DylYg5oH0P1K01bfqRp6+PhG +LEr2GDu41smvUQiCCsIJTxwGKFYygFuKM4OfB6ieydTQJnZNc+1QNSDnIhizZ98O +j9H8bnfUeHSbVjL9oONFOIUbLzqF/FzdL7yvlifFDdI998uBc2iYprh3m1NOAxQu +6TXhxK2j34qPaBhGdtPaOXsKW0qkA0XySROSh9EWnkoQx4bdc71dmbCJflxeWkOV +RVCHwEU1oRK3FA73LzMP9C/rSp8TiTYc39rNSq4Tnbm5EDcHEI298egp3xnsxekC +AwEAAaNNMEswHQYDVR0OBBYEFN+n2yc9ULcaMkqTfXRGQ9AuU/H7MB8GA1UdIwQY +MBaAFN+n2yc9ULcaMkqTfXRGQ9AuU/H7MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEF +BQADggEBAJim5Ep7rD6wfbg2aWdltsrHeSbX/1iva/yPkFyMvDMpTpeGKqRWQlRL +e39PyqF6QyZGsfUJsib/UzsUQD0xuabwpS2aOIy3Ie+x+xmNga1FdYvN9NbnPUyi +7VoQ5lZSe+ZQHa5iYWuDJtrAcFUib3YrTOKtgDiHroMICWCQEnK4vwMHk0G9yvHJ +RJVqubu+JSEwivgtQRdcUHBSz9GHgCm58YyV9we6UAVFSudpFfTRbr5gKIiP858q +atCQ7S3S25DHcr8Hj1RmaiLmhe1o5LtG282y5zGte+8TlMnimwCoeldRVngH9Nhs +bnqtc2ouTrKiR0Ec+QsV1a1hfhRuj2M= +-----END CERTIFICATE----- +` + testCertificateRsaSha1SelfSignedKey = `-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcq0BDC63fUGNG +R/BmwxDR4ag6xLmom9V8vjPKnUUG1mQUWAy0EK73gzn6e6D0+Q8pWIOaB9D9StNW +36kaevj4RixK9hg7uNbJr1EIggrCCU8cBihWMoBbijODnweonsnU0CZ2TXPtUDUg +5yIYs2ffDo/R/G531Hh0m1Yy/aDjRTiFGy86hfxc3S+8r5YnxQ3SPffLgXNomKa4 +d5tTTgMULuk14cSto9+Kj2gYRnbT2jl7CltKpANF8kkTkofRFp5KEMeG3XO9XZmw +iX5cXlpDlUVQh8BFNaEStxQO9y8zD/Qv60qfE4k2HN/azUquE525uRA3BxCNvfHo +Kd8Z7MXpAgMBAAECggEAY78lNSk6Vw9HUKWEDW9vUu/l02rJYWXPgquXTab5ZLXU +Vz3VwC8qZ8dxlb/8ab+LEu1nz2BpH5WLImHHVqjvkYpmyxuiqJxMuq38uxPNORhs +IgbGhPAfBUHbN0vTcm0UXpYYTLGGDWeMHGteBjxSX4l9iTXJ2XC5Yjw1Iqdy6kew +wEACuHgROJKYFEBeufhuSOSpplrepaqpBV4g5l75BVCBYQ/nQLsKcLQgaQ42kx+x +7YNvSlGeieEcj/Eft5zB6HxADfjyMlNwDJ2bi37oq9s9q8PKVBVFYyCOAz06ZGuo +pwY8z2Qpi3j1D0nnPWMXjEP5NmDotORy4EFJtfSC4QKBgQD5G28GHxtp1197hMhB +SZ8bzFQ6kBFxVHjrgjxYb8kS5j2ANm49/oW+PnnNwFbO84fgC97oQDE0K8cPBL3A +tcsQvbvz29M2VcPu9zus6YxRcsGTyCLRg0aT4NuXtRccYg681jH1FTFZCiNpZGnx +Z6C1+zW9CcB1aBbzjiRlbPx6+wKBgQDixl+awgDIt19HnsUVup7+zSEXxT/8ixc9 +QENdZaEC8lZJY/WzehKgZpMjmN0zTmWGU2anq6i5tbivyFXaLlZTFdpjK1eq4h/n +JU9oJjMhZzoRA6Vhlrqiy6CTECa/fyr/d7zB9bkLveSUds/U0n4P6oU2msOtAJ8d +SFtApbHtawKBgQDAfbRzFIKIbQa5Wcesu4kZX/EON9liq5Ws1rxu0iKcWhHYCzdw +7EbI1Vol5aSu0nyCYmnjKgdbeyCcuFswmMnLq/Ga5Jj3eZqoA5+3Y9kr7vMqkRJm +t3xINQ860ZKEOjmNLi74ZWH2neDzRcaf5iXHudCyvOBdWQuzNHlnbqpDFQKBgCrV +o5tcx78h++pQUBPRo1SntHeD95khQKt+JvtORgKDec71BaT4CuqnVWWk6ytUxJKB +0GMdZopli9QQOD80/3NELnMK7c1GVxZXEs+uX3wQvoQWNzfeu7QiWFtO8rK7N4j3 +ufy9CE3yeWmdo5YkiFFDUBRHWWylMGjckPf+FESvAoGAdZ63rjBO9XT2I/zu+Yvj +fTror7gkwHlb5H1O/ynA/R6TdMjlCZHl1Sv6ThdS77nzrEML1U3DfmEm+D3NgtVd +zEfT6Sd9HQFjt1qjydVxicSNPUc4Uv30WZ6+HsIqp7ER9XzYEPPsUkfQxZEghddb +X7ziGItWQDkoCNS0SzR0rqw= +-----END PRIVATE KEY----- +` + + // openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha256Key.key -out testCertificateRsaSha256.csr -subj '/CN=www.example.com/ST=SC/C=US/emailAddress=example@example.com/O=Example/OU=Example' + // openssl x509 -req -days 3650 -sha256 -in testCertificateRsaSha256.csr -CA testCertificateRsaSha1CA.crt -CAcreateserial -CAkey testCertificateRsaSha1CA.key -extensions ext -extfile <(echo $'[ext]\nbasicConstraints = CA:FALSE') -out testCertificateRsaSha256.crt + // + // Key = testCertificateRsaSha256Key + // CA = testCertificateRsaSha1CA + testCertificateRsaSha256 = `-----BEGIN CERTIFICATE----- +MIIDTDCCArWgAwIBAgIUESnhsJLBoYVoOfqUpoJcxIjpr9IwDQYJKoZIhvcNAQEF +BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRAwDgYDVQQLDAdU +ZXN0IENBMRowGAYDVQQDDBF3d3cuZXhhbXBsZWNhLmNvbTEiMCAGCSqGSIb3DQEJ +ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDEyMTIwMTA3MTVaFw0zNDEyMTAw +MTA3MTVaMIGhMQswCQYDVQQGEwJVUzELMAkGA1UECAwCU0MxFTATBgNVBAcMDERl +ZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UE +CwwHVGVzdCBDQTEaMBgGA1UEAwwRd3d3LmV4YW1wbGVjYS5jb20xIjAgBgkqhkiG +9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBANiNMnmpMORg+X9TujvAXx1ysM9SzuYLX5SKhxq9SiSqKE+YZjxpkf2E +vBKraxgKIBEHrGpn5CX2YKycT0Tio6G98/8O/xyDAqdHIE5PCD9srz5INtw5Vx9u +LbtSOPwzLoN6qQIH31rdXShdkKVKDegsKgPaRPBlY1O43sXgkCahAgMBAAGjfzB9 +MB0GA1UdDgQWBBRcxFzhkQELDqWRGp2Hjnb+PHSDYzAfBgNVHSMEGDAWgBRcxFzh +kQELDqWRGp2Hjnb+PHSDYzAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEA +u1CFt+f41DhmsOWkaT7SLBR8ODmdq91ta8vYP+L3Ws2fZ2tUNH/DX/lofR90GXA3 +L5W8aWhQYdk+S7zuCFmt18QFjRXX0szbLawGRA+t4zQy/AeOIVnmrlKSs6rQ4I+e +yuoyUfLE8+ULl92NZbj3pHKnWLddD7uVK2GYHr/P8kQ= +-----END CERTIFICATE----- +` + testCertificateRsaSha256Key = `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANiNMnmpMORg+X9T +ujvAXx1ysM9SzuYLX5SKhxq9SiSqKE+YZjxpkf2EvBKraxgKIBEHrGpn5CX2YKyc +T0Tio6G98/8O/xyDAqdHIE5PCD9srz5INtw5Vx9uLbtSOPwzLoN6qQIH31rdXShd +kKVKDegsKgPaRPBlY1O43sXgkCahAgMBAAECgYAxSVGnpv5dvESM2j2Uw9/iD+x2 +A17btNL4N98wEs0BM0khdIowTcbQcJltll41hnht59UyEps2mLDAGINiJkMfbWyD +uKuy1Lmo/+QE4hTZ7VSIoznmpQr4XjytHmSVP5JYBSQIG/uCSg2OoMwjwnXLO6rO +NbYX2392upZZm135UQJBAPzubZkElK19qU0hbMwWfgJE2OwYuo6lS/3x/l40sgHq +NbkurL8W/NIF5v+/X9DOCYbUqp8E0DtLZPmXebACDoMCQQDbLccxv9erBBq2+YNJ +P2ZYzHbwSrj98NLMwdMkutbbHd2521DSXbaT0mdb2QT3MpdK0PT98JnJccI53vHQ +ua0LAkEAoLFGVjIv121/s24p9hvQINbmzlEDrX7dIdCuH+HwugC38xfxTlJne3Oe +iBto33sXWF8iq3beaN2EoIIZILadywJAD+K7g0GSUhTUEtr2xwJPWrRHEpd33P/t +Z2XM9eaM2AjMH0JkEzszlnczgpayI3CJQqTufNFJdC5Ik4UzJZuvjQJBAM8cYMDt +tO6ylsZ2JWKlnsFVW0Nsx696Y3dLygymVLlU607/a7QP9Lakf9XwI8dSmZDIuW9l +w0VeEQOmXrayLUM= +-----END PRIVATE KEY----- +` + + // openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha1IntCA.key -out testCertificateRsaSha1IntCA.csr -subj '/CN=www.example-int.com/ST=SC/C=US/emailAddress=example@example.com/O=Example/OU=Example' + // openssl req -x509 -days 3650 -sha1 -in testCertificateRsaSha1IntCA.csr -CA testCertificateRsaSha1CA.crt -CAkey testCertificateRsaSha1CA.key -addext "keyUsage=cRLSign, digitalSignature, keyCertSign" -addext "extendedKeyUsage=serverAuth, clientAuth" -nodes -out testCertificateRsaSha1IntCA.crt + // + // Key = testCertificateRsaSha1IntCAKey + // CA = testCertificateRsaSha1CA + testCertificateRsaSha1IntCA = `-----BEGIN CERTIFICATE----- +MIIDKzCCApSgAwIBAgIUHQKtMkN+OTAgVcWesPBcLGdKKyYwDQYJKoZIhvcNAQEF +BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRAwDgYDVQQLDAdU +ZXN0IENBMRowGAYDVQQDDBF3d3cuZXhhbXBsZWNhLmNvbTEiMCAGCSqGSIb3DQEJ +ARYTZXhhbXBsZUBleGFtcGxlLmNvbTAeFw0yNDEyMTIwMTE1MTZaFw0zNDEyMTAw +MTE1MTZaMIGAMRwwGgYDVQQDDBN3d3cuZXhhbXBsZS1pbnQuY29tMQswCQYDVQQI +DAJTQzELMAkGA1UEBhMCVVMxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBs +ZS5jb20xEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAsMB0V4YW1wbGUwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAMhQWsJpwgn9o/3tMXh9UVwuU8f+FzYWI3ff +F0XrI9M3Th1DvxwCldx818S1QrnJoSVH8BMFacdXFFMo48xcRSO9muS05KY5xVmU +3J96Ylca/oNsk5w7vKzuGFX4LcnfD3iRC7ZyfTi8ZgkrFIk0TmB6WHQUACh5Atnq +uFQPzOeBAgMBAAGjfzB9MB0GA1UdDgQWBBQ+Q7pxwLdGiV1AX0JBsh0mu6+UnzAf +BgNVHSMEGDAWgBRcxFzhkQELDqWRGp2Hjnb+PHSDYzAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJ +KoZIhvcNAQEFBQADgYEAYVUcnEMt4ybpAsje3torX95N/7gsdmSsZCLWHPy6mkLB +YpKUy/tiwcKkStau30HU3glfwD/ys+8SrXERRodU+ja5FOr3Usj74GGzwYI10PE/ +0FjI+IHPPK1F1djbJmjeEG2nT7qa51ugN3pmf6ci2SfamuLDjEI7EUTUwqCbw2E= +-----END CERTIFICATE----- +` + // Key is not used, but keep here for reference for signing new certs if needed. + testCertificateRsaSha1IntCAKey = `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMhQWsJpwgn9o/3t +MXh9UVwuU8f+FzYWI3ffF0XrI9M3Th1DvxwCldx818S1QrnJoSVH8BMFacdXFFMo +48xcRSO9muS05KY5xVmU3J96Ylca/oNsk5w7vKzuGFX4LcnfD3iRC7ZyfTi8Zgkr +FIk0TmB6WHQUACh5AtnquFQPzOeBAgMBAAECgYA+rf4oVWV1MNvOyhivxi7eNFTd +AKIMt5KzoKgspa5ZGjYkLB2xyxFPo/T0RW+yqOf2vXLe0NPPn2zptKLLQJgVUDer +zNkVH+aw2WTgC7QXzCz1CuJALoIh3R/uz3Ksdqc3QgjQkPsECKwJDdqKQmlhGvCR +XWIfT96CHF1Xv/2QXQJBAON/E6QcR3BJsa/nkN/5bzYNtM+UKnZmZueCGdEyOuJE +AGUdbGdV9Tg6WWUZn2j5VGF41s/l8KL4umzm7rL/0pMCQQDhaWbS+meWkKGdNI4q +TkwSRX4iRLotq6gEsSAVoxBWfjjtRmcuxi3WFjflMa1ZfKBJCgEvVhFpb1ow0FVa +vMYbAkEA049PoqQxwzilJ2J/leoPBAOHDCtLucPNGqogfCzsGZMHkwDj2M1VOC77 +B0vmtOZ5FBQeIERDnisUo0W24XuKRQJBAJJHtmS//61kGp1MV934hcFtu5c9hpzQ +wu6Yi7u+4IFg1EyW3asrDN/b91YTUO27xMDhbzdq4U3M53i6GkoSK3UCQFN5heH6 +qOKGY3fpKIlWEk5W9wLZZlguOcCmOR1f3n/PtRC+71lFFpjDzeCyONoWEtLY4TgR +XseuoC1yof+Q05c= +-----END PRIVATE KEY----- +` + // openssl ecparam -out exampleca.key -name secp224r1 -genkey // openssl req -x509 -sha1 -key exampleca.key -days 3650 -out exampleca.crt -addext "keyUsage=cRLSign, digitalSignature, keyCertSign" -addext "extendedKeyUsage=serverAuth, clientAuth" -nodes -subj '/C=US/ST=SC/L=Default City/O=Default Company Ltd/OU=Test CA/CN=www.exampleca.com/emailAddress=example@example.com' // openssl ecparam -out testCertificateEcdsaSha1.key -name secp224r1 -genkey @@ -1943,6 +2181,89 @@ func TestExtendedValidateRoute(t *testing.T) { }, expectedErrors: 1, }, + { + name: "Edge termination with self-signed cert using SHA1 with RSA key", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: testCertificateRsaSha1SelfSigned, + Key: testCertificateRsaSha1SelfSignedKey, + }, + }, + }, + expectedErrors: 0, + }, + { + name: "Reencrypt termination with destination CA root and intermediate cert using SHA1 with RSA key", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationReencrypt, + DestinationCACertificate: testCertificateRsaSha1CA + testCertificateRsaSha1IntCA, + }, + }, + }, + expectedErrors: 1, + }, + { + // Root CAs are self-signed; therefore not subject to signature algorithm restrictions. + name: "Reencrypt termination with destination CA root cert using SHA1 with RSA key", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationReencrypt, + DestinationCACertificate: testCertificateRsaSha1CA, + }, + }, + }, + expectedErrors: 0, + }, + { + // Root CAs are self-signed; therefore not subject to signature algorithm restrictions. + name: "Edge termination with root CA cert using SHA1 and server cert using SHA256", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + CACertificate: testCertificateRsaSha1CA, + Certificate: testCertificateRsaSha256, + Key: testCertificateRsaSha256Key, + }, + }, + }, + expectedErrors: 0, + }, + { + // Intermediate CAs are NOT self-signed; therefore are subject to signature algorithm restrictions. + name: "Edge termination with root CA cert using SHA1, intermediate cert using SHA1, and server cert using SHA256", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + CACertificate: testCertificateRsaSha1CA + testCertificateRsaSha1IntCA, + Certificate: testCertificateRsaSha256, + Key: testCertificateRsaSha256Key, + }, + }, + }, + expectedErrors: 1, + }, + { + // Make sure our isSelfSignedCert function doesn't assume that when subject is + // equal to the issuer that it's a self-signed cert. + name: "Edge termination with CA-signed cert using SHA1, but cert subject is equal to issuer", + route: &routev1.Route{ + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: testCertificateRsaSha1SameSubjIssuer, + Key: testCertificateRsaSha1SameSubjIssuerKey, + }, + }, + }, + expectedErrors: 1, + }, } for _, tc := range tests { @@ -1993,3 +2314,27 @@ func TestExtendedValidateRoute(t *testing.T) { }) } } + +func processCertificate(certPath string) (*x509.Certificate, error) { + // Read and decode the PEM file + pemData, err := ioutil.ReadFile(certPath) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", certPath, err) + } + + block, _ := pem.Decode(pemData) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block from %s", certPath) + } + + // Parse the certificate + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate from %s: %w", certPath, err) + } + + // Do something with the certificate, e.g., print its subject + fmt.Println("Certificate Subject:", cert.Subject) + + return cert, nil +}