SSL 3.11.1 : Certificate-based authentication fails with Firefox
Paul Guyot
pguyot@REDACTED
Tue Jun 1 17:30:54 CEST 2010
Hello,
Certificate-based authentication fails with Firefox (on MacOS X), while the same setup succeeds with Safari, Chrome and curl (which probably use OpenSSL). The server is a very simple erlang SSL server that verifies the peer certificate :
ssl:ssl_accept(ClientTransportSocket, [{ssl_imp, new}, {active, false}, {fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacertfile, ?ALL_CERTIFICATES}, {certfile, ?SERVER_CERT_FILE}, {keyfile, ?SERVER_KEY_FILE}, {validate_extensions_fun, fun validate_extensions/4}, {verify_fun, fun(ErrorList) -> case ErrorList of [] -> true; _ -> io:format("ErrorList is ~p~n", [ErrorList]), false end end}], infinity)
The bug seems to be in next_state/3, for the handshake case, around line 1766 of ssl_connection.erl (from the dev branch on github).
{Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version),
Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}},
lists:foldl(Handle, Start, Packets)
Handlers (invoked from Handle) can call next_record to get the next packet. For example, handle_peer_cert/3 (line 1148), is as follows :
handle_peer_cert(PeerCert, PublicKeyInfo,
#state{session = Session} = State0) ->
State1 = State0#state{session =
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
{Record, State} = next_record(State1),
next_state(certify, Record, State).
next_record/1 will then attempt to get the next record, ignoring Packets list:
next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
inet:setopts(Socket, [{active,once}]),
{no_record, State};
next_record(#state{tls_cipher_texts = [CT | Rest],
connection_states = ConnStates0} = State) ->
case ssl_record:decode_cipher_text(CT, ConnStates0) of
{Plain, ConnStates} ->
{Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}};
#alert{} = Alert ->
{Alert, State}
end.
If the client sends a single packet in the handshake buffer (i.e. lists:foldl only runs once), everything is fine. The handler will call next_record that will either serve the packet from tls_cipher_texts or handshake with the transport layer with {active, once}.
If the client sends more than one packet in the handshake buffer (Data + Buf0 above), and more data to the socket that can get returned by next_record, events end up being processed in wrong order.
This is typically what happens with Firefox.
Packets is composed of three elements :
[{{certificate, [<<48,130,5,6,48,...>>]}, <<11, ...>>},
{{client_key_exchange, {client_diffie_hellman_public, <<...>>}}, <<16,...>>},
{{certificate_verify, <<120, ...>>}, <<15,...>>}]
While at the same time, a record is present in tls_cipher_texts.
Eventually, the process fails with a function_clause error because the state isn't what is expected (in this very case, there is no cipher_state defined here since no client_key_exchange packet was processed):
** Reason for termination =
** {function_clause,[{ssl_cipher,block_decipher,
[#Fun<ssl_cipher.3.32442245>,undefined,20,
<<120,5,167,147,201,226,3,57,134,28,129,147,
41,178,143,254,181,2,49,7,140,13,191,3,135,
237,179,175,167,41,239,235,34,22,61,214,
228,77,124,198,115,53,25,153,151,63,51,197>>,
{3,1}]},
{ssl_record,decipher,2},
{ssl_record,decode_cipher_text,2},
{ssl_connection,next_record,1},
{ssl_connection,next_state,3},
{lists,foldl,3}, <--- this is the lists:foldl above
{ssl_connection,next_state,3},
{gen_fsm,handle_msg,7}]}
Paul
More information about the erlang-bugs
mailing list