Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 91044ae

Browse files
committed
Send ALPN in TLS handshake, require it in direct SSL connections
libpq now always tries to send ALPN. With the traditional negotiated SSL connections, the server accepts the ALPN, and refuses the connection if it's not what we expect, but connecting without ALPN is still OK. With the new direct SSL connections, ALPN is mandatory. NOTE: This uses "TBD-pgsql" as the protocol ID. We must register a proper one with IANA before the release! Author: Greg Stark, Heikki Linnakangas Reviewed-by: Matthias van de Meent, Jacob Champion
1 parent d39a49c commit 91044ae

File tree

7 files changed

+157
-2
lines changed

7 files changed

+157
-2
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,18 @@ const char *PQsslAttribute(const PGconn *conn, const char *attribute_name);
29442944
</para>
29452945
</listitem>
29462946
</varlistentry>
2947+
<varlistentry>
2948+
<term><literal>alpn</literal></term>
2949+
<listitem>
2950+
<para>
2951+
Application protocol selected by the TLS Application-Layer
2952+
Protocol Negotiation (ALPN) extension. The only protocol
2953+
supported by libpq is <literal>TBD-pgsql</literal>, so this is
2954+
mainly useful for checking whether the server supported ALPN or
2955+
not. Empty string if ALPN was not used.
2956+
</para>
2957+
</listitem>
2958+
</varlistentry>
29472959
</variablelist>
29482960
</para>
29492961

src/backend/libpq/be-secure-openssl.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ static int ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdat
6767
static int dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
6868
static int verify_cb(int ok, X509_STORE_CTX *ctx);
6969
static void info_cb(const SSL *ssl, int type, int args);
70+
static int alpn_cb(SSL *ssl,
71+
const unsigned char **out,
72+
unsigned char *outlen,
73+
const unsigned char *in,
74+
unsigned int inlen,
75+
void *userdata);
7076
static bool initialize_dh(SSL_CTX *context, bool isServerStart);
7177
static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
7278
static const char *SSLerrmessage(unsigned long ecode);
@@ -432,6 +438,9 @@ be_tls_open_server(Port *port)
432438
/* set up debugging/info callback */
433439
SSL_CTX_set_info_callback(SSL_context, info_cb);
434440

441+
/* enable ALPN */
442+
SSL_CTX_set_alpn_select_cb(SSL_context, alpn_cb, port);
443+
435444
if (!(port->ssl = SSL_new(SSL_context)))
436445
{
437446
ereport(COMMERROR,
@@ -571,6 +580,32 @@ be_tls_open_server(Port *port)
571580
return -1;
572581
}
573582

583+
/* Get the protocol selected by ALPN */
584+
port->alpn_used = false;
585+
{
586+
const unsigned char *selected;
587+
unsigned int len;
588+
589+
SSL_get0_alpn_selected(port->ssl, &selected, &len);
590+
591+
/* If ALPN is used, check that we negotiated the expected protocol */
592+
if (selected != NULL)
593+
{
594+
if (len == strlen(PG_ALPN_PROTOCOL) &&
595+
memcmp(selected, PG_ALPN_PROTOCOL, strlen(PG_ALPN_PROTOCOL)) == 0)
596+
{
597+
port->alpn_used = true;
598+
}
599+
else
600+
{
601+
/* shouldn't happen */
602+
ereport(COMMERROR,
603+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
604+
errmsg("received SSL connection request with unexpected ALPN protocol")));
605+
}
606+
}
607+
}
608+
574609
/* Get client certificate, if available. */
575610
port->peer = SSL_get_peer_certificate(port->ssl);
576611

@@ -1259,6 +1294,48 @@ info_cb(const SSL *ssl, int type, int args)
12591294
}
12601295
}
12611296

1297+
/* See pqcomm.h comments on OpenSSL implementation of ALPN (RFC 7301) */
1298+
static const unsigned char alpn_protos[] = PG_ALPN_PROTOCOL_VECTOR;
1299+
1300+
/*
1301+
* Server callback for ALPN negotiation. We use the standard "helper" function
1302+
* even though currently we only accept one value.
1303+
*/
1304+
static int
1305+
alpn_cb(SSL *ssl,
1306+
const unsigned char **out,
1307+
unsigned char *outlen,
1308+
const unsigned char *in,
1309+
unsigned int inlen,
1310+
void *userdata)
1311+
{
1312+
/*
1313+
* Why does OpenSSL provide a helper function that requires a nonconst
1314+
* vector when the callback is declared to take a const vector? What are
1315+
* we to do with that?
1316+
*/
1317+
int retval;
1318+
1319+
Assert(userdata != NULL);
1320+
Assert(out != NULL);
1321+
Assert(outlen != NULL);
1322+
Assert(in != NULL);
1323+
1324+
retval = SSL_select_next_proto((unsigned char **) out, outlen,
1325+
alpn_protos, sizeof(alpn_protos),
1326+
in, inlen);
1327+
if (*out == NULL || *outlen > sizeof(alpn_protos) || outlen <= 0)
1328+
return SSL_TLSEXT_ERR_NOACK; /* can't happen */
1329+
1330+
if (retval == OPENSSL_NPN_NEGOTIATED)
1331+
return SSL_TLSEXT_ERR_OK;
1332+
else if (retval == OPENSSL_NPN_NO_OVERLAP)
1333+
return SSL_TLSEXT_ERR_NOACK;
1334+
else
1335+
return SSL_TLSEXT_ERR_NOACK;
1336+
}
1337+
1338+
12621339
/*
12631340
* Set DH parameters for generating ephemeral DH keys. The
12641341
* DH parameters can take a long time to compute, so they must be

src/backend/tcop/backend_startup.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,14 @@ ProcessSSLStartup(Port *port)
407407
}
408408
Assert(port->ssl_in_use);
409409

410+
if (!port->alpn_used)
411+
{
412+
ereport(COMMERROR,
413+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
414+
errmsg("received direct SSL connection request without ALPN protocol negotiation extension")));
415+
goto reject;
416+
}
417+
410418
if (Trace_connection_negotiation)
411419
ereport(LOG,
412420
(errmsg("direct SSL connection accepted")));

src/bin/psql/command.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3882,18 +3882,21 @@ printSSLInfo(void)
38823882
const char *protocol;
38833883
const char *cipher;
38843884
const char *compression;
3885+
const char *alpn;
38853886

38863887
if (!PQsslInUse(pset.db))
38873888
return; /* no SSL */
38883889

