%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2013. 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 = case pubkey_cert:select_extension(?'id-ce-subjectAltName',
TBSCert#'OTPTBSCertificate'.extensions) of
undefined ->
[];
Ext ->
Ext#'Extension'.extnValue
end,
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) ->
%% RFC 5280 section 5.2.5
%% Check that at least one IssuingDistributionPointName in the CRL lines up
%% with a DistributionPointName in the certificate.
Matches = [X || X <- IDPNames, Y <- DPorIssuerNames, X == Y],
case Matches of
[] ->
throw({bad_crl, scope_error});
_ ->
ok
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, _} = public_key:pkix_sign_types(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(asn1_NOVALUE) ->
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.