aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public_key/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/public_key/src')
-rw-r--r--lib/public_key/src/Makefile3
-rw-r--r--lib/public_key/src/pubkey_cert.erl228
-rw-r--r--lib/public_key/src/pubkey_crl.erl701
-rw-r--r--lib/public_key/src/pubkey_pem.erl12
-rw-r--r--lib/public_key/src/public_key.app.src1
-rw-r--r--lib/public_key/src/public_key.erl188
6 files changed, 1001 insertions, 132 deletions
diff --git a/lib/public_key/src/Makefile b/lib/public_key/src/Makefile
index d5cd13d81a..d24122221a 100644
--- a/lib/public_key/src/Makefile
+++ b/lib/public_key/src/Makefile
@@ -44,7 +44,8 @@ MODULES = \
pubkey_ssh \
pubkey_pbe \
pubkey_cert \
- pubkey_cert_records
+ pubkey_cert_records \
+ pubkey_crl
HRL_FILES = $(INCLUDE)/public_key.hrl
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index f9e2025479..f53c94b334 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -26,10 +26,11 @@
-export([init_validation_state/3, prepare_for_next_cert/2,
validate_time/3, validate_signature/6,
validate_issuer/4, validate_names/6,
- validate_revoked_status/3, validate_extensions/4,
+ validate_extensions/4,
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, verify_fun/4]).
+ verify_data/1, verify_fun/4, select_extension/2, match_name/3,
+ extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1]).
-define(NULL, 0).
@@ -204,17 +205,6 @@ validate_names(OtpCert, Permit, Exclude, Last, UserState, VerifyFun) ->
end.
%%--------------------------------------------------------------------
--spec validate_revoked_status(#'OTPCertificate'{}, term(), fun()) ->
- term().
-%%
-%% Description: Check if certificate has been revoked.
-%%--------------------------------------------------------------------
-validate_revoked_status(_OtpCert, UserState, _VerifyFun) ->
- %% TODO: Implement or leave for application?!
- %% valid |
- %% throw({bad_cert, cert_revoked})
- UserState.
-%%--------------------------------------------------------------------
-spec validate_extensions(#'OTPCertificate'{}, #path_validation_state{},
term(), fun())->
{#path_validation_state{}, UserState :: term()}.
@@ -256,8 +246,10 @@ is_self_signed(#'OTPCertificate'{tbsCertificate=
%%
%% Description: Checks if <Issuer> issued <Candidate>.
%%--------------------------------------------------------------------
-is_issuer({rdnSequence, Issuer}, {rdnSequence, Candidate}) ->
- is_dir_name(Issuer, Candidate, true).
+is_issuer({rdnSequence, _} = Issuer, {rdnSequence, _} = Candidate) ->
+ {rdnSequence, IssuerDirName} = normalize_general_name(Issuer),
+ {rdnSequence, CandidateDirName} = normalize_general_name(Candidate),
+ is_dir_name(IssuerDirName, CandidateDirName, true).
%%--------------------------------------------------------------------
-spec issuer_id(#'OTPCertificate'{}, self | other) ->
{ok, {integer(), term()}} | {error, issuer_not_found}.
@@ -307,9 +299,9 @@ verify_fun(Otpcert, Result, UserState0, VerifyFun) ->
{valid,UserState} ->
UserState;
{fail, Reason} ->
- case Result of
+ case Reason of
{bad_cert, _} ->
- throw(Result);
+ throw(Reason);
_ ->
throw({bad_cert, Reason})
end;
@@ -321,6 +313,91 @@ verify_fun(Otpcert, Result, UserState0, VerifyFun) ->
UserState
end
end.
+%%--------------------------------------------------------------------
+-spec select_extension(Oid ::tuple(),[#'Extension'{}]) ->
+ #'Extension'{} | undefined.
+%%
+%% Description: Extracts a specific extension from a list of extensions.
+%%--------------------------------------------------------------------
+select_extension(_, []) ->
+ undefined;
+select_extension(Id, [#'Extension'{extnID = Id} = Extension | _]) ->
+ Extension;
+select_extension(Id, [_ | Extensions]) ->
+ select_extension(Id, Extensions).
+
+%%--------------------------------------------------------------------
+%% TODO:
+%%
+%% Description:
+%%--------------------------------------------------------------------
+match_name(rfc822Name, Name, [PermittedName | Rest]) ->
+ match_name(fun is_valid_host_or_domain/2, Name, PermittedName, Rest);
+
+match_name(directoryName, DirName, [PermittedName | Rest]) ->
+ match_name(fun is_rdnSeq/2, DirName, PermittedName, Rest);
+
+match_name(uniformResourceIdentifier, URI, [PermittedName | Rest]) ->
+ case split_uri(URI) of
+ incomplete ->
+ false;
+ {_, _, Host, _, _} ->
+ match_name(fun is_valid_host_or_domain/2, Host,
+ PermittedName, Rest)
+ end;
+
+match_name(emailAddress, Name, [PermittedName | Rest]) ->
+ Fun = fun(Email, PermittedEmail) ->
+ is_valid_email_address(Email, PermittedEmail,
+ string:tokens(PermittedEmail,"@"))
+ end,
+ match_name(Fun, Name, PermittedName, Rest);
+
+match_name(dNSName, Name, [PermittedName | Rest]) ->
+ Fun = fun(Domain, [$.|Domain]) -> true;
+ (Name1,Name2) ->
+ lists:suffix(string:to_lower(Name2),
+ string:to_lower(Name1))
+ end,
+ match_name(Fun, Name, [$.|PermittedName], Rest);
+
+match_name(x400Address, OrAddress, [PermittedAddr | Rest]) ->
+ match_name(fun is_or_address/2, OrAddress, PermittedAddr, Rest);
+
+match_name(ipAdress, IP, [PermittedIP | Rest]) ->
+ Fun = fun([IP1, IP2, IP3, IP4],
+ [IP5, IP6, IP7, IP8, M1, M2, M3, M4]) ->
+ is_permitted_ip([IP1, IP2, IP3, IP4],
+ [IP5, IP6, IP7, IP8],
+ [M1, M2, M3, M4]);
+ ([IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8,
+ IP9, IP10, IP11, IP12, IP13, IP14, IP15, IP16],
+ [IP17, IP18, IP19, IP20, IP21, IP22, IP23, IP24,
+ IP25, IP26, IP27, IP28, IP29, IP30, IP31, IP32,
+ M1, M2, M3, M4, M5, M6, M7, M8,
+ M9, M10, M11, M12, M13, M14, M15, M16]) ->
+ is_permitted_ip([IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8,
+ IP9, IP10, IP11, IP12, IP13,
+ IP14, IP15, IP16],
+ [IP17, IP18, IP19, IP20, IP21, IP22, IP23,
+ IP24,IP25, IP26, IP27, IP28, IP29, IP30,
+ IP31, IP32],
+ [M1, M2, M3, M4, M5, M6, M7, M8, M9, M10,
+ M11, M12, M13, M14, M15, M16]);
+ (_,_) ->
+ false
+ end,
+ match_name(Fun, IP, PermittedIP, Rest).
+
+match_name(Fun, Name, PermittedName, []) ->
+ Fun(Name, PermittedName);
+match_name(Fun, Name, PermittedName, [Head | Tail]) ->
+ case Fun(Name, PermittedName) of
+ true ->
+ true;
+ false ->
+ match_name(Fun, Name, Head, Tail)
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -332,7 +409,7 @@ do_normalize_general_name(Issuer) ->
(Atter) ->
Atter
end,
- lists:sort(lists:map(Normalize, Issuer)).
+ lists:map(Normalize, Issuer).
%% See rfc3280 4.1.2.6 Subject: regarding emails.
extract_email({rdnSequence, List}) ->
@@ -477,13 +554,6 @@ strip_spaces(String) ->
string:tokens(String, " ")),
string:strip(NewString).
-select_extension(_, []) ->
- undefined;
-select_extension(Id, [#'Extension'{extnID = Id} = Extension | _]) ->
- Extension;
-select_extension(Id, [_ | Extensions]) ->
- select_extension(Id, Extensions).
-
%% No extensions present
validate_extensions(OtpCert, asn1_NOVALUE, ValidationState, ExistBasicCon,
SelfSigned, UserState, VerifyFun) ->
@@ -503,18 +573,16 @@ validate_extensions(OtpCert, [], ValidationState =
true ->
{ValidationState#path_validation_state{max_path_length = Len - 1},
UserState0};
- %% basic_constraint must appear in certs used for digital sign
- %% see 4.2.1.10 in rfc 3280
false ->
- UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint},
- UserState0, VerifyFun),
- case SelfSigned of
+ %% 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
true ->
- {ValidationState, UserState};
- false ->
- {ValidationState#path_validation_state{max_path_length =
- Len - 1},
- UserState}
+ missing_basic_constraints(OtpCert, SelfSigned,
+ ValidationState, VerifyFun,
+ UserState0, Len);
+ false -> %% Example CRL signer only
+ {ValidationState, UserState0}
end
end;
@@ -861,74 +929,6 @@ type_subtree_names(Type, SubTrees) ->
[Name || #'GeneralSubtree'{base = {TreeType, Name}} <- SubTrees,
TreeType =:= Type].
-match_name(rfc822Name, Name, [PermittedName | Rest]) ->
- match_name(fun is_valid_host_or_domain/2, Name, PermittedName, Rest);
-
-match_name(directoryName, DirName, [PermittedName | Rest]) ->
- match_name(fun is_rdnSeq/2, DirName, PermittedName, Rest);
-
-match_name(uniformResourceIdentifier, URI, [PermittedName | Rest]) ->
- case split_uri(URI) of
- incomplete ->
- false;
- {_, _, Host, _, _} ->
- match_name(fun is_valid_host_or_domain/2, Host,
- PermittedName, Rest)
- end;
-
-match_name(emailAddress, Name, [PermittedName | Rest]) ->
- Fun = fun(Email, PermittedEmail) ->
- is_valid_email_address(Email, PermittedEmail,
- string:tokens(PermittedEmail,"@"))
- end,
- match_name(Fun, Name, PermittedName, Rest);
-
-match_name(dNSName, Name, [PermittedName | Rest]) ->
- Fun = fun(Domain, [$.|Domain]) -> true;
- (Name1,Name2) ->
- lists:suffix(string:to_lower(Name2),
- string:to_lower(Name1))
- end,
- match_name(Fun, Name, [$.|PermittedName], Rest);
-
-match_name(x400Address, OrAddress, [PermittedAddr | Rest]) ->
- match_name(fun is_or_address/2, OrAddress, PermittedAddr, Rest);
-
-match_name(ipAdress, IP, [PermittedIP | Rest]) ->
- Fun = fun([IP1, IP2, IP3, IP4],
- [IP5, IP6, IP7, IP8, M1, M2, M3, M4]) ->
- is_permitted_ip([IP1, IP2, IP3, IP4],
- [IP5, IP6, IP7, IP8],
- [M1, M2, M3, M4]);
- ([IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8,
- IP9, IP10, IP11, IP12, IP13, IP14, IP15, IP16],
- [IP17, IP18, IP19, IP20, IP21, IP22, IP23, IP24,
- IP25, IP26, IP27, IP28, IP29, IP30, IP31, IP32,
- M1, M2, M3, M4, M5, M6, M7, M8,
- M9, M10, M11, M12, M13, M14, M15, M16]) ->
- is_permitted_ip([IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8,
- IP9, IP10, IP11, IP12, IP13,
- IP14, IP15, IP16],
- [IP17, IP18, IP19, IP20, IP21, IP22, IP23,
- IP24,IP25, IP26, IP27, IP28, IP29, IP30,
- IP31, IP32],
- [M1, M2, M3, M4, M5, M6, M7, M8, M9, M10,
- M11, M12, M13, M14, M15, M16]);
- (_,_) ->
- false
- end,
- match_name(Fun, IP, PermittedIP, Rest).
-
-match_name(Fun, Name, PermittedName, []) ->
- Fun(Name, PermittedName);
-match_name(Fun, Name, PermittedName, [Head | Tail]) ->
- case Fun(Name, PermittedName) of
- true ->
- true;
- false ->
- match_name(Fun, Name, Head, Tail)
- end.
-
is_permitted_ip([], [], []) ->
true;
is_permitted_ip([CandidatIp | CandidatIpRest],
@@ -1037,3 +1037,25 @@ is_dh(?'dhpublicnumber')->
true;
is_dh(_) ->
false.
+
+is_digitally_sign_cert(OtpCert) ->
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
+ case pubkey_cert:select_extension(?'id-ce-keyUsage', Extensions) of
+ undefined ->
+ false;
+ #'Extension'{extnValue = KeyUse} ->
+ lists:member(keyCertSign, KeyUse)
+ end.
+
+missing_basic_constraints(OtpCert, SelfSigned, ValidationState, VerifyFun, UserState0,Len) ->
+ UserState = verify_fun(OtpCert, {bad_cert, missing_basic_constraint},
+ UserState0, VerifyFun),
+ case SelfSigned of
+ true ->
+ {ValidationState, UserState};
+ false ->
+ {ValidationState#path_validation_state{max_path_length =
+ Len - 1},
+ UserState}
+ end.
diff --git a/lib/public_key/src/pubkey_crl.erl b/lib/public_key/src/pubkey_crl.erl
new file mode 100644
index 0000000000..3e4c3c8b6d
--- /dev/null
+++ b/lib/public_key/src/pubkey_crl.erl
@@ -0,0 +1,701 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2012. 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
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(pubkey_crl).
+
+-include("public_key.hrl").
+
+-export([validate/7, init_revokation_state/0, fresh_crl/3, verify_crl_signature/4,
+ is_delta_crl/1, combines/2, match_one/2]).
+
+-record(userstate, {dpcrls,
+ idp
+ }).
+
+validate(OtpCert, OtherDPCRLs, DP, {DerCRL, CRL}, {DerDeltaCRL, DeltaCRL},
+ Options, RevokedState0) ->
+ RevokedState =
+ case verify_crl(OtpCert, DP, CRL, DerCRL, DeltaCRL,
+ DerDeltaCRL, OtherDPCRLs, Options, RevokedState0) of
+ {valid, Revoked, DeltaRevoked, RevokedState1, IDP} ->
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ SerialNumber = TBSCert#'OTPTBSCertificate'.serialNumber,
+ CertIssuer = TBSCert#'OTPTBSCertificate'.issuer,
+ TBSCRL = CRL#'CertificateList'.tbsCertList,
+ CRLIssuer = TBSCRL#'TBSCertList'.issuer,
+ AltNames = subject_alt_names(TBSCert#'OTPTBSCertificate'.extensions),
+ revoked_status(DP, IDP, {directoryName, CRLIssuer},
+ [ {directoryName, CertIssuer} | AltNames], SerialNumber, Revoked,
+ DeltaRevoked, RevokedState1);
+ {invalid, Revoked} ->
+ Revoked
+ end,
+ crl_status(RevokedState).
+
+init_revokation_state() ->
+ #revoke_state{reasons_mask = sets:new(),
+ interim_reasons_mask = sets:new(),
+ cert_status = unrevoked}.
+
+fresh_crl(_, {undefined, undefined}, _) ->
+ %% Typically happens when there is no delta CRL that covers a CRL
+ no_fresh_crl;
+
+fresh_crl(DP, {_, #'CertificateList'{tbsCertList = TBSCRL}} = CRL, CallBack) ->
+ Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ UpdateTime =
+ pubkey_cert:time_str_2_gregorian_sec(TBSCRL#'TBSCertList'.nextUpdate),
+ case Now >= UpdateTime of
+ true ->
+ case CallBack(DP, CRL) of
+ CRL ->
+ no_fresh_crl;
+ NewCRL ->
+ fresh_crl(DP, NewCRL, CallBack)
+ end;
+ false ->
+ {fresh, CRL}
+ end.
+
+is_delta_crl(#'CertificateList'{tbsCertList = TBSCRL}) ->
+ Extensions = TBSCRL#'TBSCertList'.crlExtensions,
+ case pubkey_cert:select_extension(?'id-ce-deltaCRLIndicator',
+ Extensions) of
+ undefined ->
+ false;
+ _ ->
+ true
+ end.
+
+combines(CRL, DeltaCRL) ->
+ check_crl_num(CRL, DeltaCRL) andalso
+ check_delta_issuer_and_scope(CRL, DeltaCRL).
+
+crl_status(State)->
+ %% Fun argument is to enable future implementation of CRL checking
+ %% that does not care about all possible reasons.
+ crl_status(State, fun all_reasons/0).
+
+crl_status({skip, #revoke_state{cert_status = Status} = RevokedState}, _) ->
+ {undetermined, status(Status), RevokedState};
+
+crl_status(#revoke_state{cert_status = unrevoked = Status,
+ valid_ext = false} = RevokedState, _) ->
+ {undetermined, Status, RevokedState};
+
+crl_status(#revoke_state{cert_status = Status,
+ valid_ext = false}, _) ->
+ {finished, status(Status)};
+
+crl_status(#revoke_state{reasons_mask = Mask,
+ cert_status = Status,
+ valid_ext = true} = RevokedState, Fun) ->
+ case is_all_reasons(Mask, Fun) of
+ true ->
+ {finished, status(Status)};
+ false when (Status == unrevoked) ->
+ {undetermined, Status, RevokedState};
+ _ ->
+ {finished ,status(Status)}
+ end.
+
+verify_crl(OtpCert, DP, CRL, DerCRL, DeltaCRL, DerDeltaCRL, OtherDPCRLs,
+ Options, State0) ->
+ #'CertificateList'{tbsCertList =
+ #'TBSCertList'{crlExtensions = Extensions,
+ revokedCertificates = TmpRevoked}
+ } = CRL,
+ Revoked = revoked(TmpRevoked),
+ IDP = issuing_distribution_point(Extensions),
+
+ DeltaRevoked = delta_revoked(DeltaCRL),
+
+ ValidExt = verify_extensions(Extensions) and
+ verify_extensions(Revoked),
+
+ IntMask = compute_interim_reasons_mask(DP, IDP),
+
+ RevokedState =
+ State0#revoke_state{interim_reasons_mask = IntMask,
+ valid_ext = ValidExt},
+
+ {Fun, AdditionalArgs} = IssuerFun = proplists:get_value(issuer_fun, Options),
+
+ try verify_issuer_and_scope(OtpCert, DP, IDP, CRL) of
+ {ok, Issuer} ->
+ case Fun(DP, CRL, Issuer, AdditionalArgs) of
+ {ok, TrustedOtpCert, Path} ->
+ verify_mask_and_signatures(Revoked, DeltaRevoked,
+ RevokedState,
+ CRL, DerCRL, DeltaCRL, DerDeltaCRL,
+ IssuerFun, TrustedOtpCert, Path, OtherDPCRLs, IDP);
+ _ ->
+ {invalid, State0#revoke_state{valid_ext = ValidExt}}
+ end;
+ {error, issuer_not_found} ->
+ case Fun(DP, CRL, issuer_not_found, AdditionalArgs) of
+ {ok, TrustedOtpCert, Path} ->
+ verify_mask_and_signatures(Revoked, DeltaRevoked,
+ RevokedState, CRL, DerCRL, DeltaCRL,
+ DerDeltaCRL, IssuerFun,
+ TrustedOtpCert, Path, OtherDPCRLs, IDP);
+ _ ->
+ {invalid, {skip, State0}}
+ end
+ catch
+ throw:{bad_crl, invalid_issuer} ->
+ {invalid, {skip, State0}};
+ throw:_ ->
+ {invalid, State0#revoke_state{valid_ext = ValidExt}}
+ end.
+
+verify_mask_and_signatures(Revoked, DeltaRevoked, RevokedState, CRL, DerCRL, DeltaCRL, DerDeltaCRL,
+ IssuerFun, TrustedOtpCert, Path, OtherDPCRLs, IDP) ->
+
+ ReasonsMask = sets:union(RevokedState#revoke_state.reasons_mask,
+ RevokedState#revoke_state.interim_reasons_mask),
+ try
+ verify_interim_reasons_mask(RevokedState),
+ true = verify_crl_signatures(CRL, DerCRL, DeltaCRL, DerDeltaCRL,
+ TrustedOtpCert, Path, IssuerFun, OtherDPCRLs, IDP),
+ {valid, Revoked, DeltaRevoked, RevokedState#revoke_state{reasons_mask = ReasonsMask}, IDP}
+ catch
+ throw:_ ->
+ {invalid, RevokedState};
+ error:{badmatch, _} ->
+ {invalid, RevokedState}
+ end.
+
+
+verify_crl_signatures(CRL, DerCRL, DeltaCRL, DerDeltaCRL, TrustedOtpCert, Path,
+ IssuerFun, OtherDPCRLs, IDP) ->
+ try
+ VerifyFunAndState =
+ {fun(_, {bad_cert, _} = Reason, _UserState) ->
+ {fail, Reason};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_Cert, valid, UserState) ->
+ {valid, UserState};
+ (Cert, valid_peer, UserState) ->
+ case verify_crl_keybit(Cert, cRLSign) of
+ true ->
+ handle_crlsigner(Cert, IssuerFun, UserState);
+ false ->
+ {fail, crl_sign_bit_not_set}
+ end
+ end, #userstate{dpcrls = OtherDPCRLs, idp = IDP}},
+
+ {ok, {{_,Key, KeyParams},_}} =
+ public_key:pkix_path_validation(TrustedOtpCert, Path,
+ [{verify_fun, VerifyFunAndState}]),
+ true = verify_crl_signature(CRL, DerCRL, Key, KeyParams),
+ true = verify_crl_signature(DeltaCRL, DerDeltaCRL, Key, KeyParams)
+ catch
+ error:{badmatch, _} ->
+ false
+ end.
+
+handle_crlsigner(OtpCert, IssuerFun, #userstate{idp = IDP} = UserState) ->
+ case verify_crl_keybit(OtpCert, keyCertSign) of
+ true ->
+ {valid, UserState};
+ false ->
+ case not is_indirect_crl(IDP) andalso not public_key:pkix_is_self_signed(OtpCert) of
+ true ->
+ validate_crl_signing_cert(OtpCert, IssuerFun, UserState);
+ false ->
+ {valid, UserState}
+ end
+ end.
+
+validate_crl_signing_cert(_, _,#userstate{dpcrls = []} = UserState) ->
+ {valid, UserState};
+validate_crl_signing_cert(OtpCert, IssuerFun, #userstate{dpcrls = CRLInfo} = UserState) ->
+ case public_key:pkix_crls_validate(OtpCert, CRLInfo, [{issuer_fun, IssuerFun}]) of
+ valid ->
+ {valid, UserState};
+ Reason ->
+ {fail, Reason}
+ end.
+
+delta_revoked(undefined)->
+ [];
+delta_revoked(#'CertificateList'{tbsCertList =
+ #'TBSCertList'{revokedCertificates
+ = DeltaRevoked}}) ->
+ revoked(DeltaRevoked).
+
+revoked(asn1_NOVALUE) ->
+ [];
+revoked(Revoked) ->
+ Revoked.
+
+revoked_status(DP, IDP, CRLIssuer, Names, SerialNumber, Revoked, DeltaRevoked, RevokedState0) ->
+ DefaultIssuer0 = default_issuer(CRLIssuer, DeltaRevoked),
+ RevokedState1 = check_revoked(DP, IDP, DefaultIssuer0, Names, SerialNumber, DeltaRevoked, RevokedState0),
+ RevokedState = case RevokedState1#revoke_state.cert_status of
+ unrevoked when RevokedState1#revoke_state.cert_status =/= removeFromCRL ->
+ DefaultIssuer = default_issuer(CRLIssuer, Revoked),
+ check_revoked(DP, IDP, DefaultIssuer, Names, SerialNumber,
+ Revoked, RevokedState1);
+ _ ->
+ RevokedState1
+ end,
+ case RevokedState#revoke_state.cert_status of
+ removeFromCRL ->
+ RevokedState#revoke_state{cert_status = unrevoked};
+ _ ->
+ RevokedState
+ end.
+
+default_issuer(_, []) ->
+ undefined;
+default_issuer(Default, [#'TBSCertList_revokedCertificates_SEQOF'{crlEntryExtensions = Extensions}| _]) ->
+ case extension_value(?'id-ce-certificateIssuer', 'GeneralNames', Extensions) of
+ undefined ->
+ [pubkey_cert_records:transform(Default, decode)];
+ GeneralNames ->
+ gen_names(GeneralNames)
+ end.
+
+is_all_reasons(Mask, AllReasonsFun) ->
+ AllReasons = AllReasonsFun(),
+ case sets:is_subset(AllReasons, Mask) of
+ true ->
+ true;
+ false ->
+ %% As the "uspecified" reason should not
+ %% be explicitly used according to RFC 3280
+ %% and the conformance tests have test cases
+ %% that should succed, and that does not specify
+ %% "unspecified", we tolorate that it is not included.
+ sets:is_subset(sets:del_element(unspecified, AllReasons), Mask)
+ end.
+
+all_reasons() ->
+ sets:from_list([unspecified, keyCompromise,
+ cACompromise, affiliationChanged, superseded,
+ cessationOfOperation, certificateHold,
+ privilegeWithdrawn, aACompromise]).
+
+verify_issuer_and_scope(#'OTPCertificate'{tbsCertificate = TBSCert} = Cert,
+ #'DistributionPoint'{cRLIssuer = DPIssuer} = DP, IDP,
+ #'CertificateList'{tbsCertList = TBSCRL} = CRL)
+ when DPIssuer =/= asn1_NOVALUE ->
+ CRLIssuer = pubkey_cert_records:transform(TBSCRL#'TBSCertList'.issuer, decode),
+ Issuer = dp_crlissuer_to_issuer(DPIssuer),
+ case pubkey_cert:is_issuer(Issuer, CRLIssuer) and is_indirect_crl(IDP) of
+ true ->
+ verify_scope(Cert, DP, IDP),
+ issuer_id(Cert, CRL);
+ false ->
+ %% otherwise verify that the CRL issuer matches the certificate issuer
+ verify_issuer_and_scope(Cert, DP#'DistributionPoint'{
+ distributionPoint = [TBSCert#'OTPTBSCertificate'.issuer],
+ cRLIssuer = asn1_NOVALUE},
+ IDP, CRL)
+ end;
+verify_issuer_and_scope(#'OTPCertificate'{tbsCertificate = TBSCert}= Cert,
+ DP, IDP,
+ #'CertificateList'{tbsCertList = TBSCRL}) ->
+ CRLIssuer = pubkey_cert_records:transform(TBSCRL#'TBSCertList'.issuer, decode),
+ CertIssuer = TBSCert#'OTPTBSCertificate'.issuer,
+ case pubkey_cert:is_issuer(CertIssuer, CRLIssuer) of
+ true ->
+ verify_scope(Cert, DP, IDP),
+ issuer_id(Cert);
+ false ->
+ throw({bad_crl, invalid_issuer})
+ end.
+
+dp_crlissuer_to_issuer(DPCRLIssuer) ->
+ [{directoryName, Issuer}] = pubkey_cert_records:transform(DPCRLIssuer, decode),
+ Issuer.
+
+is_indirect_crl(#'IssuingDistributionPoint'{indirectCRL = Value})->
+ Value;
+is_indirect_crl(_) ->
+ false.
+
+verify_scope(_,_, undefined) ->
+ ok;
+verify_scope(#'OTPCertificate'{tbsCertificate = TBSCert}, #'DistributionPoint'{cRLIssuer = DPIssuer} = DP, IDP) ->
+ CertIssuer = TBSCert#'OTPTBSCertificate'.issuer,
+ Names = case gen_names(DPIssuer) of
+ [{directoryName, TNames}] ->
+ TNames;
+ Other ->
+ Other
+ end,
+ DPName = dp_names(DP#'DistributionPoint'.distributionPoint, Names, CertIssuer),
+ IDPName = dp_names(IDP#'IssuingDistributionPoint'.distributionPoint, Names, CertIssuer),
+ verify_scope(DPName, IDPName, Names, TBSCert, IDP).
+
+verify_scope(asn1_NOVALUE, _, asn1_NOVALUE, _, _) ->
+ throw({bad_crl, scope_error1});
+verify_scope(asn1_NOVALUE, IDPName, DPIssuerNames, TBSCert, IDP) ->
+ verify_dp_name(IDPName, DPIssuerNames),
+ verify_dp_bools(TBSCert, IDP);
+
+verify_scope(DPName, IDPName, _, TBSCert, IDP) ->
+ verify_dp_name(IDPName, DPName),
+ verify_dp_bools(TBSCert, IDP).
+
+dp_names(asn1_NOVALUE, _, _) ->
+ asn1_NOVALUE;
+dp_names({fullName, Name}, _, _) ->
+ gen_names(Name);
+dp_names({nameRelativeToCRLIssuer, Fragment}, asn1_NOVALUE, {rdnSequence, RelativeDestinguistNames}) ->
+ [{directoryName, {rdnSequence, RelativeDestinguistNames ++
+ [lists:map(fun(AttrAndValue) ->
+ pubkey_cert_records:transform(AttrAndValue, decode)
+ end, Fragment)]}}];
+dp_names({nameRelativeToCRLIssuer, Fragment},{rdnSequence, RelativeDestinguistNames}, _) ->
+ [{directoryName, {rdnSequence, RelativeDestinguistNames ++
+ [lists:map(fun(AttrAndValue) ->
+ pubkey_cert_records:transform(AttrAndValue, decode)
+ end, Fragment)]}}];
+dp_names([{rdnSequence, _}] = Name0, _,_) ->
+ [Name] = pubkey_cert_records:transform(Name0, decode),
+ [{directoryName, Name}].
+
+gen_names(asn1_NOVALUE) ->
+ asn1_NOVALUE;
+gen_names([]) ->
+ [];
+gen_names([{NameType, Name} | Rest]) ->
+ [ {NameType, pubkey_cert_records:transform(Name, decode)} | gen_names(Rest)].
+
+verify_dp_name(asn1_NOVALUE, _) ->
+ ok;
+
+verify_dp_name(IDPNames, DPorIssuerNames) ->
+ case match_one(DPorIssuerNames, IDPNames) of
+ true ->
+ ok;
+ false ->
+ throw({bad_crl, scope_error})
+ end.
+
+match_one([], _) ->
+ false;
+match_one([{Type, Name} | Names], CandidateNames) ->
+ Candidates = [NameName || {NameType, NameName} <- CandidateNames, NameType == Type],
+ case Candidates of
+ [] ->
+ false;
+ [_|_] -> case pubkey_cert:match_name(Type, Name, Candidates) of
+ true ->
+ true;
+ false ->
+ match_one(Names, CandidateNames)
+ end
+ end.
+
+verify_dp_bools(TBSCert, IDP) ->
+ BasicConstraints =
+ pubkey_cert:select_extension(?'id-ce-basicConstraints',
+ TBSCert#'OTPTBSCertificate'.extensions),
+
+ case verify_onlyContainsUserCerts(BasicConstraints, IDP) andalso
+ verify_onlyContainsCACerts(BasicConstraints, IDP) andalso
+ verify_onlyContainsAttributeCerts(IDP) of
+ true ->
+ ok;
+ _ ->
+ throw({bad_crl, scope_error})
+ end.
+
+verify_onlyContainsUserCerts(
+ #'Extension'{extnValue = #'BasicConstraints'{cA = true}},
+ #'IssuingDistributionPoint'{onlyContainsUserCerts = true}) ->
+ false;
+verify_onlyContainsUserCerts(_,_) ->
+ true.
+
+verify_onlyContainsCACerts(
+ #'Extension'{extnValue = #'BasicConstraints'{cA = true}},
+ #'IssuingDistributionPoint'{onlyContainsCACerts = true}) ->
+ true;
+verify_onlyContainsCACerts(_,#'IssuingDistributionPoint'{onlyContainsCACerts = true}) ->
+ false;
+verify_onlyContainsCACerts(_,_) ->
+ true.
+
+verify_onlyContainsAttributeCerts(
+ #'IssuingDistributionPoint'{onlyContainsAttributeCerts = Bool}) ->
+ not Bool.
+
+check_crl_num(#'CertificateList'{tbsCertList = TBSCRL},
+ #'CertificateList'{tbsCertList = TBSDeltaCRL})->
+ Extensions = TBSCRL#'TBSCertList'.crlExtensions,
+ DeltaExtensions = TBSDeltaCRL#'TBSCertList'.crlExtensions,
+
+ try
+ CRLNum = assert_extension_value(?'id-ce-cRLNumber', 'CRLNumber', Extensions),
+ DeltaBaseNum = assert_extension_value(?'id-ce-deltaCRLIndicator',
+ 'CRLNumber', DeltaExtensions),
+ DeltaCRLNum = assert_extension_value(?'id-ce-cRLNumber', 'CRLNumber', DeltaExtensions),
+ (CRLNum >= DeltaBaseNum) andalso (CRLNum < DeltaCRLNum)
+ catch
+ throw:no_extension_present ->
+ false
+ end;
+check_crl_num(_,_) ->
+ false.
+
+
+extension_value(Extension, ExtType, Extensions) ->
+ case pubkey_cert:select_extension(Extension, Extensions) of
+ #'Extension'{extnValue = Value} ->
+ public_key:der_decode(ExtType, list_to_binary(Value));
+ _ ->
+ undefined
+ end.
+
+
+assert_extension_value(Extension, ExtType, Extensions) ->
+ case extension_value(Extension, ExtType, Extensions) of
+ undefined ->
+ throw(no_extension_present);
+ Value ->
+ Value
+ end.
+
+check_delta_issuer_and_scope(_, undefined) ->
+ true;
+check_delta_issuer_and_scope(#'CertificateList'{tbsCertList = TBSCRL},
+ #'CertificateList'{tbsCertList = TBSDeltaCRL}) ->
+ case pubkey_cert:is_issuer(TBSCRL#'TBSCertList'.issuer,
+ TBSDeltaCRL#'TBSCertList'.issuer) of
+ true ->
+ check_delta_scope(TBSCRL, TBSDeltaCRL);
+ false ->
+ false
+ end.
+
+check_delta_scope(#'TBSCertList'{crlExtensions = Extensions},
+ #'TBSCertList'{crlExtensions = DeltaExtensions})->
+ IDP = issuing_distribution_point(Extensions),
+ DeltaIDP = issuing_distribution_point(DeltaExtensions),
+
+ AuthKey = authority_key_identifier(Extensions),
+ DeltaAuthKey = authority_key_identifier(DeltaExtensions),
+ is_match(IDP, DeltaIDP) andalso is_match(AuthKey, DeltaAuthKey).
+
+is_match(X, X) ->
+ true;
+is_match(_,_) ->
+ false.
+
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = asn1_NOVALUE},
+ #'IssuingDistributionPoint'{onlySomeReasons =
+ asn1_NOVALUE}) ->
+ all_reasons();
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = asn1_NOVALUE},
+ undefined) ->
+ all_reasons();
+
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = asn1_NOVALUE},
+ #'IssuingDistributionPoint'{onlySomeReasons =
+ IDPReasons}) ->
+ sets:from_list(IDPReasons);
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = DPReasons},
+ #'IssuingDistributionPoint'{onlySomeReasons =
+ asn1_NOVALUE}) ->
+ sets:from_list(DPReasons);
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = DPReasons},
+ undefined) ->
+ sets:from_list(DPReasons);
+compute_interim_reasons_mask(#'DistributionPoint'{reasons = DPReasons},
+ #'IssuingDistributionPoint'{onlySomeReasons =
+ IDPReasons}) ->
+ sets:intersection(sets:from_list(DPReasons), sets:from_list(IDPReasons)).
+
+verify_interim_reasons_mask(#revoke_state{reasons_mask = Mask,
+ interim_reasons_mask = IntMask}) ->
+ case sets:fold(fun(Element, Acc) ->
+ case sets:is_element(Element, Mask) of
+ true ->
+ Acc;
+ false ->
+ true
+ end
+ end, false, IntMask) of
+ true ->
+ ok;
+ false ->
+ throw({bad_crl, mask_error})
+ end.
+
+verify_crl_signature(undefined, undefined, _,_) ->
+ true;
+verify_crl_signature(CRL, DerCRL, Key, KeyParams) ->
+ {DigestType, PlainText, Signature} = extract_crl_verify_data(CRL, DerCRL),
+ case Key of
+ #'RSAPublicKey'{} ->
+ public_key:verify(PlainText, DigestType, Signature, Key);
+ _ ->
+ public_key:verify(PlainText, DigestType, Signature,
+ {Key, KeyParams})
+ end.
+extract_crl_verify_data(CRL, DerCRL) ->
+ {0, Signature} = CRL#'CertificateList'.signature,
+ #'AlgorithmIdentifier'{algorithm = SigAlg} =
+ CRL#'CertificateList'.signatureAlgorithm,
+ PlainText = encoded_tbs_crl(DerCRL),
+ DigestType = pubkey_cert:digest_type(SigAlg),
+ {DigestType, PlainText, Signature}.
+
+encoded_tbs_crl(CRL) ->
+ {ok, PKIXCRL} =
+ 'OTP-PUB-KEY':decode_TBSCertList_exclusive(CRL),
+ {'CertificateList',
+ {'CertificateList_tbsCertList', EncodedTBSCertList}, _, _} = PKIXCRL,
+ EncodedTBSCertList.
+
+check_revoked(_,_,_,_,_,[], State) ->
+ State;
+check_revoked(#'DistributionPoint'{cRLIssuer = DPIssuer} = DP, IDP, DefaultIssuer0, Names, SerialNr,
+ [#'TBSCertList_revokedCertificates_SEQOF'{userCertificate =
+ SerialNr,
+ crlEntryExtensions =
+ Extensions}| Rest],
+ State) ->
+ Reason = revoked_reason(Extensions),
+ case (DPIssuer =/= asn1_NOVALUE) and is_indirect_crl(IDP) of
+ true ->
+ handle_indirect_crl_check(DP, IDP, DefaultIssuer0, Names, SerialNr, Extensions, Reason, Rest, State);
+ false ->
+ State#revoke_state{cert_status = Reason}
+ end;
+
+check_revoked(DP, IDP, DefaultIssuer0, Names, SerialNr,
+ [#'TBSCertList_revokedCertificates_SEQOF'{crlEntryExtensions =
+ Extensions}| Rest], State) ->
+ DefaultIssuer = case extension_value(?'id-ce-certificateIssuer', 'GeneralNames', Extensions) of
+ undefined ->
+ DefaultIssuer0;
+ GeneralNames ->
+ gen_names(GeneralNames)
+ end,
+ check_revoked(DP, IDP, DefaultIssuer, Names, SerialNr, Rest, State).
+
+handle_indirect_crl_check(DP, IDP, DefaultIssuer0, Names, SerialNr, Extensions, Reason, Rest, State) ->
+ case check_crl_issuer_extension(Names, Extensions, DefaultIssuer0) of
+ {true, _} ->
+ State#revoke_state{cert_status = Reason};
+ {false, DefaultIssuer} ->
+ check_revoked(DP, IDP, DefaultIssuer, Names, SerialNr, Rest, State)
+ end.
+
+check_crl_issuer_extension(Names, Extensions, Default0) ->
+ case extension_value(?'id-ce-certificateIssuer', 'GeneralNames', Extensions) of
+ undefined ->
+ {match_one(Default0, Names), Default0};
+ GeneralNames ->
+ Default = gen_names(GeneralNames),
+ {match_one(Default, Names), Default}
+ end.
+
+revoked_reason(Extensions) ->
+ case extension_value(?'id-ce-cRLReasons', 'CRLReason', Extensions) of
+ undefined ->
+ unspecified;
+ Value ->
+ Value
+ end.
+
+verify_crl_keybit(#'OTPCertificate'{tbsCertificate = TBS}, Bit) ->
+ case pubkey_cert:select_extension( ?'id-ce-keyUsage',
+ TBS#'OTPTBSCertificate'.extensions) of
+ #'Extension'{extnID = ?'id-ce-keyUsage',
+ extnValue = KeyUse} ->
+ lists:member(Bit, KeyUse);
+ _ ->
+ true
+ end.
+
+issuer_id(Cert, #'CertificateList'{tbsCertList = TBSCRL}) ->
+ Extensions =
+ pubkey_cert:extensions_list(TBSCRL#'TBSCertList'.crlExtensions),
+ case authority_key_identifier(Extensions) of
+ undefined ->
+ issuer_id(Cert);
+ #'AuthorityKeyIdentifier'{authorityCertIssuer = asn1_NOVALUE,
+ authorityCertSerialNumber = asn1_NOVALUE} ->
+ issuer_id(Cert);
+ #'AuthorityKeyIdentifier'{authorityCertIssuer = Issuer,
+ authorityCertSerialNumber = Nr} ->
+ {ok, {Nr, Issuer}}
+ end.
+
+issuer_id(#'OTPCertificate'{} = Cert) ->
+ case public_key:pkix_is_self_signed(Cert) of
+ true ->
+ public_key:pkix_issuer_id(Cert, self);
+ false ->
+ public_key:pkix_issuer_id(Cert, other)
+ end.
+
+status(unrevoked) ->
+ unrevoked;
+status(Reason) ->
+ {revoked, Reason}.
+
+verify_extensions([#'TBSCertList_revokedCertificates_SEQOF'{crlEntryExtensions = Ext} | Rest]) ->
+ verify_extensions(pubkey_cert:extensions_list(Ext)) and verify_extensions(Rest);
+verify_extensions([]) ->
+ true;
+verify_extensions([#'Extension'{critical = true, extnID = Id} | Rest]) ->
+ case lists:member(Id, [?'id-ce-authorityKeyIdentifier',
+ ?'id-ce-issuerAltName',
+ ?'id-ce-cRLNumber',
+ ?'id-ce-certificateIssuer',
+ ?'id-ce-deltaCRLIndicator',
+ ?'id-ce-issuingDistributionPoint',
+ ?'id-ce-freshestCRL']) of
+ true ->
+ verify_extensions(Rest);
+ false ->
+ false
+ end;
+verify_extensions([_Ext | Rest]) ->
+ verify_extensions(Rest).
+
+issuing_distribution_point(Extensions) ->
+ Enc = extension_value(?'id-ce-issuingDistributionPoint',
+ 'IssuingDistributionPoint', Extensions),
+ pubkey_cert_records:transform(Enc, decode).
+
+authority_key_identifier(Extensions) ->
+ Enc = extension_value(?'id-ce-authorityKeyIdentifier',
+ 'AuthorityKeyIdentifier', Extensions),
+ pubkey_cert_records:transform(Enc, decode).
+
+subject_alt_names(Extensions) ->
+ Enc = extension_value(?'id-ce-subjectAltName',
+ 'GeneralNames', Extensions),
+ case Enc of
+ undefined ->
+ [];
+ _ ->
+ pubkey_cert_records:transform(Enc, decode)
+ end.
diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl
index 4012825f20..6bdc35fb79 100644
--- a/lib/public_key/src/pubkey_pem.erl
+++ b/lib/public_key/src/pubkey_pem.erl
@@ -167,6 +167,8 @@ split_lines(Bin) ->
%% Ignore white space at end of line
join_entry([<<"-----END ", _/binary>>| Lines], Entry) ->
{lists:reverse(Entry), Lines};
+join_entry([<<"-----END X509 CRL-----", _/binary>>| Lines], Entry) ->
+ {lists:reverse(Entry), Lines};
join_entry([Line | Lines], Entry) ->
join_entry(Lines, [Line | Entry]).
@@ -198,7 +200,9 @@ pem_start('DHParameter') ->
pem_start('CertificationRequest') ->
<<"-----BEGIN CERTIFICATE REQUEST-----">>;
pem_start('ContentInfo') ->
- <<"-----BEGIN PKCS7-----">>.
+ <<"-----BEGIN PKCS7-----">>;
+pem_start('CertificateList') ->
+ <<"-----BEGIN X509 CRL-----">>.
pem_end(<<"-----BEGIN CERTIFICATE-----">>) ->
<<"-----END CERTIFICATE-----">>;
@@ -220,6 +224,8 @@ pem_end(<<"-----BEGIN CERTIFICATE REQUEST-----">>) ->
<<"-----END CERTIFICATE REQUEST-----">>;
pem_end(<<"-----BEGIN PKCS7-----">>) ->
<<"-----END PKCS7-----">>;
+pem_end(<<"-----BEGIN X509 CRL-----">>) ->
+ <<"-----END X509 CRL-----">>;
pem_end(_) ->
undefined.
@@ -242,7 +248,9 @@ asn1_type(<<"-----BEGIN ENCRYPTED PRIVATE KEY-----">>) ->
asn1_type(<<"-----BEGIN CERTIFICATE REQUEST-----">>) ->
'CertificationRequest';
asn1_type(<<"-----BEGIN PKCS7-----">>) ->
- 'ContentInfo'.
+ 'ContentInfo';
+asn1_type(<<"-----BEGIN X509 CRL-----">>) ->
+ 'CertificateList'.
pem_decrypt() ->
<<"Proc-Type: 4,ENCRYPTED">>.
diff --git a/lib/public_key/src/public_key.app.src b/lib/public_key/src/public_key.app.src
index 4cc81ea573..9f0677606d 100644
--- a/lib/public_key/src/public_key.app.src
+++ b/lib/public_key/src/public_key.app.src
@@ -7,6 +7,7 @@
pubkey_ssh,
pubkey_cert,
pubkey_cert_records,
+ pubkey_crl,
'OTP-PUB-KEY',
'PKCS-FRAME'
]},
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index d5df53e848..5686920dd4 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -42,7 +42,8 @@
pkix_issuer_id/2,
pkix_normalize_name/1,
pkix_path_validation/3,
- ssh_decode/2, ssh_encode/2
+ ssh_decode/2, ssh_encode/2,
+ pkix_crls_validate/3
]).
-type rsa_padding() :: 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding'
@@ -50,6 +51,8 @@
-type public_crypt_options() :: [{rsa_pad, rsa_padding()}].
-type rsa_digest_type() :: 'md5' | 'sha'| 'sha224' | 'sha256' | 'sha384' | 'sha512'.
-type dss_digest_type() :: 'none' | 'sha'. %% None is for backwards compatibility
+-type crl_reason() :: unspecified | keyCompromise | cACompromise | affiliationChanged | superseded
+ | cessationOfOperation | certificateHold | privilegeWithdrawn | aACompromise.
-define(UINT32(X), X:32/unsigned-big-integer).
-define(DER_NULL, <<5, 0>>).
@@ -428,9 +431,9 @@ pkix_verify(DerCert, #'RSAPublicKey'{} = RSAKey)
verify(PlainText, DigestType, Signature, RSAKey).
%%--------------------------------------------------------------------
--spec pkix_is_issuer(Cert::binary()| #'OTPCertificate'{},
- IssuerCert::binary()|
- #'OTPCertificate'{}) -> boolean().
+-spec pkix_is_issuer(Cert :: der_encoded()| #'OTPCertificate'{} | #'CertificateList'{},
+ IssuerCert :: der_encoded()|
+ #'OTPCertificate'{}) -> boolean().
%%
%% Description: Checks if <IssuerCert> issued <Cert>.
%%--------------------------------------------------------------------
@@ -443,7 +446,11 @@ pkix_is_issuer(Cert, IssuerCert) when is_binary(IssuerCert) ->
pkix_is_issuer(#'OTPCertificate'{tbsCertificate = TBSCert},
#'OTPCertificate'{tbsCertificate = Candidate}) ->
pubkey_cert:is_issuer(TBSCert#'OTPTBSCertificate'.issuer,
- Candidate#'OTPTBSCertificate'.subject).
+ Candidate#'OTPTBSCertificate'.subject);
+pkix_is_issuer(#'CertificateList'{tbsCertList = TBSCRL},
+ #'OTPCertificate'{tbsCertificate = Candidate}) ->
+ pubkey_cert:is_issuer(Candidate#'OTPTBSCertificate'.subject,
+ pubkey_cert_records:transform(TBSCRL#'TBSCertList'.issuer, decode)).
%%--------------------------------------------------------------------
-spec pkix_is_self_signed(Cert::binary()| #'OTPCertificate'{}) -> boolean().
@@ -502,7 +509,7 @@ pkix_normalize_name(Issuer) ->
%%--------------------------------------------------------------------
-spec pkix_path_validation(Cert::binary()| #'OTPCertificate'{} | atom(),
CertChain :: [binary()] ,
- Options :: list()) ->
+ Options :: proplist:proplist()) ->
{ok, {PublicKeyInfo :: term(),
PolicyTree :: term()}} |
{error, {bad_cert, Reason :: term()}}.
@@ -511,7 +518,7 @@ pkix_normalize_name(Issuer) ->
pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)->
{VerifyFun, Userstat0} =
proplists:get_value(verify_fun, Options0, ?DEFAULT_VERIFYFUN),
- Otpcert = pkix_decode_cert(Cert, otp),
+ Otpcert = otp_cert(Cert),
Reason = {bad_cert, PathErr},
try VerifyFun(Otpcert, Reason, Userstat0) of
{valid, Userstate} ->
@@ -537,6 +544,27 @@ pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options)
Options),
path_validation(CertChain, ValidationState).
+%--------------------------------------------------------------------
+-spec pkix_crls_validate(#'OTPCertificate'{},
+ [{DP::#'DistributionPoint'{} ,CRL::#'CertificateList'{}}],
+ Options :: proplist:proplist()) -> valid | {bad_cert, revocation_status_undetermined}
+ | {bad_cert, {revoked, crl_reason()}}.
+
+%% Description: Performs a basic path validation according to RFC 5280.
+%%--------------------------------------------------------------------
+pkix_crls_validate(OtpCert, [{_,_,_} |_] = DPAndCRLs, Options) ->
+ pkix_crls_validate(OtpCert, DPAndCRLs, DPAndCRLs,
+ Options, pubkey_crl:init_revokation_state());
+
+pkix_crls_validate(OtpCert, DPAndCRLs0, Options) ->
+ CallBack = proplists:get_value(update_crl, Options, fun(_, CurrCRL) ->
+ CurrCRL
+ end),
+ DPAndCRLs = sort_dp_crls(DPAndCRLs0, CallBack),
+ pkix_crls_validate(OtpCert, DPAndCRLs, DPAndCRLs,
+ Options, pubkey_crl:init_revokation_state()).
+
+
%%--------------------------------------------------------------------
-spec ssh_decode(binary(), public_key | ssh_file()) -> [{public_key(), Attributes::list()}].
%%
@@ -611,12 +639,13 @@ path_validation([DerCert | Rest], ValidationState = #path_validation_state{
{error, Reason}
end;
-path_validation([DerCert | _] = Path,
+path_validation([Cert | _] = Path,
#path_validation_state{user_state = UserState0,
verify_fun = VerifyFun} =
ValidationState) ->
Reason = {bad_cert, max_path_length_reached},
- OtpCert = pkix_decode_cert(DerCert, otp),
+ OtpCert = otp_cert(Cert),
+
try VerifyFun(OtpCert, Reason, UserState0) of
{valid, UserState} ->
path_validation(Path,
@@ -630,7 +659,7 @@ path_validation([DerCert | _] = Path,
{error, Reason}
end.
-validate(DerCert, #path_validation_state{working_issuer_name = Issuer,
+validate(Cert, #path_validation_state{working_issuer_name = Issuer,
working_public_key = Key,
working_public_key_parameters =
KeyParams,
@@ -641,31 +670,31 @@ validate(DerCert, #path_validation_state{working_issuer_name = Issuer,
verify_fun = VerifyFun} =
ValidationState0) ->
- OtpCert = pkix_decode_cert(DerCert, otp),
+ OtpCert = otp_cert(Cert),
- UserState1 = pubkey_cert:validate_time(OtpCert, UserState0, VerifyFun),
+ {ValidationState1, UserState1} =
+ pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState0,
+ VerifyFun),
- UserState2 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState1, 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
- UserState3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
- UserState2,VerifyFun),
+ UserState2 = pubkey_cert:validate_time(OtpCert, UserState1, VerifyFun),
- UserState4 = pubkey_cert:validate_revoked_status(OtpCert, UserState3, VerifyFun),
-
- {ValidationState1, UserState5} =
- pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState4,
- VerifyFun),
+ UserState3 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState2, VerifyFun),
- %% We want the key_usage extension to be checked before we validate
- %% the signature.
- UserState6 = pubkey_cert:validate_signature(OtpCert, DerCert,
- Key, KeyParams, UserState5, VerifyFun),
+ UserState4 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
+ UserState3, VerifyFun),
+
+ UserState5 = pubkey_cert:validate_signature(OtpCert, der_cert(Cert),
+ Key, KeyParams, UserState4, VerifyFun),
UserState = case Last of
false ->
- pubkey_cert:verify_fun(OtpCert, valid, UserState6, VerifyFun);
+ pubkey_cert:verify_fun(OtpCert, valid, UserState5, VerifyFun);
true ->
pubkey_cert:verify_fun(OtpCert, valid_peer,
- UserState6, VerifyFun)
+ UserState5, VerifyFun)
end,
ValidationState =
@@ -676,3 +705,110 @@ validate(DerCert, #path_validation_state{working_issuer_name = Issuer,
sized_binary(Binary) ->
Size = size(Binary),
<<?UINT32(Size), Binary/binary>>.
+
+otp_cert(Der) when is_binary(Der) ->
+ pkix_decode_cert(Der, otp);
+otp_cert(#'OTPCertificate'{} =Cert) ->
+ Cert.
+
+der_cert(#'OTPCertificate'{} = Cert) ->
+ pkix_encode('OTPCertificate', Cert, otp);
+der_cert(Der) when is_binary(Der) ->
+ Der.
+
+pkix_crls_validate(_, [],_, _, _) ->
+ {bad_cert, revocation_status_undetermined};
+pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest], All, Options, RevokedState0) ->
+ CallBack = proplists:get_value(update_crl, Options, fun(_, CurrCRL) ->
+ CurrCRL
+ end),
+ case pubkey_crl:fresh_crl(DP, CRL, CallBack) of
+ {fresh, CRL} ->
+ do_pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],
+ All, Options, RevokedState0);
+ {fresh, NewCRL} ->
+ NewAll = [{DP, NewCRL, DeltaCRL} | All -- [{DP, CRL, DeltaCRL}]],
+ do_pkix_crls_validate(OtpCert, [{DP, NewCRL, DeltaCRL} | Rest],
+ NewAll, Options, RevokedState0);
+ no_fresh_crl ->
+ pkix_crls_validate(OtpCert, Rest, All, Options, RevokedState0)
+ end.
+
+do_pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest], All, Options, RevokedState0) ->
+ OtherDPCRLs = All -- [{DP, CRL, DeltaCRL}],
+ case pubkey_crl:validate(OtpCert, OtherDPCRLs, DP, CRL, DeltaCRL, Options, RevokedState0) of
+ {undetermined, _, _} when Rest == []->
+ {bad_cert, revocation_status_undetermined};
+ {undetermined, _, RevokedState} when Rest =/= []->
+ pkix_crls_validate(OtpCert, Rest, All, Options, RevokedState);
+ {finished, unrevoked} ->
+ valid;
+ {finished, Status} ->
+ {bad_cert, Status}
+ end.
+
+sort_dp_crls(DpsAndCrls, FreshCB) ->
+ Sorted = do_sort_dp_crls(DpsAndCrls, dict:new()),
+ sort_crls(Sorted, FreshCB, []).
+
+do_sort_dp_crls([], Dict) ->
+ dict:to_list(Dict);
+do_sort_dp_crls([{DP, CRL} | Rest], Dict0) ->
+ Dict = try dict:fetch(DP, Dict0) of
+ _ ->
+ dict:append(DP, CRL, Dict0)
+ catch _:_ ->
+ dict:store(DP, [CRL], Dict0)
+ end,
+ do_sort_dp_crls(Rest, Dict).
+
+sort_crls([], _, Acc) ->
+ Acc;
+
+sort_crls([{DP, AllCRLs} | Rest], FreshCB, Acc)->
+ {DeltaCRLs, CRLs} = do_sort_crls(AllCRLs),
+ DpsAndCRLs = combine(CRLs, DeltaCRLs, DP, FreshCB, []),
+ sort_crls(Rest, FreshCB, DpsAndCRLs ++ Acc).
+
+do_sort_crls(CRLs) ->
+ lists:partition(fun({_, CRL}) ->
+ pubkey_crl:is_delta_crl(CRL)
+ end, CRLs).
+
+combine([], _,_,_,Acc) ->
+ Acc;
+combine([{_, CRL} = Entry | CRLs], DeltaCRLs, DP, FreshCB, Acc) ->
+ DeltaCRL = combine(CRL, DeltaCRLs),
+ case pubkey_crl:fresh_crl(DP, DeltaCRL, FreshCB) of
+ no_fresh_crl ->
+ combine(CRLs, DeltaCRLs, DP, FreshCB, [{DP, Entry, {undefined, undefined}} | Acc]);
+ {fresh, NewDeltaCRL} ->
+ combine(CRLs, DeltaCRLs, DP, FreshCB, [{DP, Entry, NewDeltaCRL} | Acc])
+ end.
+
+combine(CRL, DeltaCRLs) ->
+ Deltas = lists:filter(fun({_,DeltaCRL}) ->
+ pubkey_crl:combines(CRL, DeltaCRL)
+ end, DeltaCRLs),
+ case Deltas of
+ [] ->
+ {undefined, undefined};
+ [Delta] ->
+ Delta;
+ [_,_|_] ->
+ Fun =
+ fun({_, #'CertificateList'{tbsCertList = FirstTBSCRL}} = CRL1,
+ {_, #'CertificateList'{tbsCertList = SecondTBSCRL}} = CRL2) ->
+ Time1 = pubkey_cert:time_str_2_gregorian_sec(
+ FirstTBSCRL#'TBSCertList'.thisUpdate),
+ Time2 = pubkey_cert:time_str_2_gregorian_sec(
+ SecondTBSCRL#'TBSCertList'.thisUpdate),
+ case Time1 > Time2 of
+ true ->
+ CRL1;
+ false ->
+ CRL2
+ end
+ end,
+ lists:foldl(Fun, hd(Deltas), tl(Deltas))
+ end.