From 751eea49f5b66e5fa1c556c892e0d8aeb8c30762 Mon Sep 17 00:00:00 2001
From: Tony Cheneau <tony.cheneau@clients.fr>
Date: Fri, 19 May 2017 16:45:06 +0200
Subject: [PATCH 3/3] Add "subjectdn" option support (match subject DN against
 a regexp)

Add a new service option to filter the Distinguished Name of client's X.509
certificate. This is pretty similar to the checkEmail/checkHost feature.

The point is to reject the clients whose certificates don't match the regexp.
---
 doc/stunnel.8.in    |  4 ++++
 doc/stunnel.html.in |  6 ++++++
 doc/stunnel.pod.in  |  5 +++++
 src/common.h        |  2 ++
 src/options.c       | 34 ++++++++++++++++++++++++++++++++++
 src/prototypes.h    |  4 ++++
 src/verify.c        | 26 ++++++++++++++++++++++++++
 7 files changed, 81 insertions(+)

diff --git a/doc/stunnel.8.in b/doc/stunnel.8.in
index 29586c8..cef9c70 100644
--- a/doc/stunnel.8.in
+++ b/doc/stunnel.8.in
@@ -937,6 +937,10 @@ See the \fBoptions\fR option documentation for details.
 .IP "\fBstack\fR = \s-1BYTES \s0(except for \s-1FORK\s0 model)" 4
 .IX Item "stack = BYTES (except for FORK model)"
 thread stack size
+.IP "\fBsubjectdn\fR = \s-1REGEX \s0(Unix only)" 4
+.IX Item "subjectdn = REGEX (Unix only)"
+Verify the host's certificate Subject Name field against a regular expression.
+The \s-1REGEX\s0 must conform to the \s-1POSIX\s0 Extended Regular Expression syntax.
 .IP "\fBTIMEOUTbusy\fR = \s-1SECONDS\s0" 4
 .IX Item "TIMEOUTbusy = SECONDS"
 time to wait for expected data
diff --git a/doc/stunnel.html.in b/doc/stunnel.html.in
index a553b48..a680d86 100644
--- a/doc/stunnel.html.in
+++ b/doc/stunnel.html.in
@@ -1109,6 +1109,12 @@
 <p>thread stack size</p>
 
 </dd>
+<dt id="subjectdn-REGEX-Unix-only"><b>subjectdn</b> = REGEX (Unix only)</dt>
+<dd>
+
+<p>Verify the host&#39;s certificate Subject Name field against a regular expression. The REGEX must conform to the POSIX Extended Regular Expression syntax.</p>
+
+</dd>
 <dt id="TIMEOUTbusy-SECONDS"><b>TIMEOUTbusy</b> = SECONDS</dt>
 <dd>
 
diff --git a/doc/stunnel.pod.in b/doc/stunnel.pod.in
index 02b25a7..7e5c262 100644
--- a/doc/stunnel.pod.in
+++ b/doc/stunnel.pod.in
@@ -1001,6 +1001,11 @@ See the B<options> option documentation for details.
 
 thread stack size
 
+=item B<subjectdn> = REGEX (Unix only)
+
+Verify the host's certificate Subject Name field against a regular expression.
+The REGEX must conform to the POSIX Extended Regular Expression syntax.
+
 =item B<TIMEOUTbusy> = SECONDS
 
 time to wait for expected data
diff --git a/src/common.h b/src/common.h
index c6547fe..718f4f4 100644
--- a/src/common.h
+++ b/src/common.h
@@ -400,6 +400,8 @@ extern char *sys_errlist[];
 #include <linux/sched.h> /* SCHED_BATCH */
 #endif
 
+#include <regex.h>
+
 #endif /* USE_WIN32 */
 
 #ifndef S_ISREG
diff --git a/src/options.c b/src/options.c
index 9413cc6..dc5980f 100644
--- a/src/options.c
+++ b/src/options.c
@@ -1499,6 +1499,40 @@ NOEXPORT char *parse_service_option(CMD cmd, SERVICE_OPTIONS *section,
 
 #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */
 
+    /* subjectDN */
+#ifndef USE_WIN32
+    switch (cmd) {
+    case CMD_BEGIN:
+      section->check_subject=NULL;
+      break;
+    case CMD_EXEC:
+      if(strcasecmp(opt, "subjectdn"))
+        break;
+      section->check_subject = malloc(sizeof(regex_t));
+      if (section->check_subject == NULL) return "Unable to allocate memory for regexp";
+      if (regcomp(section->check_subject, arg, REG_NOSUB | REG_EXTENDED))
+                  return "Unable to compile regexp";
+      return NULL; /* OK */
+    case CMD_END:
+      break;
+    case CMD_DUP:
+      break;
+    case CMD_FREE:
+      if (section->check_subject) {
+        regfree(section->check_subject);
+        free(section->check_subject);
+        section->check_subject=NULL;
+      }
+      break;
+    case CMD_DEFAULT:
+      break;
+    case CMD_HELP:
+      s_log(LOG_NOTICE, "%-22s = regexp to verify the host's certificate Subject Name against",
+            "subjectdn");
+      break;
+    }
+#endif /* (!defined USE_WIN32) */
+
     /* ciphers */
     switch(cmd) {
     case CMD_BEGIN:
diff --git a/src/prototypes.h b/src/prototypes.h
index 8cb6f33..c00fc49 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -214,6 +214,10 @@ typedef struct service_options_struct {
     NAME_LIST *config;                               /* OpenSSL CONF options */
 #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */
 
+#ifndef USE_WIN32
+    regex_t * check_subject; /* verify peer cert SubjectName against a regex */
+#endif
+
         /* service-specific data for ctx.c */
     char *cipher_list;
     char *cert;                                             /* cert filename */
diff --git a/src/verify.c b/src/verify.c
index b4b5115..63e90d2 100644
--- a/src/verify.c
+++ b/src/verify.c
@@ -302,6 +302,32 @@ NOEXPORT int cert_check_subject(CLI *c, X509_STORE_CTX *callback_ctx) {
     NAME_LIST *ptr;
     char *peername=NULL;
 
+#ifndef USE_WIN32
+    if (c->opt->check_subject) {
+      char * subject = NULL;
+      int ret = 0;
+      //subject=X509_NAME2text(X509_get_subject_name(cert));
+      subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
+      ret = regexec(c->opt->check_subject, subject, 0, NULL, 0);
+      OPENSSL_free(subject);
+
+      switch (ret) {
+      case 0:
+        s_log(LOG_INFO, "CERT: subject DN matches regex");
+        break;
+      case REG_NOMATCH:
+        s_log(LOG_INFO, "CERT: subject DN doesn't match regex");
+        return 0; /* reject */
+      case REG_ESPACE:
+        s_log(LOG_INFO, "CERT: ran out of memory while trying to match regexp");
+        return 0; /* reject */
+      default:
+        s_log(LOG_INFO, "CERT: unknow error while matching subject DN against regexp");
+        return 0; /* reject */
+      }
+    }
+#endif /* !defined USE_WIN32 */
+
     if(!c->opt->check_host && !c->opt->check_email && !c->opt->check_ip) {
         s_log(LOG_INFO, "CERT: No subject checks configured");
         return 1; /* accept */
-- 
2.7.4

