mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
SSL patch that adds support for optional client certificates.
If the user has certificates in $HOME/.postgresql/postgresql.crt and $HOME/.postgresql/postgresql.key exist, they are provided to the server. The certificate used to sign this cert must be known to the server, in $DataDir/root.crt. If successful, the cert's "common name" is logged. Client certs are not used for authentication, but they could be via the port->peer (X509 *), port->peer_dn (char *) or port->peer_cn (char *) fields. Or any other function could be used, e.g., many sites like the issuer + serial number hash. Bear Giles
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.4 2002/06/14 04:35:02 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.5 2002/06/14 04:36:58 momjian Exp $
|
||||||
*
|
*
|
||||||
* Since the server static private key ($DataDir/server.key)
|
* Since the server static private key ($DataDir/server.key)
|
||||||
* will normally be stored unencrypted so that the database
|
* will normally be stored unencrypted so that the database
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
* [*] private key permissions
|
* [*] private key permissions
|
||||||
*
|
*
|
||||||
* milestone 4: provide endpoint authentication (client)
|
* milestone 4: provide endpoint authentication (client)
|
||||||
* [ ] server verifies client certificates
|
* [*] server verifies client certificates
|
||||||
*
|
*
|
||||||
* milestone 5: provide informational callbacks
|
* milestone 5: provide informational callbacks
|
||||||
* [ ] provide informational callbacks
|
* [ ] provide informational callbacks
|
||||||
@@ -124,6 +124,7 @@ ssize_t secure_write(Port *, const void *ptr, size_t len);
|
|||||||
static DH *load_dh_file(int keylength);
|
static DH *load_dh_file(int keylength);
|
||||||
static DH *load_dh_buffer(const char *, size_t);
|
static DH *load_dh_buffer(const char *, size_t);
|
||||||
static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
|
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 int initialize_SSL(void);
|
||||||
static void destroy_SSL(void);
|
static void destroy_SSL(void);
|
||||||
static int open_server_SSL(Port *);
|
static int open_server_SSL(Port *);
|
||||||
@@ -137,7 +138,7 @@ static const char *SSLerrmessage(void);
|
|||||||
* (total in both directions) before we require renegotiation.
|
* (total in both directions) before we require renegotiation.
|
||||||
*/
|
*/
|
||||||
#define RENEGOTIATION_LIMIT (64 * 1024)
|
#define RENEGOTIATION_LIMIT (64 * 1024)
|
||||||
|
#define CA_PATH NULL
|
||||||
static SSL_CTX *SSL_context = NULL;
|
static SSL_CTX *SSL_context = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -521,6 +522,24 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
|
|||||||
return r;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize global SSL context.
|
* Initialize global SSL context.
|
||||||
*/
|
*/
|
||||||
@@ -583,6 +602,17 @@ initialize_SSL (void)
|
|||||||
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
|
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
|
||||||
SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +645,24 @@ open_server_SSL (Port *port)
|
|||||||
}
|
}
|
||||||
port->count = 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: libpq-be.h,v 1.30 2002/06/14 04:33:53 momjian Exp $
|
* $Id: libpq-be.h,v 1.31 2002/06/14 04:36:58 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@@ -70,6 +70,9 @@ typedef struct Port
|
|||||||
*/
|
*/
|
||||||
#ifdef USE_SSL
|
#ifdef USE_SSL
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
|
X509 *peer;
|
||||||
|
char peer_dn[128 + 1];
|
||||||
|
char peer_cn[SM_USER + 1];
|
||||||
unsigned long count;
|
unsigned long count;
|
||||||
#endif
|
#endif
|
||||||
} Port;
|
} Port;
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.2 2002/06/14 04:31:49 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.3 2002/06/14 04:36:58 momjian Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* The client *requires* a valid server certificate. Since
|
* The client *requires* a valid server certificate. Since
|
||||||
@@ -52,6 +52,20 @@
|
|||||||
* should normally be stored encrypted. However we still
|
* should normally be stored encrypted. However we still
|
||||||
* support EPH since it's useful for other reasons.
|
* 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
|
* OS DEPENDENCIES
|
||||||
* The code currently assumes a POSIX password entry. How should
|
* The code currently assumes a POSIX password entry. How should
|
||||||
* Windows and Mac users be handled?
|
* Windows and Mac users be handled?
|
||||||
@@ -71,7 +85,7 @@
|
|||||||
* [*] emphermal DH keys, default values
|
* [*] emphermal DH keys, default values
|
||||||
*
|
*
|
||||||
* milestone 4: provide endpoint authentication (client)
|
* milestone 4: provide endpoint authentication (client)
|
||||||
* [ ] server verifies client certificates
|
* [*] server verifies client certificates
|
||||||
*
|
*
|
||||||
* milestone 5: provide informational callbacks
|
* milestone 5: provide informational callbacks
|
||||||
* [ ] provide informational callbacks
|
* [ ] provide informational callbacks
|
||||||
@@ -135,6 +149,7 @@ static int verify_peer(PGconn *);
|
|||||||
static DH *load_dh_file(int keylength);
|
static DH *load_dh_file(int keylength);
|
||||||
static DH *load_dh_buffer(const char *, size_t);
|
static DH *load_dh_buffer(const char *, size_t);
|
||||||
static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
|
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 int initialize_SSL(PGconn *);
|
||||||
static void destroy_SSL(void);
|
static void destroy_SSL(void);
|
||||||
static int open_client_SSL(PGconn *);
|
static int open_client_SSL(PGconn *);
|
||||||
@@ -614,6 +629,101 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
* Initialize global SSL context.
|
||||||
*/
|
*/
|
||||||
@@ -666,6 +776,9 @@ initialize_SSL (PGconn *conn)
|
|||||||
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
|
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
|
||||||
SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,6 +804,7 @@ open_client_SSL (PGconn *conn)
|
|||||||
int r;
|
int r;
|
||||||
|
|
||||||
if (!(conn->ssl = SSL_new(SSL_context)) ||
|
if (!(conn->ssl = SSL_new(SSL_context)) ||
|
||||||
|
!SSL_set_app_data(conn->ssl, conn) ||
|
||||||
!SSL_set_fd(conn->ssl, conn->sock) ||
|
!SSL_set_fd(conn->ssl, conn->sock) ||
|
||||||
SSL_connect(conn->ssl) <= 0)
|
SSL_connect(conn->ssl) <= 0)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user