Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit36f40ce

Browse files
committed
libpq: Add sslcertmode option to control client certificates
The sslcertmode option controls whether the server is allowed and/orrequired to request a certificate from the client. There are threemodes:- "allow" is the default and follows the current behavior, where aconfigured client certificate is sent if the server requests one(via one of its default locations or sslcert). With the currentimplementation, will happen whenever TLS is negotiated.- "disable" causes the client to refuse to send a client certificateeven if sslcert is configured or if a client certificate is available inone of its default locations.- "require" causes the client to fail if a client certificate is neversent and the server opens a connection anyway. This doesn't add anyadditional security, since there is no guarantee that the server isvalidating the certificate correctly, but it may helpful to troubleshootmore complicated TLS setups.sslcertmode=require requires SSL_CTX_set_cert_cb(), available sinceOpenSSL 1.0.2. Note that LibreSSL does not include it.Using a connection parameter different than require_auth has come up asthe simplest design because certificate authentication does not relydirectly on any of the AUTH_REQ_* codes, and one may want to require acertificate to be sent in combination of a given authentication method,like SCRAM-SHA-256.TAP tests are added in src/test/ssl/, some of them relying on sslinfo tocheck if a certificate has been set. These are compatible across allthe versions of OpenSSL supported on HEAD (currently down to 1.0.1).Author: Jacob ChampionReviewed-by: Aleksander Alekseev, Peter Eisentraut, David G. Johnston,Michael PaquierDiscussion:https://postgr.es/m/9e5a8ccddb8355ea9fa4b75a1e3a9edc88a70cd3.camel@vmware.com
1 parente522049 commit36f40ce

File tree

12 files changed

+270
-9
lines changed

12 files changed

+270
-9
lines changed

‎configure

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12973,13 +12973,15 @@ else
1297312973
fi
1297412974

1297512975
fi
12976-
# Function introduced in OpenSSL 1.0.2.
12977-
for ac_func in X509_get_signature_nid
12976+
# Functions introduced in OpenSSL 1.0.2. LibreSSL does not have
12977+
# SSL_CTX_set_cert_cb().
12978+
for ac_func in X509_get_signature_nid SSL_CTX_set_cert_cb
1297812979
do :
12979-
ac_fn_c_check_func "$LINENO" "X509_get_signature_nid" "ac_cv_func_X509_get_signature_nid"
12980-
if test "x$ac_cv_func_X509_get_signature_nid" = xyes; then :
12980+
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
12981+
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
12982+
if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
1298112983
cat >>confdefs.h <<_ACEOF
12982-
#defineHAVE_X509_GET_SIGNATURE_NID 1
12984+
#define`$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
1298312985
_ACEOF
1298412986

1298512987
fi

‎configure.ac

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,8 +1373,9 @@ if test "$with_ssl" = openssl ; then
13731373
AC_SEARCH_LIBS(CRYPTO_new_ex_data,[eay32 crypto],[],[AC_MSG_ERROR([library 'eay32' or 'crypto' is required for OpenSSL])])
13741374
AC_SEARCH_LIBS(SSL_new,[ssleay32 ssl],[],[AC_MSG_ERROR([library 'ssleay32' or 'ssl' is required for OpenSSL])])
13751375
fi
1376-
# Function introduced in OpenSSL 1.0.2.
1377-
AC_CHECK_FUNCS([X509_get_signature_nid])
1376+
# Functions introduced in OpenSSL 1.0.2. LibreSSL does not have
1377+
# SSL_CTX_set_cert_cb().
1378+
AC_CHECK_FUNCS([X509_get_signature_nid SSL_CTX_set_cert_cb])
13781379
# Functions introduced in OpenSSL 1.1.0. We used to check for
13791380
# OPENSSL_VERSION_NUMBER, but that didn't work with 1.1.0, because LibreSSL
13801381
# defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it

‎doc/src/sgml/libpq.sgml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,62 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
18101810
</listitem>
18111811
</varlistentry>
18121812

