Skip to content

Commit eb7afc1

Browse files
committed
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
1 parent b8b6691 commit eb7afc1

File tree

3 files changed

+171
-6
lines changed

3 files changed

+171
-6
lines changed

src/backend/libpq/be-secure.c

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
*
1313
* IDENTIFICATION
14-
* $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.4 2002/06/14 04:35:02 momjian Exp $
14+
* $Header: /cvsroot/pgsql/src/backend/libpq/be-secure.c,v 1.5 2002/06/14 04:36:58 momjian Exp $
1515
*
1616
* Since the server static private key ($DataDir/server.key)
1717
* will normally be stored unencrypted so that the database
@@ -62,7 +62,7 @@
6262
* [*] private key permissions
6363
*
6464
* milestone 4: provide endpoint authentication (client)
65-
* [ ] server verifies client certificates
65+
* [*] server verifies client certificates
6666
*
6767
* milestone 5: provide informational callbacks
6868
* [ ] provide informational callbacks
@@ -124,6 +124,7 @@ ssize_t secure_write(Port *, const void *ptr, size_t len);
124124
static DH *load_dh_file(int keylength);
125125
static DH *load_dh_buffer(const char *, size_t);
126126
static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
127+
static int verify_cb(int, X509_STORE_CTX *);
127128
static int initialize_SSL(void);
128129
static void destroy_SSL(void);
129130
static int open_server_SSL(Port *);
@@ -137,7 +138,7 @@ static const char *SSLerrmessage(void);
137138
* (total in both directions) before we require renegotiation.
138139
*/
139140
#define RENEGOTIATION_LIMIT (64 * 1024)
140-
141+
#define CA_PATH NULL
141142
static SSL_CTX *SSL_context = NULL;
142143
#endif
143144

@@ -521,6 +522,24 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
521522
return r;
522523
}
523524

525+
/*
526+
* Certificate verification callback
527+
*
528+
* This callback allows us to log intermediate problems during
529+
* verification, but for now we'll see if the final error message
530+
* contains enough information.
531+
*
532+
* This callback also allows us to override the default acceptance
533+
* criteria (e.g., accepting self-signed or expired certs), but
534+
* for now we accept the default checks.
535+
*/
536+
static int
537+
verify_cb (int ok, X509_STORE_CTX *ctx)
538+
{
539+
return ok;
540+
}
541+
542+
524543
/*
525544
* Initialize global SSL context.
526545
*/
@@ -583,6 +602,17 @@ initialize_SSL (void)
583602
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
584603
SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
585604

605+
/* accept client certificates, but don't require them. */
606+
snprintf(fnbuf, sizeof fnbuf, "%s/root.crt", DataDir);
607+
if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, CA_PATH))
608+
{
609+
postmaster_error("could not read root cert file (%s): %s",
610+
fnbuf, SSLerrmessage());
611+
ExitPostmaster(1);
612+
}
613+
SSL_CTX_set_verify(SSL_context,
614+
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_cb);
615+
586616
return 0;
587617
}
588618

@@ -615,6 +645,24 @@ open_server_SSL (Port *port)
615645
}
616646
port->count = 0;
617647

648+
/* get client certificate, if available. */
649+
port->peer = SSL_get_peer_certificate(port->ssl);
650+
if (port->peer == NULL)
651+
{
652+
strncpy(port->peer_dn, "(anonymous)", sizeof (port->peer_dn));
653+
strncpy(port->peer_cn, "(anonymous)", sizeof (port->peer_cn));
654+
}
655+
else
656+
{
657+
X509_NAME_oneline(X509_get_subject_name(port->peer),
658+
port->peer_dn, sizeof (port->peer_dn));
659+
port->peer_dn[sizeof(port->peer_dn)-1] = '\0';
660+
X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
661+
NID_commonName, port->peer_cn, sizeof (port->peer_cn));
662+
port->peer_cn[sizeof(port->peer_cn)-1] = '\0';
663+
}
664+
elog(DEBUG, "secure connection from '%s'", port->peer_cn);
665+
618666
return 0;
619667
}
620668

src/include/libpq/libpq-be.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
1212
* Portions Copyright (c) 1994, Regents of the University of California
1313
*
14-
* $Id: libpq-be.h,v 1.30 2002/06/14 04:33:53 momjian Exp $
14+
* $Id: libpq-be.h,v 1.31 2002/06/14 04:36:58 momjian Exp $
1515
*
1616
*-------------------------------------------------------------------------
1717
*/
@@ -70,6 +70,9 @@ typedef struct Port
7070
*/
7171
#ifdef USE_SSL
7272
SSL *ssl;
73+
X509 *peer;
74+
char peer_dn[128 + 1];
75+
char peer_cn[SM_USER + 1];
7376
unsigned long count;
7477
#endif
7578
} Port;

