%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%

-module(public_key).

-include("public_key.hrl").

-export([pem_decode/1, pem_encode/1, 
	 der_decode/2, der_encode/2,
	 pem_entry_decode/1,
	 pem_entry_decode/2,
	 pem_entry_encode/2,
	 pem_entry_encode/3,
	 pkix_decode_cert/2, pkix_encode/3,
	 encrypt_private/2, encrypt_private/3,
	 decrypt_private/2, decrypt_private/3, 
	 encrypt_public/2, encrypt_public/3, 
	 decrypt_public/2, decrypt_public/3,
	 dh_gex_group/4,
	 dh_gex_group_sizes/0,
	 sign/3, sign/4, verify/4, verify/5,
	 generate_key/1,
	 compute_key/2, compute_key/3,
	 pkix_sign/2, pkix_verify/2,
	 pkix_hash_type/1,
	 pkix_sign_types/1,
	 pkix_is_self_signed/1, 
	 pkix_is_fixed_dh_cert/1,
	 pkix_is_issuer/2,
	 pkix_issuer_id/2,
	 pkix_subject_id/1,
         pkix_normalize_name/1,
	 pkix_path_validation/3,
	 pkix_verify_hostname/2, pkix_verify_hostname/3,
         pkix_verify_hostname_match_fun/1,
	 pkix_crls_validate/3,
	 pkix_dist_point/1,
	 pkix_dist_points/1,
	 pkix_match_dist_point/2,
	 pkix_crl_verify/2,
	 pkix_crl_issuer/1,
	 short_name_hash/1,
         pkix_test_data/1,
         pkix_test_root_cert/2,
         pkix_ocsp_validate/5,
         ocsp_responder_id/1,
         ocsp_extensions/1,
         cacerts_get/0,
         cacerts_load/0,
         cacerts_load/1,
         cacerts_clear/0
	]).

%%----------------
%% Moved to ssh
-removed([{ssh_decode,2, "use ssh_file:decode/2 instead"},
          {ssh_encode,2, "use ssh_file:encode/2 instead"},
          {ssh_hostkey_fingerprint,1, "use ssh:hostkey_fingerprint/1 instead"},
          {ssh_hostkey_fingerprint,2, "use ssh:hostkey_fingerprint/2 instead"}
         ]).

-export([ssh_curvename2oid/1, oid2ssh_curvename/1]).
%% When removing for OTP-25.0, remember to also remove
%%   - most of pubkey_ssh.erl except
%%       + dh_gex_group/4
%%       + dh_gex_group_sizes/0
%%   - pubkey_pem:pem_start({no_asn1, new_openssh})
%%   - pubkey_pem:pem_end(<<"-----BEGIN OPENSSH PRIVATE KEY-----">>)
%%   - pubkey_pem:asn1_type(<<"-----BEGIN OPENSSH PRIVATE KEY-----">>) -> {no_asn1, new_openssh}

%%----------------------------------------------------------------
%% Types
-export_type([public_key/0,
              private_key/0,
              pem_entry/0,
	      pki_asn1_type/0,
              asn1_type/0,
              der_encoded/0,
              key_params/0,
              digest_type/0,
              issuer_name/0,
              cert/0,
              combined_cert/0,
              cert_id/0,
              oid/0,
              cert_opt/0,
              chain_opts/0,
              conf_opt/0,
              test_config/0,
              test_root_cert/0]).


