diff --git a/init.c b/init.c index ef09e8e6..b1f65a97 100644 --- a/init.c +++ b/init.c @@ -1706,8 +1706,10 @@ socket_restart_pause (struct context *c) if (auth_retry_get () == AR_NOINTERACT) sec = 10; +#if 0 /* not really needed because of c->persist.restart_sleep_seconds */ if (c->options.server_poll_timeout && sec > 1) sec = 1; +#endif #endif if (c->persist.restart_sleep_seconds > 0 && c->persist.restart_sleep_seconds > sec) @@ -2057,6 +2059,7 @@ do_init_crypto_tls (struct context *c, const unsigned int flags) to.ns_cert_type = options->ns_cert_type; memmove (to.remote_cert_ku, options->remote_cert_ku, sizeof (to.remote_cert_ku)); to.remote_cert_eku = options->remote_cert_eku; + to.verify_hash = options->verify_hash; to.es = c->c2.es; #ifdef ENABLE_DEBUG diff --git a/openvpn.8 b/openvpn.8 index 1953b161..85889de6 100644 --- a/openvpn.8 +++ b/openvpn.8 @@ -3887,6 +3887,22 @@ that for certificate authority functions, you must set up the files ). .\"********************************************************* .TP +.B --extra-certs file +Specify a +.B file +containing one or more PEM certs (concatenated together) +that complete the +local certificate chain. + +This option is useful for "split" CAs, where the CA for server +certs is different than the CA for client certs. Putting certs +in this file allows them to be used to complete the local +certificate chain without trusting them to verify the peer-submitted +certificate, as would be the case if the certs were placed in the +.B ca +file. +.\"********************************************************* +.TP .B --key file Local peer's private key in .pem format. Use the private key which was generated when you built your peer's certificate (see @@ -3903,6 +3919,17 @@ and .B --key. .\"********************************************************* .TP +.B --verify-hash hash +Specify SHA1 fingerprint for level-1 cert. The level-1 cert is the +CA (or intermediate cert) that signs the leaf certificate, and is +one removed from the leaf certificate in the direction of the root. +When accepting a connection from a peer, the level-1 cert +fingerprint must match +.B hash +or certificate verification will fail. Hash is specified +as XX:XX:... For example: AD:B0:95:D8:09:C8:36:45:12:A9:89:C8:90:09:CB:13:72:A6:AD:16 +.\"********************************************************* +.TP .B --pkcs11-cert-private [0|1]... Set if access to certificate object should be performed after login. Every provider has its own setting. diff --git a/options.c b/options.c index fd3fec60..1cfccd3f 100644 --- a/options.c +++ b/options.c @@ -507,9 +507,11 @@ static const char usage_message[] = " Use \"openssl dhparam -out dh1024.pem 1024\" to generate.\n" "--cert file : Local certificate in .pem format -- must be signed\n" " by a Certificate Authority in --ca file.\n" + "--extra-certs file : one or more PEM certs that complete the cert chain.\n" "--key file : Local private key in .pem format.\n" "--pkcs12 file : PKCS#12 file containing local private key, local certificate\n" " and optionally the root CA certificate.\n" + "--verify-hash : Specify SHA1 fingerprint for level-1 cert.\n" #ifdef WIN32 "--cryptoapicert select-string : Load the certificate and private key from the\n" " Windows Certificate System Store.\n" @@ -894,6 +896,40 @@ is_stateful_restart (const struct options *o) return is_persist_option (o) || connection_list_defined (o); } +#ifdef USE_SSL +static uint8_t * +parse_hash_fingerprint(const char *str, int nbytes, int msglevel, struct gc_arena *gc) +{ + int i; + const char *cp = str; + uint8_t *ret = (uint8_t *) gc_malloc (nbytes, true, gc); + char term = 1; + int byte; + char bs[3]; + + for (i = 0; i < nbytes; ++i) + { + if (strlen(cp) < 2) + msg (msglevel, "format error in hash fingerprint: %s", str); + bs[0] = *cp++; + bs[1] = *cp++; + bs[2] = 0; + byte = 0; + if (sscanf(bs, "%x", &byte) != 1) + msg (msglevel, "format error in hash fingerprint hex byte: %s", str); + ret[i] = (uint8_t)byte; + term = *cp++; + if (term != ':' && term != 0) + msg (msglevel, "format error in hash fingerprint delimiter: %s", str); + if (term == 0) + break; + } + if (term != 0 || i != nbytes-1) + msg (msglevel, "hash fingerprint is different length than expected (%d bytes): %s", nbytes, str); + return ret; +} +#endif + #ifdef WIN32 #ifdef ENABLE_DEBUG @@ -5758,6 +5794,22 @@ add_option (struct options *options, } #endif } + else if (streq (p[0], "extra-certs") && p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->extra_certs_file = p[1]; +#if ENABLE_INLINE_FILES + if (streq (p[1], INLINE_FILE_TAG) && p[2]) + { + options->extra_certs_file_inline = p[2]; + } +#endif + } + else if (streq (p[0], "verify-hash") && p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->verify_hash = parse_hash_fingerprint(p[1], SHA_DIGEST_LENGTH, msglevel, &options->gc); + } #ifdef WIN32 else if (streq (p[0], "cryptoapicert") && p[1]) { diff --git a/options.h b/options.h index 74ba9d42..f0baabe0 100644 --- a/options.h +++ b/options.h @@ -477,6 +477,7 @@ struct options const char *ca_path; const char *dh_file; const char *cert_file; + const char *extra_certs_file; const char *priv_key_file; const char *pkcs12_file; const char *cipher_list; @@ -487,6 +488,7 @@ struct options #if ENABLE_INLINE_FILES const char *ca_file_inline; const char *cert_file_inline; + const char *extra_certs_file_inline; char *priv_key_file_inline; const char *dh_file_inline; const char *pkcs12_file_inline; /* contains the base64 encoding of pkcs12 file */ @@ -495,6 +497,7 @@ struct options int ns_cert_type; /* set to 0, NS_SSL_SERVER, or NS_SSL_CLIENT */ unsigned remote_cert_ku[MAX_PARMS]; const char *remote_cert_eku; + uint8_t *verify_hash; #ifdef ENABLE_PKCS11 const char *pkcs11_providers[MAX_PARMS]; diff --git a/ssl.c b/ssl.c index 095b6a62..df237ccf 100644 --- a/ssl.c +++ b/ssl.c @@ -910,6 +910,16 @@ verify_callback (int preverify_ok, X509_STORE_CTX * ctx) goto err; /* Reject connection */ } + /* verify level 1 cert, i.e. the CA that signed our leaf cert */ + if (ctx->error_depth == 1 && opt->verify_hash) + { + if (memcmp (ctx->current_cert->sha1_hash, opt->verify_hash, SHA_DIGEST_LENGTH)) + { + msg (D_TLS_ERRORS, "TLS Error: level-1 certificate hash verification failed"); + goto err; + } + } + /* save common name in session object */ if (ctx->error_depth == 0) set_common_name (session, common_name); @@ -2140,6 +2150,37 @@ init_ssl (const struct options *options) msg (M_SSLERR, "Cannot load certificate chain file %s (SSL_use_certificate_chain_file)", options->cert_file); } + /* Load extra certificates that are part of our own certificate + chain but shouldn't be included in the verify chain */ + if (options->extra_certs_file || options->extra_certs_file_inline) + { + BIO *bio; + X509 *cert; +#if ENABLE_INLINE_FILES + if (!strcmp (options->extra_certs_file, INLINE_FILE_TAG) && options->extra_certs_file_inline) + { + bio = BIO_new_mem_buf ((char *)options->extra_certs_file_inline, -1); + } + else +#endif + { + bio = BIO_new(BIO_s_file()); + if (BIO_read_filename(bio, options->extra_certs_file) <= 0) + msg (M_SSLERR, "Cannot load extra-certs file: %s", options->extra_certs_file); + } + for (;;) + { + cert = NULL; + if (!PEM_read_bio_X509 (bio, &cert, 0, NULL)) /* takes ownership of cert */ + break; + if (!cert) + msg (M_SSLERR, "Error reading extra-certs certificate"); + if (SSL_CTX_add_extra_chain_cert(ctx, cert) != 1) + msg (M_SSLERR, "Error adding extra-certs certificate"); + } + BIO_free (bio); + } + /* Require peer certificate verification */ #if P2MP_SERVER if (options->ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED) diff --git a/ssl.h b/ssl.h index fe494b10..1b23d7d3 100644 --- a/ssl.h +++ b/ssl.h @@ -466,6 +466,7 @@ struct tls_options int ns_cert_type; unsigned remote_cert_ku[MAX_PARMS]; const char *remote_cert_eku; + uint8_t *verify_hash; /* allow openvpn config info to be passed over control channel */ diff --git a/version.m4 b/version.m4 index 85cd0434..3642b719 100644 --- a/version.m4 +++ b/version.m4 @@ -1,5 +1,5 @@ dnl define the OpenVPN version -define(PRODUCT_VERSION,[2.1.3u]) +define(PRODUCT_VERSION,[2.1.3v]) dnl define the TAP version define(PRODUCT_TAP_ID,[tap0901]) define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9])