src/interfaces/libpq/fe-secure.c

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
*
1313
* IDENTIFICATION
14-
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.2 2002/06/14 04:31:49 momjian Exp $
14+
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-secure.c,v 1.3 2002/06/14 04:36:58 momjian Exp $
1515
*
1616
* NOTES
1717
* The client *requires* a valid server certificate. Since
@@ -52,6 +52,20 @@
5252
* should normally be stored encrypted. However we still
5353
* support EPH since it's useful for other reasons.
5454
*
55+
* ...
56+
*
57+
* Client certificates are supported, if the server requests
58+
* or requires them. Client certificates can be used for
59+
* authentication, to prevent sessions from being hijacked,
60+
* or to allow "road warriors" to access the database while
61+
* keeping it closed to everyone else.
62+
*
63+
* The user's certificate and private key are located in
64+
* $HOME/.postgresql/postgresql.crt
65+
* and
66+
* $HOME/.postgresql/postgresql.key
67+
* respectively.
68+
*
5569
* OS DEPENDENCIES
5670
* The code currently assumes a POSIX password entry. How should
5771
* Windows and Mac users be handled?
@@ -71,7 +85,7 @@
7185
* [*] emphermal DH keys, default values
7286
*
7387
* milestone 4: provide endpoint authentication (client)
74-
* [ ] server verifies client certificates
88+
* [*] server verifies client certificates
7589
*
7690
* milestone 5: provide informational callbacks
7791
* [ ] provide informational callbacks
@@ -135,6 +149,7 @@ static int verify_peer(PGconn *);
135149
static DH *load_dh_file(int keylength);
136150
static DH *load_dh_buffer(const char *, size_t);
137151
static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
152+
static int client_cert_cb(SSL *, X509 **, EVP_PKEY **);
138153
static int initialize_SSL(PGconn *);
139154
static void destroy_SSL(void);
140155
static int open_client_SSL(PGconn *);
@@ -614,6 +629,101 @@ tmp_dh_cb (SSL *s, int is_export, int keylength)
614629
return r;
615630
}
616631

632+
/*
633+
* Callback used by SSL to load client cert and key.
634+
* This callback is only called when the server wants a
635+
* client cert.
636+
*
637+
* Returns 1 on success, 0 on no data, -1 on error.
638+
*/
639+
static int
640+
client_cert_cb (SSL *ssl, X509 **x509, EVP_PKEY **pkey)
641+
{
642+
struct passwd *pwd;
643+
struct stat buf, buf2;
644+
char fnbuf[2048];
645+
FILE *fp;
646+
PGconn *conn = (PGconn *) SSL_get_app_data(ssl);
647+
int (*cb)() = NULL; /* how to read user password */
648+
649+
if ((pwd = getpwuid(getuid())) == NULL)
650+
{
651+
printfPQExpBuffer(&conn->errorMessage,
652+
libpq_gettext("unable to get user information\n"));
653+
return -1;
654+
}
655+
656+
/* read the user certificate */
657+
snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.crt",
658+
pwd->pw_dir);
659+
if (stat(fnbuf, &buf) == -1)
660+
return 0;
661+
if ((fp = fopen(fnbuf, "r")) == NULL)
662+
{
663+
printfPQExpBuffer(&conn->errorMessage,
664+
libpq_gettext("unable to open certificate (%s): %s\n"),
665+
fnbuf, strerror(errno));
666+
return -1;
667+
}
668+
if (PEM_read_X509(fp, x509, NULL, NULL) == NULL)
669+
{
670+
printfPQExpBuffer(&conn->errorMessage,
671+
libpq_gettext("unable to read certificate (%s): %s\n"),
672+
fnbuf, SSLerrmessage());
673+
fclose(fp);
674+
return -1;
675+
}
676+
fclose(fp);
677+
678+
/* read the user key */
679+
snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.key",
680+
pwd->pw_dir);
681+
if (stat(fnbuf, &buf) == -1)
682+
{
683+
printfPQExpBuffer(&conn->errorMessage,
684+
libpq_gettext("certificate present, but not private key (%s)\n"),
685+
fnbuf);
686+
X509_free(*x509);
687+
return 0;
688+
}
689+
if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) ||
690+
buf.st_uid != getuid())
691+
{
692+
printfPQExpBuffer(&conn->errorMessage,
693+
libpq_gettext("private key has bad permissions (%s)\n"), fnbuf);
694+
X509_free(*x509);
695+
return -1;
696+
}
697+
if ((fp = fopen(fnbuf, "r")) == NULL)
698+
{
699+
printfPQExpBuffer(&conn->errorMessage,
700+
libpq_gettext("unable to open private key file (%s): %s\n"),
701+
fnbuf, strerror(errno));
702+
X509_free(*x509);
703+
return -1;
704+
}
705+
if (fstat(fileno(fp), &buf2) == -1 ||
706+
buf.st_dev != buf2.st_dev || buf.st_ino != buf2.st_ino)
707+
{
708+
printfPQExpBuffer(&conn->errorMessage,
709+
libpq_gettext("private key changed under us (%s)\n"), fnbuf);
710+
X509_free(*x509);
711+
return -1;
712+
}
713+
if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL)
714+
{
715+
printfPQExpBuffer(&conn->errorMessage,
716+
libpq_gettext("unable to read private key (%s): %s\n"),
717+
fnbuf, SSLerrmessage());
718+
X509_free(*x509);
719+
fclose(fp);
720+
return -1;
721+
}
722+
fclose(fp);
723+
724+
return 1;
725+
}
726+
617727
/*
618728
* Initialize global SSL context.
619729
*/
@@ -666,6 +776,9 @@ initialize_SSL (PGconn *conn)
666776
SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb);
667777
SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE);
668778

779+
/* set up mechanism to provide client certificate, if available */
780+
SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb);
781+
669782
return 0;
670783
}
671784

@@ -691,6 +804,7 @@ open_client_SSL (PGconn *conn)
691804
int r;
692805

693806
if (!(conn->ssl = SSL_new(SSL_context)) ||
807+
!SSL_set_app_data(conn->ssl, conn) ||
694808
!SSL_set_fd(conn->ssl, conn->sock) ||
695809
SSL_connect(conn->ssl) <= 0)
696810
{

0 commit comments

Comments
 (0)