Hi,

This patch leverages PKCS#11 support in nginx http module using libp11.
This allows the private key to be stored in a dedicated hardware (or
software) component.

The following patch does not deal with the "configure" tools of nginx.
I wanted to get feedback prior to writing nginx "autoconf" scripts to
deal with multiple platforms.

To test, apply the patch, run configure (with http/ssl enabled), and
modify objs/Makefile to add "-lp11" to link the libp11 library.

To configure use the following parameters:
  * ssl_pkcs11, on or off
  * ssl_certificate, no change the server certificate is fetched on the disk
  * ssl_certificate_key, string mapped to the PKCS#11 "label" attribute
  * ssl_pkcs11_pin, string of the token PIN
  * ssl_pkcs11_module, path to the PKCS#11 shared library

Instead of tweaking ngx_ssl_certificate function, I have added
the ngx_ssl_certificate_pkcs11 function which is used when ssl_pkcs11 is
enabled.

This approach could also be applied to the nginx mail module.

Feedback appreciated.

Regards,


-- 
Cordialement,

Thomas Calderon
Laboratoire architectures matérielles et logicielles
Sous-direction expertise
ANSSI
Tél: 01 71 75 88 55
# HG changeset patch
# User Thomas Calderon <thomas.calde...@ssi.gouv.fr>
# Date 1415031852 -3600
#      Mon Nov 03 17:24:12 2014 +0100
# Node ID b42ce1de67d6ef5b434ee38a5c1adac9be7b4e9e
# Parent  dff86e2246a53b0f4a61935cd5c8c0a0f66d0ca2
Add PKCS#11 support to nginx http module.

This patch leverages PKCS#11 support in nginx http module using libp11.
This allows the private key to be stored in a dedicated hardware (or software)
component.

To configure use the following parameters:
  * ssl_pkcs11, on or off
  * ssl_certificate, no change the server certificate is fetched on the disk
  * ssl_certificate_key, string mapped to the PKCS#11 "label" attribute
  * ssl_pkcs11_pin, string of the token PIN
  * ssl_pkcs11_module, path to the PKCS#11 shared library

Instead of tweaking the ngx_ssl_certificate function, I have added another
function (ngx_ssl_certificate_pkcs11) which is called when ssl_pkcs11 is
enabled.

diff -r dff86e2246a5 -r b42ce1de67d6 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c	Mon Aug 25 13:41:31 2014 +0400
+++ b/src/event/ngx_event_openssl.c	Mon Nov 03 17:24:12 2014 +0100
@@ -189,6 +189,197 @@
 
 
 ngx_int_t
