Index: postgresql/src/backend/postmaster/be-secure.c diff -c postgresql/src/backend/postmaster/be-secure.c:1.3 postgresql/src/backend/postmaster/be-secure.c:1.4 *** postgresql/src/backend/postmaster/be-secure.c:1.3 Fri May 24 17:32:24 2002 --- postgresql/src/backend/postmaster/be-secure.c Fri May 24 21:44:30 2002 *************** *** 11,17 **** * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/backend/postmaster/be-secure.c,v 1.3 2002/05/24 23:32:24 bear Exp $ * * PATCH LEVEL * milestone 1: fix basic coding errors --- 11,17 ---- * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/backend/postmaster/be-secure.c,v 1.4 2002/05/25 03:44:30 bear Exp $ * * PATCH LEVEL * milestone 1: fix basic coding errors *************** *** 21,27 **** * * 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?' --- 21,27 ---- * * 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?' Index: postgresql/src/interfaces/libpq/fe-secure.c diff -c postgresql/src/interfaces/libpq/fe-secure.c:1.3 postgresql/src/interfaces/libpq/fe-secure.c:1.5 *** postgresql/src/interfaces/libpq/fe-secure.c:1.3 Fri May 24 17:32:24 2002 --- postgresql/src/interfaces/libpq/fe-secure.c Fri May 24 21:44:30 2002 *************** *** 11,24 **** * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/interfaces/libpq/fe-secure.c,v 1.3 2002/05/24 23:32:24 bear Exp $ * * NOTES ! * The server cert, or better yet the cert that was used to ! * sign the server cert (hence allowing the DBA to easily add ! * additional servers, modify certs, etc.) should be in the ! * "$HOME/.postgresql/root.crt" file. * * OS DEPENDENCIES * The code currently assumes a POSIX password entry. How should * Windows and Mac users be handled? --- 11,50 ---- * * * IDENTIFICATION ! * $Header: /usr/local/cvsroot/postgresql/src/interfaces/libpq/fe-secure.c,v 1.5 2002/05/25 03:44:30 bear Exp $ * * NOTES ! * The client *requires* a valid server certificate. Since ! * SSH tunnels provide anonymous confidentiality, the presumption ! * is that sites that want endpoint authentication will use the ! * direct SSL support, while sites that are comfortable with ! * anonymous connections will use SSH tunnels. * + * This code verifies the server certificate, to detect simple + * "man-in-the-middle" and "impersonation" attacks. The + * server certificate, or better yet the CA certificate used + * to sign the server certificate, should be present in the + * "$HOME/.postgresql/root.crt" file. If this file isn't + * readable, or the server certificate can't be validated, + * secure_open_client() will return an error code. + * + * Additionally, the server certificate's "common name" must + * resolve to the other end of the socket. This makes it + * substantially harder to pull off a "man-in-the-middle" or + * "impersonation" attack even if the server's private key + * has been stolen. This check limits acceptable network + * layers to Unix sockets (weird, but legal), TCPv4 and TCPv6. + * + * Unfortunately neither the current front- or back-end handle + * failure gracefully, resulting in the backend hiccupping. + * This points out problems in each (the frontend shouldn't even + * try to do SSL if secure_initialize() fails, and the backend + * shouldn't crash/recover if an SSH negotiation fails. The + * backend definitely needs to be fixed, to prevent a "denial + * of service" attack, but I don't know enough about how the + * backend works (especially that pre-SSL negotiation) to identify + * a fix. + * * OS DEPENDENCIES * The code currently assumes a POSIX password entry. How should * Windows and Mac users be handled? *************** *** 31,37 **** * * 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?' --- 57,63 ---- * * 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?' *************** *** 57,62 **** --- 83,89 ---- #include #include #include + #include #include "libpq-fe.h" #include "libpq-int.h" *************** *** 97,102 **** --- 124,130 ---- #ifdef USE_SSL static int verify_cb(int ok, X509_STORE_CTX *ctx); + static int verify_peer(PGconn *); static int initialize_SSL(PGconn *); static void destroy_SSL(void); static int open_client_SSL(PGconn *); *************** *** 184,192 **** case SSL_ERROR_WANT_READ: break; case SSL_ERROR_SYSCALL: ! errno = get_last_socket_error(); printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL SYSCALL error: %s\n"), strerror(errno)); break; case SSL_ERROR_SSL: printfPQExpBuffer(&conn->errorMessage, --- 212,221 ---- case SSL_ERROR_WANT_READ: break; case SSL_ERROR_SYSCALL: ! SOCK_ERRNO = get_last_socket_error(); printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL SYSCALL error: %s\n"), ! SOCK_STRERROR(SOCK_ERRNO)); break; case SSL_ERROR_SSL: printfPQExpBuffer(&conn->errorMessage, *************** *** 194,200 **** /* fall through */ case SSL_ERROR_ZERO_RETURN: secure_close(conn); ! errno = ECONNRESET; n = -1; break; } --- 223,229 ---- /* fall through */ case SSL_ERROR_ZERO_RETURN: secure_close(conn); ! SOCK_ERRNO = ECONNRESET; n = -1; break; } *************** *** 229,237 **** case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SYSCALL: ! errno = get_last_socket_error(); printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL SYSCALL error: %s\n"), strerror(errno)); break; case SSL_ERROR_SSL: printfPQExpBuffer(&conn->errorMessage, --- 258,267 ---- case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SYSCALL: ! SOCK_ERRNO = get_last_socket_error(); printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("SSL SYSCALL error: %s\n"), ! SOCK_STRERROR(SOCK_ERRNO)); break; case SSL_ERROR_SSL: printfPQExpBuffer(&conn->errorMessage, *************** *** 239,245 **** /* fall through */ case SSL_ERROR_ZERO_RETURN: secure_close(conn); ! errno = ECONNRESET; n = -1; break; } --- 269,275 ---- /* fall through */ case SSL_ERROR_ZERO_RETURN: secure_close(conn); ! SOCK_ERRNO = ECONNRESET; n = -1; break; } *************** *** 277,282 **** --- 307,407 ---- } /* + * Verify that common name resolves to peer. + * This function is not thread-safe due to gethostbyname2(). + */ + static int + verify_peer (PGconn *conn) + { + struct hostent *h = NULL; + struct sockaddr addr; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + socklen_t len; + char **s; + unsigned long l; + + /* get the address on the other side of the socket */ + len = sizeof(addr); + if (getpeername(conn->sock, &addr, &len) == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("error querying socket: %s\n"), + SOCK_STRERROR(SOCK_ERRNO)); + return -1; + } + + /* weird, but legal case */ + if (addr.sa_family == AF_UNIX) + return 0; + + /* what do we know about the peer's common name? */ + if ((h = gethostbyname2(conn->peer_cn, addr.sa_family)) == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("error getting information about host (%s): %s\n"), + conn->peer_cn, hstrerror(h_errno)); + return -1; + } + + /* does the address match? */ + switch (addr.sa_family) + { + case AF_INET: + sin = (struct sockaddr_in *) &addr; + for (s = h->h_addr_list; *s != NULL; s++) + { + if (!memcmp(&sin->sin_addr.s_addr, *s, h->h_length)) + return 0; + } + break; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) &addr; + for (s = h->h_addr_list; *s != NULL; s++) + { + if (!memcmp(sin6->sin6_addr.in6_u.u6_addr8, *s, h->h_length)) + return 0; + } + break; + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("sorry, this protocol not yet supported\n")); + return -1; + } + + /* the prior test should be definitive, but in practice + * it sometimes fails. So we also check the aliases. */ + for (s = h->h_aliases; *s != NULL; s++) + { + if (strcasecmp(conn->peer_cn, *s) == 0) + return 0; + } + + /* generate protocol-aware error message */ + switch (addr.sa_family) + { + case AF_INET: + sin = (struct sockaddr_in *) &addr; + l = ntohl(sin->sin_addr.s_addr); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server common name '%s' does not resolve to %ld.%ld.%ld.%ld\n"), + conn->peer_cn, (l >> 24) % 0x100, (l >> 16) % 0x100, + (l >> 8) % 0x100, l % 0x100); + break; + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server common name '%s' does not resolve to peer address\n"), + conn->peer_cn); + } + + return -1; + } + + /* * Initialize global SSL context. */ static int *************** *** 304,322 **** { snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/root.crt", pwd->pw_dir); ! if (stat(fnbuf, &buf) != -1) { ! if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, 0)) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not read root cert list (%s): %s"), ! fnbuf, SSLerrmessage()); ! return -1; ! } } } ! SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb); SSL_CTX_set_verify_depth(SSL_context, 1); return 0; --- 429,452 ---- { snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/root.crt", pwd->pw_dir); ! if (stat(fnbuf, &buf) == -1) ! { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not read root cert list(%s): %s"), ! fnbuf, strerror(errno)); ! return -1; ! } ! if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, 0)) { ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("could not read root cert list (%s): %s"), ! fnbuf, SSLerrmessage()); ! return -1; } } ! SSL_CTX_set_verify(SSL_context, ! SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); SSL_CTX_set_verify_depth(SSL_context, 1); return 0; *************** *** 355,366 **** --- 485,527 ---- } /* check the certificate chain of the server */ + /* this eliminates simple man-in-the-middle attacks and + * simple impersonations */ r = SSL_get_verify_result(conn->ssl); if (r != X509_V_OK) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate could not be validated: %s\n"), X509_verify_cert_error_string(r)); + close_SSL(conn); + return -1; + } + + /* pull out server distinguished and common names */ + conn->peer = SSL_get_peer_certificate(conn->ssl); + if (conn->peer == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate could not be obtained: %s\n"), + SSLerrmessage()); + close_SSL(conn); + return -1; + } + + X509_NAME_oneline(X509_get_subject_name(conn->peer), + conn->peer_dn, sizeof(conn->peer_dn)); + conn->peer_dn[sizeof(conn->peer_dn)-1] = '\0'; + + X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer), + NID_commonName, conn->peer_cn, SM_USER); + conn->peer_cn[SM_USER] = '\0'; + + /* verify that the common name resolves to peer */ + /* this is necessary to eliminate man-in-the-middle attacks + * and impersonations where the attacker somehow learned + * the server's private key */ + if (verify_peer(conn) == -1) + { close_SSL(conn); return -1; } Index: postgresql/src/interfaces/libpq/libpq-int.h diff -c postgresql/src/interfaces/libpq/libpq-int.h:1.1.1.1 postgresql/src/interfaces/libpq/libpq-int.h:1.2 *** postgresql/src/interfaces/libpq/libpq-int.h:1.1.1.1 Thu May 23 22:56:22 2002 --- postgresql/src/interfaces/libpq/libpq-int.h Fri May 24 19:58:03 2002 *************** *** 12,18 **** * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $Id: libpq-int.h,v 1.1.1.1 2002/05/24 04:56:22 bear Exp $ * *------------------------------------------------------------------------- */ --- 12,18 ---- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $Id: libpq-int.h,v 1.2 2002/05/25 01:58:03 bear Exp $ * *------------------------------------------------------------------------- */ *************** *** 270,275 **** --- 270,278 ---- bool allow_ssl_try; /* Allowed to try SSL negotiation */ bool require_ssl; /* Require SSL to make connection */ SSL *ssl; /* SSL status, if have SSL connection */ + X509 *peer; /* X509 cert of server */ + char peer_dn[256+1]; /* peer distinguished name */ + char peer_cn[SM_USER+1]; /* peer common name */ #endif /* Buffer for current error message */