Skip to content

Commit

Permalink
OTP-8554 Certificate extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
IngelaAndin authored and Erlang/OTP committed Apr 13, 2010
1 parent 3137955 commit e8b92d4
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 61 deletions.
11 changes: 8 additions & 3 deletions lib/public_key/src/public_key.erl
Expand Up @@ -28,7 +28,7 @@
encrypt_public/3, decrypt_public/2, decrypt_public/3,
encrypt_private/2, encrypt_private/3, gen_key/1, sign/2, sign/3,
verify_signature/3, verify_signature/4, verify_signature/5,
pem_to_der/1, pem_to_der/2,
pem_to_der/1, pem_to_der/2, der_to_pem/2,
pkix_decode_cert/2, pkix_encode_cert/1, pkix_transform/2,
pkix_is_self_signed/1, pkix_is_fixed_dh_cert/1,
pkix_issuer_id/2,
Expand Down Expand Up @@ -163,6 +163,10 @@ pem_to_der(File, Password) when is_list(File) ->
pubkey_pem:read_file(File, Password);
pem_to_der(PemBin, Password) when is_binary(PemBin) ->
pubkey_pem:decode(PemBin, Password).

der_to_pem(File, TypeDerList) ->
pubkey_pem:write_file(File, TypeDerList).

%%--------------------------------------------------------------------
%% Function: pkix_decode_cert(BerCert, Type) -> {ok, Cert} | {error, Reason}
%%
Expand Down Expand Up @@ -314,9 +318,10 @@ sign(Msg, #'RSAPrivateKey'{} = Key) when is_binary(Msg) ->
sign(Msg, #'DSAPrivateKey'{} = Key) when is_binary(Msg) ->
pubkey_crypto:sign(Msg, Key);

sign(#'OTPTBSCertificate'{signature = SigAlg} = TBSCert, Key) ->
sign(#'OTPTBSCertificate'{signature = #'SignatureAlgorithm'{algorithm = Alg}
= SigAlg} = TBSCert, Key) ->
Msg = pubkey_cert_records:encode_tbs_cert(TBSCert),
DigestType = pubkey_cert:digest_type(SigAlg),
DigestType = pubkey_cert:digest_type(Alg),
Signature = pubkey_crypto:sign(DigestType, Msg, Key),
Cert = #'OTPCertificate'{tbsCertificate= TBSCert,
signatureAlgorithm = SigAlg,
Expand Down
6 changes: 5 additions & 1 deletion lib/ssl/src/ssl.erl
Expand Up @@ -547,6 +547,7 @@ handle_options(Opts0, Role) ->
fail_if_no_peer_cert = validate_option(fail_if_no_peer_cert,
FailIfNoPeerCert),
verify_client_once = handle_option(verify_client_once, Opts, false),
validate_extensions_fun = handle_option(validate_extensions_fun, Opts, undefined),
depth = handle_option(depth, Opts, 1),
certfile = CertFile,
keyfile = handle_option(keyfile, Opts, CertFile),
Expand All @@ -563,7 +564,7 @@ handle_options(Opts0, Role) ->
},

CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed}),
SslOptions = [versions, verify, verify_fun,
SslOptions = [versions, verify, verify_fun, validate_extensions_fun,
fail_if_no_peer_cert, verify_client_once,
depth, certfile, keyfile,
key, password, cacertfile, dhfile, ciphers,
Expand Down Expand Up @@ -598,6 +599,9 @@ validate_option(fail_if_no_peer_cert, Value)
validate_option(verify_client_once, Value)
when Value == true; Value == false ->
Value;

validate_option(validate_extensions_fun, Value) when Value == undefined; is_function(Value) ->
Value;
validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
Expand Down
53 changes: 47 additions & 6 deletions lib/ssl/src/ssl_certificate.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
%%
%%
%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%%
%% %CopyrightEnd%
%%

Expand All @@ -29,10 +29,12 @@
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_debug.hrl").
-include_lib("public_key/include/public_key.hrl").

-export([trusted_cert_and_path/3,
certificate_chain/2,
file_to_certificats/1]).
file_to_certificats/1,
validate_extensions/6]).

%%====================================================================
%% Internal application API
Expand Down Expand Up @@ -87,6 +89,30 @@ file_to_certificats(File) ->
{ok, List} = ssl_manager:cache_pem_file(File),
[Bin || {cert, Bin, not_encrypted} <- List].


%% Validates ssl/tls specific extensions
validate_extensions([], ValidationState, UnknownExtensions, _, AccErr, _) ->
{UnknownExtensions, ValidationState, AccErr};

validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage',
extnValue = KeyUse,
critical = true} | Rest],
ValidationState, UnknownExtensions, Verify, AccErr0, Role) ->
case is_valid_extkey_usage(KeyUse, Role) of
true ->
validate_extensions(Rest, ValidationState, UnknownExtensions,
Verify, AccErr0, Role);
false ->
AccErr =
not_valid_extension({bad_cert, invalid_ext_key_usage}, Verify, AccErr0),
validate_extensions(Rest, ValidationState, UnknownExtensions, Verify, AccErr, Role)
end;

validate_extensions([Extension | Rest], ValidationState, UnknownExtensions,
Verify, AccErr, Role) ->
validate_extensions(Rest, ValidationState, [Extension | UnknownExtensions],
Verify, AccErr, Role).

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
Expand Down Expand Up @@ -154,3 +180,18 @@ not_valid(Alert, true, _) ->
throw(Alert);
not_valid(_, false, {ErlCert, Path}) ->
{ErlCert, Path, [{bad_cert, unknown_ca}]}.

is_valid_extkey_usage(KeyUse, client) ->
%% Client wants to verify server
is_valid_key_usage(KeyUse,?'id-kp-serverAuth');
is_valid_extkey_usage(KeyUse, server) ->
%% Server wants to verify client
is_valid_key_usage(KeyUse, ?'id-kp-clientAuth').

is_valid_key_usage(KeyUse, Use) ->
lists:member(Use, KeyUse).

not_valid_extension(Error, true, _) ->
throw(Error);
not_valid_extension(Error, false, AccErrors) ->
[Error | AccErrors].
7 changes: 4 additions & 3 deletions lib/ssl/src/ssl_connection.erl
Expand Up @@ -436,15 +436,16 @@ certify(#certificate{asn1_certificates = []},

certify(#certificate{} = Cert,
#state{negotiated_version = Version,
role = Role,
cert_db_ref = CertDbRef,
ssl_options = Opts} = State) ->
case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
Opts#ssl_options.verify_fun) of
Opts#ssl_options.verify_fun,
Opts#ssl_options.validate_extensions_fun, Role) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(PeerCert, PublicKeyInfo,
State#state{client_certificate_requested
= false});
State#state{client_certificate_requested = false});
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify_certificate, State),
{stop, normal, State}
Expand Down
23 changes: 20 additions & 3 deletions lib/ssl/src/ssl_handshake.erl
Expand Up @@ -32,7 +32,7 @@
-include_lib("public_key/include/public_key.hrl").

-export([master_secret/4, client_hello/4, server_hello/3, hello/2,
hello_request/0, certify/5, certificate/3,
hello_request/0, certify/7, certificate/3,
client_certificate_verify/6,
certificate_verify/6, certificate_request/2,
key_exchange/2, server_key_exchange_hash/2, finished/4,
Expand Down Expand Up @@ -161,10 +161,25 @@ hello(#client_hello{client_version = ClientVersion, random = Random} = Hello,
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
MaxPathLen, Verify, VerifyFun) ->
MaxPathLen, Verify, VerifyFun, ValidateFun, Role) ->
[PeerCert | _] = ASN1Certs,
VerifyBool = verify_bool(Verify),


ValidateExtensionFun =
case ValidateFun of
undefined ->
fun(Extensions, ValidationState, Verify0, AccError) ->
ssl_certificate:validate_extensions(Extensions, ValidationState,
[], Verify0, AccError, Role)
end;
Fun ->
fun(Extensions, ValidationState, Verify0, AccError) ->
{NewExtensions, NewValidationState, NewAccError}
= ssl_certificate:validate_extensions(Extensions, ValidationState,
[], Verify0, AccError, Role),
Fun(NewExtensions, NewValidationState, Verify0, NewAccError)
end
end,
try
%% Allow missing root_cert and check that with VerifyFun
ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef, false) of
Expand All @@ -174,6 +189,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
[{max_path_length,
MaxPathLen},
{verify, VerifyBool},
{validate_extensions_fun,
ValidateExtensionFun},
{acc_errors,
VerifyErrors}]),
case Result of
Expand Down
2 changes: 2 additions & 0 deletions lib/ssl/src/ssl_internal.hrl
Expand Up @@ -57,6 +57,8 @@
verify_fun, % fun(CertVerifyErrors) -> boolean()
fail_if_no_peer_cert, % boolean()
verify_client_once, % boolean()
%% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError}
validate_extensions_fun,
depth, % integer()
certfile, % file()
keyfile, % file()
Expand Down

0 comments on commit e8b92d4

Please sign in to comment.