On Thu, Jul 7, 2022 at 2:50 AM Peter Eisentraut <peter.eisentr...@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
commit 7489e52168b76df3a84c68d79fb22651a05a0c05 Author: Jacob Champion <jchamp...@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();
From 6e516c02a726bf7170a74c0cc2a762835bef0256 Mon Sep 17 00:00:00 2001 From: Jacob Champion <pchamp...@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
From 601b4f23eb8a3d304faee94124ca8f5153fb7fb6 Mon Sep 17 00:00:00 2001 From: Jacob Champion <jchamp...@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