38893890
protocol = PQsslAttribute(pset.db, "protocol");
38903891
cipher = PQsslAttribute(pset.db, "cipher");
38913892
compression = PQsslAttribute(pset.db, "compression");
3893+
alpn = PQsslAttribute(pset.db, "alpn");
38923894

3893-
printf(_("SSL connection (protocol: %s, cipher: %s, compression: %s)\n"),
3895+
printf(_("SSL connection (protocol: %s, cipher: %s, compression: %s, ALPN: %s)\n"),
38943896
protocol ? protocol : _("unknown"),
38953897
cipher ? cipher : _("unknown"),
3896-
(compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
3898+
(compression && strcmp(compression, "off") != 0) ? _("on") : _("off"),
3899+
alpn ? alpn : _("none"));
38973900
}
38983901

38993902
/*

src/include/libpq/libpq-be.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ typedef struct Port
203203
char *peer_cn;
204204
char *peer_dn;
205205
bool peer_cert_valid;
206+
bool alpn_used;
206207

207208
/*
208209
* OpenSSL structures. (Keep these last so that the locations of other

src/include/libpq/pqcomm.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ typedef struct CancelRequestPacket
139139
uint32 cancelAuthCode; /* secret key to authorize cancel */
140140
} CancelRequestPacket;
141141

142+
/* Application-Layer Protocol Negotiation is required for direct connections
143+
* to avoid protocol confusion attacks (e.g https://alpaca-attack.com/).
144+
*
145+
* ALPN is specified in RFC 7301
146+
*
147+
* This string should be registered at:
148+
* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
149+
*
150+
* OpenSSL uses this wire-format for the list of alpn protocols even in the
151+
* API. Both server and client take the same format parameter but the client
152+
* actually sends it to the server as-is and the server it specifies the
153+
* preference order to use to choose the one selected to send back.
154+
*
155+
* c.f. https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_select_cb.html
156+
*
157+
* The #define can be used to initialize a char[] vector to use directly in the API
158+
*/
159+
#define PG_ALPN_PROTOCOL "TBD-pgsql"
160+
#define PG_ALPN_PROTOCOL_VECTOR { 9, 'T','B','D','-','p','g','s','q','l' }
142161

143162
/*
144163
* A client can also start by sending a SSL or GSSAPI negotiation request to

src/interfaces/libpq/fe-secure-openssl.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,9 @@ destroy_ssl_system(void)
885885
#endif
886886
}
887887

888+
/* See pqcomm.h comments on OpenSSL implementation of ALPN (RFC 7301) */
889+
static unsigned char alpn_protos[] = PG_ALPN_PROTOCOL_VECTOR;
890+
888891
/*
889892
* Create per-connection SSL object, and load the client certificate,
890893
* private key, and trusted CA certs.
@@ -1233,6 +1236,22 @@ initialize_SSL(PGconn *conn)
12331236
}
12341237
}
12351238

1239+
/* Set ALPN */
1240+
{
1241+
int retval;
1242+
1243+
retval = SSL_set_alpn_protos(conn->ssl, alpn_protos, sizeof(alpn_protos));
1244+
1245+
if (retval != 0)
1246+
{
1247+
char *err = SSLerrmessage(ERR_get_error());
1248+
1249+
libpq_append_conn_error(conn, "could not set ssl alpn extension: %s", err);
1250+
SSLerrfree(err);
1251+
return -1;
1252+
}
1253+
}
1254+
12361255
/*
12371256
* Read the SSL key. If a key is specified, treat it as an engine:key
12381257
* combination if there is colon present - we don't support files with
@@ -1754,6 +1773,7 @@ PQsslAttributeNames(PGconn *conn)
17541773
"cipher",
17551774
"compression",
17561775
"protocol",
1776+
"alpn",
17571777
NULL
17581778
};
17591779
static const char *const empty_attrs[] = {NULL};
@@ -1808,6 +1828,21 @@ PQsslAttribute(PGconn *conn, const char *attribute_name)
18081828
if (strcmp(attribute_name, "protocol") == 0)
18091829
return SSL_get_version(conn->ssl);
18101830

1831+
if (strcmp(attribute_name, "alpn") == 0)
1832+
{
1833+
const unsigned char *data;
1834+
unsigned int len;
1835+
static char alpn_str[256]; /* alpn doesn't support longer than 255
1836+
* bytes */
1837+
1838+
SSL_get0_alpn_selected(conn->ssl, &data, &len);
1839+
if (data == NULL || len == 0 || len > sizeof(alpn_str) - 1)
1840+
return NULL;
1841+
memcpy(alpn_str, data, len);
1842+
alpn_str[len] = 0;
1843+
return alpn_str;
1844+
}
1845+
18111846
return NULL; /* unknown attribute */
18121847
}
18131848

0 commit comments

Comments
 (0)