1813+
<varlistentry id="libpq-connect-sslcertmode" xreflabel="sslcertmode">
1814+
<term><literal>sslcertmode</literal></term>
1815+
<listitem>
1816+
<para>
1817+
This option determines whether a client certificate may be sent to the
1818+
server, and whether the server is required to request one. There are
1819+
three modes:
1820+
1821+
<variablelist>
1822+
<varlistentry>
1823+
<term><literal>disable</literal></term>
1824+
<listitem>
1825+
<para>
1826+
A client certificate is never sent, even if one is available
1827+
(default location or provided via
1828+
<xref linkend="libpq-connect-sslcert" />).
1829+
</para>
1830+
</listitem>
1831+
</varlistentry>
1832+
1833+
<varlistentry>
1834+
<term><literal>allow</literal> (default)</term>
1835+
<listitem>
1836+
<para>
1837+
A certificate may be sent, if the server requests one and the
1838+
client has one to send.
1839+
</para>
1840+
</listitem>
1841+
</varlistentry>
1842+
1843+
<varlistentry>
1844+
<term><literal>require</literal></term>
1845+
<listitem>
1846+
<para>
1847+
The server <emphasis>must</emphasis> request a certificate. The
1848+
connection will fail if the client does not send a certificate and
1849+
the server successfully authenticates the client anyway.
1850+
</para>
1851+
</listitem>
1852+
</varlistentry>
1853+
</variablelist>
1854+
</para>
1855+
1856+
<note>
1857+
<para>
1858+
<literal>sslcertmode=require</literal> doesn't add any additional
1859+
security, since there is no guarantee that the server is validating
1860+
the certificate correctly; PostgreSQL servers generally request TLS
1861+
certificates from clients whether they validate them or not. The
1862+
option may be useful when troubleshooting more complicated TLS
1863+
setups.
1864+
</para>
1865+
</note>
1866+
</listitem>
1867+
</varlistentry>
1868+
18131869
<varlistentry id="libpq-connect-sslrootcert" xreflabel="sslrootcert">
18141870
<term><literal>sslrootcert</literal></term>
18151871
<listitem>
@@ -7986,6 +8042,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
79868042
</para>
79878043
</listitem>
79888044

8045+
<listitem>
8046+
<para>
8047+
<indexterm>
8048+
<primary><envar>PGSSLCERTMODE</envar></primary>
8049+
</indexterm>
8050+
<envar>PGSSLCERTMODE</envar> behaves the same as the <xref
8051+
linkend="libpq-connect-sslcertmode"/> connection parameter.
8052+
</para>
8053+
</listitem>
8054+
79898055
<listitem>
79908056
<para>
79918057
<indexterm>

‎meson.build

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,8 +1221,9 @@ if sslopt in ['auto', 'openssl']
12211221
['CRYPTO_new_ex_data', {'required':true}],
12221222
['SSL_new', {'required':true}],
12231223

1224-
#Function introduced in OpenSSL 1.0.2.
1224+
#Functions introduced in OpenSSL 1.0.2.
12251225
['X509_get_signature_nid'],
1226+
['SSL_CTX_set_cert_cb'],# not in LibreSSL
12261227

12271228
# Functions introduced in OpenSSL 1.1.0. We used to check for
12281229
# OPENSSL_VERSION_NUMBER, but that didn't work with 1.1.0, because LibreSSL

‎src/include/pg_config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@
394394
/* Define to 1 if you have spinlocks. */
395395
#undef HAVE_SPINLOCKS
396396

397+
/* Define to 1 if you have the `SSL_CTX_set_cert_cb' function. */
398+
#undef HAVE_SSL_CTX_SET_CERT_CB
399+
397400
/* Define to 1 if stdbool.h conforms to C99. */
398401
#undef HAVE_STDBOOL_H
399402

‎src/interfaces/libpq/fe-auth.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,25 @@ check_expected_areq(AuthRequest areq, PGconn *conn)
798798
StaticAssertDecl((sizeof(conn->allowed_auth_methods)*CHAR_BIT)>AUTH_REQ_MAX,
799799
"AUTH_REQ_MAX overflows the allowed_auth_methods bitmask");
800800

801+
if (conn->sslcertmode[0]=='r'/* require */
802+
&&areq==AUTH_REQ_OK)
803+
{
804+
/*
805+
* Trade off a little bit of complexity to try to get these error
806+
* messages as precise as possible.
807+
*/
808+
if (!conn->ssl_cert_requested)
809+
{
810+
libpq_append_conn_error(conn,"server did not request an SSL certificate");
811+
return false;
812+
}
813+
elseif (!conn->ssl_cert_sent)
814+
{
815+
libpq_append_conn_error(conn,"server accepted connection without a valid SSL certificate");
816+
return false;
817+
}
818+
}
819+
801820
/*
802821
* If the user required a specific auth method, or specified an allowed
803822
* set, then reject all others here, and make sure the server actually

‎src/interfaces/libpq/fe-connect.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,10 @@ static intldapServiceLookup(const char *purl, PQconninfoOption *options,
125125
#defineDefaultTargetSessionAttrs"any"
126126
#ifdefUSE_SSL
127127
#defineDefaultSSLMode "prefer"
128+
#defineDefaultSSLCertMode "allow"
128129
#else
129130
#defineDefaultSSLMode"disable"
131+
#defineDefaultSSLCertMode "disable"
130132
#endif
131133
#ifdefENABLE_GSS
132134
#include"fe-gssapi-common.h"
@@ -283,6 +285,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
283285
"SSL-Client-Key","",64,
284286
offsetof(structpg_conn,sslkey)},
285287

288+
{"sslcertmode","PGSSLCERTMODE",NULL,NULL,
289+
"SSL-Client-Cert-Mode","",8,/* sizeof("disable") == 8 */
290+
offsetof(structpg_conn,sslcertmode)},
291+
286292
{"sslpassword",NULL,NULL,NULL,
287293
"SSL-Client-Key-Password","*",20,
288294
offsetof(structpg_conn,sslpassword)},
@@ -1506,6 +1512,52 @@ connectOptions2(PGconn *conn)
15061512
return false;
15071513
}
15081514

