aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2010-09-03 15:04:49 +0200
committerIngela Anderton Andin <[email protected]>2010-09-06 14:52:50 +0200
commit71e720e56888faa6da3856d5c5e08c8e983c9d5d (patch)
tree5d04ed4f0ac8ce5ecf6855f2af9f7523d6adacc3
parente752dcdefda84291cf819036a600b983326dae25 (diff)
downloadotp-71e720e56888faa6da3856d5c5e08c8e983c9d5d.tar.gz
otp-71e720e56888faa6da3856d5c5e08c8e983c9d5d.tar.bz2
otp-71e720e56888faa6da3856d5c5e08c8e983c9d5d.zip
Handling of path validation errors by the application
Changed the behavior of the verify_fun option so that the application can be responsible for handling path validation errors even on the server side. Also replaced the not yet documented validate_extensions_fun to be handled by the verify_fun instead. If the verify callback fun returns {fail, Reason}, the verification process is immediately stopped and an alert is sent to the peer and the TLS/SSL handshake is terminated. If the verify callback fun returns {valid, UserState}, the verification process is continued. If the verify callback fun always returns {valid, UserState}, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established. The verify callback fun will also be able to verify application specific extensions.
-rw-r--r--lib/public_key/include/public_key.hrl11
-rw-r--r--lib/public_key/src/pubkey_cert.erl349
-rw-r--r--lib/public_key/src/public_key.erl127
-rw-r--r--lib/public_key/test/public_key_SUITE.erl35
-rw-r--r--lib/ssl/src/ssl.erl76
-rw-r--r--lib/ssl/src/ssl_certificate.erl39
-rw-r--r--lib/ssl/src/ssl_connection.erl3
-rw-r--r--lib/ssl/src/ssl_handshake.erl131
-rw-r--r--lib/ssl/src/ssl_session.erl4
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl150
10 files changed, 489 insertions, 436 deletions
diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl
index 6503321042..82681502ab 100644
--- a/lib/public_key/include/public_key.hrl
+++ b/lib/public_key/include/public_key.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -28,6 +28,13 @@
algorithm,
parameters = asn1_NOVALUE}).
+-define(DEFAULT_VERIFYFUN,
+ {fun(_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []}).
+
-record(path_validation_state, {
valid_policy_tree,
explicit_policy,
@@ -42,7 +49,7 @@
working_public_key_parameters,
working_issuer_name,
max_path_length,
- acc_errors, %% If verify_none option is set
+ verify_fun,
user_state
}).
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index 64fc8ab5bc..b3c230df25 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -27,7 +27,6 @@
validate_time/3, validate_signature/6,
validate_issuer/4, validate_names/6,
validate_revoked_status/3, validate_extensions/4,
- validate_unknown_extensions/3,
normalize_general_name/1, digest_type/1, is_self_signed/1,
is_issuer/2, issuer_id/2, is_fixed_dh_cert/1,
verify_data/1]).
@@ -68,13 +67,14 @@ init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen,
Options, false)),
PolicyMapping = policy_indicator(MaxLen,
proplists:get_value(policy_mapping, Options, false)),
- AccErrors = proplists:get_value(acc_errors, Options, []),
- State = #path_validation_state{max_path_length = MaxLen,
- valid_policy_tree = PolicyTree,
- explicit_policy = ExplicitPolicy,
- inhibit_any_policy = InhibitAnyPolicy,
- policy_mapping = PolicyMapping,
- acc_errors = AccErrors,
+ {VerifyFun, UserState} = proplists:get_value(verify_fun, Options, ?DEFAULT_VERIFYFUN),
+ State = #path_validation_state{max_path_length = MaxLen,
+ valid_policy_tree = PolicyTree,
+ explicit_policy = ExplicitPolicy,
+ inhibit_any_policy = InhibitAnyPolicy,
+ policy_mapping = PolicyMapping,
+ verify_fun = VerifyFun,
+ user_state = UserState,
cert_num = 0},
prepare_for_next_cert(OtpCert, State).
@@ -112,12 +112,12 @@ prepare_for_next_cert(OtpCert, ValidationState = #path_validation_state{
}.
%%--------------------------------------------------------------------
--spec validate_time(#'OTPCertificate'{}, list(), boolean()) -> list().
+-spec validate_time(#'OTPCertificate'{}, term(), fun()) -> term().
%%
%% Description: Check that the certificate validity period includes the
%% current time.
%%--------------------------------------------------------------------
-validate_time(OtpCert, AccErr, Verify) ->
+validate_time(OtpCert, UserState, VerifyFun) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
{'Validity', NotBeforeStr, NotAfterStr}
= TBSCert#'OTPTBSCertificate'.validity,
@@ -127,27 +127,27 @@ validate_time(OtpCert, AccErr, Verify) ->
case ((NotBefore =< Now) and (Now =< NotAfter)) of
true ->
- AccErr;
+ UserState;
false ->
- not_valid({bad_cert, cert_expired}, Verify, AccErr)
+ verify_fun(OtpCert, {bad_cert, cert_expired}, UserState, VerifyFun)
end.
%%--------------------------------------------------------------------
--spec validate_issuer(#'OTPCertificate'{}, term(), list(), boolean()) -> list().
+-spec validate_issuer(#'OTPCertificate'{}, term(), term(), fun()) -> term().
%%
%% Description: Check that the certificate issuer name is the working_issuer_name
%% in path_validation_state.
%%--------------------------------------------------------------------
-validate_issuer(OtpCert, Issuer, AccErr, Verify) ->
+validate_issuer(OtpCert, Issuer, UserState, VerifyFun) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
case is_issuer(Issuer, TBSCert#'OTPTBSCertificate'.issuer) of
true ->
- AccErr;
+ UserState;
_ ->
- not_valid({bad_cert, invalid_issuer}, Verify, AccErr)
+ verify_fun(OtpCert, {bad_cert, invalid_issuer}, UserState, VerifyFun)
end.
%%--------------------------------------------------------------------
-spec validate_signature(#'OTPCertificate'{}, der_encoded(),
- term(),term(), list(), boolean()) -> list().
+ term(),term(), term(), fun()) -> term().
%%
%% Description: Check that the signature on the certificate can be verified using
@@ -155,24 +155,24 @@ validate_issuer(OtpCert, Issuer, AccErr, Verify) ->
%% the working_public_key_parameters in path_validation_state.
%%--------------------------------------------------------------------
validate_signature(OtpCert, DerCert, Key, KeyParams,
- AccErr, Verify) ->
+ UserState, VerifyFun) ->
case verify_signature(OtpCert, DerCert, Key, KeyParams) of
true ->
- AccErr;
+ UserState;
false ->
- not_valid({bad_cert, invalid_signature}, Verify, AccErr)
+ verify_fun(OtpCert, {bad_cert, invalid_signature}, UserState, VerifyFun)
end.
%%--------------------------------------------------------------------
-spec validate_names(#'OTPCertificate'{}, list(), list(),
- term(), list(), boolean())-> list().
+ term(), term(), fun())-> term().
%%
%% Description: Validate Subject Alternative Name.
%%--------------------------------------------------------------------
-validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) ->
+validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) ->
case is_self_signed(OtpCert) andalso (not Last) of
true ->
- AccErr;
+ UserState;
false ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
Subject = TBSCert#'OTPTBSCertificate'.subject,
@@ -196,51 +196,36 @@ validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) ->
(not is_excluded(Name, Exclude)) andalso
(not is_excluded(AltNames, Exclude))) of
true ->
- AccErr;
+ UserState;
false ->
- not_valid({bad_cert, name_not_permitted},
- Verify, AccErr)
+ verify_fun(OtpCert, {bad_cert, name_not_permitted},
+ UserState, VerifyFun)
end
end.
%%--------------------------------------------------------------------
--spec validate_revoked_status(#'OTPCertificate'{}, boolean(), list()) ->
- list().
+-spec validate_revoked_status(#'OTPCertificate'{}, term(), fun()) ->
+ term().
%%
%% Description: Check if certificate has been revoked.
%%--------------------------------------------------------------------
-validate_revoked_status(_OtpCert, _Verify, AccErr) ->
+validate_revoked_status(_OtpCert, UserState, _VerifyFun) ->
%% TODO: Implement or leave for application?!
- %% true |
+ %% valid |
%% throw({bad_cert, cert_revoked})
- AccErr.
+ UserState.
%%--------------------------------------------------------------------
-spec validate_extensions(#'OTPCertificate'{}, #path_validation_state{},
- boolean(), list())->
- {#path_validation_state{},
- UnknownExtensions :: list(), AccErrors :: list()}.
+ term(), fun())->
+ {#path_validation_state{}, UserState :: term()}.
%%
%% Description: Check extensions included in basic path validation.
%%--------------------------------------------------------------------
-validate_extensions(OtpCert, ValidationState, Verify, AccErr) ->
+validate_extensions(OtpCert, ValidationState, UserState, VerifyFun) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
Extensions = TBSCert#'OTPTBSCertificate'.extensions,
- validate_extensions(Extensions, ValidationState, no_basic_constraint,
- is_self_signed(OtpCert), [], Verify, AccErr).
-
-%--------------------------------------------------------------------
- -spec validate_unknown_extensions(list(), list(), boolean())-> list().
-%%
-%% Description: Check that all critical extensions has been handled.
-%%--------------------------------------------------------------------
-validate_unknown_extensions([], AccErr, _Verify) ->
- AccErr;
-validate_unknown_extensions([#'Extension'{critical = true} | _],
- AccErr, Verify) ->
- not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr);
-validate_unknown_extensions([#'Extension'{critical = false} | Rest],
- AccErr, Verify) ->
- validate_unknown_extensions(Rest, AccErr, Verify).
+ validate_extensions(OtpCert, Extensions, ValidationState, no_basic_constraint,
+ is_self_signed(OtpCert), UserState, VerifyFun).
%%--------------------------------------------------------------------
-spec normalize_general_name({rdnSequence, term()}) -> {rdnSequence, term()}.
@@ -330,10 +315,25 @@ extensions_list(asn1_NOVALUE) ->
extensions_list(Extensions) ->
Extensions.
-not_valid(Error, true, _) ->
- throw(Error);
-not_valid(Error, false, AccErrors) ->
- [Error | AccErrors].
+verify_fun(Otpcert, Result, UserState0, VerifyFun) ->
+ case VerifyFun(Otpcert, Result, UserState0) of
+ {valid,UserState} ->
+ UserState;
+ {fail, Reason} ->
+ case Result of
+ {bad_cert, _} ->
+ throw(Result);
+ _ ->
+ throw({bad_cert, Reason})
+ end;
+ {unknown, UserState} ->
+ case Result of
+ {extension, #'Extension'{critical = true}} ->
+ throw({bad_cert, unknown_critical_extension});
+ _ ->
+ UserState
+ end
+ end.
extract_verify_data(OtpCert, DerCert) ->
{0, Signature} = OtpCert#'OTPCertificate'.signature,
@@ -460,198 +460,189 @@ select_extension(Id, [_ | Extensions]) ->
select_extension(Id, Extensions).
%% No extensions present
-validate_extensions(asn1_NOVALUE, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr) ->
- validate_extensions([], ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
-
-validate_extensions([], ValidationState, basic_constraint, _SelfSigned,
- UnknownExtensions, _Verify, AccErr) ->
- {ValidationState, UnknownExtensions, AccErr};
-validate_extensions([], ValidationState =
- #path_validation_state{max_path_length = Len,
- last_cert = Last},
- no_basic_constraint, SelfSigned, UnknownExtensions,
- Verify, AccErr0) ->
+validate_extensions(OtpCert, asn1_NOVALUE, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun) ->
+ validate_extensions(OtpCert, [], ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
+
+validate_extensions(_,[], ValidationState, basic_constraint, _SelfSigned,
+ UserState, _) ->
+ {ValidationState, UserState};
+validate_extensions(OtpCert, [], ValidationState =
+ #path_validation_state{max_path_length = Len,
+ last_cert = Last},
+ no_basic_constraint, SelfSigned, UserState0, VerifyFun) ->
case Last of
true when SelfSigned ->
- {ValidationState, UnknownExtensions, AccErr0};
+ {ValidationState, UserState0};
true ->
{ValidationState#path_validation_state{max_path_length = Len - 1},
- UnknownExtensions, AccErr0};
+ UserState0};
%% basic_constraint must appear in certs used for digital sign
%% see 4.2.1.10 in rfc 3280
false ->
- AccErr = not_valid({bad_cert, missing_basic_constraint},
- Verify, AccErr0),
+ UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint},
+ UserState0, VerifyFun),
case SelfSigned of
true ->
- {ValidationState, UnknownExtensions, AccErr};
+ {ValidationState, UserState};
false ->
{ValidationState#path_validation_state{max_path_length =
- Len - 1},
- UnknownExtensions, AccErr}
+ Len - 1},
+ UserState}
end
end;
-validate_extensions([#'Extension'{extnID = ?'id-ce-basicConstraints',
+validate_extensions(OtpCert,
+ [#'Extension'{extnID = ?'id-ce-basicConstraints',
extnValue =
- #'BasicConstraints'{cA = true,
- pathLenConstraint = N}} |
+ #'BasicConstraints'{cA = true,
+ pathLenConstraint = N}} |
Rest],
- ValidationState =
- #path_validation_state{max_path_length = Len}, _,
- SelfSigned, UnknownExtensions, Verify, AccErr) ->
+ ValidationState =
+ #path_validation_state{max_path_length = Len}, _,
+ SelfSigned, UserState, VerifyFun) ->
Length = if SelfSigned -> erlang:min(N, Len);
true -> erlang:min(N, Len-1)
end,
- validate_extensions(Rest,
+ validate_extensions(OtpCert, Rest,
ValidationState#path_validation_state{max_path_length =
- Length},
- basic_constraint, SelfSigned, UnknownExtensions,
- Verify, AccErr);
+ Length},
+ basic_constraint, SelfSigned,
+ UserState, VerifyFun);
%% The pathLenConstraint field is meaningful only if cA is set to
%% TRUE.
-validate_extensions([#'Extension'{extnID = ?'id-ce-basicConstraints',
- extnValue =
- #'BasicConstraints'{cA = false}} |
- Rest], ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr) ->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
-
-%%
-validate_extensions([#'Extension'{extnID = ?'id-ce-keyUsage',
- extnValue = KeyUse
- } | Rest],
- #path_validation_state{last_cert=Last} = ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr0) ->
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue =
+ #'BasicConstraints'{cA = false}} |
+ Rest], ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun) ->
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
+
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-keyUsage',
+ extnValue = KeyUse
+ } | Rest],
+ #path_validation_state{last_cert=Last} = ValidationState,
+ ExistBasicCon, SelfSigned,
+ UserState0, VerifyFun) ->
case Last orelse is_valid_key_usage(KeyUse, keyCertSign) of
true ->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify,
- AccErr0);
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState0, VerifyFun);
false ->
- AccErr = not_valid({bad_cert, invalid_key_usage}, Verify, AccErr0),
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify,
- AccErr)
+ UserState = verify_fun(OtpCert, {bad_cert, invalid_key_usage},
+ UserState0, VerifyFun),
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun)
end;
-validate_extensions([#'Extension'{extnID = ?'id-ce-subjectAltName',
- extnValue = Names} | Rest],
- ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr0) ->
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-subjectAltName',
+ extnValue = Names} | Rest],
+ ValidationState, ExistBasicCon,
+ SelfSigned, UserState0, VerifyFun) ->
case validate_subject_alt_names(Names) of
true when Names =/= [] ->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify,
- AccErr0);
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState0, VerifyFun);
_ ->
- AccErr =
- not_valid({bad_cert, invalid_subject_altname},
- Verify, AccErr0),
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify,
- AccErr)
+ UserState = verify_fun(OtpCert, {bad_cert, invalid_subject_altname},
+ UserState0, VerifyFun),
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun)
end;
%% This extension SHOULD NOT be marked critical. Its value
%% does not have to be further validated at this point.
-validate_extensions([#'Extension'{extnID = ?'id-ce-issuerAltName',
- extnValue = _} | Rest],
- ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr) ->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-issuerAltName',
+ extnValue = _} | Rest],
+ ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun) ->
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
%% This extension MUST NOT be marked critical.Its value
%% does not have to be further validated at this point.
-validate_extensions([#'Extension'{extnID = Id,
- extnValue = _,
- critical = false} | Rest],
+validate_extensions(OtpCert, [#'Extension'{extnID = Id,
+ extnValue = _,
+ critical = false} | Rest],
ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr)
+ ExistBasicCon, SelfSigned,
+ UserState, VerifyFun)
when Id == ?'id-ce-subjectKeyIdentifier';
Id == ?'id-ce-authorityKeyIdentifier'->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
-validate_extensions([#'Extension'{extnID = ?'id-ce-nameConstraints',
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-nameConstraints',
extnValue = NameConst} | Rest],
ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr) ->
+ ExistBasicCon, SelfSigned, UserState, VerifyFun) ->
Permitted = NameConst#'NameConstraints'.permittedSubtrees,
Excluded = NameConst#'NameConstraints'.excludedSubtrees,
NewValidationState = add_name_constraints(Permitted, Excluded,
ValidationState),
- validate_extensions(Rest, NewValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
+ validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
-validate_extensions([#'Extension'{extnID = ?'id-ce-certificatePolicies',
- critical = true} | Rest], ValidationState,
- ExistBasicCon, SelfSigned,
- UnknownExtensions, Verify, AccErr0) ->
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-certificatePolicies',
+ critical = true} = Ext| Rest], ValidationState,
+ ExistBasicCon, SelfSigned, UserState0, VerifyFun) ->
%% TODO: Remove this clause when policy handling is
%% fully implemented
- AccErr =
- not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr0),
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
-
-validate_extensions([#'Extension'{extnID = ?'id-ce-certificatePolicies',
- extnValue = #'PolicyInformation'{
- policyIdentifier = Id,
- policyQualifiers = Qualifier}}
- | Rest], #path_validation_state{valid_policy_tree = Tree}
+ UserState = verify_fun(OtpCert, {extension, Ext},
+ UserState0, VerifyFun),
+ validate_extensions(OtpCert,Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
+
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-certificatePolicies',
+ extnValue = #'PolicyInformation'{
+ policyIdentifier = Id,
+ policyQualifiers = Qualifier}}
+ | Rest], #path_validation_state{valid_policy_tree = Tree}
= ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr) ->
+ ExistBasicCon, SelfSigned, UserState, VerifyFun) ->
%% TODO: Policy imp incomplete
NewTree = process_policy_tree(Id, Qualifier, Tree),
- validate_extensions(Rest,
+ validate_extensions(OtpCert, Rest,
ValidationState#path_validation_state{
valid_policy_tree = NewTree},
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr);
+ ExistBasicCon, SelfSigned, UserState, VerifyFun);
-validate_extensions([#'Extension'{extnID = ?'id-ce-policyConstraints',
- critical = true} | Rest], ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions, Verify,
- AccErr0) ->
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints',
+ critical = true} = Ext | Rest], ValidationState,
+ ExistBasicCon, SelfSigned, UserState0, VerifyFun) ->
%% TODO: Remove this clause when policy handling is
%% fully implemented
- AccErr =
- not_valid({bad_cert, unknown_critical_extension}, Verify, AccErr0),
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
-validate_extensions([#'Extension'{extnID = ?'id-ce-policyConstraints',
- extnValue = #'PolicyConstraints'{
- requireExplicitPolicy = ExpPolicy,
- inhibitPolicyMapping = MapPolicy}}
- | Rest], ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr) ->
+ UserState = verify_fun(OtpCert, {extension, Ext},
+ UserState0, VerifyFun),
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
+validate_extensions(OtpCert, [#'Extension'{extnID = ?'id-ce-policyConstraints',
+ extnValue = #'PolicyConstraints'{
+ requireExplicitPolicy = ExpPolicy,
+ inhibitPolicyMapping = MapPolicy}}
+ | Rest], ValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun) ->
%% TODO: Policy imp incomplete
- NewValidationState = add_policy_constraints(ExpPolicy, MapPolicy,
+ NewValidationState = add_policy_constraints(ExpPolicy, MapPolicy,
ValidationState),
- validate_extensions(Rest, NewValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr);
+ validate_extensions(OtpCert, Rest, NewValidationState, ExistBasicCon,
+ SelfSigned, UserState, VerifyFun);
-validate_extensions([Extension | Rest], ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions,
- Verify, AccErr) ->
- validate_extensions(Rest, ValidationState, ExistBasicCon, SelfSigned,
- [Extension | UnknownExtensions], Verify, AccErr).
+validate_extensions(OtpCert, [#'Extension'{} = Extension | Rest],
+ ValidationState, ExistBasicCon,
+ SelfSigned, UserState0, VerifyFun) ->
+ UserState = verify_fun(OtpCert, {extension, Extension}, UserState0, VerifyFun),
+ validate_extensions(OtpCert, Rest, ValidationState, ExistBasicCon, SelfSigned,
+ UserState, VerifyFun).
is_valid_key_usage(KeyUse, Use) ->
lists:member(Use, KeyUse).
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index f9b992afd3..68bf04eeff 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -109,7 +109,8 @@ pem_entry_decode({Asn1Type, CryptDer, {Cipher, Salt}} = PemEntry,
%%--------------------------------------------------------------------
-spec pem_entry_encode(pki_asn1_type(), term()) -> pem_entry().
-spec pem_entry_encode(pki_asn1_type(), term(),
- {{Cipher :: string(), Salt :: binary()}, string()}) -> pem_entry().
+ {{Cipher :: string(), Salt :: binary()}, string()}) ->
+ pem_entry().
%
%% Description: Creates a pem entry that can be feed to pem_encode/1.
%%--------------------------------------------------------------------
@@ -440,17 +441,25 @@ pkix_normalize_name(Issuer) ->
CertChain :: [der_encoded()] ,
Options :: list()) ->
{ok, {PublicKeyInfo :: term(),
- PolicyTree :: term(),
- [{bad_cert, Reason :: term()}]}} |
+ PolicyTree :: term()}} |
{error, {bad_cert, Reason :: term()}}.
%% Description: Performs a basic path validation according to RFC 5280.
%%--------------------------------------------------------------------
-pkix_path_validation(unknown_ca, [Cert | Chain], Options) ->
- case proplists:get_value(verify, Options, true) of
- true ->
- {error, {bad_cert, unknown_ca}};
- false ->
- pkix_path_validation(Cert, Chain, [{acc_errors, [{bad_cert, unknown_ca}]}])
+pkix_path_validation(unknown_ca, [Cert | Chain], Options0) ->
+ {VerifyFun, Userstat0} =
+ proplists:get_value(verify_fun, Options0, ?DEFAULT_VERIFYFUN),
+ Otpcert = pkix_decode_cert(Cert, otp),
+ Reason = {bad_cert, unknown_ca},
+ try VerifyFun(Otpcert, Reason, Userstat0) of
+ {valid, Userstate} ->
+ Options = proplists:delete(verify_fun, Options0),
+ pkix_path_validation(Otpcert, Chain, [{verify_fun,
+ {VerifyFun, Userstate}}| Options]);
+ {fail, _} ->
+ {error, Reason}
+ catch
+ _:_ ->
+ {error, Reason}
end;
pkix_path_validation(TrustedCert, CertChain, Options) when
is_binary(TrustedCert) -> OtpCert = pkix_decode_cert(TrustedCert,
@@ -462,12 +471,7 @@ pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options)
ValidationState = pubkey_cert:init_validation_state(TrustedCert,
MaxPathDefault,
Options),
- Fun = proplists:get_value(validate_extensions_fun, Options,
- fun(Extensions, State, _, AccError) ->
- {Extensions, State, AccError}
- end),
- Verify = proplists:get_value(verify, Options, true),
- path_validation(CertChain, ValidationState, Fun, Verify).
+ path_validation(CertChain, ValidationState).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -490,38 +494,40 @@ path_validation([], #path_validation_state{working_public_key_algorithm
PublicKey,
working_public_key_parameters
= PublicKeyParams,
- valid_policy_tree = Tree,
- acc_errors = AccErrors
- }, _, _) ->
- {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree, AccErrors}};
+ valid_policy_tree = Tree
+ }) ->
+ {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree}};
path_validation([DerCert | Rest], ValidationState = #path_validation_state{
- max_path_length = Len},
- Fun, Verify) when Len >= 0 ->
- try validate(DerCert,
- ValidationState#path_validation_state{last_cert=Rest=:=[]},
- Fun, Verify) of
+ max_path_length = Len}) when Len >= 0 ->
+ try validate(DerCert,
+ ValidationState#path_validation_state{last_cert=Rest=:=[]}) of
#path_validation_state{} = NewValidationState ->
- path_validation(Rest, NewValidationState, Fun, Verify)
+ path_validation(Rest, NewValidationState)
catch
throw:Reason ->
{error, Reason}
end;
-path_validation(_, _, _, true) ->
- {error, {bad_cert, max_path_length_reached}};
+path_validation([DerCert | _] = Path,
+ #path_validation_state{user_state = UserState0,
+ verify_fun = VerifyFun} =
+ ValidationState) ->
+ Reason = {bad_cert, max_path_length_reached},
+ OtpCert = pkix_decode_cert(DerCert, otp),
+ try VerifyFun(OtpCert, Reason, UserState0) of
+ {valid, UserState} ->
+ path_validation(Path,
+ ValidationState#path_validation_state{
+ max_path_length = 0,
+ user_state = UserState});
+ {fail, _} ->
+ {error, Reason}
+ catch
+ _:_ ->
+ {error, Reason}
+ end.
-path_validation(_, #path_validation_state{working_public_key_algorithm
- = Algorithm,
- working_public_key =
- PublicKey,
- working_public_key_parameters
- = PublicKeyParams,
- valid_policy_tree = Tree,
- acc_errors = AccErrors
- }, _, false) ->
- {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree,
- [{bad_cert, max_path_length_reached}|AccErrors]}}.
validate(DerCert, #path_validation_state{working_issuer_name = Issuer,
working_public_key = Key,
@@ -531,40 +537,29 @@ validate(DerCert, #path_validation_state{working_issuer_name = Issuer,
excluded_subtrees = Exclude,
last_cert = Last,
user_state = UserState0,
- acc_errors = AccErr0} =
- ValidationState0, ValidateExtensionFun, Verify) ->
+ verify_fun = VerifyFun} =
+ ValidationState0) ->
OtpCert = pkix_decode_cert(DerCert, otp),
- %% All validate functions will throw {bad_cert, Reason} if they
- %% fail and Verify = true if Verify = false errors
- %% will be accumulated in the validationstate
- AccErr1 = pubkey_cert:validate_time(OtpCert, AccErr0, Verify),
- AccErr2 = pubkey_cert:validate_issuer(OtpCert, Issuer, AccErr1, Verify),
+ UserState1 = pubkey_cert:validate_time(OtpCert, UserState0, VerifyFun),
+
+ UserState2 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState1, VerifyFun),
- AccErr3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
- AccErr2, Verify),
- AccErr4 =
- pubkey_cert:validate_revoked_status(OtpCert, Verify, AccErr3),
+ UserState3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
+ UserState2,VerifyFun),
+
+ UserState4 = pubkey_cert:validate_revoked_status(OtpCert, UserState3, VerifyFun),
- {ValidationState1, UnknownExtensions0, AccErr5} =
- pubkey_cert:validate_extensions(OtpCert, ValidationState0, Verify,
- AccErr4),
- %% We want the key_usage extension to be checked before we validate
+ {ValidationState1, UserState5} =
+ pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState4,
+ VerifyFun),
+
+ %% We want the key_usage extension to be checked before we validate
%% the signature.
- AccErr6 =
- pubkey_cert:validate_signature(OtpCert, DerCert, Key, KeyParams,
- AccErr5, Verify),
-
- {UnknownExtensions, UserState, AccErr7} =
- ValidateExtensionFun(UnknownExtensions0, UserState0, Verify, AccErr6),
-
- %% Check that all critical extensions have been handled
- AccErr =
- pubkey_cert:validate_unknown_extensions(UnknownExtensions, AccErr7,
- Verify),
+ UserState = pubkey_cert:validate_signature(OtpCert, DerCert,
+ Key, KeyParams, UserState5, VerifyFun),
ValidationState =
- ValidationState1#path_validation_state{user_state = UserState,
- acc_errors = AccErr},
+ ValidationState1#path_validation_state{user_state = UserState},
pubkey_cert:prepare_for_next_cert(OtpCert, ValidationState).
sized_binary(Binary) when is_binary(Binary) ->
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 09235ff460..46b8c3db8b 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -369,16 +369,35 @@ pkix_path_validation(Config) when is_list(Config) ->
CertK3 = {Cert3,_} = erl_make_certs:make_cert([{issuer, CertK1},
{extensions, [{basic_constraints, false}]}]),
{Cert4,_} = erl_make_certs:make_cert([{issuer, CertK3}]),
- {error, E={bad_cert,missing_basic_constraint}} =
+ {error, {bad_cert,missing_basic_constraint}} =
public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], []),
-
- {ok, {_,_,[E]}} = public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4],
- [{verify,false}]),
-
- {error, {bad_cert,unknown_ca}} = public_key:pkix_path_validation(unknown_ca, [Cert1, Cert3, Cert4], []),
- {ok, {_,_,[{bad_cert,unknown_ca}]}} =
- public_key:pkix_path_validation(unknown_ca, [Cert1], [{verify, false}]),
+ VerifyFunAndState0 = {fun(_,{bad_cert, missing_basic_constraint}, UserState) ->
+ {valid, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []},
+ {ok, _} =
+ public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4],
+ [{verify_fun, VerifyFunAndState0}]),
+
+ {error, {bad_cert, unknown_ca}} =
+ public_key:pkix_path_validation(unknown_ca, [Cert1, Cert3, Cert4], []),
+
+ VerifyFunAndState1 =
+ {fun(_,{bad_cert, unknown_ca}, UserState) ->
+ {valid, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []},
+
+ {ok, _} =
+ public_key:pkix_path_validation(unknown_ca, [Cert1], [{verify_fun,
+ VerifyFunAndState1}]),
ok.
%%--------------------------------------------------------------------
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 90bb50fdcb..dbc5faff14 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -523,55 +523,55 @@ old_listen(Port, Options) ->
{ok, Pid} = ssl_broker:start_broker(listener),
ssl_broker:listen(Pid, Port, Options).
-handle_options(Opts0, Role) ->
+handle_options(Opts0, _Role) ->
Opts = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
ReuseSessionFun = fun(_, _, _, _) -> true end,
- AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc;
- (Other, Acc) -> [Other | Acc]
- end,
-
- VerifyFun =
- fun(ErrorList) ->
- case lists:foldl(AcceptBadCa, [], ErrorList) of
- [] -> true;
- [_|_] -> false
- end
- end,
-
- UserFailIfNoPeerCert = validate_option(fail_if_no_peer_cert,
- proplists:get_value(fail_if_no_peer_cert, Opts, false)),
+ VerifyNoneFun =
+ {fun(_,{bad_cert, unknown_ca}, UserState) ->
+ {valid, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []},
+
+ UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
+ UserVerifyFun = handle_option(verify_fun, Opts, undefined),
CaCerts = handle_option(cacerts, Opts, undefined),
- {Verify, FailIfNoPeerCert, CaCertDefault} =
+ {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} =
%% Handle 0, 1, 2 for backwards compatibility
case proplists:get_value(verify, Opts, verify_none) of
0 ->
- {verify_none, false, ca_cert_default(verify_none, Role, CaCerts)};
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
1 ->
- {verify_peer, false, ca_cert_default(verify_peer, Role, CaCerts)};
+ {verify_peer, false,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
2 ->
- {verify_peer, true, ca_cert_default(verify_peer, Role, CaCerts)};
+ {verify_peer, true,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
verify_none ->
- {verify_none, false, ca_cert_default(verify_none, Role, CaCerts)};
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
verify_peer ->
{verify_peer, UserFailIfNoPeerCert,
- ca_cert_default(verify_peer, Role, CaCerts)};
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
Value ->
throw({error, {eoptions, {verify, Value}}})
- end,
+ end,
CertFile = handle_option(certfile, Opts, ""),
SSLOptions = #ssl_options{
versions = handle_option(versions, Opts, []),
verify = validate_option(verify, Verify),
- verify_fun = handle_option(verify_fun, Opts, VerifyFun),
+ verify_fun = VerifyFun,
fail_if_no_peer_cert = FailIfNoPeerCert,
verify_client_once = handle_option(verify_client_once, Opts, false),
- validate_extensions_fun = handle_option(validate_extensions_fun, Opts, undefined),
depth = handle_option(depth, Opts, 1),
cert = handle_option(cert, Opts, undefined),
certfile = CertFile,
@@ -591,7 +591,7 @@ handle_options(Opts0, Role) ->
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
- SslOptions = [versions, verify, verify_fun, validate_extensions_fun,
+ SslOptions = [versions, verify, verify_fun,
fail_if_no_peer_cert, verify_client_once,
depth, cert, certfile, key, keyfile,
password, cacerts, cacertfile, dhfile, ciphers,
@@ -618,7 +618,21 @@ validate_option(ssl_imp, Value) when Value == new; Value == old ->
validate_option(verify, Value)
when Value == verify_none; Value == verify_peer ->
Value;
-validate_option(verify_fun, Value) when is_function(Value) ->
+validate_option(verify_fun, undefined) ->
+ undefined;
+%% Backwards compatibility
+validate_option(verify_fun, Fun) when is_function(Fun) ->
+ {fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true ->
+ {valid, OldFun};
+ false ->
+ {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, Fun};
+validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) ->
Value;
validate_option(fail_if_no_peer_cert, Value)
when Value == true; Value == false ->
@@ -626,9 +640,6 @@ validate_option(fail_if_no_peer_cert, Value)
validate_option(verify_client_once, Value)
when Value == true; Value == false ->
Value;
-
-validate_option(validate_extensions_fun, Value) when Value == undefined; is_function(Value) ->
- Value;
validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
@@ -720,12 +731,11 @@ ca_cert_default(_,_, [_|_]) ->
undefined;
ca_cert_default(verify_none, _, _) ->
undefined;
-%% Client may leave verification up to the user
-ca_cert_default(verify_peer, client,_) ->
+ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
undefined;
-%% Server that wants to verify_peer must have
+%% Server that wants to verify_peer and has no verify_fun must have
%% some trusted certs.
-ca_cert_default(verify_peer, server, _) ->
+ca_cert_default(verify_peer, undefined, _) ->
"".
emulated_options() ->
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 5026c760bd..6cf57ced81 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -34,7 +34,8 @@
-export([trusted_cert_and_path/2,
certificate_chain/2,
file_to_certificats/1,
- validate_extensions/6,
+ %validate_extensions/6,
+ validate_extension/3,
is_valid_extkey_usage/2,
is_valid_key_usage/2,
select_extension/2,
@@ -110,32 +111,25 @@ file_to_certificats(File) ->
{ok, List} = ssl_manager:cache_pem_file(File),
[Bin || {'Certificate', Bin, not_encrypted} <- List].
%%--------------------------------------------------------------------
--spec validate_extensions([#'Extension'{}], term(), [#'Extension'{}],
- boolean(), list(), client | server) -> {[#'Extension'{}], term(), list()}.
+-spec validate_extension(term(), #'Extension'{}, term()) -> {valid, term()} |
+ {fail, tuple()} |
+ {unknown, term()}.
%%
%% Description: Validates ssl/tls specific extensions
%%--------------------------------------------------------------------
-validate_extensions([], ValidationState, UnknownExtensions, _, AccErr, _) ->
- {UnknownExtensions, ValidationState, AccErr};
-
-validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage',
- extnValue = KeyUse,
- critical = true} | Rest],
- ValidationState, UnknownExtensions, Verify, AccErr0, Role) ->
+validate_extension(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage',
+ extnValue = KeyUse,
+ critical = true}}, Role) ->
case is_valid_extkey_usage(KeyUse, Role) of
true ->
- validate_extensions(Rest, ValidationState, UnknownExtensions,
- Verify, AccErr0, Role);
+ {valid, Role};
false ->
- AccErr =
- not_valid_extension({bad_cert, invalid_ext_key_usage}, Verify, AccErr0),
- validate_extensions(Rest, ValidationState, UnknownExtensions, Verify, AccErr, Role)
+ {fail, {bad_cert, invalid_ext_key_usage}}
end;
-
-validate_extensions([Extension | Rest], ValidationState, UnknownExtensions,
- Verify, AccErr, Role) ->
- validate_extensions(Rest, ValidationState, [Extension | UnknownExtensions],
- Verify, AccErr, Role).
+validate_extension(_, {bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+validate_extension(_, _, Role) ->
+ {unknown, Role}.
%%--------------------------------------------------------------------
-spec is_valid_key_usage(list(), term()) -> boolean().
@@ -248,8 +242,3 @@ is_valid_extkey_usage(KeyUse, client) ->
is_valid_extkey_usage(KeyUse, server) ->
%% Server wants to verify client
is_valid_key_usage(KeyUse, ?'id-kp-clientAuth').
-
-not_valid_extension(Error, true, _) ->
- throw(Error);
-not_valid_extension(Error, false, AccErrors) ->
- [Error | AccErrors].
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index c004effb85..7689976ff6 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -500,8 +500,7 @@ certify(#certificate{} = Cert,
ssl_options = Opts} = State) ->
case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
- Opts#ssl_options.verify_fun,
- Opts#ssl_options.validate_extensions_fun, Role) of
+ Opts#ssl_options.verify_fun, Role) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(PeerCert, PublicKeyInfo,
State#state{client_certificate_requested = false});
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index add5147fb4..99bc47f04b 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -32,15 +32,13 @@
-include_lib("public_key/include/public_key.hrl").
-export([master_secret/4, client_hello/5, server_hello/4, hello/4,
- hello_request/0, certify/7, certificate/3,
- client_certificate_verify/6,
- certificate_verify/6, certificate_request/2,
- key_exchange/2, server_key_exchange_hash/2, finished/4,
- verify_connection/5,
- get_tls_handshake/2, decode_client_key/3,
- server_hello_done/0, sig_alg/1,
- encode_handshake/3, init_hashes/0,
- update_hashes/2, decrypt_premaster_secret/2]).
+ hello_request/0, certify/6, certificate/3,
+ client_certificate_verify/6, certificate_verify/6,
+ certificate_request/2, key_exchange/2, server_key_exchange_hash/2,
+ finished/4, verify_connection/5, get_tls_handshake/2,
+ decode_client_key/3, server_hello_done/0, sig_alg/1,
+ encode_handshake/3, init_hashes/0, update_hashes/2,
+ decrypt_premaster_secret/2]).
-type tls_handshake() :: #client_hello{} | #server_hello{} |
#server_hello_done{} | #certificate{} | #certificate_request{} |
@@ -177,59 +175,55 @@ hello(#client_hello{client_version = ClientVersion, random = Random,
%%--------------------------------------------------------------------
-spec certify(#certificate{}, term(), integer() | nolimit,
- verify_peer | verify_none, fun(), fun(),
+ verify_peer | verify_none, {fun(), term},
client | server) -> {der_cert(), public_key_info()} | #alert{}.
%%
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
-certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
- MaxPathLen, Verify, VerifyFun, ValidateFun, Role) ->
+certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
+ MaxPathLen, _Verify, VerifyFunAndState, Role) ->
[PeerCert | _] = ASN1Certs,
- VerifyBool = verify_bool(Verify),
- ValidateExtensionFun =
- case ValidateFun of
+ ValidationFunAndState =
+ case VerifyFunAndState of
undefined ->
- fun(Extensions, ValidationState, Verify0, AccError) ->
- ssl_certificate:validate_extensions(Extensions, ValidationState,
- [], Verify0, AccError, Role)
- end;
- Fun ->
- fun(Extensions, ValidationState, Verify0, AccError) ->
- {NewExtensions, NewValidationState, NewAccError}
- = ssl_certificate:validate_extensions(Extensions, ValidationState,
- [], Verify0, AccError, Role),
- Fun(NewExtensions, NewValidationState, Verify0, NewAccError)
- end
+ {fun(OtpCert, ExtensionOrError, SslState) ->
+ ssl_certificate:validate_extension(OtpCert,
+ ExtensionOrError, SslState)
+ end, Role};
+ {Fun, UserState0} ->
+ {fun(OtpCert, ExtensionOrError, {SslState, UserState}) ->
+ case ssl_certificate:validate_extension(OtpCert,
+ ExtensionOrError,
+ SslState) of
+ {valid, _} ->
+ apply_user_fun(Fun, OtpCert,
+ ExtensionOrError, UserState,
+ SslState);
+ {fail, Reason} ->
+ apply_user_fun(Fun, OtpCert, Reason, UserState,
+ SslState);
+ {unknown, _} ->
+ apply_user_fun(Fun, OtpCert,
+ ExtensionOrError, UserState, SslState)
+ end
+ end, {Role, UserState0}}
end,
- try
- ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef) of
- {TrustedErlCert, CertPath} ->
- Result = public_key:pkix_path_validation(TrustedErlCert,
- CertPath,
- [{max_path_length,
- MaxPathLen},
- {verify, VerifyBool},
- {validate_extensions_fun,
- ValidateExtensionFun}]),
- case Result of
- {error, Reason} ->
- path_validation_alert(Reason, Verify);
- {ok, {PublicKeyInfo,_, []}} ->
- {PeerCert, PublicKeyInfo};
- {ok, {PublicKeyInfo,_, AccErrors = [Error | _]}} ->
- case VerifyFun(AccErrors) of
- true ->
- {PeerCert, PublicKeyInfo};
- false ->
- path_validation_alert(Error, Verify)
- end
- end
- catch
- throw:Alert ->
- Alert
+
+ {TrustedErlCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef),
+
+ case public_key:pkix_path_validation(TrustedErlCert,
+ CertPath,
+ [{max_path_length,
+ MaxPathLen},
+ {verify_fun, ValidationFunAndState}]) of
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
+ {error, Reason} ->
+ path_validation_alert(Reason)
end.
-
+
%%--------------------------------------------------------------------
-spec certificate(der_cert(), term(), client | server) -> #certificate{} | #alert{}.
%%
@@ -490,26 +484,21 @@ get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length),
get_tls_handshake_aux(Data, Acc) ->
{lists:reverse(Acc), Data}.
-verify_bool(verify_peer) ->
- true;
-verify_bool(verify_none) ->
- false.
-
-path_validation_alert({bad_cert, cert_expired}, _) ->
+path_validation_alert({bad_cert, cert_expired}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED);
-path_validation_alert({bad_cert, invalid_issuer}, _) ->
+path_validation_alert({bad_cert, invalid_issuer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
-path_validation_alert({bad_cert, invalid_signature} , _) ->
+path_validation_alert({bad_cert, invalid_signature}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
-path_validation_alert({bad_cert, name_not_permitted}, _) ->
+path_validation_alert({bad_cert, name_not_permitted}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
-path_validation_alert({bad_cert, unknown_critical_extension}, _) ->
+path_validation_alert({bad_cert, unknown_critical_extension}) ->
?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
-path_validation_alert({bad_cert, cert_revoked}, _) ->
+path_validation_alert({bad_cert, cert_revoked}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
-path_validation_alert({bad_cert, unknown_ca}, _) ->
+path_validation_alert({bad_cert, unknown_ca}) ->
?ALERT_REC(?FATAL, ?UNKNOWN_CA);
-path_validation_alert(_, _) ->
+path_validation_alert(_) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
select_session(Hello, Port, Session, Version,
@@ -1132,3 +1121,13 @@ key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss;
?KEY_EXCHANGE_DIFFIE_HELLMAN;
key_exchange_alg(_) ->
?NULL.
+
+apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) ->
+ case Fun(OtpCert, ExtensionOrError, UserState0) of
+ {valid, UserState} ->
+ {valid, {SslState, UserState}};
+ {fail, _} = Fail ->
+ Fail;
+ {unknown, UserState} ->
+ {unknown, {SslState, UserState}}
+ end.
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 6db13e5b7a..25e7445180 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -113,7 +113,7 @@ select_session(Sessions, #ssl_options{ciphers = Ciphers,
List ->
hd(List)
end.
-
+
%% If we can not generate a not allready in use session ID in
%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The
%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 1013f2bb6e..47c7407a2e 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -232,10 +232,11 @@ all(suite) ->
server_renegotiate, client_renegotiate_reused_session,
server_renegotiate_reused_session, client_no_wrap_sequence_number,
server_no_wrap_sequence_number, extended_key_usage,
- validate_extensions_fun, no_authority_key_identifier,
+ no_authority_key_identifier,
invalid_signature_client, invalid_signature_server, cert_expired,
client_with_cert_cipher_suites_handshake, unknown_server_ca_fail,
- unknown_server_ca_accept, der_input
+ der_input, unknown_server_ca_accept_verify_none, unknown_server_ca_accept_verify_peer,
+ unknown_server_ca_accept_backwardscompatibilty
].
%% Test cases starts here.
@@ -1260,7 +1261,6 @@ eoptions(Config) when is_list(Config) ->
{verify_fun, function},
{fail_if_no_peer_cert, 0},
{verify_client_once, 1},
- {validate_extensions_fun, function},
{depth, four},
{certfile, 'cert.pem'},
{keyfile,'key.pem' },
@@ -2271,14 +2271,7 @@ client_verify_none_active_once(Config) when is_list(Config) ->
{mfa, {?MODULE, send_recv_result_active_once, []}},
{options, [{active, once} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- %% TODO: send message to test process to make sure
- %% verifyfun has beeen run as it has the same behavior as
- %% the default fun
- VerifyFun = fun([{bad_cert, unknown_ca}]) ->
- true;
- (_) ->
- false
- end,
+
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -2286,8 +2279,7 @@ client_verify_none_active_once(Config) when is_list(Config) ->
send_recv_result_active_once,
[]}},
{options, [{active, once},
- {verify, verify_none},
- {verify_fun, VerifyFun}
+ {verify, verify_none}
| ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -2578,41 +2570,6 @@ extended_key_usage(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
-validate_extensions_fun(doc) ->
- ["Test that it is possible to specify a validate_extensions_fun"];
-
-validate_extensions_fun(suite) ->
- [];
-
-validate_extensions_fun(Config) when is_list(Config) ->
- ClientOpts = ?config(client_verification_opts, Config),
- ServerOpts = ?config(server_verification_opts, Config),
-
- Fun = fun(Extensions, State, _, AccError) ->
- {Extensions, State, AccError}
- end,
-
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, send_recv_result_active, []}},
- {options, [{validate_extensions_fun, Fun},
- {verify, verify_peer} | ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
-
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, send_recv_result_active, []}},
- {options,[{validate_extensions_fun, Fun} | ClientOpts]}]),
-
- ssl_test_lib:check_result(Server, ok, Client, ok),
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
-
-%%--------------------------------------------------------------------
no_authority_key_identifier(doc) ->
["Test cert that does not have authorityKeyIdentifier extension"
" but are present in trusted certs db."];
@@ -2899,24 +2856,34 @@ unknown_server_ca_fail(Config) when is_list(Config) ->
no_result, []}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
+
+ FunAndState = {fun(_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []},
+
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {ssl_test_lib,
no_result, []}},
{options,
- [{verify, verify_peer}| ClientOpts]}]),
+ [{verify, verify_peer},
+ {verify_fun, FunAndState}
+ | ClientOpts]}]),
- ssl_test_lib:check_result(Server, {error,"unknown ca"}, Client, {error, "unknown ca"}),
+ ssl_test_lib:check_result(Server, {error,"unknown ca"},
+ Client, {error, "unknown ca"}),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
-unknown_server_ca_accept(doc) ->
+unknown_server_ca_accept_verify_none(doc) ->
["Test that the client succeds if the ca is unknown in verify_none mode"];
-unknown_server_ca_accept(suite) ->
+unknown_server_ca_accept_verify_none(suite) ->
[];
-unknown_server_ca_accept(Config) when is_list(Config) ->
+unknown_server_ca_accept_verify_none(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2937,6 +2904,83 @@ unknown_server_ca_accept(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, ok, Client, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+unknown_server_ca_accept_verify_peer(doc) ->
+ ["Test that the client succeds if the ca is unknown in verify_peer mode"
+ " with a verify_fun that accepts the unknown ca error"];
+unknown_server_ca_accept_verify_peer(suite) ->
+ [];
+unknown_server_ca_accept_verify_peer(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) ->
+ {valid, UserState};
+ (_,{bad_cert, _} = Reason, _) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState}
+ end, []},
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_recv_result_active, []}},
+ {options,
+ [{verify, verify_peer},
+ {verify_fun, FunAndState}| ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+unknown_server_ca_accept_backwardscompatibilty(doc) ->
+ ["Test that the client succeds if the ca is unknown in verify_none mode"];
+unknown_server_ca_accept_backwardscompatibilty(suite) ->
+ [];
+unknown_server_ca_accept_backwardscompatibilty(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc;
+ (Other, Acc) -> [Other | Acc]
+ end,
+ VerifyFun =
+ fun(ErrorList) ->
+ case lists:foldl(AcceptBadCa, [], ErrorList) of
+ [] -> true;
+ [_|_] -> false
+ end
+ end,
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_recv_result_active, []}},
+ {options,
+ [{verify, verify_peer},
+ {verify_fun, VerifyFun}| ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
der_input(doc) ->