Hi,

On Wed, Jul 02, 2014 at 01:34:51PM +0200, Markus Gebert wrote:
> I hope this is the right mailing list to publish a patch. If not,
> please let me know where to place it or how I should get in contact
> with the relayd maintainer(s).
> 
> I've added some new SSL features and config options to relayd that we
> needed at work:
> 
> - ssl edh params "value"
>   -> allows to control the use of OpenSSL built-in DH params. With
>      them, relayd is now able to use EDH/DHE ciphers. They are the
>      only option to provide forward secrecy for older clients.
> 
> - ssl [no] cipher-server-preference
>   -> allows the admin to control SSL_OP_CIPHER_SERVER_PREFERENCE to be
>      able to force some cipher preferences on clients (i.e. to prefer
>      ciphers that provide forward secrecy).
> 
> - ssl [no] client-renegotiation
>   -> allows the interception of ("secure") client initiated
>      renegotioations, which are considered a risk in DDoS scenarios
>      because many CPU cycles can be burned this way on a single TCP
>      connection without an obvious way for the administrator to 
>      immediately know what's happening.
> 
> 
> The patch was originally written against the FreeBSD port of relayd, but
> since all the changes should be easily portable to OpenBSD and it makes
> sense to add the features upstream, I recreated the patch so that it
> applies to OpenBSD (5.5 and CURRENT). It's only compile-tested, but the
> changed code parts do not really differ between the platforms, so I
> think the patch should be safe to use.
> 
> Patch for OpenBSD 5.5:
> http://gebert.net/share/relayd-ssl-5.5.diff
> 
> Patch for OpenBSD CURRENT as of 2014-07-01:
> http://gebert.net/share/relayd-ssl-current-20140701.diff
> 
> 
> Please feel free to change any defaults I’ve chosen for the new config
> options. They were chosen for the environmet I run relayd in and might
> not fit your policies about introducing new features.
> 
> Any feedback is welcome!
> 

Thank you for the diff, I updated the diff and changed a few defaults
after discussing them with jsing@.  I also adjusted the grammar a
little bit to the following style (also for ecdh):

ssl edh                 - enable edh with the default maximum of 1024 bits
ssl no edh              - disable edh (the default, provided for consistency)
ssl edh params 8192     - set a non-default maximum

I removed the "auto" option because providing 8192 will effectively be
the same.  I also adjusted the ecdh option accordingly:

ssl ecdh                - enable ecdh (the default, using prime256v1)
ssl no ecdh             - disable ecdh
ssl ecdh curve prime256v1       - set a specific curve

The other options cipher-server-preference and client-renegotiation
make sense, but I changed it to allow "client-renegotiation" by
default - I want to avoid too many differences to the standard SSL
library.  Please note that relayd is now developed for LibreSSL where
more defaults might be different to OpenSSL in the future.


Reyk

Index: config.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/config.c,v
retrieving revision 1.17
diff -u -p -u -p -r1.17 config.c
--- config.c    11 Jul 2014 11:48:50 -0000      1.17
+++ config.c    11 Jul 2014 15:28:21 -0000
@@ -118,6 +118,7 @@ config_init(struct relayd *env)
                    SSLCIPHERS_DEFAULT,
                    sizeof(env->sc_proto_default.sslciphers));
                env->sc_proto_default.sslecdhcurve = SSLECDHCURVE_DEFAULT;
