From 6869810b4ec0c7241404ab5f7bb080417871d16e Mon Sep 17 00:00:00 2001
From: daurnimator <quae@daurnimator.com>
Date: Fri, 7 Jun 2019 18:09:54 +1000
Subject: src/openssl.c: Add cert:verify() to verify a certificate without a
 store

---
 doc/luaossl.tex             |  13 +++++
 regress/167-verify-cert.lua |  47 +++++++++++++++++
 regress/regress.lua         |   2 +
 src/openssl.c               | 120 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 182 insertions(+)
 create mode 100755 regress/167-verify-cert.lua

diff --git a/doc/luaossl.tex b/doc/luaossl.tex
index 8561957..b874e89 100644
--- a/doc/luaossl.tex
+++ b/doc/luaossl.tex
@@ -597,6 +597,19 @@ Returns the type of signature used to sign the certificate as a string. e.g. ``R
 
 Signs and updates the instance certificate using the \module{openssl.pkey} $key$. $type$ is an optional string describing the digest type. See \module{pkey:sign}, regarding which types of digests are valid. If $type$ is omitted than a default type is used---``sha1'' for RSA keys, ``dss1'' for DSA keys, and ``ecdsa-with-SHA1'' for EC keys.
 
+\subsubsection[\fn{x509:verify}]{\fn{x509:verify\{ $\ldots$ \}}}
+
+Verifies the certificate against to the specified parameters.
+
+\begin{ctabular}{ c | c | p{9cm}}
+field & type & description\\\hline
+.store & \module{openssl.x509.store} & The certificate store to verify against, any custom settings from the store will be used. \\
+.chain & \module{openssl.x509.chain} & A collection of additional certificates to consider \\
+.params & \module{openssl.x509.verify\_param} & The verification parameters to use; overrides any parameters in $.store$
+\end{ctabular}
+
+Returns two values. The first is a boolean value for whether the specified certificate $crt$ was verified. If true, the second value is a \module{openssl.x509.chain} object validation chain. If false, the second value is a string describing why verification failed.
+
 \subsubsection[\fn{x509:text}]{\fn{x509:text()}}
 
 Returns a human-readable textual representation of the X.509 certificate.
