-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Gracefully handle bad validity dates in public_key:pkix_path_validation #7515
Conversation
CT Test ResultsNo tests were run for this PR. This is either because the build failed, or the PR is based on a branch without GH actions tests configured. Results for commit 97d4036 To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass. See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally. Artifacts// Erlang/OTP Github Action Bot |
I am not very fond of these kind of workarounds, but we have been forced to be pragmatic in other cases too, and it seems harmless enough solution. It is vacation time now so I would like to wait a little for final inclusion decision so that we can have a team review. And we would also need to add the new error to the documentation. |
Thanks @IngelaAndin - To be honest I would have also gladly glossed over this edge case, but we are getting plenty of hardware certificates not abiding to the RFCs and we do not have many alternatives. Please let me know if you would like me to add the documentation commit to the PR as well. |
% First test exception throwing without validation function | ||
try | ||
public_key:pkix_path_validation(Root, CertificateChain, []) | ||
catch | ||
% We expect this function to throw if the date is wrong | ||
error:function_clause -> | ||
ok; | ||
_ -> | ||
throw("Expected function_clause error on bad date") | ||
end, |
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 is wrong:
Replace try catch with:
{error, {bad_cert, invalid_validity_dates}} = public_key:pkix_path_validation(Root, CertificateChain, []),
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 is the current behaviour if no verify fun is supplied. Why would you like to change it?
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.
Since the test will be merged along with your changes there is no reason to test the behavior in the old code.
You shall test the new functionality, i.e. that {error, {bad_cert, invalid_validity_dates}} is returned if no verify_fun is provided.
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.
@andreasanta what about this one?
FYI: This code does not seem to verify what you probably intended (error:function_clause
catch part is skipped, testcase does not notice).
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.
@dgud This diff does not alter the old functionality if you don't provide verify_fun
. This is why I am leaving the testcase. In order to preserve backwards compatibility, I am still raising an error if no verify_fun
is specified.
@u3s Sorry maybe I did not explain my diff correctly. The previous behaviour has not been altered on purpose. You can optionally alter if if you pass a verify_fun
that intercepts the validation error. This testcase exists to ensure that the previous behaviour - i.e. throwing an error upon failed validation - still happens if you don't specify a verify_fun
.
Please let me know if you'd like a different approach.
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.
You have altered the functionality if no verify_fun is used and you fail to test it.
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.
I do not see a reason to preserve the crash behavior for something that should not really happen. I think this PR is about adding a workaround | graceful handling of that it sometimes does due to mistakes that are out of users control.
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.
@IngelaAndin Refactored as suggested, plus some other improvements, to improve readability.
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.
You have altered the functionality if no verify_fun is used and you fail to test it.
Sorry I do not understand it. I have written two tests, one that lets the code crash as expected, the other prevents the crash by supplying a custom verify_fun
. What did I miss?
Yes we want you to add a documentation commit where the invalid_validity_dates are added to the possible bad_cert_reason() type. |
Also if you base your patch on maint instead of master we can include it for OTP-26.1 instead of waiting for OTP-27. This I think should be ok as it does not really change any API behavior (only adds an error reason) and it is unlikely to change any existing verify_fun behavior but rather expands the workaround possibilities for the verify_fun, which is one of its uses although the main purpose is to be able to handle application specific extensions. But that is up to you. |
@andreasanta and adding the bad_cert_reason() to the documentation is actually adding it to the spec in public_key.erl |
091adec
to
6900b58
Compare
6900b58
to
bffa3d4
Compare
@IngelaAndin All done. Addressed comments, rebased on maint and added docs. Please confirm if ok. |
6af8068
to
e9adb6e
Compare
lib/public_key/src/pubkey_cert.erl
Outdated
NewState -> | ||
NewState |
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.
I have doubts about this catch everything clause.
Previous code didn't work that way.
Is there a reason for it?
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.
No reason, preventing crash in case of unexpected value. I can remove it if necessary.
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.
I actually think that the last case is the case that the catch verify_fun returns a state. I think the following code could benefit readablility:
validate_time(OtpCert, UserState, VerifyFun) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
{'Validity', NotBeforeStr, NotAfterStr}
= TBSCert#'OTPTBSCertificate'.validity,
Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
case valid_dates(NotBeforeStr, NotAfterStr}) of
{true, {NotBefore, NotAfter}} ->
case ((NotBefore =< Now) and (Now =< NotAfter)) of
true ->
UserState;
false ->
verify_fun(OtpCert, {bad_cert, cert_expired}, UserState, VerifyFun)
end;
{false, Error} ->
verify_fun(OtpCert, {bad_cert, Error}, UserState, VerifyFun)
end.
valid_dates(NotBeforeStr, NotAfterStr) ->
try
NotBefore = time_str_2_gregorian_sec(notBefore, NotBeforeStr),
NotAfter = time_str_2_gregorian_sec(notAfter, NotAfterStr),
{true, {NotBefore, NotAfter}}
catch
error:function_clause ->
{false, invalid_validity_dates}
end.
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 could be related to comment on tests!
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.
@IngelaAndin ah yes thank you for flagging this! Would you like me to refactor as suggested?
ea7b500
to
0e656af
Compare
lib/public_key/src/pubkey_cert.erl
Outdated
% Parse and check validity of the certificate dates, and if it fails, invoke `verify_fun` to | ||
% hand over control to the caller in order to decide what to do. | ||
case parse_and_check_validity_dates(OtpCert) of | ||
|
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.
@adresanta I do not think we need these empty lines between the case clauses. No other cases in the application are written like that. Otherwise I think this is now looking good.
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.
We should adhere to coding style of the application. Otherwise I think it looks good :)
0e656af
to
9b6516f
Compare
Done away with the spacing within case clauses, thank you. |
So last testing round, should be able to merge tomorrow. |
@andreasanta some other changes has created a merge conflict, could you please rebase your branch on latest maint and resolve it so that we can use the merge button, |
lib/public_key/src/public_key.erl
Outdated
@@ -160,7 +160,7 @@ | |||
-type oid() :: tuple(). | |||
-type cert_id() :: {SerialNr::integer(), issuer_name()} . | |||
-type issuer_name() :: {rdnSequence,[[#'AttributeTypeAndValue'{}]]} . | |||
-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature | name_not_permitted | missing_basic_constraint | invalid_key_usage | duplicate_cert_in_path | {revoked, crl_reason()} | atom(). | |||
-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature | name_not_permitted | missing_basic_constraint | invalid_key_usage | duplicate_cert_in_path | {revoked, crl_reason()} | invalid_validity_dates | atom(). |
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.
line above 200 chars. break line please, so there is =< 100 chars per line.
pkix_path_validation_bad_date() -> | ||
[{doc, "Ensure bad date formats in `validity` are handled gracefully by verify fun"}]. | ||
pkix_path_validation_bad_date(Config) when is_list(Config) -> | ||
|
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.
lib/public_key/src/pubkey_cert.erl
Outdated
@@ -140,19 +140,47 @@ prepare_for_next_cert(OtpCert, ValidationState = #path_validation_state{ | |||
%% current time. | |||
%%-------------------------------------------------------------------- | |||
validate_time(OtpCert, UserState, VerifyFun) -> | |||
|
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.
please stick to convention in module and don't add empty line after ->
@andreasanta ping |
9b6516f
to
7dae72f
Compare
Rebased and adapted to style reqs. Thank you. |
@andreasanta Thanks, need one more test day (night) for sanity check of the rebase and small adjustments and then we should finally be able to merge. |
If a certificate contains an invalid date in the
validity
attribute, the functionpublic_key:pkix_path_validation
raises afunction_clause
error due to the invocation ofcalendar:date_to_gregorian_days
with an invalid date.In the attached unit test, the leaf certificate contains the invalid date string "19000100000000Z", and this would entirely fail the validation of the certificate chain.
In this PR, I am handling this edge case by delegating the decision to
verify_fun
and thus the caller. Ifverify_fun
traps the error and decides to continue, validation is not interrupted, otherwise thefunction_clause
error is raised.This is an example error reproduced locally with the attached certificate:
NOTE: I am aware invalid dates should never be passed to this function, but I am dealing with a hardware implementation that cannot be easily changed, thus I have to permit a more lenient parsing and recovery from error. Also, I do not believe a
function_clause
error is the right response, since it's too generic and cannot be handled accordingly or interpreted by the caller.CC @IngelaAndin @potatosalad for visibility