SSL 3.11.1 : Certificate-based authentication fails with Firefox

Paul Guyot <>
Tue Jun 1 17:30:54 CEST 2010


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}

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,
                     {lists,foldl,3},				<--- this is the lists:foldl above


More information about the erlang-bugs mailing list