-type public_key()           ::  rsa_public_key() | rsa_pss_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
-type private_key()          ::  rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
-type rsa_public_key()       ::  #'RSAPublicKey'{}.
-type rsa_private_key()      ::  #'RSAPrivateKey'{}. 
-type dss_public_key()       :: integer().
-type rsa_pss_public_key()   ::  {rsa_pss_public_key(), #'RSASSA-PSS-params'{}}.
-type rsa_pss_private_key()  ::  { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}.
-type dsa_private_key()      ::  #'DSAPrivateKey'{}.
-type dsa_public_key()       :: {dss_public_key(), #'Dss-Parms'{}}.
-type public_key_params()    :: 'NULL' | #'RSASSA-PSS-params'{} |  {namedCurve, oid()} | #'ECParameters'{} | #'Dss-Parms'{}.
-type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}.
-type ecpk_parameters_api() :: ecpk_parameters() | #'ECParameters'{} | {namedCurve, Name::crypto:ec_named_curve()}.
-type ec_public_key()        :: {#'ECPoint'{}, ecpk_parameters_api()}.
-type ec_private_key()       :: #'ECPrivateKey'{}.
-type ed_public_key()        :: {#'ECPoint'{}, ed_params()}.
-type ed_private_key()       :: #'ECPrivateKey'{parameters :: ed_params()}.
-type ed_oid_name()            ::  'id-Ed25519' | 'id-Ed448'.
-type ed_params()            ::  {namedCurve, ed_oid_name()}.
-type key_params()           :: #'DHParameter'{} | {namedCurve, oid()} | #'ECParameters'{} | 
                                {rsa, Size::integer(), PubExp::integer()}. 
-type der_encoded()          :: binary().
-type pki_asn1_type()        ::  'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey'
			       | 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter'
                               | 'SubjectPublicKeyInfo' | 'PrivateKeyInfo' | 
				 'CertificationRequest' | 'CertificateList' |
				 'ECPrivateKey' | 'EcpkParameters'.
-type pem_entry()            :: {pki_asn1_type(), 
				 der_or_encrypted_der(),
				 not_encrypted | cipher_info()
				}.
-type der_or_encrypted_der() :: binary().
-type cipher_info()          :: {cipher(),
                                 cipher_info_params()} .
-type cipher()               :: string() . % "RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC", 
-type cipher_info_params()   :: salt()
                              | {#'PBEParameter'{}, digest_type()}
                              | #'PBES2-params'{} .

-type salt()                 :: binary(). % crypto:strong_rand_bytes(8)
-type asn1_type()            :: atom(). %% see "OTP-PUB-KEY.hrl
-type digest_type()          :: none % None is for backwards compatibility
                              | sha1 % Backwards compatibility
                              | crypto:rsa_digest_type()
                              | crypto:dss_digest_type()
                              | crypto:ecdsa_digest_type().
-type crl_reason()           ::  unspecified | keyCompromise | cACompromise | affiliationChanged | superseded
			       | cessationOfOperation | certificateHold | privilegeWithdrawn |  aACompromise.
-type oid()                  :: tuple().
-type cert_id()              :: {SerialNr::integer(), issuer_name()} .
-type issuer_name()          :: {rdnSequence,[[#'AttributeTypeAndValue'{}]]} .
-type bad_cert_reason()      :: cert_expired | invalid_issuer | invalid_signature | name_not_permitted | missing_basic_constraint | invalid_key_usage | {revoked, crl_reason()} | atom().

-type combined_cert()        :: #cert{}.
-type cert()                 :: der_cert() | otp_cert().
-type der_cert()             :: der_encoded().
-type otp_cert()             :: #'OTPCertificate'{}.
-type public_key_info()      :: {key_oid_name(),  rsa_public_key() | #'ECPoint'{} | dss_public_key(),  public_key_params()}.
-type key_oid_name()              :: 'rsaEncryption' | 'id-RSASSA-PSS' | 'id-ecPublicKey' | 'id-Ed25519' | 'id-Ed448' | 'id-dsa'.
-type cert_opt()  :: {digest, public_key:digest_type()} |
                     {key, public_key:key_params() | public_key:private_key()} |
                     {validity, {From::erlang:timestamp(), To::erlang:timestamp()}} |
                     {extensions, [#'Extension'{}]}.
-type chain_end()   :: root | peer.
-type chain_opts()  :: #{chain_end() := [cert_opt()],  intermediates =>  [[cert_opt()]]}.
-type conf_opt()    :: {cert, public_key:der_encoded()} |
                       {key,  public_key:private_key()} |
                       {cacerts, [public_key:der_encoded()]}.
-type test_config() ::
        #{server_config := [conf_opt()],  client_config :=  [conf_opt()]}.
-type test_root_cert() ::
        #{cert := der_encoded(), key := public_key:private_key()}.

-define(UINT32(X), X:32/unsigned-big-integer).
-define(DER_NULL, <<5, 0>>).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
-spec pem_decode(binary()) -> [pem_entry()].
%%
%% Description: Decode PEM binary data and return
%% entries as asn1 der encoded entities. 
%%--------------------------------------------------------------------
pem_decode(PemBin) when is_binary(PemBin) ->
    pubkey_pem:decode(PemBin).

%%--------------------------------------------------------------------
-spec pem_encode([pem_entry()]) -> binary().
%%
%% Description: Creates a PEM binary.
%%--------------------------------------------------------------------
pem_encode(PemEntries) when is_list(PemEntries) ->
    iolist_to_binary(pubkey_pem:encode(PemEntries)).

%%--------------------------------------------------------------------
%% Description: Decodes a pem entry. pem_decode/1 returns a list of
%% pem entries.
%%--------------------------------------------------------------------
-spec pem_entry_decode(PemEntry) -> term() when PemEntry :: pem_entry() .

pem_entry_decode({'SubjectPublicKeyInfo', Der, _}) ->
    {_, {'AlgorithmIdentifier', AlgId, Params}, Key0}
        = der_decode('SubjectPublicKeyInfo', Der),
    KeyType = pubkey_cert_records:supportedPublicKeyAlgorithms(AlgId),
    case KeyType of
        'RSAPublicKey' ->
            der_decode(KeyType, Key0);
        'DSAPublicKey' ->
            {params, DssParams} = der_decode('DSAParams', Params),
            {der_decode(KeyType, Key0), DssParams};
        'ECPoint' ->
	    ECCParams = der_decode('EcpkParameters', Params),
            {#'ECPoint'{point = Key0}, ECCParams}
    end;
pem_entry_decode({Asn1Type, Der, not_encrypted}) when is_atom(Asn1Type),
						      is_binary(Der) ->
    der_decode(Asn1Type, Der).

-spec pem_entry_decode(PemEntry, Password) -> term() when
      PemEntry :: pem_entry(),
      Password :: iodata() | fun(() -> iodata()).
pem_entry_decode(PemEntry, PasswordFun) when is_function(PasswordFun) ->
     pem_entry_decode(PemEntry, PasswordFun());
pem_entry_decode({Asn1Type, Der, not_encrypted}, _) when is_atom(Asn1Type),
							 is_binary(Der) ->
    der_decode(Asn1Type, Der);
pem_entry_decode({Asn1Type, CryptDer, {Cipher, #'PBES2-params'{}}} = PemEntry, 
		 Password) when is_atom(Asn1Type) andalso
				is_binary(CryptDer) andalso
				is_list(Cipher) ->
    do_pem_entry_decode(PemEntry, Password);
pem_entry_decode({Asn1Type, CryptDer, {Cipher, {#'PBEParameter'{},_}}} = PemEntry, 
 		 Password) when is_atom(Asn1Type) andalso
 				is_binary(CryptDer) andalso
 				is_list(Cipher) andalso
				is_list(Password) ->
    do_pem_entry_decode(PemEntry, Password);
pem_entry_decode({Asn1Type, CryptDer, {Cipher, Salt}} = PemEntry, 
		 Password) when is_atom(Asn1Type) andalso
				is_binary(CryptDer) andalso
				is_list(Cipher) andalso
				is_binary(Salt) andalso
				((erlang:byte_size(Salt) == 8) or (erlang:byte_size(Salt) == 16)) andalso
				is_list(Password) ->
    do_pem_entry_decode(PemEntry, Password).

%%--------------------------------------------------------------------
%%
%% Description: Creates a pem entry that can be feed to pem_encode/1.
%%--------------------------------------------------------------------
-spec pem_entry_encode(Asn1Type, Entity) -> pem_entry() when Asn1Type :: pki_asn1_type(),
                                                             Entity :: term() .

pem_entry_encode('SubjectPublicKeyInfo', Entity=#'RSAPublicKey'{}) ->
    KeyDer = der_encode('RSAPublicKey', Entity),
    Spki = subject_public_key_info(#'AlgorithmIdentifier'{algorithm = ?'rsaEncryption',
                                                          parameters =?DER_NULL}, KeyDer),
    pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode('SubjectPublicKeyInfo',
                 {DsaInt, Params=#'Dss-Parms'{}}) when is_integer(DsaInt) ->
    KeyDer = der_encode('DSAPublicKey', DsaInt),
    ParamDer = der_encode('DSAParams', {params, Params}),
    Spki = subject_public_key_info(#'AlgorithmIdentifier'{algorithm =?'id-dsa',
                                                          parameters = ParamDer},
                                   KeyDer),
    pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode('SubjectPublicKeyInfo',
		 {#'ECPoint'{point = Key}, {namedCurve, ?'id-Ed25519' = ID}}) when is_binary(Key)->
    Spki = subject_public_key_info(#'AlgorithmIdentifier'{algorithm = ID}, Key),
    pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode('SubjectPublicKeyInfo',
		 {#'ECPoint'{point = Key}, {namedCurve, ?'id-Ed448' = ID}}) when is_binary(Key)->
    Spki = subject_public_key_info(#'AlgorithmIdentifier'{algorithm = ID}, Key),
    pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode('SubjectPublicKeyInfo',
		 {#'ECPoint'{point = Key}, ECParam}) when is_binary(Key)->
    Params = der_encode('EcpkParameters',ECParam),
    Spki = subject_public_key_info(#'AlgorithmIdentifier'{algorithm =?'id-ecPublicKey',
                                                          parameters = Params},
                                   Key),
    pem_entry_encode('SubjectPublicKeyInfo', Spki);
pem_entry_encode(Asn1Type, Entity)  when is_atom(Asn1Type) ->
    Der = der_encode(Asn1Type, Entity),
    {Asn1Type, Der, not_encrypted}.

-spec pem_entry_encode(Asn1Type, Entity, InfoPwd) ->
                              pem_entry() when Asn1Type :: pki_asn1_type(),
                                               Entity :: term(),
                                               InfoPwd :: {CipherInfo,Password},
                                               CipherInfo :: cipher_info(),
                                               Password :: iodata() .
pem_entry_encode(Asn1Type, Entity, {{Cipher, #'PBES2-params'{}} = CipherInfo, 
				    Password}) when is_atom(Asn1Type) andalso
						    is_list(Password) andalso
						    is_list(Cipher) ->
    do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password);
pem_entry_encode(Asn1Type, Entity, {{Cipher,
 				     {#'PBEParameter'{}, _}} = CipherInfo,
 				    Password}) when is_atom(Asn1Type) andalso
 						    is_list(Password) andalso
 						    is_list(Cipher) ->
    do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password);
pem_entry_encode(Asn1Type, Entity, {{Cipher, Salt} = CipherInfo, 
				    Password}) when is_atom(Asn1Type) andalso
						    is_list(Password) andalso
						    is_list(Cipher) andalso
						    is_binary(Salt) andalso
						    ((erlang:byte_size(Salt) == 8) or
						     (erlang:byte_size(Salt) == 16)) ->
    do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password).
    
%%--------------------------------------------------------------------
-spec der_decode(Asn1Type, Der) -> Entity when Asn1Type :: asn1_type(),
                                               Der :: der_encoded(),
                                               Entity :: term().
%%
%% Description: Decodes a public key asn1 der encoded entity.
%%--------------------------------------------------------------------
der_decode(Asn1Type, Der) when (((Asn1Type == 'PrivateKeyInfo')
                                 orelse
                                   (Asn1Type == 'EncryptedPrivateKeyInfo'))
                                andalso is_binary(Der)) ->
    try
	{ok, Decoded} = 'PKCS-FRAME':decode(Asn1Type, Der),
	der_priv_key_decode(Decoded)
    catch
	error:{badmatch, {error, _}} = Error ->
            handle_pkcs_frame_error(Asn1Type, Der, Error)
    end;

der_decode(Asn1Type, Der) when is_atom(Asn1Type), is_binary(Der) ->
    try 
	{ok, Decoded} = 'OTP-PUB-KEY':decode(Asn1Type, Der),
	Decoded
    catch	    
	error:{badmatch, {error, _}} = Error ->
	    erlang:error(Error)
    end.

handle_pkcs_frame_error('PrivateKeyInfo', Der, _) ->
    try
	{ok, Decoded} = 'PKCS-FRAME':decode('OneAsymmetricKey', Der),
	der_priv_key_decode(Decoded)
    catch
	error:{badmatch, {error, _}} = Error ->
	    erlang:error(Error)
    end;
handle_pkcs_frame_error(_, _, Error) ->
    erlang:error(Error).

der_priv_key_decode(#'PrivateKeyInfo'{version = v1,
                                      privateKeyAlgorithm =
                                          #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey',
                                                                                parameters = {asn1_OPENTYPE, Parameters}},
                                      privateKey = PrivKey}) ->
    EcPrivKey = der_decode('ECPrivateKey', PrivKey),
    EcPrivKey#'ECPrivateKey'{parameters = der_decode('EcpkParameters', Parameters)};
der_priv_key_decode(#'PrivateKeyInfo'{version = v1,
                                      privateKeyAlgorithm =#'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = CurveOId},
                                      privateKey = CurvePrivKey}) when
      CurveOId == ?'id-Ed25519'orelse
      CurveOId == ?'id-Ed448' ->
    PrivKey = der_decode('CurvePrivateKey', CurvePrivKey),
    #'ECPrivateKey'{version = 1, parameters = {namedCurve, CurveOId}, privateKey = PrivKey};
der_priv_key_decode(#'OneAsymmetricKey'{
                       privateKeyAlgorithm = #'OneAsymmetricKey_privateKeyAlgorithm'{algorithm = CurveOId},
                       privateKey = CurvePrivKey,
                       attributes = Attr,
                       publicKey = PubKey}) when
      CurveOId == ?'id-Ed25519'orelse
      CurveOId == ?'id-Ed448' ->
    PrivKey = der_decode('CurvePrivateKey', CurvePrivKey),
    #'ECPrivateKey'{version = 2, parameters = {namedCurve, CurveOId}, privateKey = PrivKey,
                    attributes = Attr,
                    publicKey = PubKey};
der_priv_key_decode({'PrivateKeyInfo', v1,
	{'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', _}, PrivKey, _}) ->
	der_decode('RSAPrivateKey', PrivKey);
der_priv_key_decode({'PrivateKeyInfo', v1,
                     {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS', 
                      {asn1_OPENTYPE, Parameters}}, PrivKey, _}) ->
    Key = der_decode('RSAPrivateKey', PrivKey),
    Params = der_decode('RSASSA-PSS-params', Parameters),
    {Key, Params};
der_priv_key_decode(#'PrivateKeyInfo'{version = v1,
                                      privateKeyAlgorithm = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa',
                                                                                                  parameters =
                                                                                                      {asn1_OPENTYPE, Parameters}},
                                      privateKey = PrivKey}) ->
    {params, #'Dss-Parms'{p=P, q=Q, g=G}} = der_decode('DSAParams', Parameters),
    X = der_decode('Prime-p', PrivKey),
    #'DSAPrivateKey'{p=P, q=Q, g=G, x=X};
der_priv_key_decode(PKCS8Key) ->
    PKCS8Key.

%%--------------------------------------------------------------------
-spec der_encode(Asn1Type, Entity) -> Der when Asn1Type :: asn1_type(),
                                               Entity :: term(),
                                               Der :: binary() .
%%
%% Description: Encodes a public key entity with asn1 DER encoding.
%%--------------------------------------------------------------------
der_encode('PrivateKeyInfo', #'DSAPrivateKey'{p=P, q=Q, g=G, x=X}) ->
    Params = der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G}),
    Alg =  #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa',
                                                 parameters =
                                                     {asn1_OPENTYPE, Params}},
    Key = der_encode('Prime-p', X),
    der_encode('PrivateKeyInfo',
               #'PrivateKeyInfo'{version = v1,
                                 privateKeyAlgorithm = Alg,
                                 privateKey = Key});
der_encode('PrivateKeyInfo', #'RSAPrivateKey'{} = PrivKey) ->
    Parms = ?DER_NULL,
    Alg = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption',
                                                parameters = {asn1_OPENTYPE, Parms}},
    Key = der_encode('RSAPrivateKey', PrivKey),
    der_encode('PrivateKeyInfo',
               #'PrivateKeyInfo'{version = v1,
                                 privateKeyAlgorithm = Alg,
                                 privateKey = Key});
der_encode('PrivateKeyInfo', {#'RSAPrivateKey'{} = PrivKey, Parameters}) ->
    Params = der_encode('RSASSA-PSS-params', Parameters),
    Alg = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-RSASSA-PSS',
                                                parameters =
                                                    {asn1_OPENTYPE, Params}},
    Key = der_encode('RSAPrivateKey', PrivKey),
    der_encode('PrivateKeyInfo', #'PrivateKeyInfo'{version = v1,
                                                   privateKeyAlgorithm = Alg,
                                                   privateKey = Key});
der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = {namedCurve, CurveOId},
                                             privateKey = Key}) when
      CurveOId == ?'id-Ed25519' orelse
      CurveOId == ?'id-Ed448' ->
    CurvePrivKey = der_encode('CurvePrivateKey', Key),
    Alg = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = CurveOId},
    der_encode('PrivateKeyInfo', #'PrivateKeyInfo'{version = v1,
                                                   privateKeyAlgorithm = Alg,
                                                   privateKey = CurvePrivKey});
der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = Parameters} = PrivKey) ->
    Params = der_encode('EcpkParameters', Parameters),
    Alg = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey',
                                                parameters = {asn1_OPENTYPE, Params}},
    Key = der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}),
    der_encode('PrivateKeyInfo',
               #'PrivateKeyInfo'{version = v1,
                                 privateKeyAlgorithm = Alg,
                                 privateKey = Key});
der_encode('OneAsymmetricKey', #'ECPrivateKey'{parameters = {namedCurve, CurveOId},
                                               privateKey = Key,
                                               attributes = Attr,
                                               publicKey = PubKey}) when
      CurveOId == ?'id-Ed25519' orelse
      CurveOId == ?'id-Ed448' ->
    CurvePrivKey = der_encode('CurvePrivateKey', Key),
    Alg = #'OneAsymmetricKey_privateKeyAlgorithm'{algorithm = CurveOId},
    der_encode('OneAsymmetricKey',
               #'OneAsymmetricKey'{version = 1,
                                   privateKeyAlgorithm = Alg,
                                   privateKey = CurvePrivKey,
                                   attributes = Attr,
                                   publicKey = PubKey});
der_encode('OneAsymmetricKey', #'ECPrivateKey'{parameters = {namedCurve, CurveOId},
                                               privateKey = Key,
                                               attributes = Attr,
                                               publicKey = PubKey}) ->
    Alg = #'OneAsymmetricKey_privateKeyAlgorithm'{algorithm = CurveOId},
    der_encode('OneAsymmetricKey',
               #'OneAsymmetricKey'{version = 1,
                                   privateKeyAlgorithm = Alg,
                                   privateKey = Key,
                                   attributes = Attr,
                                   publicKey = PubKey});
