-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OCPBUGS-45290: Reject All CA-Signed Certs Using SHA1 #642
base: master
Are you sure you want to change the base?
Conversation
Skipping CI for Draft Pull Request. |
@gcs278: This pull request references Jira Issue OCPBUGS-45290, which is invalid:
Comment The bug has been updated to refer to the pull request using the external bug tracker. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
d2d10e7
to
36105f1
Compare
6b47be2
to
7bb99ea
Compare
/retest |
/jira refresh |
@gcs278: This pull request references Jira Issue OCPBUGS-45290, which is invalid:
Comment The bug has been updated to refer to the pull request using the external bug tracker. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
/jira refresh |
@gcs278: This pull request references Jira Issue OCPBUGS-45290, which is valid. The bug has been moved to the POST state. 3 validation(s) were run on this bug
Requesting review from QA contact: In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
tested it with 4.18.0-0.ci.test-2024-12-06-060222-ci-ln-4l8tlmt-latest, same steps as the bug
/label qe-approved |
@gcs278: This pull request references Jira Issue OCPBUGS-45290, which is valid. 3 validation(s) were run on this bug
Requesting review from QA contact: In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
/retest-required |
Andy offered to help review (thanks!) |
/assign |
// 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())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this log message identify the route, so that the invalid certificate can be found/fixed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message gets written to the route's Admitted
condition type with the reason ExtendedValidationFailed
. It is directly written on the affected route, which makes it clear of which route is impacted.
// isSelfSignedCert determines if a provided certificate is | ||
// self-signed by checking if the issuer is equal to the subject. | ||
func isSelfSignedCert(cert *x509.Certificate) bool { | ||
return bytes.Equal(cert.RawIssuer, cert.RawSubject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a discussion of how to tell when a cert is self-signed in https://security.stackexchange.com/questions/93162/how-to-know-if-certificate-is-self-signed, and one example shows "Just because the fields (Issuer and Subject) have the same value that does not mean the certificate is self-signed". Do you think that could apply here, and you also might need to check if there's an Authority present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea great point. I did find that same comment, but I ended up proceeding with this logic because HAProxy also considers Issuer == Subject
as self-signed and allows SHA1 ca-signed leaf certs that have the same Subject & Issuer.
But you made me think about testing Intermediate certs. Just now, I tested a SHA1 Issuer == Subject
Intermediate CA cert, and that caused HAProxy to fail. That's not good.
You are right, we need to be more elegant here and handle when the Issuer == Subject
edge case. However, I'm struggling to find a perfect solution. I see Authority Key Identifier (AKI) is marked not-critical in the RFC and I see discussion of how it's only recommended, not mandated. I also found an interesting bug in which even the x509 Go library sets AKI to Subject Key Id (SKI) if Subject == Issuer
(basically it also assumes if Subj == Issuer, that it's self signed). So I think AKI is not really that reliable to use.
So, I have two options right now:
- Check
Subject == Issuer && (AKI == nil OR AKI == SKI)
- This probably covers 99% of cases, but one could still make a cert with
Subject == Issuer
andAKI == nil
while it NOT being self-signed, causing HAProxy to crash
- This probably covers 99% of cases, but one could still make a cert with
- Call
CheckSignature
and validate if cert is signed by itself- I think this is pretty flawless, except:
- It's going to be a bit more computationally expensive
- There's a risk that the x509 GO library could just flat out reject verification of SHA1 certs in the future. It already flat out rejects doing verifications on MD5 certs. We have to vendor updates to it though, and our unit tests would catch them.
- I think this is pretty flawless, except:
The more I think about this, I wish I could do Option 1, and we had NE-557 to catch the odd cases where Subject == Issuer and AKI isn't added.
But I think we should do option 2, I'll provide benchmarking results. And I tested with the 143 public root CAs on my laptop (some are SHA1) and the function works correctly for them.
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should you only check the CACertificate
for whether it's self-signed, instead of within validateCertSignatureAlgorithms
? In other words, is a self-signed cert okay to be ignored for a DestinationCACertificate
or Certificate
, which are checked by validateCertSignatureAlgorithm
or validateCertificatePEM
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words, is a self-signed cert okay to be ignored for a DestinationCACertificate or Certificate
DestinationCACertificate
needs to allow SHA1 self-signed certs (a lot of root CAs use SHA1). So it is required for us to ignore self-signed certs in our SHA1 validation here.
As for Certificate
, there isn't a critical business need to allow SHA1 self-signed leaf certs unlike DestinationCACertificate
and CACertificate
. But two thoughts:
- I think the router should be accurate in what it's rejecting, and shouldn't reject things that HAProxy actually supports.
- 4.15 dev clusters (I hope not prod...) may be using self-signed SHA1 certs for testing. When they upgrade, we are currently forcing them to recreate all of them, even though 4.16 HAProxy supports them. Unnecessary upgrade pains for customers.
Open to other opinions.
return certs, fmt.Errorf("router does not support certs using MD5") | ||
default: | ||
// Acceptable algorithm | ||
if err := validateCertSignatureAlgorithms(certs); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, this allows the cert to be self-signed now - introducing new behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Our validation was invalid previously, rejecting valid scenarios, so this fixes that.
Do you think that will be a problem?
// openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha1.key -out testCertificateRsaSha1.csr -subj '/CN=www.example.com/ST=SC/C=US/[email protected]/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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
// 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 | |
// openssl x509 -req -days 3650 -sha1 -in testCertificateRsaSha1.csr -CA testCertificateRsaSha1CA.crt -CA createserial -CAkey testCertificateRsaSha1CA.key -extensions ext -extfile <(echo $'[ext]\nbasicConstraints = CA:FALSE') -out testCertificateRsaSha1.crt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually -CAcreateserial
is the correct arg: https://linux.die.net/man/1/x509#:~:text=called%20%22mycacert.srl%22.-,%2DCAcreateserial,-with%20this%20option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments, mostly about why it is okay for any certificate to be self-signed now.
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.
7bb99ea
to
e68af03
Compare
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review @candita. I have pushed some fixes and should be ready for review.
// isSelfSignedCert determines if a provided certificate is | ||
// self-signed by checking if the issuer is equal to the subject. | ||
func isSelfSignedCert(cert *x509.Certificate) bool { | ||
return bytes.Equal(cert.RawIssuer, cert.RawSubject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea great point. I did find that same comment, but I ended up proceeding with this logic because HAProxy also considers Issuer == Subject
as self-signed and allows SHA1 ca-signed leaf certs that have the same Subject & Issuer.
But you made me think about testing Intermediate certs. Just now, I tested a SHA1 Issuer == Subject
Intermediate CA cert, and that caused HAProxy to fail. That's not good.
You are right, we need to be more elegant here and handle when the Issuer == Subject
edge case. However, I'm struggling to find a perfect solution. I see Authority Key Identifier (AKI) is marked not-critical in the RFC and I see discussion of how it's only recommended, not mandated. I also found an interesting bug in which even the x509 Go library sets AKI to Subject Key Id (SKI) if Subject == Issuer
(basically it also assumes if Subj == Issuer, that it's self signed). So I think AKI is not really that reliable to use.
So, I have two options right now:
- Check
Subject == Issuer && (AKI == nil OR AKI == SKI)
- This probably covers 99% of cases, but one could still make a cert with
Subject == Issuer
andAKI == nil
while it NOT being self-signed, causing HAProxy to crash
- This probably covers 99% of cases, but one could still make a cert with
- Call
CheckSignature
and validate if cert is signed by itself- I think this is pretty flawless, except:
- It's going to be a bit more computationally expensive
- There's a risk that the x509 GO library could just flat out reject verification of SHA1 certs in the future. It already flat out rejects doing verifications on MD5 certs. We have to vendor updates to it though, and our unit tests would catch them.
- I think this is pretty flawless, except:
The more I think about this, I wish I could do Option 1, and we had NE-557 to catch the odd cases where Subject == Issuer and AKI isn't added.
But I think we should do option 2, I'll provide benchmarking results. And I tested with the 143 public root CAs on my laptop (some are SHA1) and the function works correctly for them.
// openssl req -newkey rsa:1024 -nodes -keyout testCertificateRsaSha1.key -out testCertificateRsaSha1.csr -subj '/CN=www.example.com/ST=SC/C=US/[email protected]/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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually -CAcreateserial
is the correct arg: https://linux.die.net/man/1/x509#:~:text=called%20%22mycacert.srl%22.-,%2DCAcreateserial,-with%20this%20option
return certs, fmt.Errorf("router does not support certs using MD5") | ||
default: | ||
// Acceptable algorithm | ||
if err := validateCertSignatureAlgorithms(certs); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Our validation was invalid previously, rejecting valid scenarios, so this fixes that.
Do you think that will be a problem?
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words, is a self-signed cert okay to be ignored for a DestinationCACertificate or Certificate
DestinationCACertificate
needs to allow SHA1 self-signed certs (a lot of root CAs use SHA1). So it is required for us to ignore self-signed certs in our SHA1 validation here.
As for Certificate
, there isn't a critical business need to allow SHA1 self-signed leaf certs unlike DestinationCACertificate
and CACertificate
. But two thoughts:
- I think the router should be accurate in what it's rejecting, and shouldn't reject things that HAProxy actually supports.
- 4.15 dev clusters (I hope not prod...) may be using self-signed SHA1 certs for testing. When they upgrade, we are currently forcing them to recreate all of them, even though 4.16 HAProxy supports them. Unnecessary upgrade pains for customers.
Open to other opinions.
@gcs278: The following tests failed, say
Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
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.