diff --git a/regress/167-verify-cert.lua b/regress/167-verify-cert.lua
new file mode 100755
index 0000000..b7433e8
--- /dev/null
+++ b/regress/167-verify-cert.lua
@@ -0,0 +1,47 @@
+#!/usr/bin/env lua
+
+local regress = require "regress"
+
+if (regress.openssl.OPENSSL_VERSION_NUMBER and regress.openssl.OPENSSL_VERSION_NUMBER < 0x10002000)
+	or (regress.openssl.LIBRESSL_VERSION_NUMBER and regress.openssl.LIBRESSL_VERSION_NUMBER < 0x20705000)
+then
+	-- skipping test due to different behaviour in earlier OpenSSL versions
+	return
+end
+
+local params = regress.verify_param.new()
+params:setDepth(0)
+
+local ca_key, ca_crt = regress.genkey()
+do -- should fail as no trust anchor
+	regress.check(not ca_crt:verify({params=params, chain=nil, store=nil}))
+end
+
+local store = regress.store.new()
+store:add(ca_crt)
+do -- should succeed as cert is in the store
+	regress.check(ca_crt:verify({params=params, chain=nil, store=store}))
+end
+
+local intermediate_key, intermediate_crt = regress.genkey(nil, ca_key, ca_crt)
+do -- should succeed as ca cert is in the store
+	regress.check(intermediate_crt:verify({params=params, chain=nil, store=store}))
+end
+
+local _, crt = regress.genkey(nil, intermediate_key, intermediate_crt)
+do -- should fail as intermediate cert is missing
+	regress.check(not crt:verify({params=params, chain=nil, store=store}))
+end
+
+local chain = regress.chain.new()
+chain:add(intermediate_crt)
+do -- should fail as max depth is too low
+	regress.check(not crt:verify({params=params, chain=chain, store=store}))
+end
+
+params:setDepth(1)
+do -- should succeed
+	regress.check(crt:verify({params=params, chain=chain, store=store}))
+end
+
+regress.say "OK"
diff --git a/regress/regress.lua b/regress/regress.lua
index 19ee065..5cdd22d 100644
--- a/regress/regress.lua
+++ b/regress/regress.lua
@@ -8,7 +8,9 @@ local regress = {
 	x509 = require"openssl.x509",
 	name = require"openssl.x509.name",
 	altname = require"openssl.x509.altname",
+	chain = require"openssl.x509.chain",
 	store = require"openssl.x509.store",
+	verify_param = require"openssl.x509.verify_param",
 	pack = table.pack or function (...)
 		local t = { ... }
 		t.n = select("#", ...)
diff --git a/src/openssl.c b/src/openssl.c
index 9e0447f..ffb34d6 100644
--- a/src/openssl.c
+++ b/src/openssl.c
@@ -7160,6 +7160,125 @@ static int xc_sign(lua_State *L) {
 } /* xc_sign() */
 
 
+static int xc_verify(lua_State *L) {
+	X509 *crt = checksimple(L, 1, X509_CERT_CLASS);
+	X509_STORE *store = NULL;
+	STACK_OF(X509) *chain = NULL;
+	X509_VERIFY_PARAM *params = NULL;
+	X509_STORE_CTX *ctx = NULL;
+	int ok, why;
+	STACK_OF(X509) **proof;
+
+	if (lua_istable(L, 2)) {
+		if (lua_getfield(L, 2, "store") != LUA_TNIL) {
+			store = checksimple(L, -1, X509_STORE_CLASS);
+		} else if (!(OPENSSL_PREREQ(1,0,2) || LIBRESSL_PREREQ(2,7,5))) {
+			/*
+			Without .store OpenSSL 1.0.1 crashes e.g.
+
+			#0  X509_STORE_get_by_subject (vs=vs@entry=0x6731b0, type=type@entry=1, name=name@entry=0x66a360, ret=ret@entry=0x7fffffffe580) at x509_lu.c:293
+			#1  0x00007ffff69653ca in X509_STORE_CTX_get1_issuer (issuer=0x7fffffffe620, ctx=0x6731b0, x=0x665db0) at x509_lu.c:604
+			#2  0x00007ffff696117c in X509_verify_cert (ctx=ctx@entry=0x6731b0) at x509_vfy.c:256
+
+			Was fixed in LibreSSL somewhere between 2.6.5 and 2.7.5
+			*/
+			luaL_argerror(L, 2, ".store required in OpenSSL <= 1.0.1");
+		}
+		lua_pop(L, 1);
+
+		if (lua_getfield(L, 2, "chain") != LUA_TNIL) {
+			chain = checksimple(L, -1, X509_CHAIN_CLASS);
+		}
+		lua_pop(L, 1);
+
+		if (lua_getfield(L, 2, "params") != LUA_TNIL) {
+			params = checksimple(L, -1, X509_VERIFY_PARAM_CLASS);
+		}
+		lua_pop(L, 1);
+
+		if (lua_getfield(L, 2, "crls") != LUA_TNIL) {
+			luaL_argerror(L, 2, "crls not yet supported");
+		}
+		lua_pop(L, 1);
+
+		if (lua_getfield(L, 2, "dane") != LUA_TNIL) {
+			luaL_argerror(L, 2, "dane not yet supported");
+		}
+		lua_pop(L, 1);
+	}
+
+	/* pre-allocate space for a successful return */
+	proof = prepsimple(L, X509_CHAIN_CLASS);
+
+	if (chain) {
+		X509 *elm;
+		int i, n;
+
+		if (!(chain = sk_X509_dup(chain)))
+			goto eossl;
+
+		n = sk_X509_num(chain);
+
+		for (i = 0; i < n; i++) {
+			if (!(elm = sk_X509_value(chain, i)))
+				continue;
+			X509_up_ref(elm);
+		}
+	}
+
+	if (!(ctx = X509_STORE_CTX_new()) || !X509_STORE_CTX_init(ctx, store, crt, chain)) {
+		sk_X509_pop_free(chain, X509_free);
+		goto eossl;
+	}
+
+	if (params) {
+		X509_VERIFY_PARAM *params_copy = X509_VERIFY_PARAM_new();
+		if (!params_copy)
+			goto eossl;
+
+		ok = X509_VERIFY_PARAM_inherit(params_copy, params);
+		if (!ok) {
+			X509_VERIFY_PARAM_free(params_copy);
+			goto eossl;
+		}
+
+		X509_STORE_CTX_set0_param(ctx, params_copy);
+	}
+
+	ERR_clear_error();
+
+	ok = X509_verify_cert(ctx);
+
+	switch (ok) {
+	case 1: /* verified */
+		if (!(*proof = X509_STORE_CTX_get1_chain(ctx)))
+			goto eossl;
+		X509_STORE_CTX_free(ctx);
+
+		lua_pushboolean(L, 1);
+		lua_pushvalue(L, -2);
+
+		return 2;
+	case 0: /* not verified */
+		why = X509_STORE_CTX_get_error(ctx);
+		X509_STORE_CTX_free(ctx);
+
+		lua_pushboolean(L, 0);
+		lua_pushstring(L, X509_verify_cert_error_string(why));
+
+		return 2;
+	default:
+		goto eossl;
+	}
+
+eossl:
+	if (ctx)
+		X509_STORE_CTX_free(ctx);
+
+	return auxL_error(L, auxL_EOPENSSL, "x509.cert:verify");
+} /* xc_verify() */
+
+
 static int xc_text(lua_State *L) {
 	static const struct { const char *kw; unsigned int flag; } map[] = {
 		{ "no_header", X509_FLAG_NO_HEADER },
@@ -7311,6 +7430,7 @@ static const auxL_Reg xc_methods[] = {
 	{ "getPublicKeyDigest", &xc_getPublicKeyDigest },
 	{ "getSignatureName", &xc_getSignatureName },
 	{ "sign",          &xc_sign },
+	{ "verify",        &xc_verify },
 	{ "text",          &xc_text },
 	{ "toPEM",         &xc_toPEM },
 	{ "tostring",      &xc__tostring },
-- 
cgit v1.2.3-59-g8ed1b