[stunnel-users] Patch: verify_depth and remote_subj

Michael Smith msmith at cbnco.com
Thu May 26 17:52:44 CEST 2005


Hi,

I've attached a patch against stunnel 4.10. It adds two verification 
options to the global configuration:

===
remote_subj = expected subject for the remote certificate

If set, the remote certificate's subject must match this string. You can
obtain the subject in the proper format by running

    openssl x509 -in servercert.pem -noout -subject

Example:

    remote_subj = /C=CA/ST=Ontario/CN=gw

Requires verify=2 or greater.

verify_depth = maximum number of CA certificates in chain

Specifies the maximum certificate chain depth when verifying the peer
certificate:

    0: peer certificate must be self-signed
    1: peer certificate must be signed by a root CA
    2: peer certificate can be signed by at most one intermediate CA

The stunnel default depth is 9 for backwards compatibility. Please note 
that Apache/SSL defaults to 1. If your non-CA certificates are marked with 
the X509v3 basic constraint "CA:FALSE", you probably do not need to worry 
about chain depth. This is the default behaviour for most CA tools, 
including the scripts that ship with OpenSSL.
===

remote_subj probably belongs in per-service config, not global, but the 
SSL verification setup is global and I didn't want to start messing with 
it.

Mike
-------------- next part --------------
Index: src/options.c
===================================================================
RCS file: /sourceforge/cvs/abrazo/base/os/packages/stunnel/src/options.c,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -c -r1.1.1.1 -r1.3
*** src/options.c	20 May 2005 17:00:09 -0000	1.1.1.1
--- src/options.c	26 May 2005 15:25:54 -0000	1.3
***************
*** 664,669 ****
--- 664,710 ----
          break;
      }
  
+     /* remote_subj */
+     switch(cmd) {
+     case CMD_INIT:
+         options.remote_subj=NULL;
+         break;
+     case CMD_EXEC:
+         if(strcasecmp(opt, "remote_subj"))
+             break;
+         options.remote_subj=stralloc(arg);
+         return NULL; /* OK */
+     case CMD_DEFAULT:
+         break;
+     case CMD_HELP:
+         log_raw("%-15s = expected subject (DN) for the remote certificate",
+ 		"remote_subj");
+         break;
+     }
+ 
+     /* verify_depth */
+     switch(cmd) {
+     case CMD_INIT:
+ 	/* The OpenSSL default depth is 9. Apache/SSL defaults to 1. */
+         options.verify_depth=9;
+         break;
+     case CMD_EXEC:
+         if(strcasecmp(opt, "verify_depth"))
+             break;
+         options.verify_depth=atoi(arg);
+ 	if (options.verify_depth < 0)
+ 		return "verify_depth must be >= 0";
+         return NULL; /* OK */
+     case CMD_DEFAULT:
+         log_raw("%-15s = 9", "verify_depth");
+         break;
+     case CMD_HELP:
+         log_raw("%-15s = maximum certificate chain length (0 == peer only, "
+ 		"1 == peer and CA, 9 == peer, 8 intermediaries and 1 CA)",
+ 		"verify_depth");
+         break;
+     }
+ 
      if(cmd==CMD_EXEC)
          return option_not_found;
      return NULL; /* OK */
Index: src/prototypes.h
===================================================================
RCS file: /sourceforge/cvs/abrazo/base/os/packages/stunnel/src/prototypes.h,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -c -r1.1.1.1 -r1.3
*** src/prototypes.h	20 May 2005 17:00:09 -0000	1.1.1.1
--- src/prototypes.h	26 May 2005 15:25:54 -0000	1.3
***************
*** 138,143 ****
--- 138,146 ----
  #endif
      char *output_file;
  
