From a4a9842bceea0d0d498223bd450b162a8eef1aa4 Mon Sep 17 00:00:00 2001
From: Maxime Bizon <mbizon@freebox.fr>
Date: Fri, 3 Jan 2020 17:35:39 +0100
Subject: [PATCH 2/5] add support for fbxpki engine

---
 src/openvpn/options.c     |  16 +++--
 src/openvpn/ssl_openssl.c | 141 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 151 insertions(+), 6 deletions(-)

diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index b029652d..f51fc947 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -3232,6 +3232,7 @@ options_postprocess_mutate(struct options *o)
 #define CHKACC_FILEXSTWR (1<<2)  /** If file exists, is it writable? */
 #define CHKACC_ACPTSTDIN (1<<3)  /** If filename is stdin, it's allowed and "exists" */
 #define CHKACC_PRIVATE (1<<4)    /** Warn if this (private) file is group/others accessible */
+#define CHKACC_FILE_FBXPKI (1<<5) /** same as file, but with fbxpki: prefix allowed */
 
 static bool
 check_file_access(const int type, const char *file, const int mode, const char *opt)
@@ -3271,6 +3272,13 @@ check_file_access(const int type, const char *file, const int mode, const char *
         errcode = errno;
     }
 
+    if (!errcode && (type & CHKACC_FILE_FBXPKI)) {
+	    if (strncmp(file, "fbxpki:", 7) && (platform_access(file, mode) != 0) )
+	    {
+		    errcode = errno;
+	    }
+    }
+
     /* If the file exists and is accessible, is it writable? */
     if (!errcode && (type & CHKACC_FILEXSTWR) && (platform_access(file, F_OK) == 0) )
     {
@@ -3281,7 +3289,7 @@ check_file_access(const int type, const char *file, const int mode, const char *
     }
 
     /* Warn if a given private file is group/others accessible. */
-    if (type & CHKACC_PRIVATE)
+    if ((type & CHKACC_PRIVATE) && !(type & CHKACC_FILE_FBXPKI))
     {
         platform_stat_t st;
         if (platform_stat(file, &st))
@@ -3441,13 +3449,13 @@ options_postprocess_filechecks(struct options *options)
     errs |= check_file_access_inline(options->dh_file_inline, CHKACC_FILE,
                                      options->dh_file, R_OK, "--dh");
 
-    errs |= check_file_access_inline(options->ca_file_inline, CHKACC_FILE,
+    errs |= check_file_access_inline(options->ca_file_inline, CHKACC_FILE_FBXPKI,
                                      options->ca_file, R_OK, "--ca");
 
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
                                      options->ca_path, R_OK, "--capath");
 
-    errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,
+    errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE_FBXPKI,
                                      options->cert_file, R_OK, "--cert");
 
     errs |= check_file_access_inline(options->extra_certs_file, CHKACC_FILE,
@@ -3459,7 +3467,7 @@ options_postprocess_filechecks(struct options *options)
 #endif
     {
         errs |= check_file_access_inline(options->priv_key_file_inline,
-                                         CHKACC_FILE|CHKACC_PRIVATE,
+                                         CHKACC_FILE_FBXPKI|CHKACC_PRIVATE,
                                          options->priv_key_file, R_OK, "--key");
     }
 
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 28b0c399..64304f22 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -65,6 +65,11 @@
 #include <openssl/ec.h>
 #endif
 
+#include <fbxpki_ipc.h>
+#if HAVE_OPENSSL_ENGINE
+#include <openssl/engine.h>
+#endif
+
 /*
  * Allocate space in SSL objects in which to store a struct tls_session
  * pointer back to parent.
@@ -939,6 +944,47 @@ tls_ctx_add_extra_certs(struct tls_root_ctx *ctx, BIO *bio, bool optional)
     }
 }
 
+static int get_fbxpki_cert_list(const char *key,
+               int cmd_type,
+               char **ret_certs,
+               size_t *ret_certs_len)
+{
+    struct fbxpki_ipc_hdr *cmd;
+    uint8_t *certs;
+    size_t cmdlen, certs_len;
+    int fd, ret;
+
+    fd = fbxpki_ipc_open_fd();
+    if (fd < 0)
+        return 1;
+
+    cmdlen = sizeof (*cmd) + strlen(key) + 1;
+    cmd = malloc(cmdlen);
+    if (!cmd) {
+        close(fd);
+        return 1;
+    }
+
+    FBXPKI_IPC_FILL(cmd, cmd_type, 0, cmdlen);
+    memcpy(cmd->data, key, strlen(key) + 1);
+
+    ret = fbxpki_ipc_do_cmd(fd, cmd, cmdlen, &certs, &certs_len);
+    free(cmd);
+    close(fd);
+    if (ret)
+        return 1;
+
+    if (certs_len && certs[certs_len - 1]) {
+        /* must be null terminated */
+        free(certs);
+        return 1;
+    }
+
+    *ret_certs = (char *)certs;
+    *ret_certs_len = certs_len;
+    return 0;
+}
+
 void
 tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
                        bool cert_file_inline)