der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') orelse
                                  (Asn1Type == 'OneAsymmetricKey') orelse
				  (Asn1Type == 'EncryptedPrivateKeyInfo') ->
     try
         {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity),
         Encoded
     catch
	error:{badmatch, {error, _}} = Error ->
             erlang:error(Error)
     end;
der_encode(Asn1Type, Entity) when is_atom(Asn1Type) ->
    try 
	{ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity),
	Encoded
    catch	    
	error:{badmatch, {error, _}} = Error ->
	    erlang:error(Error)
    end.

%%--------------------------------------------------------------------
-spec pkix_decode_cert(Cert, Type) ->
          #'Certificate'{} | otp_cert()
              when Cert :: der_cert(),
                   Type :: plain | otp .
%%
%% Description: Decodes an asn1 der encoded pkix certificate. The otp
%% option will use the customized asn1 specification OTP-PKIX.asn1 for
%% decoding and also recursively decode most of the standard
%% extensions.
%% --------------------------------------------------------------------
pkix_decode_cert(DerCert, plain)  when is_binary(DerCert) ->
    der_decode('Certificate', DerCert);
pkix_decode_cert(DerCert, otp) when is_binary(DerCert) ->
    try 
	{ok, #'OTPCertificate'{}= Cert} = 
	    pubkey_cert_records:decode_cert(DerCert),
	Cert
    catch
	error:{badmatch, {error, _}} = Error ->
	    erlang:error(Error)
    end.

%%--------------------------------------------------------------------
-spec pkix_encode(Asn1Type, Entity, Type) -> Der
                                                 when Asn1Type :: asn1_type(),
                                                      Entity :: term(),
                                                      Type :: otp | plain,
                                                      Der :: der_encoded() .
%%
%% Description: Der encodes a certificate or part of a certificate.
%% This function must be used for encoding certificates or parts of certificates
%% that are decoded with the otp format, whereas for the plain format this
%% function will only call der_encode/2.   
%%--------------------------------------------------------------------
pkix_encode(Asn1Type, Term, plain) when is_atom(Asn1Type) ->
    der_encode(Asn1Type, Term);

pkix_encode(Asn1Type, Term0, otp) when is_atom(Asn1Type) ->
    Term = pubkey_cert_records:transform(Term0, encode),
    der_encode(Asn1Type, Term).

%%--------------------------------------------------------------------
%%
%% Description: Public key decryption using the private key.
%%--------------------------------------------------------------------
-spec decrypt_private(CipherText, Key) ->
                             PlainText when CipherText :: binary(),
                                            Key :: rsa_private_key(),
                                            PlainText ::  binary() .
decrypt_private(CipherText, Key) ->
    decrypt_private(CipherText, Key, []).

-spec decrypt_private(CipherText, Key, Options) ->
                             PlainText when CipherText :: binary(),
                                            Key :: rsa_private_key(),
                                            Options :: crypto:pk_encrypt_decrypt_opts(),
                                            PlainText ::  binary() .
decrypt_private(CipherText,
		#'RSAPrivateKey'{} = Key,
		Options)
  when is_binary(CipherText),
       is_list(Options) ->
    crypto:private_decrypt(rsa, CipherText, format_rsa_private_key(Key), default_options(Options)).

%%--------------------------------------------------------------------
%% Description: Public key decryption using the public key.
%%--------------------------------------------------------------------
-spec decrypt_public(CipherText, Key) ->
			    PlainText
                                when CipherText :: binary(),
                                     Key :: rsa_public_key(),
                                     PlainText :: binary() .
decrypt_public(CipherText, Key) ->
    decrypt_public(CipherText, Key, []).

-spec decrypt_public(CipherText, Key, Options) ->
			    PlainText
                                when CipherText :: binary(),
                                     Key :: rsa_public_key(),
                                     Options :: crypto:pk_encrypt_decrypt_opts(),
                                     PlainText :: binary() .
decrypt_public(CipherText, #'RSAPublicKey'{modulus = N, publicExponent = E}, 
	       Options) when is_binary(CipherText), is_list(Options)  ->
    crypto:public_decrypt(rsa, CipherText,[E, N], default_options(Options)).

%%--------------------------------------------------------------------
%% Description: Public key encryption using the public key.
%%--------------------------------------------------------------------
-spec encrypt_public(PlainText, Key) ->
			     CipherText
                                 when  PlainText :: binary(),
                                       Key :: rsa_public_key(),
                                       CipherText :: binary() .
encrypt_public(PlainText, Key) ->
    encrypt_public(PlainText, Key, []).


-spec encrypt_public(PlainText, Key, Options) ->
			     CipherText
                                 when  PlainText :: binary(),
                                       Key :: rsa_public_key(),
                                       Options :: crypto:pk_encrypt_decrypt_opts(),
                                       CipherText :: binary() .
encrypt_public(PlainText, #'RSAPublicKey'{modulus=N,publicExponent=E}, 
	       Options) when is_binary(PlainText), is_list(Options) ->
    crypto:public_encrypt(rsa, PlainText, [E,N], default_options(Options)).

%%--------------------------------------------------------------------
%%
%% Description: Public key encryption using the private key.
%%--------------------------------------------------------------------
-spec encrypt_private(PlainText, Key) ->
			     CipherText
                                 when  PlainText :: binary(),
                                       Key :: rsa_private_key(),
                                       CipherText :: binary() .
encrypt_private(PlainText, Key) ->
    encrypt_private(PlainText, Key, []).


-spec encrypt_private(PlainText, Key, Options) ->
			     CipherText
                                 when  PlainText :: binary(),
                                       Key :: rsa_private_key(),
                                       Options :: crypto:pk_encrypt_decrypt_opts(),
                                       CipherText :: binary() .
encrypt_private(PlainText,
		#'RSAPrivateKey'{modulus = N, publicExponent = E,
				 privateExponent = D} = Key,
		Options)
  when is_binary(PlainText),
       is_integer(N), is_integer(E), is_integer(D),
       is_list(Options) ->
    crypto:private_encrypt(rsa, PlainText, format_rsa_private_key(Key), default_options(Options)).

%%--------------------------------------------------------------------
%% Description: List available group sizes among the pre-computed dh groups
%%--------------------------------------------------------------------
-spec dh_gex_group_sizes() -> [pos_integer()].
dh_gex_group_sizes() ->
    pubkey_ssh:dh_gex_group_sizes().

%%--------------------------------------------------------------------
%% Description: Select a precomputed group
%%--------------------------------------------------------------------
-spec dh_gex_group(MinSize, SuggestedSize, MaxSize, Groups) ->
                          {ok,{Size,Group}} | {error,term()}
                              when MinSize :: pos_integer(),
                                   SuggestedSize :: pos_integer(),
                                   MaxSize :: pos_integer(),
                                   Groups :: undefined | [{Size,[Group]}],
                                   Size :: pos_integer(),
                                   Group :: {G,P},
                                   G :: pos_integer(),
                                   P :: pos_integer() .
dh_gex_group(Min, N, Max, Groups) ->
    pubkey_ssh:dh_gex_group(Min, N, Max, Groups).

%%--------------------------------------------------------------------
%% Description: Generate a new key pair
%%--------------------------------------------------------------------
-spec generate_key(DHparams | ECparams | RSAparams) ->
                          DHkeys | ECkey | RSAkey
                              when DHparams :: #'DHParameter'{},
                                   DHkeys :: {PublicDH::binary(), PrivateDH::binary()},
                                   ECparams :: ecpk_parameters_api(),
                                   ECkey :: #'ECPrivateKey'{},
                                   RSAparams :: {rsa, Size, PubExp},
                                   Size::pos_integer(),
                                   PubExp::pos_integer(),
                                   RSAkey :: #'RSAPrivateKey'{} .

generate_key(#'DHParameter'{prime = P, base = G}) ->
    crypto:generate_key(dh, [P, G]);
generate_key({namedCurve, _} = Params) ->
    ec_generate_key(Params);
generate_key({ecParameters, _} = Params) ->
    ec_generate_key(Params);
generate_key(#'ECParameters'{} = Params) ->
    ec_generate_key(Params);
generate_key({rsa, ModulusSize, PublicExponent}) ->
    case crypto:generate_key(rsa, {ModulusSize,PublicExponent}) of
        {[E, N], [E, N, D, P, Q, D_mod_P_1, D_mod_Q_1, InvQ_mod_P]} ->
            Nint = crypto:bytes_to_integer(N),
            Eint = crypto:bytes_to_integer(E),
            #'RSAPrivateKey'{version = 'two-prime', % Two-factor (I guess since otherPrimeInfos is not given)
                             modulus = Nint,
                             publicExponent = Eint,
                             privateExponent = crypto:bytes_to_integer(D),
                             prime1 = crypto:bytes_to_integer(P),
                             prime2 = crypto:bytes_to_integer(Q),
                             exponent1 = crypto:bytes_to_integer(D_mod_P_1),
                             exponent2 = crypto:bytes_to_integer(D_mod_Q_1),
                             coefficient = crypto:bytes_to_integer(InvQ_mod_P)};

        {[E, N], [E, N, D]} -> % FIXME: what to set the other fields in #'RSAPrivateKey'?
                               % Answer: Miller [Mil76]
                               %   G.L. Miller. Riemann's hypothesis and tests for primality.
                               %   Journal of Computer and Systems Sciences,
                               %   13(3):300-307,
                               %   1976.
            Nint = crypto:bytes_to_integer(N),
            Eint = crypto:bytes_to_integer(E),
            #'RSAPrivateKey'{version = 'two-prime', % Two-factor (I guess since otherPrimeInfos is not given)
                              modulus = Nint,
                              publicExponent = Eint,
                              privateExponent = crypto:bytes_to_integer(D),
                              prime1 = '?',
                              prime2 = '?',
                              exponent1 = '?',
                              exponent2 = '?',
                              coefficient = '?'};
        
        Other ->
            Other
    end.

%%--------------------------------------------------------------------
%% Description: Compute shared secret
%%--------------------------------------------------------------------
-spec compute_key(OthersECDHkey, MyECDHkey) -> 
                         SharedSecret
                             when OthersECDHkey :: #'ECPoint'{},
                                  MyECDHkey :: #'ECPrivateKey'{},
                                  SharedSecret :: binary().
compute_key(#'ECPoint'{point = Point}, #'ECPrivateKey'{privateKey = PrivKey,
						       parameters = {namedCurve, Curve} = Param})
  when (Curve == ?'id-X25519') orelse
       (Curve == ?'id-X448') ->
    ECCurve = ec_curve_spec(Param),
    crypto:compute_key(eddh, Point, PrivKey, ECCurve);
compute_key(#'ECPoint'{point = Point}, #'ECPrivateKey'{privateKey = PrivKey,
						       parameters = Param}) ->
    ECCurve = ec_curve_spec(Param),
    crypto:compute_key(ecdh, Point, PrivKey, ECCurve).

