Thursday, April 30, 2020

mail/alpine vs GMail vs TLSv1.3

procter reported to me yesterday that the last time that he could use
his GMail account with alpine was before the Hobart hackathon, i.e.,
before the TLSv1.3 client was enabled.

There are two problems:

First, if you establish a TLSv1.3 connection to imap.gmail.com:993
without SNI, it answers with a self-signed cert containing

subject=/OU=No SNI provided; please fix your client./CN=invalid2.invalid
issuer=/OU=No SNI provided; please fix your client./CN=invalid2.invalid

Unless you turn off certificate validation in the alpine config, the
connection will fail. The SNI hunk is taken from alpine 2.22 [1].


Second, our TLSv1.3 stack tends to want more retries. alpine already
retries reads, but doesn't do it for writes. We verified that SSL_write
returns SSL_ERROR_WANT_WRITE. I did essentially the same thing we did
(and shortly after undid) for wget.


procter verified that the combination of these two fixes allows him to
use alpine with GMail imap and smtp again.

I'm both surprised and a bit worried that it took so long for somebody
to report this.

An alternative would be to update to alpine 2.22, but I suspect that the
SSL_write issue is still present there, so the patch below would seem to
be the safer option.

[1]: https://repo.or.cz/alpine.git/blob/99948a254e2c2352547b962cbd1c23738e7af6b3:/imap/src/osdep/unix/ssl_unix.c#l446

Index: Makefile
===================================================================
RCS file: /var/cvs/ports/mail/alpine/Makefile,v
retrieving revision 1.46
diff -u -p -r1.46 Makefile
--- Makefile 20 Mar 2020 16:44:24 -0000 1.46
+++ Makefile 29 Apr 2020 13:29:40 -0000
@@ -28,7 +28,7 @@ PKGNAME-mailutil= mailutil-uw-${V}
PKGNAME-pico= pico-${PICO_V}
PKGNAME-pilot= pilot-${PILOT_V}

-REVISION= 3
+REVISION= 4
REVISION-pico= 20
REVISION-pilot= 20

Index: patches/patch-imap_src_osdep_unix_ssl_unix_c
===================================================================
RCS file: patches/patch-imap_src_osdep_unix_ssl_unix_c
diff -N patches/patch-imap_src_osdep_unix_ssl_unix_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-imap_src_osdep_unix_ssl_unix_c 30 Apr 2020 10:49:20 -0000
@@ -0,0 +1,57 @@
+$OpenBSD$
+
+Workarounds for GMail:
+* imap.gmail.com requires SNI for TLSv1.3 clients
+* retry the writes if we're told to do so.
+
+Index: imap/src/osdep/unix/ssl_unix.c
+--- imap/src/osdep/unix/ssl_unix.c.orig
++++ imap/src/osdep/unix/ssl_unix.c
+@@ -266,6 +266,7 @@ static char *ssl_start_work (SSLSTREAM *stream,char *h
+ {
+ BIO *bio;
+ X509 *cert;
++ int ssl_err;
+ unsigned long sl,tl;
+ char *s,*t,*err,tmp[MAILTMPLEN], buf[256];
+ sslcertificatequery_t scq =
+@@ -313,12 +314,22 @@ static char *ssl_start_work (SSLSTREAM *stream,char *h
+ /* create connection */
+ if (!(stream->con = (SSL *) SSL_new (stream->context)))
+ return "SSL connection failed";
++ if (host && !SSL_set_tlsext_host_name(stream->con, host)) {
++ return "Server Name Identification (SNI) failed";
++ }
+ bio = BIO_new_socket (stream->tcpstream->tcpsi,BIO_NOCLOSE);
+ SSL_set_bio (stream->con,bio,bio);
+ SSL_set_connect_state (stream->con);
+ if (SSL_in_init (stream->con)) SSL_total_renegotiations (stream->con);
+ /* now negotiate SSL */
+- if (SSL_write (stream->con,"",0) < 0)
++ do {
++ ssl_err = SSL_write (stream->con,"",0);
++ } while ((ssl_err == -1 &&
++ SSL_get_error(stream->con, ssl_err) == SSL_ERROR_SYSCALL && errno == EINTR) ||
++ (ssl_err < 0 &&
++ (SSL_get_error(stream->con, ssl_err) == SSL_ERROR_WANT_READ ||
++ SSL_get_error(stream->con, ssl_err) == SSL_ERROR_WANT_WRITE)));
++ if (ssl_err < 0)
+ return ssl_last_error ? ssl_last_error : "SSL negotiation failed";
+ /* need to validate host names? */
+ if (!(flags & NET_NOVALIDATECERT) &&
+@@ -626,7 +637,14 @@ long ssl_sout (SSLSTREAM *stream,char *string,unsigned
+ /* until request satisfied */
+ for (i = 0; size > 0; string += i,size -= i)
+ /* write as much as we can */
+- if ((i = SSL_write (stream->con,string,(int) min (SSLBUFLEN,size))) < 0) {
++ do {
++ i = SSL_write (stream->con,string,(int) min (SSLBUFLEN,size));
++ } while ((i == -1 &&
++ SSL_get_error(stream->con, i) == SSL_ERROR_SYSCALL && errno == EINTR) ||
++ (i < 0 &&
++ (SSL_get_error(stream->con, i) == SSL_ERROR_WANT_READ ||
++ SSL_get_error(stream->con, i) == SSL_ERROR_WANT_WRITE)));
++ if (i < 0) {
+ if (tcpdebug) {
+ char tmp[MAILTMPLEN];
+ sprintf (tmp,"SSL data write I/O error %d SSL error %d",

No comments:

Post a Comment