int openconnect_open_https(struct openconnect_info *vpninfo) { method_const SSL_METHOD *ssl3_method; SSL *https_ssl; BIO *https_bio; int ssl_sock; int err; if (vpninfo->https_ssl) return 0; if (vpninfo->peer_cert) { X509_free(vpninfo->peer_cert); vpninfo->peer_cert = NULL; } ssl_sock = connect_https_socket(vpninfo); if (ssl_sock < 0) return ssl_sock; ssl3_method = TLSv1_client_method(); if (!vpninfo->https_ctx) { vpninfo->https_ctx = SSL_CTX_new(ssl3_method); /* Some servers (or their firewalls) really don't like seeing extensions. */ #ifdef SSL_OP_NO_TICKET SSL_CTX_set_options(vpninfo->https_ctx, SSL_OP_NO_TICKET); #endif if (vpninfo->cert) { err = load_certificate(vpninfo); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Loading certificate failed. Aborting.\n")); SSL_CTX_free(vpninfo->https_ctx); vpninfo->https_ctx = NULL; close(ssl_sock); return err; } check_certificate_expiry(vpninfo); } /* We just want to do: SSL_CTX_set_purpose(vpninfo->https_ctx, X509_PURPOSE_ANY); ... but it doesn't work with OpenSSL < 0.9.8k because of problems with inheritance (fixed in v1.1.4.6 of crypto/x509/x509_vpm.c) so we have to play silly buggers instead. This trick doesn't work _either_ in < 0.9.7 but I don't know of _any_ workaround which will, and can't be bothered to find out either. */ #if OPENSSL_VERSION_NUMBER >= 0x00908000 SSL_CTX_set_cert_verify_callback(vpninfo->https_ctx, ssl_app_verify_callback, NULL); #endif SSL_CTX_set_default_verify_paths(vpninfo->https_ctx); #ifdef ANDROID_KEYSTORE if (vpninfo->cafile && !strncmp(vpninfo->cafile, "keystore:", 9)) { STACK_OF(X509_INFO) *stack; X509_STORE *store; X509_INFO *info; BIO *b = BIO_from_keystore(vpninfo, vpninfo->cafile); if (!b) { SSL_CTX_free(vpninfo->https_ctx); vpninfo->https_ctx = NULL; close(ssl_sock); return -EINVAL; } stack = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL); BIO_free(b); if (!stack) { vpn_progress(vpninfo, PRG_ERR, _("Failed to read certs from CA file '%s'\n"), vpninfo->cafile); openconnect_report_ssl_errors(vpninfo); SSL_CTX_free(vpninfo->https_ctx); vpninfo->https_ctx = NULL; close(ssl_sock); return -ENOENT; } store = SSL_CTX_get_cert_store(vpninfo->https_ctx); while ((info = sk_X509_INFO_pop(stack))) { if (info->x509) X509_STORE_add_cert(store, info->x509); if (info->crl) X509_STORE_add_crl(store, info->crl); X509_INFO_free(info); } sk_X509_INFO_free(stack); } else #endif if (vpninfo->cafile) { if (!SSL_CTX_load_verify_locations(vpninfo->https_ctx, vpninfo->cafile, NULL)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to open CA file '%s'\n"), vpninfo->cafile); openconnect_report_ssl_errors(vpninfo); SSL_CTX_free(vpninfo->https_ctx); vpninfo->https_ctx = NULL; close(ssl_sock); return -EINVAL; } } } https_ssl = SSL_new(vpninfo->https_ctx); workaround_openssl_certchain_bug(vpninfo, https_ssl); https_bio = BIO_new_socket(ssl_sock, BIO_NOCLOSE); BIO_set_nbio(https_bio, 1); SSL_set_bio(https_ssl, https_bio, https_bio); vpn_progress(vpninfo, PRG_INFO, _("SSL negotiation with %s\n"), vpninfo->hostname); while ((err = SSL_connect(https_ssl)) <= 0) { fd_set wr_set, rd_set; int maxfd = ssl_sock; FD_ZERO(&wr_set); FD_ZERO(&rd_set); err = SSL_get_error(https_ssl, err); if (err == SSL_ERROR_WANT_READ) FD_SET(ssl_sock, &rd_set); else if (err == SSL_ERROR_WANT_WRITE) FD_SET(ssl_sock, &wr_set); else { vpn_progress(vpninfo, PRG_ERR, _("SSL connection failure\n")); openconnect_report_ssl_errors(vpninfo); SSL_free(https_ssl); close(ssl_sock); return -EINVAL; } cmd_fd_set(vpninfo, &rd_set, &maxfd); select(maxfd + 1, &rd_set, &wr_set, NULL, NULL); if (is_cancel_pending(vpninfo, &rd_set)) { vpn_progress(vpninfo, PRG_ERR, _("SSL connection cancelled\n")); SSL_free(https_ssl); close(ssl_sock); return -EINVAL; } } if (verify_peer(vpninfo, https_ssl)) { SSL_free(https_ssl); close(ssl_sock); return -EINVAL; } vpninfo->ssl_fd = ssl_sock; vpninfo->https_ssl = https_ssl; /* Stash this now, because it might not be available later if the server has disconnected. */ vpninfo->peer_cert = SSL_get_peer_certificate(vpninfo->https_ssl); vpn_progress(vpninfo, PRG_INFO, _("Connected to HTTPS on %s\n"), vpninfo->hostname); return 0; }
/* * Attempt to negotiate SSL connection. */ static PostgresPollingStatusType open_client_SSL(PGconn *conn) { int r; r = SSL_connect(conn->ssl); if (r <= 0) { int err = SSL_get_error(conn->ssl, r); switch (err) { case SSL_ERROR_WANT_READ: return PGRES_POLLING_READING; case SSL_ERROR_WANT_WRITE: return PGRES_POLLING_WRITING; case SSL_ERROR_SYSCALL: { char sebuf[256]; if (r == -1) printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL SYSCALL error: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); else printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL SYSCALL error: EOF detected\n")); close_SSL(conn); return PGRES_POLLING_FAILED; } case SSL_ERROR_SSL: { /* * If there are problems with the local certificate files, * these will be detected by client_cert_cb() which is * called from SSL_connect(). We want to return that * error message and not the rather unhelpful error that * OpenSSL itself returns. So check to see if an error * message was already stored. */ if (conn->errorMessage.len == 0) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL error: %s\n"), err); SSLerrfree(err); } close_SSL(conn); return PGRES_POLLING_FAILED; } default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("unrecognized SSL error code: %d\n"), err); close_SSL(conn); return PGRES_POLLING_FAILED; } } /* check the certificate chain of the server */ #ifdef NOT_USED /* CLIENT CERTIFICATES NOT REQUIRED bjm 2002-09-26 */ /* * 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 PGRES_POLLING_FAILED; } #endif /* pull out server distinguished and common names */ conn->peer = SSL_get_peer_certificate(conn->ssl); if (conn->peer == NULL) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate could not be obtained: %s\n"), err); SSLerrfree(err); close_SSL(conn); return PGRES_POLLING_FAILED; } 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 */ #ifdef NOT_USED /* CLIENT CERTIFICATES NOT REQUIRED bjm 2002-09-26 */ /* * 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 PGRES_POLLING_FAILED; } #endif /* SSL handshake is complete */ return PGRES_POLLING_OK; }