From 7ea3d78d5fd3940bcc3ffcd6d0a2a0a2c2464efd 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/4] add support for fbxpki engine

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

diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index cfdfa7d6..15da9276 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -3053,6 +3053,7 @@ options_postprocess_mutate(struct options *o)
 #define CHKACC_INLINE (1<<3)     /** File is present if it's an inline file */
 #define CHKACC_ACPTSTDIN (1<<4)  /** If filename is stdin, it's allowed and "exists" */
 #define CHKACC_PRIVATE (1<<5)    /** Warn if this (private) file is group/others accessible */
+#define CHKACC_FILE_FBXPKI (1<<6)       /** 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)
@@ -3098,6 +3099,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) )
     {
@@ -3108,7 +3116,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))
@@ -3241,16 +3249,16 @@ options_postprocess_filechecks(struct options *options)
 #ifdef ENABLE_CRYPTO
     /* ** SSL/TLS/crypto related files ** */
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->dh_file, R_OK, "--dh");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca");
+    errs |= check_file_access(CHKACC_FILE_FBXPKI|CHKACC_INLINE, 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(CHKACC_FILE|CHKACC_INLINE, options->cert_file, R_OK, "--cert");
+    errs |= check_file_access(CHKACC_FILE_FBXPKI|CHKACC_INLINE, options->cert_file, R_OK, "--cert");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->extra_certs_file, R_OK,
                               "--extra-certs");
 #ifdef MANAGMENT_EXTERNAL_KEY
     if (!(options->management_flags & MF_EXTERNAL_KEY))
 #endif
     {
-        errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+        errs |= check_file_access(CHKACC_FILE_FBXPKI|CHKACC_INLINE|CHKACC_PRIVATE,
                                   options->priv_key_file, R_OK, "--key");
     }
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index a78dae99..200794d2 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -60,6 +60,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.
@@ -852,6 +857,46 @@ tls_ctx_add_extra_certs(struct tls_root_ctx *ctx, BIO *bio)
     }
 }
 
+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);
+	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;
+}
+
 /* Like tls_ctx_load_cert, but returns a copy of the certificate in **X509 */
 static void
 tls_ctx_load_cert_file_and_copy(struct tls_root_ctx *ctx,
@@ -862,6 +907,8 @@ tls_ctx_load_cert_file_and_copy(struct tls_root_ctx *ctx,
     X509 *x = NULL;
     int ret = 0;
     bool inline_file = false;
+    char *fbxpki_certs = NULL;
+    size_t fbxpki_certs_len;
 
     ASSERT(NULL != ctx);
     if (NULL != x509)
@@ -871,7 +918,28 @@ tls_ctx_load_cert_file_and_copy(struct tls_root_ctx *ctx,
 
     inline_file = (strcmp(cert_file, INLINE_FILE_TAG) == 0);
 
-    if (inline_file && 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 (inline_file && cert_file_inline)
     {
         in = BIO_new_mem_buf((char *)cert_file_inline, -1);
     }
@@ -926,6 +994,8 @@ end:
     {
         X509_free(x);
     }
+    if (fbxpki_certs)
+	    free(fbxpki_certs);
 }
 
 void
@@ -935,6 +1005,53 @@ tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
     tls_ctx_load_cert_file_and_copy(ctx, cert_file, cert_file_inline, NULL);
 }
 
+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
 tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
                        const char *priv_key_file_inline
@@ -949,6 +1066,9 @@ tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
 
     ssl_ctx = ctx->ctx;
 
+    if (!strncmp(priv_key_file, "fbxpki:", 7))
+	    return fbxpki_setup_key(ctx, priv_key_file + 7);
+
     if (!strcmp(priv_key_file, INLINE_FILE_TAG) && priv_key_file_inline)
     {
         in = BIO_new_mem_buf((char *)priv_key_file_inline, -1);
@@ -1279,7 +1399,22 @@ 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 (!strcmp(ca_file, INLINE_FILE_TAG) && ca_file_inline)
+	char *fbxpki_certs = NULL;
+	size_t fbxpki_certs_len;
+
+	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 (!strcmp(ca_file, INLINE_FILE_TAG) && ca_file_inline)
         {
             in = BIO_new_mem_buf((char *)ca_file_inline, -1);
         }
@@ -1389,6 +1524,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.17.1

