Index: postgresql/src/backend/postmaster/be-secure.c diff -c postgresql/src/backend/postmaster/be-secure.c:1.8 postgresql/src/backend/postmaster/be-secure.c:1.9 *** postgresql/src/backend/postmaster/be-secure.c:1.8 Sat May 25 00:51:56 2002 --- postgresql/src/backend/postmaster/be-secure.c Sat May 25 01:44:46 2002 *************** *** 11,17 **** * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/backend/postmaster/be-secure.c,v 1.8 2002/05/25 06:51:56 bear Exp $ * * NOTES * --- 11,17 ---- * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/backend/postmaster/be-secure.c,v 1.9 2002/05/25 07:44:46 bear Exp $ * * NOTES * *************** *** 76,82 **** * [*] private key permissions * * milestone 4: provide endpoint authentication (client) ! * [ ] server verifies client certificates * * milestone 5: provide informational callbacks * [ ] provide informational callbacks --- 76,82 ---- * [*] private key permissions * * milestone 4: provide endpoint authentication (client) ! * [*] server verifies client certificates * * milestone 5: provide informational callbacks * [ ] provide informational callbacks *************** *** 140,145 **** --- 140,146 ---- 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 int initialize_SSL(void); static void destroy_SSL(void); static int open_server_SSL(Port *); *************** *** 153,159 **** * (total in both directions) before we require renegotiation. */ #define RENEGOTIATION_LIMIT (64 * 1024) ! static SSL_CTX *SSL_context = NULL; #endif --- 154,160 ---- * (total in both directions) before we require renegotiation. */ #define RENEGOTIATION_LIMIT (64 * 1024) ! #define CA_PATH NULL static SSL_CTX *SSL_context = NULL; #endif *************** *** 538,543 **** --- 539,562 ---- } /* + * 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; + } + + + /* * Initialize global SSL context. */ static int *************** *** 619,624 **** --- 638,654 ---- 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; } *************** *** 650,655 **** --- 680,703 ---- 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(DEBUG, "secure connection from '%s'", port->peer_cn); return 0; } Index: postgresql/src/include/libpq/libpq-be.h diff -c postgresql/src/include/libpq/libpq-be.h:1.2 postgresql/src/include/libpq/libpq-be.h:1.3 *** postgresql/src/include/libpq/libpq-be.h:1.2 Sat May 25 00:33:05 2002 --- postgresql/src/include/libpq/libpq-be.h Sat May 25 01:44:46 2002 *************** *** 11,17 **** * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $Id: libpq-be.h,v 1.2 2002/05/25 06:33:05 bear Exp $ * *------------------------------------------------------------------------- */ --- 11,17 ---- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $Id: libpq-be.h,v 1.3 2002/05/25 07:44:46 bear Exp $ * *------------------------------------------------------------------------- */ *************** *** 70,75 **** --- 70,78 ---- */ #ifdef USE_SSL SSL *ssl; + X509 *peer; + char peer_dn[128 + 1]; + char peer_cn[SM_USER + 1]; unsigned long count; #endif } Port; Index: postgresql/src/interfaces/libpq/fe-secure.c diff -c postgresql/src/interfaces/libpq/fe-secure.c:1.7 postgresql/src/interfaces/libpq/fe-secure.c:1.8 *** postgresql/src/interfaces/libpq/fe-secure.c:1.7 Sat May 25 00:18:48 2002 --- postgresql/src/interfaces/libpq/fe-secure.c Sat May 25 01:44:46 2002 *************** *** 11,17 **** * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/interfaces/libpq/fe-secure.c,v 1.7 2002/05/25 06:18:48 bear Exp $ * * NOTES * The client *requires* a valid server certificate. Since --- 11,17 ---- * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/interfaces/libpq/fe-secure.c,v 1.8 2002/05/25 07:44:46 bear Exp $ * * NOTES * The client *requires* a valid server certificate. Since *************** *** 64,69 **** --- 64,83 ---- * should normally be stored encrypted. However we still * support EPH since it's useful for other reasons. * + * ... + * + * Client certificates are supported, if the server requests + * or requires them. Client certificates can be used for + * authentication, to prevent sessions from being hijacked, + * or to allow "road warriors" to access the database while + * keeping it closed to everyone else. + * + * The user's certificate and private key are located in + * $HOME/.postgresql/postgresql.crt + * and + * $HOME/.postgresql/postgresql.key + * respectively. + * * OS DEPENDENCIES * The code currently assumes a POSIX password entry. How should * Windows and Mac users be handled? *************** *** 83,89 **** * [*] emphermal DH keys, default values * * milestone 4: provide endpoint authentication (client) ! * [ ] server verifies client certificates * * milestone 5: provide informational callbacks * [ ] provide informational callbacks --- 97,103 ---- * [*] emphermal DH keys, default values * * milestone 4: provide endpoint authentication (client) ! * [*] server verifies client certificates * * milestone 5: provide informational callbacks * [ ] provide informational callbacks *************** *** 148,153 **** --- 162,168 ---- 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 client_cert_cb(SSL *, X509 **, EVP_PKEY **); static int initialize_SSL(PGconn *); static void destroy_SSL(void); static int open_client_SSL(PGconn *); *************** *** 628,633 **** --- 643,743 ---- } /* + * Callback used by SSL to load client cert and key. + * This callback is only called when the server wants a + * client cert. + * + * Returns 1 on success, 0 on no data, -1 on error. + */ + static int + client_cert_cb (SSL *ssl, X509 **x509, EVP_PKEY **pkey) + { + struct passwd *pwd; + struct stat buf, buf2; + char fnbuf[2048]; + FILE *fp; + PGconn *conn = (PGconn *) SSL_get_app_data(ssl); + int (*cb)() = NULL; /* how to read user password */ + + if ((pwd = getpwuid(getuid())) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to get user information\n")); + return -1; + } + + /* read the user certificate */ + snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.crt", + pwd->pw_dir); + if (stat(fnbuf, &buf) == -1) + return 0; + if ((fp = fopen(fnbuf, "r")) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to open certificate (%s): %s\n"), + fnbuf, strerror(errno)); + return -1; + } + if (PEM_read_X509(fp, x509, NULL, NULL) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to read certificate (%s): %s\n"), + fnbuf, SSLerrmessage()); + fclose(fp); + return -1; + } + fclose(fp); + + /* read the user key */ + snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.key", + pwd->pw_dir); + if (stat(fnbuf, &buf) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but not private key (%s)\n"), + fnbuf); + X509_free(*x509); + return 0; + } + if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) || + buf.st_uid != getuid()) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key has bad permissions (%s)\n"), fnbuf); + X509_free(*x509); + return -1; + } + if ((fp = fopen(fnbuf, "r")) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to open private key file (%s): %s\n"), + fnbuf, strerror(errno)); + X509_free(*x509); + return -1; + } + if (fstat(fileno(fp), &buf2) == -1 || + buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key changed under us (%s)\n"), fnbuf); + X509_free(*x509); + return -1; + } + if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to read private key (%s): %s\n"), + fnbuf, SSLerrmessage()); + X509_free(*x509); + fclose(fp); + return -1; + } + fclose(fp); + + return 1; + } + + /* * Initialize global SSL context. */ static int *************** *** 700,705 **** --- 810,818 ---- SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb); SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); + /* set up mechanism to provide client certificate, if available */ + SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb); + return 0; } *************** *** 726,731 **** --- 839,845 ---- int r; if (!(conn->ssl = SSL_new(SSL_context)) || + !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock) || SSL_connect(conn->ssl) <= 0) {