@@ -946,10 +992,33 @@ tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
     BIO *in = NULL;
     X509 *x = NULL;
     int ret = 0;
+    char *fbxpki_certs = NULL;
+    size_t fbxpki_certs_len;
 
     ASSERT(NULL != ctx);
 
-    if (cert_file_inline)
+    if (!strncmp(cert_file, "fbxpki:", 7)) {
+        char *p;
+
+        if (get_fbxpki_cert_list(cert_file + 7,
+                     E_CMD_GET_CERT_CHAIN,
+                     &fbxpki_certs,
+                     &fbxpki_certs_len))
+            crypto_msg(M_FATAL, "Cannot get cert with fbxpki");
+
+        if (!fbxpki_certs_len)
+            crypto_msg(M_FATAL, "fbxpki cert not found");
+
+        /* concatenate all pems */
+        p = fbxpki_certs;
+        for (size_t i = 0; i < fbxpki_certs_len - 1; i++) {
+            if (!p[i])
+                p[i] = '\n';
+        }
+
+        in = BIO_new_mem_buf(fbxpki_certs, -1);
+    }
+    else if (cert_file_inline)
     {
         in = BIO_new_mem_buf((char *) cert_file, -1);
     }
@@ -1004,6 +1073,55 @@ end:
     {
         X509_free(x);
     }
+    if (fbxpki_certs)
+	    free(fbxpki_certs);
+}
+
+static int fbxpki_setup_key(struct tls_root_ctx *ctx,
+                const char *path)
+{
+#if HAVE_OPENSSL_ENGINE
+    SSL_CTX *ssl_ctx = ctx->ctx;
+    ENGINE *e;
+    EVP_PKEY *pkey;
+
+    e = ENGINE_by_id("fbxpki");
+    if (!e) {
+        crypto_msg(M_WARN, "failed to find fbxpki engine");
+        return 1;
+    }
+
+    if (!ENGINE_init(e)) {
+        crypto_msg(M_WARN, "failed to init fbxpki engine");
+        ENGINE_free(e);
+        return 1;
+    }
+
+    pkey = ENGINE_load_private_key(e, path, NULL, NULL);
+    ENGINE_finish(e);
+    ENGINE_free(e);
+
+    if (!pkey) {
+        crypto_msg(M_WARN, "failed to load key from fbxpki engine");
+        return 1;
+    }
+
+    if (!SSL_CTX_use_PrivateKey(ssl_ctx, pkey)) {
+        crypto_msg(M_WARN, "failed to use key from fbxpki engine");
+        return 1;
+    }
+
+    /* Check Private Key */
+    if (!SSL_CTX_check_private_key(ssl_ctx))
+    {
+        crypto_msg(M_FATAL, "Private key does not match the certificate");
+    }
+
+    return 0;
+#else
+    crypto_msg(M_FATAL, "engine support not enabled");
+    return 1;
+#endif
 }
 
 int
@@ -1023,6 +1141,8 @@ tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
     {
         in = BIO_new_mem_buf((char *) priv_key_file, -1);
     }
+    else if (!strncmp(priv_key_file, "fbxpki:", 7))
+        return fbxpki_setup_key(ctx, priv_key_file + 7);
     else
     {
         in = BIO_new_file(priv_key_file, "r");
@@ -1544,6 +1664,8 @@ tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
     X509_NAME *xn = NULL;
     BIO *in = NULL;
     int i, added = 0, prev = 0;
+    char *fbxpki_certs = NULL;
+    size_t fbxpki_certs_len;
 
     ASSERT(NULL != ctx);
 
@@ -1556,7 +1678,19 @@ tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
     /* Try to add certificates and CRLs from ca_file */
     if (ca_file)
     {
-        if (ca_file_inline)
+        if (!strncmp(ca_file, "fbxpki:", 7)) {
+           if (get_fbxpki_cert_list(ca_file + 7,
+                        E_CMD_GET_CERT,
+                        &fbxpki_certs,
+                        &fbxpki_certs_len))
+               crypto_msg(M_FATAL, "Cannot get ca with fbxpki");
+
+           if (!fbxpki_certs_len)
+               crypto_msg(M_FATAL, "fbxpki ca not found");
+
+           in = BIO_new_mem_buf(fbxpki_certs, -1);
+        }
+        else if (ca_file_inline)
         {
             in = BIO_new_mem_buf((char *)ca_file, -1);
         }
@@ -1667,6 +1801,9 @@ tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
         {
             BIO_free(in);
         }
+
+        if (fbxpki_certs)
+            free(fbxpki_certs);
     }
 
     /* Set a store for certs (CA & CRL) with a lookup on the "capath" hash directory */
-- 
2.32.0

