Skip to content

Commit d39a49c

Browse files
committed
Support TLS handshake directly without SSLRequest negotiation
By skipping SSLRequest, you can eliminate one round-trip when establishing a TLS connection. It is also more friendly to generic TLS proxies that don't understand the PostgreSQL protocol. This is disabled by default in libpq, because the direct TLS handshake will fail with old server versions. It can be enabled with the sslnegotation=direct option. It will still fall back to the negotiated TLS handshake if the server rejects the direct attempt, either because it is an older version or the server doesn't support TLS at all, but the fallback can be disabled with the sslnegotiation=requiredirect option. Author: Greg Stark, Heikki Linnakangas Reviewed-by: Matthias van de Meent, Jacob Champion
1 parent 05fd30c commit d39a49c

File tree

12 files changed

+609
-167
lines changed

12 files changed

+609
-167
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,8 +1740,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
17401740
encryption, regardless of the value of <literal>sslmode</literal>.
17411741
To force use of <acronym>SSL</acronym> encryption in an
17421742
environment that has working <acronym>GSSAPI</acronym>
1743-
infrastructure (such as a Kerberos server), also
1744-
set <literal>gssencmode</literal> to <literal>disable</literal>.
1743+
infrastructure (such as a Kerberos server), also set
1744+
<literal>gssencmode</literal> to <literal>disable</literal>.
17451745
</para>
17461746
</listitem>
17471747
</varlistentry>
@@ -1768,6 +1768,67 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
17681768
</listitem>
17691769
</varlistentry>
17701770

1771+
<varlistentry id="libpq-connect-sslnegotiation" xreflabel="sslnegotiation">
1772+
<term><literal>sslnegotiation</literal></term>
1773+
<listitem>
1774+
<para>
1775+
This option controls whether <productname>PostgreSQL</productname>
1776+
will perform its protocol negotiation to request encryption from the
1777+
server or will just directly make a standard <acronym>SSL</acronym>
1778+
connection. Traditional <productname>PostgreSQL</productname>
1779+
protocol negotiation is the default and the most flexible with
1780+
different server configurations. If the server is known to support
1781+
direct <acronym>SSL</acronym> connections then the latter requires one
1782+
fewer round trip reducing connection latency and also allows the use
1783+
of protocol agnostic SSL network tools.
1784+
</para>
1785+
1786+
<variablelist>
1787+
<varlistentry>
1788+
<term><literal>postgres</literal></term>
1789+
<listitem>
1790+
<para>
1791+
perform <productname>PostgreSQL</productname> protocol
1792+
negotiation. This is the default if the option is not provided.
1793+
</para>
1794+
</listitem>
1795+
</varlistentry>
1796+
1797+
<varlistentry>
1798+
<term><literal>direct</literal></term>
1799+
<listitem>
1800+
<para>
1801+
first attempt to establish a standard SSL connection and if that
1802+
fails reconnect and perform the negotiation. This fallback
1803+
process adds significant latency if the initial SSL connection
1804+
fails.
1805+
</para>
1806+
</listitem>
1807+
</varlistentry>
1808+
1809+
<varlistentry>
1810+
<term><literal>requiredirect</literal></term>
1811+
<listitem>
1812+
<para>
1813+
attempt to establish a standard SSL connection and if that fails
1814+
return a connection failure immediately.
1815+
</para>
1816+
</listitem>
1817+
</varlistentry>
1818+
</variablelist>
1819+
1820+
<para>
1821+
Note that if <literal>gssencmode</literal> is set
1822+
to <literal>prefer</literal>, a <acronym>GSS</acronym> connection is
1823+
attempted first. If the server ejectes GSS encryption, SSL is
1824+
negotiated over the same TCP connection using the traditional postgres
1825+
protocol, regardless of <literal>sslnegotiation</literal>. In other
1826+
words, the direct SSL handshake is not used, if a TCP connection has
1827+
already been established and can be used for the SSL handshake.
1828+
</para>
1829+
</listitem>
1830+
</varlistentry>
1831+
17711832
<varlistentry id="libpq-connect-sslcompression" xreflabel="sslcompression">
17721833
<term><literal>sslcompression</literal></term>
17731834
<listitem>
@@ -2001,11 +2062,13 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
20012062

