%%<copyright>
%% <year>2008-2008</year>
%% <holder>Ericsson AB, All Rights Reserved</holder>
%%</copyright>
%%<legalnotice>
%% 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.
%%
%% The Initial Developer of the Original Code is Ericsson AB.
%%</legalnotice>
%%

-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(File) ->
%%           pem_to_der(File, Password) -> {ok, [Entry]} | {error, Reason}
%%
%%	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: Read and decode PEM file and returns 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(File) ->
    pubkey_pem:read_file(File).

pem_to_der(File, Password) ->
    pubkey_pem:read_file(File, 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(_, _, _, verify_peer) ->
    {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
					}, _, verify_none) ->
    {ok, {{Algorithm, PublicKey, PublicKeyParams}, Tree, 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_signature(OtpCert, DerCert, Key, KeyParams,
				       AccErr1, Verify),
    AccErr3 = pubkey_cert:validate_issuer(OtpCert, Issuer, AccErr2, Verify),

    AccErr4 = pubkey_cert:validate_names(OtpCert, Permit, Exclude, Last,
					 AccErr3, Verify),
    AccErr5 = 
	pubkey_cert:validate_revoked_status(OtpCert, Verify, AccErr4),
    
    {ValidationState1, UnknownExtensions0, AccErr6} = 
	pubkey_cert:validate_extensions(OtpCert, ValidationState0, Verify,
					AccErr5),
    
    {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).