+ngx_ssl_certificate_pkcs11(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+    ngx_str_t *confkey, ngx_str_t *pkcs11_module, ngx_str_t *pkcs11_pin)
+{
+    BIO     *bio;
+    X509    *x509;
+    u_long   n;
+
+    PKCS11_KEY *keys;
+    EVP_PKEY *pkey = NULL;
+    PKCS11_SLOT *slot;
+    unsigned int num_keys;
+    unsigned int i;
+
+    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't
+     * allow to access certificate later from SSL_CTX, so we reimplement
+     * it here
+     */
+
+    bio = BIO_new_file((char *) cert->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", cert->data);
+        return NGX_ERROR;
+    }
+
+    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_certificate(\"%s\") failed", cert->data);
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    X509_free(x509);
+
+    /* read rest of the chain */
+
+    for ( ;; ) {
+
+        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+        if (x509 == NULL) {
+            n = ERR_peek_last_error();
+
+            if (ERR_GET_LIB(n) == ERR_LIB_PEM
+                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
+            {
+                /* end of file */
+                ERR_clear_error();
+                break;
+            }
+
+            /* some real error */
+
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "PEM_read_bio_X509(\"%s\") failed", cert->data);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+
+        if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_add_extra_chain_cert(\"%s\") failed",
+                          cert->data);
+            X509_free(x509);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+    }
+
+    BIO_free(bio);
+
+    /* Build the path to PKCS#11 module (add build prefix if no
+     * absolute path given.
+     */
+    if (ngx_conf_full_name(cf->cycle, pkcs11_module, 1) != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    /* Start the libp11 dance */
+    ssl->pkcs11_ctx = PKCS11_CTX_new();
+    if(!(ssl->pkcs11_ctx))
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_CTX_new failed");
+        return NGX_ERROR;
+    }
+
+    if(PKCS11_CTX_load(ssl->pkcs11_ctx, (const char *)pkcs11_module->data) != 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_CTX_load(\"%s\") failed", pkcs11_module->data);
+        return NGX_ERROR;
+    }
+
+    if(PKCS11_enumerate_slots(ssl->pkcs11_ctx,  &ssl->slots, &ssl->nslots) != 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_enumerate_slots failed");
+        return NGX_ERROR;
+    }
+
+    /* Find the first slot with a token */
+    slot = PKCS11_find_token(ssl->pkcs11_ctx, ssl->slots, ssl->nslots);
+    if(!slot)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_find_token failed");
+        return NGX_ERROR;
+    }
+
+    /* 0 means RO session */
+    if(PKCS11_open_session(slot, 0) != 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_open_session failed");
+        return NGX_ERROR;
+    }
+
+    /* 0 means USER login */
+    if(PKCS11_login(slot, 0, (const char *)pkcs11_pin->data) !=0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_login failed");
+        return NGX_ERROR;
+    }
+
+    /* We have to enumerate all key, we cannot filter them */
+    if(PKCS11_enumerate_keys(slot->token, &keys, &num_keys) !=0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PKCS11_enumerate_keys failed");
+        return NGX_ERROR;
+    }
+
+    /* Let's try to find a matching key */
+    for(i=0; i < num_keys; i++){
+        if(PKCS11_get_key_type(&keys[i]) == EVP_PKEY_RSA)
+        {
+            /* check that required label match, assume LABEL is at mot 64 */
+            if(memcmp(confkey->data, keys[i].label,
+                    strnlen((const char *)confkey->data, 64)) == 0)
+            {
+                pkey = PKCS11_get_private_key(&keys[i]);
+            }
+            break;
+        }
+    }
+
+    /* Did we find a matching key ? */
+    if(!pkey){
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "Could not find a PKCS11 label key matching (\"%s\")", confkey->data);
+        return NGX_ERROR;
+    }
+
+    /* Use the retrieved EVP_PKEY from libp11 */
+    if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_PrivateKey(\"%s\") failed", confkey->data);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
 ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
 {
     ssl->ctx = SSL_CTX_new(SSLv23_method());
@@ -2839,6 +3030,12 @@
 ngx_ssl_cleanup_ctx(void *data)
 {
     ngx_ssl_t  *ssl = data;
+    if(ssl->pkcs11_ctx)
+    {
+        PKCS11_release_all_slots(ssl->pkcs11_ctx, ssl->slots, ssl->nslots);
+        PKCS11_CTX_unload(ssl->pkcs11_ctx);
+        PKCS11_CTX_free(ssl->pkcs11_ctx);
+    }
 
     SSL_CTX_free(ssl->ctx);
 }
diff -r dff86e2246a5 -r b42ce1de67d6 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h	Mon Aug 25 13:41:31 2014 +0400
+++ b/src/event/ngx_event_openssl.h	Mon Nov 03 17:24:12 2014 +0100
@@ -32,6 +32,8 @@
 
 #define NGX_SSL_NAME     "OpenSSL"
 
+#include <libp11.h>
+
 
 #define ngx_ssl_session_t       SSL_SESSION
 #define ngx_ssl_conn_t          SSL
@@ -39,6 +41,9 @@
 
 typedef struct {
     SSL_CTX                    *ctx;
+    PKCS11_CTX                 *pkcs11_ctx;
+    PKCS11_SLOT                *slots;
+    unsigned int                nslots;
     ngx_log_t                  *log;
     size_t                      buffer_size;
 } ngx_ssl_t;
@@ -124,6 +129,9 @@
 ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data);
 ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_str_t *cert, ngx_str_t *key, ngx_array_t *passwords);