20022063
<para>
20032064
The Server Name Indication can be used by SSL-aware proxies to route
2004-
connections without having to decrypt the SSL stream. (Note that this
2005-
requires a proxy that is aware of the PostgreSQL protocol handshake,
2006-
not just any SSL proxy.) However, <acronym>SNI</acronym> makes the
2007-
destination host name appear in cleartext in the network traffic, so
2008-
it might be undesirable in some cases.
2065+
connections without having to decrypt the SSL stream. (Note that
2066+
unless the proxy is aware of the PostgreSQL protocol handshake this
2067+
would require setting <literal>sslnegotiation</literal>
2068+
to <literal>direct</literal> or <literal>requiredirect</literal>.)
2069+
However, <acronym>SNI</acronym> makes the destination host name appear
2070+
in cleartext in the network traffic, so it might be undesirable in
2071+
some cases.
20092072
</para>
20102073
</listitem>
20112074
</varlistentry>
@@ -8676,6 +8739,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
86768739
</para>
86778740
</listitem>
86788741

8742+
<listitem>
8743+
<para>
8744+
<indexterm>
8745+
<primary><envar>PGSSLNEGOTIATION</envar></primary>
8746+
</indexterm>
8747+
<envar>PGSSLNEGOTIATION</envar> behaves the same as the <xref
8748+
linkend="libpq-connect-sslnegotiation"/> connection parameter.
8749+
</para>
8750+
</listitem>
8751+
86798752
<listitem>
86808753
<para>
86818754
<indexterm>

doc/src/sgml/protocol.sgml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,11 +1529,47 @@ SELCT 1/0;<!-- this typo is intentional -->
15291529
bytes.
15301530
</para>
15311531

1532+
<para>
1533+
Likewise the server expects the client to not begin
1534+
the <acronym>SSL</acronym> negotiation until it receives the server's
1535+
single byte response to the <acronym>SSL</acronym> request. If the
1536+
client begins the <acronym>SSL</acronym> negotiation immediately without
1537+
waiting for the server response to be received it can reduce connection
1538+
latency by one round-trip. However this comes at the cost of not being
1539+
able to handle the case where the server sends a negative response to the
1540+
<acronym>SSL</acronym> request. In that case instead of continuing with either GSSAPI or an
1541+
unencrypted connection or a protocol error the server will simply
1542+
disconnect.
1543+
</para>
1544+
15321545
<para>
15331546
An initial SSLRequest can also be used in a connection that is being
15341547
opened to send a CancelRequest message.
15351548
</para>
15361549

1550+
<para>
1551+
A second alternate way to initiate <acronym>SSL</acronym> encryption is
1552+
available. The server will recognize connections which immediately
1553+
begin <acronym>SSL</acronym> negotiation without any previous SSLRequest
1554+
packets. Once the <acronym>SSL</acronym> connection is established the
1555+
server will expect a normal startup-request packet and continue
1556+
negotiation over the encrypted channel. In this case any other requests
1557+
for encryption will be refused. This method is not preferred for general
1558+
purpose tools as it cannot negotiate the best connection encryption
1559+
available or handle unencrypted connections. However it is useful for
1560+
environments where both the server and client are controlled together.
1561+
In that case it avoids one round trip of latency and allows the use of
1562+
network tools that depend on standard <acronym>SSL</acronym> connections.
1563+
When using <acronym>SSL</acronym> connections in this style the client is
1564+
required to use the ALPN extension defined
1565+
by <ulink url="https://tools.ietf.org/html/rfc7301">RFC 7301</ulink> to
1566+
protect against protocol confusion attacks.
1567+
The <productname>PostgreSQL</productname> protocol is "TBD-pgsql" as
1568+
registered
1569+
at <ulink url="https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids">IANA
1570+
TLS ALPN Protocol IDs</ulink> registry.
1571+
</para>
1572+
15371573
<para>
15381574
While the protocol itself does not provide a way for the server to
15391575
force <acronym>SSL</acronym> encryption, the administrator can

