From 8caa869460850aa24bef3e240ceaad9ca389ff05 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 7 Aug 2024 16:24:42 +0200 Subject: [PATCH 1/2] public_key: Add verify_fun with arity 4 --- lib/public_key/src/pubkey_cert.erl | 186 ++++++++++++++++------------- lib/public_key/src/public_key.erl | 83 +++++++++---- 2 files changed, 162 insertions(+), 107 deletions(-) diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index da7ae93c9388..be47f651f904 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -32,7 +32,8 @@ validate_signature/6, verify_data/1, verify_fun/4, - prepare_for_next_cert/2]). + prepare_for_next_cert/2, + apply_fun/5]). %% Utility functions -export([normalize_general_name/1, @@ -65,13 +66,13 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec init_validation_state(#'OTPCertificate'{}, integer(), list()) -> +-spec init_validation_state(#cert{}, integer(), list()) -> #path_validation_state{}. %% %% Description: Creates initial version of path_validation_state for %% basic path validation of x509 certificates. %%-------------------------------------------------------------------- -init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen, +init_validation_state(Cert, DefaultPathLen, Options) -> PolicyTree = pubkey_policy_tree:root(), MaxLen = proplists:get_value(max_path_length, Options, DefaultPathLen), @@ -96,33 +97,34 @@ init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen, verify_fun = VerifyFun, user_state = UserState, cert_num = 0}, - prepare_for_next_cert(OtpCert, State). + prepare_for_next_cert(Cert, State). %%-------------------------------------------------------------------- --spec validate_extensions(#'OTPCertificate'{}, #path_validation_state{}, +-spec validate_extensions(#cert{}, #path_validation_state{}, term(), fun())-> {#path_validation_state{}, UserState :: term()}. %% %% Description: Check extensions included in basic path validation. %%-------------------------------------------------------------------- -validate_extensions(OtpCert, ValidationState0, UserState0, VerifyFun) -> +validate_extensions(Cert, ValidationState0, UserState0, VerifyFun) -> + OtpCert = otp_cert(Cert), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, case TBSCert#'OTPTBSCertificate'.version of N when N >= 3 -> Extensions = TBSCert#'OTPTBSCertificate'.extensions, - validate_extensions(OtpCert, Extensions, + validate_extensions(Cert, Extensions, ValidationState0, no_basic_constraint, is_self_signed(OtpCert), UserState0, VerifyFun); _ -> %% Extensions not present in versions 1 & 2 {ValidationState0, UserState0} end. %%-------------------------------------------------------------------- --spec validate_policy_tree(#'OTPCertificate'{}, #path_validation_state{})-> +-spec validate_policy_tree(#cert{}, #path_validation_state{})-> #path_validation_state{} | no_return(). %% %% Description: Check policy tree requirements after handling of certificate extensions %%-------------------------------------------------------------------- -validate_policy_tree(OtpCert, +validate_policy_tree(Cert, #path_validation_state{explicit_policy = ExplicitPolicyConstraint, valid_policy_tree = Tree, user_state = UserState0, @@ -133,9 +135,9 @@ validate_policy_tree(OtpCert, ValidationState; false -> UserState = - verify_fun(OtpCert, {bad_cert, - {policy_requirement_not_met, - {{explicit_policy, ExplicitPolicyConstraint}, + verify_fun(Cert, {bad_cert, + {policy_requirement_not_met, + {{explicit_policy, ExplicitPolicyConstraint}, {policy_set, pubkey_policy_tree:constrained_policy_node_set(Tree)}}}}, UserState0, VerifyFun), @@ -143,21 +145,22 @@ validate_policy_tree(OtpCert, end. %%-------------------------------------------------------------------- --spec validate_time(#'OTPCertificate'{}, term(), fun()) -> term(). +-spec validate_time(#cert{}, term(), fun()) -> term(). %% %% Description: Check that the certificate validity period includes the %% current time. %%-------------------------------------------------------------------- -validate_time(OtpCert, UserState, VerifyFun) -> +validate_time(Cert, UserState, VerifyFun) -> % 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. + OtpCert = otp_cert(Cert), case parse_and_check_validity_dates(OtpCert) of expired -> % Certificate has correctly formatted dates but it's expired - verify_fun(OtpCert, {bad_cert, cert_expired}, UserState, VerifyFun); + verify_fun(Cert, {bad_cert, cert_expired}, UserState, VerifyFun); error -> % Certificate has incorrectly formatted dates, attempt to delegate decision to app function - verify_fun(OtpCert, {bad_cert, invalid_validity_dates}, UserState, VerifyFun); + verify_fun(Cert, {bad_cert, invalid_validity_dates}, UserState, VerifyFun); % Validation succeded and certificate is not expired, no new state needed ok -> UserState @@ -191,26 +194,28 @@ parse_and_check_validity_dates(OtpCert) -> end. %%-------------------------------------------------------------------- --spec validate_issuer(#'OTPCertificate'{}, term(), term(), fun()) -> term() | no_return(). +-spec validate_issuer(#cert{}, term(), term(), fun()) -> term() | no_return(). %% %% Description: Check that the certificate issuer name is the working_issuer_name %% in path_validation_state. %%-------------------------------------------------------------------- -validate_issuer(OtpCert, Issuer, UserState, VerifyFun) -> +validate_issuer(Cert, Issuer, UserState, VerifyFun) -> + OtpCert = otp_cert(Cert), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, case is_issuer(Issuer, TBSCert#'OTPTBSCertificate'.issuer) of true -> UserState; _ -> - verify_fun(OtpCert, {bad_cert, invalid_issuer}, UserState, VerifyFun) + verify_fun(Cert, {bad_cert, invalid_issuer}, UserState, VerifyFun) end. %%-------------------------------------------------------------------- --spec validate_names(#'OTPCertificate'{}, no_constraints | list(), list(), +-spec validate_names(#cert{}, no_constraints | list(), list(), term(), term(), fun())-> term() | no_return(). %% %% Description: Validate Subject Alternative Name. %%-------------------------------------------------------------------- -validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) -> +validate_names(Cert, Permit, Exclude, Last, UserState, VerifyFun) -> + OtpCert = otp_cert(Cert), case is_self_signed(OtpCert) andalso (not Last) of true -> UserState; @@ -239,13 +244,13 @@ validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) -> true -> UserState; false -> - verify_fun(OtpCert, {bad_cert, name_not_permitted}, + verify_fun(Cert, {bad_cert, name_not_permitted}, UserState, VerifyFun) end end. %%-------------------------------------------------------------------- --spec validate_signature(#'OTPCertificate'{}, DER::binary(), +-spec validate_signature(#cert{}, DER::binary(), term(),term(), term(), fun()) -> term() | no_return(). %% @@ -253,14 +258,14 @@ validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) -> %% working_public_key_algorithm, the working_public_key, and %% the working_public_key_parameters in path_validation_state. %%-------------------------------------------------------------------- -validate_signature(OtpCert, DerCert, Key, KeyParams, +validate_signature(Cert, DerCert, Key, KeyParams, UserState, VerifyFun) -> - + OtpCert = otp_cert(Cert), case verify_signature(OtpCert, DerCert, Key, KeyParams) of true -> UserState; false -> - verify_fun(OtpCert, {bad_cert, invalid_signature}, UserState, VerifyFun) + verify_fun(Cert, {bad_cert, invalid_signature}, UserState, VerifyFun) end. %%-------------------------------------------------------------------- @@ -277,7 +282,7 @@ verify_data(DerCert) -> extract_verify_data(OtpCert, DerCert). %%-------------------------------------------------------------------- --spec verify_fun(#'OTPCertificate'{}, {bad_cert, public_key:bad_cert_reason()} | +-spec verify_fun(#cert{}, {bad_cert, public_key:bad_cert_reason()} | {extension, #'Extension'{}}| valid | valid_peer, term(), fun()) -> term() | no_return(). %% @@ -285,9 +290,9 @@ verify_data(DerCert) -> %% validation errors and unknown extensions and optional do other %% things with a validated certificate. %% -------------------------------------------------------------------- -verify_fun(Otpcert, Result, UserState0, VerifyFun) -> - case VerifyFun(Otpcert, Result, UserState0) of - {valid, UserState} -> +verify_fun(#cert{der = DerCert, otp = OtpCert}, Result, UserState0, VerifyFun) -> + case apply_fun(VerifyFun, DerCert, OtpCert, Result, UserState0) of + {valid, UserState} -> UserState; {valid_peer, UserState} -> UserState; @@ -308,21 +313,21 @@ verify_fun(Otpcert, Result, UserState0, VerifyFun) -> end. %%-------------------------------------------------------------------- --spec prepare_for_next_cert(#'OTPCertificate'{}, #path_validation_state{}) -> +-spec prepare_for_next_cert(#cert{}, #path_validation_state{}) -> #path_validation_state{} | no_return(). %% %% Description: Update path_validation_state for next iteration. %%-------------------------------------------------------------------- -prepare_for_next_cert(OtpCert, #path_validation_state{policy_mapping_ext = Ext} = +prepare_for_next_cert(Cert, #path_validation_state{policy_mapping_ext = Ext} = ValidationState0) when Ext =/= undefined -> - ValidationState1 = handle_policy_mappings(OtpCert, ValidationState0), + ValidationState1 = handle_policy_mappings(Cert, ValidationState0), ValidationState = ValidationState1#path_validation_state{policy_mapping_ext = undefined, current_any_policy_qualifiers = undefined}, - prepare_for_next_cert(OtpCert, ValidationState); -prepare_for_next_cert(OtpCert, #path_validation_state{ + prepare_for_next_cert(Cert, ValidationState); +prepare_for_next_cert(Cert, #path_validation_state{ working_public_key_algorithm = PrevAlgo, working_public_key_parameters = PrevParams, @@ -331,6 +336,7 @@ prepare_for_next_cert(OtpCert, #path_validation_state{ inhibit_policy_mapping = PolicyMappingConstraint, inhibit_any_policy = AnyPolicyConstraint } = ValidationState0) -> + OtpCert = otp_cert(Cert), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, Issuer = TBSCert#'OTPTBSCertificate'.subject, @@ -372,7 +378,15 @@ prepare_for_next_cert(OtpCert, #path_validation_state{ }, ValidationState2 = handle_policy_constraints(ValidationState1), ValidationState = handle_inhibit_anypolicy(ValidationState2), - handle_last_cert(OtpCert, ValidationState). + handle_last_cert(Cert, ValidationState). + +apply_fun(Fun, DerCert, OtpCert, Result, UserState) -> + if is_function(Fun, 4) -> + Fun(OtpCert, DerCert, Result, UserState); + is_function(Fun, 3) -> + Fun(OtpCert, Result, UserState) + end. + %%==================================================================== %% Utility functions @@ -682,15 +696,15 @@ root_cert(Name, Opts) -> %%-------------------------------------------------------------------- %% No extensions present -validate_extensions(OtpCert, asn1_NOVALUE, ValidationState, ExistBasicCon, +validate_extensions(Cert, asn1_NOVALUE, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> - validate_extensions(OtpCert, [], ValidationState, ExistBasicCon, + validate_extensions(Cert, [], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); validate_extensions(_,[], ValidationState, basic_constraint, _SelfSigned, UserState, _) -> {ValidationState, UserState}; -validate_extensions(OtpCert, [], ValidationState = +validate_extensions(Cert, [], ValidationState = #path_validation_state{max_path_length = Len, last_cert = Last}, no_basic_constraint, SelfSigned, UserState0, VerifyFun) -> @@ -703,9 +717,9 @@ validate_extensions(OtpCert, [], ValidationState = false -> %% basic_constraint must appear in certs used for digital sign %% see 4.2.1.10 in rfc 3280 - case is_digitally_sign_cert(OtpCert) of + case is_digitally_sign_cert(Cert) of true -> - missing_basic_constraints(OtpCert, SelfSigned, + missing_basic_constraints(Cert, SelfSigned, ValidationState, VerifyFun, UserState0, Len); false -> %% Example CRL signer only @@ -713,7 +727,7 @@ validate_extensions(OtpCert, [], ValidationState = end end; -validate_extensions(OtpCert, +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = #'BasicConstraints'{cA = true, @@ -725,22 +739,22 @@ validate_extensions(OtpCert, Length = if SelfSigned -> erlang:min(N, Len); true -> erlang:min(N, Len-1) end, - validate_extensions(OtpCert, Rest, + validate_extensions(Cert, Rest, ValidationState#path_validation_state{max_path_length = Length}, basic_constraint, SelfSigned, UserState, VerifyFun); %% The pathLenConstraint field is meaningful only if cA is set to %% TRUE. -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-basicConstraints', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-basicConstraints', extnValue = #'BasicConstraints'{cA = false}} | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-keyUsage', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-keyUsage', extnValue = KeyUse } | Rest], #path_validation_state{last_cert=Last} = ValidationState, @@ -748,32 +762,32 @@ validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-keyUsage', UserState0, VerifyFun) -> case Last orelse is_valid_key_usage(KeyUse, keyCertSign) of true -> - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState0, VerifyFun); false -> - UserState = verify_fun(OtpCert, {bad_cert, invalid_key_usage}, + UserState = verify_fun(Cert, {bad_cert, invalid_key_usage}, UserState0, VerifyFun), - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) end; -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-subjectAltName', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = Names, critical = true} = Ext | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState0, VerifyFun) -> case validate_subject_alt_names(Names) of true -> - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState0, VerifyFun); false -> - UserState = verify_fun(OtpCert, {extension, Ext}, + UserState = verify_fun(Cert, {extension, Ext}, UserState0, VerifyFun), - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) end; -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-nameConstraints', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-nameConstraints', extnValue = NameConst} | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> @@ -783,40 +797,40 @@ validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-nameConstraints', NewValidationState = add_name_constraints(Permitted, Excluded, ValidationState), - validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, NewValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-certificatePolicies', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-certificatePolicies', extnValue = Info} | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> Tree = process_policy_tree(Info, SelfSigned, ValidationState), - validate_extensions(OtpCert, Rest, + validate_extensions(Cert, Rest, ValidationState#path_validation_state{ policy_ext_present = true, current_any_policy_qualifiers = current_any_policy_qualifiers(Info), valid_policy_tree = Tree}, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints'} = Ext +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-policyConstraints'} = Ext | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> NewValidationState = ValidationState#path_validation_state{policy_constraint_ext = Ext}, - validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, NewValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyMappings'} = Ext +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-policyMappings'} = Ext | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> NewValidationState = ValidationState#path_validation_state{policy_mapping_ext = Ext}, - validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, NewValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-inhibitAnyPolicy'} = Ext +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-inhibitAnyPolicy'} = Ext | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun) -> NewValidationState = ValidationState#path_validation_state{policy_inhibitany_ext = Ext}, - validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon, + validate_extensions(Cert, Rest, NewValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-extKeyUsage', +validate_extensions(Cert, [#'Extension'{extnID = ?'id-ce-extKeyUsage', critical = true, extnValue = KeyUse} = Extension | Rest], #path_validation_state{last_cert = false} = ValidationState, ExistBasicCon, @@ -824,22 +838,23 @@ validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-extKeyUsage', UserState = case ext_keyusage_includes_any(KeyUse) of true -> %% CA cert that specifies ?anyExtendedKeyUsage should not be marked critical - verify_fun(OtpCert, {bad_cert, invalid_ext_key_usage}, UserState0, VerifyFun); + verify_fun(Cert, {bad_cert, invalid_ext_key_usage}, UserState0, VerifyFun); false -> - verify_fun(OtpCert, {extension, Extension}, UserState0, VerifyFun) + verify_fun(Cert, {extension, Extension}, UserState0, VerifyFun) end, - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned, + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun); -validate_extensions(OtpCert, [#'Extension'{} = Extension | Rest], +validate_extensions(Cert, [#'Extension'{} = Extension | Rest], ValidationState, ExistBasicCon, SelfSigned, UserState0, VerifyFun) -> - UserState = verify_fun(OtpCert, {extension, Extension}, UserState0, VerifyFun), - validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned, + UserState = verify_fun(Cert, {extension, Extension}, UserState0, VerifyFun), + validate_extensions(Cert, Rest, ValidationState, ExistBasicCon, SelfSigned, UserState, VerifyFun). -handle_last_cert(OtpCert, #path_validation_state{last_cert = true, +handle_last_cert(Cert, #path_validation_state{last_cert = true, user_initial_policy_set = PolicySet, valid_policy_tree = Tree} = ValidationState0) -> + OtpCert = otp_cert(Cert), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), @@ -854,7 +869,7 @@ handle_last_cert(OtpCert, #path_validation_state{last_cert = true, ValidationState0 end, ValidTree = policy_tree_intersection(PolicySet, Tree), - validate_policy_tree(OtpCert, + validate_policy_tree(Cert, ValidationState#path_validation_state{valid_policy_tree = ValidTree}); handle_last_cert(_, ValidationState) -> ValidationState. @@ -1035,13 +1050,13 @@ any_ext_policy_children(#{expected_policy_set := ExpPolicySet}, Qualifiers, AllL %% Start Prepare Next Cert Policy Handling RFC 5280 Section 6.1.4 ------------- %% 6.1.4. b start: -handle_policy_mappings(OtpCert, +handle_policy_mappings(Cert, #path_validation_state{valid_policy_tree = Tree0, policy_mapping_ext = #'Extension'{extnID = ?'id-ce-policyMappings', extnValue = PolicyMappings}} = ValidationState) -> - case handle_policy_mappings(PolicyMappings, OtpCert, Tree0, ValidationState) of + case handle_policy_mappings(PolicyMappings, Cert, Tree0, ValidationState) of {tree, Tree} -> ValidationState#path_validation_state{valid_policy_tree = Tree}; {user_state, UState} -> @@ -1050,10 +1065,10 @@ handle_policy_mappings(OtpCert, handle_policy_mappings([], _, Tree, _) -> {tree, Tree}; -handle_policy_mappings([Mappings | Rest], OtpCert, Tree0, ValidationState) -> - case handle_policy_mapping(Mappings, OtpCert, Tree0, ValidationState) of +handle_policy_mappings([Mappings | Rest], Cert, Tree0, ValidationState) -> + case handle_policy_mapping(Mappings, Cert, Tree0, ValidationState) of {tree, Tree} -> - handle_policy_mappings(Rest, OtpCert, Tree, ValidationState); + handle_policy_mappings(Rest, Cert, Tree, ValidationState); Other -> Other end. @@ -1066,7 +1081,7 @@ handle_policy_mapping(#'PolicyMappings_SEQOF'{ IssuerPolicy, subjectDomainPolicy = SubjectPolicy} = Ext, - OtpCert, Tree0, + Cert, Tree0, #path_validation_state{inhibit_policy_mapping = PolicyMappingConstraint, current_any_policy_qualifiers = @@ -1081,7 +1096,7 @@ handle_policy_mapping(#'PolicyMappings_SEQOF'{ PolicyMappingConstraint, AnyQualifiers), {tree, Tree}; false -> - UserState = verify_fun(OtpCert, {bad_cert, {invalid_policy_mapping, Ext}}, + UserState = verify_fun(Cert, {bad_cert, {invalid_policy_mapping, Ext}}, UserState, VerifyFun), {user_state, UserState} end. @@ -1757,7 +1772,8 @@ is_dh(?'dhpublicnumber')-> is_dh(_) -> false. -is_digitally_sign_cert(OtpCert) -> +is_digitally_sign_cert(Cert) -> + OtpCert = otp_cert(Cert), TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions), case pubkey_cert:select_extension(?'id-ce-keyUsage', Extensions) of @@ -1767,8 +1783,8 @@ is_digitally_sign_cert(OtpCert) -> lists:member(keyCertSign, KeyUse) end. -missing_basic_constraints(OtpCert, SelfSigned, ValidationState, VerifyFun, UserState0,Len) -> - UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint}, +missing_basic_constraints(Cert, SelfSigned, ValidationState, VerifyFun, UserState0,Len) -> + UserState = verify_fun(Cert, {bad_cert, missing_basic_constraint}, UserState0, VerifyFun), case SelfSigned of true -> @@ -2081,3 +2097,9 @@ ext_keyusage_includes_any(KeyUse) when is_list(KeyUse) -> ext_keyusage_includes_any(_) -> false. +otp_cert(Der) when is_binary(Der) -> + public_key:pkix_decode_cert(Der, otp); +otp_cert(#'OTPCertificate'{} = Cert) -> + Cert; +otp_cert(#cert{otp = OtpCert}) -> + OtpCert. diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 0ac347dbe5ca..8e216aa1ff85 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -1571,20 +1571,37 @@ is intended as information to end users. Available options: -- **\{verify_fun, \{fun(), InitialUserState::term()\}** - The fun must be +- **\{verify_fun, \{fun(), UserState::term()\}** - The fun must be defined as: ```erlang fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | {extension, #'Extension'{}}, - InitialUserState :: term()) -> + UserState :: term()) -> {valid, UserState :: term()} | {valid_peer, UserState :: term()} | {fail, Reason :: term()} | {unknown, UserState :: term()}. ``` + or as: + + ```erlang + fun(OtpCert :: #'OTPCertificate'{}, + DerCert :: der_encoded(), + Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | + {extension, #'Extension'{}}, + UserState :: term()) -> + {valid, UserState :: term()} | + {valid_peer, UserState :: term()} | + {fail, Reason :: term()} | + {unknown, UserState :: term()}. + ``` + + The verify callback can have 3 or 4 arguments in case the DER encoded + version is needed by the callback. + If the verify callback fun returns `{fail, Reason}`, the verification process is immediately stopped. If the verify callback fun returns `{valid, UserState}`, the verification process is continued. This can be used @@ -1593,6 +1610,15 @@ Available options: unknown to the user application, the return value `{unknown, UserState}` is to be used. + > #### Note {: .note } + > If you need the DER encoded version of the certificate and have + > the OTP decoded version encoding it back can fail to give the correct result, + > due to work arounds for common misbehaving encoders. So it is recommended + > to call `pkix_path_validation` with `Cert` and `CertChain` arguments as + > `der_encoded() | #cert{}` and `[der_encoded() | #cert{}]`. Also note + > that the path validation itself needs both the encoded and the + > decoded version of the certificate. + > #### Warning {: .warning } > > Note that user defined custom `verify_fun` may alter original path @@ -1650,20 +1676,16 @@ Explanations of reasons for a bad certificate: {ok, {PublicKeyInfo, ConstrainedPolicyNodes}} | {error, {bad_cert, Reason :: bad_cert_reason()}} when - Cert :: cert() | atom(), + Cert :: cert() | combined_cert() | atom(), CertChain :: [cert() | combined_cert()], Options :: [{max_path_length, integer()} | {verify_fun, {fun(), term()}}], PublicKeyInfo :: public_key_info(), ConstrainedPolicyNodes :: [policy_node()]. %%-------------------------------------------------------------------- -pkix_path_validation(TrustedCert, CertChain, Options) - when is_binary(TrustedCert) -> - - OtpCert = pkix_decode_cert(TrustedCert, otp), - pkix_path_validation(OtpCert, CertChain, Options); -pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) - when is_list(CertChain), is_list(Options) -> +pkix_path_validation(Cert, CertChain, Options) + when (not is_atom(Cert)), is_list(CertChain), is_list(Options) -> + TrustedCert = combined_cert(Cert), MaxPathDefault = length(CertChain), {VerifyFun, UserState0} = proplists:get_value(verify_fun, Options, ?DEFAULT_VERIFYFUN), @@ -1683,15 +1705,15 @@ pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) throw:{bad_cert, _} = Result -> {error, Result} end; -pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)-> +pkix_path_validation(PathErr, [Cert0 | Chain], Options0) when is_atom(PathErr)-> {VerifyFun, Userstat0} = proplists:get_value(verify_fun, Options0, ?DEFAULT_VERIFYFUN), - Otpcert = otp_cert(Cert), + Cert = combined_cert(Cert0), Reason = {bad_cert, PathErr}, - try VerifyFun(Otpcert, Reason, Userstat0) of + try pubkey_cert:apply_fun(VerifyFun, Cert#cert.otp, Cert#cert.der, Reason, Userstat0) of {valid, Userstate} -> Options = proplists:delete(verify_fun, Options0), - pkix_path_validation(Otpcert, Chain, [{verify_fun, + pkix_path_validation(Cert, Chain, [{verify_fun, {VerifyFun, Userstate}}| Options]); {fail, UserReason} -> {error, UserReason} @@ -2395,8 +2417,9 @@ path_validation([Cert | _] = Path, ValidationState) -> Reason = {bad_cert, max_path_length_reached}, OtpCert = otp_cert(Cert), + DerCert = der_cert(Cert), - try VerifyFun(OtpCert, Reason, UserState0) of + try pubkey_cert:apply_fun(VerifyFun, OtpCert, DerCert, Reason, UserState0) of {valid, UserState} -> path_validation(Path, ValidationState#path_validation_state{ @@ -2409,7 +2432,7 @@ path_validation([Cert | _] = Path, {error, Reason} end. -validate(Cert, #path_validation_state{working_issuer_name = Issuer, +validate(Cert0, #path_validation_state{working_issuer_name = Issuer, working_public_key = Key, working_public_key_parameters = KeyParams, @@ -2420,37 +2443,37 @@ validate(Cert, #path_validation_state{working_issuer_name = Issuer, verify_fun = VerifyFun} = ValidationState0) -> - OtpCert = otp_cert(Cert), + Cert = combined_cert(Cert0), {ValidationState1, UserState1} = - pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState0, + pubkey_cert:validate_extensions(Cert, ValidationState0, UserState0, VerifyFun), %% We want the key_usage extension to be checked before we validate %% other things so that CRL validation errors will comply to standard %% test suite description - UserState2 = pubkey_cert:validate_time(OtpCert, UserState1, VerifyFun), + UserState2 = pubkey_cert:validate_time(Cert, UserState1, VerifyFun), - UserState3 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState2, VerifyFun), + UserState3 = pubkey_cert:validate_issuer(Cert, Issuer, UserState2, VerifyFun), - UserState4 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last, + UserState4 = pubkey_cert:validate_names(Cert, Permit, Exclude, Last, UserState3, VerifyFun), - UserState5 = pubkey_cert:validate_signature(OtpCert, der_cert(Cert), + UserState5 = pubkey_cert:validate_signature(Cert, der_cert(Cert), Key, KeyParams, UserState4, VerifyFun), UserState = case Last of false -> - pubkey_cert:verify_fun(OtpCert, valid, UserState5, VerifyFun); + pubkey_cert:verify_fun(Cert, valid, UserState5, VerifyFun); true -> - pubkey_cert:verify_fun(OtpCert, valid_peer, + pubkey_cert:verify_fun(Cert, valid_peer, UserState5, VerifyFun) end, ValidationState = ValidationState1#path_validation_state{user_state = UserState}, - pubkey_cert:prepare_for_next_cert(OtpCert, ValidationState). + pubkey_cert:prepare_for_next_cert(Cert, ValidationState). otp_cert(Der) when is_binary(Der) -> pkix_decode_cert(Der, otp); @@ -2462,6 +2485,16 @@ otp_cert(#cert{otp = OtpCert}) -> combined_cert(#'Certificate'{} = Cert) -> Der = der_encode('Certificate', Cert), Otp = pkix_decode_cert(Der, otp), + #cert{der = Der, otp = Otp}; +combined_cert(#cert{} = CombinedCert) -> + CombinedCert; +combined_cert(Der) when is_binary(Der) -> + Otp = pkix_decode_cert(Der, otp), + #cert{der = Der, otp = Otp}; +combined_cert(#'OTPCertificate'{} = Otp) -> + %% Note that this conversion is not + %% safe due to encodeing work arounds. + Der = der_cert(Otp), #cert{der = Der, otp = Otp}. der_cert(#'OTPCertificate'{} = Cert) -> From ca2f6cf5a25df2d80887ea477f53a8359ce90840 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 16 Aug 2024 11:00:02 +0200 Subject: [PATCH 2/2] ssl: Use public_key:verify_fun/4 --- lib/ssl/src/ssl.app.src | 2 +- lib/ssl/src/ssl.erl | 18 +++++++++--------- lib/ssl/src/ssl_handshake.erl | 35 +++++++++++++++++------------------ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 7515edf756e3..d1a6565fd0dd 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -92,6 +92,6 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-6.0","public_key-1.15","kernel-9.0", + {runtime_dependencies, ["stdlib-6.0","public_key-@OTP-19169@","kernel-9.0", "erts-15.0","crypto-5.0", "inets-5.10.7", "runtime_tools-1.15.1"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index b0fd043a444f..da49a69963f5 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -800,13 +800,13 @@ Common certificate related options to both client and server. Default option `verify_fun` in `verify_peer mode`: ```erlang - {fun(_,{bad_cert, _} = Reason, _) -> + {fun(_, _, {bad_cert, _} = Reason, _) -> {fail, Reason}; - (_,{extension, _}, UserState) -> + (_, _, {extension, _}, UserState) -> {unknown, UserState}; - (_, valid, UserState) -> + (_, _, valid, UserState) -> {valid, UserState}; - (_, valid_peer, UserState) -> + (_, _, valid_peer, UserState) -> {valid, UserState} end, []} ``` @@ -814,15 +814,15 @@ Common certificate related options to both client and server. Default option `verify_fun` in mode `verify_none`: ```erlang - {fun(_,{bad_cert, _}, UserState) -> + {fun(_, _, {bad_cert, _}, UserState) -> {valid, UserState}; - (_,{extension, #'Extension'{critical = true}}, UserState) -> + (_, _, {extension, #'Extension'{critical = true}}, UserState) -> {valid, UserState}; - (_,{extension, _}, UserState) -> + (_, _, {extension, _}, UserState) -> {unknown, UserState}; - (_, valid, UserState) -> + (_, _, valid, UserState) -> {valid, UserState}; - (_, valid_peer, UserState) -> + (_, _, valid_peer, UserState) -> {valid, UserState} end, []} ``` diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index eb9ab86ecee0..1cf61001b106 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -2070,7 +2070,7 @@ path_validate(TrustedAndPath, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHa Version, SslOptions, ExtInfo, InitialInvalidated, InitialPotentialError). validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) -> - {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> + {fun(OtpCert, DerCert, {extension, _} = Extension, {SslState, UserState}) -> case ssl_certificate:validate(OtpCert, Extension, SslState, @@ -2078,32 +2078,32 @@ validation_fun_and_state({Fun, UserState0}, VerifyState, CertPath, LogLevel) -> {valid, NewSslState} -> {valid, {NewSslState, UserState}}; {fail, Reason} -> - apply_user_fun(Fun, OtpCert, Reason, UserState, + apply_user_fun(Fun, OtpCert, DerCert, Reason, UserState, SslState, CertPath, LogLevel); {unknown, _} -> - apply_user_fun(Fun, OtpCert, - Extension, UserState, SslState, CertPath, - LogLevel) + apply_user_fun(Fun, OtpCert, DerCert, + Extension, UserState, SslState, + CertPath, LogLevel) end; - (OtpCert, VerifyResult, {SslState, UserState}) -> - apply_user_fun(Fun, OtpCert, VerifyResult, UserState, + (OtpCert, DerCert, VerifyResult, {SslState, UserState}) -> + apply_user_fun(Fun, OtpCert, DerCert, VerifyResult, UserState, SslState, CertPath, LogLevel) end, {VerifyState, UserState0}}; validation_fun_and_state(undefined, VerifyState, CertPath, LogLevel) -> - {fun(OtpCert, {extension, _} = Extension, SslState) -> + {fun(OtpCert, _DerCert, {extension, _} = Extension, SslState) -> ssl_certificate:validate(OtpCert, Extension, SslState, LogLevel); - (OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or - (VerifyResult == valid_peer) -> + (OtpCert, _DerCert, VerifyResult, SslState) when (VerifyResult == valid) or + (VerifyResult == valid_peer) -> case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> ssl_certificate:validate(OtpCert, VerifyResult, SslState, LogLevel); Reason -> {fail, Reason} end; - (OtpCert, VerifyResult, SslState) -> + (OtpCert, _DerCert, VerifyResult, SslState) -> ssl_certificate:validate(OtpCert, VerifyResult, SslState, LogLevel) @@ -2114,22 +2114,22 @@ path_validation_options(Opts, ValidationFunAndState) -> [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)}, {verify_fun, ValidationFunAndState} | PolicyOpts]. -apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when +apply_user_fun(Fun, OtpCert, DerCert, VerifyResult0, UserState0, SslState, CertPath, LogLevel) when (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) -> VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState, LogLevel), - case apply_fun(Fun, OtpCert, VerifyResult, UserState0, CertPath) of + case apply_fun(Fun, OtpCert, DerCert, VerifyResult, UserState0) of {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer) -> case cert_status_check(OtpCert, SslState, VerifyResult, CertPath, LogLevel) of valid -> {Valid, {SslState, UserState}}; Result -> - apply_user_fun(Fun, OtpCert, Result, UserState, SslState, CertPath, LogLevel) + apply_user_fun(Fun, OtpCert, DerCert, Result, UserState, SslState, CertPath, LogLevel) end; {fail, _} = Fail -> Fail end; -apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, CertPath, _LogLevel) -> - case apply_fun(Fun, OtpCert, ExtensionOrError, UserState0, CertPath) of +apply_user_fun(Fun, OtpCert, DerCert, ExtensionOrError, UserState0, SslState, _, _) -> + case apply_fun(Fun, OtpCert, DerCert, ExtensionOrError, UserState0) of {Valid, UserState} when (Valid == valid) orelse (Valid == valid_peer)-> {Valid, {SslState, UserState}}; {fail, _} = Fail -> @@ -2138,9 +2138,8 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, CertPath, _ {unknown, {SslState, UserState}} end. -apply_fun(Fun, OtpCert, ExtensionOrError, UserState, CertPath) -> +apply_fun(Fun, OtpCert, DerCert, ExtensionOrError, UserState) -> if is_function(Fun, 4) -> - #cert{der=DerCert} = lists:keyfind(OtpCert, #cert.otp, CertPath), Fun(OtpCert, DerCert, ExtensionOrError, UserState); is_function(Fun, 3) -> Fun(OtpCert, ExtensionOrError, UserState)