[racket-dev] [patch] OpenSSL ECDH(E) + DHE support.

From: Edward Lee (e45lee at uwaterloo.ca)
Date: Fri Feb 14 10:31:16 EST 2014

OK, here's a finalized version of my patch, that:
  - Fixes a few typos.
  - Uses Racket's I/O system to load the DH parameters.
  - Adds two functions to enable ECDH(E) + DHE
  - TLS 1.1/1.2 server/client contexts
  - Embeds reasonable defaults for ECDH(E) and DHE mode.
  - Has the standard TLS curves as defined
    https://tools.ietf.org/html/rfc4492#section-5.1.1

Are there any other things I should address?

On Tue, Feb 11, 2014 at 10:34:53AM -0500, Edward Lee wrote:
> Here's an updated patch that that:
>   - Fixes a few typos.
>   - Uses Racket's I/O system to read the Diffie-Hellman parameters.
> 
> Things that are not yet added in yet:
>   - All the other elliptic curve names used in TLS.
> 
> On Mon, Feb 10, 2014 at 08:36:02PM -0500, Edward Lee wrote:
> > On Mon, Feb 10, 2014 at 06:37:56PM -0500, Ryan Culpepper wrote:
> > > On 02/08/2014 05:52 PM, Edward Lee wrote:
> > > >Hi.
> > > >
> > > >Racket's OpenSSL bindings do not currently enable the ECDH(E) and the
> > > >DHE ciphers, which are needed for perfect forward secrecy.
> > > >
> > > >I've attached a patch that:
> > > >   - Embeds reasonable defaults for DHE mode.
> > > >   - Adds two functions, ssl-server-context-enable-dhe! and
> > > >     ssl-server-context-enable-ecdhe! that when given
> > > >     DHE/ECDHE setup arguments (for DHE, a DH parameter file path,
> > > >     for ECDHE, the name of one of the built-in OpenSSL elliptic curves
> > > >     [currently, only secp521r1])
> > > >   - (unrelated, but also useful) Adds bindings for TLS 1.1/1.2-only
> > > >     server/client contexts.
> > > >
> > > >This patch is currently a work in progress (it currently only supports
> > > >one elliptic curve name) that works well enough for what I am using it
> > > >for, but I'm interested in getting this patch upstream.
> > > 
> > > Here are a few comments:
> > > 
> > > What is SSL_CTRL_SET_ECDH_AUTO? I couldn't find it in the openssl
> > > headers, and I searched a few recent versions. It seems unused here,
> > > anyway.
> > It's not defined in OpenSSL 1.0.1, I think.  It can be used for enabling
> > ECDH(E) in OpenSSL >= 1.0.2, with OpenSSL automatically selecting the
> > curve - <https://www.openssl.org/news/changelog.html>, "Support for
> > automatic EC temporary key parameter selection..."
> > > 
> > > I think it would be better to read the DH params file into memory
> > > using Racket file operations and then use a memory BIO for
> > > PEM_read_bio_DHparams. Two reasons: the file access will then
> > > automatically go through Racket's security-guard checks, and the
> > > file read will go through Racket's IO system, so if the read blocks
> > > it won't block all Racket threads.
> > I will investigate this, and time permitting, will change my patch to
> > use Racket's IO system for loading the parameters.
> > > 
> > > In ...enable-ecdhe!, the 'case' form doesn't need 'quote'. It should
> > > be this instead:
> > > 
> > >   (case name
> > >     [(secp521r1) NID_secp521r1]
> > >     [else ___])
> > > 
> > > As it is, the function will accept 'quote as the name of a curve.
> > Thanks for pointing this out.
> > > 
> > > >What's the exact process for this?
> > > 
> > > If you have a github account, you can fork the repo there and submit
> > > a pull request. But we can certainly handle patches like this too.
> > > 
> > > Ryan
> > Great, thanks.
> > 
> > --Edward
> > _________________________
> >   Racket Developers list:
> >   http://lists.racket-lang.org/dev

