From e8b92d40b92142d1654994f16855922b8060a484 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 13 Apr 2010 14:00:59 +0000 Subject: [PATCH] OTP-8554 Certificate extensions --- lib/public_key/src/public_key.erl | 11 +- lib/ssl/src/ssl.erl | 6 +- lib/ssl/src/ssl_certificate.erl | 53 ++++++- lib/ssl/src/ssl_connection.erl | 7 +- lib/ssl/src/ssl_handshake.erl | 23 +++- lib/ssl/src/ssl_internal.hrl | 2 + lib/ssl/test/ssl_basic_SUITE.erl | 191 ++++++++++++++++++++------ lib/ssl/test/ssl_test_lib.erl | 11 +- lib/ssl/test/ssl_to_openssl_SUITE.erl | 60 +++++++- 9 files changed, 303 insertions(+), 61 deletions(-) diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 9a90ffe8885a..157e76bb2127 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -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, @@ -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} %% @@ -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, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index da5f7507626b..95133cb2168b 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -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), @@ -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, @@ -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; diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index d97b61a5cebf..686e90a70cc8 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/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% %% @@ -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 @@ -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 %%-------------------------------------------------------------------- @@ -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]. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 2d8f20bc29fd..6dd05688f0bb 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -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} diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 02f689f25190..9f5ac7106ae2 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -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, @@ -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 @@ -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 diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index ab24c28b2fb7..8d19abfe1e5a 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -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() diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 3f64a3144882..ea9796b28517 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -26,12 +26,14 @@ -include("test_server.hrl"). -include("test_server_line.hrl"). +-include_lib("public_key/include/public_key.hrl"). -define('24H_in_sec', 86400). -define(TIMEOUT, 60000). -define(EXPIRE, 10). -define(SLEEP, 500). + -behaviour(ssl_session_cache_api). %% For the session cache tests @@ -166,7 +168,8 @@ all(suite) -> %%, session_cache_process_list, session_cache_process_mnesia ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session, client_renegotiate, server_renegotiate, - client_no_wrap_sequence_number, server_no_wrap_sequence_number + client_no_wrap_sequence_number, server_no_wrap_sequence_number, + extended_key_usage, validate_extensions_fun ]. %% Test cases starts here. @@ -878,9 +881,8 @@ tcp_connect(suite) -> []; tcp_connect(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, @@ -905,7 +907,7 @@ tcp_connect(Config) when is_list(Config) -> ssl_test_lib:close(Server). -dummy(Socket) -> +dummy(_Socket) -> %% Should not happen as the ssl connection will not be established %% due to fatal handshake failiure exit(kill). @@ -966,6 +968,7 @@ ekeyfile(Config) when is_list(Config) -> ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, {from, self()}, {options, BadOpts}]), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -1013,6 +1016,7 @@ ecacertfile(Config) when is_list(Config) -> ClientOpts = [{reuseaddr, true}|?config(client_opts, Config)], ServerBadOpts = [{reuseaddr, true}|?config(server_bad_ca, Config)], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Port = ssl_test_lib:inet_port(ServerNode), Server0 = @@ -1059,27 +1063,28 @@ eoptions(Config) when is_list(Config) -> ClientOpts = ?config(client_opts, Config), ServerOpts = ?config(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), + %% Emulated opts Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{active, trice} | ServerOpts]}]), + Client0 = ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, [{active, trice} | ClientOpts]}]), + {port, 0}, {host, Hostname}, + {from, self()}, + {options, [{active, trice} | ClientOpts]}]), ssl_test_lib:check_result(Server0, {error, {eoptions, {active,trice}}}, - Client0, {error, {eoptions, {active,trice}}}), + Client0, {error, {eoptions, {active,trice}}}), Server1 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{header, a} | ServerOpts]}]), Client1 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{header, a} | ClientOpts]}]), @@ -1087,25 +1092,25 @@ eoptions(Config) when is_list(Config) -> Client1, {error, {eoptions, {header, a}}}), Server2 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{mode, a} | ServerOpts]}]), - Client2 = ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, 0}, {host, Hostname}, {from, self()}, {options, [{mode, a} | ClientOpts]}]), ssl_test_lib:check_result(Server2, {error, {eoptions, {mode, a}}}, Client2, {error, {eoptions, {mode, a}}}), Server3 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{packet, 8.0} | ServerOpts]}]), + Client3 = ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, 0}, {host, Hostname}, {from, self()}, {options, [{packet, 8.0} | ClientOpts]}]), ssl_test_lib:check_result(Server3, {error, {eoptions, {packet, 8.0}}}, @@ -1113,11 +1118,12 @@ eoptions(Config) when is_list(Config) -> %% ssl Server4 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{verify, 4} | ServerOpts]}]), + Client4 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{verify, 4} | ClientOpts]}]), @@ -1125,11 +1131,12 @@ eoptions(Config) when is_list(Config) -> Client4, {error, {eoptions, {verify, 4}}}), Server5 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{depth, four} | ServerOpts]}]), + Client5 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{depth, four} | ClientOpts]}]), @@ -1137,11 +1144,12 @@ eoptions(Config) when is_list(Config) -> Client5, {error, {eoptions, {depth, four}}}), Server6 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{cacertfile, ""} | ServerOpts]}]), + Client6 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{cacertfile, ""} | ClientOpts]}]), @@ -1149,11 +1157,12 @@ eoptions(Config) when is_list(Config) -> Client6, {error, {eoptions, {cacertfile, ""}}}), Server7 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{certfile, 'cert.pem'} | ServerOpts]}]), + Client7 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{certfile, 'cert.pem'} | ClientOpts]}]), @@ -1162,11 +1171,12 @@ eoptions(Config) when is_list(Config) -> Client7, {error, {eoptions, {certfile, 'cert.pem'}}}), Server8 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{keyfile,'key.pem' } | ServerOpts]}]), + Client8 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{keyfile, 'key.pem'} | ClientOpts]}]), @@ -1175,11 +1185,12 @@ eoptions(Config) when is_list(Config) -> Client8, {error, {eoptions, {keyfile, 'key.pem'}}}), Server9 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{key, 'key.pem' } | ServerOpts]}]), + Client9 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{key, 'key.pem'} | ClientOpts]}]), @@ -1187,11 +1198,12 @@ eoptions(Config) when is_list(Config) -> Client9, {error, {eoptions, {key, 'key.pem'}}}), Server10 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{password, foo} | ServerOpts]}]), + Client10 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{password, foo} | ClientOpts]}]), @@ -1199,11 +1211,12 @@ eoptions(Config) when is_list(Config) -> Client10, {error, {eoptions, {password, foo}}}), %% Misc Server11 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{ssl_imp, cool} | ServerOpts]}]), + Client11 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{ssl_imp, cool} | ClientOpts]}]), @@ -1211,11 +1224,12 @@ eoptions(Config) when is_list(Config) -> Client11, {error, {eoptions, {ssl_imp, cool}}}), Server12 = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {options, [{debug, cool} | ServerOpts]}]), + Client12 = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, {host, Hostname}, {from, self()}, {options, [{debug, cool} | ClientOpts]}]), @@ -1874,17 +1888,19 @@ server_require_peer_cert_fail(Config) when is_list(Config) -> | ?config(server_verification_opts, Config)], BadClientOpts = ?config(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Port = ssl_test_lib:inet_port(ServerNode), + Port = ssl_test_lib:inet_port(ServerNode), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port}, {from, self()}, {mfa, {?MODULE, send_recv_result, []}}, {options, [{active, false} | ServerOpts]}]), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, send_recv_result, []}}, - {options, [{active, false} | BadClientOpts]}]), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result, []}}, + {options, [{active, false} | BadClientOpts]}]), ssl_test_lib:check_result(Server, {error, esslaccept}, Client, {error, esslconnect}), @@ -2145,6 +2161,99 @@ server_no_wrap_sequence_number(Config) when is_list(Config) -> ssl_test_lib:close(Client), ok. +%%-------------------------------------------------------------------- +extended_key_usage(doc) -> + ["Test cert that has a critical extended_key_usage extension"]; + +extended_key_usage(suite) -> + []; + +extended_key_usage(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + NewCertFile = filename:join(PrivDir, "cert.pem"), + + {ok, [{cert, DerCert, _}]} = public_key:pem_to_der(CertFile), + + {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile), + + {ok, Key} = public_key:decode_private_key(KeyInfo), + + {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp), + + ExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-serverAuth']}, + + OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, + + Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions, + + NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{extensions = [ExtKeyUsageExt |Extensions]}, + + NewDerCert = public_key:sign(NewOTPTbsCert, Key), + + public_key:der_to_pem(NewCertFile, [{cert, NewDerCert}]), + + NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, NewServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +validate_extensions_fun(doc) -> + ["Test that it is possible to specify a validate_extensions_fun"]; + +validate_extensions_fun(suite) -> + []; + +validate_extensions_fun(Config) when is_list(Config) -> + ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ?config(server_verification_opts, Config), + DataDir = ?config(data_dir, Config), + + Fun = fun(Extensions, State, _, AccError) -> + {Extensions, State, AccError} + end, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options, [{validate_extensions_fun, Fun}, + {verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_active, []}}, + {options,[{validate_extensions_fun, Fun} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 466e987e3e32..63a9bb8d87de 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -129,7 +129,11 @@ remove_close_msg(ReconnectTimes) -> start_client(Args) -> - spawn_link(?MODULE, run_client, [Args]). + Result = spawn_link(?MODULE, run_client, [Args]), + receive + connected -> + Result + end. run_client(Opts) -> Node = proplists:get_value(node, Opts), @@ -140,6 +144,7 @@ run_client(Opts) -> test_server:format("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), case rpc:call(Node, ssl, connect, [Host, Port, Options]) of {ok, Socket} -> + Pid ! connected, test_server:format("Client: connected~n", []), case proplists:get_value(port, Options) of 0 -> @@ -271,7 +276,7 @@ cert_options(Config) -> "badcert.pem"]), BadKeyFile = filename:join([?config(priv_dir, Config), "badkey.pem"]), - [{client_opts, [{ssl_imp, new}]}, + [{client_opts, [{ssl_imp, new},{reuseaddr, true}]}, {client_verification_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, @@ -370,7 +375,7 @@ run_upgrade_client(Opts) -> test_server:format("gen_tcp:connect(~p, ~p, ~p)~n", [Host, Port, TcpOptions]), {ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]), - + case proplists:get_value(port, Opts) of 0 -> {ok, {_, NewPort}} = inet:sockname(Socket), diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 624404b556ee..06e5d2ef1825 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -32,6 +32,7 @@ -define(SLEEP, 1000). -define(OPENSSL_RENEGOTIATE, "r\n"). -define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_GARBAGE, "P\n"). %% Test server callback functions %%-------------------------------------------------------------------- @@ -131,7 +132,8 @@ all(suite) -> tls1_erlang_client_openssl_server_client_cert, tls1_erlang_server_openssl_client_client_cert, tls1_erlang_server_erlang_client_client_cert, - ciphers + ciphers, + erlang_client_bad_openssl_server ]. %% Test cases starts here. @@ -945,6 +947,52 @@ cipher(CipherSuite, Version, Config) -> Return. %%-------------------------------------------------------------------- +erlang_client_bad_openssl_server(doc) -> + [""]; +erlang_client_bad_openssl_server(suite) -> + []; +erlang_client_bad_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ?config(client_verification_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + test_server:sleep(?SLEEP), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [tlsv1]} | ClientOpts]}]), + + %% Send garbage + port_command(OpensslPort, ?OPENSSL_GARBAGE), + + test_server:sleep(?SLEEP), + + Client ! server_sent_garbage, + + ssl_test_lib:check_result(Client, true), + + ssl_test_lib:close(Client), + %% Clean close down! + close_port(OpensslPort), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- erlang_ssl_receive(Socket, Data) -> test_server:format("Connection info: ~p~n", @@ -1014,3 +1062,13 @@ close_loop(Port, Time, SentClose) -> io:format("Timeout~n",[]) end end. + + +server_sent_garbage(Socket) -> + receive + server_sent_garbage -> + {error, closed} == ssl:send(Socket, "data") + end. + + +