Don't require zlib in pkgconfig if it was found without it
[platform/upstream/openconnect.git] / openssl.c
index 9b617cf..851a711 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -23,6 +23,7 @@
  */
 
 #include <errno.h>
+#include <sys/types.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <ctype.h>
@@ -51,7 +52,7 @@ int openconnect_sha1(unsigned char *result, void *data, int len)
 }
 
 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;
@@ -162,19 +163,6 @@ int openconnect_SSL_read(struct openconnect_info *vpninfo, char *buf, size_t len
        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;
@@ -236,6 +224,163 @@ int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len
        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;
@@ -245,8 +390,8 @@ static int pem_pw_cb(char *buf, int len, int w, void *v)
        if (vpninfo->cert_password) {
                pass = vpninfo->cert_password;
                vpninfo->cert_password = NULL;
-       } else if (request_passphrase(vpninfo, &pass,
-                                     _("Enter PEM pass phrase:")))
+       } else if (request_passphrase(vpninfo, "openconnect_pem",
+                                     &pass, _("Enter PEM pass phrase:")))
                return -1;
 
        plen = strlen(pass);
@@ -280,7 +425,7 @@ static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12
           when PKCS12_parse() returns an error, but *ca is left pointing
           to the freed memory. */
        ca = NULL;
-       if (!pass && request_passphrase(vpninfo, &pass,
+       if (!pass && request_passphrase(vpninfo, "openconnect_pkcs12", &pass,
                                        _("Enter PKCS#12 pass phrase:")) < 0) {
                PKCS12_free(p12);
                return -EINVAL;
@@ -303,11 +448,17 @@ static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12
                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!"));
@@ -359,6 +510,9 @@ static int load_tpm_certificate(struct openconnect_info *vpninfo)
 {
        ENGINE *e;
        EVP_PKEY *key;
+       UI_METHOD *meth = NULL;
+       int ret = 0;
+
        ENGINE_load_builtin_engines();
 
        e = ENGINE_by_id("tpm");
@@ -382,24 +536,32 @@ static int load_tpm_certificate(struct openconnect_info *vpninfo)
                                     _("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)
@@ -413,6 +575,7 @@ 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;
@@ -426,19 +589,91 @@ static int reload_pem_cert(struct openconnect_info *vpninfo)
                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;
 
@@ -465,16 +700,69 @@ static int load_certificate(struct openconnect_info *vpninfo)
        }
 
        /* 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");
@@ -517,31 +805,15 @@ static int load_certificate(struct openconnect_info *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];
@@ -557,13 +829,13 @@ static int get_cert_fingerprint(struct openconnect_info *vpninfo,
 }
 
 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);
 }
@@ -1045,6 +1317,42 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
 #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,
@@ -1121,7 +1429,7 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
        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);
@@ -1138,9 +1446,19 @@ void openconnect_close_https(struct openconnect_info *vpninfo)
                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 ();
@@ -1149,7 +1467,7 @@ void openconnect_init_openssl(void)
 }
 
 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;