Subject: Re: [ietf-smtp] TLS1.3

From: Viktor Dukhovni <[email-masked]>
Date: Tue, 5 May 2020 09:50:20 -0400

On Wed, Apr 29, 2020 at 06:02:13PM -0400, Sam Varshavchik wrote:

> > It emits a fatal "unexpected message" alert.  Turn off the
> > client-cert request, and it works fine.
> >
> > Name withheld to protect the guilty.
> 
> I wouldn't necessarily blame the operator. It's almost a sure bet that  
> they're using OpenSSL, and this developer's opinion is that OpenSSL is one  
> of the worst, most confusing, and the most poorly documented API libraries  
> in existence.

I are of course free to vent, but on this particular issue the
frustration is misdirected.  The OpenSSL TLS client silently ignores
solications for client certificates when none are configured at the
client.  It is up to the server to then accept or deny the handshake.
[ Rare applications that want to select certificates dynamically can
register for a callback, but then the decisions are theirs to make. ]

> Things  
> have reached the stage where it's been determined that the OpenSSL library  
> is reporting an error code that its own documentation describes thusly:
> 
>       SSL_ERROR_SYSCALL
>           Some non-recoverable, fatal I/O error occurred. The OpenSSL error
>           queue may contain more information on the error. For socket I/O on
>           Unix systems, consult errno for details.
>
> And, immediately after that, the code in question does "consult errno", and  
> finds that it's zero. No error has occured, according to the operating  
> system.

This is likely TLS session termination without a TLS close notify, i.e.
potentially vulnerable to truncation attacks.  An empty read on EOF does
not set errno.  In OpenSSL 1.1.1e there was a fix for that, to make it
an SSL_ERROR_SSL, but that turned to have too many backwards compatibility
issues, so it got rolled back in 1.1.1f.

-- 
    Viktor.

---

From: Sam Varshavchik <[email-masked]>
Date: Tue, 05 May 2020 20:03:53 -0400

> register for a callback, but then the decisions are theirs to make. ]

So after reading the following:

# When a certificate was set using the SSL_CTX_use_certificate(3) family of  
# functions, it will be sent to the server. The TLS standard requires that  
# only a certificate is sent, if it matches the list of acceptable CAs sent by  
# the server. This constraint is violated by the default behavior of the  
# OpenSSL library.

This appeared to me like a client that was configured with a certificate for  
some other host, but sending it in error, here, and the server rejecting the  
connection for that reason.

Problematic TLS implementation have been quite common, historically. For a  
long time I was seeing frequently TLS-related misconfigurations with SMTP  
servers. I lost count how many servers I saw advertising STARTTLS but  
choking when taken up on their offer:

220 poorly-configured-server.example.com ESMTP
EHLO hythere
250-poorly-configured-server.example.com Ok.
250-STARTTLS
250-PIPELINING
250-8BITMIME
250-SIZE
250 DSN
STARTTLS
450 Not now, maybe someday.

At one point I had to carefully implement an adaptive, transient TLS  
blacklist: ignore the server's STARTTLS if you have a prior record of the  
server failing to negotiate a TLS connection.

> issues, so it got rolled back in 1.1.1f.

I did not find this called out in the documentation, just a blanket  
statement: check errno, here's your error. This behavior was observed not  
with an active connection, but in a middle of a handshake, inside  
SSL_accept. The connection failed with no other indication of what the issue  
was. I'm too lazy to dig into openssl's source, but it seems that SSL_accept  
uses BIO_read, where "no data was successfully read or written if the result  
is 0 or -1". Then the default BIO_should_retry returns an indication  
calculated as follows: the read or write failed with -1 and errno was EAGAIN  
or EWOULDBLOCK. A closed socket connection (obviously) does not meet that  
criteria.

So, I surmised that SSL_accept was likely calling BIO_read, and if it  
returns 0 or -1 BIO_should_retry gets called; and SSL_ERROR_WANT_READ if so,  
otherwise it must be SSL_ERROR_SYSCALL. And in this sequence, I was getting  
an SSL_ERROR_SYSCALL but errno remained unset. I could not see how this was  
documented behavior, from any public documentation.

---

From: Viktor Dukhovni <[email-masked]>
Date: Tue, 5 May 2020 22:11:37 -0400

> connection for that reason.

No that's not it.  And actually, OpenSSL is generally correct in using
the client certificate anyway.  The server may, e.g., not trust any CAs,
and verify client certs by fingerprint.  A client that is an MTA, and
not some user worried about privacy leaks has nothing to hide in
sendings the only certificate it is configured with.  The specification
assumes a narrow trust model that does not match reality.

> 450 Not now, maybe someday.

Indeed a minority are misconfigured in this manner.  When TLS is
optional (opportunistic and unauthenticated), Postfix will retry
in the clear when this happens, provided the message has been in the
queue for at least one retry interval.

> server failing to negotiate a TLS connection.

See above.

-- 
    Viktor.