-spec compute_key(OthersDHkey, MyDHkey, DHparms) -> 
                         SharedSecret
                             when OthersDHkey :: crypto:dh_public(), % Was: binary(),
                                  MyDHkey :: crypto:dh_private(), % Was: binary(),
                                  DHparms ::  #'DHParameter'{},
                                  SharedSecret :: binary().
compute_key(PubKey, PrivKey, #'DHParameter'{prime = P, base = G}) ->
    crypto:compute_key(dh, PubKey, PrivKey, [P, G]).

%%--------------------------------------------------------------------
-spec pkix_sign_types(AlgorithmId) -> 
                             {DigestType, SignatureType}
                                 when AlgorithmId :: oid(),
                                      %% Relevant dsa digest type is a subset of rsa_digest_type()
                                      DigestType :: crypto:rsa_digest_type() | none,
                                      SignatureType :: rsa | dsa | ecdsa | eddsa.
%% Description:
%%--------------------------------------------------------------------
pkix_sign_types(?sha1WithRSAEncryption) ->
    {sha, rsa};
pkix_sign_types(?'sha-1WithRSAEncryption') ->
    {sha, rsa};
pkix_sign_types(?sha224WithRSAEncryption) ->
    {sha224, rsa};
pkix_sign_types(?sha256WithRSAEncryption) ->
    {sha256, rsa};
pkix_sign_types(?sha384WithRSAEncryption) ->
    {sha384, rsa};
pkix_sign_types(?sha512WithRSAEncryption) ->
    {sha512, rsa};
pkix_sign_types(?md5WithRSAEncryption) ->
    {md5, rsa};
pkix_sign_types(?'id-dsa-with-sha1') ->
    {sha, dsa};
pkix_sign_types(?'id-dsaWithSHA1') ->
    {sha, dsa};
pkix_sign_types(?'id-dsa-with-sha224') ->
    {sha224, dsa};
pkix_sign_types(?'id-dsa-with-sha256') ->
    {sha256, dsa};
pkix_sign_types(?'ecdsa-with-SHA1') ->
    {sha, ecdsa};
pkix_sign_types(?'ecdsa-with-SHA256') ->
    {sha256, ecdsa};
pkix_sign_types(?'ecdsa-with-SHA384') ->
    {sha384, ecdsa};
pkix_sign_types(?'ecdsa-with-SHA512') ->
    {sha512, ecdsa};
pkix_sign_types(?'id-Ed25519') ->
    {none, eddsa};
pkix_sign_types(?'id-Ed448') ->
    {none, eddsa}.

%%--------------------------------------------------------------------
-spec pkix_hash_type(HashOid::oid()) -> DigestType:: md5 | crypto:sha1() | crypto:sha2().
          
pkix_hash_type(?'id-sha1') ->
    sha;
pkix_hash_type(?'id-sha512') ->
    sha512;
pkix_hash_type(?'id-sha384') ->
    sha384;
pkix_hash_type(?'id-sha256') ->
    sha256;
pkix_hash_type('id-sha224') ->
    sha224;
pkix_hash_type('id-md5') ->
    md5.

%%--------------------------------------------------------------------
%% Description: Create digital signature.
%%--------------------------------------------------------------------
-spec sign(Msg, DigestType, Key) ->
                  Signature when Msg ::  binary() | {digest,binary()},
                                 DigestType :: digest_type(),
                                 Key :: private_key(),
                                 Signature :: binary() .
sign(DigestOrPlainText, DigestType, Key) ->
    sign(DigestOrPlainText, DigestType, Key, []).

-spec sign(Msg, DigestType, Key, Options) ->
                  Signature when Msg ::  binary() | {digest,binary()},
                                 DigestType :: digest_type(),
                                 Key :: private_key(),
                                 Options :: crypto:pk_sign_verify_opts(),
                                 Signature :: binary() .
sign(Digest, none, Key = #'DSAPrivateKey'{}, Options) when is_binary(Digest) ->
    %% Backwards compatible
    sign({digest, Digest}, sha, Key, Options);
sign(DigestOrPlainText, DigestType, Key, Options) ->
    case format_sign_key(Key) of
	badarg ->
	    erlang:error(badarg, [DigestOrPlainText, DigestType, Key, Options]);
	{Algorithm, CryptoKey} ->
	    try crypto:sign(Algorithm, DigestType, DigestOrPlainText, CryptoKey, Options)
            catch %% Compatible with old error schema
                error:{notsup,_,_} -> error(notsup);
                error:{error,_,_} -> error(error);
                error:{badarg,_,_} -> error(badarg)
            end
    end.

%%--------------------------------------------------------------------
%% Description: Verifies a digital signature.
%%--------------------------------------------------------------------
-spec verify(Msg, DigestType, Signature, Key) ->
                    boolean() when Msg :: binary() | {digest, binary()},
                                   DigestType :: digest_type(),
                                   Signature :: binary(),
                                   Key :: public_key().

verify(DigestOrPlainText, DigestType, Signature, Key) ->
    verify(DigestOrPlainText, DigestType, Signature, Key, []).

-spec verify(Msg, DigestType, Signature, Key, Options) ->
                    boolean() when Msg :: binary() | {digest, binary()},
                                   DigestType :: digest_type(),
                                   Signature :: binary(),
                                   Key :: public_key(),
                                   Options :: crypto:pk_sign_verify_opts().

verify(Digest, none, Signature, Key = {_, #'Dss-Parms'{}}, Options) when is_binary(Digest) ->
    %% Backwards compatible
    verify({digest, Digest}, sha, Signature, Key, Options);
verify(DigestOrPlainText, DigestType, Signature, Key, Options) when is_binary(Signature) ->
    case format_verify_key(Key) of
	badarg ->
	    erlang:error(badarg, [DigestOrPlainText, DigestType, Signature, Key, Options]);
	{Algorithm, CryptoKey} ->
	    try crypto:verify(Algorithm, DigestType, DigestOrPlainText, Signature, CryptoKey, Options)
            catch %% Compatible with old error schema
                error:{notsup,_,_} -> error(notsup);
                error:{error,_,_} -> error(error);
                error:{badarg,_,_} -> error(badarg)
            end
    end;
verify(_,_,_,_,_) ->
    %% If Signature is a bitstring and not a binary we know already at this
    %% point that the signature is invalid.
    false.

%%--------------------------------------------------------------------
-spec pkix_dist_point(Cert) -> DistPoint when Cert :: cert(),
                                              DistPoint :: #'DistributionPoint'{}.
%% Description:  Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
%%--------------------------------------------------------------------
pkix_dist_point(OtpCert) when is_binary(OtpCert) ->
    pkix_dist_point(pkix_decode_cert(OtpCert, otp));
pkix_dist_point(OtpCert) ->
    Issuer = public_key:pkix_normalize_name(
	       pubkey_cert_records:transform(
		 OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, encode)),
    
    TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
    Extensions = pubkey_cert:extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
    AltNames = case pubkey_cert:select_extension(?'id-ce-issuerAltName', Extensions) of 
		   undefined ->
		       [];
		   #'Extension'{extnValue = Value} ->
		       Value
	       end,
    Point = {fullName, [{directoryName, Issuer} | AltNames]},
    #'DistributionPoint'{cRLIssuer = asn1_NOVALUE,
			 reasons = asn1_NOVALUE,
			 distributionPoint =  Point}.	
%%--------------------------------------------------------------------
-spec pkix_dist_points(Cert) -> DistPoints when Cert :: cert(),
                                                DistPoints :: [ #'DistributionPoint'{} ].
%% Description:  Extracts distributionpoints specified in the certificates extensions.
%%--------------------------------------------------------------------
pkix_dist_points(OtpCert) when is_binary(OtpCert) ->
    pkix_dist_points(pkix_decode_cert(OtpCert, otp));
pkix_dist_points(OtpCert) ->
    Value = pubkey_cert:distribution_points(OtpCert),
    lists:foldl(fun(Point, Acc0) ->
			DistPoint = pubkey_cert_records:transform(Point, decode),
			[DistPoint | Acc0]
		end, 
		[], Value).

%%--------------------------------------------------------------------
-spec pkix_match_dist_point(CRL, DistPoint) ->
                                   boolean()
                                       when CRL :: der_encoded() | #'CertificateList'{},
                                            DistPoint :: #'DistributionPoint'{}.
%% Description: Check whether the given distribution point matches
%% the "issuing distribution point" of the CRL.
%%--------------------------------------------------------------------
pkix_match_dist_point(CRL, DistPoint) when is_binary(CRL) ->
    pkix_match_dist_point(der_decode('CertificateList', CRL), DistPoint);
pkix_match_dist_point(#'CertificateList'{},
		      #'DistributionPoint'{distributionPoint = asn1_NOVALUE}) ->
    %% No distribution point name specified - that's considered a match.
    true;
pkix_match_dist_point(#'CertificateList'{
			 tbsCertList =
			     #'TBSCertList'{
				crlExtensions = Extensions}},
		      #'DistributionPoint'{
			 distributionPoint = {fullName, DPs}}) ->
    case pubkey_cert:select_extension(?'id-ce-issuingDistributionPoint', Extensions) of
	undefined ->
	    %% If the CRL doesn't have an IDP extension, it
	    %% automatically qualifies.
	    true;
	#'Extension'{extnValue = IDPValue} ->
	    %% If the CRL does have an IDP extension, it must match
	    %% the given DistributionPoint to be considered a match.
	    IDPEncoded = der_decode('IssuingDistributionPoint', IDPValue),
	    #'IssuingDistributionPoint'{distributionPoint = {fullName, IDPs}} =
		pubkey_cert_records:transform(IDPEncoded, decode),
	    pubkey_crl:match_one(IDPs, DPs)
    end.

%%--------------------------------------------------------------------
-spec pkix_sign(Cert, Key) -> Der when Cert :: #'OTPTBSCertificate'{}, 
                                       Key :: private_key(),
                                       Der :: der_encoded() .
%%
%% Description: Sign a pkix x.509 certificate. Returns the corresponding
%% der encoded 'Certificate'{}
%%--------------------------------------------------------------------
pkix_sign(#'OTPTBSCertificate'{signature = 
				   #'SignatureAlgorithm'{} 
			       = SigAlg} = TBSCert, Key) ->
    Msg = pkix_encode('OTPTBSCertificate', TBSCert, otp),
    {DigestType, _, Opts} = pubkey_cert:x509_pkix_sign_types(SigAlg),
    Signature = sign(Msg, DigestType, format_pkix_sign_key(Key), Opts),
    Cert = #'OTPCertificate'{tbsCertificate= TBSCert,
			     signatureAlgorithm = SigAlg,
			     signature = Signature
			    },
    pkix_encode('OTPCertificate', Cert, otp).

%%--------------------------------------------------------------------
-spec pkix_verify(Cert, Key) -> boolean() when Cert :: der_cert(),
                                               Key :: public_key() .
