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];