> diff --git a/racket/collects/openssl/dh4096.pem b/racket/collects/openssl/dh4096.pem
> new file mode 100644
> index 0000000..1b35ad8
> --- /dev/null
> +++ b/racket/collects/openssl/dh4096.pem
> @@ -0,0 +1,18 @@
> +-----BEGIN DH PARAMETERS-----
> +MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ
> +l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt
> +Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS
> +Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98
> +VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc
> +alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM
> +sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9
> +ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte
> +OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH
> +AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL
> +KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=
> +-----END DH PARAMETERS-----
> +
> +These are the 4096 bit DH parameters from "Assigned Number for SKIP Protocols"
> +(http://www.skip-vpn.org/spec/numbers.html).
> +See there for how they were generated.
> +Note that g is not a generator, but this is not a problem since p is a safe prime.
> diff --git a/racket/collects/openssl/mzssl.rkt b/racket/collects/openssl/mzssl.rkt
> index 2f16517..e6ffedb 100644
> --- a/racket/collects/openssl/mzssl.rkt
> +++ b/racket/collects/openssl/mzssl.rkt
> @@ -34,6 +34,7 @@ TO DO:
>           racket/tcp
>           racket/string
>           racket/lazy-require
> +         racket/runtime-path
>           "libcrypto.rkt"
>           "libssl.rkt")
>  (lazy-require
> @@ -41,7 +42,9 @@ TO DO:
>   ["private/macosx.rkt" (load-macosx-keychain)])
>  
>  (define protocol-symbol/c
> -  (or/c 'sslv2-or-v3 'sslv2 'sslv3 'tls))
> +  (or/c 'sslv2-or-v3 'sslv2 'sslv3 'tls 'tls11 'tls12))
> +(define curves/c
> +  (or/c 'secp521r1))
>  
>  (define verify-source/c
>    (or/c path-string?
> @@ -50,6 +53,7 @@ TO DO:
>          (list/c 'macosx-keychain path-string?)))
>  
>  (provide
> + ssl-dh-param-path
>   (contract-out
>    [ssl-available? boolean?]
>    [ssl-load-fail-reason (or/c #f string?)]
> @@ -59,6 +63,10 @@ TO DO:
>     (c-> ssl-client-context?)]
>    [ssl-make-server-context
>     (->* () (protocol-symbol/c) ssl-server-context?)]
> +  [ssl-server-context-enable-dhe!
> +   (->* (ssl-server-context?) (path-string?) void?)]
> +  [ssl-server-context-enable-ecdhe!
> +   (->* (ssl-server-context?) (curves/c) void?)]
>    [ssl-client-context?
>     (c-> any/c boolean?)]
>    [ssl-server-context?
> @@ -185,6 +193,8 @@ TO DO:
>  (define-cpointer-type _X509*)
>  (define-cpointer-type _ASN1_STRING*)
>  (define-cpointer-type _STACK*)
> +(define-cpointer-type _DH*)
> +(define-cpointer-type _EC_KEY*)
>  (define-cstruct _GENERAL_NAME ([type _int] [d _ASN1_STRING*]))
>  
>  (define-ssl SSLv2_client_method (_fun -> _SSL_METHOD*))
> @@ -195,9 +205,19 @@ TO DO:
>  (define-ssl SSLv23_server_method (_fun -> _SSL_METHOD*))
>  (define-ssl TLSv1_client_method (_fun -> _SSL_METHOD*))
>  (define-ssl TLSv1_server_method (_fun -> _SSL_METHOD*))
> +(define-ssl TLSv1_1_client_method (_fun -> _SSL_METHOD*))
> +(define-ssl TLSv1_1_server_method (_fun -> _SSL_METHOD*))
> +(define-ssl TLSv1_2_client_method (_fun -> _SSL_METHOD*))
> +(define-ssl TLSv1_2_server_method (_fun -> _SSL_METHOD*))
> +
> +(define-crypto DH_free (_fun _DH* -> _void) #:wrap (deallocator))
> +(define-crypto EC_KEY_free (_fun _EC_KEY* -> _void) #:wrap (deallocator))
> +
> +(define-crypto EC_KEY_new_by_curve_name (_fun _int -> _EC_KEY*) #:wrap (allocator EC_KEY_free))
>  
>  (define-crypto BIO_s_mem (_fun -> _BIO_METHOD*))
>  (define-crypto BIO_new (_fun _BIO_METHOD* -> _BIO*/null))
> +(define-crypto BIO_new_mem_buf (_fun _pointer _int -> _BIO*))
>  (define-crypto BIO_free (_fun _BIO* -> _void))
>  
>  (define-crypto BIO_read (_fun _BIO* _bytes _int -> _int))
> @@ -259,6 +279,7 @@ TO DO:
>  (define-ssl SSL_load_error_strings (_fun -> _void))
>  
>  (define-crypto GENERAL_NAME_free _fpointer)
> +(define-crypto PEM_read_bio_DHparams (_fun _BIO* _pointer _pointer _pointer -> _DH*) #:wrap (allocator DH_free))
>  (define-crypto ASN1_STRING_length (_fun _ASN1_STRING* -> _int))
>  (define-crypto ASN1_STRING_data (_fun _ASN1_STRING* -> _pointer))
>  (define-crypto X509_NAME_get_index_by_NID (_fun _X509_NAME* _int _int -> _int))
> @@ -331,8 +352,20 @@ TO DO:
>  (define NID_commonName 13)
>  (define GEN_DNS 2)
>  
> +(define SSL_CTRL_SET_ECDH_AUTO 94)
> +(define SSL_CTRL_OPTIONS 32)
> +(define SSL_CTRL_SET_TMP_DH 3)
> +(define SSL_CTRL_SET_TMP_ECDH 4)
> +
> +(define SSL_OP_SINGLE_ECDH_USE #x00080000)
> +(define SSL_OP_SINGLE_DH_USE #x00100000)
> +
> +(define NID_secp521r1 716)
> +
>  (define-mzscheme scheme_make_custodian (_fun _pointer -> _scheme))
>  
> +(define-runtime-path ssl-dh-param-path "dh4096.pem")
> +
>  ;; Make this bigger than 4096 to accommodate at least
>  ;; 4096 of unencrypted data
>  (define BUFFER-SIZE 8000)
> @@ -467,6 +500,10 @@ TO DO:
>        (if client? SSLv3_client_method SSLv3_server_method)]
>       [(tls)
>        (if client? TLSv1_client_method TLSv1_server_method)]
> +     [(tls11)
> +      (if client? TLSv1_1_client_method TLSv1_1_server_method)]
> +     [(tls12)
> +      (if client? TLSv1_2_client_method TLSv1_2_server_method)]
>       [else
>        (error 'encrypt->method "internal error, unknown encrypt: ~e" e)])))
>  
> @@ -515,6 +552,34 @@ TO DO:
>  (define (ssl-seal-context! mzctx)
>    (set-ssl-context-sealed?! mzctx #t))
>  
> +(define (ssl-server-context-enable-ecdhe! context [name 'secp521r1])
> +  (define (symbol->nid name)
> +    (case name
> +      [(secp521r1) NID_secp521r1]
> +      [else NID_secp521r1]))
> +  (define ctx (extract-ctx 'ssl-server-context-enable-ecdhe! #t context))
> +  (define key (EC_KEY_new_by_curve_name (symbol->nid name)))
> +  (check-valid key 'ssl-server-context-enable-ecdhe! "Could not enable ECDH(E)")
> +  (unless (= 1 (SSL_CTX_ctrl ctx SSL_CTRL_SET_TMP_ECDH 0 key))
> +    (error 'ssl-server-context-enable-ecdhe! "Could not enable ECDH(E)"))
> +  (SSL_CTX_ctrl ctx SSL_CTRL_OPTIONS SSL_OP_SINGLE_ECDH_USE #f)
> +  (void))
> +
> +(define (ssl-server-context-enable-dhe! context [path ssl-dh-param-path])
> +  (define params (call-with-input-file path port->bytes))
> +  (define params-bio (BIO_new_mem_buf params (bytes-length params)))
> +  (check-valid params-bio 'ssl-server-context-enable-dhe! "Diffie-Hellman parameters")
> +  (with-failure
> +    (lambda ()
> +      (BIO_free params-bio))
> +    (define ctx (extract-ctx 'ssl-server-context-enable-dhe! #t context))
> +    (define dh (PEM_read_bio_DHparams params-bio #f #f #f))
> +    (check-valid dh 'ssl-server-context-enable-dhe "Diffie-Hellman parameters")
> +    (unless (= 1 (SSL_CTX_ctrl ctx SSL_CTRL_SET_TMP_DH 0 dh))
> +      (error 'ssl-server-context-enable-dhe "Could not enable DHE"))
> +    (SSL_CTX_ctrl ctx SSL_CTRL_OPTIONS SSL_OP_SINGLE_DH_USE #f)
> +    (void)))
> +
>  (define (ssl-load-... who load-it ssl-context-or-listener pathname
>                        #:try? [try? #f])
>    (let ([ctx (get-context/listener who ssl-context-or-listener

> _________________________
>   Racket Developers list:
>   http://lists.racket-lang.org/dev


Posted on the dev mailing list.