aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public_key/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/public_key/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/public_key/src')
-rw-r--r--lib/public_key/src/Makefile112
-rw-r--r--lib/public_key/src/pubkey_cert.erl988
-rw-r--r--lib/public_key/src/pubkey_cert_records.erl538
-rw-r--r--lib/public_key/src/pubkey_crypto.erl137
-rw-r--r--lib/public_key/src/pubkey_pem.erl192
-rw-r--r--lib/public_key/src/public_key.app.src16
-rw-r--r--lib/public_key/src/public_key.appup.src6
-rw-r--r--lib/public_key/src/public_key.erl411
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).