From 99dfb7a48650df6676e0adcc1eced4484ce47b78 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Mon, 14 Apr 2025 19:26:29 +0300 Subject: [PATCH] x509-username-field: add "issuer" keyword to get X509_get_issuer_name(3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for accepting clients based on their certificate authority: x509-username-field issuer CN verify-x509-name ...CA=ExampleCA_ match-prefix `tls-verify` or `plugin` can do the equivalent, but require additional code execution and always incur overhead or may not be an option when running with reduced privileges, e.g. `chroot`. Motivation: `ca` contains the root CA and the intermediate CA issuing client certificates. Under the same root CA, another intermediate CA exists, not intended for VPN. The problem is that OpenVPN successfully validates all certificate chains, i.e. with both intermediates, whilst one one of them should allow peers to connect. AFAIU, this is expected OpenSSL behaviour, at least when OpenVPN peers send not only their own, but also their issuer CA’s certificate via `cert`. Thus reusing the customisable username mechanism allows for limiting to certain CAs, i.e. rejecting unwanted peers, as early as possible during handshake. (`remote-cert-ku` might be a viable alternative, but requires the X509v3 extension and respective key usage bits set up front; I have not tried that.) Tested against - FreeBSD 13, OpenSSL 1.1.1t, OpenVPN 2.5.6 - OpenBSD 7.7-stable, LibreSSL 4.1.0, OpenVPN 2.6.4 - OpenBSD 7.7-current, LibreSSL 4.1.0, OpenVPN f7aedca7 --- src/openvpn/ssl_verify_openssl.c | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c index 40d117bbcc9..22e91af75b8 100644 --- a/src/openvpn/ssl_verify_openssl.c +++ b/src/openvpn/ssl_verify_openssl.c @@ -276,6 +276,45 @@ backend_x509_get_username(char *common_name, size_t cn_len, char *x509_username_ snprintf(common_name, cn_len, "0x%s", serial); gc_free(&gc); } + else if (strcmp("issuer", x509_username_field) == 0) + { + struct gc_arena gc; + BIO *issuer_bio = BIO_new(BIO_s_mem()); + BUF_MEM *issuer_mem; + char *issuer = NULL; + + if (!issuer_bio) + { + return FAILURE; + } + + gc = gc_new(); + X509_NAME_print_ex(out, X509_get_issuer_name(peer_cert), 0, XN_FLAG_ONELINE); + + if (BIO_eof(issuer_bio)) + { + BIO_free(issuer_bio); + gc_free(&gc); + return FAILURE; + } + + BIO_get_mem_ptr(issuer_bio, &issuer_mem); + issuer = gc_malloc(issuer_mem->length + 1, false, gc); + memcpy(issuer, issuer_mem->data, issuer_mem->length); + issuer[issuer_mem->length] = '\0'; + + if (!issuer || cn_len <= strlen(issuer)+2) + { + BIO_free(issuer_bio); + gc_free(&gc); + return FAILURE; + } + + snprintf(common_name, cn_len, "%s", issuer); + + BIO_free(issuer_bio); + gc_free(&gc); + } else #endif /* ifdef ENABLE_X509ALTUSERNAME */ if (FAILURE