[PATCH] Log details for client certificate failures
Hello,
(I'm cleaning up some old git branches and found this. It was helpful
when I was trying to debug failures between an NSS client and an
OpenSSL server, and it seems general enough to help for more
complicated OpenSSL-only setups as well.)
Currently, debugging client cert verification failures is mostly
limited to looking at the TLS alert code on the client side. For simple
deployments, usually it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can
be an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub, so I've filled it out with error handling and added a COMMERROR
log so that a DBA can debug client failures more easily.
It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: failed certificate's subject: /CN=Test CA for PostgreSQL SSL regression test client certs
LOG: could not accept SSL connection: certificate verify failed
It might be even nicer to make this available to the client, but I
think the server log is an appropriate place for this information -- an
admin might not want to advertise exactly why a client certificate has
failed verification (other than what's already available via the TLS
alert, that is), and I think more complicated failures (with
intermediate CAs, etc.) are going to need administrator intervention
anyway. So having to check the logs doesn't seem like a big hurdle.
One question/concern -- the Subject that's printed to the logs could be
pretty big (OpenSSL limits the incoming certificate chain to 100K, by
default), which introduces an avenue for intentional log spamming. Is
there an existing convention for limiting the length of log output used
for debugging? Maybe I should just hardcode a smaller limit and
truncate anything past that? Or we could just log the Common Name,
which should be limited to 64 bytes...
I'll add this to the July commitfest.
Thanks,
--Jacob
Attachments:
0001-Log-details-for-client-certificate-failures.patchtext/x-patch; name=0001-Log-details-for-client-certificate-failures.patchDownload
From e38e9afd8adfa5c39ea1f43f6c4d5b9238ec353c Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 29 Mar 2021 10:07:31 -0700
Subject: [PATCH] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly limited
to looking at the TLS alert code on the client side. For simple
deployments, sometimes it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can be
an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Fill it out with error handling and add a COMMERROR log so that a
DBA can debug client failures more easily. It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: failed certificate's subject: /CN=Test CA for PostgreSQL SSL regression test client certs
LOG: could not accept SSL connection: certificate verify failed
---
src/backend/libpq/be-secure-openssl.c | 33 +++++++++++++++++++++++++--
src/test/ssl/t/001_ssltests.pl | 31 +++++++++++++++++++++++--
2 files changed, 60 insertions(+), 4 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..1683f9cc9e 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1080,8 +1080,7 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * verification.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1089,36 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ int depth;
+ int errcode;
+ const char *errstring;
+ X509 *cert;
+ char *certname = NULL;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ certname = X509_NAME_to_cstring(X509_get_subject_name(cert));
+
+ ereport(COMMERROR,
+ (errmsg("client certificate verification failed at depth %d: %s",
+ depth, errstring),
+ /* only print detail if we have a certificate to print */
+ certname && errdetail("failed certificate's subject: %s", certname)));
+
+ if (certname)
+ pfree(certname);
+
return ok;
}
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 58d2bc336f..ecc7bd2bce 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -666,6 +666,10 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate's subject: \/CN=ssltestuser/,
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -708,7 +712,26 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate's subject: \/CN=ssltestuser/,
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
+ qr/failed certificate's subject: \/CN=Test CA for PostgreSQL SSL regression test client certs/,
+ ]);
# test server-side CRL directory
switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir');
@@ -717,6 +740,10 @@ switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-c
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate's subject: \/CN=ssltestuser/,
+ ]);
done_testing();
--
2.25.1
On 03.05.22 19:04, Jacob Champion wrote:
One question/concern -- the Subject that's printed to the logs could be
pretty big (OpenSSL limits the incoming certificate chain to 100K, by
default), which introduces an avenue for intentional log spamming. Is
there an existing convention for limiting the length of log output used
for debugging? Maybe I should just hardcode a smaller limit and
truncate anything past that? Or we could just log the Common Name,
which should be limited to 64 bytes...
The information in pg_stat_ssl is limited to NAMEDATALEN (see struct
PgBackendSSLStatus).
It might make sense to align what your patch prints to identify
certificates with what is shown in that view.
On Tue, 2022-05-03 at 21:06 +0200, Peter Eisentraut wrote:
The information in pg_stat_ssl is limited to NAMEDATALEN (see struct
PgBackendSSLStatus).It might make sense to align what your patch prints to identify
certificates with what is shown in that view.
Sure, a max length should be easy enough to do. Is there a reason to
limit to NAMEDATALEN specifically? I was under the impression that we
would rather not have had that limitation in the stats framework, if we
could have avoided it. (In particular I think NAMEDATALEN will cut off
the longest possible Common Name by just five bytes.)
Thanks,
--Jacob
On 04.05.22 01:05, Jacob Champion wrote:
On Tue, 2022-05-03 at 21:06 +0200, Peter Eisentraut wrote:
The information in pg_stat_ssl is limited to NAMEDATALEN (see struct
PgBackendSSLStatus).It might make sense to align what your patch prints to identify
certificates with what is shown in that view.Sure, a max length should be easy enough to do. Is there a reason to
limit to NAMEDATALEN specifically? I was under the impression that we
would rather not have had that limitation in the stats framework, if we
could have avoided it. (In particular I think NAMEDATALEN will cut off
the longest possible Common Name by just five bytes.)
Just saying that cutting it off appears to be acceptable. A bit more
than 63 bytes should be okay for the log.
In terms of aligning what is printed, I meant that pg_stat_ssl uses the
issuer plus serial number to identify the certificate unambiguously.
On Wed, 2022-05-04 at 15:53 +0200, Peter Eisentraut wrote:
Just saying that cutting it off appears to be acceptable. A bit more
than 63 bytes should be okay for the log.
Gotcha.
In terms of aligning what is printed, I meant that pg_stat_ssl uses the
issuer plus serial number to identify the certificate unambiguously.
Oh, that's a great idea. I'll do that too.
Thanks!
--Jacob
On Thu, 2022-05-05 at 15:12 +0000, Jacob Champion wrote:
On Wed, 2022-05-04 at 15:53 +0200, Peter Eisentraut wrote:
In terms of aligning what is printed, I meant that pg_stat_ssl uses the
issuer plus serial number to identify the certificate unambiguously.Oh, that's a great idea. I'll do that too.
v2 limits the maximum subject length and adds the serial number to the
logs.
Thanks!
--Jacob
Attachments:
since-v1.diff.txttext/plain; name=since-v1.diff.txtDownload
commit eb5ddc1d8909fe322ddeb1ec4bf9118bb9544667
Author: Jacob Champion <pchampion@vmware.com>
Date: Wed May 11 10:33:33 2022 -0700
squash! Log details for client certificate failures
- Add a maximum Subject length to prevent malicious client certs from
spamming the logs.
- Add the certificate serial number to the output, for disambiguation if
the Subject gets truncated unhelpfully.
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 1683f9cc9e..3ccc23ba62 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1094,6 +1094,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
const char *errstring;
X509 *cert;
char *certname = NULL;
+ char *truncated = NULL;
+ char *serialno = NULL;
if (ok)
{
@@ -1108,14 +1110,56 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
cert = X509_STORE_CTX_get_current_cert(ctx);
if (cert)
+ {
+ size_t namelen;
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject for logging, but don't let maliciously huge certs
+ * flood the logs.
+ *
+ * Common Names are 64 chars max, so for a common case where the CN is
+ * the last field, we can still print the longest possible CN with a
+ * 7-character prefix (".../CN=[64 chars]"), for a reasonable limit of
+ * 71 characters.
+ */
+#define MAXLEN 71
certname = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ namelen = strlen(certname);
+
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the Subject, not the beginning, since the most
+ * specific field is likely to give users the most information.
+ */
+ truncated = certname + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ }
+#undef MAXLEN
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ BN_free(b);
+ }
ereport(COMMERROR,
(errmsg("client certificate verification failed at depth %d: %s",
depth, errstring),
/* only print detail if we have a certificate to print */
- certname && errdetail("failed certificate's subject: %s", certname)));
+ certname && errdetail("failed certificate had subject '%s', serial number %s",
+ truncated ? truncated : certname,
+ serialno ? serialno : _("unknown"))));
+ if (serialno)
+ OPENSSL_free(serialno);
if (certname)
pfree(certname);
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000..a1db55b5c3
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.crt
@@ -0,0 +1,69 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 2315418733629425152 (0x2022051214444600)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN = Test CA for PostgreSQL SSL regression test client certs
+ Validity
+ Not Before: May 12 21:44:47 2022 GMT
+ Not After : Sep 27 21:44:47 2049 GMT
+ Subject: O = PostgreSQL Global Development Group, OU = Some Organizational Unit, CN = ssl-123456789012345678901234567890123456789012345678901234567890
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public-Key: (2048 bit)
+ Modulus:
+ 00:d8:31:99:e1:e2:55:1b:81:4f:09:46:43:5a:0c:
+ d8:cd:5c:91:00:88:fe:8e:21:61:c3:bd:05:87:71:
+ 0d:81:e2:03:b8:f2:d5:65:5e:9f:28:fc:06:d5:22:
+ e4:08:fd:54:38:69:7e:2b:9e:c0:36:ab:19:0e:5a:
+ 4e:08:03:d6:93:23:93:0d:0c:a2:b0:4a:06:bf:7e:
+ d6:ed:71:18:7f:fc:43:79:64:81:4f:8e:41:7a:cf:
+ 82:5a:38:22:b5:5c:a9:aa:19:15:0d:27:b8:c6:95:
+ 6e:27:97:d1:d0:f2:4d:fb:13:69:54:18:62:da:80:
+ b7:38:48:fd:41:92:20:e8:64:db:e9:41:f5:f3:fa:
+ f2:3c:3d:70:b1:f4:41:8b:d1:fa:a4:34:20:f1:b5:
+ dc:8e:73:99:26:0f:32:3d:15:63:03:27:f5:31:7d:
+ ff:6b:01:39:24:55:81:35:40:72:b6:20:39:02:e4:
+ de:cf:df:19:ee:ba:b6:65:41:21:66:1c:ab:ae:72:
+ 82:a0:f5:fb:07:ae:f8:f6:b1:8c:f9:f0:b1:e7:e4:
+ 76:77:fa:25:d6:11:1b:51:75:0a:8e:e0:e7:6a:68:
+ 3c:46:22:78:01:df:40:e2:7a:f6:c1:b4:2d:8c:74:
+ 94:13:83:0b:af:6a:bd:22:78:59:d0:90:d3:32:b0:
+ db:cf
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha256WithRSAEncryption
+ ae:be:6b:d5:4c:60:53:33:f2:92:6b:b9:65:d2:fa:cb:c4:c0:
+ 65:b4:90:d7:20:58:d2:78:6c:47:04:64:0d:de:ba:88:f2:bd:
+ 53:a4:fa:2b:6f:a9:2f:c5:5c:ea:2a:28:bf:d5:8e:b4:74:3a:
+ a2:13:f7:81:26:af:77:8d:1c:1d:19:85:6a:ec:08:b1:9e:16:
+ 93:49:a3:7e:da:4e:b8:f1:69:d6:1c:a8:f1:df:d0:62:b6:c4:
+ 9b:7a:8d:7b:9a:a5:9f:94:bd:87:c8:a9:a1:8b:c5:b5:fe:73:
+ 94:ad:8a:1c:91:73:f5:e8:59:31:9d:73:18:01:11:34:25:4c:
+ f1:45:10:2b:25:2e:64:1e:46:ca:b6:65:6e:c9:5f:e8:ff:fd:
+ 06:c7:39:e1:85:16:c4:fd:a9:af:9a:ff:44:9e:83:79:77:c8:
+ cc:01:5f:5d:3f:00:15:59:42:94:04:40:f7:8f:60:a5:7d:0b:
+ 56:b5:eb:c8:33:7a:d0:25:09:f3:c4:09:99:80:90:dc:0b:48:
+ 83:36:18:7a:a7:1d:60:74:dd:f1:e4:b6:f4:fe:70:84:37:54:
+ e4:db:d1:38:ff:8b:8c:44:de:45:e2:45:e0:17:f2:73:68:0c:
+ ef:3c:59:0f:60:43:49:fe:5e:c6:f9:cd:aa:2c:b8:db:f9:51:
+ d5:be:2c:cf
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkICCCAiBRIUREYAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNTEyMjE0NDQ3WhcNNDkwOTI3MjE0NDQ3WjCBnDEsMCoGA1UE
+CgwjUG9zdGdyZVNRTCBHbG9iYWwgRGV2ZWxvcG1lbnQgR3JvdXAxITAfBgNVBAsM
+GFNvbWUgT3JnYW5pemF0aW9uYWwgVW5pdDFJMEcGA1UEAwxAc3NsLTEyMzQ1Njc4
+OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
+Nzg5MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgxmeHiVRuBTwlG
+Q1oM2M1ckQCI/o4hYcO9BYdxDYHiA7jy1WVenyj8BtUi5Aj9VDhpfiuewDarGQ5a
+TggD1pMjkw0MorBKBr9+1u1xGH/8Q3lkgU+OQXrPglo4IrVcqaoZFQ0nuMaVbieX
+0dDyTfsTaVQYYtqAtzhI/UGSIOhk2+lB9fP68jw9cLH0QYvR+qQ0IPG13I5zmSYP
+Mj0VYwMn9TF9/2sBOSRVgTVAcrYgOQLk3s/fGe66tmVBIWYcq65ygqD1+weu+Pax
+jPnwsefkdnf6JdYRG1F1Co7g52poPEYieAHfQOJ69sG0LYx0lBODC69qvSJ4WdCQ
+0zKw288CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEArr5r1UxgUzPykmu5ZdL6y8TA
+ZbSQ1yBY0nhsRwRkDd66iPK9U6T6K2+pL8Vc6ioov9WOtHQ6ohP3gSavd40cHRmF
+auwIsZ4Wk0mjftpOuPFp1hyo8d/QYrbEm3qNe5qln5S9h8ipoYvFtf5zlK2KHJFz
+9ehZMZ1zGAERNCVM8UUQKyUuZB5GyrZlbslf6P/9Bsc54YUWxP2pr5r/RJ6DeXfI
+zAFfXT8AFVlClARA949gpX0LVrXryDN60CUJ88QJmYCQ3AtIgzYYeqcdYHTd8eS2
+9P5whDdU5NvROP+LjETeReJF4Bfyc2gM7zxZD2BDSf5exvnNqiy42/lR1b4szw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index ecc7bd2bce..4c7a8bd7a2 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -668,7 +668,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate's subject: \/CN=ssltestuser/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -715,7 +715,16 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
- qr/failed certificate's subject: \/CN=ssltestuser/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576/,
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152/,
]);
# Use an invalid cafile here so that the next test won't be able to verify the
@@ -730,7 +739,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
- qr/failed certificate's subject: \/CN=Test CA for PostgreSQL SSL regression test client certs/,
+ qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577/,
]);
# test server-side CRL directory
@@ -743,7 +752,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate's subject: \/CN=ssltestuser/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 1546b5081b..cdfd14fcdf 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
v2-0001-Log-details-for-client-certificate-failures.patchtext/x-patch; name=v2-0001-Log-details-for-client-certificate-failures.patchDownload
From 71148b4a38d459ca86d8637577c60b8cae568242 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 29 Mar 2021 10:07:31 -0700
Subject: [PATCH v2] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly limited
to looking at the TLS alert code on the client side. For simple
deployments, sometimes it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can be
an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Fill it out with error handling and add a COMMERROR log so that a
DBA can debug client failures more easily. It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: failed certificate had subject '/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577
LOG: could not accept SSL connection: certificate verify failed
The printed Subject's length is limited to prevent malicious client
certs from spamming the logs. In case the truncation makes things
ambiguous, the certificate's serial number is also logged.
---
src/backend/libpq/be-secure-openssl.c | 77 ++++++++++++++++++++++++++-
src/test/ssl/conf/client-long.config | 14 +++++
src/test/ssl/ssl/client-long.crt | 20 +++++++
src/test/ssl/ssl/client-long.key | 27 ++++++++++
src/test/ssl/sslfiles.mk | 2 +-
src/test/ssl/t/001_ssltests.pl | 40 +++++++++++++-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 2 +-
7 files changed, 176 insertions(+), 6 deletions(-)
create mode 100644 src/test/ssl/conf/client-long.config
create mode 100644 src/test/ssl/ssl/client-long.crt
create mode 100644 src/test/ssl/ssl/client-long.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..3ccc23ba62 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1080,8 +1080,7 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * verification.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1089,80 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ int depth;
+ int errcode;
+ const char *errstring;
+ X509 *cert;
+ char *certname = NULL;
+ char *truncated = NULL;
+ char *serialno = NULL;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ {
+ size_t namelen;
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject for logging, but don't let maliciously huge certs
+ * flood the logs.
+ *
+ * Common Names are 64 chars max, so for a common case where the CN is
+ * the last field, we can still print the longest possible CN with a
+ * 7-character prefix (".../CN=[64 chars]"), for a reasonable limit of
+ * 71 characters.
+ */
+#define MAXLEN 71
+ certname = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ namelen = strlen(certname);
+
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the Subject, not the beginning, since the most
+ * specific field is likely to give users the most information.
+ */
+ truncated = certname + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ }
+#undef MAXLEN
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ BN_free(b);
+ }
+
+ ereport(COMMERROR,
+ (errmsg("client certificate verification failed at depth %d: %s",
+ depth, errstring),
+ /* only print detail if we have a certificate to print */
+ certname && errdetail("failed certificate had subject '%s', serial number %s",
+ truncated ? truncated : certname,
+ serialno ? serialno : _("unknown"))));
+
+ if (serialno)
+ OPENSSL_free(serialno);
+ if (certname)
+ pfree(certname);
+
return ok;
}
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000..a1db55b5c3
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkICCCAiBRIUREYAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNTEyMjE0NDQ3WhcNNDkwOTI3MjE0NDQ3WjCBnDEsMCoGA1UE
+CgwjUG9zdGdyZVNRTCBHbG9iYWwgRGV2ZWxvcG1lbnQgR3JvdXAxITAfBgNVBAsM
+GFNvbWUgT3JnYW5pemF0aW9uYWwgVW5pdDFJMEcGA1UEAwxAc3NsLTEyMzQ1Njc4
+OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
+Nzg5MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgxmeHiVRuBTwlG
+Q1oM2M1ckQCI/o4hYcO9BYdxDYHiA7jy1WVenyj8BtUi5Aj9VDhpfiuewDarGQ5a
+TggD1pMjkw0MorBKBr9+1u1xGH/8Q3lkgU+OQXrPglo4IrVcqaoZFQ0nuMaVbieX
+0dDyTfsTaVQYYtqAtzhI/UGSIOhk2+lB9fP68jw9cLH0QYvR+qQ0IPG13I5zmSYP
+Mj0VYwMn9TF9/2sBOSRVgTVAcrYgOQLk3s/fGe66tmVBIWYcq65ygqD1+weu+Pax
+jPnwsefkdnf6JdYRG1F1Co7g52poPEYieAHfQOJ69sG0LYx0lBODC69qvSJ4WdCQ
+0zKw288CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEArr5r1UxgUzPykmu5ZdL6y8TA
+ZbSQ1yBY0nhsRwRkDd66iPK9U6T6K2+pL8Vc6ioov9WOtHQ6ohP3gSavd40cHRmF
+auwIsZ4Wk0mjftpOuPFp1hyo8d/QYrbEm3qNe5qln5S9h8ipoYvFtf5zlK2KHJFz
+9ehZMZ1zGAERNCVM8UUQKyUuZB5GyrZlbslf6P/9Bsc54YUWxP2pr5r/RJ6DeXfI
+zAFfXT8AFVlClARA949gpX0LVrXryDN60CUJ88QJmYCQ3AtIgzYYeqcdYHTd8eS2
+9P5whDdU5NvROP+LjETeReJF4Bfyc2gM7zxZD2BDSf5exvnNqiy42/lR1b4szw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 58d2bc336f..4c7a8bd7a2 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -666,6 +666,10 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -708,7 +712,35 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576/,
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152/,
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577/,
+ ]);
# test server-side CRL directory
switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-crldir');
@@ -717,6 +749,10 @@ switch_server_cert($node, certfile => 'server-cn-only', crldir => 'root+client-c
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt " . sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
+ ]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 1546b5081b..cdfd14fcdf 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
On 13.05.22 00:36, Jacob Champion wrote:
On Thu, 2022-05-05 at 15:12 +0000, Jacob Champion wrote:
On Wed, 2022-05-04 at 15:53 +0200, Peter Eisentraut wrote:
In terms of aligning what is printed, I meant that pg_stat_ssl uses the
issuer plus serial number to identify the certificate unambiguously.Oh, that's a great idea. I'll do that too.
v2 limits the maximum subject length and adds the serial number to the
logs.
I wrote that pg_stat_ssl uses the *issuer* plus serial number to
identify a certificate. What your patch shows is the subject and the
serial number, which isn't the same thing. Let's get that sorted out
one way or the other.
Another point, your patch produces
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: ...
DETAIL: failed certificate had subject ...
LOG: could not accept SSL connection: certificate verify failed
I guess what we really would like is
LOG: connection received: host=localhost port=44120
LOG: could not accept SSL connection: certificate verify failed
DETAIL: client certificate verification failed at depth 1: ...
failed certificate had subject ...
But I suppose that would be very cumbersome to produce with the callback
structure provided by OpenSSL?
I'm not saying the proposed way is unacceptable, but maybe it's worth
being explicit about this tradeoff.
On 30 Jun 2022, at 10:43, Peter Eisentraut <peter.eisentraut@enterprisedb.com> wrote:
I wrote that pg_stat_ssl uses the *issuer* plus serial number to identify a certificate. What your patch shows is the subject and the serial number, which isn't the same thing. Let's get that sorted out one way or the other.
Quick observation on this one, the string format of an issuer and serial number is defined as a “Certificate Exact Assertion” in RFC 4523.
I added this to httpd a while back:
SSL_CLIENT_CERT_RFC4523_CEA
It would be good to interoperate.
Regards,
Graham
—
On Thu, Jun 30, 2022 at 2:43 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
On 13.05.22 00:36, Jacob Champion wrote:
v2 limits the maximum subject length and adds the serial number to the
logs.I wrote that pg_stat_ssl uses the *issuer* plus serial number to
identify a certificate. What your patch shows is the subject and the
serial number, which isn't the same thing. Let's get that sorted out
one way or the other.
Sorry for the misunderstanding! v3 adds the Issuer to the logs as well.
I wanted to clarify that this "issuer" has not actually been verified,
but all I could come up with was "purported issuer" which doesn't read
well to me. "Claimed issuer"? "Alleged issuer"? Thoughts?
Another point, your patch produces
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: ...
DETAIL: failed certificate had subject ...
LOG: could not accept SSL connection: certificate verify failedI guess what we really would like is
LOG: connection received: host=localhost port=44120
LOG: could not accept SSL connection: certificate verify failed
DETAIL: client certificate verification failed at depth 1: ...
failed certificate had subject ...But I suppose that would be very cumbersome to produce with the callback
structure provided by OpenSSL?
I was about to say "yes, very cumbersome", but I actually think we
might be able to do that without bubbling the error up through
multiple callback layers, using SSL_set_ex_data() and friends. I'll
take a closer look.
Thanks!
--Jacob
Attachments:
since-v2.diff.txttext/plain; charset=US-ASCII; name=since-v2.diff.txtDownload
commit dfe12fa0c3b93cf98169da2095e53edd79c936e6
Author: Jacob Champion <jchampion@timescale.com>
Date: Fri Jul 1 13:39:34 2022 -0700
squash! Log details for client certificate failures
Add the Issuer to the log.
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3ccc23ba62..80b361b105 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1076,6 +1076,40 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
return 0;
}
+/*
+ * Examines the provided certificate name, and if it's too long to log, modifies
+ * and truncates it. The return value is NULL if no truncation was needed; it
+ * otherwise points into the middle of the input string, and should not be
+ * freed.
+ */
+static char *
+truncate_cert_name(char *name)
+{
+ size_t namelen = strlen(name);
+ char *truncated;
+
+ /*
+ * Common Names are 64 chars max, so for a common case where the CN is the
+ * last field, we can still print the longest possible CN with a 7-character
+ * prefix (".../CN=[64 chars]"), for a reasonable limit of 71 characters.
+ */
+#define MAXLEN 71
+
+ if (namelen <= MAXLEN)
+ return NULL;
+
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+
+#undef MAXLEN
+
+ return truncated;
+}
+
/*
* Certificate verification callback
*
@@ -1093,8 +1127,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
int errcode;
const char *errstring;
X509 *cert;
- char *certname = NULL;
- char *truncated = NULL;
+ char *subject = NULL, *issuer = NULL;
+ char *sub_truncated = NULL, *iss_truncated = NULL;
char *serialno = NULL;
if (ok)
@@ -1111,33 +1145,18 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
cert = X509_STORE_CTX_get_current_cert(ctx);
if (cert)
{
- size_t namelen;
ASN1_INTEGER *sn;
BIGNUM *b;
/*
- * Get the Subject for logging, but don't let maliciously huge certs
- * flood the logs.
- *
- * Common Names are 64 chars max, so for a common case where the CN is
- * the last field, we can still print the longest possible CN with a
- * 7-character prefix (".../CN=[64 chars]"), for a reasonable limit of
- * 71 characters.
+ * Get the Subject and Issuer for logging, but don't let maliciously
+ * huge certs flood the logs.
*/
-#define MAXLEN 71
- certname = X509_NAME_to_cstring(X509_get_subject_name(cert));
- namelen = strlen(certname);
+ subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ sub_truncated = truncate_cert_name(subject);
- if (namelen > MAXLEN)
- {
- /*
- * Keep the end of the Subject, not the beginning, since the most
- * specific field is likely to give users the most information.
- */
- truncated = certname + namelen - MAXLEN;
- truncated[0] = truncated[1] = truncated[2] = '.';
- }
-#undef MAXLEN
+ issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
+ iss_truncated = truncate_cert_name(issuer);
/*
* Pull the serial number, too, in case a Subject is still ambiguous.
@@ -1154,14 +1173,19 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
(errmsg("client certificate verification failed at depth %d: %s",
depth, errstring),
/* only print detail if we have a certificate to print */
- certname && errdetail("failed certificate had subject '%s', serial number %s",
- truncated ? truncated : certname,
- serialno ? serialno : _("unknown"))));
+ subject && errdetail("failed certificate had subject '%s', "
+ "serial number %s, "
+ "purported issuer '%s'",
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer)));
if (serialno)
OPENSSL_free(serialno);
- if (certname)
- pfree(certname);
+ if (issuer)
+ pfree(issuer);
+ if (subject)
+ pfree(subject);
return ok;
}
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 7378cb5dc6..a9b737ed09 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -685,7 +685,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -737,7 +737,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
$node->connect_fails(
@@ -746,7 +746,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
- qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152/,
+ qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
# Use an invalid cafile here so that the next test won't be able to verify the
@@ -761,7 +761,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
- qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577/,
+ qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
]);
# test server-side CRL directory
@@ -778,7 +778,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
done_testing();
v3-0001-Log-details-for-client-certificate-failures.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Log-details-for-client-certificate-failures.patchDownload
From 92d601172133347dd319a6ff8d1323aaafb3cf52 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 29 Mar 2021 10:07:31 -0700
Subject: [PATCH v3] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly limited
to looking at the TLS alert code on the client side. For simple
deployments, sometimes it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can be
an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Fill it out with error handling and add a COMMERROR log so that a
DBA can debug client failures more easily. It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: failed certificate had subject '/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '/CN=Root CA'
LOG: could not accept SSL connection: certificate verify failed
The length of the Subject and Issuer strings is limited to prevent
malicious client certs from spamming the logs. In case the truncation
makes things ambiguous, the certificate's serial number is also logged.
---
src/backend/libpq/be-secure-openssl.c | 101 +++++++++++++++++++++++++-
src/test/ssl/conf/client-long.config | 14 ++++
src/test/ssl/ssl/client-long.crt | Bin 0 -> 1224 bytes
src/test/ssl/ssl/client-long.key | 27 +++++++
src/test/ssl/sslfiles.mk | 2 +-
src/test/ssl/t/001_ssltests.pl | 40 +++++++++-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 2 +-
7 files changed, 180 insertions(+), 6 deletions(-)
create mode 100644 src/test/ssl/conf/client-long.config
create mode 100644 src/test/ssl/ssl/client-long.crt
create mode 100644 src/test/ssl/ssl/client-long.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..80b361b105 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1076,12 +1076,45 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
return 0;
}
+/*
+ * Examines the provided certificate name, and if it's too long to log, modifies
+ * and truncates it. The return value is NULL if no truncation was needed; it
+ * otherwise points into the middle of the input string, and should not be
+ * freed.
+ */
+static char *
+truncate_cert_name(char *name)
+{
+ size_t namelen = strlen(name);
+ char *truncated;
+
+ /*
+ * Common Names are 64 chars max, so for a common case where the CN is the
+ * last field, we can still print the longest possible CN with a 7-character
+ * prefix (".../CN=[64 chars]"), for a reasonable limit of 71 characters.
+ */
+#define MAXLEN 71
+
+ if (namelen <= MAXLEN)
+ return NULL;
+
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+
+#undef MAXLEN
+
+ return truncated;
+}
+
/*
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * verification.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1123,70 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ int depth;
+ int errcode;
+ const char *errstring;
+ X509 *cert;
+ char *subject = NULL, *issuer = NULL;
+ char *sub_truncated = NULL, *iss_truncated = NULL;
+ char *serialno = NULL;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ {
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject and Issuer for logging, but don't let maliciously
+ * huge certs flood the logs.
+ */
+ subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ sub_truncated = truncate_cert_name(subject);
+
+ issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
+ iss_truncated = truncate_cert_name(issuer);
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ BN_free(b);
+ }
+
+ ereport(COMMERROR,
+ (errmsg("client certificate verification failed at depth %d: %s",
+ depth, errstring),
+ /* only print detail if we have a certificate to print */
+ subject && errdetail("failed certificate had subject '%s', "
+ "serial number %s, "
+ "purported issuer '%s'",
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer)));
+
+ if (serialno)
+ OPENSSL_free(serialno);
+ if (issuer)
+ pfree(issuer);
+ if (subject)
+ pfree(subject);
+
return ok;
}
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000000000000000000000000000000000..a1db55b5c3e7c6923f978810c38d5f5d50656608
GIT binary patch
literal 1224
zcmZ`($+D|B4Bhh;y-(c=L*T*UEo{t~FqkmJ>=FhuB_tsU!`C<WzN+p;S8Jh?k55@T
zl1pD-t3l1M+`m*U2rb=GL8yI$UyNf}C}~s`w3fR18U|JrXaqQbHPqw7I9eA0VGXKd
z1RTwpFajG$-53JLak-#lG67S}YBaqnv!%mNCIq+a8i!Fd(ui{%%e1>xu)9of?-zbY
zb0c1I6*d>6>n=A;8N$bNZaSk`Wv<g$qZ?|o(`c-Vu#l{ni`vl(E#V)mQQ-|~gQNBa
z;3ADt>DWYuvgAfSr?DG^Dl{*KQj=t)ff4g*GJP_G`^gpv_9s7q$qGUFN5<eVhVFhY
zNkl`zzL7+{7gwD<C(8T#fp|UVNVgp=`^g5z39M3v!*`Vn=G-RRF<uBJr*T*}B!ah=
z>x9<gqKJ1FDmv(2;{WlOKg6~FM{7iw*!w=Us!!&Ewet`IM5*QjwBF-hU1JDbI$aC1
zj)ON0$If6d*E_t!Wi3{%ZNIBX<|&Zm6C!2}el;_f#KmoHWcZ3|5j0ASs(V?#ixHil
z1{TjuNGam(PScL8(?8)JK9vpk<|f13%2?=H8f`p$diu2Mch)n`9_K#QF?)ME=lD_=
zCx*?@9F{|Pjxiyx2Y4-&wT;&Q<5exc>7Y{jlH80+b}(5?KEdXJS<5##!4jA2k@$?$
zurV2(n-c2EmLH&Z3}PC_AbS$(;X^A7+tMf`>2-~vm5}Tqi8Fm5ZZj6|jenk)#=nh2
zQMUQ5l<b@Y2B&js|BH(Jz4tXz7r-pBXG_Xpum>`+9rTbYDUZ+5-X*z;8Ftx^yez9=
z)Bp&93u*hP+S*V?76Lrz0C?*JH1K?iPcE8hwD@y-eT!*+kY+gqOnNzDZia-tRYNca
zEy^XS_V-F8h0>ni+z)b`Np;_Ul*D_PSn4ZZpvUtk-?5n)Ty^IB9<3qWB`tex%Gcd{
z`}&=$=3dUXm^?4qRd~3C_~>rpQK{tm-jk31D#fMS^Lx|Q7e*<n)JeI;09xRxu_H$j
z-p|oHg`zP()9x~KrQ~lFIAr3EM9Jjy_f^d{uq9NSN{d;5UY<f3=y9iZAOOlvq4yJe
zh@YqDj9iITBU_fSeI+Uuz{{cwA;lxhi5cc{apW1r7sm?ak?4K`&)*yxEi8h<)_1U+
qGoCuXmMKL%M8{Grrq9iN)boztb^&+U4a@lZH}kWXG#CAMSN#oVvxiUs
literal 0
HcmV?d00001
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 707f4005af..a9b737ed09 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -683,6 +683,10 @@ $node->connect_fails(
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -730,7 +734,35 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
+ ]);
# test server-side CRL directory
switch_server_cert(
@@ -743,6 +775,10 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index aed6005b43..a43e64c04f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
On Thu, Jun 30, 2022 at 2:54 AM Graham Leggett <minfrin@sharp.fm> wrote:
I added this to httpd a while back:
SSL_CLIENT_CERT_RFC4523_CEA
It would be good to interoperate.
What kind of interoperation did you have in mind? Are there existing
tools that want to scrape this information for observability?
I think the CEA syntax might not be a good fit for this particular
patch: first, we haven't actually verified the certificate, so no one
should be using it to assert certificate equality (and I'm truncating
the Issuer anyway, to avoid letting someone flood the logs). Second,
this is designed to be human-readable rather than machine-readable.
Thanks,
--Jacob
On Fri, Jul 1, 2022 at 1:51 PM Jacob Champion <jchampion@timescale.com> wrote:
Sorry for the misunderstanding! v3 adds the Issuer to the logs as well.
Resending v3; I messed up the certificate diff with my gitconfig.
--Jacob
Attachments:
v3-0001-Log-details-for-client-certificate-failures.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Log-details-for-client-certificate-failures.patchDownload
From 8d03e043cd86ec81dfb49a138e871c5ac110dc06 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 29 Mar 2021 10:07:31 -0700
Subject: [PATCH v3] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly limited
to looking at the TLS alert code on the client side. For simple
deployments, sometimes it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can be
an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Fill it out with error handling and add a COMMERROR log so that a
DBA can debug client failures more easily. It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: failed certificate had subject '/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '/CN=Root CA'
LOG: could not accept SSL connection: certificate verify failed
The length of the Subject and Issuer strings is limited to prevent
malicious client certs from spamming the logs. In case the truncation
makes things ambiguous, the certificate's serial number is also logged.
---
src/backend/libpq/be-secure-openssl.c | 101 +++++++++++++++++++++++++-
src/test/ssl/conf/client-long.config | 14 ++++
src/test/ssl/ssl/client-long.crt | 20 +++++
src/test/ssl/ssl/client-long.key | 27 +++++++
src/test/ssl/sslfiles.mk | 2 +-
src/test/ssl/t/001_ssltests.pl | 40 +++++++++-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 2 +-
7 files changed, 200 insertions(+), 6 deletions(-)
create mode 100644 src/test/ssl/conf/client-long.config
create mode 100644 src/test/ssl/ssl/client-long.crt
create mode 100644 src/test/ssl/ssl/client-long.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..80b361b105 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1076,12 +1076,45 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
return 0;
}
+/*
+ * Examines the provided certificate name, and if it's too long to log, modifies
+ * and truncates it. The return value is NULL if no truncation was needed; it
+ * otherwise points into the middle of the input string, and should not be
+ * freed.
+ */
+static char *
+truncate_cert_name(char *name)
+{
+ size_t namelen = strlen(name);
+ char *truncated;
+
+ /*
+ * Common Names are 64 chars max, so for a common case where the CN is the
+ * last field, we can still print the longest possible CN with a 7-character
+ * prefix (".../CN=[64 chars]"), for a reasonable limit of 71 characters.
+ */
+#define MAXLEN 71
+
+ if (namelen <= MAXLEN)
+ return NULL;
+
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+
+#undef MAXLEN
+
+ return truncated;
+}
+
/*
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * verification.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1123,70 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ int depth;
+ int errcode;
+ const char *errstring;
+ X509 *cert;
+ char *subject = NULL, *issuer = NULL;
+ char *sub_truncated = NULL, *iss_truncated = NULL;
+ char *serialno = NULL;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ {
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject and Issuer for logging, but don't let maliciously
+ * huge certs flood the logs.
+ */
+ subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ sub_truncated = truncate_cert_name(subject);
+
+ issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
+ iss_truncated = truncate_cert_name(issuer);
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ BN_free(b);
+ }
+
+ ereport(COMMERROR,
+ (errmsg("client certificate verification failed at depth %d: %s",
+ depth, errstring),
+ /* only print detail if we have a certificate to print */
+ subject && errdetail("failed certificate had subject '%s', "
+ "serial number %s, "
+ "purported issuer '%s'",
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer)));
+
+ if (serialno)
+ OPENSSL_free(serialno);
+ if (issuer)
+ pfree(issuer);
+ if (subject)
+ pfree(subject);
+
return ok;
}
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000..a1db55b5c3
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkICCCAiBRIUREYAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNTEyMjE0NDQ3WhcNNDkwOTI3MjE0NDQ3WjCBnDEsMCoGA1UE
+CgwjUG9zdGdyZVNRTCBHbG9iYWwgRGV2ZWxvcG1lbnQgR3JvdXAxITAfBgNVBAsM
+GFNvbWUgT3JnYW5pemF0aW9uYWwgVW5pdDFJMEcGA1UEAwxAc3NsLTEyMzQ1Njc4
+OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
+Nzg5MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgxmeHiVRuBTwlG
+Q1oM2M1ckQCI/o4hYcO9BYdxDYHiA7jy1WVenyj8BtUi5Aj9VDhpfiuewDarGQ5a
+TggD1pMjkw0MorBKBr9+1u1xGH/8Q3lkgU+OQXrPglo4IrVcqaoZFQ0nuMaVbieX
+0dDyTfsTaVQYYtqAtzhI/UGSIOhk2+lB9fP68jw9cLH0QYvR+qQ0IPG13I5zmSYP
+Mj0VYwMn9TF9/2sBOSRVgTVAcrYgOQLk3s/fGe66tmVBIWYcq65ygqD1+weu+Pax
+jPnwsefkdnf6JdYRG1F1Co7g52poPEYieAHfQOJ69sG0LYx0lBODC69qvSJ4WdCQ
+0zKw288CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEArr5r1UxgUzPykmu5ZdL6y8TA
+ZbSQ1yBY0nhsRwRkDd66iPK9U6T6K2+pL8Vc6ioov9WOtHQ6ohP3gSavd40cHRmF
+auwIsZ4Wk0mjftpOuPFp1hyo8d/QYrbEm3qNe5qln5S9h8ipoYvFtf5zlK2KHJFz
+9ehZMZ1zGAERNCVM8UUQKyUuZB5GyrZlbslf6P/9Bsc54YUWxP2pr5r/RJ6DeXfI
+zAFfXT8AFVlClARA949gpX0LVrXryDN60CUJ88QJmYCQ3AtIgzYYeqcdYHTd8eS2
+9P5whDdU5NvROP+LjETeReJF4Bfyc2gM7zxZD2BDSf5exvnNqiy42/lR1b4szw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 707f4005af..a9b737ed09 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -683,6 +683,10 @@ $node->connect_fails(
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -730,7 +734,35 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
+ qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
+ ]);
# test server-side CRL directory
switch_server_cert(
@@ -743,6 +775,10 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index aed6005b43..a43e64c04f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
On 05.07.22 18:34, Jacob Champion wrote:
On Fri, Jul 1, 2022 at 1:51 PM Jacob Champion <jchampion@timescale.com> wrote:
Sorry for the misunderstanding! v3 adds the Issuer to the logs as well.
Resending v3; I messed up the certificate diff with my gitconfig.
This patch looks pretty good to me. Some minor details:
I looked into how you decode the serial number. I have found some code
elsewhere that passed the result of X509_get_serialNumber() directly to
ASN1_INTEGER_set(). But I guess a serial number of maximum length 20
octets wouldn't fit into a 32-bit long. (There is
ASN1_INTEGER_set_int64(), but that requires OpenSSL 1.1.0.) Does that
match your understanding?
For the detail string, I think we could do something like:
DETAIL: Failed certificate data (unverified): subject '%s', serial
number %s, issuer '%s'
On Thu, Jul 7, 2022 at 2:50 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I looked into how you decode the serial number. I have found some code
elsewhere that passed the result of X509_get_serialNumber() directly to
ASN1_INTEGER_set(). But I guess a serial number of maximum length 20
octets wouldn't fit into a 32-bit long. (There is
ASN1_INTEGER_set_int64(), but that requires OpenSSL 1.1.0.) Does that
match your understanding?
Yep. And the bit lengths of the serial numbers used in the test suite
are in the low 60s already. Many people will just randomize their
serial numbers, so I think BN_bn2dec() is the way to go.
For the detail string, I think we could do something like:
DETAIL: Failed certificate data (unverified): subject '%s', serial
number %s, issuer '%s'
Done that way in v4.
I also added an optional 0002 that bubbles the error info up to the
final ereport(ERROR), using errdetail() and errhint(). I can squash it
into 0001 if you like it, or drop it if you don't. (This approach
could be adapted to the client, too.)
Thanks!
--Jacob
Attachments:
since-v3.diff.txttext/plain; charset=US-ASCII; name=since-v3.diff.txtDownload
commit 7489e52168b76df3a84c68d79fb22651a05a0c05
Author: Jacob Champion <jchampion@timescale.com>
Date: Fri Jul 8 09:19:32 2022 -0700
squash! Log details for client certificate failures
Change detail message, per review.
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 80b361b105..a2cbcdad5f 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1173,9 +1173,10 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
(errmsg("client certificate verification failed at depth %d: %s",
depth, errstring),
/* only print detail if we have a certificate to print */
- subject && errdetail("failed certificate had subject '%s', "
+ subject && errdetail("Failed certificate data (unverified): "
+ "subject '%s', "
"serial number %s, "
- "purported issuer '%s'",
+ "issuer '%s'",
sub_truncated ? sub_truncated : subject,
serialno ? serialno : _("unknown"),
iss_truncated ? iss_truncated : issuer)));
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a9b737ed09..0f837e1b9f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -685,7 +685,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656577, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -737,7 +737,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656576, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656576, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
$node->connect_fails(
@@ -746,7 +746,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
- qr/failed certificate had subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ qr/Failed certificate data \(unverified\): subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
# Use an invalid cafile here so that the next test won't be able to verify the
@@ -761,7 +761,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
log_like => [
qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
- qr/failed certificate had subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, purported issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
]);
# test server-side CRL directory
@@ -778,7 +778,7 @@ $node->connect_fails(
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
log_like => [
qr/client certificate verification failed at depth 0: certificate revoked/,
- qr/failed certificate had subject '\/CN=ssltestuser', serial number 2315134995201656577, purported issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656577, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
]);
done_testing();
v4-0001-Log-details-for-client-certificate-failures.patchtext/x-patch; charset=US-ASCII; name=v4-0001-Log-details-for-client-certificate-failures.patchDownload
From 6e516c02a726bf7170a74c0cc2a762835bef0256 Mon Sep 17 00:00:00 2001
From: Jacob Champion <pchampion@vmware.com>
Date: Mon, 29 Mar 2021 10:07:31 -0700
Subject: [PATCH v4 1/2] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly limited
to looking at the TLS alert code on the client side. For simple
deployments, sometimes it's enough to see "sslv3 alert certificate
revoked" and know exactly what needs to be fixed, but if you add any
more complexity (multiple CA layers, misconfigured CA certificates,
etc.), trying to debug what happened based on the TLS alert alone can be
an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Fill it out with error handling and add a COMMERROR log so that a
DBA can debug client failures more easily. It ends up looking like
LOG: connection received: host=localhost port=44120
LOG: client certificate verification failed at depth 1: unable to get local issuer certificate
DETAIL: Failed certificate data (unverified): subject '/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, issuer '/CN=Root CA'
LOG: could not accept SSL connection: certificate verify failed
The length of the Subject and Issuer strings is limited to prevent
malicious client certs from spamming the logs. In case the truncation
makes things ambiguous, the certificate's serial number is also logged.
---
src/backend/libpq/be-secure-openssl.c | 102 +++++++++++++++++++++++++-
src/test/ssl/conf/client-long.config | 14 ++++
src/test/ssl/ssl/client-long.crt | 20 +++++
src/test/ssl/ssl/client-long.key | 27 +++++++
src/test/ssl/sslfiles.mk | 2 +-
src/test/ssl/t/001_ssltests.pl | 40 +++++++++-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 2 +-
7 files changed, 201 insertions(+), 6 deletions(-)
create mode 100644 src/test/ssl/conf/client-long.config
create mode 100644 src/test/ssl/ssl/client-long.crt
create mode 100644 src/test/ssl/ssl/client-long.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..a2cbcdad5f 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1076,12 +1076,45 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
return 0;
}
+/*
+ * Examines the provided certificate name, and if it's too long to log, modifies
+ * and truncates it. The return value is NULL if no truncation was needed; it
+ * otherwise points into the middle of the input string, and should not be
+ * freed.
+ */
+static char *
+truncate_cert_name(char *name)
+{
+ size_t namelen = strlen(name);
+ char *truncated;
+
+ /*
+ * Common Names are 64 chars max, so for a common case where the CN is the
+ * last field, we can still print the longest possible CN with a 7-character
+ * prefix (".../CN=[64 chars]"), for a reasonable limit of 71 characters.
+ */
+#define MAXLEN 71
+
+ if (namelen <= MAXLEN)
+ return NULL;
+
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+
+#undef MAXLEN
+
+ return truncated;
+}
+
/*
* Certificate verification callback
*
* This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * verification.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1123,71 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ int depth;
+ int errcode;
+ const char *errstring;
+ X509 *cert;
+ char *subject = NULL, *issuer = NULL;
+ char *sub_truncated = NULL, *iss_truncated = NULL;
+ char *serialno = NULL;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ {
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject and Issuer for logging, but don't let maliciously
+ * huge certs flood the logs.
+ */
+ subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ sub_truncated = truncate_cert_name(subject);
+
+ issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
+ iss_truncated = truncate_cert_name(issuer);
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ BN_free(b);
+ }
+
+ ereport(COMMERROR,
+ (errmsg("client certificate verification failed at depth %d: %s",
+ depth, errstring),
+ /* only print detail if we have a certificate to print */
+ subject && errdetail("Failed certificate data (unverified): "
+ "subject '%s', "
+ "serial number %s, "
+ "issuer '%s'",
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer)));
+
+ if (serialno)
+ OPENSSL_free(serialno);
+ if (issuer)
+ pfree(issuer);
+ if (subject)
+ pfree(subject);
+
return ok;
}
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000..a1db55b5c3
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkICCCAiBRIUREYAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNTEyMjE0NDQ3WhcNNDkwOTI3MjE0NDQ3WjCBnDEsMCoGA1UE
+CgwjUG9zdGdyZVNRTCBHbG9iYWwgRGV2ZWxvcG1lbnQgR3JvdXAxITAfBgNVBAsM
+GFNvbWUgT3JnYW5pemF0aW9uYWwgVW5pdDFJMEcGA1UEAwxAc3NsLTEyMzQ1Njc4
+OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
+Nzg5MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgxmeHiVRuBTwlG
+Q1oM2M1ckQCI/o4hYcO9BYdxDYHiA7jy1WVenyj8BtUi5Aj9VDhpfiuewDarGQ5a
+TggD1pMjkw0MorBKBr9+1u1xGH/8Q3lkgU+OQXrPglo4IrVcqaoZFQ0nuMaVbieX
+0dDyTfsTaVQYYtqAtzhI/UGSIOhk2+lB9fP68jw9cLH0QYvR+qQ0IPG13I5zmSYP
+Mj0VYwMn9TF9/2sBOSRVgTVAcrYgOQLk3s/fGe66tmVBIWYcq65ygqD1+weu+Pax
+jPnwsefkdnf6JdYRG1F1Co7g52poPEYieAHfQOJ69sG0LYx0lBODC69qvSJ4WdCQ
+0zKw288CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEArr5r1UxgUzPykmu5ZdL6y8TA
+ZbSQ1yBY0nhsRwRkDd66iPK9U6T6K2+pL8Vc6ioov9WOtHQ6ohP3gSavd40cHRmF
+auwIsZ4Wk0mjftpOuPFp1hyo8d/QYrbEm3qNe5qln5S9h8ipoYvFtf5zlK2KHJFz
+9ehZMZ1zGAERNCVM8UUQKyUuZB5GyrZlbslf6P/9Bsc54YUWxP2pr5r/RJ6DeXfI
+zAFfXT8AFVlClARA949gpX0LVrXryDN60CUJ88QJmYCQ3AtIgzYYeqcdYHTd8eS2
+9P5whDdU5NvROP+LjETeReJF4Bfyc2gM7zxZD2BDSf5exvnNqiy42/lR1b4szw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 707f4005af..0f837e1b9f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -683,6 +683,10 @@ $node->connect_fails(
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656577, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -730,7 +734,35 @@ $node->connect_ok(
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656576, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: unable to get local issuer certificate/,
+ qr/Failed certificate data \(unverified\): subject '\.\.\.\/CN=ssl-123456789012345678901234567890123456789012345678901234567890', serial number 2315418733629425152, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr/client certificate verification failed at depth 1: unable to get local issuer certificate/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, issuer '\/CN=Test root CA for PostgreSQL SSL regression test suite'/,
+ ]);
# test server-side CRL directory
switch_server_cert(
@@ -743,6 +775,10 @@ $node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr/client certificate verification failed at depth 0: certificate revoked/,
+ qr/Failed certificate data \(unverified\): subject '\/CN=ssltestuser', serial number 2315134995201656577, issuer '\/CN=Test CA for PostgreSQL SSL regression test client certs'/,
+ ]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index aed6005b43..a43e64c04f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
v4-0002-squash-Log-details-for-client-certificate-failure.patchtext/x-patch; charset=US-ASCII; name=v4-0002-squash-Log-details-for-client-certificate-failure.patchDownload
From 601b4f23eb8a3d304faee94124ca8f5153fb7fb6 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Fri, 8 Jul 2022 11:12:43 -0700
Subject: [PATCH v4 2/2] squash! Log details for client certificate failures
Add an ex_data pointer to the SSL handle, so that we can store error
information from the verification callback. This lets us add our
error details directly to the final "could not accept SSL connection"
log message, as opposed to issuing intermediate LOGs.
The new format looks like this:
LOG: connection received: host=localhost port=43112
LOG: could not accept SSL connection: certificate verify failed
DETAIL: client certificate verification failed at depth 1: unable to get local issuer certificate
HINT: Failed certificate data (unverified): subject '/CN=Test CA for PostgreSQL SSL regression test client certs', serial number 2315134995201656577, issuer '/CN=Test root CA for PostgreSQL SSL regression test suite'
---
src/backend/libpq/be-secure-openssl.c | 68 ++++++++++++++++++---------
1 file changed, 46 insertions(+), 22 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index a2cbcdad5f..b3d5e681ed 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -81,6 +81,15 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v);
static const char *ssl_protocol_version_to_string(int v);
+/* Helpers for storing application data in the SSL handle */
+struct pg_ex_data
+{
+ /* to bubble errors out of callbacks */
+ char *errdetail;
+ char *errhint;
+};
+static int ssl_ex_index;
+
/* ------------------------------------------------------------ */
/* Public interface */
/* ------------------------------------------------------------ */
@@ -102,6 +111,7 @@ be_tls_init(bool isServerStart)
SSL_library_init();
SSL_load_error_strings();
#endif
+ ssl_ex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
SSL_initialized = true;
}
@@ -413,6 +423,7 @@ be_tls_open_server(Port *port)
int err;
int waitfor;
unsigned long ecode;
+ struct pg_ex_data exdata = {0};
bool give_proto_hint;
Assert(!port->ssl);
@@ -445,6 +456,15 @@ be_tls_open_server(Port *port)
SSLerrmessage(ERR_get_error()))));
return -1;
}
+ if (!SSL_set_ex_data(port->ssl, ssl_ex_index, &exdata))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not attach application data to SSL handle: %s",
+ SSLerrmessage(ERR_get_error()))));
+ return -1;
+ }
+
port->ssl_in_use = true;
aloop:
@@ -537,10 +557,15 @@ aloop:
give_proto_hint = false;
break;
}
+
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not accept SSL connection: %s",
SSLerrmessage(ecode)),
+ exdata.errdetail ?
+ errdetail("%s", exdata.errdetail) : 0,
+ exdata.errhint ?
+ errhint("%s", exdata.errhint) :
give_proto_hint ?
errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
ssl_min_protocol_version ?
@@ -1113,8 +1138,8 @@ truncate_cert_name(char *name)
/*
* Certificate verification callback
*
- * This callback allows us to log intermediate problems during
- * verification.
+ * This callback allows us to examine intermediate problems during
+ * verification, for later logging.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1123,13 +1148,12 @@ truncate_cert_name(char *name)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ SSL *ssl;
int depth;
int errcode;
const char *errstring;
+ struct pg_ex_data *exdata;
X509 *cert;
- char *subject = NULL, *issuer = NULL;
- char *sub_truncated = NULL, *iss_truncated = NULL;
- char *serialno = NULL;
if (ok)
{
@@ -1138,13 +1162,23 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
}
/* Pull all the information we have on the verification failure. */
+ ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
depth = X509_STORE_CTX_get_error_depth(ctx);
errcode = X509_STORE_CTX_get_error(ctx);
errstring = X509_verify_cert_error_string(errcode);
+ /* Store our detail message in SSL application data, to be logged later. */
+ exdata = SSL_get_ex_data(ssl, ssl_ex_index);
+ exdata->errdetail =
+ psprintf(_("client certificate verification failed at depth %d: %s"),
+ depth, errstring);
+
cert = X509_STORE_CTX_get_current_cert(ctx);
if (cert)
{
+ char *subject, *issuer;
+ char *sub_truncated, *iss_truncated;
+ char *serialno;
ASN1_INTEGER *sn;
BIGNUM *b;
@@ -1166,27 +1200,17 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
b = ASN1_INTEGER_to_BN(sn, NULL);
serialno = BN_bn2dec(b);
- BN_free(b);
- }
+ exdata->errhint =
+ psprintf(_("Failed certificate data (unverified): subject '%s', serial number %s, issuer '%s'"),
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer);
- ereport(COMMERROR,
- (errmsg("client certificate verification failed at depth %d: %s",
- depth, errstring),
- /* only print detail if we have a certificate to print */
- subject && errdetail("Failed certificate data (unverified): "
- "subject '%s', "
- "serial number %s, "
- "issuer '%s'",
- sub_truncated ? sub_truncated : subject,
- serialno ? serialno : _("unknown"),
- iss_truncated ? iss_truncated : issuer)));
-
- if (serialno)
+ BN_free(b);
OPENSSL_free(serialno);
- if (issuer)
pfree(issuer);
- if (subject)
pfree(subject);
+ }
return ok;
}
--
2.25.1
On 01 Jul 2022, at 22:59, Jacob Champion <jchampion@timescale.com> wrote:
I added this to httpd a while back:
SSL_CLIENT_CERT_RFC4523_CEA
It would be good to interoperate.
What kind of interoperation did you have in mind? Are there existing
tools that want to scrape this information for observability?
This is for human troubleshooting.
I think the CEA syntax might not be a good fit for this particular
patch: first, we haven't actually verified the certificate, so no one
should be using it to assert certificate equality (and I'm truncating
the Issuer anyway, to avoid letting someone flood the logs). Second,
this is designed to be human-readable rather than machine-readable.
This is what a CEA looks like:
{ serialNumber 400410167207191393705333222102472642510002355884, issuer rdnSequence:”CN=Foo UK G1,O=Foo,C=UK" }
Whitespace and escaping is important above.
When troubleshooting, you want a string like the above that you can cut and paste and search for in other systems and log files. The verification status of the cert isn’t an issue at this point, you have a system in front of you where it doesn’t work when it should, and you need to know exactly what’s connecting, not what you think you’re connecting to, and you need precise data.
Please don’t invent another format, or try and truncate the data. This is a huge headache when troubleshooting.
Regards,
Graham
—
On 08.07.22 20:39, Jacob Champion wrote:
I also added an optional 0002 that bubbles the error info up to the
final ereport(ERROR), using errdetail() and errhint(). I can squash it
into 0001 if you like it, or drop it if you don't. (This approach
could be adapted to the client, too.)
I squashed those two together. I also adjusted the error message a bit
more for project style. (We can put both lines into detail.)
I had to read up on this "ex_data" API. Interesting. But I'm wondering
a bit about how the life cycle of these objects is managed. What
happens if the allocated error string is deallocated before its
containing object? Or vice versa? How do we ensure we don't
accidentally reuse the error string when the code runs again? (I guess
currently it can't?) Maybe we should avoid this and just put the
errdetail itself into a static variable that we unset once the message
is printed?
Attachments:
v5-0001-Log-details-for-client-certificate-failures.patchtext/plain; charset=UTF-8; name=v5-0001-Log-details-for-client-certificate-failures.patchDownload
From e9bbf69618bfea433a6916af9cdcc59f04b96eda Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 11 Jul 2022 14:28:43 +0200
Subject: [PATCH v5] Log details for client certificate failures
Currently, debugging client cert verification failures is mostly
limited to looking at the TLS alert code on the client side. For
simple deployments, sometimes it's enough to see "sslv3 alert
certificate revoked" and know exactly what needs to be fixed, but if
you add any more complexity (multiple CA layers, misconfigured CA
certificates, etc.), trying to debug what happened based on the TLS
alert alone can be an exercise in frustration.
Luckily, the server has more information about exactly what failed in
the chain, and we already have the requisite callback implemented as a
stub. Add an ex_data pointer to the SSL handle, so that we can store
error information from the verification callback. This lets us add
our error details directly to the final "could not accept SSL
connection" log message, as opposed to issuing intermediate LOGs.
It ends up looking like
LOG: connection received: host=localhost port=43112
LOG: could not accept SSL connection: certificate verify failed
DETAIL: Client certificate verification failed at depth 1: unable to get local issuer certificate.
Failed certificate data (unverified): subject "/CN=Test CA for PostgreSQL SSL regression test client certs", serial number 2315134995201656577, issuer "/CN=Test root CA for PostgreSQL SSL regression test suite".
The length of the Subject and Issuer strings is limited to prevent
malicious client certs from spamming the logs. In case the truncation
makes things ambiguous, the certificate's serial number is also
logged.
Author: Jacob Champion <pchampion@vmware.com>
Discussion: https://www.postgresql.org/message-id/flat/d13c4a5787c2a3f83705124f0391e0738c796751.camel@vmware.com
---
src/backend/libpq/be-secure-openssl.c | 132 +++++++++++++++++++++++++-
src/test/ssl/conf/client-long.config | 14 +++
src/test/ssl/ssl/client-long.crt | 20 ++++
src/test/ssl/ssl/client-long.key | 27 ++++++
src/test/ssl/sslfiles.mk | 2 +-
src/test/ssl/t/001_ssltests.pl | 40 +++++++-
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 2 +-
7 files changed, 230 insertions(+), 7 deletions(-)
create mode 100644 src/test/ssl/conf/client-long.config
create mode 100644 src/test/ssl/ssl/client-long.crt
create mode 100644 src/test/ssl/ssl/client-long.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3d0168a369..8d5eee0d16 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -81,6 +81,14 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v);
static const char *ssl_protocol_version_to_string(int v);
+/* Helpers for storing application data in the SSL handle */
+struct pg_ex_data
+{
+ /* to bubble errors out of callbacks */
+ char *errdetail;
+};
+static int ssl_ex_index;
+
/* ------------------------------------------------------------ */
/* Public interface */
/* ------------------------------------------------------------ */
@@ -102,6 +110,7 @@ be_tls_init(bool isServerStart)
SSL_library_init();
SSL_load_error_strings();
#endif
+ ssl_ex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
SSL_initialized = true;
}
@@ -413,6 +422,7 @@ be_tls_open_server(Port *port)
int err;
int waitfor;
unsigned long ecode;
+ struct pg_ex_data exdata = {0};
bool give_proto_hint;
Assert(!port->ssl);
@@ -445,6 +455,15 @@ be_tls_open_server(Port *port)
SSLerrmessage(ERR_get_error()))));
return -1;
}
+ if (!SSL_set_ex_data(port->ssl, ssl_ex_index, &exdata))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not attach application data to SSL handle: %s",
+ SSLerrmessage(ERR_get_error()))));
+ return -1;
+ }
+
port->ssl_in_use = true;
aloop:
@@ -537,10 +556,12 @@ be_tls_open_server(Port *port)
give_proto_hint = false;
break;
}
+
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not accept SSL connection: %s",
SSLerrmessage(ecode)),
+ exdata.errdetail ? errdetail_internal("%s", exdata.errdetail) : 0,
give_proto_hint ?
errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
ssl_min_protocol_version ?
@@ -1076,12 +1097,46 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
return 0;
}
+/*
+ * Examines the provided certificate name, and if it's too long to log, modifies
+ * and truncates it. The return value is NULL if no truncation was needed; it
+ * otherwise points into the middle of the input string, and should not be
+ * freed.
+ */
+static char *
+truncate_cert_name(char *name)
+{
+ size_t namelen = strlen(name);
+ char *truncated;
+
+ /*
+ * Common Names are 64 chars max, so for a common case where the CN is the
+ * last field, we can still print the longest possible CN with a
+ * 7-character prefix (".../CN=[64 chars]"), for a reasonable limit of 71
+ * characters.
+ */
+#define MAXLEN 71
+
+ if (namelen <= MAXLEN)
+ return NULL;
+
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+
+#undef MAXLEN
+
+ return truncated;
+}
+
/*
* Certificate verification callback
*
- * This callback allows us to log intermediate problems during
- * verification, but for now we'll see if the final error message
- * contains enough information.
+ * This callback allows us to examine intermediate problems during
+ * verification, for later logging.
*
* This callback also allows us to override the default acceptance
* criteria (e.g., accepting self-signed or expired certs), but
@@ -1090,6 +1145,77 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
+ SSL *ssl;
+ int depth;
+ int errcode;
+ const char *errstring;
+ StringInfoData str;
+ struct pg_ex_data *exdata;
+ X509 *cert;
+
+ if (ok)
+ {
+ /* Nothing to do for the successful case. */
+ return ok;
+ }
+
+ /* Pull all the information we have on the verification failure. */
+ ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ errcode = X509_STORE_CTX_get_error(ctx);
+ errstring = X509_verify_cert_error_string(errcode);
+
+ initStringInfo(&str);
+ appendStringInfo(&str,
+ _("Client certificate verification failed at depth %d: %s."),
+ depth, errstring);
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ if (cert)
+ {
+ char *subject,
+ *issuer;
+ char *sub_truncated,
+ *iss_truncated;
+ char *serialno;
+ ASN1_INTEGER *sn;
+ BIGNUM *b;
+
+ /*
+ * Get the Subject and Issuer for logging, but don't let maliciously
+ * huge certs flood the logs.
+ */
+ subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
+ sub_truncated = truncate_cert_name(subject);
+
+ issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
+ iss_truncated = truncate_cert_name(issuer);
+
+ /*
+ * Pull the serial number, too, in case a Subject is still ambiguous.
+ * This mirrors be_tls_get_peer_serial().
+ */
+ sn = X509_get_serialNumber(cert);
+ b = ASN1_INTEGER_to_BN(sn, NULL);
+ serialno = BN_bn2dec(b);
+
+ appendStringInfoChar(&str, '\n');
+ appendStringInfo(&str,
+ _("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
+ sub_truncated ? sub_truncated : subject,
+ serialno ? serialno : _("unknown"),
+ iss_truncated ? iss_truncated : issuer);
+
+ BN_free(b);
+ OPENSSL_free(serialno);
+ pfree(issuer);
+ pfree(subject);
+ }
+
+ /* Store our detail message in SSL application data, to be logged later. */
+ exdata = SSL_get_ex_data(ssl, ssl_ex_index);
+ exdata->errdetail = str.data;
+
return ok;
}
diff --git a/src/test/ssl/conf/client-long.config b/src/test/ssl/conf/client-long.config
new file mode 100644
index 0000000000..0e92a8fbfe
--- /dev/null
+++ b/src/test/ssl/conf/client-long.config
@@ -0,0 +1,14 @@
+# An OpenSSL format CSR config file for creating a client certificate with a
+# long Subject.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+# Common Names are 64 characters max
+CN = ssl-123456789012345678901234567890123456789012345678901234567890
+OU = Some Organizational Unit
+O = PostgreSQL Global Development Group
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-long.crt b/src/test/ssl/ssl/client-long.crt
new file mode 100644
index 0000000000..a1db55b5c3
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkICCCAiBRIUREYAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNTEyMjE0NDQ3WhcNNDkwOTI3MjE0NDQ3WjCBnDEsMCoGA1UE
+CgwjUG9zdGdyZVNRTCBHbG9iYWwgRGV2ZWxvcG1lbnQgR3JvdXAxITAfBgNVBAsM
+GFNvbWUgT3JnYW5pemF0aW9uYWwgVW5pdDFJMEcGA1UEAwxAc3NsLTEyMzQ1Njc4
+OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
+Nzg5MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgxmeHiVRuBTwlG
+Q1oM2M1ckQCI/o4hYcO9BYdxDYHiA7jy1WVenyj8BtUi5Aj9VDhpfiuewDarGQ5a
+TggD1pMjkw0MorBKBr9+1u1xGH/8Q3lkgU+OQXrPglo4IrVcqaoZFQ0nuMaVbieX
+0dDyTfsTaVQYYtqAtzhI/UGSIOhk2+lB9fP68jw9cLH0QYvR+qQ0IPG13I5zmSYP
+Mj0VYwMn9TF9/2sBOSRVgTVAcrYgOQLk3s/fGe66tmVBIWYcq65ygqD1+weu+Pax
+jPnwsefkdnf6JdYRG1F1Co7g52poPEYieAHfQOJ69sG0LYx0lBODC69qvSJ4WdCQ
+0zKw288CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEArr5r1UxgUzPykmu5ZdL6y8TA
+ZbSQ1yBY0nhsRwRkDd66iPK9U6T6K2+pL8Vc6ioov9WOtHQ6ohP3gSavd40cHRmF
+auwIsZ4Wk0mjftpOuPFp1hyo8d/QYrbEm3qNe5qln5S9h8ipoYvFtf5zlK2KHJFz
+9ehZMZ1zGAERNCVM8UUQKyUuZB5GyrZlbslf6P/9Bsc54YUWxP2pr5r/RJ6DeXfI
+zAFfXT8AFVlClARA949gpX0LVrXryDN60CUJ88QJmYCQ3AtIgzYYeqcdYHTd8eS2
+9P5whDdU5NvROP+LjETeReJF4Bfyc2gM7zxZD2BDSf5exvnNqiy42/lR1b4szw==
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-long.key b/src/test/ssl/ssl/client-long.key
new file mode 100644
index 0000000000..5b455a021f
--- /dev/null
+++ b/src/test/ssl/ssl/client-long.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2DGZ4eJVG4FPCUZDWgzYzVyRAIj+jiFhw70Fh3ENgeIDuPLV
+ZV6fKPwG1SLkCP1UOGl+K57ANqsZDlpOCAPWkyOTDQyisEoGv37W7XEYf/xDeWSB
+T45Bes+CWjgitVypqhkVDSe4xpVuJ5fR0PJN+xNpVBhi2oC3OEj9QZIg6GTb6UH1
+8/ryPD1wsfRBi9H6pDQg8bXcjnOZJg8yPRVjAyf1MX3/awE5JFWBNUBytiA5AuTe
+z98Z7rq2ZUEhZhyrrnKCoPX7B6749rGM+fCx5+R2d/ol1hEbUXUKjuDnamg8RiJ4
+Ad9A4nr2wbQtjHSUE4MLr2q9InhZ0JDTMrDbzwIDAQABAoIBADwJykpIqIny5xgU
+QzAG0U52nm4fnVGrQ5MwMxDh/HZNZes+xLRaCqk/FEasYdd9Qp5H7Zn/hDGqYlLy
+ESl4p2ZFQtkk4SlD5YvYladq+PrR+4sCtkZ5owWQCwsy+7CSAywRux7kIRRE+0pT
+hxkXsUBAq8eG3i0AAeHHo01KX4kptlJ5d1pFKKAPThTUHCT4VPHg8r59IdsNy6wC
++0E5ZRWsVUePy+ERuarX/um896hgbaiDJLFk02Orlc87+OBmRwO8J+KoUOEcAiTO
+OZqGGaDEn5Y2mEdp2cCmq7+Izcklaha6CPsoV8+O2HK8PKvBIQmlgbDmal4/RNqr
+JFqYz0ECgYEA+5z74Tmj+tzH57lcdMqVpndG39N8spBe8JbiFL16qOb6gRDytXjc
+hY6IQo4JStpJulnPBZ5JQSbSBgCOzYWJJVBnnwMJKjNCd1th4znjxxMOe4LiDTtw
+D3hQtzBU9FlI2sjWEUKf1xCyi9N41ApQC5eDWWd/0GN9+xAsxRjLL00CgYEA2/aH
+4kNVsBHQ7vmv+sNsWeIgKg7PC7hRjcCYQG9ylBbBnFtv5XJYicXwqorqngzJPoGw
+gB7iaSWL1UNAOSWRSFYe+woPpkY7n6Pbq211nzqV1avAdVrLylJwyE+EOQgTS30D
+8BHv0I714PMd/QLK5NSUEr1IRtCfLeMpcSg6YYsCgYEAv3O86KxeTMTvyy9s3WVE
+p4y8vhUDHi/iPbjhQBzJF3nhhJGrzE+xpGJG5jWDdpRQY15wuvqtDMkIKA8GmfWQ
+3Hao0gKSV6z3VzCOdEKZQeILNAnsDVt7shm/eRRqoB7L48XLtQh37UJESUbY+qb6
+L0fTZxTs2VjLBF1TY4mxGUUCgYEA1PLENKnJkA5/fowd8aA2CoKfbvgtPARyd9Bn
+1aHPhEzPnabsGm7sBl2qFAEvCFoKjkgR7sd3nCHsUUetKmYTU7uEfLcN1YSS/oct
+CLaMs92M53JCfZqsRrAvXc2VjX0i6Ocb49QJnph4tBHKC4MjmAuxWr8C9QPNxyfv
+nAw9EOcCgYBYzejUzp6xiv5YzpwIncIF0A5E6VITcsW+LOR/FZOUPso0X2hQoIEs
+wx8HjKCGfvX6628vnaWJC099hTmOzVwpEgik5zOmeAmZ//gt2I53Yg/loQUzH0CD
+iXxrg/4Up7Yxx897w11ukOZv2xwmAFO1o52Q8k7d5FiMfEIzAkS3Pg==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index cc023667af..5f67aba795 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,7 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext
+CLIENTS := client client-dn client-revoked client_ext client-long
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 707f4005af..90ba5a7515 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -683,6 +683,10 @@ sub switch_server_cert
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert",
expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr{Client certificate verification failed at depth 0: certificate revoked},
+ qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number 2315134995201656577, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+ ],
# revoked certificates should not authenticate the user
log_unlike => [qr/connection authenticated:/],);
@@ -730,7 +734,35 @@ sub switch_server_cert
$node->connect_fails(
$common_connstr . " " . "sslmode=require sslcert=ssl/client.crt",
"intermediate client certificate is missing",
- expected_stderr => qr/SSL error: tlsv1 alert unknown ca/);
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr{Client certificate verification failed at depth 0: unable to get local issuer certificate},
+ qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number 2315134995201656576, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+ ]);
+
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client-long.crt " . sslkey('client-long.key'),
+ "logged client certificate Subjects are truncated if they're too long",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr{Client certificate verification failed at depth 0: unable to get local issuer certificate},
+ qr{Failed certificate data \(unverified\): subject "\.\.\./CN=ssl-123456789012345678901234567890123456789012345678901234567890", serial number 2315418733629425152, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+ ]);
+
+# Use an invalid cafile here so that the next test won't be able to verify the
+# client CA.
+switch_server_cert($node, certfile => 'server-cn-only', cafile => 'server-cn-only');
+
+# intermediate CA is provided but doesn't have a trusted root (checks error
+# logging for cert chain depths > 0)
+$node->connect_fails(
+ "$common_connstr sslmode=require sslcert=ssl/client+client_ca.crt",
+ "intermediate client certificate is untrusted",
+ expected_stderr => qr/SSL error: tlsv1 alert unknown ca/,
+ log_like => [
+ qr{Client certificate verification failed at depth 1: unable to get local issuer certificate},
+ qr{Failed certificate data \(unverified\): subject "/CN=Test CA for PostgreSQL SSL regression test client certs", serial number 2315134995201656577, issuer "/CN=Test root CA for PostgreSQL SSL regression test suite"},
+ ]);
# test server-side CRL directory
switch_server_cert(
@@ -743,6 +775,10 @@ sub switch_server_cert
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt "
. sslkey('client-revoked.key'),
"certificate authorization fails with revoked client cert with server-side CRL directory",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ log_like => [
+ qr{Client certificate verification failed at depth 0: certificate revoked},
+ qr{Failed certificate data \(unverified\): subject "/CN=ssltestuser", serial number 2315134995201656577, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+ ]);
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index aed6005b43..a43e64c04f 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,7 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key");
+ "client_ext.key", "client-long.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.37.0
On Sat, Jul 9, 2022 at 6:49 AM Graham Leggett <minfrin@sharp.fm> wrote:
Please don’t invent another format, or try and truncate the data. This is a huge headache when troubleshooting.
I hear you, and I agree that correlating these things across machines
is something we should be making easier. I'm just not convinced that
the particular format you've proposed, with a new set of rules for
quoting and escaping, needs to be part of this patch. (And I think
there are good reasons to truncate unverified cert data, so there'd
have to be clear benefits to offset the risk of opening it up.)
Searching Google for "issuer rdnSequence" comes up with mostly false
positives related to LDAP filtering and certificate dumps, and the
true positives seem to be mail threads that you've participated in. Do
many LDAP servers log certificate failures in this format by default?
(For that matter, does httpd?) The discussion at the time you added
this to httpd [1]https://lists.apache.org/thread/1665qc4mod7ppp58qk3bqc2l3wtl3lkn seemed to be making the point that this was a niche
format, suited mostly for interaction with LDAP filters -- and Kaspar
additionally pointed out that it's not a canonical format, so all of
our implementations would have to have an ad hoc agreement to choose
exactly one encoding.
If you're using randomized serial numbers, you should be able to grep
for those by themselves and successfully match many different formats,
no? To me, that seems good enough for a first patch, considering we
don't currently log any of this information.
--Jacobfi
[1]: https://lists.apache.org/thread/1665qc4mod7ppp58qk3bqc2l3wtl3lkn
On Mon, Jul 11, 2022 at 6:09 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I squashed those two together. I also adjusted the error message a bit
more for project style. (We can put both lines into detail.)
Oh, okay. Log parsers don't have any issues with that?
I had to read up on this "ex_data" API. Interesting. But I'm wondering
a bit about how the life cycle of these objects is managed. What
happens if the allocated error string is deallocated before its
containing object? Or vice versa?
Yeah, I'm currently leaning heavily on the lack of any memory context
switches here. And I end up leaking out a pointer to the stale stack
of be_tls_open_server(), which is gross -- it works since there are no
other clients, but that could probably come back to bite us.
The ex_data API exposes optional callbacks for new/dup/free (I'm
currently setting those to NULL), so we can run custom code whenever
the SSL* is destroyed. If you'd rather the data have the same lifetime
of the SSL* object, we can switch to malloc/strdup/free (or even
OPENSSL_strdup() in later versions). But since we don't have any use
for the ex_data outside of this function, maybe we should just clear
it before we return, rather than carrying it around.
How do we ensure we don't
accidentally reuse the error string when the code runs again? (I guess
currently it can't?)
The ex_data is associated with the SSL*, not the global SSL_CTX*, so
that shouldn't be an issue. A new SSL* gets created at the start of
be_tls_open_server().
Maybe we should avoid this and just put the
errdetail itself into a static variable that we unset once the message
is printed?
If you're worried about the lifetime of the palloc'd data being too
short, does switching to a static variable help in that case?
--Jacob
On 13.07.22 01:06, Jacob Champion wrote:
I had to read up on this "ex_data" API. Interesting. But I'm wondering
a bit about how the life cycle of these objects is managed. What
happens if the allocated error string is deallocated before its
containing object? Or vice versa?Yeah, I'm currently leaning heavily on the lack of any memory context
switches here. And I end up leaking out a pointer to the stale stack
of be_tls_open_server(), which is gross -- it works since there are no
other clients, but that could probably come back to bite us.The ex_data API exposes optional callbacks for new/dup/free (I'm
currently setting those to NULL), so we can run custom code whenever
the SSL* is destroyed. If you'd rather the data have the same lifetime
of the SSL* object, we can switch to malloc/strdup/free (or even
OPENSSL_strdup() in later versions). But since we don't have any use
for the ex_data outside of this function, maybe we should just clear
it before we return, rather than carrying it around.
Concretely, I was thinking like the attached top-up patch.
The other way can surely be made to work somehow, but this seems much
simpler and with fewer questions about the details.
Attachments:
v5-0002-squash-Log-details-for-client-certificate-failure.patchtext/plain; charset=UTF-8; name=v5-0002-squash-Log-details-for-client-certificate-failure.patchDownload
From 662bc1d19101865f91c6ed4a98c0f13f3757e1c7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 14 Jul 2022 22:08:53 +0200
Subject: [PATCH v5 2/2] squash! Log details for client certificate failures
---
src/backend/libpq/be-secure-openssl.c | 29 +++++----------------------
1 file changed, 5 insertions(+), 24 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 8d5eee0d16..2147524ffc 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -81,13 +81,7 @@ static bool ssl_is_server_start;
static int ssl_protocol_version_to_openssl(int v);
static const char *ssl_protocol_version_to_string(int v);
-/* Helpers for storing application data in the SSL handle */
-struct pg_ex_data
-{
- /* to bubble errors out of callbacks */
- char *errdetail;
-};
-static int ssl_ex_index;
+static const char *cert_errdetail;
/* ------------------------------------------------------------ */
/* Public interface */
@@ -110,7 +104,6 @@ be_tls_init(bool isServerStart)
SSL_library_init();
SSL_load_error_strings();
#endif
- ssl_ex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
SSL_initialized = true;
}
@@ -422,7 +415,6 @@ be_tls_open_server(Port *port)
int err;
int waitfor;
unsigned long ecode;
- struct pg_ex_data exdata = {0};
bool give_proto_hint;
Assert(!port->ssl);
@@ -455,14 +447,6 @@ be_tls_open_server(Port *port)
SSLerrmessage(ERR_get_error()))));
return -1;
}
- if (!SSL_set_ex_data(port->ssl, ssl_ex_index, &exdata))
- {
- ereport(COMMERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("could not attach application data to SSL handle: %s",
- SSLerrmessage(ERR_get_error()))));
- return -1;
- }
port->ssl_in_use = true;
@@ -561,7 +545,7 @@ be_tls_open_server(Port *port)
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not accept SSL connection: %s",
SSLerrmessage(ecode)),
- exdata.errdetail ? errdetail_internal("%s", exdata.errdetail) : 0,
+ cert_errdetail ? errdetail_internal("%s", cert_errdetail) : 0,
give_proto_hint ?
errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
ssl_min_protocol_version ?
@@ -570,6 +554,7 @@ be_tls_open_server(Port *port)
ssl_max_protocol_version ?
ssl_protocol_version_to_string(ssl_max_protocol_version) :
MAX_OPENSSL_TLS_VERSION) : 0));
+ cert_errdetail = NULL;
break;
case SSL_ERROR_ZERO_RETURN:
ereport(COMMERROR,
@@ -1145,12 +1130,10 @@ truncate_cert_name(char *name)
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
- SSL *ssl;
int depth;
int errcode;
const char *errstring;
StringInfoData str;
- struct pg_ex_data *exdata;
X509 *cert;
if (ok)
@@ -1160,7 +1143,6 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
}
/* Pull all the information we have on the verification failure. */
- ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
depth = X509_STORE_CTX_get_error_depth(ctx);
errcode = X509_STORE_CTX_get_error(ctx);
errstring = X509_verify_cert_error_string(errcode);
@@ -1212,9 +1194,8 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
pfree(subject);
}
- /* Store our detail message in SSL application data, to be logged later. */
- exdata = SSL_get_ex_data(ssl, ssl_ex_index);
- exdata->errdetail = str.data;
+ /* Store our detail message to be logged later. */
+ cert_errdetail = str.data;
return ok;
}
--
2.37.0
On Thu, Jul 14, 2022 at 1:12 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
Concretely, I was thinking like the attached top-up patch.
The other way can surely be made to work somehow, but this seems much
simpler and with fewer questions about the details.
Ah, seeing it side-by-side helps. That's much easier, I agree.
Thanks,
--Jacob
On 14.07.22 23:09, Jacob Champion wrote:
On Thu, Jul 14, 2022 at 1:12 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:Concretely, I was thinking like the attached top-up patch.
The other way can surely be made to work somehow, but this seems much
simpler and with fewer questions about the details.Ah, seeing it side-by-side helps. That's much easier, I agree.
Committed like that.
On 7/15/22 09:34, Peter Eisentraut wrote:
Committed like that.
Thanks for all the reviews!
--Jacob
Hi,
On 2022-07-15 09:46:40 -0700, Jacob Champion wrote:
On 7/15/22 09:34, Peter Eisentraut wrote:
Committed like that.
Thanks for all the reviews!
This might have been discussed somewhere, but I'm worried about emitting
unescaped data from pre-auth clients. What guarantees that subject / issuer
name only contain printable ascii-chars? Printing terminal control chars or
such would not be great, nor would splitting a string at a multi-boundary.
Greetings,
Andres Freund
On 7/15/22 12:11, Andres Freund wrote:
This might have been discussed somewhere, but I'm worried about emitting
unescaped data from pre-auth clients. What guarantees that subject / issuer
name only contain printable ascii-chars? Printing terminal control chars or
such would not be great, nor would splitting a string at a multi-boundary.
Hm. The last time I asked about that, Magnus pointed out that we reflect
port->user_name as-is [1]/messages/by-id/CABUevExVHryTasKmtJW5RtU-dBesYj4bV7ggpeVMfiPCHCvLNA@mail.gmail.com, so I kind of stopped worrying about it. Is
this more dangerous than that? (And do we want to fix it now,
regardless?) What guarantees are we supposed to be making for log encoding?
Thanks,
--Jacob
[1]: /messages/by-id/CABUevExVHryTasKmtJW5RtU-dBesYj4bV7ggpeVMfiPCHCvLNA@mail.gmail.com
/messages/by-id/CABUevExVHryTasKmtJW5RtU-dBesYj4bV7ggpeVMfiPCHCvLNA@mail.gmail.com
Hi,
On 2022-07-15 13:20:59 -0700, Jacob Champion wrote:
On 7/15/22 12:11, Andres Freund wrote:
This might have been discussed somewhere, but I'm worried about emitting
unescaped data from pre-auth clients. What guarantees that subject / issuer
name only contain printable ascii-chars? Printing terminal control chars or
such would not be great, nor would splitting a string at a multi-boundary.Hm. The last time I asked about that, Magnus pointed out that we reflect
port->user_name as-is [1], so I kind of stopped worrying about it.
I think we need to fix a number of these. But no, I don't think we should just
add more because we've not been careful in a bunch of other places.
Is this more dangerous than that?
Hard to say.
(And do we want to fix it now, regardless?)
Yes.
What guarantees are we supposed to be making for log encoding?
I don't know, but I don't think not caring at all is a good
option. Particularly for unauthenticated data I'd say that escaping everything
but printable ascii chars is a sensible approach.
Greetings,
Andres Freund
On 7/15/22 13:35, Andres Freund wrote:
(And do we want to fix it now, regardless?)
Yes.
Cool. I can get on board with that.
What guarantees are we supposed to be making for log encoding?
I don't know, but I don't think not caring at all is a good
option. Particularly for unauthenticated data I'd say that escaping everything
but printable ascii chars is a sensible approach.
It'll also be painful for anyone whose infrastructure isn't in a Latin
character set... Maybe that's worth the tradeoff for a v1.
Is there an acceptable approach that could centralize it, so we fix it
once and are done? E.g. a log_encoding GUC and either conversion or
escaping in send_message_to_server_log()?
--Jacob
Hi,
On 2022-07-15 14:01:53 -0700, Jacob Champion wrote:
On 7/15/22 13:35, Andres Freund wrote:
(And do we want to fix it now, regardless?)
Yes.
Cool. I can get on board with that.
What guarantees are we supposed to be making for log encoding?
I don't know, but I don't think not caring at all is a good
option. Particularly for unauthenticated data I'd say that escaping everything
but printable ascii chars is a sensible approach.It'll also be painful for anyone whose infrastructure isn't in a Latin
character set... Maybe that's worth the tradeoff for a v1.
I don't think it's a huge issue, or really avoidable, pre-authentication.
Don't we require all server-side encodings to be supersets of ascii?
We already have pg_clean_ascii() and use it for application_name, fwiw.
Is there an acceptable approach that could centralize it, so we fix it
once and are done? E.g. a log_encoding GUC and either conversion or
escaping in send_message_to_server_log()?
Introducing escaping to ascii for all log messages seems like it'd be
incredibly invasive, and would remove a lot of worthwhile information. Nor
does it really address the whole scope - consider e.g. the truncation in this
patch, that can't be done correctly by the time send_message_to_server_log()
is reached - just chopping in the middle of a multi-byte string would have
made the string invalidly encoded. And we can't perform encoding conversion
from client data until we've gone further into the authentication process, I
think.
Always escaping ANSI escape codes (or rather the non-printable ascii range) is
more convincing. Then we'd just need to make sure that client controlled data
is properly encoded before handing it over to other parts of the system.
I can see a point in a log_encoding GUC at some point, but it seems a bit
separate from the discussion here.
Greetings,
Andres Freund
Andres Freund <andres@anarazel.de> writes:
We already have pg_clean_ascii() and use it for application_name, fwiw.
Yeah, we should just use that. If anyone wants to upgrade the situation
for non-ASCII data later, fixing it for all of these cases at once would
be appropriate.
regards, tom lane
On 7/15/22 14:19, Andres Freund wrote:
On 2022-07-15 14:01:53 -0700, Jacob Champion wrote:
On 7/15/22 13:35, Andres Freund wrote:
I don't know, but I don't think not caring at all is a good
option. Particularly for unauthenticated data I'd say that escaping everything
but printable ascii chars is a sensible approach.It'll also be painful for anyone whose infrastructure isn't in a Latin
character set... Maybe that's worth the tradeoff for a v1.I don't think it's a huge issue, or really avoidable, pre-authentication.
Don't we require all server-side encodings to be supersets of ascii?
Well, I was going to say that for this feature, where the goal is to
debug a failed certificate chain, having to manually unescape the logged
certificate names if your infrastructure already handled UTF-8 natively
would be a real pain.
But your later point about truncation makes that moot; I forgot that my
patch can truncate in the middle of a UTF-8 sequence, so you're probably
dealing with replacement glyphs anyway. I don't really have a leg to
stand on there.
We already have pg_clean_ascii() and use it for application_name, fwiw.
That seems much worse than escaping for this particular patch; if your
cert's Common Name is in (non-ASCII) UTF-8 then all you'll see is
"CN=?????????" in the log lines that were supposed to be helping you
root-cause. Escaping would be much more helpful in this case.
Is there an acceptable approach that could centralize it, so we fix it
once and are done? E.g. a log_encoding GUC and either conversion or
escaping in send_message_to_server_log()?Introducing escaping to ascii for all log messages seems like it'd be
incredibly invasive, and would remove a lot of worthwhile information. Nor
does it really address the whole scope - consider e.g. the truncation in this
patch, that can't be done correctly by the time send_message_to_server_log()
is reached - just chopping in the middle of a multi-byte string would have
made the string invalidly encoded. And we can't perform encoding conversion
from client data until we've gone further into the authentication process, I
think.Always escaping ANSI escape codes (or rather the non-printable ascii range) is
more convincing. Then we'd just need to make sure that client controlled data
is properly encoded before handing it over to other parts of the system.I can see a point in a log_encoding GUC at some point, but it seems a bit
separate from the discussion here.
Fair enough.
--Jacob
Hi,
On 2022-07-15 14:51:38 -0700, Jacob Champion wrote:
We already have pg_clean_ascii() and use it for application_name, fwiw.
That seems much worse than escaping for this particular patch; if your
cert's Common Name is in (non-ASCII) UTF-8 then all you'll see is
"CN=?????????" in the log lines that were supposed to be helping you
root-cause. Escaping would be much more helpful in this case.
I'm doubtful that's all that common. But either way, I suggest a separate
patch to deal with that...
Greetings,
Andres Freund
On Fri, Jul 15, 2022 at 4:45 PM Andres Freund <andres@anarazel.de> wrote:
On 2022-07-15 14:51:38 -0700, Jacob Champion wrote:
That seems much worse than escaping for this particular patch; if your
cert's Common Name is in (non-ASCII) UTF-8 then all you'll see is
"CN=?????????" in the log lines that were supposed to be helping you
root-cause. Escaping would be much more helpful in this case.I'm doubtful that's all that common.
Probably not, but the more systems that support it without weird
usability bugs, the more common it will hopefully become.
But either way, I suggest a separate patch to deal with that...
Proposed fix attached, which uses \x-escaping for bytes outside of
printable ASCII.
Thanks,
--Jacob
Attachments:
0001-Don-t-reflect-unescaped-cert-data-to-the-logs.patchtext/x-patch; charset=UTF-8; name=0001-Don-t-reflect-unescaped-cert-data-to-the-logs.patchDownload
From 3c8e910a75c74f85b640f7d728205c276b1d1c51 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Mon, 18 Jul 2022 15:01:19 -0700
Subject: [PATCH] Don't reflect unescaped cert data to the logs
Commit 3a0e385048 introduced a new path for unauthenticated bytes from
the client certificate to be printed unescaped to the logs. There are a
handful of these already, but it doesn't make sense to keep making the
problem worse. \x-escape any unprintable bytes.
The test case introduces a revoked UTF-8 certificate. This requires the
addition of the `-utf8` flag to `openssl req`. Since the existing
certificates all use an ASCII subset, this won't modify the existing
certificates' subjects if/when they get regenerated; this was verified
experimentally with
$ make sslfiles-clean
$ make sslfiles
Unfortunately the test can't be run as-is yet due to a test timing
issue; see 55828a6b60.
---
src/backend/libpq/be-secure-openssl.c | 74 ++++++++++++-------
src/test/ssl/conf/client-revoked-utf8.config | 13 ++++
src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 | 19 ++---
src/test/ssl/ssl/client-revoked-utf8.crt | 18 +++++
src/test/ssl/ssl/client-revoked-utf8.key | 27 +++++++
src/test/ssl/ssl/client.crl | 19 ++---
.../ssl/ssl/root+client-crldir/9bb9e3c3.r0 | 19 ++---
src/test/ssl/ssl/root+client.crl | 19 ++---
src/test/ssl/sslfiles.mk | 10 ++-
src/test/ssl/t/001_ssltests.pl | 13 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 3 +-
11 files changed, 167 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/conf/client-revoked-utf8.config
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.crt
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 9cec6866a3..ad0d79a511 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -35,6 +35,7 @@
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
#include "utils/memutils.h"
/*
@@ -1082,16 +1083,17 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
}
/*
- * Examines the provided certificate name, and if it's too long to log, modifies
- * and truncates it. The return value is NULL if no truncation was needed; it
- * otherwise points into the middle of the input string, and should not be
- * freed.
+ * Examines the provided certificate name, and if it's too long to log or
+ * contains unprintable ASCII, escapes and truncates it. The return value is
+ * always a new palloc'd string.
*/
static char *
-truncate_cert_name(char *name)
+prepare_cert_name(char *name)
{
size_t namelen = strlen(name);
- char *truncated;
+ char *truncated = name;
+ char *escaped;
+ int i;
/*
* Common Names are 64 chars max, so for a common case where the CN is the
@@ -1101,19 +1103,37 @@ truncate_cert_name(char *name)
*/
#define MAXLEN 71
- if (namelen <= MAXLEN)
- return NULL;
-
- /*
- * Keep the end of the name, not the beginning, since the most specific
- * field is likely to give users the most information.
- */
- truncated = name + namelen - MAXLEN;
- truncated[0] = truncated[1] = truncated[2] = '.';
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ namelen = MAXLEN;
+ }
#undef MAXLEN
- return truncated;
+ escaped = palloc(namelen * 4 + 1); /* one byte becomes four: "\xCC" */
+ i = 0;
+
+ for (char *c = truncated; *c; c++)
+ {
+ /* Keep printable characters including space. Escape everything else. */
+ if (*c >= 0x20 && *c <= 0x7E)
+ escaped[i++] = *c;
+ else
+ {
+ escaped[i++] = '\\';
+ escaped[i++] = 'x';
+ i += hex_encode(c, 1, &escaped[i]);
+ }
+ }
+
+ escaped[i] = '\0';
+ return escaped;
}
/*
@@ -1156,21 +1176,24 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
{
char *subject,
*issuer;
- char *sub_truncated,
- *iss_truncated;
+ char *sub_prepared,
+ *iss_prepared;
char *serialno;
ASN1_INTEGER *sn;
BIGNUM *b;
/*
* Get the Subject and Issuer for logging, but don't let maliciously
- * huge certs flood the logs.
+ * huge certs flood the logs, and don't reflect non-ASCII bytes into it
+ * either.
*/
subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
- sub_truncated = truncate_cert_name(subject);
+ sub_prepared = prepare_cert_name(subject);
+ pfree(subject);
issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
- iss_truncated = truncate_cert_name(issuer);
+ iss_prepared = prepare_cert_name(issuer);
+ pfree(issuer);
/*
* Pull the serial number, too, in case a Subject is still ambiguous.
@@ -1183,14 +1206,13 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
appendStringInfoChar(&str, '\n');
appendStringInfo(&str,
_("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
- sub_truncated ? sub_truncated : subject,
- serialno ? serialno : _("unknown"),
- iss_truncated ? iss_truncated : issuer);
+ sub_prepared, serialno ? serialno : _("unknown"),
+ iss_prepared);
BN_free(b);
OPENSSL_free(serialno);
- pfree(issuer);
- pfree(subject);
+ pfree(iss_prepared);
+ pfree(sub_prepared);
}
/* Store our detail message to be logged later. */
diff --git a/src/test/ssl/conf/client-revoked-utf8.config b/src/test/ssl/conf/client-revoked-utf8.config
new file mode 100644
index 0000000000..ff57e6de8a
--- /dev/null
+++ b/src/test/ssl/conf/client-revoked-utf8.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate contains a non-ASCII CN encoded in UTF-8. It is revoked by the
+# client CA.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Οδυσσέας
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.crt b/src/test/ssl/ssl/client-revoked-utf8.crt
new file mode 100644
index 0000000000..de471d1e60
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2DCCAcACCCAiBxgVKBUAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNzE4MjIyODE1WhcNNDkxMjAzMjIyODE1WjAbMRkwFwYDVQQD
+DBDOn860z4XPg8+Dzq3Osc+CMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqRUMYw05cV5kzi
+YQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb7KbPvoZQdXuV
+8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2/SS/UonX5ar5
+58rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBPVRpX3w9TIpBL
+BZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyyjgrny2SrW0W4
+KlhU09y+m5gKL358z8tj599DowIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAE47ns
+wfceztieaRQtoF+gPcCuImOJqaB7jTE6cQ+aoW/q+sUlOh7AD0IZqhS4o0A4O+ny
+MD7kHkpYP+ctHNomsSQRkFTDZ2ZJGcRgxbwMOSvsKcgNOTMGqpXQiP0x0m7QMBGl
+EHeu5MqG/IK/ZlH9aOTvSnHegB6ztct/7wXMeFCflsWLp6wvnv9YpddaaXf95Oms
+9kwbVYkI1wxaBsAO8VGbJw1YtdErgd65qKTJa45xndtm61i1Jeig5asSNQPwjfZ5
+aNHZ9GsSwsc31Q/6iiezbPwgdAi3ih//uB2hznkMhObnqzR3n8Sw9zgL7DdFr2y9
+2R7kJuGq6DvlWFYS
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.key b/src/test/ssl/ssl/client-revoked-utf8.key
new file mode 100644
index 0000000000..86d9697096
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqR
+UMYw05cV5kziYQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb
+7KbPvoZQdXuV8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2
+/SS/UonX5ar558rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBP
+VRpX3w9TIpBLBZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyy
+jgrny2SrW0W4KlhU09y+m5gKL358z8tj599DowIDAQABAoIBAQCpdePmUInb0kDy
+SCzziOyJ+b8YWB4dOy1uCoQVuvcxSWz2jP1GrIgo2SGdzv7VOcSWnDyiLw9olVYO
+cOS3bnQTiMfgGqAgr/Gir4P3wXx2l80nOvcQimj32cJ7VdCNBEtoBopiTCzU5itM
+dsvNydaIuIyhZFdBnL33kfAskIVbqbgNyMCuDvhEMGFh7T193j3cKnvcfoHsSoNK
+65MT53764P404avgH9+C0W41GvXoMY5BUphUiCwi5TKIvalNP9Gu2LI3R4J3tAE1
+QSR/3Jtaunb5izCyi23MZC+mdz9EALeYRmLpXsspbHaPXDpUA67xifCKnX4JUPVf
+Op5XcXjhAoGBAN/Jt7TFPypRtbW200zx6F3RmToWRnvgn5vaNTmiy0ivcHJu4MLS
+o0yiV3VWksf1PCInK19C1yFo6H2lkhKhvipL62MoRkspOUJcMh42DPPf/RDMhYnF
+8MVQ1TlMdg/I4YXGzsrfl93eFERRjWiAt8b58D9OVpWcQNZMPmGztes3AoGBANcr
+n8ZmGZ5JDzbc+N9l1bGJuRT6PvH0rpoKjWOyaVMDedAUnCbfq01j42zXMfQLR1nE
+67Z6oWrBNNdEJFBhTzTZ+ZYXxpJP/FYJQ19dOCTKN1LQ79OAbSsU0NBLkss4a903
+9JQ+zhrEIEaXCTV9sEnp10KrEo6ctuaqMOkVCBj1AoGBAJy/Xb1wq12o/e3ZsQck
+Ke4M8ZaOI7CBFUrE/KLyNBElUU3V+/h6MYdr7nZxvT3xt7z0UpzW5HiyUqYvYrFK
+OTjHFIjPnOzoYwLoMPKYSVpIealal+54hryucatAszE7MzvQlOfk1SrCcs+nj7Sy
+9Aaa6nxtEpiYaZGwtcEZb0LhAoGAJYODjbGLUd9m+ae49CnrAdMDI7cldkW0k0K3
+t+QJHOIEQNT3DIf+c7Wwlu9F1EiLHgmJFv12WwhoUAefVSxCBPLj4tkuU6ACXHWs
++1ljSna/An9O8M75OYOdjFNAupGRrLXuvFHe2SfMgMIgZuUM8TYFw6fTym1kLf8K
+G/kAumkCgYBBD0TXDDAmVCYECSG1Uz35vm9GitbIe++o2ykO2sdB5mPRiMsfVJw4
+bVInkvV6Y2u4ltsNsS/0Y3A2xq/CnYhc7PeIIWFnfoyuHaIM4TIAflpM6qf4lOWE
+8Ot31P8Mt5U0cvCBuKpu0r9by66xX6yqKCqTPMSvbL7MCx5ukGYY7g==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client.crl b/src/test/ssl/ssl/client.crl
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client.crl
+++ b/src/test/ssl/ssl/client.crl
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client.crl b/src/test/ssl/ssl/root+client.crl
index 02eff4d326..459f48da43 100644
--- a/src/test/ssl/ssl/root+client.crl
+++ b/src/test/ssl/ssl/root+client.crl
@@ -10,13 +10,14 @@ SBNr2rpYp7Coc3GeCoWPcClgSrABD3Z5GY1YAdLGiXVKaH3CmdJTznhEPagE4z5R
+GrJP3XxJ1OC
-----END X509 CRL-----
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 5f67aba795..a843a21d42 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,8 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext client-long
+CLIENTS := client client-dn client-revoked client_ext client-long \
+ client-revoked-utf8
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
@@ -175,7 +176,7 @@ $(CLIENT_CERTS): ssl/%.crt: ssl/%.csr conf/%.config conf/cas.config ssl/client_c
# The CSRs don't need to persist after a build.
.INTERMEDIATE: $(CERTIFICATES:%=ssl/%.csr)
ssl/%.csr: ssl/%.key conf/%.config
- openssl req -new -key $< -out $@ -config conf/$*.config
+ openssl req -new -utf8 -key $< -out $@ -config conf/$*.config
#
# CA State
@@ -215,8 +216,9 @@ ssl/server.crl: ssl/server-revoked.crt ssl/server_ca.crt | $(server_ca_state_fil
openssl ca -config conf/cas.config -name server_ca -revoke $<
openssl ca -config conf/cas.config -name server_ca -gencrl -out $@
-ssl/client.crl: ssl/client-revoked.crt ssl/client_ca.crt | $(client_ca_state_files)
- openssl ca -config conf/cas.config -name client_ca -revoke $<
+ssl/client.crl: ssl/client-revoked.crt ssl/client-revoked-utf8.crt ssl/client_ca.crt | $(client_ca_state_files)
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked.crt
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked-utf8.crt
openssl ca -config conf/cas.config -name client_ca -gencrl -out $@
#
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fa26212de9..efab2ac194 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -793,4 +793,17 @@ $node->connect_fails(
# ]
);
+# revoked client cert, non-ASCII subject
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
+ . sslkey('client-revoked-utf8.key'),
+ "certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # temporarily(?) skip this check due to timing issue
+# log_like => [
+# qr{Client certificate verification failed at depth 0: certificate revoked},
+# qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number 2315420958437414144, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+# ]
+);
+
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index a43e64c04f..6386a25323 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,8 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key", "client-long.key");
+ "client_ext.key", "client-long.key",
+ "client-revoked-utf8.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
Hi,
On 2022-07-19 09:07:31 -0700, Jacob Champion wrote:
On Fri, Jul 15, 2022 at 4:45 PM Andres Freund <andres@anarazel.de> wrote:
On 2022-07-15 14:51:38 -0700, Jacob Champion wrote:
That seems much worse than escaping for this particular patch; if your
cert's Common Name is in (non-ASCII) UTF-8 then all you'll see is
"CN=?????????" in the log lines that were supposed to be helping you
root-cause. Escaping would be much more helpful in this case.I'm doubtful that's all that common.
Probably not, but the more systems that support it without weird
usability bugs, the more common it will hopefully become.But either way, I suggest a separate patch to deal with that...
Proposed fix attached, which uses \x-escaping for bytes outside of
printable ASCII.
I don't think this should be open coded in the ssl part of the code. IMO this
should replace the existing ascii escape function instead. I strongly oppose
open coding this functionality in prepare_cert_name().
Greetings,
Andres Freund
[resending to list]
On 7/19/22 09:14, Andres Freund wrote:
IMO this should replace the existing ascii escape function instead.
That will affect the existing behavior of application_name and
cluster_name; is that acceptable?
--Jacob
Jacob Champion <jchampion@timescale.com> writes:
On 7/19/22 09:14, Andres Freund wrote:
IMO this should replace the existing ascii escape function instead.
That will affect the existing behavior of application_name and
cluster_name; is that acceptable?
I think Andres' point is exactly that these should all act alike.
Having said that, I struggle to see why we are panicking about badly
encoded log data from this source while blithely ignoring the problems
posed by non-ASCII role names, database names, and tablespace names.
regards, tom lane
Hi,
On 2022-07-19 12:39:43 -0400, Tom Lane wrote:
Having said that, I struggle to see why we are panicking about badly
encoded log data from this source while blithely ignoring the problems
posed by non-ASCII role names, database names, and tablespace names.
I think we should fix these as well. I'm not as concerned about post-auth
encoding issues (i.e. tablespace name) as about pre-auth data (role name,
database name) - obviously being allowed to log in already is a pretty good
filter...
Greetings,
Andres Freund
On Tue, Jul 19, 2022 at 10:09 AM Andres Freund <andres@anarazel.de> wrote:
On 2022-07-19 12:39:43 -0400, Tom Lane wrote:
Having said that, I struggle to see why we are panicking about badly
encoded log data from this source while blithely ignoring the problems
posed by non-ASCII role names, database names, and tablespace names.I think we should fix these as well. I'm not as concerned about post-auth
encoding issues (i.e. tablespace name) as about pre-auth data (role name,
database name) - obviously being allowed to log in already is a pretty good
filter...
v2 adds escaping to pg_clean_ascii(). My original attempt used
StringInfo allocation, but that didn't play well with guc_malloc(), so
I switched to a two-pass API where the caller allocates. Let me know
if I'm missing something obvious; this way is more verbose than I'd
like...
Thanks,
--Jacob
Attachments:
v2-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchtext/x-patch; charset=US-ASCII; name=v2-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchDownload
From d99b59652f0b8479a5df0bc50357b5c4617f9fc2 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Tue, 19 Jul 2022 10:48:58 -0700
Subject: [PATCH v2 1/2] pg_clean_ascii(): escape bytes rather than lose them
Rather than replace each unprintable byte with a '?' character, replace
it with a hex escape instead. The API is now two-pass (one pass to get
the escaped length of the string, the second pass to perform the
escaping), in order to allow the use of guc_malloc'd buffers.
---
src/backend/postmaster/postmaster.c | 4 +--
src/backend/utils/misc/guc.c | 10 ++++++--
src/common/string.c | 38 ++++++++++++++++++++++-------
src/include/common/string.h | 2 +-
4 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 1c25457526..8f5cdf4380 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2284,9 +2284,9 @@ retry1:
*/
if (strcmp(nameptr, "application_name") == 0)
{
- char *tmp_app_name = pstrdup(valptr);
+ char *tmp_app_name = palloc(pg_clean_ascii(valptr, NULL));
- pg_clean_ascii(tmp_app_name);
+ pg_clean_ascii(valptr, tmp_app_name);
port->application_name = tmp_app_name;
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0328029d43..2e1a7af315 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12480,7 +12480,10 @@ static bool
check_application_name(char **newval, void **extra, GucSource source)
{
/* Only allow clean ASCII chars in the application name */
- pg_clean_ascii(*newval);
+ char *buf = guc_malloc(ERROR, pg_clean_ascii(*newval, NULL));
+
+ pg_clean_ascii(*newval, buf);
+ *newval = buf;
return true;
}
@@ -12496,7 +12499,10 @@ static bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
/* Only allow clean ASCII chars in the cluster name */
- pg_clean_ascii(*newval);
+ char *buf = guc_malloc(ERROR, pg_clean_ascii(*newval, NULL));
+
+ pg_clean_ascii(*newval, buf);
+ *newval = buf;
return true;
}
diff --git a/src/common/string.c b/src/common/string.c
index 16940d1fa7..82a8afa4a9 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -22,6 +22,7 @@
#endif
#include "common/string.h"
+#include "lib/stringinfo.h"
/*
@@ -59,9 +60,11 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
/*
- * pg_clean_ascii -- Replace any non-ASCII chars with a '?' char
+ * pg_clean_ascii -- Replace any non-ASCII chars with a "\xXX" string
*
- * Modifies the string passed in which must be '\0'-terminated.
+ * Puts an escaped copy into the dst buffer, which must be at least as big as
+ * the return value of pg_clean_ascii(src, NULL). The input string must be
+ * '\0'-terminated.
*
* This function exists specifically to deal with filtering out
* non-ASCII characters in a few places where the client can provide an almost
@@ -73,22 +76,39 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
* In general, this function should NOT be used- instead, consider how to handle
* the string without needing to filter out the non-ASCII characters.
*
- * Ultimately, we'd like to improve the situation to not require stripping out
- * all non-ASCII but perform more intelligent filtering which would allow UTF or
+ * Ultimately, we'd like to improve the situation to not require replacing all
+ * non-ASCII but perform more intelligent filtering which would allow UTF or
* similar, but it's unclear exactly what we should allow, so stick to ASCII only
* for now.
*/
-void
-pg_clean_ascii(char *str)
+size_t
+pg_clean_ascii(const char *str, char *dst)
{
- /* Only allow clean ASCII chars in the string */
- char *p;
+ const char *p;
+ size_t i = 0;
for (p = str; *p != '\0'; p++)
{
+ /* Only allow clean ASCII chars in the string */
if (*p < 32 || *p > 126)
- *p = '?';
+ {
+ if (dst)
+ snprintf(&dst[i], 5, "\\x%02x", (unsigned char) *p);
+ i += 4;
+ }
+ else
+ {
+ if (dst)
+ dst[i] = *p;
+ i++;
+ }
}
+
+ if (dst)
+ dst[i] = '\0';
+ i++;
+
+ return i;
}
diff --git a/src/include/common/string.h b/src/include/common/string.h
index cf00fb53cd..0466da640c 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,7 +24,7 @@ typedef struct PromptInterruptContext
extern bool pg_str_endswith(const char *str, const char *end);
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
-extern void pg_clean_ascii(char *str);
+extern size_t pg_clean_ascii(const char *str, char *dst);
extern int pg_strip_crlf(char *str);
extern bool pg_is_ascii(const char *str);
--
2.25.1
v2-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchtext/x-patch; charset=UTF-8; name=v2-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchDownload
From 17e82750ced795a581d2880db221410dd95a4d7d Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Mon, 18 Jul 2022 15:01:19 -0700
Subject: [PATCH v2 2/2] Don't reflect unescaped cert data to the logs
Commit 3a0e385048 introduced a new path for unauthenticated bytes from
the client certificate to be printed unescaped to the logs. There are a
handful of these already, but it doesn't make sense to keep making the
problem worse. \x-escape any unprintable bytes.
The test case introduces a revoked UTF-8 certificate. This requires the
addition of the `-utf8` flag to `openssl req`. Since the existing
certificates all use an ASCII subset, this won't modify the existing
certificates' subjects if/when they get regenerated; this was verified
experimentally with
$ make sslfiles-clean
$ make sslfiles
Unfortunately the test can't be run as-is yet due to a test timing
issue; see 55828a6b60.
---
src/backend/libpq/be-secure-openssl.c | 60 +++++++++++--------
src/test/ssl/conf/client-revoked-utf8.config | 13 ++++
src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 | 19 +++---
src/test/ssl/ssl/client-revoked-utf8.crt | 18 ++++++
src/test/ssl/ssl/client-revoked-utf8.key | 27 +++++++++
src/test/ssl/ssl/client.crl | 19 +++---
.../ssl/ssl/root+client-crldir/9bb9e3c3.r0 | 19 +++---
src/test/ssl/ssl/root+client.crl | 19 +++---
src/test/ssl/sslfiles.mk | 10 ++--
src/test/ssl/t/001_ssltests.pl | 13 ++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 3 +-
11 files changed, 153 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/conf/client-revoked-utf8.config
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.crt
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 9cec6866a3..f0a0ebcbb7 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -29,12 +29,14 @@
#include <arpa/inet.h>
#endif
+#include "common/string.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
#include "utils/memutils.h"
/*
@@ -1082,16 +1084,16 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
}
/*
- * Examines the provided certificate name, and if it's too long to log, modifies
- * and truncates it. The return value is NULL if no truncation was needed; it
- * otherwise points into the middle of the input string, and should not be
- * freed.
+ * Examines the provided certificate name, and if it's too long to log or
+ * contains unprintable ASCII, escapes and truncates it. The return value is
+ * always a new palloc'd string.
*/
static char *
-truncate_cert_name(char *name)
+prepare_cert_name(char *name)
{
size_t namelen = strlen(name);
- char *truncated;
+ char *truncated = name;
+ char *escaped;
/*
* Common Names are 64 chars max, so for a common case where the CN is the
@@ -1101,19 +1103,23 @@ truncate_cert_name(char *name)
*/
#define MAXLEN 71
- if (namelen <= MAXLEN)
- return NULL;
-
- /*
- * Keep the end of the name, not the beginning, since the most specific
- * field is likely to give users the most information.
- */
- truncated = name + namelen - MAXLEN;
- truncated[0] = truncated[1] = truncated[2] = '.';
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ namelen = MAXLEN;
+ }
#undef MAXLEN
- return truncated;
+ escaped = palloc(pg_clean_ascii(truncated, NULL));
+ pg_clean_ascii(truncated, escaped);
+
+ return escaped;
}
/*
@@ -1156,21 +1162,24 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
{
char *subject,
*issuer;
- char *sub_truncated,
- *iss_truncated;
+ char *sub_prepared,
+ *iss_prepared;
char *serialno;
ASN1_INTEGER *sn;
BIGNUM *b;
/*
* Get the Subject and Issuer for logging, but don't let maliciously
- * huge certs flood the logs.
+ * huge certs flood the logs, and don't reflect non-ASCII bytes into it
+ * either.
*/
subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
- sub_truncated = truncate_cert_name(subject);
+ sub_prepared = prepare_cert_name(subject);
+ pfree(subject);
issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
- iss_truncated = truncate_cert_name(issuer);
+ iss_prepared = prepare_cert_name(issuer);
+ pfree(issuer);
/*
* Pull the serial number, too, in case a Subject is still ambiguous.
@@ -1183,14 +1192,13 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
appendStringInfoChar(&str, '\n');
appendStringInfo(&str,
_("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
- sub_truncated ? sub_truncated : subject,
- serialno ? serialno : _("unknown"),
- iss_truncated ? iss_truncated : issuer);
+ sub_prepared, serialno ? serialno : _("unknown"),
+ iss_prepared);
BN_free(b);
OPENSSL_free(serialno);
- pfree(issuer);
- pfree(subject);
+ pfree(iss_prepared);
+ pfree(sub_prepared);
}
/* Store our detail message to be logged later. */
diff --git a/src/test/ssl/conf/client-revoked-utf8.config b/src/test/ssl/conf/client-revoked-utf8.config
new file mode 100644
index 0000000000..ff57e6de8a
--- /dev/null
+++ b/src/test/ssl/conf/client-revoked-utf8.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate contains a non-ASCII CN encoded in UTF-8. It is revoked by the
+# client CA.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Οδυσσέας
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.crt b/src/test/ssl/ssl/client-revoked-utf8.crt
new file mode 100644
index 0000000000..de471d1e60
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2DCCAcACCCAiBxgVKBUAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNzE4MjIyODE1WhcNNDkxMjAzMjIyODE1WjAbMRkwFwYDVQQD
+DBDOn860z4XPg8+Dzq3Osc+CMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqRUMYw05cV5kzi
+YQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb7KbPvoZQdXuV
+8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2/SS/UonX5ar5
+58rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBPVRpX3w9TIpBL
+BZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyyjgrny2SrW0W4
+KlhU09y+m5gKL358z8tj599DowIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAE47ns
+wfceztieaRQtoF+gPcCuImOJqaB7jTE6cQ+aoW/q+sUlOh7AD0IZqhS4o0A4O+ny
+MD7kHkpYP+ctHNomsSQRkFTDZ2ZJGcRgxbwMOSvsKcgNOTMGqpXQiP0x0m7QMBGl
+EHeu5MqG/IK/ZlH9aOTvSnHegB6ztct/7wXMeFCflsWLp6wvnv9YpddaaXf95Oms
+9kwbVYkI1wxaBsAO8VGbJw1YtdErgd65qKTJa45xndtm61i1Jeig5asSNQPwjfZ5
+aNHZ9GsSwsc31Q/6iiezbPwgdAi3ih//uB2hznkMhObnqzR3n8Sw9zgL7DdFr2y9
+2R7kJuGq6DvlWFYS
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.key b/src/test/ssl/ssl/client-revoked-utf8.key
new file mode 100644
index 0000000000..86d9697096
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqR
+UMYw05cV5kziYQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb
+7KbPvoZQdXuV8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2
+/SS/UonX5ar558rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBP
+VRpX3w9TIpBLBZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyy
+jgrny2SrW0W4KlhU09y+m5gKL358z8tj599DowIDAQABAoIBAQCpdePmUInb0kDy
+SCzziOyJ+b8YWB4dOy1uCoQVuvcxSWz2jP1GrIgo2SGdzv7VOcSWnDyiLw9olVYO
+cOS3bnQTiMfgGqAgr/Gir4P3wXx2l80nOvcQimj32cJ7VdCNBEtoBopiTCzU5itM
+dsvNydaIuIyhZFdBnL33kfAskIVbqbgNyMCuDvhEMGFh7T193j3cKnvcfoHsSoNK
+65MT53764P404avgH9+C0W41GvXoMY5BUphUiCwi5TKIvalNP9Gu2LI3R4J3tAE1
+QSR/3Jtaunb5izCyi23MZC+mdz9EALeYRmLpXsspbHaPXDpUA67xifCKnX4JUPVf
+Op5XcXjhAoGBAN/Jt7TFPypRtbW200zx6F3RmToWRnvgn5vaNTmiy0ivcHJu4MLS
+o0yiV3VWksf1PCInK19C1yFo6H2lkhKhvipL62MoRkspOUJcMh42DPPf/RDMhYnF
+8MVQ1TlMdg/I4YXGzsrfl93eFERRjWiAt8b58D9OVpWcQNZMPmGztes3AoGBANcr
+n8ZmGZ5JDzbc+N9l1bGJuRT6PvH0rpoKjWOyaVMDedAUnCbfq01j42zXMfQLR1nE
+67Z6oWrBNNdEJFBhTzTZ+ZYXxpJP/FYJQ19dOCTKN1LQ79OAbSsU0NBLkss4a903
+9JQ+zhrEIEaXCTV9sEnp10KrEo6ctuaqMOkVCBj1AoGBAJy/Xb1wq12o/e3ZsQck
+Ke4M8ZaOI7CBFUrE/KLyNBElUU3V+/h6MYdr7nZxvT3xt7z0UpzW5HiyUqYvYrFK
+OTjHFIjPnOzoYwLoMPKYSVpIealal+54hryucatAszE7MzvQlOfk1SrCcs+nj7Sy
+9Aaa6nxtEpiYaZGwtcEZb0LhAoGAJYODjbGLUd9m+ae49CnrAdMDI7cldkW0k0K3
+t+QJHOIEQNT3DIf+c7Wwlu9F1EiLHgmJFv12WwhoUAefVSxCBPLj4tkuU6ACXHWs
++1ljSna/An9O8M75OYOdjFNAupGRrLXuvFHe2SfMgMIgZuUM8TYFw6fTym1kLf8K
+G/kAumkCgYBBD0TXDDAmVCYECSG1Uz35vm9GitbIe++o2ykO2sdB5mPRiMsfVJw4
+bVInkvV6Y2u4ltsNsS/0Y3A2xq/CnYhc7PeIIWFnfoyuHaIM4TIAflpM6qf4lOWE
+8Ot31P8Mt5U0cvCBuKpu0r9by66xX6yqKCqTPMSvbL7MCx5ukGYY7g==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client.crl b/src/test/ssl/ssl/client.crl
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client.crl
+++ b/src/test/ssl/ssl/client.crl
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client.crl b/src/test/ssl/ssl/root+client.crl
index 02eff4d326..459f48da43 100644
--- a/src/test/ssl/ssl/root+client.crl
+++ b/src/test/ssl/ssl/root+client.crl
@@ -10,13 +10,14 @@ SBNr2rpYp7Coc3GeCoWPcClgSrABD3Z5GY1YAdLGiXVKaH3CmdJTznhEPagE4z5R
+GrJP3XxJ1OC
-----END X509 CRL-----
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 5f67aba795..a843a21d42 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,8 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext client-long
+CLIENTS := client client-dn client-revoked client_ext client-long \
+ client-revoked-utf8
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
@@ -175,7 +176,7 @@ $(CLIENT_CERTS): ssl/%.crt: ssl/%.csr conf/%.config conf/cas.config ssl/client_c
# The CSRs don't need to persist after a build.
.INTERMEDIATE: $(CERTIFICATES:%=ssl/%.csr)
ssl/%.csr: ssl/%.key conf/%.config
- openssl req -new -key $< -out $@ -config conf/$*.config
+ openssl req -new -utf8 -key $< -out $@ -config conf/$*.config
#
# CA State
@@ -215,8 +216,9 @@ ssl/server.crl: ssl/server-revoked.crt ssl/server_ca.crt | $(server_ca_state_fil
openssl ca -config conf/cas.config -name server_ca -revoke $<
openssl ca -config conf/cas.config -name server_ca -gencrl -out $@
-ssl/client.crl: ssl/client-revoked.crt ssl/client_ca.crt | $(client_ca_state_files)
- openssl ca -config conf/cas.config -name client_ca -revoke $<
+ssl/client.crl: ssl/client-revoked.crt ssl/client-revoked-utf8.crt ssl/client_ca.crt | $(client_ca_state_files)
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked.crt
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked-utf8.crt
openssl ca -config conf/cas.config -name client_ca -gencrl -out $@
#
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fa26212de9..efab2ac194 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -793,4 +793,17 @@ $node->connect_fails(
# ]
);
+# revoked client cert, non-ASCII subject
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
+ . sslkey('client-revoked-utf8.key'),
+ "certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # temporarily(?) skip this check due to timing issue
+# log_like => [
+# qr{Client certificate verification failed at depth 0: certificate revoked},
+# qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number 2315420958437414144, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+# ]
+);
+
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index a43e64c04f..6386a25323 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,8 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key", "client-long.key");
+ "client_ext.key", "client-long.key",
+ "client-revoked-utf8.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
Hi,
On 2022-07-19 15:08:38 -0700, Jacob Champion wrote:
v2 adds escaping to pg_clean_ascii(). My original attempt used
StringInfo allocation, but that didn't play well with guc_malloc(), so
I switched to a two-pass API where the caller allocates. Let me know
if I'm missing something obvious; this way is more verbose than I'd
like...
Hm, that's pretty awkward. Perhaps we can have a better API for
everything but guc.c?
Or alternatively, perhaps we can just make pg_clean_ascii() return NULL
if allocation failed and then guc_strdup() the result in guc.c?
If we end up needing a two phase approach, why use the same function for
both phases? That seems quite awkward.
Greetings,
Andres Freund
On Tue, Jul 19, 2022 at 3:38 PM Andres Freund <andres@anarazel.de> wrote:
Or alternatively, perhaps we can just make pg_clean_ascii() return NULL
if allocation failed and then guc_strdup() the result in guc.c?
The guc_strdup() approach really reduces the amount of code, so that's
what I did in v3. I'm not following why we need to return NULL on
failure, though -- both palloc() and guc_malloc() ERROR on failure, so
is it okay to keep those semantics the same?
If we end up needing a two phase approach, why use the same function for
both phases? That seems quite awkward.
Mostly so the byte counting always agrees between the two phases, no
matter how the implementation evolves. But it's hopefully moot now.
--Jacob
Attachments:
v3-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchtext/x-patch; charset=US-ASCII; name=v3-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchDownload
From dfd76f4dbcf3834371442d593d315797762bbd11 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Tue, 19 Jul 2022 10:48:58 -0700
Subject: [PATCH v3 1/2] pg_clean_ascii(): escape bytes rather than lose them
Rather than replace each unprintable byte with a '?' character, replace
it with a hex escape instead. The API now allocates a copy rather than
modifying the input in place.
---
src/backend/postmaster/postmaster.c | 6 +-----
src/backend/utils/misc/guc.c | 4 ++--
src/common/string.c | 26 +++++++++++++++++---------
src/include/common/string.h | 2 +-
4 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 1c25457526..5e8cd770c0 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2284,11 +2284,7 @@ retry1:
*/
if (strcmp(nameptr, "application_name") == 0)
{
- char *tmp_app_name = pstrdup(valptr);
-
- pg_clean_ascii(tmp_app_name);
-
- port->application_name = tmp_app_name;
+ port->application_name = pg_clean_ascii(valptr);
}
}
offset = valoffset + strlen(valptr) + 1;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0328029d43..60400752e5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12480,7 +12480,7 @@ static bool
check_application_name(char **newval, void **extra, GucSource source)
{
/* Only allow clean ASCII chars in the application name */
- pg_clean_ascii(*newval);
+ *newval = guc_strdup(ERROR, pg_clean_ascii(*newval));
return true;
}
@@ -12496,7 +12496,7 @@ static bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
/* Only allow clean ASCII chars in the cluster name */
- pg_clean_ascii(*newval);
+ *newval = guc_strdup(ERROR, pg_clean_ascii(*newval));
return true;
}
diff --git a/src/common/string.c b/src/common/string.c
index 16940d1fa7..db15324c62 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -22,6 +22,7 @@
#endif
#include "common/string.h"
+#include "lib/stringinfo.h"
/*
@@ -59,9 +60,9 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
/*
- * pg_clean_ascii -- Replace any non-ASCII chars with a '?' char
+ * pg_clean_ascii -- Replace any non-ASCII chars with a "\xXX" string
*
- * Modifies the string passed in which must be '\0'-terminated.
+ * Makes a palloc'd copy of the string passed in, which must be '\0'-terminated.
*
* This function exists specifically to deal with filtering out
* non-ASCII characters in a few places where the client can provide an almost
@@ -73,22 +74,29 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
* In general, this function should NOT be used- instead, consider how to handle
* the string without needing to filter out the non-ASCII characters.
*
- * Ultimately, we'd like to improve the situation to not require stripping out
- * all non-ASCII but perform more intelligent filtering which would allow UTF or
+ * Ultimately, we'd like to improve the situation to not require replacing all
+ * non-ASCII but perform more intelligent filtering which would allow UTF or
* similar, but it's unclear exactly what we should allow, so stick to ASCII only
* for now.
*/
-void
-pg_clean_ascii(char *str)
+char *
+pg_clean_ascii(const char *str)
{
- /* Only allow clean ASCII chars in the string */
- char *p;
+ StringInfoData buf;
+ const char *p;
+
+ initStringInfo(&buf);
for (p = str; *p != '\0'; p++)
{
+ /* Only allow clean ASCII chars in the string */
if (*p < 32 || *p > 126)
- *p = '?';
+ appendStringInfo(&buf, "\\x%02x", (unsigned char) *p);
+ else
+ appendStringInfoChar(&buf, *p);
}
+
+ return buf.data;
}
diff --git a/src/include/common/string.h b/src/include/common/string.h
index cf00fb53cd..d10d0c9cbf 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,7 +24,7 @@ typedef struct PromptInterruptContext
extern bool pg_str_endswith(const char *str, const char *end);
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
-extern void pg_clean_ascii(char *str);
+extern char *pg_clean_ascii(const char *str);
extern int pg_strip_crlf(char *str);
extern bool pg_is_ascii(const char *str);
--
2.25.1
v3-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchtext/x-patch; charset=UTF-8; name=v3-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchDownload
From ad875607a0ff2c9a7d054d3812ed260acbc79f70 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Mon, 18 Jul 2022 15:01:19 -0700
Subject: [PATCH v3 2/2] Don't reflect unescaped cert data to the logs
Commit 3a0e385048 introduced a new path for unauthenticated bytes from
the client certificate to be printed unescaped to the logs. There are a
handful of these already, but it doesn't make sense to keep making the
problem worse. \x-escape any unprintable bytes.
The test case introduces a revoked UTF-8 certificate. This requires the
addition of the `-utf8` flag to `openssl req`. Since the existing
certificates all use an ASCII subset, this won't modify the existing
certificates' subjects if/when they get regenerated; this was verified
experimentally with
$ make sslfiles-clean
$ make sslfiles
Unfortunately the test can't be run in the CI yet due to a test timing
issue; see 55828a6b60.
---
src/backend/libpq/be-secure-openssl.c | 57 ++++++++++---------
src/test/ssl/conf/client-revoked-utf8.config | 13 +++++
src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 | 19 ++++---
src/test/ssl/ssl/client-revoked-utf8.crt | 18 ++++++
src/test/ssl/ssl/client-revoked-utf8.key | 27 +++++++++
src/test/ssl/ssl/client.crl | 19 ++++---
.../ssl/ssl/root+client-crldir/9bb9e3c3.r0 | 19 ++++---
src/test/ssl/ssl/root+client.crl | 19 ++++---
src/test/ssl/sslfiles.mk | 10 ++--
src/test/ssl/t/001_ssltests.pl | 13 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 3 +-
11 files changed, 150 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/conf/client-revoked-utf8.config
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.crt
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 9cec6866a3..5318719acb 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -29,12 +29,14 @@
#include <arpa/inet.h>
#endif
+#include "common/string.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
#include "utils/memutils.h"
/*
@@ -1082,16 +1084,16 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
}
/*
- * Examines the provided certificate name, and if it's too long to log, modifies
- * and truncates it. The return value is NULL if no truncation was needed; it
- * otherwise points into the middle of the input string, and should not be
- * freed.
+ * Examines the provided certificate name, and if it's too long to log or
+ * contains unprintable ASCII, escapes and truncates it. The return value is
+ * always a new palloc'd string. (The input string is still modified in place,
+ * for ease of implementation.)
*/
static char *
-truncate_cert_name(char *name)
+prepare_cert_name(char *name)
{
size_t namelen = strlen(name);
- char *truncated;
+ char *truncated = name;
/*
* Common Names are 64 chars max, so for a common case where the CN is the
@@ -1101,19 +1103,20 @@ truncate_cert_name(char *name)
*/
#define MAXLEN 71
- if (namelen <= MAXLEN)
- return NULL;
-
- /*
- * Keep the end of the name, not the beginning, since the most specific
- * field is likely to give users the most information.
- */
- truncated = name + namelen - MAXLEN;
- truncated[0] = truncated[1] = truncated[2] = '.';
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ namelen = MAXLEN;
+ }
#undef MAXLEN
- return truncated;
+ return pg_clean_ascii(truncated);
}
/*
@@ -1156,21 +1159,24 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
{
char *subject,
*issuer;
- char *sub_truncated,
- *iss_truncated;
+ char *sub_prepared,
+ *iss_prepared;
char *serialno;
ASN1_INTEGER *sn;
BIGNUM *b;
/*
* Get the Subject and Issuer for logging, but don't let maliciously
- * huge certs flood the logs.
+ * huge certs flood the logs, and don't reflect non-ASCII bytes into it
+ * either.
*/
subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
- sub_truncated = truncate_cert_name(subject);
+ sub_prepared = prepare_cert_name(subject);
+ pfree(subject);
issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
- iss_truncated = truncate_cert_name(issuer);
+ iss_prepared = prepare_cert_name(issuer);
+ pfree(issuer);
/*
* Pull the serial number, too, in case a Subject is still ambiguous.
@@ -1183,14 +1189,13 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
appendStringInfoChar(&str, '\n');
appendStringInfo(&str,
_("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
- sub_truncated ? sub_truncated : subject,
- serialno ? serialno : _("unknown"),
- iss_truncated ? iss_truncated : issuer);
+ sub_prepared, serialno ? serialno : _("unknown"),
+ iss_prepared);
BN_free(b);
OPENSSL_free(serialno);
- pfree(issuer);
- pfree(subject);
+ pfree(iss_prepared);
+ pfree(sub_prepared);
}
/* Store our detail message to be logged later. */
diff --git a/src/test/ssl/conf/client-revoked-utf8.config b/src/test/ssl/conf/client-revoked-utf8.config
new file mode 100644
index 0000000000..ff57e6de8a
--- /dev/null
+++ b/src/test/ssl/conf/client-revoked-utf8.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate contains a non-ASCII CN encoded in UTF-8. It is revoked by the
+# client CA.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Οδυσσέας
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.crt b/src/test/ssl/ssl/client-revoked-utf8.crt
new file mode 100644
index 0000000000..de471d1e60
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2DCCAcACCCAiBxgVKBUAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNzE4MjIyODE1WhcNNDkxMjAzMjIyODE1WjAbMRkwFwYDVQQD
+DBDOn860z4XPg8+Dzq3Osc+CMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqRUMYw05cV5kzi
+YQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb7KbPvoZQdXuV
+8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2/SS/UonX5ar5
+58rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBPVRpX3w9TIpBL
+BZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyyjgrny2SrW0W4
+KlhU09y+m5gKL358z8tj599DowIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAE47ns
+wfceztieaRQtoF+gPcCuImOJqaB7jTE6cQ+aoW/q+sUlOh7AD0IZqhS4o0A4O+ny
+MD7kHkpYP+ctHNomsSQRkFTDZ2ZJGcRgxbwMOSvsKcgNOTMGqpXQiP0x0m7QMBGl
+EHeu5MqG/IK/ZlH9aOTvSnHegB6ztct/7wXMeFCflsWLp6wvnv9YpddaaXf95Oms
+9kwbVYkI1wxaBsAO8VGbJw1YtdErgd65qKTJa45xndtm61i1Jeig5asSNQPwjfZ5
+aNHZ9GsSwsc31Q/6iiezbPwgdAi3ih//uB2hznkMhObnqzR3n8Sw9zgL7DdFr2y9
+2R7kJuGq6DvlWFYS
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.key b/src/test/ssl/ssl/client-revoked-utf8.key
new file mode 100644
index 0000000000..86d9697096
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqR
+UMYw05cV5kziYQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb
+7KbPvoZQdXuV8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2
+/SS/UonX5ar558rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBP
+VRpX3w9TIpBLBZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyy
+jgrny2SrW0W4KlhU09y+m5gKL358z8tj599DowIDAQABAoIBAQCpdePmUInb0kDy
+SCzziOyJ+b8YWB4dOy1uCoQVuvcxSWz2jP1GrIgo2SGdzv7VOcSWnDyiLw9olVYO
+cOS3bnQTiMfgGqAgr/Gir4P3wXx2l80nOvcQimj32cJ7VdCNBEtoBopiTCzU5itM
+dsvNydaIuIyhZFdBnL33kfAskIVbqbgNyMCuDvhEMGFh7T193j3cKnvcfoHsSoNK
+65MT53764P404avgH9+C0W41GvXoMY5BUphUiCwi5TKIvalNP9Gu2LI3R4J3tAE1
+QSR/3Jtaunb5izCyi23MZC+mdz9EALeYRmLpXsspbHaPXDpUA67xifCKnX4JUPVf
+Op5XcXjhAoGBAN/Jt7TFPypRtbW200zx6F3RmToWRnvgn5vaNTmiy0ivcHJu4MLS
+o0yiV3VWksf1PCInK19C1yFo6H2lkhKhvipL62MoRkspOUJcMh42DPPf/RDMhYnF
+8MVQ1TlMdg/I4YXGzsrfl93eFERRjWiAt8b58D9OVpWcQNZMPmGztes3AoGBANcr
+n8ZmGZ5JDzbc+N9l1bGJuRT6PvH0rpoKjWOyaVMDedAUnCbfq01j42zXMfQLR1nE
+67Z6oWrBNNdEJFBhTzTZ+ZYXxpJP/FYJQ19dOCTKN1LQ79OAbSsU0NBLkss4a903
+9JQ+zhrEIEaXCTV9sEnp10KrEo6ctuaqMOkVCBj1AoGBAJy/Xb1wq12o/e3ZsQck
+Ke4M8ZaOI7CBFUrE/KLyNBElUU3V+/h6MYdr7nZxvT3xt7z0UpzW5HiyUqYvYrFK
+OTjHFIjPnOzoYwLoMPKYSVpIealal+54hryucatAszE7MzvQlOfk1SrCcs+nj7Sy
+9Aaa6nxtEpiYaZGwtcEZb0LhAoGAJYODjbGLUd9m+ae49CnrAdMDI7cldkW0k0K3
+t+QJHOIEQNT3DIf+c7Wwlu9F1EiLHgmJFv12WwhoUAefVSxCBPLj4tkuU6ACXHWs
++1ljSna/An9O8M75OYOdjFNAupGRrLXuvFHe2SfMgMIgZuUM8TYFw6fTym1kLf8K
+G/kAumkCgYBBD0TXDDAmVCYECSG1Uz35vm9GitbIe++o2ykO2sdB5mPRiMsfVJw4
+bVInkvV6Y2u4ltsNsS/0Y3A2xq/CnYhc7PeIIWFnfoyuHaIM4TIAflpM6qf4lOWE
+8Ot31P8Mt5U0cvCBuKpu0r9by66xX6yqKCqTPMSvbL7MCx5ukGYY7g==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client.crl b/src/test/ssl/ssl/client.crl
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client.crl
+++ b/src/test/ssl/ssl/client.crl
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client.crl b/src/test/ssl/ssl/root+client.crl
index 02eff4d326..459f48da43 100644
--- a/src/test/ssl/ssl/root+client.crl
+++ b/src/test/ssl/ssl/root+client.crl
@@ -10,13 +10,14 @@ SBNr2rpYp7Coc3GeCoWPcClgSrABD3Z5GY1YAdLGiXVKaH3CmdJTznhEPagE4z5R
+GrJP3XxJ1OC
-----END X509 CRL-----
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 5f67aba795..a843a21d42 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,8 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext client-long
+CLIENTS := client client-dn client-revoked client_ext client-long \
+ client-revoked-utf8
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
@@ -175,7 +176,7 @@ $(CLIENT_CERTS): ssl/%.crt: ssl/%.csr conf/%.config conf/cas.config ssl/client_c
# The CSRs don't need to persist after a build.
.INTERMEDIATE: $(CERTIFICATES:%=ssl/%.csr)
ssl/%.csr: ssl/%.key conf/%.config
- openssl req -new -key $< -out $@ -config conf/$*.config
+ openssl req -new -utf8 -key $< -out $@ -config conf/$*.config
#
# CA State
@@ -215,8 +216,9 @@ ssl/server.crl: ssl/server-revoked.crt ssl/server_ca.crt | $(server_ca_state_fil
openssl ca -config conf/cas.config -name server_ca -revoke $<
openssl ca -config conf/cas.config -name server_ca -gencrl -out $@
-ssl/client.crl: ssl/client-revoked.crt ssl/client_ca.crt | $(client_ca_state_files)
- openssl ca -config conf/cas.config -name client_ca -revoke $<
+ssl/client.crl: ssl/client-revoked.crt ssl/client-revoked-utf8.crt ssl/client_ca.crt | $(client_ca_state_files)
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked.crt
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked-utf8.crt
openssl ca -config conf/cas.config -name client_ca -gencrl -out $@
#
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fa26212de9..efab2ac194 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -793,4 +793,17 @@ $node->connect_fails(
# ]
);
+# revoked client cert, non-ASCII subject
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
+ . sslkey('client-revoked-utf8.key'),
+ "certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # temporarily(?) skip this check due to timing issue
+# log_like => [
+# qr{Client certificate verification failed at depth 0: certificate revoked},
+# qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number 2315420958437414144, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+# ]
+);
+
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index a43e64c04f..6386a25323 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,8 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key", "client-long.key");
+ "client_ext.key", "client-long.key",
+ "client-revoked-utf8.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
Jacob Champion <jchampion@timescale.com> writes:
The guc_strdup() approach really reduces the amount of code, so that's
what I did in v3. I'm not following why we need to return NULL on
failure, though -- both palloc() and guc_malloc() ERROR on failure, so
is it okay to keep those semantics the same?
guc_malloc's behavior varies depending on elevel. It's *not*
equivalent to palloc.
regards, tom lane
On Wed, Jul 20, 2022 at 3:15 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
guc_malloc's behavior varies depending on elevel. It's *not*
equivalent to palloc.
Right, sorry -- a better way for me to ask the question:
I'm currently hardcoding an elevel of ERROR on the new guc_strdup()s,
because that seems to be a common case for the check hooks. If that's
okay, is there any reason not to use palloc() semantics for
pg_clean_ascii()? (And if it's not okay, why?)
--Jacob
Jacob Champion <jchampion@timescale.com> writes:
I'm currently hardcoding an elevel of ERROR on the new guc_strdup()s,
because that seems to be a common case for the check hooks.
Really? That's almost certainly NOT okay. As an example, if you
have a problem with a new value loaded from postgresql.conf during
SIGHUP processing, throwing ERROR will cause the postmaster to exit.
I wouldn't be too surprised if there are isolated cases where people
didn't understand what they were doing and wrote that, but that
needs to be fixed not emulated.
regards, tom lane
On Wed, Jul 20, 2022 at 3:42 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Jacob Champion <jchampion@timescale.com> writes:
I'm currently hardcoding an elevel of ERROR on the new guc_strdup()s,
because that seems to be a common case for the check hooks.Really? That's almost certainly NOT okay. As an example, if you
have a problem with a new value loaded from postgresql.conf during
SIGHUP processing, throwing ERROR will cause the postmaster to exit.
v4 attempts to fix this by letting the check hooks pass
MCXT_ALLOC_NO_OOM to pg_clean_ascii(). (It's ignored in the frontend,
which just mallocs.)
I wouldn't be too surprised if there are isolated cases where people
didn't understand what they were doing and wrote that, but that
needs to be fixed not emulated.
I might be missing something, but in guc.c at least it appears to be
the rule and not the exception.
Thanks,
--Jacob
Attachments:
since-v3.diff.txttext/plain; charset=US-ASCII; name=since-v3.diff.txtDownload
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 5318719acb..1394ecaa2b 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1116,7 +1116,7 @@ prepare_cert_name(char *name)
#undef MAXLEN
- return pg_clean_ascii(truncated);
+ return pg_clean_ascii(truncated, 0);
}
/*
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5e8cd770c0..52fb2e52f1 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2284,7 +2284,7 @@ retry1:
*/
if (strcmp(nameptr, "application_name") == 0)
{
- port->application_name = pg_clean_ascii(valptr);
+ port->application_name = pg_clean_ascii(valptr, 0);
}
}
offset = valoffset + strlen(valptr) + 1;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 60400752e5..2f99fe9a6d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12479,9 +12479,18 @@ assign_maintenance_io_concurrency(int newval, void *extra)
static bool
check_application_name(char **newval, void **extra, GucSource source)
{
+ char *clean;
+
/* Only allow clean ASCII chars in the application name */
- *newval = guc_strdup(ERROR, pg_clean_ascii(*newval));
+ clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
+ if (!clean)
+ return false;
+
+ clean = guc_strdup(WARNING, clean);
+ if (!clean)
+ return false;
+ *newval = clean;
return true;
}
@@ -12495,9 +12504,18 @@ assign_application_name(const char *newval, void *extra)
static bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
+ char *clean;
+
/* Only allow clean ASCII chars in the cluster name */
- *newval = guc_strdup(ERROR, pg_clean_ascii(*newval));
+ clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
+ if (!clean)
+ return false;
+
+ clean = guc_strdup(WARNING, clean);
+ if (!clean)
+ return false;
+ *newval = clean;
return true;
}
diff --git a/src/common/string.c b/src/common/string.c
index db15324c62..97b3d45d36 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -62,7 +62,10 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
/*
* pg_clean_ascii -- Replace any non-ASCII chars with a "\xXX" string
*
- * Makes a palloc'd copy of the string passed in, which must be '\0'-terminated.
+ * Makes a newly allocated copy of the string passed in, which must be
+ * '\0'-terminated. In the backend, additional alloc_flags may be provided and
+ * will be passed as-is to palloc_extended(); in the frontend, alloc_flags is
+ * ignored and the copy is malloc'd.
*
* This function exists specifically to deal with filtering out
* non-ASCII characters in a few places where the client can provide an almost
@@ -80,23 +83,46 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
* for now.
*/
char *
-pg_clean_ascii(const char *str)
+pg_clean_ascii(const char *str, int alloc_flags)
{
- StringInfoData buf;
- const char *p;
+ size_t dstlen;
+ char *dst;
+ const char *p;
+ size_t i = 0;
- initStringInfo(&buf);
+ /* Worst case, each byte can become four bytes, plus a null terminator. */
+ dstlen = strlen(str) * 4 + 1;
+
+#ifdef FRONTEND
+ dst = malloc(dstlen);
+#else
+ dst = palloc_extended(dstlen, alloc_flags);
+#endif
+
+ if (!dst)
+ return NULL;
for (p = str; *p != '\0'; p++)
{
+
/* Only allow clean ASCII chars in the string */
if (*p < 32 || *p > 126)
- appendStringInfo(&buf, "\\x%02x", (unsigned char) *p);
+ {
+ Assert(i < (dstlen - 3));
+ snprintf(&dst[i], dstlen - i, "\\x%02x", (unsigned char) *p);
+ i += 4;
+ }
else
- appendStringInfoChar(&buf, *p);
+ {
+ Assert(i < dstlen);
+ dst[i] = *p;
+ i++;
+ }
}
- return buf.data;
+ Assert(i < dstlen);
+ dst[i] = '\0';
+ return dst;
}
diff --git a/src/include/common/string.h b/src/include/common/string.h
index d10d0c9cbf..3d59172151 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,7 +24,7 @@ typedef struct PromptInterruptContext
extern bool pg_str_endswith(const char *str, const char *end);
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
-extern char *pg_clean_ascii(const char *str);
+extern char *pg_clean_ascii(const char *str, int alloc_flags);
extern int pg_strip_crlf(char *str);
extern bool pg_is_ascii(const char *str);
v4-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchtext/x-patch; charset=US-ASCII; name=v4-0001-pg_clean_ascii-escape-bytes-rather-than-lose-them.patchDownload
From 9d0330a7527ac2390b316db0915646137512109b Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Tue, 19 Jul 2022 10:48:58 -0700
Subject: [PATCH v4 1/2] pg_clean_ascii(): escape bytes rather than lose them
Rather than replace each unprintable byte with a '?' character, replace
it with a hex escape instead. The API now allocates a copy rather than
modifying the input in place.
---
src/backend/postmaster/postmaster.c | 6 +---
src/backend/utils/misc/guc.c | 22 ++++++++++--
src/common/string.c | 52 ++++++++++++++++++++++++-----
src/include/common/string.h | 2 +-
4 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 1c25457526..52fb2e52f1 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2284,11 +2284,7 @@ retry1:
*/
if (strcmp(nameptr, "application_name") == 0)
{
- char *tmp_app_name = pstrdup(valptr);
-
- pg_clean_ascii(tmp_app_name);
-
- port->application_name = tmp_app_name;
+ port->application_name = pg_clean_ascii(valptr, 0);
}
}
offset = valoffset + strlen(valptr) + 1;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0328029d43..2f99fe9a6d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12479,9 +12479,18 @@ assign_maintenance_io_concurrency(int newval, void *extra)
static bool
check_application_name(char **newval, void **extra, GucSource source)
{
+ char *clean;
+
/* Only allow clean ASCII chars in the application name */
- pg_clean_ascii(*newval);
+ clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
+ if (!clean)
+ return false;
+
+ clean = guc_strdup(WARNING, clean);
+ if (!clean)
+ return false;
+ *newval = clean;
return true;
}
@@ -12495,9 +12504,18 @@ assign_application_name(const char *newval, void *extra)
static bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
+ char *clean;
+
/* Only allow clean ASCII chars in the cluster name */
- pg_clean_ascii(*newval);
+ clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
+ if (!clean)
+ return false;
+
+ clean = guc_strdup(WARNING, clean);
+ if (!clean)
+ return false;
+ *newval = clean;
return true;
}
diff --git a/src/common/string.c b/src/common/string.c
index 16940d1fa7..97b3d45d36 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -22,6 +22,7 @@
#endif
#include "common/string.h"
+#include "lib/stringinfo.h"
/*
@@ -59,9 +60,12 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
/*
- * pg_clean_ascii -- Replace any non-ASCII chars with a '?' char
+ * pg_clean_ascii -- Replace any non-ASCII chars with a "\xXX" string
*
- * Modifies the string passed in which must be '\0'-terminated.
+ * Makes a newly allocated copy of the string passed in, which must be
+ * '\0'-terminated. In the backend, additional alloc_flags may be provided and
+ * will be passed as-is to palloc_extended(); in the frontend, alloc_flags is
+ * ignored and the copy is malloc'd.
*
* This function exists specifically to deal with filtering out
* non-ASCII characters in a few places where the client can provide an almost
@@ -73,22 +77,52 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
* In general, this function should NOT be used- instead, consider how to handle
* the string without needing to filter out the non-ASCII characters.
*
- * Ultimately, we'd like to improve the situation to not require stripping out
- * all non-ASCII but perform more intelligent filtering which would allow UTF or
+ * Ultimately, we'd like to improve the situation to not require replacing all
+ * non-ASCII but perform more intelligent filtering which would allow UTF or
* similar, but it's unclear exactly what we should allow, so stick to ASCII only
* for now.
*/
-void
-pg_clean_ascii(char *str)
+char *
+pg_clean_ascii(const char *str, int alloc_flags)
{
- /* Only allow clean ASCII chars in the string */
- char *p;
+ size_t dstlen;
+ char *dst;
+ const char *p;
+ size_t i = 0;
+
+ /* Worst case, each byte can become four bytes, plus a null terminator. */
+ dstlen = strlen(str) * 4 + 1;
+
+#ifdef FRONTEND
+ dst = malloc(dstlen);
+#else
+ dst = palloc_extended(dstlen, alloc_flags);
+#endif
+
+ if (!dst)
+ return NULL;
for (p = str; *p != '\0'; p++)
{
+
+ /* Only allow clean ASCII chars in the string */
if (*p < 32 || *p > 126)
- *p = '?';
+ {
+ Assert(i < (dstlen - 3));
+ snprintf(&dst[i], dstlen - i, "\\x%02x", (unsigned char) *p);
+ i += 4;
+ }
+ else
+ {
+ Assert(i < dstlen);
+ dst[i] = *p;
+ i++;
+ }
}
+
+ Assert(i < dstlen);
+ dst[i] = '\0';
+ return dst;
}
diff --git a/src/include/common/string.h b/src/include/common/string.h
index cf00fb53cd..3d59172151 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,7 +24,7 @@ typedef struct PromptInterruptContext
extern bool pg_str_endswith(const char *str, const char *end);
extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
-extern void pg_clean_ascii(char *str);
+extern char *pg_clean_ascii(const char *str, int alloc_flags);
extern int pg_strip_crlf(char *str);
extern bool pg_is_ascii(const char *str);
--
2.25.1
v4-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchtext/x-patch; charset=UTF-8; name=v4-0002-Don-t-reflect-unescaped-cert-data-to-the-logs.patchDownload
From e7cf0ab92f4f8d8405d8ca9735de4d83f126a3f9 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jchampion@timescale.com>
Date: Mon, 18 Jul 2022 15:01:19 -0700
Subject: [PATCH v4 2/2] Don't reflect unescaped cert data to the logs
Commit 3a0e385048 introduced a new path for unauthenticated bytes from
the client certificate to be printed unescaped to the logs. There are a
handful of these already, but it doesn't make sense to keep making the
problem worse. \x-escape any unprintable bytes.
The test case introduces a revoked UTF-8 certificate. This requires the
addition of the `-utf8` flag to `openssl req`. Since the existing
certificates all use an ASCII subset, this won't modify the existing
certificates' subjects if/when they get regenerated; this was verified
experimentally with
$ make sslfiles-clean
$ make sslfiles
Unfortunately the test can't be run in the CI yet due to a test timing
issue; see 55828a6b60.
---
src/backend/libpq/be-secure-openssl.c | 57 ++++++++++---------
src/test/ssl/conf/client-revoked-utf8.config | 13 +++++
src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 | 19 ++++---
src/test/ssl/ssl/client-revoked-utf8.crt | 18 ++++++
src/test/ssl/ssl/client-revoked-utf8.key | 27 +++++++++
src/test/ssl/ssl/client.crl | 19 ++++---
.../ssl/ssl/root+client-crldir/9bb9e3c3.r0 | 19 ++++---
src/test/ssl/ssl/root+client.crl | 19 ++++---
src/test/ssl/sslfiles.mk | 10 ++--
src/test/ssl/t/001_ssltests.pl | 13 +++++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 3 +-
11 files changed, 150 insertions(+), 67 deletions(-)
create mode 100644 src/test/ssl/conf/client-revoked-utf8.config
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.crt
create mode 100644 src/test/ssl/ssl/client-revoked-utf8.key
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 9cec6866a3..1394ecaa2b 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -29,12 +29,14 @@
#include <arpa/inet.h>
#endif
+#include "common/string.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
#include "utils/memutils.h"
/*
@@ -1082,16 +1084,16 @@ dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
}
/*
- * Examines the provided certificate name, and if it's too long to log, modifies
- * and truncates it. The return value is NULL if no truncation was needed; it
- * otherwise points into the middle of the input string, and should not be
- * freed.
+ * Examines the provided certificate name, and if it's too long to log or
+ * contains unprintable ASCII, escapes and truncates it. The return value is
+ * always a new palloc'd string. (The input string is still modified in place,
+ * for ease of implementation.)
*/
static char *
-truncate_cert_name(char *name)
+prepare_cert_name(char *name)
{
size_t namelen = strlen(name);
- char *truncated;
+ char *truncated = name;
/*
* Common Names are 64 chars max, so for a common case where the CN is the
@@ -1101,19 +1103,20 @@ truncate_cert_name(char *name)
*/
#define MAXLEN 71
- if (namelen <= MAXLEN)
- return NULL;
-
- /*
- * Keep the end of the name, not the beginning, since the most specific
- * field is likely to give users the most information.
- */
- truncated = name + namelen - MAXLEN;
- truncated[0] = truncated[1] = truncated[2] = '.';
+ if (namelen > MAXLEN)
+ {
+ /*
+ * Keep the end of the name, not the beginning, since the most specific
+ * field is likely to give users the most information.
+ */
+ truncated = name + namelen - MAXLEN;
+ truncated[0] = truncated[1] = truncated[2] = '.';
+ namelen = MAXLEN;
+ }
#undef MAXLEN
- return truncated;
+ return pg_clean_ascii(truncated, 0);
}
/*
@@ -1156,21 +1159,24 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
{
char *subject,
*issuer;
- char *sub_truncated,
- *iss_truncated;
+ char *sub_prepared,
+ *iss_prepared;
char *serialno;
ASN1_INTEGER *sn;
BIGNUM *b;
/*
* Get the Subject and Issuer for logging, but don't let maliciously
- * huge certs flood the logs.
+ * huge certs flood the logs, and don't reflect non-ASCII bytes into it
+ * either.
*/
subject = X509_NAME_to_cstring(X509_get_subject_name(cert));
- sub_truncated = truncate_cert_name(subject);
+ sub_prepared = prepare_cert_name(subject);
+ pfree(subject);
issuer = X509_NAME_to_cstring(X509_get_issuer_name(cert));
- iss_truncated = truncate_cert_name(issuer);
+ iss_prepared = prepare_cert_name(issuer);
+ pfree(issuer);
/*
* Pull the serial number, too, in case a Subject is still ambiguous.
@@ -1183,14 +1189,13 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
appendStringInfoChar(&str, '\n');
appendStringInfo(&str,
_("Failed certificate data (unverified): subject \"%s\", serial number %s, issuer \"%s\"."),
- sub_truncated ? sub_truncated : subject,
- serialno ? serialno : _("unknown"),
- iss_truncated ? iss_truncated : issuer);
+ sub_prepared, serialno ? serialno : _("unknown"),
+ iss_prepared);
BN_free(b);
OPENSSL_free(serialno);
- pfree(issuer);
- pfree(subject);
+ pfree(iss_prepared);
+ pfree(sub_prepared);
}
/* Store our detail message to be logged later. */
diff --git a/src/test/ssl/conf/client-revoked-utf8.config b/src/test/ssl/conf/client-revoked-utf8.config
new file mode 100644
index 0000000000..ff57e6de8a
--- /dev/null
+++ b/src/test/ssl/conf/client-revoked-utf8.config
@@ -0,0 +1,13 @@
+# An OpenSSL format CSR config file for creating a client certificate.
+#
+# The certificate contains a non-ASCII CN encoded in UTF-8. It is revoked by the
+# client CA.
+
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+CN = Οδυσσέας
+
+# no extensions in client certs
diff --git a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.crt b/src/test/ssl/ssl/client-revoked-utf8.crt
new file mode 100644
index 0000000000..de471d1e60
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2DCCAcACCCAiBxgVKBUAMA0GCSqGSIb3DQEBCwUAMEIxQDA+BgNVBAMMN1Rl
+c3QgQ0EgZm9yIFBvc3RncmVTUUwgU1NMIHJlZ3Jlc3Npb24gdGVzdCBjbGllbnQg
+Y2VydHMwHhcNMjIwNzE4MjIyODE1WhcNNDkxMjAzMjIyODE1WjAbMRkwFwYDVQQD
+DBDOn860z4XPg8+Dzq3Osc+CMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqRUMYw05cV5kzi
+YQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb7KbPvoZQdXuV
+8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2/SS/UonX5ar5
+58rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBPVRpX3w9TIpBL
+BZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyyjgrny2SrW0W4
+KlhU09y+m5gKL358z8tj599DowIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAE47ns
+wfceztieaRQtoF+gPcCuImOJqaB7jTE6cQ+aoW/q+sUlOh7AD0IZqhS4o0A4O+ny
+MD7kHkpYP+ctHNomsSQRkFTDZ2ZJGcRgxbwMOSvsKcgNOTMGqpXQiP0x0m7QMBGl
+EHeu5MqG/IK/ZlH9aOTvSnHegB6ztct/7wXMeFCflsWLp6wvnv9YpddaaXf95Oms
+9kwbVYkI1wxaBsAO8VGbJw1YtdErgd65qKTJa45xndtm61i1Jeig5asSNQPwjfZ5
+aNHZ9GsSwsc31Q/6iiezbPwgdAi3ih//uB2hznkMhObnqzR3n8Sw9zgL7DdFr2y9
+2R7kJuGq6DvlWFYS
+-----END CERTIFICATE-----
diff --git a/src/test/ssl/ssl/client-revoked-utf8.key b/src/test/ssl/ssl/client-revoked-utf8.key
new file mode 100644
index 0000000000..86d9697096
--- /dev/null
+++ b/src/test/ssl/ssl/client-revoked-utf8.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvBiL1mVjTrzZ6sbrvfu746dzh+EEyuJNkCwPeJTtpva2wqqR
+UMYw05cV5kziYQ3UikMP5Yz0FXTeWoahSpJAWeR5XsFx3wOQvRzwi1KWm2CHr/rb
+7KbPvoZQdXuV8UeKrQ6PrEvjoarHAUZuWyUC6EnEAGuiKl5yuax5mkTcK5F8pig2
+/SS/UonX5ar558rOUEaIdyZmXtrO86cm5S5Oz3G2naQB3PPPOhtkoGBHikRHiqBP
+VRpX3w9TIpBLBZbT4MIZ+fCjZ9wXj4aiDUzPglu6/Tfx9sNcxc6Ilz/XHfPuBVyy
+jgrny2SrW0W4KlhU09y+m5gKL358z8tj599DowIDAQABAoIBAQCpdePmUInb0kDy
+SCzziOyJ+b8YWB4dOy1uCoQVuvcxSWz2jP1GrIgo2SGdzv7VOcSWnDyiLw9olVYO
+cOS3bnQTiMfgGqAgr/Gir4P3wXx2l80nOvcQimj32cJ7VdCNBEtoBopiTCzU5itM
+dsvNydaIuIyhZFdBnL33kfAskIVbqbgNyMCuDvhEMGFh7T193j3cKnvcfoHsSoNK
+65MT53764P404avgH9+C0W41GvXoMY5BUphUiCwi5TKIvalNP9Gu2LI3R4J3tAE1
+QSR/3Jtaunb5izCyi23MZC+mdz9EALeYRmLpXsspbHaPXDpUA67xifCKnX4JUPVf
+Op5XcXjhAoGBAN/Jt7TFPypRtbW200zx6F3RmToWRnvgn5vaNTmiy0ivcHJu4MLS
+o0yiV3VWksf1PCInK19C1yFo6H2lkhKhvipL62MoRkspOUJcMh42DPPf/RDMhYnF
+8MVQ1TlMdg/I4YXGzsrfl93eFERRjWiAt8b58D9OVpWcQNZMPmGztes3AoGBANcr
+n8ZmGZ5JDzbc+N9l1bGJuRT6PvH0rpoKjWOyaVMDedAUnCbfq01j42zXMfQLR1nE
+67Z6oWrBNNdEJFBhTzTZ+ZYXxpJP/FYJQ19dOCTKN1LQ79OAbSsU0NBLkss4a903
+9JQ+zhrEIEaXCTV9sEnp10KrEo6ctuaqMOkVCBj1AoGBAJy/Xb1wq12o/e3ZsQck
+Ke4M8ZaOI7CBFUrE/KLyNBElUU3V+/h6MYdr7nZxvT3xt7z0UpzW5HiyUqYvYrFK
+OTjHFIjPnOzoYwLoMPKYSVpIealal+54hryucatAszE7MzvQlOfk1SrCcs+nj7Sy
+9Aaa6nxtEpiYaZGwtcEZb0LhAoGAJYODjbGLUd9m+ae49CnrAdMDI7cldkW0k0K3
+t+QJHOIEQNT3DIf+c7Wwlu9F1EiLHgmJFv12WwhoUAefVSxCBPLj4tkuU6ACXHWs
++1ljSna/An9O8M75OYOdjFNAupGRrLXuvFHe2SfMgMIgZuUM8TYFw6fTym1kLf8K
+G/kAumkCgYBBD0TXDDAmVCYECSG1Uz35vm9GitbIe++o2ykO2sdB5mPRiMsfVJw4
+bVInkvV6Y2u4ltsNsS/0Y3A2xq/CnYhc7PeIIWFnfoyuHaIM4TIAflpM6qf4lOWE
+8Ot31P8Mt5U0cvCBuKpu0r9by66xX6yqKCqTPMSvbL7MCx5ukGYY7g==
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/ssl/client.crl b/src/test/ssl/ssl/client.crl
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/client.crl
+++ b/src/test/ssl/ssl/client.crl
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0 b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
index d93791b88a..f75eb1c0bc 100644
--- a/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
+++ b/src/test/ssl/ssl/root+client-crldir/9bb9e3c3.r0
@@ -1,11 +1,12 @@
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/ssl/root+client.crl b/src/test/ssl/ssl/root+client.crl
index 02eff4d326..459f48da43 100644
--- a/src/test/ssl/ssl/root+client.crl
+++ b/src/test/ssl/ssl/root+client.crl
@@ -10,13 +10,14 @@ SBNr2rpYp7Coc3GeCoWPcClgSrABD3Z5GY1YAdLGiXVKaH3CmdJTznhEPagE4z5R
+GrJP3XxJ1OC
-----END X509 CRL-----
-----BEGIN X509 CRL-----
-MIIBpTCBjjANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
-b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMTAz
-MDMyMjEyMDdaFw00ODA3MTkyMjEyMDdaMBswGQIIICEDAxQSBwEXDTIxMDMwMzIy
-MTIwN1owDQYJKoZIhvcNAQELBQADggEBAC1AJ+HhHg74uXNXdoXLnqDhowdx1y3z
-GKSTPH4iW6jvGp7mMeJhq7cx5kzC+Rqtjui7FjkXbvGd4f6ZVKf30tDD/LvVLxLU
-Up7TmwZjYHbB4NPMyMyqUxtusjYm6HFhbfJwf11TQFwF9yRN3MI4os3J9KTzvhY1
-AvfyEqhBdeygkc1cDduZD+cx7QFYtaeD316q4lz8yfegtxwng8/JDlThu72zdpWV
-w0LuzLei1A9cPXoXfMxRGVEOrDt5z3ArNqdD0bnXTTYqm1IX8ZRHDNeUi4NuFCCu
-tKWT4j9ad4mMcJ6TY/8MiJ14mSJmWSR8115QT69rrQIdDu0sA/sBJX0=
+MIIBwDCBqTANBgkqhkiG9w0BAQsFADBCMUAwPgYDVQQDDDdUZXN0IENBIGZvciBQ
+b3N0Z3JlU1FMIFNTTCByZWdyZXNzaW9uIHRlc3QgY2xpZW50IGNlcnRzFw0yMjA3
+MTgyMjI4MTVaFw00OTEyMDMyMjI4MTVaMDYwGQIIICEDAxQSBwEXDTIyMDcxODIy
+MjgxNVowGQIIICIHGBUoFQAXDTIyMDcxODIyMjgxNVowDQYJKoZIhvcNAQELBQAD
+ggEBAFDH3m9AHpDjkEFjO6svnLJ2bTliGeKZaJW8/RAN4mWvWDhXDQfzqGcFHN2a
+SIL57Xc4PdwTiXuU4QEP4RvWW90LYKdcrcT8uh0AN3i7ShMwcV7I7owzF5+CBuT7
+Ev0MU4QIz0PjXoybXP6b3wHhZbEjYTLYdnYdqjrsAchUpyDQn6fiC0C7FgjCi4HL
+rNm2kMchFpzd6K9e41kxWVp7xCPXgqUK8OrxlW56ObkX8UpBIZzyU6RisJKOZJAn
+/+lwT43yTtU739atdXdSMvGHT9Y7LsrSDz9zgp2/iMTmfctnPcp81J/6jQZEP8kx
+OyPyZz4xy/EShWy+KUklfOoKRo8=
-----END X509 CRL-----
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 5f67aba795..a843a21d42 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -33,7 +33,8 @@ SERVERS := server-cn-and-alt-names \
server-multiple-alt-names \
server-no-names \
server-revoked
-CLIENTS := client client-dn client-revoked client_ext client-long
+CLIENTS := client client-dn client-revoked client_ext client-long \
+ client-revoked-utf8
#
# To add a new non-standard key, add it to SPECIAL_KEYS and then add a recipe
@@ -175,7 +176,7 @@ $(CLIENT_CERTS): ssl/%.crt: ssl/%.csr conf/%.config conf/cas.config ssl/client_c
# The CSRs don't need to persist after a build.
.INTERMEDIATE: $(CERTIFICATES:%=ssl/%.csr)
ssl/%.csr: ssl/%.key conf/%.config
- openssl req -new -key $< -out $@ -config conf/$*.config
+ openssl req -new -utf8 -key $< -out $@ -config conf/$*.config
#
# CA State
@@ -215,8 +216,9 @@ ssl/server.crl: ssl/server-revoked.crt ssl/server_ca.crt | $(server_ca_state_fil
openssl ca -config conf/cas.config -name server_ca -revoke $<
openssl ca -config conf/cas.config -name server_ca -gencrl -out $@
-ssl/client.crl: ssl/client-revoked.crt ssl/client_ca.crt | $(client_ca_state_files)
- openssl ca -config conf/cas.config -name client_ca -revoke $<
+ssl/client.crl: ssl/client-revoked.crt ssl/client-revoked-utf8.crt ssl/client_ca.crt | $(client_ca_state_files)
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked.crt
+ openssl ca -config conf/cas.config -name client_ca -revoke ssl/client-revoked-utf8.crt
openssl ca -config conf/cas.config -name client_ca -gencrl -out $@
#
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fa26212de9..efab2ac194 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -793,4 +793,17 @@ $node->connect_fails(
# ]
);
+# revoked client cert, non-ASCII subject
+$node->connect_fails(
+ "$common_connstr user=ssltestuser sslcert=ssl/client-revoked-utf8.crt "
+ . sslkey('client-revoked-utf8.key'),
+ "certificate authorization fails with revoked UTF-8 client cert with server-side CRL directory",
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # temporarily(?) skip this check due to timing issue
+# log_like => [
+# qr{Client certificate verification failed at depth 0: certificate revoked},
+# qr{Failed certificate data \(unverified\): subject "/CN=\\xce\\x9f\\xce\\xb4\\xcf\\x85\\xcf\\x83\\xcf\\x83\\xce\\xad\\xce\\xb1\\xcf\\x82", serial number 2315420958437414144, issuer "/CN=Test CA for PostgreSQL SSL regression test client certs"},
+# ]
+);
+
done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index a43e64c04f..6386a25323 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -88,7 +88,8 @@ sub init
"client.key", "client-revoked.key",
"client-der.key", "client-encrypted-pem.key",
"client-encrypted-der.key", "client-dn.key",
- "client_ext.key", "client-long.key");
+ "client_ext.key", "client-long.key",
+ "client-revoked-utf8.key");
foreach my $keyfile (@keys)
{
copy("ssl/$keyfile", "$cert_tempdir/$keyfile")
--
2.25.1
On Thu, Jul 21, 2022 at 4:29 PM Jacob Champion <jchampion@timescale.com> wrote:
v4 attempts to fix this by letting the check hooks pass
MCXT_ALLOC_NO_OOM to pg_clean_ascii(). (It's ignored in the frontend,
which just mallocs.)
Ping -- should I add an open item somewhere so this isn't lost?
--Jacob
On Thu, Jul 28, 2022 at 9:19 AM Jacob Champion <jchampion@timescale.com> wrote:
On Thu, Jul 21, 2022 at 4:29 PM Jacob Champion <jchampion@timescale.com> wrote:
v4 attempts to fix this by letting the check hooks pass
MCXT_ALLOC_NO_OOM to pg_clean_ascii(). (It's ignored in the frontend,
which just mallocs.)Ping -- should I add an open item somewhere so this isn't lost?
Trying again. Peter, is this approach acceptable? Should I try something else?
Thanks,
--Jacob
On 09.09.22 00:32, Jacob Champion wrote:
On Thu, Jul 28, 2022 at 9:19 AM Jacob Champion <jchampion@timescale.com> wrote:
On Thu, Jul 21, 2022 at 4:29 PM Jacob Champion <jchampion@timescale.com> wrote:
v4 attempts to fix this by letting the check hooks pass
MCXT_ALLOC_NO_OOM to pg_clean_ascii(). (It's ignored in the frontend,
which just mallocs.)Ping -- should I add an open item somewhere so this isn't lost?
Trying again. Peter, is this approach acceptable? Should I try something else?
This looks fine to me. Committed.
On Tue, Sep 13, 2022 at 7:11 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
This looks fine to me. Committed.
Thanks!
--Jacob
Hi,
On Tue, Sep 13, 2022 at 11:11 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
On 09.09.22 00:32, Jacob Champion wrote:
On Thu, Jul 28, 2022 at 9:19 AM Jacob Champion <jchampion@timescale.com> wrote:
On Thu, Jul 21, 2022 at 4:29 PM Jacob Champion <jchampion@timescale.com> wrote:
v4 attempts to fix this by letting the check hooks pass
MCXT_ALLOC_NO_OOM to pg_clean_ascii(). (It's ignored in the frontend,
which just mallocs.)Ping -- should I add an open item somewhere so this isn't lost?
Trying again. Peter, is this approach acceptable? Should I try something else?
This looks fine to me. Committed.
While looking at the recent changes for check_cluster_name() I found
this thread. Regarding the following change made by the commit
45b1a67a0fc, there is possibly small memory leak:
static bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
+ char *clean;
+
/* Only allow clean ASCII chars in the cluster name */
- pg_clean_ascii(*newval);
+ clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
+ if (!clean)
+ return false;
+
+ clean = guc_strdup(WARNING, clean);
+ if (!clean)
+ return false;
+ *newval = clean;
return true;
}
pg_clean_ascii() does palloc_extended() to allocate memory in
Postmaster context for the new characters and the clean is then
replaced with the new memory allocated by guc_strdup(). No-one
references the memory allocated by pg_clean_ascii() and it lasts for
postmaster lifetime. Valgrind memcheck also shows:
1 bytes in 1 blocks are definitely lost in loss record 4 of 70
at 0xCD2A16: palloc_extended (mcxt.c:1239)
by 0xD09437: pg_clean_ascii (string.c:99)
by 0x7A5CF3: check_cluster_name (variable.c:1061)
by 0xCAF7CD: call_string_check_hook (guc.c:6365)
by 0xCAA724: InitializeOneGUCOption (guc.c:1439)
by 0xCAA0ED: InitializeGUCOptions (guc.c:1268)
by 0x99B245: PostmasterMain (postmaster.c:691)
by 0x858896: main (main.c:197)
I think we can fix it by the attached patch but I'd like to discuss
whether it's worth fixing it.
Regards,
--
Masahiko Sawada
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Attachments:
fix_memory_leak.patchapplication/x-patch; name=fix_memory_leak.patchDownload
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index c795cb7a29..e555fb3150 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1025,17 +1025,22 @@ bool
check_application_name(char **newval, void **extra, GucSource source)
{
char *clean;
+ char *ret;
/* Only allow clean ASCII chars in the application name */
clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
if (!clean)
return false;
- clean = guc_strdup(WARNING, clean);
- if (!clean)
+ ret = guc_strdup(WARNING, clean);
+ if (!ret)
+ {
+ pfree(clean);
return false;
+ }
- *newval = clean;
+ pfree(clean);
+ *newval = ret;
return true;
}
@@ -1056,17 +1061,22 @@ bool
check_cluster_name(char **newval, void **extra, GucSource source)
{
char *clean;
+ char *ret;
/* Only allow clean ASCII chars in the cluster name */
clean = pg_clean_ascii(*newval, MCXT_ALLOC_NO_OOM);
if (!clean)
return false;
- clean = guc_strdup(WARNING, clean);
- if (!clean)
+ ret = guc_strdup(WARNING, clean);
+ if (!ret)
+ {
+ pfree(clean);
return false;
+ }
- *newval = clean;
+ pfree(clean);
+ *newval = ret;
return true;
}
On Tue, Sep 27, 2022 at 1:51 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I think we can fix it by the attached patch but I'd like to discuss
whether it's worth fixing it.
Whoops. So every time it's changed, we leak a little postmaster memory?
Your patch looks good to me and I see no reason not to fix it.
Thanks,
--Jacob
On Wed, Sep 28, 2022 at 4:44 AM Jacob Champion <jchampion@timescale.com> wrote:
On Tue, Sep 27, 2022 at 1:51 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
I think we can fix it by the attached patch but I'd like to discuss
whether it's worth fixing it.Whoops. So every time it's changed, we leak a little postmaster memory?
No. Since cluster_name is PGC_POSTMATER, we leak a little postmaster
memory only once when starting up. application_name is PGC_USERSET but
since we normally allocate memory in PortalMemoryContext we eventually
can free it. Since check_cluster_name and check_application_name are
similar, I changed both for consistency.
Regards,
--
Masahiko Sawada
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Tue, Sep 27, 2022 at 6:14 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
No. Since cluster_name is PGC_POSTMATER, we leak a little postmaster
memory only once when starting up. application_name is PGC_USERSET but
since we normally allocate memory in PortalMemoryContext we eventually
can free it.
Oh, I see; thank you for the correction. And even if someone put an
application_name into their postgresql.conf, and then changed it a
bunch of times, we'd free the leaked memory from the config_cxt that's
created in ProcessConfigFile().
Is there a reason we don't provide a similar temporary context during
InitializeGUCOptions()? Naively it seems like that would suppress any
future one-time leaks, and maybe cut down on some Valgrind noise. Then
again, maybe there's just not that much demand for pallocs during GUC
hooks.
Thanks,
--Jacob
On Thu, Sep 29, 2022 at 1:43 AM Jacob Champion <jchampion@timescale.com> wrote:
On Tue, Sep 27, 2022 at 6:14 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
No. Since cluster_name is PGC_POSTMATER, we leak a little postmaster
memory only once when starting up. application_name is PGC_USERSET but
since we normally allocate memory in PortalMemoryContext we eventually
can free it.Oh, I see; thank you for the correction. And even if someone put an
application_name into their postgresql.conf, and then changed it a
bunch of times, we'd free the leaked memory from the config_cxt that's
created in ProcessConfigFile().
Right.
Is there a reason we don't provide a similar temporary context during
InitializeGUCOptions()? Naively it seems like that would suppress any
future one-time leaks, and maybe cut down on some Valgrind noise. Then
again, maybe there's just not that much demand for pallocs during GUC
hooks.
While this seems a future-proof idea, I wonder if it might be overkill
since we don't need to worry about accumulation of leaked memory in
this case. Given that only check_cluter_name is the case where we
found a small memory leak, I think it's adequate to fix it.
Fixing this issue suppresses the valgrind's complaint but since the
boot value of cluster_name is "" the memory leak we can avoid is only
1 byte.
Regards,
--
Masahiko Sawada
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On 29.09.22 06:52, Masahiko Sawada wrote:
While this seems a future-proof idea, I wonder if it might be overkill
since we don't need to worry about accumulation of leaked memory in
this case. Given that only check_cluter_name is the case where we
found a small memory leak, I think it's adequate to fix it.Fixing this issue suppresses the valgrind's complaint but since the
boot value of cluster_name is "" the memory leak we can avoid is only
1 byte.
I have committed this. I think it's better to keep the code locally
robust and not to have to rely on complex analysis of how GUC memory
management works.
On Sat, Oct 1, 2022 at 7:53 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
On 29.09.22 06:52, Masahiko Sawada wrote:
While this seems a future-proof idea, I wonder if it might be overkill
since we don't need to worry about accumulation of leaked memory in
this case. Given that only check_cluter_name is the case where we
found a small memory leak, I think it's adequate to fix it.Fixing this issue suppresses the valgrind's complaint but since the
boot value of cluster_name is "" the memory leak we can avoid is only
1 byte.I have committed this. I think it's better to keep the code locally
robust and not to have to rely on complex analysis of how GUC memory
management works.
Thanks! Agreed.
Regards,
--
Masahiko Sawada
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Hi,
I think that 9d58bf
<https://github.com/postgres/postgres/commit/a9d58bfe8a3ae2254e1553ab76974feeaafa0133>,
left a tiny oversight.
guc_strdup uses strdup, so must be cleaned by free not pfree.
IMO, can be used once free call too.
regards,
Ranier Vilela
Attachments:
simplifies_and_use_free_variable.patchapplication/octet-stream; name=simplifies_and_use_free_variable.patchDownload
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index e555fb3150..8cbe8f2f96 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1033,13 +1033,11 @@ check_application_name(char **newval, void **extra, GucSource source)
return false;
ret = guc_strdup(WARNING, clean);
+ free(clean);
+
if (!ret)
- {
- pfree(clean);
return false;
- }
- pfree(clean);
*newval = ret;
return true;
}
@@ -1069,13 +1067,11 @@ check_cluster_name(char **newval, void **extra, GucSource source)
return false;
ret = guc_strdup(WARNING, clean);
+ free(clean);
+
if (!ret)
- {
- pfree(clean);
return false;
- }
- pfree(clean);
*newval = ret;
return true;
}Import Notes
Resolved by subject fallback
Em qua., 5 de out. de 2022 às 09:19, Ranier Vilela <ranier.vf@gmail.com>
escreveu:
Hi,
I think that 9d58bf
<https://github.com/postgres/postgres/commit/a9d58bfe8a3ae2254e1553ab76974feeaafa0133>,
left a tiny oversight.guc_strdup uses strdup, so must be cleaned by free not pfree.
IMO, can be used once free call too.
Sorry, my fault.
Please ignore this.
regards,
Ranier Vilela