#include "postgres.h" #include #include #include #include #include "libpq/libpq.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #ifdef USE_SSL #include #include #include #endif #ifdef USE_SSL ssize_t read_SSL(Port *, void *, size_t len); ssize_t write_SSL(Port *, const void *, size_t len); int initialize_ctx(const char *, void (*err)(const char *fmt,...)); void destroy_ctx(void); int open_SSL_server(Port *); void close_SSL (Port *); static int password_cb(); static const char * SSLerrmessage(void); static void info_cb(SSL *ssl, int type, int args); static int verify_cb(int ok, X509_STORE_CTX *ctx); static DH *tmp_dh_cb(SSL *, int, int); #endif #define PING() fprintf(stderr, "%s, line %d, %s\n", __FILE__, __LINE__, __func__) #ifdef USE_SSL static SSL_CTX *ctx = NULL; static int postmaster_session_id_context = 1; /* anything will do */ /* * These are the N-bit DH parameters from "Assigned Number for * SKIP Protocols." (http://www.skip-vpn.org/spec/numbers.html). * See there for how they were generated. */ 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"; #endif /* USE_SSL */ /* * Read data from network. */ ssize_t read_SSL (Port *port, void *ptr, size_t len) { ssize_t n; #ifdef USE_SSL if (port->ssl) { n = SSL_read(port->ssl, ptr, len); switch (SSL_get_error(port->ssl, n)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: break; case SSL_ERROR_SYSCALL: elog(ERROR, "SSL SYSCALL error"); errno = get_last_socket_error(); break; case SSL_ERROR_SSL: elog(ERROR, "SSL error: %s", SSLerrmessage()); errno = ECONNRESET; break; case SSL_ERROR_ZERO_RETURN: elog(DEBUG, "SSL shutdown by peer"); errno = ECONNRESET; break; } } else #endif /* USE_SSL */ n = recv(port->sock, ptr, len, 0); return n; } /* * Write data to network. */ ssize_t write_SSL (Port *port, const void *ptr, size_t len) { ssize_t n; /* prevent being SIGPIPEd if frontend has closed the connection. */ #ifndef WIN32 pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN); #endif #ifdef USE_SSL if (port->ssl) { n = SSL_write(port->ssl, ptr, len); switch (SSL_get_error(port->ssl, n)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SYSCALL: errno = get_last_socket_error(); break; case SSL_ERROR_SSL: elog(ERROR, "SSL error: %s", SSLerrmessage()); errno = ECONNRESET; break; case SSL_ERROR_ZERO_RETURN: elog(DEBUG, "SSL shutdown by peer"); errno = ECONNRESET; break; } } else #endif /* USE_SSL */ n = send(port->sock, ptr, len, 0); #ifndef WIN32 pqsignal(SIGPIPE, oldsighandler); #endif return n; } #ifdef USE_SSL /* * Callback used by SSL to provide information messages. */ static void info_cb (SSL *ssl, int type, int args) { switch (type) { case SSL_CB_HANDSHAKE_START: elog(DEBUG, "SSL: Handshake start"); break; case SSL_CB_HANDSHAKE_DONE: elog(DEBUG, "SSL: Handshake done"); break; case SSL_CB_ACCEPT_LOOP: if (DebugLvl >= 3) elog(DEBUG, "SSL: Accept loop..."); break; case SSL_CB_ACCEPT_EXIT: elog(DEBUG, "SSL: Accept exit (%d)", args); break; case SSL_CB_CONNECT_LOOP: elog(DEBUG, "SSL: Connect loop..."); break; case SSL_CB_CONNECT_EXIT: elog(DEBUG, "SSL: Connect exit (%d)", args); break; case SSL_CB_READ_ALERT: elog(DEBUG, "SSL: Read alert (0x%04x)", args); break; case SSL_CB_WRITE_ALERT: elog(DEBUG, "SSL: Write alert (0x%04x)", args); break; } } /* * Use a password specified via the ctx default userdata field. * Clear it once it's been used. * * returns -1 on error, 0 on no data, or length of password. */ static int password_cb (char *buf, int size, int rwflag, void *userdata) { int n; char *pass = ctx->default_passwd_callback_userdata; if (pass == NULL) return 0; n = strlen(pass); if (n > size) n = size; strncpy(buf, pass, n); SSL_CTX_set_default_passwd_cb_userdata(ctx, NULL); return n; } /* * Null authentication callback */ static int verify_cb (int ok, X509_STORE_CTX *ctx) { char sn[256], buf[256]; X509 *cert; int err, depth, n; BIO *bio; cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth= X509_STORE_CTX_get_error_depth(ctx); X509_NAME_oneline(X509_get_subject_name(cert), sn, sizeof sn); if (!ok) { switch (err) { /* accept self-signed certs */ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: ok = 1; break; default: elog(ERROR, "client cert %s: %s", sn, X509_verify_cert_error_string(err)); } } switch (ctx->error) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof buf); elog(DEBUG, "client cert %s: cannot find issuer %s", sn, buf); break; case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: bio = BIO_new(BIO_s_mem()); ASN1_TIME_print(bio, X509_get_notBefore(cert)); BIO_flush(bio); n = BIO_read(bio, buf, sizeof buf - 1); buf[n] = '\0'; elog(DEBUG, "client cert %s: not valid until %s", sn, buf); break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: bio = BIO_new(BIO_s_mem()); ASN1_TIME_print(bio, X509_get_notAfter(cert)); BIO_flush(bio); n = BIO_read(bio, buf, sizeof buf - 1); buf[n] = '\0'; elog(DEBUG, "client cert %s: not valid after %s", sn, buf); break; } return ok; } /* * Load the DH parameters file, if it exists. */ static DH *load_dh_param(const char *filename) { FILE *fp; DH *dh = NULL; if ((fp = fopen(filename, "r")) == NULL) return NULL; flock(fileno(fp), LOCK_SH); dh = PEM_read_DHparams(fp, NULL, NULL, NULL); flock(fileno(fp), LOCK_UN); fclose(fp); return dh; } /* * Load the DH parameters buffer. */ static DH *load_dh_param_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); 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. If the files do not exist, we fall back * to the values provided by the OpenSSL project. */ static DH *tmp_dh_cb (SSL *s, int is_export, int keylength) { char fnbuf[2048]; static DH *dh512 = NULL; static DH *dh1024 = NULL; static DH *dh2048 = NULL; static DH *dh4096 = NULL; static DH *dh = NULL; static DH *r = NULL; switch (keylength) { case 512: if (dh512 == NULL) { snprintf(fnbuf, sizeof fnbuf, "%s/dh512.pem", DataDir); dh512 = load_dh_param(fnbuf); } if (dh512 == NULL) dh512 = load_dh_param_buffer(file_dh512, sizeof file_dh512); r = dh512; break; case 1024: if (dh1024 == NULL) { snprintf(fnbuf, sizeof fnbuf, "%s/dh1024.pem", DataDir); dh1024 = load_dh_param(fnbuf); } if (dh1024 == NULL) dh1024 = load_dh_param_buffer(file_dh1024, sizeof file_dh1024); r = dh1024; break; case 2048: if (dh2048 == NULL) { snprintf(fnbuf, sizeof fnbuf, "%s/dh2048.pem", DataDir); dh2048 = load_dh_param(fnbuf); } if (dh2048 == NULL) dh2048 = load_dh_param_buffer(file_dh2048, sizeof file_dh2048); r = dh2048; break; case 4096: if (dh4096 == NULL) { snprintf(fnbuf, sizeof fnbuf, "%s/dh4096.pem", DataDir); dh4096 = load_dh_param(fnbuf); } if (dh4096 == NULL) dh4096 = load_dh_param_buffer(file_dh4096, sizeof file_dh4096); r = dh4096; break; default: if (dh == NULL) { dh = DH_generate_parameters(keylength, 2, NULL, NULL); r = dh; } } return r; } /* * Initialize global SSL context. */ int initialize_ctx (const char *password, void (*err)(const char *fmt,...)) { SSL_METHOD *meth = NULL; char fnbuf[2048]; struct stat buf; int verify_mode = SSL_VERIFY_PEER; if (!ctx) { SSL_library_init(); SSL_load_error_strings(); // meth = SSLv23_method(); meth = TLSv1_method(); ctx = SSL_CTX_new(meth); if (!ctx) { err("failed to create SSL context: %s", SSLerrmessage()); return -1; } } /* load our keys and certificate */ snprintf(fnbuf, sizeof fnbuf, "%s/%s", DataDir, "server.crt"); if (!SSL_CTX_use_certificate_file(ctx, fnbuf, SSL_FILETYPE_PEM)) { err("failed to load server certificate (%s): %s", fnbuf, SSLerrmessage()); return -1; } SSL_CTX_set_default_passwd_cb(ctx, password_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password); snprintf(fnbuf, sizeof fnbuf, "%s/%s", DataDir, "server.key"); if (!SSL_CTX_use_PrivateKey_file(ctx, fnbuf, SSL_FILETYPE_PEM)) { err("failed to private key file (%s): %s", fnbuf, SSLerrmessage()); return -1; } if (!SSL_CTX_check_private_key(ctx)) { err("check of private key failed: %s", SSLerrmessage()); return -1; } /* load the CAs we trust */ #if defined(CA_LIST) || defined (CA_PATH) if (!SSL_CTX_load_verify_locations(ctx, CA_LIST, CA_PATH)) { err("failed to load verfication paths (%s, %s): %s\n", CA_LIST, CA_PATH, SSLerrmessage()); return -1; } #endif #if defined(CA_LIST) if (CA_LIST != NULL) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CA_LIST)); #endif SSL_CTX_set_verify(ctx, verify_mode, verify_cb); /* load randomness */ #ifdef RANDOM if (!RAND_load_file(RANDOM, 1024 * 1024)) { err("failed to read randomness (%s): %s\n", RANDOM, SSLerrmessage()); return -1; } #else /* RANDOM */ if (lstat("/dev/urandom", &buf) == 0 && S_ISCHR(buf.st_mode)) { if (!RAND_load_file("/dev/urandom", 16 * 1024)) { err("failed to read randomness (%s): %s\n", "/dev/urandom", SSLerrmessage()); return -1; } } #endif /* RANDOM */ /* * Set up SSL context to use empheral DH keys */ SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_cb); SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); /* * Set up SSL context to support sessions */ SSL_CTX_set_session_id_context(ctx, (void *) &postmaster_session_id_context, sizeof postmaster_session_id_context); /* * Set up debugging messages */ if (DebugLvl >= 2) SSL_CTX_set_info_callback(ctx, info_cb); return 0; } /* * Destroy the global SSL context. */ void destroy_ctx (void) { SSL_CTX_free(ctx); ctx = NULL; } /* * Open SSL connection. */ int open_SSL_server (Port *port) { const char *cipher = NULL; char buffer[256]; if (!(port->ssl = SSL_new(ctx)) || !SSL_set_fd(port->ssl, port->sock) || SSL_accept(port->ssl) <= 0) { elog(DEBUG, "failed to initialize SSL connection: %s (%m)", SSLerrmessage()); return STATUS_ERROR; } /* if (context) * { * SSL_set_session_id_context(port->ssl, context, * strlen((char *) context); * } */ /* SSL_clear(port->ssl); */ /* SSL_set_accept_state(port->ssl); */ port->peer = SSL_get_peer_certificate(port->ssl); if (port->peer == NULL) strncpy(buffer, "(anonymous)", sizeof buffer); else { X509_NAME_oneline(X509_get_subject_name(port->peer), buffer, sizeof buffer); } cipher = SSL_CIPHER_get_name(SSL_get_current_cipher(port->ssl)); elog(DEBUG, "SSL connection from %s with cipher %s", buffer, cipher != NULL ? cipher : "(NONE)"); if (SSL_ctrl(port->ssl, SSL_CTRL_GET_FLAGS, 0, NULL) & TLS1_FLAGS_TLS_PADDING_BUG) { elog(ERROR, "Peer has incorrect TLSv1 block padding"); } return STATUS_OK; } /* * Close SSL connection. */ 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 */