+     char *remote_subj;			/* remote cert's DN must match this */
+     int verify_depth;			/* max verify chain depth */
+ 
          /* on/off switches */
      struct {
          unsigned int cert:1;
Index: src/ssl.c
===================================================================
RCS file: /sourceforge/cvs/abrazo/base/os/packages/stunnel/src/ssl.c,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -c -r1.1.1.1 -r1.3
*** src/ssl.c	20 May 2005 17:00:09 -0000	1.1.1.1
--- src/ssl.c	26 May 2005 15:25:54 -0000	1.3
***************
*** 455,460 ****
--- 455,467 ----
  static void verify_init(void) {
      X509_LOOKUP *lookup;
  
+     if (options.remote_subj &&
+         (options.verify_level < 0
+          || (options.verify_level & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) == 0)) {
+ 	s_log(LOG_ERR, "remote_subj requires at least verify=2");
+ 	exit(1);
+     }
+ 
      if(options.verify_level<0)
          return; /* No certificate verification */
  
***************
*** 532,554 ****
      SSL_CTX_set_verify(ctx, options.verify_level==SSL_VERIFY_NONE ?
          SSL_VERIFY_PEER : options.verify_level, verify_callback);
  
      if(options.ca_dir && options.verify_use_only_my)
          s_log(LOG_NOTICE, "Peer certificate location %s", options.ca_dir);
  }
  
  static int verify_callback(int preverify_ok, X509_STORE_CTX *callback_ctx) {
          /* our verify callback function */
!     char txt[STRLEN];
      X509_OBJECT ret;
  
      X509_NAME_oneline(X509_get_subject_name(callback_ctx->current_cert),
!         txt, STRLEN);
      safestring(txt);
      if(options.verify_level==SSL_VERIFY_NONE) {
          s_log(LOG_NOTICE, "VERIFY IGNORE: depth=%d, %s",
              callback_ctx->error_depth, txt);
          return 1; /* Accept connection */
      }
      if(!preverify_ok) {
          /* Remote site specified a certificate, but it's not correct */
          s_log(LOG_WARNING, "VERIFY ERROR: depth=%d, error=%s: %s",
--- 539,578 ----
      SSL_CTX_set_verify(ctx, options.verify_level==SSL_VERIFY_NONE ?
          SSL_VERIFY_PEER : options.verify_level, verify_callback);
  
+     /*
+      * Bump up the verify depth by 1; we'll catch it in verify_callback
+      * and report that the chain is too long. Otherwise OpenSSL reports
+      * that the chain is incomplete, rather overlong.
+      */
+     SSL_CTX_set_verify_depth(ctx, options.verify_depth + 1);
+ 
      if(options.ca_dir && options.verify_use_only_my)
          s_log(LOG_NOTICE, "Peer certificate location %s", options.ca_dir);
  }
  
  static int verify_callback(int preverify_ok, X509_STORE_CTX *callback_ctx) {
          /* our verify callback function */
!     char txt[STRLEN], raw[STRLEN];
      X509_OBJECT ret;
  
      X509_NAME_oneline(X509_get_subject_name(callback_ctx->current_cert),
!         raw, STRLEN);
! 
!     strncpy(txt, raw, STRLEN);
!     txt[STRLEN-1] = '\0';
      safestring(txt);
+ 
      if(options.verify_level==SSL_VERIFY_NONE) {
          s_log(LOG_NOTICE, "VERIFY IGNORE: depth=%d, %s",
              callback_ctx->error_depth, txt);
          return 1; /* Accept connection */
      }
+     if (callback_ctx->error_depth > options.verify_depth) {
+ 	s_log(LOG_WARNING, "VERIFY ERROR: certificate chain too long at %s",
+ 	      txt);
+ 	X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ 	return 0; /* Reject connection */
+     }
      if(!preverify_ok) {
          /* Remote site specified a certificate, but it's not correct */
          s_log(LOG_WARNING, "VERIFY ERROR: depth=%d, error=%s: %s",
***************
*** 564,570 ****
      }
      if(revocation_store && !crl_callback(callback_ctx))
          return 0; /* Reject connection */
!     /* errnum = X509_STORE_CTX_get_error(ctx); */
  
      s_log(LOG_NOTICE, "VERIFY OK: depth=%d, %s", callback_ctx->error_depth, txt);
      return 1; /* Accept connection */
--- 588,609 ----
      }
      if(revocation_store && !crl_callback(callback_ctx))
          return 0; /* Reject connection */
! 
!     /*
!      * Check the subject name on the peer certificate (depth 0).
!      * You should set verifydepth=1 so that only the peer and root CA are
!      * considered. Otherwise someone with a valid cert issued by your CA
!      * could potentially trick you by issuing another cert with the same
!      * name as remote_subj.
!      */
!     if(options.remote_subj && callback_ctx->error_depth == 0
!        && strcmp(raw, options.remote_subj) != 0)
!     {
! 	s_log(LOG_WARNING, "VERIFY ERROR: remote subject %s does not "
! 	      "match configured remote_subj %s",
!               txt, options.remote_subj);
! 	return 0; /* Reject connection */
!     }
  
      s_log(LOG_NOTICE, "VERIFY OK: depth=%d, %s", callback_ctx->error_depth, txt);
      return 1; /* Accept connection */
Index: doc/stunnel.pod
===================================================================
RCS file: /sourceforge/cvs/abrazo/base/os/packages/stunnel/doc/stunnel.pod,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -c -r1.1.1.1 -r1.2
*** doc/stunnel.pod	20 May 2005 17:00:09 -0000	1.1.1.1
--- doc/stunnel.pod	26 May 2005 15:38:01 -0000	1.2
***************
*** 333,340 ****
      level 3 - verify peer with locally installed certificate
      default - no verify
  
! =back
  
  
  =head2 SERVICE-LEVEL OPTIONS
  
--- 333,367 ----
      level 3 - verify peer with locally installed certificate
      default - no verify
  
! =item B<remote_subj> = expected subject for the remote certificate
! 
! If set, the remote certificate's subject must match this string. You can
! obtain the subject in the proper format by running
! 
!     openssl x509 -in servercert.pem -noout -subject
! 
! Example:
! 
!     remote_subj = /C=CA/ST=Ontario/CN=gw
! 
! Requires B<verify=2> or greater.
  
+ =item B<verify_depth> = maximum number of CA certificates in chain
+ 
+ Specifies the maximum certificate chain depth when verifying the peer
+ certificate:
+ 
+     0: peer certificate must be self-signed
+     1: peer certificate must be signed by a root CA
+     2: peer certificate can be signed by at most one intermediate CA
+ 
+ The stunnel default depth is 9 for backwards compatibility. Please note that
+ Apache/SSL defaults to 1. If your non-CA certificates are marked with the
+ X509v3 basic constraint "CA:FALSE", you probably do not need to worry about
+ chain depth. This is the default behaviour for most CA tools, including
+ the scripts that ship with OpenSSL.
+ 
+ =back
  
  =head2 SERVICE-LEVEL OPTIONS
  


More information about the stunnel-users mailing list