From 0fa54f2517c5958f054eff99a30ebd9b41a28bac Mon Sep 17 00:00:00 2001 From: David Zhang Date: Tue, 6 Aug 2024 15:35:45 -0700 Subject: [PATCH 1/2] v1 WIP OCSP support certificate status check --- src/backend/libpq/be-secure-openssl.c | 87 ++++++++++ src/backend/libpq/be-secure.c | 1 + src/backend/utils/misc/guc_tables.c | 10 ++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/libpq/libpq.h | 1 + src/interfaces/libpq/fe-connect.c | 37 +++++ src/interfaces/libpq/fe-secure-openssl.c | 153 ++++++++++++++++++ src/interfaces/libpq/libpq-int.h | 1 + 8 files changed, 291 insertions(+) diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 7e056abd5a..efd242591c 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -50,6 +50,7 @@ #include #endif #include +#include /* default init hook can be overridden by a shared library */ @@ -87,6 +88,8 @@ 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); +static int ocsp_stapling_cb(SSL *ssl); + /* for passing data back from verify_cb() */ static const char *cert_errdetail; @@ -453,6 +456,9 @@ be_tls_open_server(Port *port) return -1; } + /* set up OCSP stapling callback */ + SSL_CTX_set_tlsext_status_cb(SSL_context, ocsp_stapling_cb); + /* set up debugging/info callback */ SSL_CTX_set_info_callback(SSL_context, info_cb); @@ -1768,3 +1774,84 @@ default_openssl_tls_init(SSL_CTX *context, bool isServerStart) SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb); } } + +/* + * OCSP stapling callback function for the server side. + * + * This function is responsible for providing the OCSP stapling response to + * the client during the SSL/TLS handshake, based on the client's request. + * + * Parameters: + * - ssl: SSL/TLS connection object. + * + * Returns: + * - SSL_TLSEXT_ERR_OK: OCSP stapling response successfully provided. + * - SSL_TLSEXT_ERR_NOACK: OCSP stapling response not provided due to errors. + * + * Steps: + * 1. Check if the server-side OCSP stapling feature is enabled. + * 2. Read OCSP response from file if client requested OCSP stapling. + * 3. Set the OCSP stapling response in the SSL/TLS connection. + */ +static int ocsp_stapling_cb(SSL *ssl) +{ + int resp_len = -1; + BIO *bio = NULL; + OCSP_RESPONSE *resp = NULL; + unsigned char *rspder = NULL; + + /* return, if ssl_ocsp_file not enabled on server */ + if (ssl_ocsp_file == NULL) + { + ereport(WARNING, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not find ssl_ocsp_file"))); + return SSL_TLSEXT_ERR_NOACK; + } + + /* whether the client requested OCSP stapling */ + if (SSL_get_tlsext_status_type(ssl) == TLSEXT_STATUSTYPE_ocsp) + { + bio = BIO_new_file(ssl_ocsp_file, "r"); + if (bio == NULL) + { + ereport(WARNING, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not read ssl_ocsp_file"))); + return SSL_TLSEXT_ERR_NOACK; + } + + resp = d2i_OCSP_RESPONSE_bio(bio, NULL); + BIO_free(bio); + if (resp == NULL) + { + ereport(WARNING, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not convert OCSP response to intarnal format"))); + return SSL_TLSEXT_ERR_NOACK; + } + + resp_len = i2d_OCSP_RESPONSE(resp, &rspder); + OCSP_RESPONSE_free(resp); + if (resp_len <= 0) + { + ereport(WARNING, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not convert OCSP response to der format"))); + return SSL_TLSEXT_ERR_NOACK; + } + + /* set up the OCSP stapling response */ + if (SSL_set_tlsext_status_ocsp_resp(ssl, rspder, resp_len) != 1) + { + ereport(WARNING, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not set up OCSP stapling response"))); + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; + } + + return SSL_TLSEXT_ERR_NOACK; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index ef20ea755b..d80e52f26d 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -35,6 +35,7 @@ char *ssl_library; char *ssl_cert_file; +char *ssl_ocsp_file; char *ssl_key_file; char *ssl_ca_file; char *ssl_crl_file; diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index c0a52cdcc3..4b499dd70b 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -4616,6 +4616,16 @@ struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"ssl_ocsp_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the SSL certificate OCSP stapling file."), + NULL + }, + &ssl_ocsp_file, + "", + NULL, NULL, NULL + }, + { {"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY, gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 9ec9f97e92..7454564bd7 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -107,6 +107,7 @@ #ssl = off #ssl_ca_file = '' #ssl_cert_file = 'server.crt' +#ssl_ocsp_file = 'server.res' #ssl_crl_file = '' #ssl_crl_dir = '' #ssl_key_file = 'server.key' diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 142c98462e..67be66d711 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -88,6 +88,7 @@ extern bool pq_check_connection(void); */ extern PGDLLIMPORT char *ssl_library; extern PGDLLIMPORT char *ssl_cert_file; +extern PGDLLIMPORT char *ssl_ocsp_file; extern PGDLLIMPORT char *ssl_key_file; extern PGDLLIMPORT char *ssl_ca_file; extern PGDLLIMPORT char *ssl_crl_file; diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 360d9a4547..35b9e49978 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -329,6 +329,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "SSL-Maximum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */ offsetof(struct pg_conn, ssl_max_protocol_version)}, + {"sslocspstapling", "PGSSLOCSPSTAPLING", "0", NULL, + "SSL-OCSP-Stapling", "", 1, + offsetof(struct pg_conn, sslocspstapling)}, + /* * As with SSL, all GSS options are exposed even in builds that don't have * support. @@ -449,6 +453,7 @@ static void pgpassfileWarning(PGconn *conn); static void default_threadlock(int acquire); static bool sslVerifyProtocolVersion(const char *version); static bool sslVerifyProtocolRange(const char *min, const char *max); +static bool sslVerifyOcspStapling(const char *stapling); /* global variable because fe-auth.c needs to access it */ @@ -1686,6 +1691,18 @@ pqConnectOptions2(PGconn *conn) return false; } + /* + * Validate sslocspstapling settings + */ + if (!sslVerifyOcspStapling(conn->sslocspstapling)) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "sslocspstapling", + conn->sslocspstapling); + return false; + } + /* * validate sslcertmode option */ @@ -4704,6 +4721,7 @@ freePGconn(PGconn *conn) free(conn->require_auth); free(conn->ssl_min_protocol_version); free(conn->ssl_max_protocol_version); + free(conn->sslocspstapling); free(conn->gssencmode); free(conn->krbsrvname); free(conn->gsslib); @@ -7656,6 +7674,25 @@ sslVerifyProtocolRange(const char *min, const char *max) return true; } +/* + * Check sslocspstapling is set properly + */ +static bool +sslVerifyOcspStapling(const char *stapling) +{ + /* + * An empty string or a NULL value is considered valid + */ + if (!stapling || strlen(stapling) == 0) + return true; + + if (pg_strcasecmp(stapling, "0") == 0 || + pg_strcasecmp(stapling, "1") == 0) + return true; + + /* anything else is wrong */ + return false; +} /* * Obtain user's home directory, return in given buffer diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c index b6fffd7b9b..5a6fa404b7 100644 --- a/src/interfaces/libpq/fe-secure-openssl.c +++ b/src/interfaces/libpq/fe-secure-openssl.c @@ -62,6 +62,7 @@ #include #endif #include +#include static int verify_cb(int ok, X509_STORE_CTX *ctx); @@ -95,6 +96,9 @@ static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER; static PQsslKeyPassHook_OpenSSL_type PQsslKeyPassHook = NULL; static int ssl_protocol_version_to_openssl(const char *protocol); +static int ocsp_response_check_cb(SSL *ssl); +#define OCSP_CERT_STATUS_OK 1 +#define OCSP_CERT_STATUS_NOK (-1) /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ @@ -1184,6 +1188,36 @@ initialize_SSL(PGconn *conn) have_cert = true; } + /* Enable OCSP stapling for certificate status check */ + if (conn->sslocspstapling && + strlen(conn->sslocspstapling) != 0 && + (strcmp(conn->sslocspstapling, "1") == 0)) + { + /* set up certificate status request */ + if (SSL_CTX_set_tlsext_status_type(SSL_context, + TLSEXT_STATUSTYPE_ocsp) != 1) + { + char *err = SSLerrmessage(ERR_get_error()); + libpq_append_conn_error(conn, + "could not set up certificate status request: %s", err); + SSLerrfree(err); + SSL_CTX_free(SSL_context); + return -1; + } + + /* set up OCSP response callback */ + if (SSL_CTX_set_tlsext_status_cb(SSL_context, + ocsp_response_check_cb) != 1) + { + char *err = SSLerrmessage(ERR_get_error()); + libpq_append_conn_error(conn, + "could not set up OCSP response callback: %s", err); + SSLerrfree(err); + SSL_CTX_free(SSL_context); + return -1; + } + } + /* * The SSL context is now loaded with the correct root and client * certificates. Create a connection-specific SSL object. The private key @@ -2146,3 +2180,122 @@ ssl_protocol_version_to_openssl(const char *protocol) return -1; } + +/* + * Verify OCSP stapling response in the context of an SSL/TLS connection. + * + * This function checks whether the server provided an OCSP response + * as part of the TLS handshake, verifies its integrity, and checks the + * revocation status of the presented certificates. + * + * Parameters: + * - ssl: SSL/TLS connection object. + * + * Returns: + * - OCSP_CERT_STATUS_OK: OCSP stapling was not requested or status is OK. + * - OCSP_CERT_STATUS_NOK: OCSP verification failed or status is not OK. + * + * Steps: + * 1. Retrieve OCSP response during handshake. + * 2. Perform basic OCSP response verification. + * 3. Verify the signature and issuer of OCSP response. + * 4. Valid the certificate status in OCSP response. + * + * Cleanup: + * - Free allocated memory for the OCSP response and basic response. + */ +static int ocsp_response_check_cb(SSL *ssl) +{ + long resp_len = 0; + const unsigned char *resp_data; + OCSP_RESPONSE *ocsp_resp = NULL; + OCSP_BASICRESP *basic_resp = NULL; + X509_STORE *trusted_store = NULL; + int ocsp_status = OCSP_CERT_STATUS_NOK; + + OCSP_CERTID *cert_id; + X509 *peer_cert = NULL; + X509 *peer_cert_issuer = NULL; + + int cert_status; + int rev_reason; + ASN1_GENERALIZEDTIME *rev_time; + ASN1_GENERALIZEDTIME *this_update; + ASN1_GENERALIZEDTIME *next_update; + + /* + * step-1: retrieve OCSP response + */ + /* check if requested a certificate status */ + if (SSL_get_tlsext_status_type(ssl) != TLSEXT_STATUSTYPE_ocsp) + return OCSP_CERT_STATUS_OK; /* didn't request this OCSP status */ + + /* check if got a correct OCSP response */ + resp_len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp_data); + if (resp_data == NULL) + goto cleanup; /* no proper OCSP response found */ + + /* convert the OCSP response to internal format */ + ocsp_resp = d2i_OCSP_RESPONSE(NULL, &resp_data, resp_len); + if (ocsp_resp == NULL) + goto cleanup; /* failed to convert this OCSP response */ + + /* + * step-2: verify the basic of OCSP response + */ + if (OCSP_response_status(ocsp_resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) + goto cleanup; /* OCSP response not successful */ + + /* get OCSP basic response structure */ + basic_resp = OCSP_response_get1_basic(ocsp_resp); + if (basic_resp == NULL) + goto cleanup; /* failed to get basic OCSP response */ + + /* + * step-3: verify OCSP response is proper signed by a trusted signer + */ + /* get local trusted certificate store */ + trusted_store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(ssl)); + if (trusted_store == NULL) + goto cleanup; + + /* verify ocsp response signature and the issuer */ + if (OCSP_basic_verify(basic_resp, NULL, trusted_store, 0) != 1) + goto cleanup; /* basic verification failed */ + + /* + * step-4: valid the certificate status inside OCSP response + */ + /* get certificate and issuer to construct OCSP status lookup id */ + peer_cert = SSL_get0_peer_certificate(ssl); + peer_cert_issuer = sk_X509_value(SSL_get0_verified_chain(ssl), 1); + + cert_id = OCSP_cert_to_id(NULL, peer_cert, peer_cert_issuer); + if (cert_id == NULL) + goto cleanup; + + /* get certificate status information */ + if (OCSP_resp_find_status(basic_resp, cert_id, + &cert_status, &rev_reason, &rev_time, &this_update, &next_update) != 1) + goto cleanup; + + /* check whether or not the certificate has been revoked */ + if (cert_status == V_OCSP_CERTSTATUS_GOOD) + { + if (OCSP_check_validity(this_update, next_update, 0, -1) == 1) + ocsp_status = OCSP_CERT_STATUS_OK; + else + goto cleanup; + } + else + goto cleanup; + +cleanup: + if (ocsp_resp != NULL) + OCSP_RESPONSE_free(ocsp_resp); + + if (basic_resp != NULL) + OCSP_BASICRESP_free(basic_resp); + + return ocsp_status; +} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index f36d76bf3f..b56433fed0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -412,6 +412,7 @@ struct pg_conn char *gssdelegation; /* Try to delegate GSS credentials? (0 or 1) */ char *ssl_min_protocol_version; /* minimum TLS protocol version */ char *ssl_max_protocol_version; /* maximum TLS protocol version */ + char *sslocspstapling; /* request ocsp stapling from server */ char *target_session_attrs; /* desired session properties */ char *require_auth; /* name of the expected auth method */ char *load_balance_hosts; /* load balance over hosts */ -- 2.34.1