diff options
-rw-r--r-- | erts/doc/src/erl_nif.xml | 30 | ||||
-rw-r--r-- | erts/emulator/beam/erl_drv_nif.h | 3 | ||||
-rw-r--r-- | erts/emulator/beam/erl_nif.h | 2 | ||||
-rw-r--r-- | erts/emulator/sys/common/erl_check_io.c | 78 | ||||
-rw-r--r-- | erts/emulator/test/nif_SUITE.erl | 22 | ||||
-rw-r--r-- | lib/crypto/c_src/crypto.c | 6 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_peer_fsm.erl | 4 | ||||
-rw-r--r-- | lib/diameter/src/diameter.appup.src | 6 | ||||
-rw-r--r-- | lib/diameter/vsn.mk | 2 | ||||
-rw-r--r-- | lib/ssl/src/ssl_certificate.erl | 97 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 54 | ||||
-rw-r--r-- | lib/ssl/test/ssl_certificate_verify_SUITE.erl | 36 | ||||
-rw-r--r-- | lib/ssl/test/ssl_handshake_SUITE.erl | 26 |
13 files changed, 281 insertions, 85 deletions
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index a20b8ee884..a5826307f7 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -3037,7 +3037,8 @@ enif_map_iterator_destroy(env, &iter);</code> <p>Argument <c>mode</c> describes the type of events to wait for. It can be <c>ERL_NIF_SELECT_READ</c>, <c>ERL_NIF_SELECT_WRITE</c> or a bitwise OR combination to wait for both. It can also be <c>ERL_NIF_SELECT_STOP</c> - which is described further below. When a read or write event is triggered, + or <c>ERL_NIF_SELECT_CANCEL</c> which are described further + below. When a read or write event is triggered, a notification message like this is sent to the process identified by <c>pid</c>:</p> <code type="none">{select, Obj, Ref, ready_input | ready_output}</code> @@ -3058,13 +3059,21 @@ enif_map_iterator_destroy(env, &iter);</code> <p>The notifications are one-shot only. To receive further notifications of the same type (read or write), repeated calls to <c>enif_select</c> must be made after receiving each notification.</p> + <p><c>ERL_NIF_SELECT_CANCEL</c> can be used to cancel previously + selected events. It must be used in a bitwise OR combination with + <c>ERL_NIF_SELECT_READ</c> and/or <c>ERL_NIF_SELECT_WRITE</c> to + indicate which type of event to cancel. The return value will + tell if the event was actualy cancelled or if a notification may + already have been sent.</p> <p>Use <c>ERL_NIF_SELECT_STOP</c> as <c>mode</c> in order to safely close an event object that has been passed to <c>enif_select</c>. The <seealso marker="#ErlNifResourceStop"><c>stop</c></seealso> callback of the resource <c>obj</c> will be called when it is safe to close the event object. This safe way of closing event objects must be used - even if all notifications have been received and no further calls to - <c>enif_select</c> have been made.</p> + even if all notifications have been received (or cancelled) and no + further calls to <c>enif_select</c> have been made. + <c>ERL_NIF_SELECT_STOP</c> will first cancel any selected events + before it calls or schedules the <c>stop</c> callback.</p> <p>The first call to <c>enif_select</c> for a specific OS <c>event</c> will establish a relation between the event object and the containing resource. All subsequent calls for an <c>event</c> must pass its containing resource as argument @@ -3086,7 +3095,15 @@ enif_map_iterator_destroy(env, &iter);</code> <item>The stop callback was called directly by <c>enif_select</c>.</item> <tag><c>ERL_NIF_SELECT_STOP_SCHEDULED</c></tag> <item>The stop callback was scheduled to run on some other thread - or later by this thread.</item> + or later by this thread.</item> + <tag><c>ERL_NIF_SELECT_READ_CANCELLED</c></tag> + <item>A read event was cancelled by <c>ERL_NIF_SELECT_CANCEL</c> or + <c>ERL_NIF_SELECT_STOP</c> and is guaranteed not to generate a + <c>ready_input</c> notification message.</item> + <tag><c>ERL_NIF_SELECT_WRITE_CANCELLED</c></tag> + <item>A write event was cancelled by <c>ERL_NIF_SELECT_CANCEL</c> or + <c>ERL_NIF_SELECT_STOP</c> and is guaranteed not to generate a + <c>ready_output</c> notification message.</item> </taglist> <p>Returns a negative value if the call failed where the following bits can be set:</p> <taglist> @@ -3112,6 +3129,11 @@ if (retval & ERL_NIF_SELECT_STOP_CALLED) { } </code> </note> + <note><p>The mode flag <c>ERL_NIF_SELECT_CANCEL</c> and the return flags + <c>ERL_NIF_SELECT_READ_CANCELLED</c> and + <c>ERL_NIF_SELECT_WRITE_CANCELLED</c> were introduced in erts-11.0 + (OTP-22.0).</p> + </note> </desc> </func> diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h index 31b4817fb1..9ef7c39d41 100644 --- a/erts/emulator/beam/erl_drv_nif.h +++ b/erts/emulator/beam/erl_drv_nif.h @@ -53,7 +53,8 @@ typedef enum { enum ErlNifSelectFlags { ERL_NIF_SELECT_READ = (1 << 0), ERL_NIF_SELECT_WRITE = (1 << 1), - ERL_NIF_SELECT_STOP = (1 << 2) + ERL_NIF_SELECT_STOP = (1 << 2), + ERL_NIF_SELECT_CANCEL = (1 << 3) }; /* diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index 4c09496ef1..c5227a0c23 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -160,6 +160,8 @@ typedef int ErlNifEvent; #define ERL_NIF_SELECT_STOP_SCHEDULED (1 << 1) #define ERL_NIF_SELECT_INVALID_EVENT (1 << 2) #define ERL_NIF_SELECT_FAILED (1 << 3) +#define ERL_NIF_SELECT_READ_CANCELLED (1 << 4) +#define ERL_NIF_SELECT_WRITE_CANCELLED (1 << 5) typedef enum { diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c index 9f115706dc..f421794f91 100644 --- a/erts/emulator/sys/common/erl_check_io.c +++ b/erts/emulator/sys/common/erl_check_io.c @@ -916,7 +916,7 @@ enif_select(ErlNifEnv* env, ctl_op = ERTS_POLL_OP_DEL; } else { - on = 1; + on = !(mode & ERL_NIF_SELECT_CANCEL); ASSERT(mode); if (mode & ERL_DRV_READ) { ctl_events |= ERTS_POLL_EV_IN; @@ -1042,38 +1042,51 @@ enif_select(ErlNifEnv* env, ret = 0; } else { /* off */ + ret = 0; if (state->type == ERTS_EV_TYPE_NIF) { - state->driver.nif->in.pid = NIL; - state->driver.nif->out.pid = NIL; - } - ASSERT(state->events==0); - if (!wake_poller) { - /* - * Safe to close fd now as it is not in pollset - * or there was no need to eject fd (kernel poll) - */ - if (state->type == ERTS_EV_TYPE_NIF) { - ASSERT(state->driver.stop.resource == resource); - call_stop = CALL_STOP_AND_RELEASE; - state->driver.stop.resource = NULL; + if (mode & ERL_NIF_SELECT_READ + && is_not_nil(state->driver.nif->in.pid)) { + state->driver.nif->in.pid = NIL; + ret |= ERL_NIF_SELECT_READ_CANCELLED; } - else { - ASSERT(!state->driver.stop.resource); - call_stop = CALL_STOP; + if (mode & ERL_NIF_SELECT_WRITE + && is_not_nil(state->driver.nif->out.pid)) { + state->driver.nif->out.pid = NIL; + ret |= ERL_NIF_SELECT_WRITE_CANCELLED; } - state->type = ERTS_EV_TYPE_NONE; - ret = ERL_NIF_SELECT_STOP_CALLED; } - else { - /* Not safe to close fd, postpone stop_select callback. */ - if (state->type == ERTS_EV_TYPE_NONE) { - ASSERT(!state->driver.stop.resource); - state->driver.stop.resource = resource; - enif_keep_resource(resource); + if (mode & ERL_NIF_SELECT_STOP) { + ASSERT(state->events==0); + if (!wake_poller) { + /* + * Safe to close fd now as it is not in pollset + * or there was no need to eject fd (kernel poll) + */ + if (state->type == ERTS_EV_TYPE_NIF) { + ASSERT(state->driver.stop.resource == resource); + call_stop = CALL_STOP_AND_RELEASE; + state->driver.stop.resource = NULL; + } + else { + ASSERT(!state->driver.stop.resource); + call_stop = CALL_STOP; + } + state->type = ERTS_EV_TYPE_NONE; + ret |= ERL_NIF_SELECT_STOP_CALLED; + } + else { + /* Not safe to close fd, postpone stop_select callback. */ + if (state->type == ERTS_EV_TYPE_NONE) { + ASSERT(!state->driver.stop.resource); + state->driver.stop.resource = resource; + enif_keep_resource(resource); + } + state->type = ERTS_EV_TYPE_STOP_NIF; + ret |= ERL_NIF_SELECT_STOP_SCHEDULED; } - state->type = ERTS_EV_TYPE_STOP_NIF; - ret = ERL_NIF_SELECT_STOP_SCHEDULED; } + else + ASSERT(mode & ERL_NIF_SELECT_CANCEL); } done: @@ -1251,7 +1264,8 @@ print_nif_select_op(erts_dsprintf_buf_t *dsbufp, (int) fd, mode & ERL_NIF_SELECT_READ ? " READ" : "", mode & ERL_NIF_SELECT_WRITE ? " WRITE" : "", - mode & ERL_NIF_SELECT_STOP ? " STOP" : "", + (mode & ERL_NIF_SELECT_STOP ? " STOP" + : (mode & ERL_NIF_SELECT_CANCEL ? " CANCEL" : "")), resource->type->module, resource->type->name, ref); @@ -2332,10 +2346,16 @@ drvmode2str(int mode) { static ERTS_INLINE char * nifmode2str(enum ErlNifSelectFlags mode) { + if (mode & ERL_NIF_SELECT_STOP) + return "STOP"; switch (mode) { case ERL_NIF_SELECT_READ: return "READ"; case ERL_NIF_SELECT_WRITE: return "WRITE"; - case ERL_NIF_SELECT_STOP: return "STOP"; + case ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE: return "READ|WRITE"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ: return "CANCEL|READ"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_WRITE: return "CANCEL|WRITE"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE: + return "CANCEL|READ|WRITE"; default: return "UNKNOWN"; } } diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index a2f3489943..edad62a9fb 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -485,12 +485,14 @@ t_on_load(Config) when is_list(Config) -> -define(ERL_NIF_SELECT_READ, (1 bsl 0)). -define(ERL_NIF_SELECT_WRITE, (1 bsl 1)). -define(ERL_NIF_SELECT_STOP, (1 bsl 2)). +-define(ERL_NIF_SELECT_CANCEL, (1 bsl 3)). -define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)). -define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)). -define(ERL_NIF_SELECT_INVALID_EVENT, (1 bsl 2)). -define(ERL_NIF_SELECT_FAILED, (1 bsl 3)). - +-define(ERL_NIF_SELECT_READ_CANCELLED, (1 bsl 4)). +-define(ERL_NIF_SELECT_WRITE_CANCELLED, (1 bsl 5)). select(Config) when is_list(Config) -> ensure_lib_loaded(Config), @@ -516,7 +518,16 @@ select(Config) when is_list(Config) -> end), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Pid,Ref), {Pid, done} = receive_any(1000), + + %% Cancel read + 0 = select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref), <<"hej">> = read_nif(R, 3), + 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref), + ?ERL_NIF_SELECT_READ_CANCELLED = + select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref), + ok = write_nif(W, <<"hej again">>), + [] = flush(0), + <<"hej again">> = read_nif(R, 9), %% Wait for write Written = write_full(W, $a), @@ -525,6 +536,15 @@ select(Config) when is_list(Config) -> Written = read_nif(R,byte_size(Written)), [{select, W, Ref, ready_output}] = flush(), + %% Cancel write + 0 = select_nif(W,?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL,W,null,Ref), + Written2 = write_full(W, $b), + 0 = select_nif(W,?ERL_NIF_SELECT_WRITE,W,null,Ref), + ?ERL_NIF_SELECT_WRITE_CANCELLED = + select_nif(W,?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL,W,null,Ref), + Written2 = read_nif(R,byte_size(Written2)), + [] = flush(0), + %% Close write and wait for EOF eagain = read_nif(R, 1), check_stop_ret(select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref)), diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 550342a88d..c7f36c95f1 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -5203,7 +5203,10 @@ static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM unsigned char *p; if (rsa == NULL) goto badarg; tmplen = RSA_size(rsa); - if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg; + if (!enif_alloc_binary(tmplen, &tmp_bin)) { + RSA_free(rsa); + goto badarg; + } p = out_bin.data; p++; i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen); @@ -5214,6 +5217,7 @@ static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM tmp_bin = in_bin; i = 1; } + RSA_free(rsa); } #endif } diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index d99f11a697..cf5e7f21d3 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% Copyright Ericsson AB 2010-2018. 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. @@ -901,7 +901,7 @@ outgoing(#diameter_packet{header = #diameter_header{is_request = false}} ok; %% Outgoing request: discard. -outgoing(Msg, #state{dpr = {_,_,_}}) -> +outgoing(Msg, #state{}) -> invalid(false, send_after_dpr, header(Msg)). header(#diameter_packet{header = H}) -> diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index 3389f11937..51830f5276 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -58,7 +58,8 @@ {"2.1.1", [{restart_application, diameter}]}, %% 20.1.2 {"2.1.2", [{restart_application, diameter}]}, %% 20.1.3 {"2.1.3", [{restart_application, diameter}]}, %% 20.2 - {"2.1.4", [{restart_application, diameter}]} %% 20.3 + {"2.1.4", [{restart_application, diameter}]}, %% 20.3 + {"2.1.5", [{update, diameter_peer_fsm}]} %% 21.0 ], [ {"0.9", [{restart_application, diameter}]}, @@ -98,6 +99,7 @@ {"2.1.1", [{restart_application, diameter}]}, {"2.1.2", [{restart_application, diameter}]}, {"2.1.3", [{restart_application, diameter}]}, - {"2.1.4", [{restart_application, diameter}]} + {"2.1.4", [{restart_application, diameter}]}, + {"2.1.5", [{update, diameter_peer_fsm}]} ] }. diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 3081034df9..8c75c9e55e 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -17,5 +17,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 2.1.5 +DIAMETER_VSN = 2.1.6 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 017d18ee2c..9997f5e0c8 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -33,6 +33,7 @@ -export([trusted_cert_and_path/4, certificate_chain/3, + certificate_chain/4, file_to_certificats/2, file_to_crls/2, validate/3, @@ -40,7 +41,8 @@ is_valid_key_usage/2, select_extension/2, extensions_list/1, - public_key_type/1 + public_key_type/1, + foldl_db/3 ]). %%==================================================================== @@ -79,7 +81,8 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) - %% Trusted must be selfsigned or it is an incomplete chain handle_path(Trusted, Path, PartialChainHandler); _ -> - %% Root CA could not be verified + %% Root CA could not be verified, but partial + %% chain handler may trusted a cert that we got handle_incomplete_chain(Path, PartialChainHandler) end end. @@ -94,10 +97,23 @@ certificate_chain(undefined, _, _) -> {error, no_cert}; certificate_chain(OwnCert, CertDbHandle, CertsDbRef) when is_binary(OwnCert) -> ErlCert = public_key:pkix_decode_cert(OwnCert, otp), - certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]); + certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert], []); certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> DerCert = public_key:pkix_encode('OTPCertificate', OwnCert, otp), - certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert]). + certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert], []). + +%%-------------------------------------------------------------------- +-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref(), [der_cert()]) -> + {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. +%% +%% Description: Create certificate chain with certs from +%%-------------------------------------------------------------------- +certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) when is_binary(Cert) -> + ErlCert = public_key:pkix_decode_cert(Cert, otp), + certificate_chain(ErlCert, Cert, CertDbHandle, CertsDbRef, [Cert], Candidates); +certificate_chain(Cert, CertDbHandle, CertsDbRef, Candidates) -> + DerCert = public_key:pkix_encode('OTPCertificate', Cert, otp), + certificate_chain(Cert, DerCert, CertDbHandle, CertsDbRef, [DerCert], Candidates). %%-------------------------------------------------------------------- -spec file_to_certificats(binary(), term()) -> [der_cert()]. %% @@ -187,9 +203,20 @@ public_key_type(?'id-ecPublicKey') -> ec. %%-------------------------------------------------------------------- +-spec foldl_db(fun(), db_handle() | {extracted, list()}, list()) -> + {ok, term()} | issuer_not_found. +%% +%% Description: +%%-------------------------------------------------------------------- +foldl_db(IsIssuerFun, CertDbHandle, []) -> + ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle); +foldl_db(IsIssuerFun, _, [_|_] = ListDb) -> + lists:foldl(IsIssuerFun, issuer_not_found, ListDb). + +%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) -> +certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain, ListDb) -> IssuerAndSelfSigned = case public_key:pkix_is_self_signed(OtpCert) of true -> @@ -200,12 +227,12 @@ certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) -> case IssuerAndSelfSigned of {_, true = SelfSigned} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned, ListDb); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) of {ok, {SerialNr, Issuer}} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, - SerialNr, Issuer, SelfSigned); + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, + SerialNr, Issuer, SelfSigned, ListDb); _ -> %% Guess the the issuer must be the root %% certificate. The verification of the @@ -214,19 +241,19 @@ certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) -> {ok, undefined, lists:reverse(Chain)} end; {{ok, {SerialNr, Issuer}}, SelfSigned} -> - certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned) + do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned, ListDb) end. -certificate_chain(_, _, [RootCert | _] = Chain, _, _, true) -> +do_certificate_chain(_, _, [RootCert | _] = Chain, _, _, true, _) -> {ok, RootCert, lists:reverse(Chain)}; -certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> +do_certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _, ListDb) -> case ssl_manager:lookup_trusted_cert(CertDbHandle, CertsDbRef, SerialNr, Issuer) of {ok, {IssuerCert, ErlCert}} -> ErlCert = public_key:pkix_decode_cert(IssuerCert, otp), certificate_chain(ErlCert, IssuerCert, - CertDbHandle, CertsDbRef, [IssuerCert | Chain]); + CertDbHandle, CertsDbRef, [IssuerCert | Chain], ListDb); _ -> %% The trusted cert may be obmitted from the chain as the %% counter part needs to have it anyway to be able to @@ -234,7 +261,8 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned {ok, undefined, lists:reverse(Chain)} end. -find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) -> + +find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef, ListDb) -> IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of @@ -252,26 +280,29 @@ find_issuer(OtpCert, BinCert, CertDbHandle, CertsDbRef) -> Acc end, - if is_reference(CertsDbRef) -> % actual DB exists - try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of - issuer_not_found -> - {error, issuer_not_found} - catch - {ok, _IssuerId} = Return -> - Return - end; - is_tuple(CertsDbRef), element(1,CertsDbRef) =:= extracted -> % cache bypass byproduct - {extracted, CertsData} = CertsDbRef, - DB = [Entry || {decoded, Entry} <- CertsData], - try lists:foldl(IsIssuerFun, issuer_not_found, DB) of - issuer_not_found -> - {error, issuer_not_found} - catch - {ok, _IssuerId} = Return -> - Return - end + Result = case is_reference(CertsDbRef) of + true -> + do_find_issuer(IsIssuerFun, CertDbHandle, ListDb); + false -> + {extracted, CertsData} = CertsDbRef, + DB = [Entry || {decoded, Entry} <- CertsData], + do_find_issuer(IsIssuerFun, CertDbHandle, DB) + end, + case Result of + issuer_not_found -> + {error, issuer_not_found}; + Result -> + Result end. +do_find_issuer(IssuerFun, CertDbHandle, CertDb) -> + try + foldl_db(IssuerFun, CertDbHandle, CertDb) + catch + throw:{ok, _} = Return -> + Return + end. + is_valid_extkey_usage(KeyUse, client) -> %% Client wants to verify server is_valid_key_usage(KeyUse,?'id-kp-serverAuth'); @@ -300,7 +331,7 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) -> {ok, IssuerId} -> {other, IssuerId}; {error, issuer_not_found} -> - case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) of + case find_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef, []) of {ok, IssuerId} -> {other, IssuerId}; Other -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 54197ea0f3..ced3c2675e 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -338,7 +338,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, Opts, CRLDbHandle, Role, Host) -> ServerName = server_name(Opts#ssl_options.server_name_indication, Host, Role), - [PeerCert | _] = ASN1Certs, + [PeerCert | ChainCerts ] = ASN1Certs, try {TrustedCert, CertPath} = ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, @@ -347,14 +347,14 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, CertDbHandle, CertDbRef, ServerName, Opts#ssl_options.customize_hostname_check, Opts#ssl_options.crl_check, CRLDbHandle, CertPath), - case public_key:pkix_path_validation(TrustedCert, - CertPath, - [{max_path_length, Opts#ssl_options.depth}, - {verify_fun, ValidationFunAndState}]) of + Options = [{max_path_length, Opts#ssl_options.depth}, + {verify_fun, ValidationFunAndState}], + case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of {ok, {PublicKeyInfo,_}} -> {PeerCert, PublicKeyInfo}; {error, Reason} -> - path_validation_alert(Reason) + handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, + CertDbHandle, CertDbRef) end catch error:{badmatch,{asn1, Asn1Reason}} -> @@ -363,7 +363,6 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, error:OtherReason -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason}) end. - %%-------------------------------------------------------------------- -spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(), binary(), ssl_handshake_history()) -> valid | #alert{}. @@ -1363,6 +1362,45 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath) {unknown, {SslState, UserState}} end. +handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, + Opts, Options, CertDbHandle, CertsDbRef) -> + handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); +handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0, + Opts, Options, CertDbHandle, CertsDbRef) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef, Chain0) of + {ok, _, [PeerCert | Chain] = OrdedChain} when Chain =/= Chain0 -> %% Chain appaears to be unorded + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(OrdedChain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + handle_path_validation_error(PathError, PeerCert, Path, + Opts, Options, CertDbHandle, CertsDbRef) + end; + _ -> + path_validation_alert(Reason) + end; +handle_path_validation_error(Reason, _, _, _, _,_, _) -> + path_validation_alert(Reason). + +handle_incomplete_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, PathError0) -> + case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of + {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found + {Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain, + CertDbHandle, CertsDbRef, + Opts#ssl_options.partial_chain), + case public_key:pkix_path_validation(Trusted, Path, Options) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, PathError} -> + path_validation_alert(PathError) + end; + _ -> + path_validation_alert(PathError0) + end. + path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); path_validation_alert({bad_cert, invalid_issuer}) -> @@ -1375,8 +1413,6 @@ path_validation_alert({bad_cert, unknown_critical_extension}) -> ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); path_validation_alert({bad_cert, {revoked, _}}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); -%%path_validation_alert({bad_cert, revocation_status_undetermined}) -> -%% ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) -> Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE), Alert#alert{reason = Details}; diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index c0981a9eaf..f677bf8a6e 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -88,7 +88,8 @@ tests() -> critical_extension_verify_client, critical_extension_verify_server, critical_extension_verify_none, - customize_hostname_check + customize_hostname_check, + incomplete_chain ]. error_handling_tests()-> @@ -1198,6 +1199,39 @@ customize_hostname_check(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). +incomplete_chain() -> + [{doc,"Test option verify_peer"}]. +incomplete_chain(Config) when is_list(Config) -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), + #{server_config := ServerConf, + client_config := ClientConf} = public_key:pkix_test_data(CertChainConf), + [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerConf), + ClientCas = proplists:get_value(cacerts, ClientConf), + + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, {verify, verify_peer}, + {cacerts, [ServerRoot]} | + proplists:delete(cacerts, ServerConf)]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, + {verify, verify_peer}, + {cacerts, ServerCas ++ ClientCas} | + proplists:delete(cacerts, ClientConf)]}]), + 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_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 9ae04184e2..b8b9989d30 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -40,7 +40,8 @@ all() -> [decode_hello_handshake, decode_single_hello_sni_extension_correctly, decode_empty_server_sni_correctly, select_proper_tls_1_2_rsa_default_hashsign, - ignore_hassign_extension_pre_tls_1_2]. + ignore_hassign_extension_pre_tls_1_2, + unorded_chain]. %%-------------------------------------------------------------------- init_per_suite(Config) -> @@ -173,6 +174,29 @@ ignore_hassign_extension_pre_tls_1_2(Config) -> {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}), {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}). +unorded_chain(Config) when is_list(Config) -> + DefConf = ssl_test_lib:default_cert_chain_conf(), + CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), + #{server_config := ServerConf, + client_config := _ClientConf} = public_key:pkix_test_data(CertChainConf), + PeerCert = proplists:get_value(cert, ServerConf), + CaCerts = [_, C1, C2] = proplists:get_value(cacerts, ServerConf), + {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, CaCerts}), + UnordedChain = case public_key:pkix_is_self_signed(C1) of + true -> + [C1, C2]; + false -> + [C2, C1] + end, + OrderedChain = [PeerCert | lists:reverse(UnordedChain)], + {ok, _, OrderedChain} = + ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + is_supported(Hash) -> Algos = crypto:supports(), Hashs = proplists:get_value(hashs, Algos), |