#include "postgres_fe.h" #include #include #include #include #include #include #include "libpq-fe.h" #include "libpq-int.h" #include "fe-auth.h" #include "pqsignal.h" #ifdef WIN32 #include "win32.h" #else #include #include #include #include #ifdef HAVE_NETINET_TCP_H #include #endif #include #endif #ifdef USE_SSL #include #include #include int initialize_ctx(const char *, void (*err)(const char *fmt,...), PGconn *); void destroy_ctx(void); int open_SSL_client(PGconn *); void close_SSL(PGconn *); SSL PGgetssl(PGconn *); static int clientCert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey); static int verify_cb(int, X509_STORE_CTX *); static void info_cb(SSL *ssl, int type, int args); static const char *SSLerrmessage(void); #endif ssize_t read_SSL(PGconn *, void *, size_t); ssize_t write_SSL(PGconn *, const void *, size_t); /* #define CA_LIST "root.pem" */ #ifdef USE_SSL static SSL_CTX *ctx = NULL; #endif #define PING() fprintf(stderr,"%s, line %d, %s\n", __FILE__, __LINE__, __func__) /* * Read data from network. */ ssize_t read_SSL (PGconn *conn, void *ptr, size_t len) { ssize_t n; #ifdef USE_SSL if (conn->ssl) { n = SSL_read(conn->ssl, ptr, len); switch (SSL_get_error(conn->ssl, n)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: break; case SSL_ERROR_SYSCALL: SOCK_ERRNO = get_last_socket_error(); break; case SSL_ERROR_SSL: // log error... SOCK_ERRNO = ECONNRESET; break; case SSL_ERROR_ZERO_RETURN: SOCK_ERRNO = ECONNRESET; break; } } else #endif /* USE_SSL */ n = recv(conn->sock, ptr, len, 0); return n; } /* * Write data to network. */ ssize_t write_SSL (PGconn *conn, const void *ptr, size_t len) { ssize_t n; /* prevent being SIGPIPEd if backend has closed the connection. */ #ifndef WIN32 pqsigfunc oldsighandler = pqsignal(SIGPIPE, SIG_IGN); #endif #ifdef USE_SSL if (conn->ssl) { n = SSL_write(conn->ssl, ptr, len); switch (SSL_get_error(conn->ssl, n)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SYSCALL: SOCK_ERRNO = get_last_socket_error(); break; case SSL_ERROR_SSL: fprintf(stderr, "ssl error\n"); // log error... SOCK_ERRNO = ECONNRESET; break; case SSL_ERROR_ZERO_RETURN: fprintf(stderr, "zero bytes\n"); SOCK_ERRNO = ECONNRESET; break; } } else #endif n = send(conn->sock, ptr, len, 0); #ifndef WIN32 pqsignal(SIGPIPE, oldsighandler); #endif return n; } #ifdef USE_SSL /* * 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: fprintf(stderr, "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); fprintf(stderr, "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'; fprintf(stderr, "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'; fprintf(stderr, "client cert %s: not valid after %s\n", sn, buf); break; } return ok; } /* * Callback used by SSL to provide information messages. */ static void info_cb (SSL *ssl, int type, int args) { PGconn *conn = NULL; conn = (PGconn *) SSL_get_app_data(ssl); if (conn == NULL || conn->Pfdebug == NULL) return; switch (type) { case SSL_CB_HANDSHAKE_START: fprintf(conn->Pfdebug, "Handshake start\n"); break; case SSL_CB_HANDSHAKE_DONE: fprintf(conn->Pfdebug, "Handshake done\n"); break; case SSL_CB_ACCEPT_LOOP: fprintf(conn->Pfdebug, "Accept loop...\n"); break; case SSL_CB_ACCEPT_EXIT: fprintf(conn->Pfdebug, "Accept exit (%d)\n", args); break; case SSL_CB_CONNECT_LOOP: fprintf(conn->Pfdebug, "Connect loop...\n"); break; case SSL_CB_CONNECT_EXIT: fprintf(conn->Pfdebug, "Connect exit (%d)\n", args); break; case SSL_CB_READ_ALERT: fprintf(conn->Pfdebug, "Read alert (0x%04x)\n", args); break; case SSL_CB_WRITE_ALERT: fprintf(conn->Pfdebug, "Write alert (0x%04x)\n", args); break; } } /* * Callback used by SSL to load client cert and key. * At the current time we require the cert and key to be * located in the .postgresql directory under the user's * home directory, and the files must be named 'postgresql.crt' * and 'postgresql.key' respectively. * * returns 1 on success, 0 on no data, -1 on error. */ static int clientCert_cb (SSL *ssl, X509 **x509, EVP_PKEY **pkey) { uid_t uid; struct passwd *pwd; char fnbuf[2048]; struct stat buf, buf1; FILE *fp; int (*cb)() = NULL; if ((uid = getuid()) == -1) { fprintf(stderr, "can't get current uid\n"); return -1; } if ((pwd = getpwuid(uid)) == NULL || !pwd->pw_dir) { fprintf(stderr, "can't get passwd entry\n"); return -1; } /* * if $HOME/.postgresql does not exist, 'no data' case. * otherwise, it must be a directory, owned by current user, * and not group- or world-accessible. */ snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql", pwd->pw_dir); if (lstat(fnbuf, &buf) == -1) return 0; if (!S_ISDIR(buf.st_mode) || buf.st_uid != uid || (buf.st_mode & (S_IRWXG | S_IRWXO)) != 0) { fprintf(stderr, "$HOME/.postgresql directory has wrong ownership or permissions\n"); return -1; } /* * make sure $HOME/.postgresql/postgresql.crt file exists, * is regular file and owned by current user. */ snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.crt", pwd->pw_dir); if (lstat(fnbuf, &buf) == -1) return 0; if (!S_ISREG(buf.st_mode) || buf.st_uid != uid) { fprintf(stderr, "certificate file has wrong ownership or permissions\n"); return -1; } if ((fp = fopen(fnbuf, "r")) == NULL) { fprintf(stderr, "can't open certificate file (%s)\n", strerror(errno)); return -1; } if (PEM_read_X509(fp, x509, NULL, NULL) == NULL) { fprintf(stderr, "can't read certificate %s\n", SSLerrmessage()); fclose(fp); return -1; } fclose(fp); /* * make sure $HOME/.postgresql/postgresql.key file exists, * is regular file, owned by current user, and not group- * or world-accessable. */ snprintf(fnbuf, sizeof fnbuf, "%s/.postgresql/postgresql.key", pwd->pw_dir); if (lstat(fnbuf, &buf) == -1) { fprintf(stderr, "certificate file exists, but no private key\n"); SSL_use_certificate(ssl, NULL); return -1; } if (!S_ISREG(buf.st_mode) || buf.st_uid != uid || (buf.st_mode & (S_IRWXG | S_IRWXO)) != 0) { fprintf(stderr, "private key file has wrong ownership or permissions\n"); SSL_use_certificate(ssl, NULL); return -1; } if ((fp = fopen(fnbuf, "r")) == NULL) { fprintf(stderr, "error opening private key file: %s\n", strerror(errno)); SSL_use_certificate(ssl, NULL); return -1; } if (fstat(fileno(fp),&buf1) == -1 || buf.st_dev != buf1.st_dev || buf.st_ino != buf1.st_ino) { fprintf(stderr, "private key changed under us!\n"); fclose(fp); SSL_use_certificate(ssl, NULL); return -1; } if (PEM_read_PrivateKey(fp, pkey, cb, NULL) == NULL) { fprintf(stderr, "can't read private key %s\n", SSLerrmessage()); fclose(fp); SSL_use_certificate(ssl, NULL); return -1; } fclose(fp); return 1; } /* * Initialize global SSL context. * * We want to use 'err' for errors, same as the corresponding * function on the server, but for now we use legacy error handler * in PGconn. */ int initialize_ctx (const char *password, void (*err)(const char * fmt,...), PGconn *conn) { SSL_METHOD *meth = NULL; struct stat buf; if (!ctx) { SSL_library_init(); SSL_load_error_strings(); // meth = SSLv23_method(); meth = TLSv1_method(); ctx = SSL_CTX_new(meth); if (!ctx) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not create SSL context: %s\n"), SSLerrmessage()); return -1; } } /* load the CAs we trust */ #ifdef CA_LIST if (!SSL_CTX_load_verify_locations(ctx, CA_LIST, 0)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read CA list (%s): %s\n"), CA_LIST, SSLerrmessage()); return -1; } #endif /* load randomness */ #ifdef RANDOM if (!RAND_load_file(RANDOM, 1024 * 1024)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not load 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)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not load randomness (%s): %s\n"), "/dev/urandom", SSLerrmessage()); return -1; } } #endif /* RANDOM */ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb); SSL_CTX_set_verify_depth(ctx, 1); SSL_CTX_set_info_callback(ctx, info_cb); SSL_CTX_set_client_cert_cb(ctx, clientCert_cb); return 0; } /* * Destroy the global SSL context. */ void destroy_ctx (void) { SSL_CTX_free(ctx); ctx = NULL; } /* * Open a SSL connection. */ int open_SSL_client (PGconn *conn) { char peerName[256]; const char *reason; int r; if (!(conn->ssl = SSL_new(ctx)) || !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock) || SSL_connect(conn->ssl) <= 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not establish SSL connection: %s\n"), SSLerrmessage()); return -1; } /* check the certificate chain */ /* for now, we allow self-signed server certs */ r = SSL_get_verify_result(conn->ssl); if (r != X509_V_OK && r != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) { switch (r) { case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: reason = "unable to get issuer cert"; break; case X509_V_ERR_UNABLE_TO_GET_CRL: reason = "unable to get CRL"; break; case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: reason = "unable to decrypt cert signature"; break; case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: reason = "unable to decrypt CRL signature"; break; case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: reason = "unable to decode issuer public key"; break; case X509_V_ERR_CERT_SIGNATURE_FAILURE: reason = "cert signature failure"; break; case X509_V_ERR_CRL_SIGNATURE_FAILURE: reason = "CRL signature failure"; break; case X509_V_ERR_CERT_NOT_YET_VALID: reason = "cert is not yet valid"; break; case X509_V_ERR_CERT_HAS_EXPIRED: reason = "cert has expired"; break; case X509_V_ERR_CRL_NOT_YET_VALID: reason = "CRL not yet valid"; break; case X509_V_ERR_CRL_HAS_EXPIRED: reason = "CRL has expired"; break; case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: reason = "error in cert notBefore field"; break; case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: reason = "error in cert notAfter field"; break; case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: reason = "error in CRL last update field"; break; case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: reason = "error in CRL next update field"; break; case X509_V_ERR_OUT_OF_MEM: reason = "out of memory"; break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: reason = "depth zero self-signed cert"; break; case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: reason = "self-signed cert in chain"; break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: reason = "unable to get issuer cert locally"; break; case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: reason = "unable to verify leaf signature"; break; case X509_V_ERR_CERT_CHAIN_TOO_LONG: reason = "cert chain too long"; break; case X509_V_ERR_CERT_REVOKED: reason = "cert revoked"; break; case X509_V_ERR_INVALID_CA: reason = "invalid CA"; break; case X509_V_ERR_PATH_LENGTH_EXCEEDED: reason = "path length exceeded"; break; case X509_V_ERR_INVALID_PURPOSE: reason = "invalid purpose"; break; case X509_V_ERR_CERT_UNTRUSTED: reason = "cert untrusted"; break; case X509_V_ERR_CERT_REJECTED: reason = "cert rejected"; break; /* These are 'informational' when looking for issuer cert */ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: reason = "cert issuer/issuer subject mismatch"; break; case X509_V_ERR_AKID_SKID_MISMATCH: reason = "cert akid/issuer skid mismatch"; break; case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: reason = "cert akid/issuer serial mismatch"; break; case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: reason = "keyusage no certsign"; break; /* The application is not happy */ case X509_V_ERR_APPLICATION_VERIFICATION: reason = "application-specific verification error"; break; default: reason = "unknown reason"; } printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate could not be verified: %s (%d)\n"), reason, r); return -1; } /* check the common name */ conn->peer = SSL_get_peer_certificate(conn->ssl); X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer), NID_commonName, peerName, sizeof peerName); if (strcasecmp(peerName, conn->pghost) != 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate name does not match hostname\n")); return -1; } return 0; } /* * Close a SSL connection. */ void close_SSL (PGconn *conn) { if (conn->ssl) { SSL_shutdown(conn->ssl); SSL_free(conn->ssl); conn->ssl = NULL; } } /* * Accessor function that retrieves SSL connection pointer. */ SSL * PQgetssl (PGconn *conn) { if (!conn) return NULL; return conn->ssl; } /* * 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 */