1515+
/*
1516+
* validate sslcertmode option
1517+
*/
1518+
if (conn->sslcertmode)
1519+
{
1520+
if (strcmp(conn->sslcertmode,"disable")!=0&&
1521+
strcmp(conn->sslcertmode,"allow")!=0&&
1522+
strcmp(conn->sslcertmode,"require")!=0)
1523+
{
1524+
conn->status=CONNECTION_BAD;
1525+
libpq_append_conn_error(conn,"invalid %s value: \"%s\"",
1526+
"sslcertmode",conn->sslcertmode);
1527+
return false;
1528+
}
1529+
#ifndefUSE_SSL
1530+
if (strcmp(conn->sslcertmode,"require")==0)
1531+
{
1532+
conn->status=CONNECTION_BAD;
1533+
libpq_append_conn_error(conn,"%s value \"%s\" invalid when SSL support is not compiled in",
1534+
"sslcertmode",conn->sslcertmode);
1535+
return false;
1536+
}
1537+
#endif
1538+
#ifndefHAVE_SSL_CTX_SET_CERT_CB
1539+
1540+
/*
1541+
* Without a certificate callback, the current implementation can't
1542+
* figure out if a certificate was actually requested, so "require" is
1543+
* useless.
1544+
*/
1545+
if (strcmp(conn->sslcertmode,"require")==0)
1546+
{
1547+
conn->status=CONNECTION_BAD;
1548+
libpq_append_conn_error(conn,"%s value \"%s\" is not supported (check OpenSSL version)",
1549+
"sslcertmode",conn->sslcertmode);
1550+
return false;
1551+
}
1552+
#endif
1553+
}
1554+
else
1555+
{
1556+
conn->sslcertmode=strdup(DefaultSSLCertMode);
1557+
if (!conn->sslcertmode)
1558+
gotooom_error;
1559+
}
1560+
15091561
/*
15101562
* validate gssencmode option
15111563
*/
@@ -4238,6 +4290,7 @@ freePGconn(PGconn *conn)
42384290
explicit_bzero(conn->sslpassword,strlen(conn->sslpassword));
42394291
free(conn->sslpassword);
42404292
}
4293+
free(conn->sslcertmode);
42414294
free(conn->sslrootcert);
42424295
free(conn->sslcrl);
42434296
free(conn->sslcrldir);

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,34 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
462462
returnok;
463463
}
464464

465+
#ifdefHAVE_SSL_CTX_SET_CERT_CB
466+
/*
467+
* Certificate selection callback
468+
*
469+
* This callback lets us choose the client certificate we send to the server
470+
* after seeing its CertificateRequest. We only support sending a single
471+
* hard-coded certificate via sslcert, so we don't actually set any certificates
472+
* here; we just use it to record whether or not the server has actually asked
473+
* for one and whether we have one to send.
474+
*/
475+
staticint
476+
cert_cb(SSL*ssl,void*arg)
477+
{
478+
PGconn*conn=arg;
479+
480+
conn->ssl_cert_requested= true;
481+
482+
/* Do we have a certificate loaded to send back? */
483+
if (SSL_get_certificate(ssl))
484+
conn->ssl_cert_sent= true;
485+
486+
/*
487+
* Tell OpenSSL that the callback succeeded; we're not required to
488+
* actually make any changes to the SSL handle.
489+
*/
490+
return1;
491+
}
492+
#endif
465493

