diff options
Diffstat (limited to 'lib/public_key/src')
-rw-r--r-- | lib/public_key/src/Makefile | 112 | ||||
-rw-r--r-- | lib/public_key/src/pubkey_cert.erl | 988 | ||||
-rw-r--r-- | lib/public_key/src/pubkey_cert_records.erl | 538 | ||||
-rw-r--r-- | lib/public_key/src/pubkey_crypto.erl | 137 | ||||
-rw-r--r-- | lib/public_key/src/pubkey_pem.erl | 192 | ||||
-rw-r--r-- | lib/public_key/src/public_key.app.src | 16 | ||||
-rw-r--r-- | lib/public_key/src/public_key.appup.src | 6 | ||||
-rw-r--r-- | lib/public_key/src/public_key.erl | 411 |
8 files changed, 2400 insertions, 0 deletions
diff --git a/lib/public_key/src/Makefile b/lib/public_key/src/Makefile new file mode 100644 index 0000000000..c30399f33a --- /dev/null +++ b/lib/public_key/src/Makefile @@ -0,0 +1,112 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2008-2009. 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% +# + +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk + +VSN = $(PUBLIC_KEY_VSN) +APP_VSN = "public_key-$(VSN)" + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/public_key-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULES = \ + public_key \ + pubkey_pem \ + pubkey_cert \ + pubkey_cert_records \ + pubkey_crypto + +HRL_FILES = $(INCLUDE)/public_key.hrl + +INTERNAL_HRL_FILES = + +ERL_FILES = $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +APP_FILE= public_key.app +APPUP_FILE= public_key.appup + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +INCLUDE = ../include +# ---------------------------------------------------- +# INETS FLAGS +# ---------------------------------------------------- +PUB_KEY_FLAGS = + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +PUB_KEY_ERL_FLAGS += -I $(INCLUDE) -I ../asn1/ + +ERL_COMPILE_FLAGS += $(PUB_KEY_ERL_FLAGS) \ + $(PUB_KEY_FLAGS) \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(HRL_FILES) + +clean: + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f core + +docs: + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(RELSYSDIR)/ebin + +release_docs_spec: + diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl new file mode 100644 index 0000000000..0ccc74799c --- /dev/null +++ b/lib/public_key/src/pubkey_cert.erl @@ -0,0 +1,988 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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_cert). + +-include("public_key.hrl"). + +-export([verify_signature/3, + 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_unknown_extensions/3, + normalize_general_name/1, digest_type/1, digest/2, is_self_signed/1, + is_issuer/2, issuer_id/2, is_fixed_dh_cert/1]). + +-define(NULL, 0). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +verify_signature(DerCert, Key, KeyParams) -> + {ok, OtpCert} = pubkey_cert_records:decode_cert(DerCert, otp), + verify_signature(OtpCert, DerCert, Key, KeyParams). + +init_validation_state(#'OTPCertificate'{} = OtpCert, DefaultPathLen, + Options) -> + PolicyTree = #policy_tree_node{valid_policy = ?anyPolicy, + qualifier_set = [], + criticality_indicator = false, + expected_policy_set = [?anyPolicy]}, + MaxLen = proplists:get_value(max_path_length, Options, DefaultPathLen), + ExplicitPolicy = policy_indicator(MaxLen, + proplists:get_value(explicit_policy, Options, false)), + InhibitAnyPolicy = policy_indicator(MaxLen, + proplists:get_value(inhibit_any_policy, + 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, + cert_num = 0}, + prepare_for_next_cert(OtpCert, State). + +prepare_for_next_cert(OtpCert, ValidationState = #path_validation_state{ + working_public_key_algorithm = PrevAlgo, + working_public_key_parameters = + PrevParams}) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + Issuer = TBSCert#'OTPTBSCertificate'.subject, + + {Algorithm, PublicKey, PublicKeyParams0} = + public_key_info(TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + ValidationState), + PublicKeyParams = + case PublicKeyParams0 of + 'NULL' when Algorithm =:= PrevAlgo -> + PrevParams; + asn1_NOVALUE when Algorithm =:= PrevAlgo -> + PrevParams; + _ -> PublicKeyParams0 + end, + + ValidationState#path_validation_state{ + working_public_key_algorithm = Algorithm, + working_public_key = PublicKey, + working_public_key_parameters = PublicKeyParams, + working_issuer_name = Issuer, + cert_num = ValidationState#path_validation_state.cert_num + 1 + }. + +validate_time(OtpCert, AccErr, Verify) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + {'Validity', NotBeforeStr, NotAfterStr} + = TBSCert#'OTPTBSCertificate'.validity, + Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()), + NotBefore = time_str_2_gregorian_sec(NotBeforeStr), + NotAfter = time_str_2_gregorian_sec(NotAfterStr), + + case ((NotBefore =< Now) and (Now =< NotAfter)) of + true -> + AccErr; + false -> + not_valid({bad_cert, cert_expired}, Verify, AccErr) + end. + +validate_issuer(OtpCert, Issuer, AccErr, Verify) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + case is_issuer(Issuer, TBSCert#'OTPTBSCertificate'.issuer) of + true -> + AccErr; + _ -> + not_valid({bad_cert, invalid_issuer}, Verify, AccErr) + end. + +validate_signature(OtpCert, DerCert, Key, KeyParams, + AccErr, Verify) -> + + case verify_signature(OtpCert, DerCert, Key, KeyParams) of + true -> + AccErr; + false -> + not_valid({bad_cert, invalid_signature}, Verify, AccErr) + end. + +validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) -> + case is_self_signed(OtpCert) andalso (not Last) of + true -> + ok; + false -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + Subject = TBSCert#'OTPTBSCertificate'.subject, + AltSubject = + select_extension(?'id-ce-subjectAltName', + TBSCert#'OTPTBSCertificate'.extensions), + + EmailAddress = extract_email(Subject), + Name = [{directoryName, Subject}|EmailAddress], + + AltNames = case AltSubject of + undefined -> []; + _ -> AltSubject#'Extension'.extnValue + end, + + case (is_permitted(Name, Permit) andalso + is_permitted(AltNames, Permit) andalso + (not is_excluded(Name, Exclude)) andalso + (not is_excluded(AltNames, Exclude))) of + true -> + AccErr; + false -> + not_valid({bad_cert, name_not_permitted}, + Verify, AccErr) + end + end. + + +%% See rfc3280 4.1.2.6 Subject: regarding emails. +extract_email({rdnSequence, List}) -> + extract_email2(List). +extract_email2([[#'AttributeTypeAndValue'{type=?'id-emailAddress', + value=Mail}]|_]) -> + [{rfc822Name, Mail}]; +extract_email2([_|Rest]) -> + extract_email2(Rest); +extract_email2([]) -> []. + +validate_revoked_status(_OtpCert, _Verify, AccErr) -> + %% true | + %% throw({bad_cert, cert_revoked}) + AccErr. + +validate_extensions(OtpCert, ValidationState, Verify, AccErr) -> + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + Extensions = TBSCert#'OTPTBSCertificate'.extensions, + validate_extensions(Extensions, ValidationState, no_basic_constraint, + is_self_signed(OtpCert), [], Verify, AccErr). + +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). + +normalize_general_name({rdnSequence, Issuer}) -> + NormIssuer = normalize_general_name(Issuer), + {rdnSequence, NormIssuer}; + +normalize_general_name(Issuer) -> + Normalize = fun([{Description, Type, {printableString, Value}}]) -> + NewValue = string:to_lower(strip_spaces(Value)), + {Description, Type, {printableString, NewValue}}; + (Atter) -> + Atter + end, + lists:sort(lists:map(Normalize, Issuer)). + +is_self_signed(#'OTPCertificate'{tbsCertificate= + #'OTPTBSCertificate'{issuer = Issuer, + subject = Subject}}) -> + is_issuer(Issuer, Subject). + +is_issuer({rdnSequence, Issuer}, {rdnSequence, Candidate}) -> + is_dir_name(Issuer, Candidate, true). + +issuer_id(Otpcert, other) -> + TBSCert = Otpcert#'OTPCertificate'.tbsCertificate, + Extensions = TBSCert#'OTPTBSCertificate'.extensions, + case select_extension(?'id-ce-authorityKeyIdentifier', Extensions) of + undefined -> + {error, issuer_not_found}; + AuthKeyExt -> + cert_auth_key_id(AuthKeyExt#'Extension'.extnValue) + end; + +issuer_id(Otpcert, self) -> + TBSCert = Otpcert#'OTPCertificate'.tbsCertificate, + Issuer = TBSCert#'OTPTBSCertificate'.issuer, + SerialNr = TBSCert#'OTPTBSCertificate'.serialNumber, + {ok, {SerialNr, normalize_general_name(Issuer)}}. + + +is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate = + #'OTPTBSCertificate'{subjectPublicKeyInfo = + SubjectPublicKeyInfo, + extensions = + Extensions}}) -> + is_fixed_dh_cert(SubjectPublicKeyInfo, Extensions). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +not_valid(Error, true, _) -> + throw(Error); +not_valid(Error, false, AccErrors) -> + [Error | AccErrors]. + +verify_signature(OtpCert, DerCert, Key, KeyParams) -> + %% Signature is an ASN1 compact bit string + {0, Signature} = OtpCert#'OTPCertificate'.signature, + SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm, + SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm, + EncTBSCert = encoded_tbs_cert(DerCert), + verify(SigAlg, EncTBSCert, Signature, Key, KeyParams). + +verify(Alg, PlainText, Signature, Key, KeyParams) -> + public_key:verify_signature(PlainText, digest_type(Alg), + Signature, Key, KeyParams). + +encoded_tbs_cert(Cert) -> + {ok, PKIXCert} = + 'OTP-PUB-KEY':decode_TBSCert_exclusive(Cert), + {'Certificate', + {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert, + EncodedTBSCert. + +digest_type(?sha1WithRSAEncryption) -> + sha; +digest_type(?md5WithRSAEncryption) -> + md5; +digest_type(?'id-dsa-with-sha1') -> + sha. + +digest(?sha1WithRSAEncryption, Msg) -> + crypto:sha(Msg); +digest(?md5WithRSAEncryption, Msg) -> + crypto:md5(Msg); +digest(?'id-dsa-with-sha1', Msg) -> + crypto:sha(Msg). + +public_key_info(PublicKeyInfo, + #path_validation_state{working_public_key_algorithm = + WorkingAlgorithm, + working_public_key_parameters = + WorkingParams}) -> + PublicKey = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey, + AlgInfo = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm, + + PublicKeyParams = AlgInfo#'PublicKeyAlgorithm'.parameters, + Algorithm = AlgInfo#'PublicKeyAlgorithm'.algorithm, + + NewPublicKeyParams = + case PublicKeyParams of + 'NULL' when WorkingAlgorithm == Algorithm -> + WorkingParams; + _ -> + PublicKeyParams + end, + {Algorithm, PublicKey, NewPublicKeyParams}. + +time_str_2_gregorian_sec({utcTime, [Y1,Y2,M1,M2,D1,D2,H1,H2,M3,M4,S1,S2,Z]}) -> + case list_to_integer([Y1,Y2]) of + N when N >= 50 -> + time_str_2_gregorian_sec({generalTime, + [$1,$9,Y1,Y2,M1,M2,D1,D2, + H1,H2,M3,M4,S1,S2,Z]}); + _ -> + time_str_2_gregorian_sec({generalTime, + [$2,$0,Y1,Y2,M1,M2,D1,D2, + H1,H2,M3,M4,S1,S2,Z]}) + end; + +time_str_2_gregorian_sec({_,[Y1,Y2,Y3,Y4,M1,M2,D1,D2,H1,H2,M3,M4,S1,S2,$Z]}) -> + Year = list_to_integer([Y1, Y2, Y3, Y4]), + Month = list_to_integer([M1, M2]), + Day = list_to_integer([D1, D2]), + Hour = list_to_integer([H1, H2]), + Min = list_to_integer([M3, M4]), + Sec = list_to_integer([S1, S2]), + calendar:datetime_to_gregorian_seconds({{Year, Month, Day}, + {Hour, Min, Sec}}). + +is_dir_name([], [], _Exact) -> true; +is_dir_name([H|R1],[H|R2], Exact) -> is_dir_name(R1,R2, Exact); +is_dir_name([[{'AttributeTypeAndValue', Type, What1}]|Rest1], + [[{'AttributeTypeAndValue', Type, What2}]|Rest2],Exact) -> + case is_dir_name2(What1,What2) of + true -> is_dir_name(Rest1,Rest2,Exact); + false -> false + end; +is_dir_name([{'AttributeTypeAndValue', Type, What1}|Rest1], + [{'AttributeTypeAndValue', Type, What2}|Rest2], Exact) -> + case is_dir_name2(What1,What2) of + true -> is_dir_name(Rest1,Rest2,Exact); + false -> false + end; +is_dir_name(_,[],false) -> + true; +is_dir_name(_,_,_) -> + false. + +is_dir_name2(Value, Value) -> true; +is_dir_name2({printableString, Value1}, {printableString, Value2}) -> + string:to_lower(strip_spaces(Value1)) =:= + string:to_lower(strip_spaces(Value2)); +is_dir_name2({utf8String, Value1}, String) -> %% BUGBUG FIX UTF8 conv + is_dir_name2({printableString, binary_to_list(Value1)}, String); +is_dir_name2(String, {utf8String, Value1}) -> %% BUGBUG FIX UTF8 conv + is_dir_name2(String, {printableString, binary_to_list(Value1)}); +is_dir_name2(_, _) -> + false. + +cert_auth_key_id(#'AuthorityKeyIdentifier'{authorityCertIssuer + = asn1_NOVALUE}) -> + {error, issuer_not_found}; +cert_auth_key_id(#'AuthorityKeyIdentifier'{authorityCertIssuer = + AuthCertIssuer, + authorityCertSerialNumber = + SerialNr}) -> + {ok, {SerialNr, decode_general_name(AuthCertIssuer)}}. + +decode_general_name([{directoryName, Issuer}]) -> + normalize_general_name(Issuer). + +%% Strip all leading and trailing spaces and make +%% sure there is no double spaces in between. +strip_spaces(String) -> + NewString = + lists:foldl(fun(Char, Acc) -> Acc ++ Char ++ " " end, [], + 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(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) -> + case Last of + true when SelfSigned -> + {ValidationState, UnknownExtensions, AccErr0}; + true -> + {ValidationState#path_validation_state{max_path_length = Len - 1}, + UnknownExtensions, AccErr0}; + %% 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), + case SelfSigned of + true -> + {ValidationState, UnknownExtensions, AccErr}; + false -> + {ValidationState#path_validation_state{max_path_length = + Len - 1}, + UnknownExtensions, AccErr} + end + end; + +validate_extensions([#'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = + #'BasicConstraints'{cA = true, + pathLenConstraint = N}} | + Rest], + ValidationState = + #path_validation_state{max_path_length = Len}, _, + SelfSigned, UnknownExtensions, Verify, AccErr) -> + Length = if SelfSigned -> min(N, Len); + true -> min(N, Len-1) + end, + validate_extensions(Rest, + ValidationState#path_validation_state{max_path_length = + Length}, + basic_constraint, SelfSigned, UnknownExtensions, + Verify, AccErr); +%% 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) -> + case Last orelse is_valid_key_usage(KeyUse, keyCertSign) of + true -> + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, + AccErr0); + false -> + AccErr = not_valid({bad_cert, invalid_key_usage}, Verify, AccErr0), + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, + AccErr) + end; + +validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = KeyUse, + critical = true} | Rest], + #path_validation_state{} = ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, Verify, + AccErr0) -> + case is_valid_extkey_usage(KeyUse) of + true -> + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, + Verify, AccErr0); + false -> + AccErr = + not_valid({bad_cert, invalid_ext_key_usage}, Verify, AccErr0), + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, AccErr) + end; + +validate_extensions([#'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = Names} | Rest], + ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, AccErr0) -> + case validate_subject_alt_names(Names) of + true when Names =/= [] -> + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, + AccErr0); + _ -> + AccErr = + not_valid({bad_cert, invalid_subject_altname}, + Verify, AccErr0), + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, + AccErr) + 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); + +%% 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], + ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, + Verify, AccErr) + when Id == ?'id-ce-subjectKeyIdentifier'; + Id == ?'id-ce-authorityKeyIdentifier'-> + validate_extensions(Rest, ValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, AccErr); + +validate_extensions([#'Extension'{extnID = ?'id-ce-nameConstraints', + extnValue = NameConst} | Rest], + ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, + Verify, AccErr) -> + 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([#'Extension'{extnID = ?'id-ce-certificatePolicies', + critical = true} | Rest], ValidationState, + ExistBasicCon, SelfSigned, + UnknownExtensions, Verify, AccErr0) -> + %% 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} + = ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, + Verify, AccErr) -> + + %% TODO: Policy imp incomplete + NewTree = process_policy_tree(Id, Qualifier, Tree), + + validate_extensions(Rest, + ValidationState#path_validation_state{ + valid_policy_tree = NewTree}, + ExistBasicCon, SelfSigned, UnknownExtensions, + Verify, AccErr); + +validate_extensions([#'Extension'{extnID = ?'id-ce-policyConstraints', + critical = true} | Rest], ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, Verify, + AccErr0) -> + %% 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) -> + + %% TODO: Policy imp incomplete + NewValidationState = add_policy_constraints(ExpPolicy, MapPolicy, + ValidationState), + + validate_extensions(Rest, NewValidationState, ExistBasicCon, + SelfSigned, UnknownExtensions, Verify, AccErr); + +validate_extensions([Extension | Rest], ValidationState, + ExistBasicCon, SelfSigned, UnknownExtensions, + Verify, AccErr) -> + validate_extensions(Rest, ValidationState, ExistBasicCon, SelfSigned, + [Extension | UnknownExtensions], Verify, AccErr). + +is_valid_key_usage(KeyUse, Use) -> + lists:member(Use, KeyUse). + +is_valid_extkey_usage(?'id-kp-clientAuth') -> + true; +is_valid_extkey_usage(?'id-kp-serverAuth') -> + true; +is_valid_extkey_usage(_) -> + false. + +validate_subject_alt_names([]) -> + true; +validate_subject_alt_names([AltName | Rest]) -> + case is_valid_subject_alt_name(AltName) of + true -> + validate_subject_alt_names(Rest); + false -> + false + end. + +is_valid_subject_alt_name({Name, Value}) when Name == rfc822Name; + Name == dNSName -> + case Value of + "" -> + false; + _ -> + true + end; + +is_valid_subject_alt_name({iPAdress, Addr}) -> + case length(Addr) of + 4 -> %ipv4 + true; + 16 -> %ipv6 + true; + _ -> + false + end; +is_valid_subject_alt_name({uniformResourceIdentifier, URI}) -> + is_valid_uri(URI); + +is_valid_subject_alt_name({directoryName, _}) -> + true; +is_valid_subject_alt_name({_, [_|_]}) -> + true; +is_valid_subject_alt_name({_, _}) -> + false. + +min(N, M) when N =< M -> + N; +min(_, M) -> + M. + +is_ip_address(Address) -> + case inet_parse:address(Address) of + {ok, _} -> + true; + _ -> + false + end. + +is_fully_qualified_name(_Name) -> + true. + +is_valid_uri(AbsURI) -> + case split_uri(AbsURI) of + incomplete -> + false; + {StrScheme, _, Host, _, _} -> + case string:to_lower(StrScheme) of + Scheme when Scheme =:= "http"; Scheme =:= "ftp" -> + is_valid_host(Host); + _ -> + false + end + end. + +is_valid_host(Host) -> + case is_ip_address(Host) of + true -> + true; + false -> + is_fully_qualified_name(Host) + end. + +%% Could have a more general split URI in stdlib? Maybe when +%% regexs are improved. Needed also in inets! +split_uri(Uri) -> + case split_uri(Uri, ":", {error, no_scheme}, 1, 1) of + {error, no_scheme} -> + incomplete; + {StrScheme, "//" ++ URIPart} -> + {Authority, PathQuery} = + split_auth_path(URIPart), + {UserInfo, HostPort} = + split_uri(Authority, "@", {"", Authority}, 1, 1), + {Host, Port} = + split_uri(HostPort, ":", {HostPort, dummy_port}, 1, 1), + {StrScheme, UserInfo, Host, Port, PathQuery} + end. + +split_auth_path(URIPart) -> + case split_uri(URIPart, "/", URIPart, 1, 0) of + Split = {_, _} -> + Split; + URIPart -> + case split_uri(URIPart, "\\?", URIPart, 1, 0) of + Split = {_, _} -> + Split; + URIPart -> + {URIPart,""} + end + end. + +split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) -> + case regexp:first_match(UriPart, SplitChar) of + {match, Match, _} -> + {string:substr(UriPart, 1, Match - SkipLeft), + string:substr(UriPart, Match + SkipRight, length(UriPart))}; + nomatch -> + NoMatchResult + end. + +is_rdnSeq({rdnSequence,[]}, {rdnSequence,[none]}) -> + true; +is_rdnSeq({rdnSequence,DirName}, {rdnSequence,Permitted}) -> + is_dir_name(DirName, Permitted, false). + +is_permitted(_, no_constraints) -> + true; +is_permitted(Names, Constraints) -> + is_valid_name(Names, Constraints, true). + +is_excluded([], _) -> + false; +is_excluded(Names, Constraints) -> + is_valid_name(Names, Constraints, false). + +is_valid_name([], _, Default) -> + Default; +is_valid_name([{Type, Name} | Rest], Constraints, Default) -> + case type_subtree_names(Type, Constraints) of + [_|_] = ConstraintNames -> + case match_name(Type, Name, ConstraintNames) of + Default -> + is_valid_name(Rest, Constraints, Default); + Fail -> + Fail + end; + [] -> + is_valid_name(Rest, Constraints,Default) + end. + +add_name_constraints(NewPermittedTrees, NewExcludedTrees, + #path_validation_state{ + permitted_subtrees = PermittedTrees, + excluded_subtrees = ExcludedTrees} = + ValidationState) -> + NewPermitted = subtree_intersection(NewPermittedTrees, PermittedTrees), + NewExcluded = subtree_union(NewExcludedTrees, ExcludedTrees), + ValidationState#path_validation_state{permitted_subtrees = NewPermitted, + excluded_subtrees = NewExcluded}. +subtree_union(asn1_NOVALUE, Trees) -> + Trees; +subtree_union(Trees1, Trees2) -> + Trees1 ++ Trees2. + +subtree_intersection(asn1_NOVALUE, Trees) -> + Trees; +subtree_intersection(List, no_constraints) -> + List; +subtree_intersection([Tree | Trees1], Trees2) -> + Trees = is_in_intersection(Tree, Trees2), + subtree_intersection(Trees1, Trees); +subtree_intersection([], TreesInt) -> + TreesInt. + +is_in_intersection(#'GeneralSubtree'{base = + {directoryName, {rdnSequence, Name1}}} + = Name, + [#'GeneralSubtree'{base = + {directoryName, {rdnSequence, Name2}}} + | Trees]) -> + case is_dir_name(Name1, Name2, false) of + true -> + [Name|Trees]; + false -> + [Name#'GeneralSubtree'{base = + {directoryName, {rdnSequence,[none]}}} + | Trees] + end; +is_in_intersection(#'GeneralSubtree'{base = {ipAdress, Ip}}, + Trees = [#'GeneralSubtree'{base = {ipAdress, Ip}} | _]) -> + %% BUGBUG + Trees; +is_in_intersection(#'GeneralSubtree'{base = {x400Address, OrAddr1}} = Addr, + [#'GeneralSubtree'{base = {x400Address, OrAddr2}} + | Trees]) -> + case is_or_address(OrAddr1, OrAddr2) of + true -> + [Addr|Trees]; + false -> + [#'GeneralSubtree'{base = {x400Address, ""}} | Trees] + end; + +is_in_intersection(#'GeneralSubtree'{base = {Type, Name1}} = Name, + [#'GeneralSubtree'{base = {Type, Name2}} + | Trees]) -> + case case_insensitive_match(Name1, Name2) of + true -> + [Name|Trees]; + false -> + [#'GeneralSubtree'{base = {Type, ""}} | Trees] + end; +is_in_intersection(New, []) -> + [New]; +is_in_intersection(Name, [Other | IntCandidates]) -> + [Other|is_in_intersection(Name, IntCandidates)]. + +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], + [PermittedIp | PermittedIpRest], [Mask | MaskRest] ) -> + case mask_cmp(CandidatIp, PermittedIp, Mask) of + true -> + is_permitted_ip(CandidatIpRest, PermittedIpRest, MaskRest); + false -> + false + end. + +mask_cmp(Canditate, Permitted, Mask) -> + (Canditate band Mask) == Permitted. + +is_valid_host_or_domain(Canditate, [$.|_] = Permitted) -> + is_suffix(Permitted, Canditate); +is_valid_host_or_domain(Canditate, Permitted) -> + case string:tokens(Canditate,"@") of + [CanditateHost] -> + case_insensitive_match(CanditateHost, Permitted); + [_, CanditateHost] -> + case_insensitive_match(CanditateHost, Permitted) + end. +is_valid_email_address(Canditate, [$.|Permitted], [_]) -> + is_suffix(Permitted, Canditate); + +is_valid_email_address(Canditate, PermittedHost, [_]) -> + [_ , CanditateHost] = string:tokens(Canditate,"@"), + case_insensitive_match(CanditateHost, PermittedHost); + +is_valid_email_address(Canditate, Permitted, [_, _]) -> + case_insensitive_match(Canditate, Permitted). + +is_suffix(Suffix, Str) -> + lists:suffix(string:to_lower(Suffix), string:to_lower(Str)). +case_insensitive_match(Str1, Str2) -> + string:to_lower(Str1) == string:to_lower(Str2). + +is_or_address(Address, Canditate) -> + %% TODO: Is case_insensitive_match sufficient? + %% study rfc2156 probably need more a complex check. + is_double_quoted(Address) andalso + is_double_quoted(Canditate) andalso + case_insensitive_match(Address, Canditate). + +is_double_quoted(["\"" | Tail]) -> + is_double_quote(lists:last(Tail)); +is_double_quoted("%22" ++ Tail) -> + case lists:reverse(Tail) of + [A, B, C | _] -> + is_double_quote([C, B, A]); + _ -> + false + end; + +is_double_quoted(_) -> + false. + +is_double_quote("%22") -> + true; +is_double_quote("\"") -> + true; +is_double_quote(_) -> + false. + +add_policy_constraints(ExpPolicy, MapPolicy, + #path_validation_state{cert_num = CertNum, + explicit_policy = CurExpPolicy, + policy_mapping = CurMapPolicy} = + ValidationState) -> + + NewExpPolicy = policy_constraint(CurExpPolicy, ExpPolicy, CertNum), + NewMapPolicy = policy_constraint(CurMapPolicy, MapPolicy, CertNum), + + ValidationState#path_validation_state{explicit_policy = NewExpPolicy, + policy_mapping = NewMapPolicy}. + +policy_constraint(Current, asn1_NOVALUE, _) -> + Current; +policy_constraint(Current, New, CertNum) -> + min(Current, New + CertNum). + +process_policy_tree(_,_, ?NULL) -> + ?NULL; +process_policy_tree(_Id, _Qualifier, Tree) -> + %% TODO real imp. + Tree. + +policy_indicator(_, true) -> + 0; +policy_indicator(N, false) -> + N + 1. + +is_fixed_dh_cert(PublicKeyInfo, Extensions) -> + AlgInfo = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm, + Algorithm = AlgInfo#'PublicKeyAlgorithm'.algorithm, + + case select_extension(?'id-ce-keyUsage', Extensions) of + undefined -> + is_dh(Algorithm); + #'Extension'{extnValue=KeyUse} -> + is_dh(Algorithm) andalso is_valid_key_usage(KeyUse, keyAgreement) + end. + +is_dh(?'dhpublicnumber')-> + true; +is_dh(_) -> + false. diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl new file mode 100644 index 0000000000..36b7c47a9c --- /dev/null +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -0,0 +1,538 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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_cert_records). + +-include("public_key.hrl"). + +-export([decode_cert/2, encode_cert/1, encode_tbs_cert/1]). + +-export([old_decode_cert/2, old_encode_cert/1]). %% Debugging and testing new code. + +%%==================================================================== +%% Internal application API +%%==================================================================== + +decode_cert(DerCert, plain) -> + 'OTP-PUB-KEY':decode('Certificate', DerCert); +decode_cert(DerCert, otp) -> + {ok, Cert} = 'OTP-PUB-KEY':decode('OTPCertificate', DerCert), + {ok, decode_all_otp(Cert)}. + +old_decode_cert(DerCert, otp) -> + {ok, Cert} = 'OTP-PUB-KEY':decode('Certificate', DerCert), + {ok, plain_to_otp(Cert)}. + +old_encode_cert(Cert) -> + PlainCert = otp_to_plain(Cert), + {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', PlainCert), + list_to_binary(EncCert). + + +encode_cert(Cert = #'Certificate'{}) -> + {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', Cert), + list_to_binary(EncCert); +encode_cert(C = #'OTPCertificate'{tbsCertificate = TBS = + #'OTPTBSCertificate'{ + issuer=Issuer0, + subject=Subject0, + subjectPublicKeyInfo=Spki0, + extensions=Exts0} + }) -> + Issuer = transform(Issuer0,encode), + Subject = transform(Subject0,encode), + Spki = encode_supportedPublicKey(Spki0), + Exts = encode_extensions(Exts0), + %% io:format("Extensions ~p~n",[Exts]), + Cert = C#'OTPCertificate'{tbsCertificate= + TBS#'OTPTBSCertificate'{ + issuer=Issuer, subject=Subject, + subjectPublicKeyInfo=Spki, + extensions=Exts}}, + {ok, EncCert} = 'OTP-PUB-KEY':encode('OTPCertificate', Cert), + list_to_binary(EncCert). + +encode_tbs_cert(TBS = #'OTPTBSCertificate'{ + issuer=Issuer0, + subject=Subject0, + subjectPublicKeyInfo=Spki0, + extensions=Exts0}) -> + Issuer = transform(Issuer0,encode), + Subject = transform(Subject0,encode), + Spki = encode_supportedPublicKey(Spki0), + Exts = encode_extensions(Exts0), + TBSCert = TBS#'OTPTBSCertificate'{issuer=Issuer,subject=Subject, + subjectPublicKeyInfo=Spki,extensions=Exts}, + {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', TBSCert), + list_to_binary(EncTBSCert). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +decode_all_otp(C = #'OTPCertificate'{tbsCertificate = TBS = + #'OTPTBSCertificate'{ + issuer=Issuer0, + subject=Subject0, + subjectPublicKeyInfo=Spki0, + extensions=Exts0} + }) -> + Issuer = transform(Issuer0,decode), + Subject = transform(Subject0,decode), + Spki = decode_supportedPublicKey(Spki0), + Exts = decode_extensions(Exts0), + %% io:format("Extensions ~p~n",[Exts]), + C#'OTPCertificate'{tbsCertificate= + TBS#'OTPTBSCertificate'{ + issuer=Issuer, subject=Subject, + subjectPublicKeyInfo=Spki,extensions=Exts}}. + + +%%% SubjectPublicKey +supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey'; +supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey'; +supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey'; +supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey'; +supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'. + +decode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = + #'PublicKeyAlgorithm'{algorithm=Algo}, + subjectPublicKey = {0,SPK0}}) -> + Type = supportedPublicKeyAlgorithms(Algo), + {ok, SPK} = 'OTP-PUB-KEY':decode(Type, SPK0), + #'OTPSubjectPublicKeyInfo'{subjectPublicKey = SPK, algorithm=PA}. + +encode_supportedPublicKey(#'OTPSubjectPublicKeyInfo'{algorithm= PA = + #'PublicKeyAlgorithm'{algorithm=Algo}, + subjectPublicKey = SPK0}) -> + Type = supportedPublicKeyAlgorithms(Algo), + {ok, SPK} = 'OTP-PUB-KEY':encode(Type, SPK0), + #'OTPSubjectPublicKeyInfo'{subjectPublicKey = {0,list_to_binary(SPK)}, algorithm=PA}. + +%%% Extensions + +extension_id(?'id-ce-authorityKeyIdentifier') -> 'AuthorityKeyIdentifier'; +extension_id(?'id-ce-subjectKeyIdentifier') -> 'SubjectKeyIdentifier'; +extension_id(?'id-ce-keyUsage') -> 'KeyUsage'; +extension_id(?'id-ce-privateKeyUsagePeriod') -> 'PrivateKeyUsagePeriod'; +extension_id(?'id-ce-certificatePolicies') -> 'CertificatePolicies'; +extension_id(?'id-ce-policyMappings') -> 'PolicyMappings'; +extension_id(?'id-ce-subjectAltName') -> 'SubjectAltName'; +extension_id(?'id-ce-issuerAltName') -> 'IssuerAltName'; +extension_id(?'id-ce-subjectDirectoryAttributes') -> 'SubjectDirectoryAttributes'; +extension_id(?'id-ce-basicConstraints' ) -> 'BasicConstraints'; +extension_id(?'id-ce-nameConstraints') -> 'NameConstraints'; +extension_id(?'id-ce-policyConstraints') -> 'PolicyConstraints'; +extension_id(?'id-ce-cRLDistributionPoints') -> 'CRLDistributionPoints'; +extension_id(?'id-ce-extKeyUsage') -> 'ExtKeyUsageSyntax'; +extension_id(?'id-ce-inhibitAnyPolicy') -> 'InhibitAnyPolicy'; +extension_id(?'id-ce-freshestCRL') -> 'FreshestCRL'; +%% Missing in public_key doc +extension_id(?'id-pe-authorityInfoAccess') -> 'AuthorityInfoAccessSyntax'; +extension_id(?'id-pe-subjectInfoAccess') -> 'SubjectInfoAccessSyntax'; +extension_id(?'id-ce-cRLNumber') -> 'CRLNumber'; +extension_id(?'id-ce-issuingDistributionPoint') -> 'IssuingDistributionPoint'; +extension_id(?'id-ce-deltaCRLIndicator') -> 'BaseCRLNumber'; +extension_id(?'id-ce-cRLReasons') -> 'CRLReason'; +extension_id(?'id-ce-certificateIssuer') -> 'CertificateIssuer'; +extension_id(?'id-ce-holdInstructionCode') -> 'HoldInstructionCode'; +extension_id(?'id-ce-invalidityDate') -> 'InvalidityDate'; +extension_id(_) -> + undefined. + + +decode_extensions(asn1_NOVALUE) -> + asn1_NOVALUE; + +decode_extensions(Exts) -> + lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) -> + case extension_id(Id) of + undefined -> Ext; + Type -> + {ok, Value} = 'OTP-PUB-KEY':decode(Type, list_to_binary(Value0)), + Ext#'Extension'{extnValue=transform(Value,decode)} + end + end, Exts). + +encode_extensions(asn1_NOVALUE) -> + asn1_NOVALUE; + +encode_extensions(Exts) -> + lists:map(fun(Ext = #'Extension'{extnID=Id, extnValue=Value0}) -> + case extension_id(Id) of + undefined -> Ext; + Type -> + Value1 = transform(Value0,encode), + {ok, Value} = 'OTP-PUB-KEY':encode(Type, Value1), + Ext#'Extension'{extnValue=list_to_binary(Value)} + end + end, Exts). + +transform(#'AttributeTypeAndValue'{type=Id,value=Value0} = ATAV, Func) -> + {ok, Value} = + case attribute_type(Id) of + Type when is_atom(Type) -> 'OTP-PUB-KEY':Func(Type, Value0); + _UnknownType -> {ok, Value0} + end, + ATAV#'AttributeTypeAndValue'{value=Value}; +transform(AKI = #'AuthorityKeyIdentifier'{authorityCertIssuer=ACI},Func) -> + AKI#'AuthorityKeyIdentifier'{authorityCertIssuer=transform(ACI,Func)}; +transform(List = [{directoryName, _}],Func) -> + [{directoryName, transform(Value,Func)} || {directoryName, Value} <- List]; +transform({directoryName, Value},Func) -> + {directoryName, transform(Value,Func)}; +transform({rdnSequence, SeqList},Func) when is_list(SeqList) -> + {rdnSequence, + lists:map(fun(Seq) -> + lists:map(fun(Element) -> transform(Element,Func) end, Seq) + end, SeqList)}; +%% transform(List = [{rdnSequence, _}|_],Func) -> +%% lists:map(fun(Element) -> transform(Element,Func) end, List); +transform(#'NameConstraints'{permittedSubtrees=Permitted, excludedSubtrees=Excluded}, Func) -> + Res = #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func), + excludedSubtrees=transform_sub_tree(Excluded,Func)}, +%% io:format("~p~n",[Res]), + Res; +transform(Other,_) -> + Other. +transform_sub_tree(asn1_NOVALUE,_) -> asn1_NOVALUE; +transform_sub_tree(TreeList,Func) -> + [Tree#'GeneralSubtree'{base=transform(Name,Func)} || + Tree = #'GeneralSubtree'{base=Name} <- TreeList]. + +attribute_type(?'id-at-name') -> 'X520name'; +attribute_type(?'id-at-surname') -> 'X520name'; +attribute_type(?'id-at-givenName') -> 'X520name'; +attribute_type(?'id-at-initials') -> 'X520name'; +attribute_type(?'id-at-generationQualifier') -> 'X520name'; +attribute_type(?'id-at-commonName') -> 'X520CommonName'; +attribute_type(?'id-at-localityName') -> 'X520LocalityName'; +attribute_type(?'id-at-stateOrProvinceName') -> 'X520StateOrProvinceName'; +attribute_type(?'id-at-organizationName') -> 'X520OrganizationName'; +attribute_type(?'id-at-organizationalUnitName') -> 'X520OrganizationalUnitName'; +attribute_type(?'id-at-title') -> 'X520Title'; +attribute_type(?'id-at-dnQualifier') -> 'X520dnQualifier'; +attribute_type(?'id-at-countryName') -> 'X520countryName'; +attribute_type(?'id-at-serialNumber') -> 'X520SerialNumber'; +attribute_type(?'id-at-pseudonym') -> 'X520Pseudonym'; +attribute_type(?'id-domainComponent') -> 'DomainComponent'; +attribute_type(?'id-emailAddress') -> 'EmailAddress'; +attribute_type(Type) -> Type. + +%%% Old code transforms + +plain_to_otp(#'Certificate'{tbsCertificate = TBSCert, + signatureAlgorithm = SigAlg, + signature = Signature} = Cert) -> + Cert#'Certificate'{tbsCertificate = plain_to_otp(TBSCert), + signatureAlgorithm = plain_to_otp(SigAlg), + signature = plain_to_otp(Signature)}; + +plain_to_otp(#'TBSCertificate'{signature = Signature, + issuer = Issuer, + subject = Subject, + subjectPublicKeyInfo = SPubKeyInfo, + extensions = Extensions} = TBSCert) -> + + TBSCert#'TBSCertificate'{signature = plain_to_otp(Signature), + issuer = plain_to_otp(Issuer), + subject = + plain_to_otp(Subject), + subjectPublicKeyInfo = + plain_to_otp(SPubKeyInfo), + extensions = + plain_to_otp_extensions(Extensions) + }; + +plain_to_otp(#'AlgorithmIdentifier'{algorithm = Algorithm, + parameters = Params}) -> + SignAlgAny = + #'SignatureAlgorithm-Any'{algorithm = Algorithm, + parameters = Params}, + {ok, AnyEnc} = 'OTP-PUB-KEY':encode('SignatureAlgorithm-Any', + SignAlgAny), + {ok, SignAlg} = 'OTP-PUB-KEY':decode('SignatureAlgorithm', + list_to_binary(AnyEnc)), + SignAlg; + +plain_to_otp({rdnSequence, SeqList}) when is_list(SeqList) -> + {rdnSequence, + lists:map(fun(Seq) -> + lists:map(fun(Element) -> + plain_to_otp(Element) + end, + Seq) + end, SeqList)}; + +plain_to_otp(#'AttributeTypeAndValue'{} = ATAV) -> + {ok, ATAVEnc} = + 'OTP-PUB-KEY':encode('AttributeTypeAndValue', ATAV), + {ok, ATAVDec} = 'OTP-PUB-KEY':decode('OTPAttributeTypeAndValue', + list_to_binary(ATAVEnc)), + #'AttributeTypeAndValue'{type = ATAVDec#'OTPAttributeTypeAndValue'.type, + value = + ATAVDec#'OTPAttributeTypeAndValue'.value}; + +plain_to_otp(#'SubjectPublicKeyInfo'{algorithm = + #'AlgorithmIdentifier'{algorithm + = Algo, + parameters = + Params}, + subjectPublicKey = PublicKey}) -> + + AnyAlgo = #'PublicKeyAlgorithm'{algorithm = Algo, + parameters = Params}, + {0, AnyKey} = PublicKey, + AnyDec = #'OTPSubjectPublicKeyInfo-Any'{algorithm = AnyAlgo, + subjectPublicKey = AnyKey}, + {ok, AnyEnc} = + 'OTP-PUB-KEY':encode('OTPSubjectPublicKeyInfo-Any', AnyDec), + {ok, InfoDec} = 'OTP-PUB-KEY':decode('OTPOLDSubjectPublicKeyInfo', + list_to_binary(AnyEnc)), + + AlgorithmDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.algorithm, + AlgoDec = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.algo, + NewParams = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.parameters, + PublicKeyDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.subjectPublicKey, + NewAlgorithmDec = + #'SubjectPublicKeyInfoAlgorithm'{algorithm = AlgoDec, + parameters = NewParams}, + #'SubjectPublicKeyInfo'{algorithm = NewAlgorithmDec, + subjectPublicKey = PublicKeyDec + }; + +plain_to_otp(#'Extension'{extnID = ExtID, + critical = Critical, + extnValue = Value}) + when ExtID == ?'id-ce-authorityKeyIdentifier'; + ExtID == ?'id-ce-subjectKeyIdentifier'; + ExtID == ?'id-ce-keyUsage'; + ExtID == ?'id-ce-privateKeyUsagePeriod'; + ExtID == ?'id-ce-certificatePolicies'; + ExtID == ?'id-ce-policyMappings'; + ExtID == ?'id-ce-subjectAltName'; + ExtID == ?'id-ce-issuerAltName'; + ExtID == ?'id-ce-subjectDirectoryAttributes'; + ExtID == ?'id-ce-basicConstraints'; + ExtID == ?'id-ce-nameConstraints'; + ExtID == ?'id-ce-policyConstraints'; + ExtID == ?'id-ce-extKeyUsage'; + ExtID == ?'id-ce-cRLDistributionPoints'; + ExtID == ?'id-ce-inhibitAnyPolicy'; + ExtID == ?'id-ce-freshestCRL' -> + ExtAny = #'Extension-Any'{extnID = ExtID, + critical = Critical, + extnValue = Value}, + {ok, AnyEnc} = 'OTP-PUB-KEY':encode('Extension-Any', ExtAny), + {ok, ExtDec} = 'OTP-PUB-KEY':decode('OTPExtension', + list_to_binary(AnyEnc)), + + ExtValue = plain_to_otp_extension_value(ExtID, + ExtDec#'OTPExtension'.extnValue), + #'Extension'{extnID = ExtID, + critical = ExtDec#'OTPExtension'.critical, + extnValue = ExtValue}; + +plain_to_otp(#'Extension'{} = Ext) -> + Ext; + +plain_to_otp(#'AuthorityKeyIdentifier'{} = Ext) -> + CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, + Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = + plain_to_otp(CertIssuer)}; + + +plain_to_otp([{directoryName, Value}]) -> + [{directoryName, plain_to_otp(Value)}]; + +plain_to_otp(Value) -> + Value. + +otp_to_plain(#'Certificate'{tbsCertificate = TBSCert, + signatureAlgorithm = SigAlg, + signature = Signature} = Cert) -> + Cert#'Certificate'{tbsCertificate = otp_to_plain(TBSCert), + signatureAlgorithm = + otp_to_plain(SigAlg), + signature = otp_to_plain(Signature)}; + +otp_to_plain(#'TBSCertificate'{signature = Signature, + issuer = Issuer, + subject = Subject, + subjectPublicKeyInfo = SPubKeyInfo, + extensions = Extensions} = TBSCert) -> + + TBSCert#'TBSCertificate'{signature = otp_to_plain(Signature), + issuer = otp_to_plain(Issuer), + subject = + otp_to_plain(Subject), + subjectPublicKeyInfo = + otp_to_plain(SPubKeyInfo), + extensions = otp_to_plain_extensions(Extensions) + }; + +otp_to_plain(#'SignatureAlgorithm'{} = SignAlg) -> + {ok, EncSignAlg} = 'OTP-PUB-KEY':encode('SignatureAlgorithm', SignAlg), + {ok, #'SignatureAlgorithm-Any'{algorithm = Algorithm, + parameters = Params}} = + 'OTP-PUB-KEY':decode('SignatureAlgorithm-Any', + list_to_binary(EncSignAlg)), + #'AlgorithmIdentifier'{algorithm = Algorithm, + parameters = Params}; + +otp_to_plain({rdnSequence, SeqList}) when is_list(SeqList) -> + {rdnSequence, + lists:map(fun(Seq) -> + lists:map(fun(Element) -> + otp_to_plain(Element) + end, + Seq) + end, SeqList)}; + +otp_to_plain(#'AttributeTypeAndValue'{type = Type, value = Value}) -> + {ok, ATAVEnc} = + 'OTP-PUB-KEY':encode('OTPAttributeTypeAndValue', + #'OTPAttributeTypeAndValue'{type = Type, + value = Value}), + {ok, ATAVDec} = 'OTP-PUB-KEY':decode('AttributeTypeAndValue', + list_to_binary(ATAVEnc)), + ATAVDec; + +otp_to_plain(#'SubjectPublicKeyInfo'{algorithm = + #'SubjectPublicKeyInfoAlgorithm'{ + algorithm = Algo, + parameters = + Params}, + subjectPublicKey = PublicKey}) -> + + OtpAlgo = #'OTPOLDSubjectPublicKeyInfo_algorithm'{algo = Algo, + parameters = Params}, + OtpDec = #'OTPOLDSubjectPublicKeyInfo'{algorithm = OtpAlgo, + subjectPublicKey = PublicKey}, + {ok, OtpEnc} = + 'OTP-PUB-KEY':encode('OTPOLDSubjectPublicKeyInfo', OtpDec), + + {ok, AnyDec} = 'OTP-PUB-KEY':decode('OTPSubjectPublicKeyInfo-Any', + list_to_binary(OtpEnc)), + + #'OTPSubjectPublicKeyInfo-Any'{algorithm = #'PublicKeyAlgorithm'{ + algorithm = NewAlgo, + parameters = NewParams}, + subjectPublicKey = Bin} = AnyDec, + + #'SubjectPublicKeyInfo'{algorithm = + #'AlgorithmIdentifier'{ + algorithm = NewAlgo, + parameters = plain_key_params(NewParams)}, + subjectPublicKey = + {0, Bin} + }; + +otp_to_plain(#'Extension'{extnID = ExtID, + extnValue = Value} = Ext) -> + ExtValue = + otp_to_plain_extension_value(ExtID, Value), + + Ext#'Extension'{extnValue = ExtValue}; + +otp_to_plain(#'AuthorityKeyIdentifier'{} = Ext) -> + CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer, + Ext#'AuthorityKeyIdentifier'{authorityCertIssuer = + otp_to_plain(CertIssuer)}; + +otp_to_plain([{directoryName, Value}]) -> + [{directoryName, otp_to_plain(Value)}]; + +otp_to_plain(Value) -> + Value. + +plain_key_params('NULL') -> + <<5,0>>; +plain_key_params(Value) -> + Value. + +plain_to_otp_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> + plain_to_otp(Value); +plain_to_otp_extension_value(_, Value) -> + Value. + +plain_to_otp_extensions(Exts) when is_list(Exts) -> + lists:map(fun(Ext) -> plain_to_otp(Ext) end, Exts). + +otp_to_plain_extension_value(?'id-ce-authorityKeyIdentifier', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('AuthorityKeyIdentifier', + otp_to_plain(Value)), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-subjectKeyIdentifier', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectKeyIdentifier', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-keyUsage', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('KeyUsage', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-privateKeyUsagePeriod', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('PrivateKeyUsagePeriod', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-certificatePolicies', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('CertificatePolicies', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-policyMappings', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyMappings', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-subjectAltName', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectAltName', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-issuerAltName', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('IssuerAltName', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-subjectDirectoryAttributes', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectDirectoryAttributes', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-basicConstraints', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('BasicConstraints', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-nameConstraints', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('NameConstraints', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-policyConstraints', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyConstraints', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-extKeyUsage', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('ExtKeyUsage', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-cRLDistributionPoints', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('CRLDistributionPoints', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-inhibitAnyPolicy', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('InhibitAnyPolicy', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(?'id-ce-freshestCRL', Value) -> + {ok, Enc} = 'OTP-PUB-KEY':encode('FreshestCRL', Value), + otp_to_plain_extension_value_format(Enc); +otp_to_plain_extension_value(_Id, Value) -> + Value. + +otp_to_plain_extension_value_format(Value) -> + list_to_binary(Value). + +otp_to_plain_extensions(Exts) when is_list(Exts) -> + lists:map(fun(Ext) -> + otp_to_plain(Ext) + end, Exts). diff --git a/lib/public_key/src/pubkey_crypto.erl b/lib/public_key/src/pubkey_crypto.erl new file mode 100644 index 0000000000..fe4e97fcc5 --- /dev/null +++ b/lib/public_key/src/pubkey_crypto.erl @@ -0,0 +1,137 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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% +%% + +%% +%% Description: Functions that call the crypto driver. + +-module(pubkey_crypto). + +-include("public_key.hrl"). + +-export([encrypt_public/3, decrypt_private/3, + encrypt_private/3, decrypt_public/3, + sign/2, sign/3, verify/5]). + +-define(UINT32(X), X:32/unsigned-big-integer). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: encrypt(PlainText, Key, Padding) -> Encrypted +%% +%% PlainText = binary() +%% Key = rsa_public_key() | rsa_private_key() +%% Padding = rsa_pkcs1_padding | rsa_pkcs1_oaep_padding +%% Encrypted = binary() +%% +%% Description: Public key encrypts PlainText. +%%-------------------------------------------------------------------- +encrypt_public(PlainText, #'RSAPublicKey'{modulus=N,publicExponent=E},Padding) -> + crypto:rsa_public_encrypt(PlainText, [crypto:mpint(E),crypto:mpint(N)],Padding); +encrypt_public(PlainText, #'RSAPrivateKey'{modulus=N,publicExponent=E},Padding) -> + crypto:rsa_public_encrypt(PlainText, [crypto:mpint(E),crypto:mpint(N)],Padding). + +encrypt_private(PlainText, #'RSAPrivateKey'{modulus = N, + publicExponent = E, + privateExponent = D}, Padding) -> + crypto:rsa_private_encrypt(PlainText, [crypto:mpint(E), + crypto:mpint(N), + crypto:mpint(D)], Padding). + +%%-------------------------------------------------------------------- +%% Function: decrypt(CipherText, Key) -> PlainText +%% +%% ChipherText = binary() +%% Key = rsa_private_key() +%% Padding = rsa_pkcs1_padding | rsa_pkcs1_oaep_padding +%% PlainText = binary() +%% +%% Description: Uses private key to decrypt public key encrypted data. +%%-------------------------------------------------------------------- +decrypt_private(CipherText, + #'RSAPrivateKey'{modulus = N,publicExponent = E,privateExponent = D}, + Padding) -> + crypto:rsa_private_decrypt(CipherText, + [crypto:mpint(E), crypto:mpint(N),crypto:mpint(D)], + Padding). +decrypt_public(CipherText, #'RSAPublicKey'{modulus = N, publicExponent = E}, Padding) -> + crypto:rsa_public_decrypt(CipherText,[crypto:mpint(E), crypto:mpint(N)], Padding); +decrypt_public(CipherText, #'RSAPrivateKey'{modulus = N, publicExponent = E}, Padding) -> + crypto:rsa_public_decrypt(CipherText,[crypto:mpint(E), crypto:mpint(N)], Padding). + +%%-------------------------------------------------------------------- +%% Function: sign(PlainText, Key) -> +%% sign(DigestType, PlainText, Key) -> Signature +%% +%% DigestType = sha | md5 +%% PlainText = binary() +%% Key = rsa_private_key() | dsa_private_key() +%% Signature = binary() +%% +%% Description: Signs PlainText using Key. +%%-------------------------------------------------------------------- +sign(PlainText, Digest) -> + sign(sha, PlainText, Digest). + +sign(DigestType, PlainText, #'RSAPrivateKey'{modulus = N, publicExponent = E, + privateExponent = D}) -> + crypto:rsa_sign(DigestType, sized_binary(PlainText), [crypto:mpint(E), + crypto:mpint(N), + crypto:mpint(D)]); + +sign(sha, PlainText, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> + crypto:dss_sign(sized_binary(PlainText), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(X)]). + +%%-------------------------------------------------------------------- +%% Function: verify(DigestType, PlainText, Signature, Key) -> true | false +%% +%% DigestType = sha | md5 +%% PlainText = binary() +%% Signature = binary() +%% Key = rsa_public_key() | dsa_public_key() +%% +%% Description: Verifies the signature <Signature>. +%%-------------------------------------------------------------------- +verify(DigestType, PlainText, Signature, + #'RSAPublicKey'{modulus = Mod, publicExponent = Exp}, _) -> + crypto:rsa_verify(DigestType, + sized_binary(PlainText), + sized_binary(Signature), + [crypto:mpint(Exp), crypto:mpint(Mod)]); + +verify(sha, PlainText, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) -> + crypto:dss_verify(sized_binary(PlainText), + sized_binary(Signature), + [crypto:mpint(P), crypto:mpint(Q), + crypto:mpint(G), crypto:mpint(Key)]). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +sized_binary(Binary) when is_binary(Binary) -> + Size = size(Binary), + <<?UINT32(Size), Binary/binary>>; +sized_binary(List) -> + sized_binary(list_to_binary(List)). + diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl new file mode 100644 index 0000000000..abd46fa00e --- /dev/null +++ b/lib/public_key/src/pubkey_pem.erl @@ -0,0 +1,192 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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% +%% + +%% + +%%% Description: Reading and writing of PEM type encoded files. +%% PEM encoded files have the following structure: +%% +%% <text> +%% -----BEGIN SOMETHING-----<CR><LF> +%% <Base64 encoding line><CR><LF> +%% <Base64 encoding line><CR><LF> +%% ... +%% -----END SOMETHING-----<CR><LF> +%% <text> +%% +%% A file can contain several BEGIN/END blocks. Text lines between +%% blocks are ignored. +%% +%% The encoding is divided into lines separated by <NL>, and each line +%% is precisely 64 characters long (excluding the <NL> characters, +%% except the last line which 64 characters long or shorter. <NL> may +%% follow the last line. + +-module(pubkey_pem). + +-export([read_file/1, read_file/2, write_file/2, decode/2]). +-export([decode_key/2]). + +-define(ENCODED_LINE_LENGTH, 64). + +%%==================================================================== +%% Internal application API +%%==================================================================== +read_file(File) -> + read_file(File, no_passwd). + +read_file(File, Passwd) -> + {ok, Bin} = file:read_file(File), + decode(Bin, Passwd). + +write_file(File, Ds) -> + file:write_file(File, encode_file(Ds)). + +decode_key({_Type, Bin, not_encrypted}, _) -> + Bin; +decode_key({_Type, Bin, {Chipher,Salt}}, Password) -> + decode_key(Bin, Password, Chipher, Salt). + +decode(Bin, Passwd) -> + decode_file(split_bin(Bin), Passwd). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +split_bin(Bin) -> + split_bin(0, Bin). + +split_bin(N, Bin) -> + case Bin of + <<Line:N/binary, "\r\n", Rest/binary>> -> + [Line | split_bin(0, Rest)]; + <<Line:N/binary, "\n", Rest/binary>> -> + [Line | split_bin(0, Rest)]; + <<Line:N/binary>> -> + [Line]; + _ -> + split_bin(N+1, Bin) + end. + +decode_file(Bin, Passwd) -> + decode_file(Bin, [], [Passwd]). + +decode_file([<<"-----BEGIN CERTIFICATE REQUEST-----", _/binary>>|Rest], Ens, Info) -> + decode_file2(Rest, [], Ens, cert_req, Info); +decode_file([<<"-----BEGIN CERTIFICATE-----", _/binary>>|Rest], Ens, Info) -> + decode_file2(Rest, [], Ens, cert, Info); +decode_file([<<"-----BEGIN RSA PRIVATE KEY-----", _/binary>>|Rest], Ens, Info) -> + decode_file2(Rest, [], Ens, rsa_private_key, Info); +decode_file([<<"-----BEGIN DSA PRIVATE KEY-----", _/binary>>|Rest], Ens, Info) -> + decode_file2(Rest, [], Ens, dsa_private_key, Info); +decode_file([<<"-----BEGIN DH PARAMETERS-----", _/binary>>|Rest], Ens, Info) -> + decode_file2(Rest, [], Ens, dh_params, Info); +decode_file([_|Rest], Ens, Info) -> + decode_file(Rest, Ens, Info); +decode_file([], Ens, _Info) -> + {ok, lists:reverse(Ens)}. + +decode_file2([<<"Proc-Type: 4,ENCRYPTED", _/binary>>| Rest0], RLs, Ens, Tag, Info0) -> + [InfoLine|Rest] = Rest0, + Info = dek_info(InfoLine, Info0), + decode_file2(Rest, RLs, Ens, Tag, Info); +decode_file2([<<"-----END", _/binary>>| Rest], RLs, Ens, Tag, Info0) -> + Cs = erlang:iolist_to_binary(lists:reverse(RLs)), + Bin = base64:mime_decode(Cs), + case Info0 of + [Password, Cipher, SaltHex | Info1] -> + Salt = unhex(SaltHex), + Enc = {Cipher, Salt}, + Decoded = decode_key(Bin, Password, Cipher, Salt), + decode_file(Rest, [{Tag, Decoded, Enc}| Ens], Info1); + _ -> + decode_file(Rest, [{Tag, Bin, not_encrypted}| Ens], Info0) + end; +decode_file2([L|Rest], RLs, Ens, Tag, Info0) -> + decode_file2(Rest, [L|RLs], Ens, Tag, Info0); +decode_file2([], _, Ens, _, _) -> + {ok, lists:reverse(Ens)}. + +%% TODO Support same as decode_file +encode_file(Ds) -> + lists:map( + fun({cert, Bin}) -> + %% PKIX (X.509) + ["-----BEGIN CERTIFICATE-----\n", + b64encode_and_split(Bin), + "-----END CERTIFICATE-----\n\n"]; + ({cert_req, Bin}) -> + %% PKCS#10 + ["-----BEGIN CERTIFICATE REQUEST-----\n", + b64encode_and_split(Bin), + "-----END CERTIFICATE REQUEST-----\n\n"]; + ({rsa_private_key, Bin}) -> + %% PKCS#? + ["XXX Following key assumed not encrypted\n", + "-----BEGIN RSA PRIVATE KEY-----\n", + b64encode_and_split(Bin), + "-----END RSA PRIVATE KEY-----\n\n"] + end, Ds). + +dek_info(Line0, Info) -> + Line = binary_to_list(Line0), + [_, DekInfo0] = string:tokens(Line, ": "), + DekInfo1 = string:tokens(DekInfo0, ",\n"), + Info ++ DekInfo1. + +unhex(S) -> + unhex(S, []). + +unhex("", Acc) -> + lists:reverse(Acc); +unhex([D1, D2 | Rest], Acc) -> + unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]). + +decode_key(Data, no_passwd, _Alg, _Salt) -> + Data; +decode_key(Data, Password, "DES-CBC", Salt) -> + Key = password_to_key(Password, Salt, 8), + IV = Salt, + crypto:des_cbc_decrypt(Key, IV, Data); +decode_key(Data, Password, "DES-EDE3-CBC", Salt) -> + Key = password_to_key(Password, Salt, 24), + IV = Salt, + <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key, + crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data). + +password_to_key(Data, Salt, KeyLen) -> + <<Key:KeyLen/binary, _/binary>> = + password_to_key(<<>>, Data, Salt, KeyLen, <<>>), + Key. + +password_to_key(_, _, _, Len, Acc) when Len =< 0 -> + Acc; +password_to_key(Prev, Data, Salt, Len, Acc) -> + M = crypto:md5([Prev, Data, Salt]), + password_to_key(M, Data, Salt, Len - size(M), <<Acc/binary, M/binary>>). + +b64encode_and_split(Bin) -> + split_lines(base64:encode(Bin)). + +split_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) -> + [Text, $\n | split_lines(Rest)]; +split_lines(Bin) -> + [Bin, $\n]. + diff --git a/lib/public_key/src/public_key.app.src b/lib/public_key/src/public_key.app.src new file mode 100644 index 0000000000..edede7c874 --- /dev/null +++ b/lib/public_key/src/public_key.app.src @@ -0,0 +1,16 @@ +{application, public_key, + [{description, "Public key infrastructure"}, + {vsn, "%VSN%"}, + {modules, [ + public_key, + pubkey_pem, + pubkey_crypto, + pubkey_cert, + pubkey_cert_records, + 'OTP-PUB-KEY' + ]}, + {applications, [crypto, kernel, stdlib]}, + {registered, []}, + {env, []} + ] +}.
\ No newline at end of file diff --git a/lib/public_key/src/public_key.appup.src b/lib/public_key/src/public_key.appup.src new file mode 100644 index 0000000000..8d33482f11 --- /dev/null +++ b/lib/public_key/src/public_key.appup.src @@ -0,0 +1,6 @@ +%% -*- erlang -*- +{"%VSN%", + [ + ], + [ + ]}. diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl new file mode 100644 index 0000000000..b0b0b7a832 --- /dev/null +++ b/lib/public_key/src/public_key.erl @@ -0,0 +1,411 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. 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(public_key). + +-include("public_key.hrl"). + +-export([decode_private_key/1, decode_private_key/2, + decrypt_private/2, decrypt_private/3, encrypt_public/2, + encrypt_public/3, decrypt_public/2, decrypt_public/3, + encrypt_private/2, encrypt_private/3, + sign/2, sign/3, + verify_signature/3, verify_signature/4, verify_signature/5, + pem_to_der/1, pem_to_der/2, + pkix_decode_cert/2, pkix_encode_cert/1, + pkix_is_self_signed/1, pkix_is_fixed_dh_cert/1, + pkix_issuer_id/2, + pkix_is_issuer/2, pkix_normalize_general_name/1, + pkix_path_validation/3 + ]). + +%%==================================================================== +%% API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: decode_private_key(KeyInfo [,Password]) -> +%% {ok, PrivateKey} | {error, Reason} +%% +%% KeyInfo = {Type, der_bin(), ChipherInfo} - as returned from +%% pem_to_der/[1,2] for private keys +%% Type = rsa_private_key | dsa_private_key +%% ChipherInfo = opaque() | no_encryption +%% +%% Description: Decodes an asn1 der encoded private key. +%%-------------------------------------------------------------------- +decode_private_key(KeyInfo) -> + decode_private_key(KeyInfo, no_passwd). + +decode_private_key(KeyInfo = {rsa_private_key, _, _}, Password) -> + DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), + 'OTP-PUB-KEY':decode('RSAPrivateKey', DerEncoded); +decode_private_key(KeyInfo = {dsa_private_key, _, _}, Password) -> + DerEncoded = pubkey_pem:decode_key(KeyInfo, Password), + 'OTP-PUB-KEY':decode('DSAPrivateKey', DerEncoded). + +%%-------------------------------------------------------------------- +%% Function: decrypt_private(CipherText, Key) -> +%% decrypt_private(CipherText, Key, Options) -> PlainTex +%% decrypt_public(CipherText, Key) -> +%% decrypt_public(CipherText, Key, Options) -> PlainTex +%% +%% CipherText = binary() +%% Key = rsa_key() +%% PlainText = binary() +%% +%% Description: Decrypts <CipherText>. +%%-------------------------------------------------------------------- +decrypt_private(CipherText, Key) -> + decrypt_private(CipherText, Key, []). +decrypt_private(CipherText, Key, Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), + pubkey_crypto:decrypt_private(CipherText, Key, Padding). + +decrypt_public(CipherText, Key) -> + decrypt_public(CipherText, Key, []). +decrypt_public(CipherText, Key, Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding), + pubkey_crypto:decrypt_public(CipherText, Key, Padding). + +%%-------------------------------------------------------------------- +%% Function: encrypt_public(PlainText, Key, Options) -> CipherText +%% encrypt_private(PlainText, Key, Options) -> CipherText +%% +%% PlainText = iolist() +%% Key = rsa_private_key() +%% CipherText = binary() +%% +%% Description: Encrypts <Plain> +%%-------------------------------------------------------------------- +encrypt_public(PlainText, Key) -> + encrypt_public(PlainText, Key, []). +encrypt_public(PlainText, Key, Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), + pubkey_crypto:encrypt_public(PlainText, Key, Padding). + +encrypt_private(PlainText, Key) -> + encrypt_private(PlainText, Key, []). +encrypt_private(PlainText, Key, Options) -> + Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding), + pubkey_crypto:encrypt_private(PlainText, Key, Padding). + +%%-------------------------------------------------------------------- +%% Function: pem_to_der(CertSource) -> +%% pem_to_der(CertSource, Password) -> {ok, [Entry]} | +%% {error, Reason} +%% +%% CertSource = File | CertData +%% CertData = binary() +%% File = path() +%% Password = string() +%% Entry = {entry_type(), der_bin(), ChipherInfo} +%% ChipherInfo = opague() | no_encryption +%% der_bin() = binary() +%% entry_type() = cert | cert_req | rsa_private_key | dsa_private_key +%% dh_params +%% +%% Description: decode PEM binary data or a PEM file and return +%% entries as asn1 der encoded entities. Currently supported entry +%% types are certificates, certificate requests, rsa private keys and +%% dsa private keys. In the case of a key entry ChipherInfo will be +%% used by decode_private_key/2 if the key is protected by a password. +%%-------------------------------------------------------------------- +pem_to_der(CertSource) -> + pem_to_der(CertSource, no_passwd). + +pem_to_der(File, Password) when is_list(File) -> + pubkey_pem:read_file(File, Password); +pem_to_der(PemBin, Password) when is_binary(PemBin) -> + pubkey_pem:decode(PemBin, Password). + +%%-------------------------------------------------------------------- +%% Function: pkix_decode_cert(BerCert, Type) -> {ok, Cert} | {error, Reason} +%% +%% BerCert = binary() +%% Type = plain | otp +%% Cert = certificate() +%% +%% Description: Decodes an asn1 ber encoded pkix certificate. +%% otp - Uses OTP-PKIX.asn1 to decode known extensions and +%% enhance the signature field in #'Certificate'{} and '#TBSCertificate'{}. +%%-------------------------------------------------------------------- +pkix_decode_cert(BinCert, Type) -> + pubkey_cert_records:decode_cert(BinCert, Type). + +%%-------------------------------------------------------------------- +%% Function: pkix_encode_cert(Cert) -> {ok, binary()} | {error, Reason} +%% +%% Cert = #'Certificate'{} +%% +%% Description: Encodes a certificate record using asn1. +%%-------------------------------------------------------------------- +pkix_encode_cert(Cert) -> + pubkey_cert_records:encode_cert(Cert). + +%%-------------------------------------------------------------------- +%% Function: pkix_path_validation(TrustedCert, CertChain, Options) -> +%% {ok, {{algorithm(), public_key(), public_key_params()} policy_tree()}} | +%% {error, Reason} +%% +%% Description: Performs a bacis path validation according to RFC 3280. +%%-------------------------------------------------------------------- +pkix_path_validation(TrustedCert, CertChain, Options) + when is_binary(TrustedCert) -> + {ok, OtpCert} = pkix_decode_cert(TrustedCert, otp), + pkix_path_validation(OtpCert, CertChain, Options); + +pkix_path_validation(#'OTPCertificate'{} = TrustedCert, CertChain, Options) + when is_list(CertChain), is_list(Options) -> + MaxPathDefault = length(CertChain), + 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). +%%-------------------------------------------------------------------- +%% Function: pkix_is_fixed_dh_cert(Cert) -> true | false +%% +%% Description: Checks if a Certificate is a fixed Diffie-Hellman Cert +%%-------------------------------------------------------------------- +pkix_is_fixed_dh_cert(#'OTPCertificate'{} = OTPCert) -> + pubkey_cert:is_fixed_dh_cert(OTPCert); +pkix_is_fixed_dh_cert(Cert) when is_binary(Cert) -> + {ok, OtpCert} = pkix_decode_cert(Cert, otp), + pkix_is_fixed_dh_cert(OtpCert). + +%%-------------------------------------------------------------------- +%% Function: pkix_is_self_signed(Cert) -> true | false +%% +%% Description: Checks if a Certificate is self signed. +%%-------------------------------------------------------------------- +pkix_is_self_signed(#'OTPCertificate'{} = OTPCert) -> + pubkey_cert:is_self_signed(OTPCert); +pkix_is_self_signed(Cert) when is_binary(Cert) -> + {ok, OtpCert} = pkix_decode_cert(Cert, otp), + pkix_is_self_signed(OtpCert). + +%%-------------------------------------------------------------------- +%% Function: pkix_issuer_id(Cert) -> {ok, {SerialNr, Issuer}} | {error, Reason} +%% +%% Cert = asn1_der_encoded() | 'OTPCertificate'{} +%% +%% Description: Returns the issuer id. +%%-------------------------------------------------------------------- +pkix_issuer_id(#'OTPCertificate'{} = OtpCert, self) -> + pubkey_cert:issuer_id(OtpCert, self); + +pkix_issuer_id(#'OTPCertificate'{} = OtpCert, other) -> + pubkey_cert:issuer_id(OtpCert, other); + +pkix_issuer_id(Cert, Signed) when is_binary(Cert) -> + {ok, OtpCert} = pkix_decode_cert(Cert, otp), + pkix_issuer_id(OtpCert, Signed). + +%%-------------------------------------------------------------------- +%% Function: pkix_is_issuer(Cert, IssuerCert) -> true | false +%% +%% Cert = asn1_der_encoded() | 'OTPCertificate'{} +%% IssuerCert = asn1_der_encoded() | 'OTPCertificate'{} +%% +%% Description: Checks if <IssuerCert> issued <Cert>. +%%-------------------------------------------------------------------- +pkix_is_issuer(Cert, IssuerCert) when is_binary(Cert) -> + {ok, OtpCert} = pkix_decode_cert(Cert, otp), + pkix_is_issuer(OtpCert, IssuerCert); + +pkix_is_issuer(Cert, IssuerCert) when is_binary(IssuerCert) -> + {ok, OtpIssuerCert} = pkix_decode_cert(IssuerCert, otp), + pkix_is_issuer(Cert, OtpIssuerCert); + +pkix_is_issuer(#'OTPCertificate'{tbsCertificate = TBSCert}, + #'OTPCertificate'{tbsCertificate = Candidate}) -> + pubkey_cert:is_issuer(TBSCert#'OTPTBSCertificate'.issuer, + Candidate#'OTPTBSCertificate'.subject). + +%%-------------------------------------------------------------------- +%% Function: pkix_normalize_general_name(Issuer) -> +%% +%% Issuer = general_name() - see PKIX +%% +%% Description: Normalizes a general name so that it can be easily +%% compared to another genral name. +%%-------------------------------------------------------------------- +pkix_normalize_general_name(Issuer) -> + pubkey_cert:normalize_general_name(Issuer). + +%%-------------------------------------------------------------------- +%% Function:sign(Msg, Key) -> {ok, Signature} +%% sign(Msg, Key, KeyParams) -> {ok, Signature} +%% +%% Msg = binary() | #'TBSCertificate'{} +%% Key = private_key() +%% KeyParams = key_params() +%% Signature = binary() +%% +%% Description: Signs plaintext Msg or #TBSCertificate{}, in the later +%% case a der encoded "#Certificate{}" will be returned. +%%-------------------------------------------------------------------- +sign(Msg, #'RSAPrivateKey'{} = Key) when is_binary(Msg) -> + pubkey_crypto:sign(Msg, Key); + +sign(Msg, #'DSAPrivateKey'{} = Key) when is_binary(Msg) -> + pubkey_crypto:sign(Msg, Key); + +sign(#'OTPTBSCertificate'{signature = SigAlg} = TBSCert, Key) -> + Msg = pubkey_cert_records:encode_tbs_cert(TBSCert), + DigestType = pubkey_cert:digest_type(SigAlg), + Signature = pubkey_crypto:sign(DigestType, Msg, Key), + Cert = #'OTPCertificate'{tbsCertificate= TBSCert, + signatureAlgorithm = SigAlg, + signature = {0, Signature} + }, + pkix_encode_cert(Cert). + +sign(DigestType, Msg, Key) -> + pubkey_crypto:sign(DigestType, Msg, Key). + +%%-------------------------------------------------------------------- +%% Function: verify_signature(PlainText, DigestType, Signature, Key) -> +%% verify_signature(PlainText, DigestType, +%% Signature, Key, KeyParams) -> +%% verify_signature(DerCert, Key, KeyParams) -> +%% +%% PlainText = binary() +%% DigestType = md5 | sha +%% DerCert = asn1_der_encoded() +%% Signature = binary() +%% Key = public_key() +%% KeyParams = key_params() +%% Verified = boolean() +%% +%% Description: Verifies the signature <Signature>. +%%-------------------------------------------------------------------- +verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key) + when is_binary(PlainText), is_binary(Signature), DigestType == sha; + DigestType == md5 -> + pubkey_crypto:verify(DigestType, PlainText, Signature, Key, undefined). + +verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key, + KeyParams) + when is_binary(PlainText), is_binary(Signature), DigestType == sha; + DigestType == md5 -> + pubkey_crypto:verify(DigestType, PlainText, Signature, Key, KeyParams); +verify_signature(PlainText, sha, Signature, Key, #'Dss-Parms'{} = KeyParams) + when is_binary(PlainText), is_binary(Signature), is_integer(Key) -> + pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams). + +verify_signature(DerCert, Key, #'Dss-Parms'{} = KeyParams) + when is_binary(DerCert), is_integer(Key) -> + pubkey_cert:verify_signature(DerCert, Key, KeyParams); +verify_signature(DerCert, #'RSAPublicKey'{} = Key, KeyParams) + when is_binary(DerCert) -> + pubkey_cert:verify_signature(DerCert, Key, KeyParams). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +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 + }, _, _) -> + {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree, AccErrors}}; + +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 + #path_validation_state{} = NewValidationState -> + path_validation(Rest, NewValidationState, Fun, Verify) + catch + throw:Reason -> + {error, Reason} + end; + +path_validation(_, _, _, true) -> + {error, {bad_cert, max_path_length_reached}}; + +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, + working_public_key_parameters = + KeyParams, + permitted_subtrees = Permit, + excluded_subtrees = Exclude, + last_cert = Last, + user_state = UserState0, + acc_errors = AccErr0} = + ValidationState0, ValidateExtensionFun, Verify) -> + {ok, 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), + + AccErr3 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last, + AccErr2, Verify), + AccErr4 = + pubkey_cert:validate_revoked_status(OtpCert, Verify, AccErr3), + + {ValidationState1, UnknownExtensions0, AccErr5} = + pubkey_cert:validate_extensions(OtpCert, ValidationState0, Verify, + AccErr4), + %% 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), + ValidationState = + ValidationState1#path_validation_state{user_state = UserState, + acc_errors = AccErr}, + pubkey_cert:prepare_for_next_cert(OtpCert, ValidationState). |