+ngx_int_t ngx_ssl_certificate_pkcs11(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *cert, ngx_str_t *key, ngx_str_t *pkcs11_module,
+    ngx_str_t *pkcs11_pin);
 ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_str_t *cert, ngx_int_t depth);
 ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
diff -r dff86e2246a5 -r b42ce1de67d6 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c	Mon Aug 25 13:41:31 2014 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c	Mon Nov 03 17:24:12 2014 +0100
@@ -79,6 +79,27 @@
       offsetof(ngx_http_ssl_srv_conf_t, enable),
       NULL },
 
+    { ngx_string("ssl_pkcs11"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_http_ssl_enable,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, enable_pkcs11),
+      NULL },
+
+    { ngx_string("ssl_pkcs11_module"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, pkcs11_module),
+      NULL },
+
+    { ngx_string("ssl_pkcs11_pin"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, pkcs11_pin),
+      NULL },
+
     { ngx_string("ssl_certificate"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -505,6 +526,8 @@
      * set by ngx_pcalloc():
      *
      *     sscf->protocols = 0;
+     *     sscf->pkcs11_module = { 0, NULL };
+     *     sscf->pkcs11_pin = { 0, NULL };
      *     sscf->certificate = { 0, NULL };
      *     sscf->certificate_key = { 0, NULL };
      *     sscf->dhparam = { 0, NULL };
@@ -519,6 +542,7 @@
      */
 
     sscf->enable = NGX_CONF_UNSET;
+    sscf->enable_pkcs11 = NGX_CONF_UNSET;
     sscf->prefer_server_ciphers = NGX_CONF_UNSET;
     sscf->buffer_size = NGX_CONF_UNSET_SIZE;
     sscf->verify = NGX_CONF_UNSET_UINT;
@@ -628,6 +652,25 @@
         }
     }
 
+    if (conf->enable_pkcs11) {
+
+        if (conf->pkcs11_module.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"ssl_pkcs11_module\" is defined for "
+                          "the \"ssl_pkcs11\" directive in %s:%ui",
+                          conf->file, conf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        if (conf->pkcs11_pin.len == 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "no \"ssl_pkcs11_pin\" is defined for "
+                          "the \"ssl_pkcs11\" directive in %s:%ui",
+                          conf->file, conf->line);
+            return NGX_CONF_ERROR;
+        }
+    }
+
     if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
         return NGX_CONF_ERROR;
     }
@@ -663,12 +706,26 @@
     cln->handler = ngx_ssl_cleanup_ctx;
     cln->data = &conf->ssl;
 
-    if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
-                            &conf->certificate_key, conf->passwords)
-        != NGX_OK)
+    if(conf->enable && conf->enable_pkcs11)
     {
-        return NGX_CONF_ERROR;
+        if (ngx_ssl_certificate_pkcs11(cf, &conf->ssl, &conf->certificate,
+                            &conf->certificate_key, &conf->pkcs11_module,
+                            &conf->pkcs11_pin)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
     }
+    else
+    {
+        if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
+                                &conf->certificate_key, conf->passwords)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
 
     if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
                                 (const char *) conf->ciphers.data)
diff -r dff86e2246a5 -r b42ce1de67d6 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h	Mon Aug 25 13:41:31 2014 +0400
+++ b/src/http/modules/ngx_http_ssl_module.h	Mon Nov 03 17:24:12 2014 +0100
@@ -16,6 +16,7 @@
 
 typedef struct {
     ngx_flag_t                      enable;
+    ngx_flag_t                      enable_pkcs11;
 
     ngx_ssl_t                       ssl;
 
@@ -32,6 +33,8 @@
 
     time_t                          session_timeout;
 
+    ngx_str_t                       pkcs11_module;
+    ngx_str_t                       pkcs11_pin;
     ngx_str_t                       certificate;
     ngx_str_t                       certificate_key;
     ngx_str_t                       dhparam;
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
http://mailman.nginx.org/mailman/listinfo/nginx-devel

Reply via email to