%%
%% Description: Verify pkix x.509 certificate signature.
%%--------------------------------------------------------------------
pkix_verify(DerCert, {Key, #'Dss-Parms'{}} = DSAKey) 
  when is_binary(DerCert), is_integer(Key) ->
    {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
    verify(PlainText, DigestType, Signature, DSAKey);

pkix_verify(DerCert,  #'RSAPublicKey'{} = RSAKey) 
  when is_binary(DerCert) ->
    {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
    verify(PlainText, DigestType, Signature, RSAKey);

pkix_verify(DerCert,  {#'RSAPublicKey'{} = RSAKey, #'RSASSA-PSS-params'{} = Params}) 
  when is_binary(DerCert) ->
    {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert),
    verify(PlainText, DigestType, Signature, RSAKey, rsa_opts(Params));

pkix_verify(DerCert, Key = {#'ECPoint'{}, {namedCurve, Curve}}) when (Curve == ?'id-Ed25519'orelse
                                                                      Curve == ?'id-Ed448') andalso is_binary(DerCert) ->
    case pubkey_cert:verify_data(DerCert) of
        {none = DigestType, PlainText, Signature} ->
            verify(PlainText, DigestType, Signature, Key);
        _ ->
            false
    end;
pkix_verify(DerCert, Key = {#'ECPoint'{}, _}) when is_binary(DerCert) ->
    case pubkey_cert:verify_data(DerCert) of
        {none, _, _} ->
            false;
        {DigestType, PlainText, Signature} ->
            verify(PlainText, DigestType, Signature, Key)
    end.

%%--------------------------------------------------------------------
-spec pkix_crl_verify(CRL, Cert) -> boolean()
                                        when CRL  :: der_encoded() | #'CertificateList'{},
                                             Cert :: cert().
%%
%% Description: Verify that Cert is the CRL signer.
%%--------------------------------------------------------------------
pkix_crl_verify(CRL, Cert) when is_binary(CRL) ->
    pkix_crl_verify(der_decode('CertificateList', CRL), Cert);
pkix_crl_verify(CRL, Cert) when is_binary(Cert) ->
    pkix_crl_verify(CRL, pkix_decode_cert(Cert, otp));
pkix_crl_verify(#'CertificateList'{} = CRL, #'OTPCertificate'{} = Cert) ->
    TBSCert = Cert#'OTPCertificate'.tbsCertificate, 
    PublicKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
    PublicKey = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
    AlgInfo = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm,
    PublicKeyParams = AlgInfo#'PublicKeyAlgorithm'.parameters,
    pubkey_crl:verify_crl_signature(CRL, 
				    der_encode('CertificateList', CRL), 
				    PublicKey, PublicKeyParams).

%%--------------------------------------------------------------------
-spec pkix_is_issuer(CertorCRL, IssuerCert) ->
          boolean() when CertorCRL :: cert() | #'CertificateList'{},
                         IssuerCert :: cert().
%%
%% Description: Checks if <IssuerCert> issued <Cert>.
%%--------------------------------------------------------------------
pkix_is_issuer(Cert, IssuerCert)  when is_binary(Cert) ->
    OtpCert = pkix_decode_cert(Cert, otp),
    pkix_is_issuer(OtpCert, IssuerCert);
pkix_is_issuer(Cert, IssuerCert) when is_binary(IssuerCert) ->
    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);
pkix_is_issuer(#'CertificateList'{tbsCertList = TBSCRL},
	       #'OTPCertificate'{tbsCertificate = Candidate}) ->
    pubkey_cert:is_issuer(Candidate#'OTPTBSCertificate'.subject,
			  pubkey_cert_records:transform(TBSCRL#'TBSCertList'.issuer, decode)).

%%--------------------------------------------------------------------
-spec pkix_is_self_signed(Cert) -> boolean() when Cert::cert().
%%
%% 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) ->
    OtpCert = pkix_decode_cert(Cert, otp),
    pkix_is_self_signed(OtpCert).
  
%%--------------------------------------------------------------------
-spec pkix_is_fixed_dh_cert(Cert) -> boolean() when Cert::cert().
%%
%% 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) ->
    OtpCert = pkix_decode_cert(Cert, otp),
    pkix_is_fixed_dh_cert(OtpCert).

%%--------------------------------------------------------------------
-spec pkix_issuer_id(Cert, IssuedBy) ->
			    {ok, ID::cert_id()} | {error, Reason}
                                when Cert::cert(),
                                     IssuedBy :: self | other,
                                     Reason :: term() .

%% Description: Returns the issuer id.
%%--------------------------------------------------------------------
pkix_issuer_id(#'OTPCertificate'{} = OtpCert, Signed) when (Signed == self) or 
							   (Signed == other) ->
    pubkey_cert:issuer_id(OtpCert, Signed);
pkix_issuer_id(Cert, Signed) when is_binary(Cert) -> 
    OtpCert = pkix_decode_cert(Cert, otp),
    pkix_issuer_id(OtpCert, Signed).

%%--------------------------------------------------------------------
-spec pkix_subject_id(Cert) -> ID
              when Cert::cert(),
                   ID::cert_id() .

%% Description: Returns the subject id.
%%--------------------------------------------------------------------
pkix_subject_id(#'OTPCertificate'{} = OtpCert) ->
    pubkey_cert:subject_id(OtpCert);
pkix_subject_id(Cert) when is_binary(Cert) -> 
    OtpCert = pkix_decode_cert(Cert, otp),
    pkix_subject_id(OtpCert).

%%--------------------------------------------------------------------
-spec pkix_crl_issuer(CRL) -> Issuer
               when CRL :: der_encoded() | #'CertificateList'{},
                    Issuer :: issuer_name() .
%% Description: Returns the issuer.
%%--------------------------------------------------------------------
pkix_crl_issuer(CRL) when is_binary(CRL) ->
    pkix_crl_issuer(der_decode('CertificateList', CRL));
pkix_crl_issuer(#'CertificateList'{} = CRL) ->
    pubkey_cert_records:transform(
      CRL#'CertificateList'.tbsCertList#'TBSCertList'.issuer, decode).

%%--------------------------------------------------------------------
-spec pkix_normalize_name(Issuer) -> Normalized 
                                         when Issuer :: issuer_name() | der_encoded(),
                                              Normalized :: issuer_name() .
%%
%% Description: Normalizes a issuer name so that it can be easily
%%              compared to another issuer name. 
%%--------------------------------------------------------------------
pkix_normalize_name(Issuer) when is_binary(Issuer) -> 
    PlainGenName = der_decode('Name', Issuer),
    GenName = pubkey_cert_records:transform(PlainGenName, decode),
    pkix_normalize_name(GenName);
pkix_normalize_name(Issuer) -> 
    pubkey_cert:normalize_general_name(Issuer).

%%-------------------------------------------------------------------- 
-spec pkix_path_validation(Cert, CertChain, Options) ->
          {ok, {PublicKeyInfo, PolicyTree}} |
          {error, {bad_cert, Reason :: bad_cert_reason()}}
              when
      Cert :: cert() | atom(),
      CertChain :: [cert() | combined_cert()],
      Options  :: [{max_path_length, integer()} | {verify_fun, {fun(), term()}}],
      PublicKeyInfo :: public_key_info(),
      PolicyTree :: list().

%% Description: Performs a basic path validation according to RFC 5280.
%%--------------------------------------------------------------------
pkix_path_validation(TrustedCert, CertChain, Options)
  when is_binary(TrustedCert) ->

    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),
    {VerifyFun, UserState0} =
	proplists:get_value(verify_fun, Options, ?DEFAULT_VERIFYFUN),
    try pubkey_cert:validate_time(TrustedCert, UserState0, VerifyFun) of
        UserState1 -> 
            ValidationState = pubkey_cert:init_validation_state(TrustedCert, 
                                                                MaxPathDefault, 
                                                                [{verify_fun, {VerifyFun, UserState1}} | 
                                                                 proplists:delete(verify_fun, Options)]),
            path_validation(CertChain, ValidationState)
    catch
        throw:{bad_cert, _} = Result ->
            {error, Result}
    end;
pkix_path_validation(PathErr, [Cert | Chain], Options0) when is_atom(PathErr)->
    {VerifyFun, Userstat0} =
	proplists:get_value(verify_fun, Options0, ?DEFAULT_VERIFYFUN),
    Otpcert = otp_cert(Cert),
    Reason = {bad_cert, PathErr},
    try VerifyFun(Otpcert, Reason, Userstat0) of
	{valid, Userstate} ->
	    Options = proplists:delete(verify_fun, Options0),
	    pkix_path_validation(Otpcert, Chain, [{verify_fun,
						   {VerifyFun, Userstate}}| Options]);
	{fail, UserReason} ->
	    {error, UserReason}
    catch
	_:_ ->
	    {error, Reason}
    end.
%--------------------------------------------------------------------
-spec pkix_crls_validate(OTPcertificate, DPandCRLs, Options) ->
                                CRLstatus when OTPcertificate :: #'OTPCertificate'{},
                                               DPandCRLs :: [DPandCRL],
                                               DPandCRL  :: {DP, {DerCRL, CRL}},
                                               DP :: #'DistributionPoint'{},
                                               DerCRL :: der_encoded(),
                                               CRL :: #'CertificateList'{},
                                               Options :: [{atom(),term()}],
                                               CRLstatus :: valid
                                                          | {bad_cert, BadCertReason},
                                               BadCertReason :: revocation_status_undetermined
                                                              | {revocation_status_undetermined, Reason::term()}
                                                              | {revoked, crl_reason()}.

%% Description: Performs a CRL validation according to RFC 5280.
%%--------------------------------------------------------------------
pkix_crls_validate(OtpCert, [{_,_,_} |_] = DPAndCRLs, Options) ->
    pkix_crls_validate(OtpCert, DPAndCRLs, DPAndCRLs,
		       Options, pubkey_crl:init_revokation_state());

pkix_crls_validate(OtpCert, DPAndCRLs0, Options) ->
    CallBack = proplists:get_value(update_crl, Options, fun(_, CurrCRL) ->
							       CurrCRL
						       end),
    DPAndCRLs = sort_dp_crls(DPAndCRLs0, CallBack),
    pkix_crls_validate(OtpCert, DPAndCRLs, DPAndCRLs,
		       Options, pubkey_crl:init_revokation_state()).

%--------------------------------------------------------------------
-type referenceIDs() :: [referenceID()] .
-type referenceID() :: {uri_id | dns_id | ip | srv_id | atom() | oid(),  string()}
                     | {ip, inet:ip_address() | string()} .

%% Description: Validates a hostname to RFC 6125
%%--------------------------------------------------------------------
-spec pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()
                                                      when Cert :: cert(),
                                                           ReferenceIDs :: referenceIDs() .
pkix_verify_hostname(Cert, ReferenceIDs) ->
    pkix_verify_hostname(Cert, ReferenceIDs, []).

-spec pkix_verify_hostname(Cert, ReferenceIDs, Options) ->
                                  boolean()
                                      when Cert :: cert(),
                                           ReferenceIDs :: referenceIDs(),
                                           Options :: [{match_fun | fail_callback | fqdn_fun, fun()}] .

pkix_verify_hostname(BinCert, ReferenceIDs, Options)  when is_binary(BinCert) ->
    pkix_verify_hostname(pkix_decode_cert(BinCert,otp), ReferenceIDs, Options);

pkix_verify_hostname(Cert = #'OTPCertificate'{tbsCertificate = TbsCert}, ReferenceIDs0, Opts) ->
    MatchFun = proplists:get_value(match_fun,     Opts, undefined),
    FailCB   = proplists:get_value(fail_callback, Opts, fun(_Cert) -> false end),
    FqdnFun  = proplists:get_value(fqdn_fun,      Opts, fun verify_hostname_extract_fqdn_default/1),

    ReferenceIDs = [{T,to_string(V)} || {T,V} <- ReferenceIDs0],
    PresentedIDs =
	try lists:keyfind(?'id-ce-subjectAltName',
			  #'Extension'.extnID,
			  TbsCert#'OTPTBSCertificate'.extensions)
	of
	    #'Extension'{extnValue = ExtVals} ->
		[{T,to_string(V)} || {T,V} <- ExtVals];
	    false ->
		[]
	catch
	    _:_ -> []
	end,
    %% PresentedIDs example: [{dNSName,"ewstest.ericsson.com"}, {dNSName,"www.ericsson.com"}]}
    case PresentedIDs of
	[] ->
	    %% Fallback to CN-ids [rfc6125, ch6]
	    case TbsCert#'OTPTBSCertificate'.subject of
		{rdnSequence,RDNseq} ->
		    PresentedCNs =
			[{cn, to_string(V)}
			 || ATVs <- RDNseq, % RDNseq is list-of-lists
			    #'AttributeTypeAndValue'{type = ?'id-at-commonName',
						     value = {_T,V}} <- ATVs
						% _T = kind of string (teletexString etc)
			],
		    %% Example of PresentedCNs:  [{cn,"www.ericsson.se"}]
		    %% match ReferenceIDs to PresentedCNs
		    verify_hostname_match_loop(verify_hostname_fqnds(ReferenceIDs, FqdnFun),
					       PresentedCNs,
					       MatchFun, FailCB, Cert);
		
		_ ->
		    false
	    end;
	_ ->
	    %% match ReferenceIDs to PresentedIDs
	    case verify_hostname_match_loop(ReferenceIDs, PresentedIDs,
					    MatchFun, FailCB, Cert) of
		false ->
		    %% Try to extract DNS-IDs from URIs etc
		    DNS_ReferenceIDs =
			[{dns_id,X} || X <- verify_hostname_fqnds(ReferenceIDs, FqdnFun)],
		    verify_hostname_match_loop(DNS_ReferenceIDs, PresentedIDs,
					       MatchFun, FailCB, Cert);
		true ->
		    true
	    end
    end.

-spec pkix_verify_hostname_match_fun(Protocol) ->  Result when
      Protocol :: https,
      Result :: fun().


pkix_verify_hostname_match_fun(https) ->
    fun({dns_id,FQDN=[_|_]}, {dNSName,Name=[_|_]}) -> verify_hostname_match_wildcard(FQDN, Name);
       (_, _) -> default
    end.

%%--------------------------------------------------------------------
-spec ssh_curvename2oid(binary()) -> oid().

%% Description: Converts from the ssh name of elliptic curves to
%% the OIDs.
%%--------------------------------------------------------------------
ssh_curvename2oid(<<"nistp256">>) ->  ?'secp256r1';
ssh_curvename2oid(<<"nistp384">>) ->  ?'secp384r1';
ssh_curvename2oid(<<"nistp521">>) ->  ?'secp521r1'.

%%--------------------------------------------------------------------
-spec oid2ssh_curvename(oid()) -> binary().

%% Description: Converts from elliptic curve OIDs to the ssh name.
%%--------------------------------------------------------------------
oid2ssh_curvename(?'secp256r1') -> <<"nistp256">>;
oid2ssh_curvename(?'secp384r1') -> <<"nistp384">>;
oid2ssh_curvename(?'secp521r1') -> <<"nistp521">>.


%%--------------------------------------------------------------------
-spec short_name_hash(Name) -> string() when Name :: issuer_name() .

%% Description: Generates OpenSSL-style hash of a name.
%%--------------------------------------------------------------------
short_name_hash({rdnSequence, _Attributes} = Name) ->
    HashThis = encode_name_for_short_hash(Name),
    <<HashValue:32/little, _/binary>> = crypto:hash(sha, HashThis),
    string:to_lower(string:right(integer_to_list(HashValue, 16), 8, $0)).


%%--------------------------------------------------------------------
-spec pkix_test_data(ChainConf) -> TestConf when
      ChainConf :: #{server_chain:= chain_opts(),
                     client_chain:= chain_opts()} |
                   chain_opts(),
      TestConf :: test_config() | [conf_opt()].

%% Description: Generates cert(s) and ssl configuration
%%--------------------------------------------------------------------

pkix_test_data(#{client_chain := ClientChain0,
                 server_chain := ServerChain0}) ->
    Default = #{intermediates => []},
    ClientChain = maps:merge(Default, ClientChain0),
    ServerChain = maps:merge(Default, ServerChain0),
    pubkey_cert:gen_test_certs(#{client_chain => ClientChain,
                                 server_chain => ServerChain});
pkix_test_data(#{} = Chain) ->
    Default = #{intermediates => []},
    pubkey_cert:gen_test_certs(maps:merge(Default, Chain)).

%%--------------------------------------------------------------------
-spec pkix_test_root_cert(Name, Options) ->
                                 RootCert
                                     when Name :: string(),
                                          Options :: [cert_opt()],
                                          RootCert :: test_root_cert().
%% Description: Generates a root cert suitable for pkix_test_data/1
%%--------------------------------------------------------------------

pkix_test_root_cert(Name, Opts) ->
    pubkey_cert:root_cert(Name, Opts).
      
%%--------------------------------------------------------------------
-spec pkix_ocsp_validate(Cert, IssuerCert, OcspRespDer, 
                         ResponderCerts, NonceExt) -> valid | {bad_cert, Reason}
              when Cert:: cert(),
                   IssuerCert:: cert(),
                   OcspRespDer::der_encoded(),
                   ResponderCerts::[der_cert()],
                   NonceExt::undefined | binary(),
                   Reason::term().

%% Description: Validate OCSP staple response
%%--------------------------------------------------------------------
pkix_ocsp_validate(DerCert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerCert) ->
    pkix_ocsp_validate(pkix_decode_cert(DerCert, otp),  IssuerCert, OcspRespDer, ResponderCerts, NonceExt);
pkix_ocsp_validate(Cert, DerIssuerCert, OcspRespDer, ResponderCerts, NonceExt) when is_binary(DerIssuerCert) ->
    pkix_ocsp_validate(Cert, pkix_decode_cert(DerIssuerCert, otp), OcspRespDer, ResponderCerts, NonceExt);
pkix_ocsp_validate(Cert, IssuerCert, OcspRespDer, ResponderCerts, NonceExt) ->
    case  ocsp_responses(OcspRespDer, ResponderCerts, NonceExt) of
        {ok, Responses} ->
            ocsp_status(Cert, IssuerCert, Responses);
        {error, Reason} ->
            {bad_cert, {revocation_status_undetermined, Reason}}
    end.

%%--------------------------------------------------------------------
-spec ocsp_extensions(undefined | binary()) -> list().
%% Description: Get OCSP stapling extensions for request
%%--------------------------------------------------------------------
ocsp_extensions(Nonce) ->
    [Extn || Extn <- [pubkey_ocsp:get_nonce_extn(Nonce),
                      pubkey_ocsp:get_acceptable_response_types_extn()],
             erlang:is_record(Extn, 'Extension')].

%%--------------------------------------------------------------------
-spec ocsp_responder_id(#'Certificate'{}) -> binary().
%%
%% Description: Get the OCSP responder ID der
%%--------------------------------------------------------------------
ocsp_responder_id(Cert) ->
    pubkey_ocsp:get_ocsp_responder_id(Cert).

%%--------------------------------------------------------------------
-spec cacerts_get() -> [combined_cert()].
%%
%% Description: Get loaded cacerts, if none are loaded it will try to
%%              load OS provided cacerts
%%--------------------------------------------------------------------
cacerts_get() ->
    pubkey_os_cacerts:get().

%%--------------------------------------------------------------------
-spec cacerts_load() -> ok | {error, Reason::term()}.
%%
%% Description: (Re)Load OS provided cacerts
%%--------------------------------------------------------------------
cacerts_load() ->
    pubkey_os_cacerts:load().

%%--------------------------------------------------------------------
-spec cacerts_load(File::file:filename_all()) -> ok | {error, Reason::term()}.
%%
%% Description: (Re)Load cacerts from a file
%%--------------------------------------------------------------------
cacerts_load(File) ->
    pubkey_os_cacerts:load([File]).

%%--------------------------------------------------------------------
-spec cacerts_clear() -> boolean().
%%
%% Description: Clears loaded cacerts, returns true if any was loaded.
%%--------------------------------------------------------------------
cacerts_clear() ->
    pubkey_os_cacerts:clear().

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
default_options([]) ->
    [{rsa_padding, rsa_pkcs1_padding}];
default_options(Opts) ->
    case proplists:get_value(rsa_pad, Opts) of
        undefined ->
            case proplists:get_value(rsa_padding, Opts) of
                undefined ->
                    case lists:dropwhile(fun erlang:is_tuple/1, Opts) of
                        [Pad|_] ->
                            set_padding(Pad, Opts);
                        [] ->
                            set_padding(rsa_pkcs1_padding, Opts)
                    end;
                Pad ->
                    set_padding(Pad, Opts)
            end;
        Pad ->
            set_padding(Pad, Opts)
    end.

set_padding(Pad, Opts) ->
    [{rsa_padding,Pad} | [{T,V} || {T,V} <- Opts,
                                   T =/= rsa_padding,
                                   T =/= rsa_pad]
    ].

format_pkix_sign_key({#'RSAPrivateKey'{} = Key, _}) ->
    %% Params are handled in option arg
    Key;
format_pkix_sign_key(Key) ->
    Key.
format_sign_key(Key = #'RSAPrivateKey'{}) ->
    {rsa, format_rsa_private_key(Key)};
format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
    {dss, [P, Q, G, X]};
format_sign_key(#'ECPrivateKey'{privateKey = PrivKey, parameters = {namedCurve, Curve} = Param})
  when (Curve == ?'id-Ed25519') orelse (Curve == ?'id-Ed448')->
    ECCurve = ec_curve_spec(Param),
    {eddsa, [PrivKey, ECCurve]};
format_sign_key(#'ECPrivateKey'{privateKey = PrivKey, parameters = Param}) ->
    ECCurve = ec_curve_spec(Param),
    {ecdsa, [PrivKey, ECCurve]};
format_sign_key({ed_pri, Curve, _Pub, Priv}) ->
    {eddsa, [Priv,Curve]};
format_sign_key(_) ->
    badarg.

format_verify_key(#'RSAPublicKey'{modulus = Mod, publicExponent = Exp}) ->
    {rsa, [Exp, Mod]};
format_verify_key({#'ECPoint'{point = Point}, {namedCurve, Curve} = Param}) when (Curve == ?'id-Ed25519') orelse
                                                                                 (Curve == ?'id-Ed448') ->
    ECCurve = ec_curve_spec(Param),
    {eddsa, [Point, ECCurve]};
format_verify_key({#'ECPoint'{point = Point}, Param}) ->
    ECCurve = ec_curve_spec(Param),
    {ecdsa, [Point, ECCurve]};
format_verify_key({Key,  #'Dss-Parms'{p = P, q = Q, g = G}}) ->
    {dss, [P, Q, G, Key]};
format_verify_key({ed_pub, Curve, Key}) ->
    {eddsa, [Key,Curve]};
%% Convert private keys to public keys
format_verify_key(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) ->
    format_verify_key(#'RSAPublicKey'{modulus = Mod, publicExponent = Exp});
format_verify_key(#'ECPrivateKey'{parameters = Param, publicKey = {_, Point}}) ->
    format_verify_key({#'ECPoint'{point = Point}, Param});
format_verify_key(#'ECPrivateKey'{parameters = Param, publicKey = Point}) ->
    format_verify_key({#'ECPoint'{point = Point}, Param});
format_verify_key(#'DSAPrivateKey'{y=Y, p=P, q=Q, g=G}) ->
    format_verify_key({Y, #'Dss-Parms'{p=P, q=Q, g=G}});
format_verify_key(_) ->
    badarg.

rsa_opts(#'RSASSA-PSS-params'{saltLength = SaltLen, 
                              maskGenAlgorithm = 
                                  #'MaskGenAlgorithm'{algorithm = ?'id-mgf1',
                                                      parameters = #'HashAlgorithm'{algorithm = HashAlgoOid}
                                                     }}) ->
    HashAlgo = pkix_hash_type(HashAlgoOid),
    [{rsa_padding, rsa_pkcs1_pss_padding},
     {rsa_pss_saltlen, SaltLen},
     {rsa_mgf1_md, HashAlgo}].

do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password) ->
    Der = der_encode(Asn1Type, Entity),
    DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password),
    {Asn1Type, DecryptDer, CipherInfo}.
  
do_pem_entry_decode({Asn1Type,_, _} = PemEntry, Password) ->
    Der = pubkey_pem:decipher(PemEntry, Password),
    der_decode(Asn1Type, Der).

path_validation([], #path_validation_state{working_public_key_algorithm
					   = Algorithm,
					   working_public_key =
					   PublicKey,
					   working_public_key_parameters 
					   = PublicKeyParams,
					   valid_policy_tree = Tree
					  }) ->
    {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree}};

path_validation([DerCert | Rest], ValidationState = #path_validation_state{
				    max_path_length = Len}) when Len >= 0 ->
    try validate(DerCert,
		 ValidationState#path_validation_state{last_cert=Rest=:=[]}) of
	#path_validation_state{} = NewValidationState ->
	    path_validation(Rest, NewValidationState)
    catch   
	throw:Reason ->
	    {error, Reason}
    end;

path_validation([Cert | _] = Path,
		#path_validation_state{user_state = UserState0,
				       verify_fun = VerifyFun} =
		    ValidationState) ->
    Reason = {bad_cert, max_path_length_reached},
    OtpCert = otp_cert(Cert),

    try VerifyFun(OtpCert,  Reason, UserState0) of
	{valid, UserState} ->
	    path_validation(Path,
			    ValidationState#path_validation_state{
			      max_path_length = 0,
			      user_state = UserState});
	{fail, _} ->
	    {error, Reason}
    catch
	_:_ ->
	    {error, Reason}
    end.

validate(Cert, #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,
                                      verify_fun = VerifyFun} =
	     ValidationState0) ->
    
    OtpCert = otp_cert(Cert),

    {ValidationState1, UserState1} =
	pubkey_cert:validate_extensions(OtpCert, ValidationState0, UserState0,
					VerifyFun),

    %% We want the key_usage extension to be checked before we validate
    %% other things so that CRL validation errors will comply to standard
    %% test suite description

    UserState2 = pubkey_cert:validate_time(OtpCert, UserState1, VerifyFun),

    UserState3 = pubkey_cert:validate_issuer(OtpCert, Issuer, UserState2, VerifyFun),

    UserState4 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
					    UserState3, VerifyFun),

    UserState5 = pubkey_cert:validate_signature(OtpCert, der_cert(Cert),
						Key, KeyParams, UserState4, VerifyFun),
    UserState = case Last of
		    false ->
			pubkey_cert:verify_fun(OtpCert, valid, UserState5, VerifyFun);
		    true ->
			pubkey_cert:verify_fun(OtpCert, valid_peer,
					       UserState5, VerifyFun)
		end,

    ValidationState  = 
	ValidationState1#path_validation_state{user_state = UserState},

    pubkey_cert:prepare_for_next_cert(OtpCert, ValidationState).

otp_cert(Der) when is_binary(Der) ->
    pkix_decode_cert(Der, otp);
otp_cert(#'OTPCertificate'{} = Cert) ->
    Cert;
otp_cert(#cert{otp = OtpCert}) ->
    OtpCert.

der_cert(#'OTPCertificate'{} = Cert) ->
    pkix_encode('OTPCertificate', Cert, otp);
der_cert(Der) when is_binary(Der) ->
    Der;
der_cert(#cert{der = DerCert}) ->
    DerCert.

pkix_crls_validate(_, [],_, Options, #revoke_state{details = Details}) ->
     case proplists:get_value(undetermined_details, Options, false) of
         false ->
             {bad_cert, revocation_status_undetermined};
         true ->
             {bad_cert, {revocation_status_undetermined, {bad_crls, format_details(Details)}}}
     end;
pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],  All, Options, RevokedState0) ->
    CallBack = proplists:get_value(update_crl, Options, fun(_, CurrCRL) ->
							       CurrCRL
						       end),
    case pubkey_crl:fresh_crl(DP, CRL, CallBack) of
	{fresh, CRL} ->
	    do_pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],
				  All, Options, RevokedState0);
	{fresh, NewCRL} ->
	    NewAll = [{DP, NewCRL, DeltaCRL} | All -- [{DP, CRL, DeltaCRL}]],
	    do_pkix_crls_validate(OtpCert, [{DP, NewCRL, DeltaCRL} | Rest],
				  NewAll, Options, RevokedState0);
	no_fresh_crl ->
	    pkix_crls_validate(OtpCert, Rest, All, Options, RevokedState0)
    end.

do_pkix_crls_validate(OtpCert, [{DP, CRL, DeltaCRL} | Rest],  All, Options, RevokedState0) ->
    OtherDPCRLs = All -- [{DP, CRL, DeltaCRL}],
    case pubkey_crl:validate(OtpCert, OtherDPCRLs, DP, CRL, DeltaCRL, Options, RevokedState0) of
	{undetermined, unrevoked, #revoke_state{details = Details}} when Rest == []->
            case proplists:get_value(undetermined_details, Options, false) of
                false ->
                    {bad_cert, revocation_status_undetermined};
                true ->
                    {bad_cert, {revocation_status_undetermined, {bad_crls, Details}}}
            end;
	{undetermined, unrevoked, RevokedState} when Rest =/= []->
	    pkix_crls_validate(OtpCert, Rest, All, Options, RevokedState);
	{finished, unrevoked} ->
	    valid;
	{finished, Status} ->
	    {bad_cert, Status}
    end.

sort_dp_crls(DpsAndCrls, FreshCB) ->
    sort_crls(maps:to_list(lists:foldl(fun group_dp_crls/2,
                                       #{},
                                       DpsAndCrls)),
              FreshCB, []).

group_dp_crls({DP,CRL}, M) ->
    case M of
        #{DP := CRLs} -> M#{DP := [CRL|CRLs]};
        _ -> M#{DP => [CRL]}
    end.

sort_crls([], _, Acc) ->
    Acc;

sort_crls([{DP, AllCRLs} | Rest], FreshCB, Acc)->
    {DeltaCRLs, CRLs} = do_sort_crls(AllCRLs),
    DpsAndCRLs = combine(CRLs, DeltaCRLs, DP, FreshCB, []),
    sort_crls(Rest, FreshCB, DpsAndCRLs ++ Acc).

do_sort_crls(CRLs) ->
    lists:partition(fun({_, CRL}) ->
			    pubkey_crl:is_delta_crl(CRL)
		    end, CRLs).

combine([], _,_,_,Acc) ->
    Acc;
combine([{_, CRL} = Entry | CRLs], DeltaCRLs, DP, FreshCB, Acc) ->
    DeltaCRL = combine(CRL, DeltaCRLs),
    case pubkey_crl:fresh_crl(DP, DeltaCRL, FreshCB) of
	no_fresh_crl ->
	    combine(CRLs, DeltaCRLs, DP, FreshCB, [{DP, Entry, {undefined, undefined}} | Acc]);
	{fresh, NewDeltaCRL} ->
	    combine(CRLs, DeltaCRLs, DP, FreshCB, [{DP, Entry, NewDeltaCRL} | Acc])
    end.

combine(CRL, DeltaCRLs) ->
    Deltas = lists:filter(fun({_,DeltaCRL}) ->
				  pubkey_crl:combines(CRL, DeltaCRL)
			  end, DeltaCRLs),
    case Deltas of
	[] ->
	    {undefined, undefined};
	[Delta] ->
	    Delta;
	[_,_|_] ->
	    Fun =
		fun({_, #'CertificateList'{tbsCertList = FirstTBSCRL}} = CRL1,
		    {_, #'CertificateList'{tbsCertList = SecondTBSCRL}} = CRL2) ->
			Time1 = pubkey_cert:time_str_2_gregorian_sec(
				  FirstTBSCRL#'TBSCertList'.thisUpdate),
			Time2 = pubkey_cert:time_str_2_gregorian_sec(
				  SecondTBSCRL#'TBSCertList'.thisUpdate),
			case Time1 > Time2 of
			      true ->
				CRL1;
			    false ->
				CRL2
			end
		end,
	    lists:foldl(Fun,  hd(Deltas), tl(Deltas))
    end.

format_rsa_private_key(#'RSAPrivateKey'{modulus = N, publicExponent = E,
					privateExponent = D,
					prime1 = P1, prime2 = P2,
					exponent1 = E1, exponent2 = E2,
					coefficient = C})
  when is_integer(N), is_integer(E), is_integer(D),
       is_integer(P1), is_integer(P2),
       is_integer(E1), is_integer(E2), is_integer(C) ->
   [E, N, D, P1, P2, E1, E2, C];

format_rsa_private_key(#'RSAPrivateKey'{modulus = N, publicExponent = E,
					privateExponent = D}) when is_integer(N),
								   is_integer(E),
								   is_integer(D) ->
   [E, N, D].

-spec ec_generate_key(ecpk_parameters_api()) -> #'ECPrivateKey'{}.
ec_generate_key(Params) ->
    Curve = ec_curve_spec(Params),
    CurveType = ec_curve_type(Curve),
    Term = crypto:generate_key(CurveType, Curve),
    NormParams = ec_normalize_params(Params),
    ec_key(Term, NormParams).

-spec ec_normalize_params(ecpk_parameters_api()) -> ecpk_parameters().
ec_normalize_params({namedCurve, Name}) when is_atom(Name) ->
	{namedCurve, pubkey_cert_records:namedCurves(Name)};
ec_normalize_params(#'ECParameters'{} = ECParams) ->
	{ecParameters, ECParams};
ec_normalize_params(Other) -> Other.

-spec ec_curve_spec(ecpk_parameters_api()) -> term().
ec_curve_spec( #'ECParameters'{fieldID = #'FieldID'{fieldType = Type,
                                                    parameters = Params}, curve = PCurve, base = Base, order = Order, cofactor = CoFactor }) ->
    Field = format_field(pubkey_cert_records:supportedCurvesTypes(Type), Params),
    Curve = {PCurve#'Curve'.a, PCurve#'Curve'.b, none},
    {Field, Curve, Base, Order, CoFactor};
ec_curve_spec({ecParameters, ECParams}) ->
    ec_curve_spec(ECParams);
ec_curve_spec({namedCurve, OID}) when is_tuple(OID), is_integer(element(1,OID)) ->
    ec_curve_spec({namedCurve,  pubkey_cert_records:namedCurves(OID)});
ec_curve_spec({namedCurve, x25519 = Name}) ->
    Name;
ec_curve_spec({namedCurve, x448 = Name}) ->
    Name;
ec_curve_spec({namedCurve, ed25519 = Name}) ->
    Name;
ec_curve_spec({namedCurve, ed448 = Name}) ->
    Name;
ec_curve_spec({namedCurve, Name}) when is_atom(Name) ->
    crypto:ec_curve(Name).

ec_curve_type(ed25519) ->
    eddsa;
ec_curve_type(ed448) ->
    eddsa;
ec_curve_type(x25519) ->
    eddh;
ec_curve_type(x448) ->
    eddh;
ec_curve_type(_) ->
    ecdh.

format_field(characteristic_two_field = Type, Params0) ->
    #'Characteristic-two'{
       m = M,
       basis = BasisOid,
       parameters = Params} = der_decode('Characteristic-two', Params0),
    {Type, M, field_param_decode(BasisOid, Params)};
format_field(prime_field, Params0) ->
    Prime = der_decode('Prime-p', Params0),
    {prime_field, Prime}.

field_param_decode(?'ppBasis', Params) ->
    #'Pentanomial'{k1 = K1, k2 = K2, k3 = K3} =
        der_decode('Pentanomial', Params),
    {ppbasis, K1, K2, K3};
field_param_decode(?'tpBasis', Params) ->
    K = der_decode('Trinomial', Params),
    {tpbasis, K};
field_param_decode(?'gnBasis', _) ->
    onbasis.
        
-spec ec_key({PubKey::term(), PrivateKey::term()}, Params::ecpk_parameters()) -> #'ECPrivateKey'{}.
ec_key({PubKey, PrivateKey}, Params) ->
    #'ECPrivateKey'{version = 1,
		    privateKey = PrivateKey,
		    parameters = Params,
		    publicKey = PubKey}.

encode_name_for_short_hash({rdnSequence, Attributes0}) ->
    Attributes = lists:map(fun normalise_attribute/1, Attributes0),
    {Encoded, _} = 'OTP-PUB-KEY':'enc_RDNSequence'(Attributes, []),
    Encoded.

%% Normalise attribute for "short hash".  If the attribute value
%% hasn't been decoded yet, decode it so we can normalise it.
normalise_attribute([#'AttributeTypeAndValue'{
                        type = _Type,
                        value = Binary} = ATV]) when is_binary(Binary) ->
    case pubkey_cert_records:transform(ATV, decode) of
	#'AttributeTypeAndValue'{value = Binary} ->
	    %% Cannot decode attribute; return original.
	    [ATV];
	DecodedATV = #'AttributeTypeAndValue'{} ->
	    %% The new value will either be String or {Encoding,String}.
	    normalise_attribute([DecodedATV])
    end;
normalise_attribute([#'AttributeTypeAndValue'{
                        type = _Type,
                        value = {Encoding, String}} = ATV])
  when
      Encoding =:= utf8String;
      Encoding =:= printableString;
      Encoding =:= teletexString;
      Encoding =:= ia5String ->
    %% These string types all give us something that the unicode
    %% module understands.
    NewValue = normalise_attribute_value(String),
    [ATV#'AttributeTypeAndValue'{value = NewValue}];
normalise_attribute([#'AttributeTypeAndValue'{
                        type = _Type,
                        value = String} = ATV]) when is_list(String) ->
    %% A string returned by pubkey_cert_records:transform/2, for
    %% certain attributes that commonly have incorrect value types.
    NewValue = normalise_attribute_value(String),
    [ATV#'AttributeTypeAndValue'{value = NewValue}].

normalise_attribute_value(String) ->
    Converted = unicode:characters_to_binary(String),
    NormalisedString = normalise_string(Converted),
    %% We can't use the encoding function for the actual type of the
    %% attribute, since some of them don't allow utf8Strings, which is
    %% the required encoding when creating the hash.
    {NewBinary, _} = 'OTP-PUB-KEY':'enc_X520CommonName'({utf8String, NormalisedString}, []),
    NewBinary.

normalise_string(String) ->
    %% Normalise attribute values as required for "short hashes", as
    %% implemented by OpenSSL.

    %% Remove ASCII whitespace from beginning and end.
    TrimmedLeft = re:replace(String, "^[\s\f\n\r\t\v]+", "", [unicode, global]),
    TrimmedRight = re:replace(TrimmedLeft, "[\s\f\n\r\t\v]+$", "", [unicode, global]),
    %% Convert multiple whitespace characters to a single space.
    Collapsed = re:replace(TrimmedRight, "[\s\f\n\r\t\v]+", "\s", [unicode, global]),
    %% Convert ASCII characters to lowercase
    Lower = ascii_to_lower(Collapsed),
    %% And we're done!
    Lower.

ascii_to_lower(String) ->
    %% Can't use string:to_lower/1, because that changes Latin-1
    %% characters as well.
    << <<(if $A =< C, C =< $Z ->
                  C + ($a - $A);
             true ->
                  C
          end)>>
       ||
        <<C>> <= iolist_to_binary(String) >>.

%%%----------------------------------------------------------------
%%% pkix_verify_hostname help functions
verify_hostname_extract_fqdn_default({dns_id,S}) ->
    S;
verify_hostname_extract_fqdn_default({uri_id,URI}) ->
    #{scheme := "https", host := Host} = uri_string:normalize(URI, [return_map]),
    Host.


verify_hostname_fqnds(L, FqdnFun) ->
    [E || E0 <- L,
	  E <- [try case FqdnFun(E0) of
			default -> verify_hostname_extract_fqdn_default(E0);
                        undefined -> undefined; % will make the "is_list(E)" test fail
			Other -> Other
		    end
		catch _:_-> undefined % will make the "is_list(E)" test fail
		end],
	  is_list(E),
	  E =/= "",
	  {error,einval} == inet:parse_address(E)
    ].


-define(srvName_OID, {1,3,6,1,4,1,434,2,2,1,37,0}).

verify_hostname_match_default(Ref, Pres) ->
    verify_hostname_match_default0(to_lower_ascii(Ref), to_lower_ascii(Pres)).

verify_hostname_match_default0(FQDN=[_|_], {cn,FQDN}) -> 
    not lists:member($*, FQDN);
verify_hostname_match_default0(FQDN=[_|_], {cn,Name=[_|_]}) -> 
    verify_hostname_match_wildcard(FQDN, Name);
verify_hostname_match_default0({dns_id,R}, {dNSName,P}) ->
    R==P;
verify_hostname_match_default0({uri_id,R}, {uniformResourceIdentifier,P}) ->
    R==P;
verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 4 ->
    %% IPv4
    try
        list_to_tuple(P)
            == if is_tuple(R), size(R)==4 -> R;
                  is_list(R) -> ok(inet:parse_ipv4strict_address(R))
               end
    catch
        _:_ ->
            false
    end;

verify_hostname_match_default0({ip,R}, {iPAddress,P}) when length(P) == 16 ->
    %% IPv6. The length 16 is due to the certificate specification.
    try
        l16_to_tup(P)
            == if is_tuple(R), size(R)==8 -> R;
                  is_list(R) -> ok(inet:parse_ipv6strict_address(R))
               end
    catch
        _:_ ->
            false
    end;
verify_hostname_match_default0({srv_id,R}, {srvName,P}) ->
    R==P;
verify_hostname_match_default0({srv_id,R}, {?srvName_OID,P}) ->
    R==P;
verify_hostname_match_default0(_, _) ->
    false.


verify_hostname_match_wildcard(FQDN, Name) ->
    [F1|Fs] = string:tokens(to_lower_ascii(FQDN), "."),
    [N1|Ns] = string:tokens(to_lower_ascii(Name), "."),
    match_wild(F1,N1) andalso Fs==Ns.


ok({ok,X}) -> X.

l16_to_tup(L) -> list_to_tuple(l16_to_tup(L, [])).
%%
l16_to_tup([A,B|T], Acc) -> l16_to_tup(T, [(A bsl 8) bor B | Acc]);
l16_to_tup([], Acc) -> lists:reverse(Acc).
    
match_wild(A,     [$*|B]) -> match_wild_suffixes(A, B);
match_wild([C|A], [ C|B]) -> match_wild(A, B);
match_wild([],        []) -> true;
match_wild(_,          _) -> false.

%% Match the parts after the only wildcard by comparing them from the end
match_wild_suffixes(A, B) -> match_wild_sfx(lists:reverse(A), lists:reverse(B)).

match_wild_sfx([$*|_],      _) -> false; % Bad name (no wildcards allowed)
match_wild_sfx(_,      [$*|_]) -> false; % Bad pattern (no more wildcards allowed)
match_wild_sfx([A|Ar], [A|Br]) -> match_wild_sfx(Ar, Br);
match_wild_sfx(Ar,         []) -> not lists:member($*, Ar); % Chk for bad name (= wildcards)
match_wild_sfx(_,           _) -> false.
    

verify_hostname_match_loop(Refs0, Pres0, undefined, FailCB, Cert) ->
    Pres = lists:map(fun to_lower_ascii/1, Pres0),
    Refs = lists:map(fun to_lower_ascii/1, Refs0),
    lists:any(
      fun(R) ->
	      lists:any(fun(P) ->
                                verify_hostname_match_default(R,P) orelse FailCB(Cert)
			end, Pres)
      end, Refs);
verify_hostname_match_loop(Refs, Pres, MatchFun, FailCB, Cert) ->
    lists:any(
      fun(R) ->
	      lists:any(fun(P) ->
				(case MatchFun(R,P) of
				     default -> verify_hostname_match_default(R,P);
				     Bool -> Bool
				 end) orelse FailCB(Cert)
			end,
			Pres)
      end,
      Refs).


to_lower_ascii({ip,_}=X) -> X;
to_lower_ascii({iPAddress,_}=X) -> X;
to_lower_ascii(S) when is_list(S) -> lists:map(fun to_lower_ascii/1, S);
to_lower_ascii({T,S}) -> {T, to_lower_ascii(S)};
to_lower_ascii(C) when $A =< C,C =< $Z -> C + ($a-$A);
to_lower_ascii(C) -> C.

to_string(S) when is_list(S) -> S;
to_string(B) when is_binary(B) -> binary_to_list(B);
to_string(X) -> X.

format_details([]) ->
    no_relevant_crls;
format_details(Details) ->
    Details.
  
ocsp_status(Cert, IssuerCert, Responses) ->
    case pubkey_ocsp:find_single_response(Cert, IssuerCert, Responses) of
        {ok, #'SingleResponse'{certStatus = CertStatus}} ->
            pubkey_ocsp:ocsp_status(CertStatus);
        {error, no_matched_response = Reason} ->
            {bad_cert, {revocation_status_undetermined, Reason}}
    end.

ocsp_responses(OCSPResponseDer, ResponderCerts, Nonce) ->
    pubkey_ocsp:verify_ocsp_response(OCSPResponseDer, 
                                     ResponderCerts, Nonce).

subject_public_key_info(Alg, PubKey) ->
    #'OTPSubjectPublicKeyInfo'{algorithm = Alg, subjectPublicKey = PubKey}.