+               env->sc_proto_default.ssldhparams = SSLDHPARAMS_DEFAULT;
                env->sc_proto_default.type = RELAY_PROTO_TCP;
                (void)strlcpy(env->sc_proto_default.name, "default",
                    sizeof(env->sc_proto_default.name));
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/parse.y,v
retrieving revision 1.185
diff -u -p -u -p -r1.185 parse.y
--- parse.y     11 Jul 2014 11:48:50 -0000      1.185
+++ parse.y     11 Jul 2014 15:28:22 -0000
@@ -167,8 +167,8 @@ typedef struct {
 %token QUERYSTR REAL REDIRECT RELAY REMOVE REQUEST RESPONSE RETRY QUICK
 %token RETURN ROUNDROBIN ROUTE SACK SCRIPT SEND SESSION SNMP SOCKET SPLICE
 %token SSL STICKYADDR STYLE TABLE TAG TAGGED TCP TIMEOUT TO ROUTER RTLABEL
-%token TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL RTABLE MATCH
-%token RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDH CURVE
+%token TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL RTABLE MATCH PARAMS
+%token RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDH EDH CURVE
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.string>      hostname interface table value optstring
@@ -177,6 +177,7 @@ typedef struct {
 %type  <v.number>      optssl optsslclient sslcache
 %type  <v.number>      redirect_proto relay_proto match
 %type  <v.number>      action ruleaf key_option
+%type  <v.number>      ssldhparams sslecdhcurve
 %type  <v.port>        port
 %type  <v.host>        host
 %type  <v.addr>        address
@@ -904,6 +905,7 @@ proto               : relay_proto PROTO STRING      {
                        TAILQ_INIT(&p->rules);
                        (void)strlcpy(p->sslciphers, SSLCIPHERS_DEFAULT,
                            sizeof(p->sslciphers));
+                       p->ssldhparams = SSLDHPARAMS_DEFAULT;
                        p->sslecdhcurve = SSLECDHCURVE_DEFAULT;
                        if (last_proto_id == INT_MAX) {
                                yyerror("too many protocols defined");
@@ -1002,15 +1004,17 @@ sslflags        : SESSION CACHE sslcache        { prot
                        }
                        free($2);
                }
-               | ECDH CURVE STRING             {
-                       if (strcmp("none", $3) == 0)
-                               proto->sslecdhcurve = 0;
-                       else if ((proto->sslecdhcurve = OBJ_sn2nid($3)) == 0) {
-                               yyerror("ECDH curve not supported");
-                               free($3);
-                               YYERROR;
-                       }
-                       free($3);
+               | NO EDH                        {
+                       proto->ssldhparams = SSLDHPARAMS_NONE;
+               }
+               | EDH ssldhparams               {
+                       proto->ssldhparams = $2;
+               }
+               | NO ECDH                       {
+                       proto->sslecdhcurve = 0;
+               }
+               | ECDH sslecdhcurve             {
+                       proto->sslecdhcurve = $2;
                }
                | CA FILENAME STRING            {
                        if (strlcpy(proto->sslca, $3,
@@ -1061,6 +1065,10 @@ flag             : STRING                        {
                                $$ = SSLFLAG_SSLV3;
                        else if (strcmp("tlsv1", $1) == 0)
                                $$ = SSLFLAG_TLSV1;
+                       else if (strcmp("cipher-server-preference", $1) == 0)
+                               $$ = SSLFLAG_CIPHER_SERVER_PREF;
+                       else if (strcmp("client-renegotiation", $1) == 0)
+                               $$ = SSLFLAG_CLIENT_RENEG;
                        else {
                                yyerror("invalid SSL flag: %s", $1);
                                free($1);
@@ -1451,6 +1459,29 @@ key_option       : /* empty */           { $$ = KEY_OPT
                | LOG                   { $$ = KEY_OPTION_LOG; }
                ;
 
+ssldhparams    : /* empty */           { $$ = SSLDHPARAMS_MIN; }
+               | PARAMS NUMBER         {
+                       if ($2 < SSLDHPARAMS_MIN) {
+                               yyerror("EDH params not supported: %d", $2);
+                               YYERROR;
+                       }
+                       $$ = $2;
+               }
+               ;
+
+sslecdhcurve   : /* empty */           { $$ = SSLECDHCURVE_DEFAULT; }
+               | CURVE STRING          {
+                       if (strcmp("none", $2) == 0)
+                               $$ = 0;
+                       else if ((proto->sslecdhcurve = OBJ_sn2nid($2)) == 0) {
+                               yyerror("ECDH curve not supported");
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+               }
+               ;
+
 relay          : RELAY STRING  {
                        struct relay *r;
 
@@ -2059,6 +2090,7 @@ lookup(char *s)
                { "digest",             DIGEST },
                { "disable",            DISABLE },
                { "ecdh",               ECDH },
+               { "edh",                EDH },
                { "error",              ERROR },
                { "expect",             EXPECT },
                { "external",           EXTERNAL },
@@ -2090,6 +2122,7 @@ lookup(char *s)
                { "nodelay",            NODELAY },
                { "nothing",            NOTHING },
                { "on",                 ON },
+               { "params",             PARAMS },
                { "parent",             PARENT },
                { "pass",               PASS },
                { "password",           PASSWORD },
Index: relay.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay.c,v
retrieving revision 1.172
diff -u -p -u -p -r1.172 relay.c
--- relay.c     9 Jul 2014 16:42:05 -0000       1.172
+++ relay.c     11 Jul 2014 15:28:23 -0000
@@ -43,6 +43,7 @@
 #include <event.h>
 #include <fnmatch.h>
 
+#include <openssl/dh.h>
 #include <openssl/ssl.h>
 
 #include "relayd.h"
@@ -72,6 +73,9 @@ void           relay_input(struct rsession *);
 
 u_int32_t       relay_hash_addr(struct sockaddr_storage *, u_int32_t);
 
+DH *            relay_ssl_get_dhparams(int);
+void            relay_ssl_callback_info(const SSL *, int, int);
+DH             *relay_ssl_callback_dh(SSL *, int, int);
 SSL_CTX                *relay_ssl_ctx_create(struct relay *);
 void            relay_ssl_transaction(struct rsession *,
                    struct ctl_relay_event *);
@@ -1847,6 +1851,103 @@ relay_dispatch_parent(int fd, struct pri
        return (0);
 }
 
+void
+relay_ssl_callback_info(const SSL *ssl, int where, int rc)
+{
+       struct ctl_relay_event *cre;
+       int ssl_state;
+
+       cre = (struct ctl_relay_event *)SSL_get_app_data(ssl);
+
+       if (cre == NULL || cre->sslreneg_state == SSLRENEG_ALLOW)
+               return;
+
+       ssl_state = SSL_get_state(ssl);
+
+       /* Check renegotiations */
+       if ((where & SSL_CB_ACCEPT_LOOP) &&
+           (cre->sslreneg_state == SSLRENEG_DENY)) {
+               if ((ssl_state == SSL3_ST_SR_CLNT_HELLO_A) ||
+                   (ssl_state == SSL23_ST_SR_CLNT_HELLO_A)) {
+                       /*
+                        * This is a client initiated renegotiation
+                        * that we do not allow
+                        */
+                       cre->sslreneg_state = SSLRENEG_ABORT;
+               }
+       } else if ((where & SSL_CB_HANDSHAKE_DONE) &&
+           (cre->sslreneg_state == SSLRENEG_INIT)) {
+               /*
+                * This is right after the first handshake,
+                * disallow any further negotiations.
+                */
+               cre->sslreneg_state = SSLRENEG_DENY;
+       }
+}
+
+DH *
+relay_ssl_get_dhparams(int keylen)
+{
+       DH              *dh;
+       BIGNUM          *(*prime)(BIGNUM *);
+       const char      *gen;
+
+       gen = "2";
+       if (keylen >= 8192)
+               prime = get_rfc3526_prime_8192;
+       else if (keylen >= 4096)
+               prime = get_rfc3526_prime_4096;
+       else if (keylen >= 3072)
+               prime = get_rfc3526_prime_3072;
+       else if (keylen >= 2048)
+               prime = get_rfc3526_prime_2048;
+       else if (keylen >= 1536)
+               prime = get_rfc3526_prime_1536;
+       else
+               prime = get_rfc2409_prime_1024;
+
+       if ((dh = DH_new()) == NULL)
+               return (NULL);
+
+       dh->p = (*prime)(NULL);
+       BN_dec2bn(&dh->g, gen);
+
+       if (dh->p == NULL || dh->g == NULL) {
+               DH_free(dh);
+               return (NULL);
+       }
+
+       return (dh);
+}
+
+DH *
+relay_ssl_callback_dh(SSL *ssl, int export, int keylen)
+{
+       struct ctl_relay_event  *cre;
+       EVP_PKEY                *pkey;
+       int                      keytype, maxlen;
+       DH                      *dh = NULL;
+
+       /* Get maximum key length from config */
+       if ((cre = (struct ctl_relay_event *)SSL_get_app_data(ssl)) == NULL)
+               return (NULL);
+       maxlen = cre->con->se_relay->rl_proto->ssldhparams;
+
+       /* Get the private key length from the cert */
+       if ((pkey = SSL_get_privatekey(ssl))) {
+               keytype = EVP_PKEY_type(pkey->type);
+               if (keytype == EVP_PKEY_RSA || keytype == EVP_PKEY_DSA)
+                       keylen = EVP_PKEY_bits(pkey);
+               else
+                       return (NULL);
+       }
+
+       /* get built-in params based on the shorter key length */
+       dh = relay_ssl_get_dhparams(MIN(keylen, maxlen));
+
+       return (dh);
+}
+
 SSL_CTX *
 relay_ssl_ctx_create(struct relay *rlay)
 {
@@ -1873,6 +1974,8 @@ relay_ssl_ctx_create(struct relay *rlay)
        SSL_CTX_set_options(ctx, SSL_OP_ALL);
        SSL_CTX_set_options(ctx,
            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+       if (proto->sslflags & SSLFLAG_CIPHER_SERVER_PREF)
+               SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
 
        /* Set the allowed SSL protocols */
        if ((proto->sslflags & SSLFLAG_SSLV2) == 0)
@@ -1882,6 +1985,9 @@ relay_ssl_ctx_create(struct relay *rlay)
        if ((proto->sslflags & SSLFLAG_TLSV1) == 0)
                SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
 
+       /* add the SSL info callback */
+       SSL_CTX_set_info_callback(ctx, relay_ssl_callback_info);
+
        if (proto->sslecdhcurve > 0) {
                /* Enable ECDHE support for TLS perfect forward secrecy */
                if ((ecdhkey =
@@ -1892,6 +1998,11 @@ relay_ssl_ctx_create(struct relay *rlay)
                EC_KEY_free(ecdhkey);
        }
 
+       if (proto->ssldhparams > 0) {
+               /* Enable EDH params (forward secrecy for older clients) */
+               SSL_CTX_set_tmp_dh_callback(ctx, relay_ssl_callback_dh);
+       }
+
        if (!SSL_CTX_set_cipher_list(ctx, proto->sslciphers))
                goto err;
 
@@ -1953,6 +2064,7 @@ void
 relay_ssl_transaction(struct rsession *con, struct ctl_relay_event *cre)
 {
        struct relay            *rlay = con->se_relay;
+       struct protocol *proto = rlay->rl_proto;
        SSL                     *ssl;
        const SSL_METHOD        *method;
        void                    (*cb)(int, short, void *);
@@ -1981,11 +2093,21 @@ relay_ssl_transaction(struct rsession *c
        if (!SSL_set_fd(ssl, cre->s))
                goto err;
 
-       if (cre->dir == RELAY_DIR_REQUEST)
+       if (cre->dir == RELAY_DIR_REQUEST) {
+               if ((proto->sslflags & SSLFLAG_CLIENT_RENEG) == 0)
+                       /* Only allow negotiation during the first handshake */
+                       cre->sslreneg_state = SSLRENEG_INIT;
+               else
+                       /* Allow client initiated renegotiations */
+                       cre->sslreneg_state = SSLRENEG_ALLOW;
                SSL_set_accept_state(ssl);
-       else
+       } else {
+               /* Always allow renegotiations if we're the client */
+               cre->sslreneg_state = SSLRENEG_ALLOW;
                SSL_set_connect_state(ssl);
+       }
 
+       SSL_set_app_data(ssl, cre);
        cre->ssl = ssl;
 
        DPRINTF("%s: session %d: scheduling on %s", __func__, con->se_id,
@@ -2166,6 +2288,11 @@ relay_ssl_readcb(int fd, short event, vo
                goto err;
        }
 
+       if (cre->sslreneg_state == SSLRENEG_ABORT) {
+               what |= EVBUFFER_ERROR;
+               goto err;
+       }
+
        if (bufev->wm_read.high != 0)
                howmuch = MIN(sizeof(rbuf), bufev->wm_read.high);
 
@@ -2235,6 +2362,11 @@ relay_ssl_writecb(int fd, short event, v
 
        if (event == EV_TIMEOUT) {
                what |= EVBUFFER_TIMEOUT;
+               goto err;
+       }
+
+        if (cre->sslreneg_state == SSLRENEG_ABORT) {
+               what |= EVBUFFER_ERROR;
                goto err;
        }
 
Index: relayd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.conf.5,v
retrieving revision 1.146
diff -u -p -u -p -r1.146 relayd.conf.5
--- relayd.conf.5       9 Jul 2014 19:17:08 -0000       1.146
+++ relayd.conf.5       11 Jul 2014 15:28:24 -0000
@@ -873,14 +873,45 @@ will be used (strong crypto cipher suite
 See the CIPHERS section of
 .Xr openssl 1
 for information about SSL cipher suites and preference lists.
-.It Ic ecdh curve Ar name
+.It Oo Ic no Oc Ic cipher-server-preference
+Prefer the server's cipher list over the client's preferences when
+choosing a cipher for the connection;
+disabled by default.
+.It Oo Ic no Oc Ic client-renegotiation
+Allow client-initiated renegotiation;
+enabled by default.
+Disable to mitigate a potential DoS risk.
+.It Ic ecdh Op Ic curve Ar name
 Set a named curve to use when generating EC keys for ECDHE-based
-cipher suites with Perfect Forward Security (PFS), or
-.Ar none
-to disable ECDHE support.
-If not specified, the default curve
+cipher suites with Perfect Forward Security (PFS).
+If the curve
+.Ar name
+is not specified, the default curve
 .Ar prime256v1
 will be used.
+ECDHE is enabled by default.
+.It Ic no ecdh
+Disable ECDHE support.
+.It Ic edh Op Ic params Ar maximum
+Enable EDH-based cipher suites with Perfect Forward Security (PFS) for
+older clients that do not support ECDHE.
+If the
+.Ar maximum
+length of the DH params is not specified, the default value of
+.Ar 1024
+bits will be used.
+Other possible values are numbers between 1024 and 8192, including
+.Ar 1024 ,
+.Ar 1536 ,
+.Ar 2048 ,
+.Ar 4096 ,
+or
+.Ar 8192 .
+Values higher than 1024 bits can cause incompatibilities with older
+SSL clients.
+.It Ic no edh
+Disable EDH support.
+This is the default.
 .It Ic session cache Ar value
 Set the maximum size of the SSL session cache.
 If the
Index: relayd.h
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v
retrieving revision 1.183
diff -u -p -u -p -r1.183 relayd.h
--- relayd.h    11 Jul 2014 11:48:50 -0000      1.183
+++ relayd.h    11 Jul 2014 15:28:24 -0000
@@ -161,6 +161,13 @@ enum direction {
        RELAY_DIR_RESPONSE      =  2
 };
 
+enum sslreneg_state {
+       SSLRENEG_INIT           = 0,    /* first/next negotiation is allowed */
+       SSLRENEG_ALLOW          = 1,    /* all (re-)negotiations are allowed */
+       SSLRENEG_DENY           = 2,    /* next renegotiation must be denied */
+       SSLRENEG_ABORT          = 3     /* the connection should be aborted */
+};
+
 struct ctl_relay_event {
        int                      s;
        in_port_t                port;
@@ -169,8 +176,10 @@ struct ctl_relay_event {
        struct evbuffer         *output;
        struct ctl_relay_event  *dst;
        struct rsession         *con;
+
        SSL                     *ssl;
        X509                    *sslcert;
+       enum sslreneg_state      sslreneg_state;
 
        off_t                    splicelen;
        int                      line;
@@ -622,18 +631,26 @@ TAILQ_HEAD(relay_rules, relay_rule);
        "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK"           \
        "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE"
 
-#define SSLFLAG_SSLV2          0x01
-#define SSLFLAG_SSLV3          0x02
-#define SSLFLAG_TLSV1          0x04
-#define SSLFLAG_VERSION                0x07
-#define SSLFLAG_DEFAULT                (SSLFLAG_SSLV3|SSLFLAG_TLSV1)
+#define SSLFLAG_SSLV2                          0x01
+#define SSLFLAG_SSLV3                          0x02
+#define SSLFLAG_TLSV1                          0x04
+#define SSLFLAG_VERSION                                0x07
+#define SSLFLAG_CIPHER_SERVER_PREF             0x08
+#define SSLFLAG_CLIENT_RENEG                   0x10
+#define SSLFLAG_DEFAULT                                \
+       (SSLFLAG_SSLV3|SSLFLAG_TLSV1|SSLFLAG_CLIENT_RENEG)
 
 #define SSLFLAG_BITS                                           \
-       "\10\01sslv2\02sslv3\03tlsv1\04version"
+       "\10\01sslv2\02sslv3\03tlsv1"                           \
+       "\04cipher-server-preference\05client-renegotiation"
 
 #define SSLCIPHERS_DEFAULT     "HIGH:!aNULL"
 #define SSLECDHCURVE_DEFAULT   NID_X9_62_prime256v1
 
+#define SSLDHPARAMS_NONE       0
+#define SSLDHPARAMS_DEFAULT    0
+#define SSLDHPARAMS_MIN                1024
+
 struct protocol {
        objid_t                  id;
        u_int32_t                flags;
@@ -642,8 +659,9 @@ struct protocol {
        int                      tcpbacklog;
        u_int8_t                 tcpipttl;
        u_int8_t                 tcpipminttl;
-       u_int8_t                 sslflags;
+       u_int16_t                sslflags;
        char                     sslciphers[768];
+       int                      ssldhparams;
        int                      sslecdhcurve;
        char                     sslca[MAXPATHLEN];
        char                     sslcacert[MAXPATHLEN];

Reply via email to