src/backend/libpq/be-secure.c

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,51 @@ secure_loaded_verify_locations(void)
109109
int
110110
secure_open_server(Port *port)
111111
{
112+
#ifdef USE_SSL
112113
int r = 0;
114+
ssize_t len;
115+
116+
/* push unencrypted buffered data back through SSL setup */
117+
len = pq_buffer_remaining_data();
118+
if (len > 0)
119+
{
120+
char *buf = palloc(len);
121+
122+
pq_startmsgread();
123+
if (pq_getbytes(buf, len) == EOF)
124+
return STATUS_ERROR; /* shouldn't be possible */
125+
pq_endmsgread();
126+
port->raw_buf = buf;
127+
port->raw_buf_remaining = len;
128+
port->raw_buf_consumed = 0;
129+
}
130+
Assert(pq_buffer_remaining_data() == 0);
113131

114-
#ifdef USE_SSL
115132
r = be_tls_open_server(port);
116133

134+
if (port->raw_buf_remaining > 0)
135+
{
136+
/*
137+
* This shouldn't be possible -- it would mean the client sent
138+
* encrypted data before we established a session key...
139+
*/
140+
elog(LOG, "buffered unencrypted data remains after negotiating SSL connection");
141+
return STATUS_ERROR;
142+
}
143+
if (port->raw_buf != NULL)
144+
{
145+
pfree(port->raw_buf);
146+
port->raw_buf = NULL;
147+
}
148+
117149
ereport(DEBUG2,
118150
(errmsg_internal("SSL connection from DN:\"%s\" CN:\"%s\"",
119151
port->peer_dn ? port->peer_dn : "(anonymous)",
120152
port->peer_cn ? port->peer_cn : "(anonymous)")));
121-
#endif
122-
123153
return r;
154+
#else
155+
return 0;
156+
#endif
124157
}
125158

126159
/*
@@ -232,6 +265,19 @@ secure_raw_read(Port *port, void *ptr, size_t len)
232265
{
233266
ssize_t n;
234267

268+
/* Read from the "unread" buffered data first. c.f. libpq-be.h */
269+
if (port->raw_buf_remaining > 0)
270+
{
271+
/* consume up to len bytes from the raw_buf */
272+
if (len > port->raw_buf_remaining)
273+
len = port->raw_buf_remaining;
274+
Assert(port->raw_buf);
275+
memcpy(ptr, port->raw_buf + port->raw_buf_consumed, len);
276+
port->raw_buf_consumed += len;
277+
port->raw_buf_remaining -= len;
278+
return len;
279+
}
280+
235281
/*
236282
* Try to read from the socket without blocking. If it succeeds we're
237283
* done, otherwise we'll wait for the socket using the latch mechanism.

src/backend/libpq/pqcomm.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,15 +1116,17 @@ pq_discardbytes(size_t len)
11161116
}
11171117

11181118
/* --------------------------------
1119-
* pq_buffer_has_data - is any buffered data available to read?
1119+
* pq_buffer_remaining_data - return number of bytes in receive buffer
11201120
*
1121-
* This will *not* attempt to read more data.
1121+
* This will *not* attempt to read more data. And reading up to that number of
1122+
* bytes should not cause reading any more data either.
11221123
* --------------------------------
11231124
*/
1124-
bool
1125-
pq_buffer_has_data(void)
1125+
ssize_t
1126+
pq_buffer_remaining_data(void)
11261127
{
1127-
return (PqRecvPointer < PqRecvLength);
1128+
Assert(PqRecvLength >= PqRecvPointer);
1129+
return (PqRecvLength - PqRecvPointer);
11281130
}
11291131

11301132

0 commit comments

Comments
 (0)