diff options
Diffstat (limited to 'src/backend/libpq/be-secure.c')
-rw-r--r-- | src/backend/libpq/be-secure.c | 748 |
1 files changed, 0 insertions, 748 deletions
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c deleted file mode 100644 index 5f63e61c5d4..00000000000 --- a/src/backend/libpq/be-secure.c +++ /dev/null @@ -1,748 +0,0 @@ -/*------------------------------------------------------------------------- - * - * be-connect.c - * functions related to setting up a secure connection to the frontend. - * Secure connections are expected to provide confidentiality, - * message integrity and endpoint authentication. - * - * - * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.10 2002/06/20 20:29:28 momjian Exp $ - * - * Since the server static private key ($DataDir/server.key) - * will normally be stored unencrypted so that the database - * backend can restart automatically, it is important that - * we select an algorithm that continues to provide confidentiality - * even if the attacker has the server's private key. Empheral - * DH (EDH) keys provide this, and in fact provide Perfect Forward - * Secrecy (PFS) except for situations where the session can - * be hijacked during a periodic handshake/renegotiation. - * Even that backdoor can be closed if client certificates - * are used (since the imposter will be unable to successfully - * complete renegotiation). - * - * N.B., the static private key should still be protected to - * the largest extent possible, to minimize the risk of - * impersonations. - * - * Another benefit of EDH is that it allows the backend and - * clients to use DSA keys. DSA keys can only provide digital - * signatures, not encryption, and are often acceptable in - * jurisdictions where RSA keys are unacceptable. - * - * The downside to EDH is that it makes it impossible to - * use ssldump(1) if there's a problem establishing an SSL - * session. In this case you'll need to temporarily disable - * EDH by commenting out the callback. - * - * ... - * - * Because the risk of cryptanalysis increases as large - * amounts of data are sent with the same session key, the - * session keys are periodically renegotiated. - * - * PATCH LEVEL - * milestone 1: fix basic coding errors - * [*] existing SSL code pulled out of existing files. - * [*] SSL_get_error() after SSL_read() and SSL_write(), - * SSL_shutdown(), default to TLSv1. - * - * milestone 2: provide endpoint authentication (server) - * [*] client verifies server cert - * [*] client verifies server hostname - * - * milestone 3: improve confidentially, support perfect forward secrecy - * [ ] use 'random' file, read from '/dev/urandom?' - * [*] emphermal DH keys, default values - * [*] periodic renegotiation - * [*] private key permissions - * - * milestone 4: provide endpoint authentication (client) - * [*] server verifies client certificates - * - * milestone 5: provide informational callbacks - * [*] provide informational callbacks - * - * other changes - * [ ] tcp-wrappers - * [ ] more informative psql - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <signal.h> -#include <fcntl.h> -#include <errno.h> -#include <ctype.h> - -#include "libpq/libpq.h" -#include "libpq/pqsignal.h" -#include "miscadmin.h" - -#ifdef WIN32 -#include "win32.h" -#else -#include <sys/socket.h> -#include <unistd.h> -#include <netdb.h> -#include <netinet/in.h> -#ifdef HAVE_NETINET_TCP_H -#include <netinet/tcp.h> -#endif -#include <arpa/inet.h> -#endif - - -#ifndef HAVE_STRDUP -#include "strdup.h" -#endif - -#ifdef USE_SSL -#include <openssl/ssl.h> -#include <openssl/e_os.h> -#include <openssl/dh.h> -#endif - -extern void ExitPostmaster(int); -extern void postmaster_error(const char *fmt,...); - -int secure_initialize(void); -void secure_destroy(void); -int secure_open_server(Port *); -void secure_close(Port *); -ssize_t secure_read(Port *, void *ptr, size_t len); -ssize_t secure_write(Port *, const void *ptr, size_t len); - -#ifdef USE_SSL -static DH *load_dh_file(int keylength); -static DH *load_dh_buffer(const char *, size_t); -static DH *tmp_dh_cb(SSL *s, int is_export, int keylength); -static int verify_cb(int, X509_STORE_CTX *); -static void info_cb(SSL *ssl, int type, int args); -static int initialize_SSL(void); -static void destroy_SSL(void); -static int open_server_SSL(Port *); -static void close_SSL(Port *); -static const char *SSLerrmessage(void); -#endif - -#ifdef USE_SSL -/* - * How much data can be sent across a secure connection - * (total in both directions) before we require renegotiation. - */ -#define RENEGOTIATION_LIMIT (64 * 1024) -#define CA_PATH NULL -static SSL_CTX *SSL_context = NULL; -#endif - -/* ------------------------------------------------------------ */ -/* Hardcoded values */ -/* ------------------------------------------------------------ */ - -/* - * Hardcoded DH parameters, used in empheral DH keying. - * As discussed above, EDH protects the confidentiality of - * sessions even if the static private key is compromised, - * so we are *highly* motivated to ensure that we can use - * EDH even if the DBA... or an attacker... deletes the - * $DataDir/dh*.pem files. - * - * We could refuse SSL connections unless a good DH parameter - * file exists, but some clients may quietly renegotiate an - * unsecured connection without fully informing the user. - * Very uncool. - * - * Alternately, the backend could attempt to load these files - * on startup if SSL is enabled - and refuse to start if any - * do not exist - but this would tend to piss off DBAs. - * - * If you want to create your own hardcoded DH parameters - * for fun and profit, review "Assigned Number for SKIP - * Protocols" (http://www.skip-vpn.org/spec/numbers.html) - * for suggestions. - */ -static const char file_dh512[] = -"-----BEGIN DH PARAMETERS-----\n\ -MEYCQQD1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak\n\ -XUGfnHy9iUsiGSa6q6Jew1XpKgVfAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh1024[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY\n\ -jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6\n\ -ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh2048[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\ -89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\ -T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\ -zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\ -Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\ -CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\ ------END DH PARAMETERS-----\n"; - -static const char file_dh4096[] = -"-----BEGIN DH PARAMETERS-----\n\ -MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ\n\ -l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt\n\ -Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS\n\ -Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98\n\ -VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc\n\ -alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM\n\ -sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9\n\ -ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte\n\ -OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH\n\ -AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n\ -KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\ ------END DH PARAMETERS-----\n"; - -/* ------------------------------------------------------------ */ -/* Procedures common to all secure sessions */ -/* ------------------------------------------------------------ */ - -/* - * Initialize global context - */ -int -secure_initialize (void) -{ - int r = 0; - -#ifdef USE_SSL - r = initialize_SSL(); -#endif - - return r; -} - -/* - * Destroy global context - */ -void -secure_destroy (void) -{ -#ifdef USE_SSL - destroy_SSL(); -#endif -} - -/* - * Attempt to negotiate secure session. - */ -int -secure_open_server (Port *port) -{ - int r = 0; - -#ifdef USE_SSL - r = open_server_SSL(port); -#endif - - return r; -} - -/* - * Close secure session. - */ -void -secure_close (Port *port) -{ -#ifdef USE_SSL - if (port->ssl) - close_SSL(port); -#endif -} - -/* - * Read data from a secure connection. - */ -ssize_t -secure_read (Port *port, void *ptr, size_t len) -{ - ssize_t n; - -#ifdef USE_SSL - if (port->ssl) - { - if (port->count > RENEGOTIATION_LIMIT) - { - SSL_renegotiate(port->ssl); - port->count = 0; - } - - n = SSL_read(port->ssl, ptr, len); - switch (SSL_get_error(port->ssl, n)) - { - case SSL_ERROR_NONE: - port->count += n; - break; - case SSL_ERROR_WANT_READ: - break; - case SSL_ERROR_SYSCALL: - errno = get_last_socket_error(); - elog(ERROR, "SSL SYSCALL error: %s", strerror(errno)); - break; - case SSL_ERROR_SSL: - elog(ERROR, "SSL error: %s", SSLerrmessage()); - /* fall through */ - case SSL_ERROR_ZERO_RETURN: - secure_close(port); - errno = ECONNRESET; - n = -1; - break; - } - } - else -#endif - n = recv(port->sock, ptr, len, 0); - - return n; -} - -/* - * Write data to a secure connection. - */ -ssize_t -secure_write (Port *port, const void *ptr, size_t len) -{ - ssize_t n; - -#ifndef WIN32 - pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN); -#endif - -#ifdef USE_SSL - if (port->ssl) - { - if (port->count > RENEGOTIATION_LIMIT) - { - SSL_renegotiate(port->ssl); - port->count = 0; - } - - n = SSL_write(port->ssl, ptr, len); - switch (SSL_get_error(port->ssl, n)) - { - case SSL_ERROR_NONE: - port->count += n; - break; - case SSL_ERROR_WANT_WRITE: - break; - case SSL_ERROR_SYSCALL: - errno = get_last_socket_error(); - elog(ERROR, "SSL SYSCALL error: %s", strerror(errno)); - break; - case SSL_ERROR_SSL: - elog(ERROR, "SSL error: %s", SSLerrmessage()); - /* fall through */ - case SSL_ERROR_ZERO_RETURN: - secure_close(port); - errno = ECONNRESET; - n = -1; - break; - } - } - else -#endif - n = send(port->sock, ptr, len, 0); - -#ifndef WIN32 - pqsignal(SIGPIPE, oldsighandler); -#endif - - return n; -} - -/* ------------------------------------------------------------ */ -/* SSL specific code */ -/* ------------------------------------------------------------ */ -#ifdef USE_SSL -/* - * Load precomputed DH parameters. - * - * To prevent "downgrade" attacks, we perform a number of checks - * to verify that the DBA-generated DH parameters file contains - * what we expect it to contain. - */ -static DH * -load_dh_file (int keylength) -{ - FILE *fp; - char fnbuf[2048]; - DH *dh = NULL; - int codes; - - /* attempt to open file. It's not an error if it doesn't exist. */ - snprintf(fnbuf, sizeof fnbuf, "%s/dh%d.pem", DataDir, keylength); - if ((fp = fopen(fnbuf, "r")) == NULL) - return NULL; - -/* flock(fileno(fp), LOCK_SH); */ - dh = PEM_read_DHparams(fp, NULL, NULL, NULL); -/* flock(fileno(fp), LOCK_UN); */ - fclose(fp); - - /* is the prime the correct size? */ - if (dh != NULL && 8*DH_size(dh) < keylength) - { - elog(LOG, "DH errors (%s): %d bits expected, %d bits found", - fnbuf, keylength, 8*DH_size(dh)); - dh = NULL; - } - - /* make sure the DH parameters are usable */ - if (dh != NULL) - { - if (DH_check(dh, &codes)) - { - elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage()); - return NULL; - } - if (codes & DH_CHECK_P_NOT_PRIME) - { - elog(LOG, "DH error (%s): p is not prime", fnbuf); - return NULL; - } - if ((codes & DH_NOT_SUITABLE_GENERATOR) && - (codes & DH_CHECK_P_NOT_SAFE_PRIME)) - { - elog(LOG, - "DH error (%s): neither suitable generator or safe prime", - fnbuf); - return NULL; - } - } - - return dh; -} - -/* - * Load hardcoded DH parameters. - * - * To prevent problems if the DH parameters files don't even - * exist, we can load DH parameters hardcoded into this file. - */ -static DH * -load_dh_buffer (const char *buffer, size_t len) -{ - BIO *bio; - DH *dh = NULL; - - bio = BIO_new_mem_buf((char *) buffer, len); - if (bio == NULL) - return NULL; - dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - if (dh == NULL) - elog(DEBUG1, "DH load buffer: %s", SSLerrmessage()); - BIO_free(bio); - - return dh; -} - -/* - * Generate an empheral DH key. Because this can take a long - * time to compute, we can use precomputed parameters of the - * common key sizes. - * - * Since few sites will bother to precompute these parameter - * files, we also provide a fallback to the parameters provided - * by the OpenSSL project. - * - * These values can be static (once loaded or computed) since - * the OpenSSL library can efficiently generate random keys from - * the information provided. - */ -static DH * -tmp_dh_cb (SSL *s, int is_export, int keylength) -{ - DH *r = NULL; - static DH *dh = NULL; - static DH *dh512 = NULL; - static DH *dh1024 = NULL; - static DH *dh2048 = NULL; - static DH *dh4096 = NULL; - - switch (keylength) - { - case 512: - if (dh512 == NULL) - dh512 = load_dh_file(keylength); - if (dh512 == NULL) - dh512 = load_dh_buffer(file_dh512, sizeof file_dh512); - r = dh512; - break; - - case 1024: - if (dh1024 == NULL) - dh1024 = load_dh_file(keylength); - if (dh1024 == NULL) - dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024); - r = dh1024; - break; - - case 2048: - if (dh2048 == NULL) - dh2048 = load_dh_file(keylength); - if (dh2048 == NULL) - dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048); - r = dh2048; - break; - - case 4096: - if (dh4096 == NULL) - dh4096 = load_dh_file(keylength); - if (dh4096 == NULL) - dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096); - r = dh4096; - break; - - default: - if (dh == NULL) - dh = load_dh_file(keylength); - r = dh; - } - - /* this may take a long time, but it may be necessary... */ - if (r == NULL || 8*DH_size(r) < keylength) - { - elog(DEBUG1, "DH: generating parameters (%d bits)....", keylength); - r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL); - } - - return r; -} - -/* - * 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 also allows us to override the default acceptance - * criteria (e.g., accepting self-signed or expired certs), but - * for now we accept the default checks. - */ -static int -verify_cb (int ok, X509_STORE_CTX *ctx) -{ - return ok; -} - -/* - * This callback is used to copy SSL information messages - * into the PostgreSQL log. - */ -static void -info_cb (SSL *ssl, int type, int args) -{ - switch (type) - { - case SSL_CB_HANDSHAKE_START: - elog(DEBUG3, "SSL: handshake start"); - break; - case SSL_CB_HANDSHAKE_DONE: - elog(DEBUG3, "SSL: handshake done"); - break; - case SSL_CB_ACCEPT_LOOP: - elog(DEBUG3, "SSL: accept loop"); - break; - case SSL_CB_ACCEPT_EXIT: - elog(DEBUG3, "SSL: accept exit (%d)", args); - break; - case SSL_CB_CONNECT_LOOP: - elog(DEBUG3, "SSL: connect loop"); - break; - case SSL_CB_CONNECT_EXIT: - elog(DEBUG3, "SSL: connect exit (%d)", args); - break; - case SSL_CB_READ_ALERT: - elog(DEBUG3, "SSL: read alert (0x%04x)", args); - break; - case SSL_CB_WRITE_ALERT: - elog(DEBUG3, "SSL: write alert (0x%04x)", args); - break; - } -} - -/* - * Initialize global SSL context. - */ -static int -initialize_SSL (void) -{ - char fnbuf[2048]; - struct stat buf; - - if (!SSL_context) - { - SSL_library_init(); - SSL_load_error_strings(); - SSL_context = SSL_CTX_new(TLSv1_method()); - if (!SSL_context) - { - postmaster_error("failed to create SSL context: %s", - SSLerrmessage()); - ExitPostmaster(1); - } - - /* - * Load and verify certificate and private key - */ - snprintf(fnbuf, sizeof(fnbuf), "%s/server.crt", DataDir); - if (!SSL_CTX_use_certificate_file(SSL_context, fnbuf, SSL_FILETYPE_PEM)) - { - postmaster_error("failed to load server certificate (%s): %s", - fnbuf, SSLerrmessage()); - ExitPostmaster(1); - } - - snprintf(fnbuf, sizeof(fnbuf), "%s/server.key", DataDir); - if (lstat(fnbuf, &buf) == -1) - { - postmaster_error("failed to stat private key file (%s): %s", - fnbuf, strerror(errno)); - ExitPostmaster(1); - } - if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || - buf.st_uid != getuid()) - { - postmaster_error("bad permissions on private key file (%s)", fnbuf); - ExitPostmaster(1); - } - if (!SSL_CTX_use_PrivateKey_file(SSL_context, fnbuf, SSL_FILETYPE_PEM)) - { - postmaster_error("failed to load private key file (%s): %s", - fnbuf, SSLerrmessage()); - ExitPostmaster(1); - } - if (!SSL_CTX_check_private_key(SSL_context)) - { - postmaster_error("check of private key failed: %s", - SSLerrmessage()); - ExitPostmaster(1); - } - } - - /* set up empheral DH keys */ - SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb); - SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); - - /* accept client certificates, but don't require them. */ - snprintf(fnbuf, sizeof fnbuf, "%s/root.crt", DataDir); - if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, CA_PATH)) - { - postmaster_error("could not read root cert file (%s): %s", - fnbuf, SSLerrmessage()); - ExitPostmaster(1); - } - SSL_CTX_set_verify(SSL_context, - SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_cb); - - return 0; -} - -/* - * Destroy global SSL context. - */ -static void -destroy_SSL (void) -{ - if (SSL_context) - { - SSL_CTX_free(SSL_context); - SSL_context = NULL; - } -} - -/* - * Attempt to negotiate SSL connection. - */ -static int -open_server_SSL (Port *port) -{ - if (!(port->ssl = SSL_new(SSL_context)) || - !SSL_set_fd(port->ssl, port->sock) || - SSL_accept(port->ssl) <= 0) - { - elog(ERROR, "failed to initialize SSL connection: %s", SSLerrmessage()); - close_SSL(port); - return -1; - } - port->count = 0; - - /* get client certificate, if available. */ - port->peer = SSL_get_peer_certificate(port->ssl); - if (port->peer == NULL) - { - strncpy(port->peer_dn, "(anonymous)", sizeof (port->peer_dn)); - strncpy(port->peer_cn, "(anonymous)", sizeof (port->peer_cn)); - } - else - { - X509_NAME_oneline(X509_get_subject_name(port->peer), - port->peer_dn, sizeof (port->peer_dn)); - port->peer_dn[sizeof(port->peer_dn)-1] = '\0'; - X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer), - NID_commonName, port->peer_cn, sizeof (port->peer_cn)); - port->peer_cn[sizeof(port->peer_cn)-1] = '\0'; - } - elog(DEBUG1, "secure connection from '%s'", port->peer_cn); - - /* set up debugging/info callback */ - SSL_CTX_set_info_callback(SSL_context, info_cb); - - return 0; -} - -/* - * Close SSL connection. - */ -static void -close_SSL (Port *port) -{ - if (port->ssl) - { - SSL_shutdown(port->ssl); - SSL_free(port->ssl); - port->ssl = NULL; - } -} - -/* - * Obtain reason string for last SSL error - * - * Some caution is needed here since ERR_reason_error_string will - * return NULL if it doesn't recognize the error code. We don't - * want to return NULL ever. - */ -static const char * -SSLerrmessage(void) -{ - unsigned long errcode; - const char *errreason; - static char errbuf[32]; - - errcode = ERR_get_error(); - if (errcode == 0) - return "No SSL error reported"; - errreason = ERR_reason_error_string(errcode); - if (errreason != NULL) - return errreason; - snprintf(errbuf, sizeof(errbuf), "SSL error code %lu", errcode); - return errbuf; -} - -#endif /* USE_SSL */ |