*/
#include <errno.h>
+#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
}
int openconnect_get_cert_DER(struct openconnect_info *vpninfo,
- struct x509_st *cert, unsigned char **buf)
+ OPENCONNECT_X509 *cert, unsigned char **buf)
{
BIO *bp = BIO_new(BIO_s_mem());
BUF_MEM *certinfo;
return done;
}
-static int print_err(const char *str, size_t len, void *ptr)
-{
- struct openconnect_info *vpninfo = ptr;
-
- vpn_progress(vpninfo, PRG_ERR, "%s", str);
- return 0;
-}
-
-void openconnect_report_ssl_errors(struct openconnect_info *vpninfo)
-{
- ERR_print_errors_cb(print_err, vpninfo);
-}
-
int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len)
{
int i = 0;
return i ?: ret;
}
+
+/* UI handling. All this just to handle the PIN callback from the TPM ENGINE,
+ and turn it into a call to our ->process_auth_form function */
+
+struct ui_data {
+ struct openconnect_info *vpninfo;
+ struct oc_form_opt **last_opt;
+ struct oc_auth_form form;
+};
+
+struct ui_form_opt {
+ struct oc_form_opt opt;
+ UI_STRING *uis;
+};
+
+ /* Ick. But there is no way to pass this sanely through OpenSSL */
+static struct openconnect_info *ui_vpninfo;
+
+static int ui_open(UI *ui)
+{
+ struct openconnect_info *vpninfo = ui_vpninfo; /* Ick */
+ struct ui_data *ui_data;
+
+ if (!vpninfo || !vpninfo->process_auth_form)
+ return 0;
+
+ ui_data = malloc(sizeof(*ui_data));
+ if (!ui_data)
+ return 0;
+
+ memset(ui_data, 0, sizeof(*ui_data));
+ ui_data->last_opt = &ui_data->form.opts;
+ ui_data->vpninfo = vpninfo;
+ ui_data->form.auth_id = (char *)"openssl_ui";
+ UI_add_user_data(ui, ui_data);
+
+ return 1;
+}
+
+static int ui_write(UI *ui, UI_STRING *uis)
+{
+ struct ui_data *ui_data = UI_get0_user_data(ui);
+ struct ui_form_opt *opt;
+
+ switch(UI_get_string_type(uis)) {
+ case UIT_ERROR:
+ ui_data->form.error = (char *)UI_get0_output_string(uis);
+ break;
+ case UIT_INFO:
+ ui_data->form.message = (char *)UI_get0_output_string(uis);
+ break;
+ case UIT_PROMPT:
+ opt = malloc(sizeof(*opt));
+ if (!opt)
+ return 1;
+ memset(opt, 0, sizeof(*opt));
+ opt->uis = uis;
+ opt->opt.label = opt->opt.name = (char *)UI_get0_output_string(uis);
+ if (UI_get_input_flags(uis) & UI_INPUT_FLAG_ECHO)
+ opt->opt.type = OC_FORM_OPT_TEXT;
+ else
+ opt->opt.type = OC_FORM_OPT_PASSWORD;
+ *(ui_data->last_opt) = &opt->opt;
+ ui_data->last_opt = &opt->opt.next;
+ break;
+
+ default:
+ fprintf(stderr, "Unhandled SSL UI request type %d\n",
+ UI_get_string_type(uis));
+ return 0;
+ }
+ return 1;
+}
+
+static int ui_flush(UI *ui)
+{
+ struct ui_data *ui_data = UI_get0_user_data(ui);
+ struct openconnect_info *vpninfo = ui_data->vpninfo;
+ struct ui_form_opt *opt;
+ int ret;
+
+ ret = vpninfo->process_auth_form(vpninfo->cbdata, &ui_data->form);
+ if (ret)
+ return 0;
+
+ for (opt = (struct ui_form_opt *)ui_data->form.opts; opt;
+ opt = (struct ui_form_opt *)opt->opt.next) {
+ if (opt->opt.value && opt->uis)
+ UI_set_result(ui, opt->uis, opt->opt.value);
+ }
+ return 1;
+}
+
+static int ui_close(UI *ui)
+{
+ struct ui_data *ui_data = UI_get0_user_data(ui);
+ struct ui_form_opt *opt, *next_opt;
+
+ opt = (struct ui_form_opt *)ui_data->form.opts;
+ while (opt) {
+ next_opt = (struct ui_form_opt *)opt->opt.next;
+ if (opt->opt.value)
+ free(opt->opt.value);
+ free(opt);
+ opt = next_opt;
+ }
+ free(ui_data);
+ UI_add_user_data(ui, NULL);
+
+ return 1;
+}
+
+static UI_METHOD *create_openssl_ui(struct openconnect_info *vpninfo)
+{
+ UI_METHOD *ui_method = UI_create_method((char *)"AnyConnect VPN UI");
+
+ /* There is a race condition here because of the use of the
+ static ui_vpninfo pointer. This sucks, but it's OpenSSL's
+ fault and in practice it's *never* going to hurt us.
+
+ This UI is only used for loading certificates from a TPM; for
+ PKCS#12 and PEM files we hook the passphrase request differently.
+ The ui_vpninfo variable is set here, and is used from ui_open()
+ when the TPM ENGINE decides it needs to ask the user for a PIN.
+
+ The race condition exists because theoretically, there
+ could be more than one thread using libopenconnect and
+ trying to authenticate to a VPN server, within the *same*
+ process. And if *both* are using certificates from the TPM,
+ and *both* manage to be within that short window of time
+ between setting ui_vpninfo and invoking ui_open() to fetch
+ the PIN, then one connection's ->process_auth_form() could
+ get a PIN request for the *other* connection.
+
+ However, the only thing that ever does run libopenconnect more
+ than once from the same process is KDE's NetworkManager support,
+ and NetworkManager doesn't *support* having more than one VPN
+ connected anyway, so first that would have to be fixed and then
+ you'd have to connect to two VPNs simultaneously by clicking
+ 'connect' on both at *exactly* the same time and then getting
+ *really* unlucky.
+
+ Oh, and the KDE support won't be using OpenSSL anyway because of
+ licensing conflicts... so although this sucks, I'm not going to
+ lose sleep over it.
+ */
+ ui_vpninfo = vpninfo;
+
+ /* Set up a UI method of our own for password/passphrase requests */
+ UI_method_set_opener(ui_method, ui_open);
+ UI_method_set_writer(ui_method, ui_write);
+ UI_method_set_flusher(ui_method, ui_flush);
+ UI_method_set_closer(ui_method, ui_close);
+
+ return ui_method;
+}
+
static int pem_pw_cb(char *buf, int len, int w, void *v)
{
struct openconnect_info *vpninfo = v;
+ char *pass = NULL;
+ int plen;
- /* Only try the provided password once... */
- SSL_CTX_set_default_passwd_cb(vpninfo->https_ctx, NULL);
- SSL_CTX_set_default_passwd_cb_userdata(vpninfo->https_ctx, NULL);
+ if (vpninfo->cert_password) {
+ pass = vpninfo->cert_password;
+ vpninfo->cert_password = NULL;
+ } else if (request_passphrase(vpninfo, "openconnect_pem",
+ &pass, _("Enter PEM pass phrase:")))
+ return -1;
- if (len <= strlen(vpninfo->cert_password)) {
+ plen = strlen(pass);
+
+ if (len <= plen) {
vpn_progress(vpninfo, PRG_ERR,
- _("PEM password too long (%zd >= %d)\n"),
- strlen(vpninfo->cert_password), len);
+ _("PEM password too long (%d >= %d)\n"),
+ plen, len);
+ free(pass);
return -1;
}
- strcpy(buf, vpninfo->cert_password);
- return strlen(vpninfo->cert_password);
+
+ memcpy(buf, pass, plen+1);
+ free(pass);
+ return plen;
}
static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12)
X509 *cert = NULL;
STACK_OF(X509) *ca;
int ret = 0;
- char pass[PEM_BUFSIZE];
+ char *pass;
+ pass = vpninfo->cert_password;
+ vpninfo->cert_password = NULL;
retrypass:
/* We do this every time round the loop, to work around a bug in
OpenSSL < 1.0.0-beta2 -- where the stack at *ca will be freed
when PKCS12_parse() returns an error, but *ca is left pointing
to the freed memory. */
ca = NULL;
- if (!vpninfo->cert_password) {
- if (EVP_read_pw_string(pass, PEM_BUFSIZE,
- "Enter PKCS#12 pass phrase:", 0))
- return -EINVAL;
+ if (!pass && request_passphrase(vpninfo, "openconnect_pkcs12", &pass,
+ _("Enter PKCS#12 pass phrase:")) < 0) {
+ PKCS12_free(p12);
+ return -EINVAL;
}
- if (!PKCS12_parse(p12, vpninfo->cert_password?:pass, &pkey, &cert, &ca)) {
+ if (!PKCS12_parse(p12, pass, &pkey, &cert, &ca)) {
unsigned long err = ERR_peek_error();
openconnect_report_ssl_errors(vpninfo);
ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
vpn_progress(vpninfo, PRG_ERR,
_("Parse PKCS#12 failed (wrong passphrase?)\n"));
- vpninfo->cert_password = NULL;
+ free(pass);
+ pass = NULL;
goto retrypass;
}
vpn_progress(vpninfo, PRG_ERR,
_("Parse PKCS#12 failed (see above errors)\n"));
PKCS12_free(p12);
+ free(pass);
return -EINVAL;
}
+ free(pass);
if (cert) {
+ char buf[200];
vpninfo->cert_x509 = cert;
SSL_CTX_use_certificate(vpninfo->https_ctx, cert);
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ vpn_progress(vpninfo, PRG_INFO,
+ _("Using client certificate '%s'\n"), buf);
} else {
vpn_progress(vpninfo, PRG_ERR,
_("PKCS#12 contained no certificate!"));
{
ENGINE *e;
EVP_PKEY *key;
+ UI_METHOD *meth = NULL;
+ int ret = 0;
+
ENGINE_load_builtin_engines();
e = ENGINE_by_id("tpm");
_("Failed to set TPM SRK password\n"));
openconnect_report_ssl_errors(vpninfo);
}
+ vpninfo->cert_password = NULL;
+ free(vpninfo->cert_password);
+ } else {
+ /* Provide our own UI method to handle the PIN callback. */
+ meth = create_openssl_ui(vpninfo);
}
- key = ENGINE_load_private_key(e, vpninfo->sslkey, NULL, NULL);
+ key = ENGINE_load_private_key(e, vpninfo->sslkey, meth, NULL);
+ if (meth)
+ UI_destroy_method(meth);
if (!key) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to load TPM private key\n"));
openconnect_report_ssl_errors(vpninfo);
- ENGINE_free(e);
- ENGINE_finish(e);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, key)) {
vpn_progress(vpninfo, PRG_ERR, _("Add key from TPM failed\n"));
openconnect_report_ssl_errors(vpninfo);
- ENGINE_free(e);
- ENGINE_finish(e);
- return -EINVAL;
+ ret = -EINVAL;
}
- return 0;
+ EVP_PKEY_free(key);
+ out:
+ ENGINE_finish(e);
+ ENGINE_free(e);
+ return ret;
}
#else
static int load_tpm_certificate(struct openconnect_info *vpninfo)
static int reload_pem_cert(struct openconnect_info *vpninfo)
{
BIO *b = BIO_new(BIO_s_file_internal());
+ char buf[200];
if (!b)
return -ENOMEM;
return -EIO;
}
vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, NULL, NULL);
+ BIO_free(b);
if (!vpninfo->cert_x509)
goto err;
+ X509_NAME_oneline(X509_get_subject_name(vpninfo->cert_x509), buf, sizeof(buf));
+ vpn_progress(vpninfo, PRG_INFO,
+ _("Using client certificate '%s'\n"), buf);
+
+ return 0;
+}
+
+#ifdef ANDROID_KEYSTORE
+static BIO *BIO_from_keystore(struct openconnect_info *vpninfo, const char *item)
+{
+ unsigned char *content;
+ BIO *b;
+ int len;
+ const char *p = item + 9;
+
+ /* Skip first two slashes if the user has given it as
+ keystore://foo ... */
+ if (*p == '/')
+ p++;
+ if (*p == '/')
+ p++;
+
+ len = keystore_fetch(p, &content);
+ if (len < 0) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to load item '%s' from keystore: %s\n"),
+ p, keystore_strerror(len));
+ return NULL;
+ }
+ if (!(b = BIO_new(BIO_s_mem())) || BIO_write(b, content, len) != len) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to create BIO for keystore item '%s'\n"),
+ p);
+ free(content);
+ BIO_free(b);
+ return NULL;
+ }
+ free(content);
+ return b;
+}
+#endif
+
+static int is_pem_password_error(struct openconnect_info *vpninfo)
+{
+ unsigned long err = ERR_peek_error();
+
+ openconnect_report_ssl_errors(vpninfo);
+
+#ifndef EVP_F_EVP_DECRYPTFINAL_EX
+#define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL
+#endif
+ /* If the user fat-fingered the passphrase, try again */
+ if (ERR_GET_LIB(err) == ERR_LIB_EVP &&
+ ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX &&
+ ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Loading private key failed (wrong passphrase?)\n"));
+ ERR_clear_error();
+ return 1;
+ }
+
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Loading private key failed (see above errors)\n"));
return 0;
}
static int load_certificate(struct openconnect_info *vpninfo)
{
+ if (!strncmp(vpninfo->sslkey, "pkcs11:", 7) ||
+ !strncmp(vpninfo->cert, "pkcs11:", 7)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("This binary built without PKCS#11 support\n"));
+ return -EINVAL;
+ }
+
vpn_progress(vpninfo, PRG_TRACE,
_("Using certificate file %s\n"), vpninfo->cert);
- if (vpninfo->cert_type == CERT_TYPE_PKCS12 ||
- vpninfo->cert_type == CERT_TYPE_UNKNOWN) {
+ if (strncmp(vpninfo->cert, "keystore:", 9) &&
+ (vpninfo->cert_type == CERT_TYPE_PKCS12 ||
+ vpninfo->cert_type == CERT_TYPE_UNKNOWN)) {
FILE *f;
PKCS12 *p12;
}
/* It's PEM or TPM now, and either way we need to load the plain cert: */
- if (!SSL_CTX_use_certificate_chain_file(vpninfo->https_ctx,
- vpninfo->cert)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Loading certificate failed\n"));
- openconnect_report_ssl_errors(vpninfo);
- return -EINVAL;
+#ifdef ANDROID_KEYSTORE
+ if (!strncmp(vpninfo->cert, "keystore:", 9)) {
+ BIO *b = BIO_from_keystore(vpninfo, vpninfo->cert);
+ if (!b)
+ return -EINVAL;
+ vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, pem_pw_cb, vpninfo);
+ BIO_free(b);
+ if (!vpninfo->cert_x509) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to load X509 certificate from keystore\n"));
+ openconnect_report_ssl_errors(vpninfo);
+ return -EINVAL;
+ }
+ if (!SSL_CTX_use_certificate(vpninfo->https_ctx, vpninfo->cert_x509)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to use X509 certificate from keystore\n"));
+ openconnect_report_ssl_errors(vpninfo);
+ X509_free(vpninfo->cert_x509);
+ vpninfo->cert_x509 = NULL;
+ return -EINVAL;
+ }
+ } else
+#endif /* ANDROID_KEYSTORE */
+ {
+ if (!SSL_CTX_use_certificate_chain_file(vpninfo->https_ctx,
+ vpninfo->cert)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Loading certificate failed\n"));
+ openconnect_report_ssl_errors(vpninfo);
+ return -EINVAL;
+ }
+
+ /* Ew, we can't get it back from the OpenSSL CTX in any sane fashion */
+ reload_pem_cert(vpninfo);
}
- /* Ew, we can't get it back from the OpenSSL CTX in any sane fashion */
- reload_pem_cert(vpninfo);
+#ifdef ANDROID_KEYSTORE
+ if (!strncmp(vpninfo->sslkey, "keystore:", 9)) {
+ EVP_PKEY *key;
+ BIO *b;
+
+ again_android:
+ b = BIO_from_keystore(vpninfo, vpninfo->sslkey);
+ if (!b)
+ return -EINVAL;
+ key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, vpninfo);
+ BIO_free(b);
+ if (!key) {
+ if (is_pem_password_error(vpninfo))
+ goto again_android;
+ return -EINVAL;
+ }
+ if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, key)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to use private key from keystore\n"));
+ EVP_PKEY_free(key);
+ X509_free(vpninfo->cert_x509);
+ vpninfo->cert_x509 = NULL;
+ return -EINVAL;
+ }
+ return 0;
+ }
+#endif /* ANDROID */
if (vpninfo->cert_type == CERT_TYPE_UNKNOWN) {
FILE *f = fopen(vpninfo->sslkey, "r");
return load_tpm_certificate(vpninfo);
/* Standard PEM certificate */
- if (vpninfo->cert_password) {
- SSL_CTX_set_default_passwd_cb(vpninfo->https_ctx,
- pem_pw_cb);
- SSL_CTX_set_default_passwd_cb_userdata(vpninfo->https_ctx,
- vpninfo);
- }
+ SSL_CTX_set_default_passwd_cb(vpninfo->https_ctx, pem_pw_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(vpninfo->https_ctx, vpninfo);
again:
if (!SSL_CTX_use_RSAPrivateKey_file(vpninfo->https_ctx, vpninfo->sslkey,
SSL_FILETYPE_PEM)) {
- unsigned long err = ERR_peek_error();
-
- openconnect_report_ssl_errors(vpninfo);
-
-#ifndef EVP_F_EVP_DECRYPTFINAL_EX
-#define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL
-#endif
- /* If the user fat-fingered the passphrase, try again */
- if (ERR_GET_LIB(err) == ERR_LIB_EVP &&
- ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX &&
- ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Loading private key failed (wrong passphrase?)\n"));
+ if (is_pem_password_error(vpninfo))
goto again;
- }
-
- vpn_progress(vpninfo, PRG_ERR,
- _("Loading private key failed (see above errors)\n"));
return -EINVAL;
}
return 0;
}
static int get_cert_fingerprint(struct openconnect_info *vpninfo,
- X509 *cert, const EVP_MD *type,
+ OPENCONNECT_X509 *cert, const EVP_MD *type,
char *buf)
{
unsigned char md[EVP_MAX_MD_SIZE];
}
int get_cert_md5_fingerprint(struct openconnect_info *vpninfo,
- X509 *cert, char *buf)
+ OPENCONNECT_X509 *cert, char *buf)
{
return get_cert_fingerprint(vpninfo, cert, EVP_md5(), buf);
}
int openconnect_get_cert_sha1(struct openconnect_info *vpninfo,
- X509 *cert, char *buf)
+ OPENCONNECT_X509 *cert, char *buf)
{
return get_cert_fingerprint(vpninfo, cert, EVP_sha1(), buf);
}
#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) {
+ 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);
+ 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,
return 0;
}
-void openconnect_close_https(struct openconnect_info *vpninfo)
+void openconnect_close_https(struct openconnect_info *vpninfo, int final)
{
if (vpninfo->peer_cert) {
X509_free(vpninfo->peer_cert);
FD_CLR(vpninfo->ssl_fd, &vpninfo->select_efds);
vpninfo->ssl_fd = -1;
}
+ if (final) {
+ if (vpninfo->https_ctx) {
+ SSL_CTX_free(vpninfo->https_ctx);
+ vpninfo->https_ctx = NULL;
+ }
+ if (vpninfo->cert_x509) {
+ X509_free(vpninfo->cert_x509);
+ vpninfo->cert_x509 = NULL;
+ }
+ }
}
-void openconnect_init_openssl(void)
+void openconnect_init_ssl(void)
{
SSL_library_init ();
ERR_clear_error ();
}
char *openconnect_get_cert_details(struct openconnect_info *vpninfo,
- struct x509_st *cert)
+ OPENCONNECT_X509 *cert)
{
BIO *bp = BIO_new(BIO_s_mem());
BUF_MEM *certinfo;