466494
/*
467495
* OpenSSL-specific wrapper around
@@ -953,6 +981,11 @@ initialize_SSL(PGconn *conn)
953981
SSL_CTX_set_default_passwd_cb_userdata(SSL_context,conn);
954982
}
955983

984+
#ifdefHAVE_SSL_CTX_SET_CERT_CB
985+
/* Set up a certificate selection callback. */
986+
SSL_CTX_set_cert_cb(SSL_context,cert_cb,conn);
987+
#endif
988+
956989
/* Disable old protocol versions */
957990
SSL_CTX_set_options(SSL_context,SSL_OP_NO_SSLv2 |SSL_OP_NO_SSLv3);
958991

@@ -1107,7 +1140,12 @@ initialize_SSL(PGconn *conn)
11071140
else
11081141
fnbuf[0]='\0';
11091142

1110-
if (fnbuf[0]=='\0')
1143+
if (conn->sslcertmode[0]=='d')/* disable */
1144+
{
1145+
/* don't send a client cert even if we have one */
1146+
have_cert= false;
1147+
}
1148+
elseif (fnbuf[0]=='\0')
11111149
{
11121150
/* no home directory, proceed without a client cert */
11131151
have_cert= false;

‎src/interfaces/libpq/libpq-int.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ struct pg_conn
384384
char*sslkey;/* client key filename */
385385
char*sslcert;/* client certificate filename */
386386
char*sslpassword;/* client key file password */
387+
char*sslcertmode;/* client cert mode (require,allow,disable) */
387388
char*sslrootcert;/* root certificate filename */
388389
char*sslcrl;/* certificate revocation list filename */
389390
char*sslcrldir;/* certificate revocation list directory name */
@@ -527,6 +528,8 @@ struct pg_conn
527528

528529
/* SSL structures */
529530
boolssl_in_use;
531+
boolssl_cert_requested;/* Did the server ask us for a cert? */
532+
boolssl_cert_sent;/* Did we send one in reply? */
530533

531534
#ifdefUSE_SSL
532535
boolallow_ssl_try;/* Allowed to try SSL negotiation */

‎src/test/ssl/t/001_ssltests.pl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ sub switch_server_cert
4242
# This is the pattern to use in pg_hba.conf to match incoming connections.
4343
my$SERVERHOSTCIDR ='127.0.0.1/32';
4444

45+
# Determine whether build supports sslcertmode=require.
46+
my$supports_sslcertmode_require =
47+
check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
48+
4549
# Allocation of base connection string shared among multiple tests.
4650
my$common_connstr;
4751

@@ -191,6 +195,22 @@ sub switch_server_cert
191195
"$common_connstr sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
192196
"cert root file that contains two certificates, order 2");
193197

198+
# sslcertmode=allow and disable should both work without a client certificate.
199+
$node->connect_ok(
200+
"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=disable",
201+
"connect with sslcertmode=disable");
202+
$node->connect_ok(
203+
"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=allow",
204+
"connect with sslcertmode=allow");
205+
206+
# sslcertmode=require, however, should fail.
207+
$node->connect_fails(
208+
"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslcertmode=require",
209+
"connect with sslcertmode=require fails without a client certificate",
210+
expected_stderr=>$supports_sslcertmode_require
211+
?qr/server accepted connection without a valid SSL certificate/
212+
:qr/sslcertmode value "require" is not supported/);
213+
194214
# CRL tests
195215

196216
# Invalid CRL filename is the same as no CRL, succeeds
@@ -538,6 +558,28 @@ sub switch_server_cert
538558
"certificate authorization succeeds with correct client cert in encrypted DER format"
539559
);
540560

561+
# correct client cert with sslcertmode=allow or require
562+
if ($supports_sslcertmode_require)
563+
{
564+
$node->connect_ok(
565+
"$common_connstr user=ssltestuser sslcertmode=require sslcert=ssl/client.crt"
566+
. sslkey('client.key'),
567+
"certificate authorization succeeds with correct client cert and sslcertmode=require"
568+
);
569+
}
570+
$node->connect_ok(
571+
"$common_connstr user=ssltestuser sslcertmode=allow sslcert=ssl/client.crt"
572+
. sslkey('client.key'),
573+
"certificate authorization succeeds with correct client cert and sslcertmode=allow"
574+
);
575+
576+
# client cert is not sent if sslcertmode=disable.
577+
$node->connect_fails(
578+
"$common_connstr user=ssltestuser sslcertmode=disable sslcert=ssl/client.crt"
579+
. sslkey('client.key'),
580+
"certificate authorization fails with correct client cert and sslcertmode=disable",
581+
expected_stderr=>qr/connection requires a valid client certificate/);
582+
541583
# correct client cert in encrypted PEM with wrong password
542584
$node->connect_fails(
543585
"$common_connstr user=ssltestuser sslcert=ssl/client.crt"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp