Okay, replies inline, followed by updated patch. On 4/29/14, Colin Percival <cperc...@tarsnap.com> wrote: > Let's add a new option instead: > -g Require perfect forward secrecy by dropping connections if the > other host is using the -f option.
Done. Also enforce that -f and -g aren't both used at the same time. Not sure if there's a better way to express that in the usage blurb, though. > This is wrong. We need to detect 1; we don't need to detect 0. (A validly > signed 0 implies that someone who has the shared key is not following the > protocol, in which case we've already lost.) Done, although I still have a question about this (below). On Wed, Apr 30, 2014 at 1:33 AM, Colin Percival <cperc...@tarsnap.com> wrote: > There's lots of "impossible" values -- all quadratic non-residues, for example > -- but there's no point checking for all of them. There's always going to be > ways that a participant can deliberately sabotage the protocol (by revealing > the negotiated keys, if nothing else); the point of the protocol is to protect > compliant hosts from non-participants. Even the -g option isn't about any > level > of cryptographic security; it's purely about detecting misconfigurations. Fair enough. But you already sanity-check the values to be < the modulus. Why is that okay but checking for 0 is not? Checking for 0 can be considered a sanity-check, too. > Style is not wrong -- the opening '{' of the function should be on a > separate line, and 'size_t i' should be the first line of the function, > followed by an empty line. (Even if you prefer a different style, you > should be consistent with spiped's style rules when working on spiped > code.) Done. Sorry, meant to copy surrounding style, but forgot. > Also, I'd prefer to avoid the early return by accumulating: > size_t i; > char y; > > for (i = 0, y = 0; i < len - 1; i++) > y |= x[i]; > return (y | (x[len - 1] - 1)); > and making the function "is_not_one". Done. I thought you might prefer a constant-time check. I was originally going to go with that, but I noticed that crypto_dh_sanitycheck was already non-constant time (since it uses memcmp). Was that deliberate? > This will all need to be revised to use the new -g / 'requirefps' option > instead of overloading the -f / 'nofps' option. Done. diff --git a/proto/proto_conn.c b/proto/proto_conn.c index f657544..f01d667 100644 --- a/proto/proto_conn.c +++ b/proto/proto_conn.c @@ -20,6 +20,7 @@ struct conn_state { struct sock_addr ** sas; int decr; int nofps; + int requirefps; int nokeepalive; const struct proto_secret * K; double timeo; @@ -56,7 +57,7 @@ starthandshake(struct conn_state * C, int s, int decr) /* Start the handshake. */ if ((C->handshake_cookie = proto_handshake(s, decr, C->nofps, - C->K, callback_handshake_done, C)) == NULL) + C->requirefps, C->K, callback_handshake_done, C)) == NULL) goto err1; /* Success! */ @@ -153,21 +154,23 @@ dropconn(struct conn_state * C) } /** - * proto_conn_create(s, sas, decr, nofps, nokeepalive, K, timeo, + * proto_conn_create(s, sas, decr, nofps, requirefps, nokeepalive, K, timeo, * callback_dead, cookie): * Create a connection with one end at ${s} and the other end connecting to * the target addresses ${sas}. If ${decr} is 0, encrypt the outgoing data; * if ${decr} is nonzero, decrypt the outgoing data. If ${nofps} is non-zero, - * don't use perfect forward secrecy. Enable transport layer keep-alives (if - * applicable) on both sockets if and only if ${nokeepalive} is zero. Drop the - * connection if the handshake or connecting to the target takes more than - * ${timeo} seconds. When the connection is dropped, invoke - * ${callback_dead}(${cookie}). Free ${sas} once it is no longer needed. + * don't use perfect forward secrecy. if ${requirefps} is non-zero, drop + * the connection if the other end tries to disable perfect forward secrecy. + * Enable transport layer keep-alives (if applicable) on both sockets if and + * only if ${nokeepalive} is zero. Drop the connection if the handshake or + * connecting to the target takes more than ${timeo} seconds. When the + * connection is dropped, invoke ${callback_dead}(${cookie}). Free ${sas} + * once it is no longer needed. */ int proto_conn_create(int s, struct sock_addr ** sas, int decr, int nofps, - int nokeepalive, const struct proto_secret * K, double timeo, - int (* callback_dead)(void *), void * cookie) + int requirefps, int nokeepalive, const struct proto_secret * K, + double timeo, int (* callback_dead)(void *), void * cookie) { struct conn_state * C; @@ -179,6 +182,7 @@ proto_conn_create(int s, struct sock_addr ** sas, int decr, int nofps, C->sas = sas; C->decr = decr; C->nofps = nofps; + C->requirefps = requirefps; C->nokeepalive = nokeepalive; C->K = K; C->timeo = timeo; diff --git a/proto/proto_conn.h b/proto/proto_conn.h index 5030d1d..f1fa42a 100644 --- a/proto/proto_conn.h +++ b/proto/proto_conn.h @@ -6,18 +6,20 @@ struct proto_secret; struct sock_addr; /** - * proto_conn_create(s, sas, decr, nofps, nokeepalive, K, timeo, + * proto_conn_create(s, sas, decr, nofps, requirefps, nokeepalive, K, timeo, * callback_dead, cookie): * Create a connection with one end at ${s} and the other end connecting to * the target addresses ${sas}. If ${decr} is 0, encrypt the outgoing data; * if ${decr} is nonzero, decrypt the outgoing data. If ${nofps} is non-zero, - * don't use perfect forward secrecy. Enable transport layer keep-alives (if - * applicable) on both sockets if and only if ${nokeepalive} is zero. Drop the - * connection if the handshake or connecting to the target takes more than - * ${timeo} seconds. When the connection is dropped, invoke - * ${callback_dead}(${cookie}). Free ${sas} once it is no longer needed. + * don't use perfect forward secrecy. if ${requirefps} is non-zero, drop + * the connection if the other end tries to disable perfect forward secrecy. + * Enable transport layer keep-alives (if applicable) on both sockets if and + * only if ${nokeepalive} is zero. Drop the connection if the handshake or + * connecting to the target takes more than ${timeo} seconds. When the + * connection is dropped, invoke ${callback_dead}(${cookie}). Free ${sas} + * once it is no longer needed. */ -int proto_conn_create(int, struct sock_addr **, int, int, int, +int proto_conn_create(int, struct sock_addr **, int, int, int, int, const struct proto_secret *, double, int (*)(void *), void *); #endif /* !_CONN_H_ */ diff --git a/proto/proto_crypt.c b/proto/proto_crypt.c index d674bfc..8a70147 100644 --- a/proto/proto_crypt.c +++ b/proto/proto_crypt.c @@ -145,14 +145,32 @@ void proto_crypt_dhmac(const struct proto_secret * K, } /** - * proto_crypt_dh_validate(yh_r, dhmac_r): + * is_not_one(x, len): + * Returns non-zero if the big-endian value stored at (${x}, ${len}) is not + * equal to 1. + */ +static int is_not_one(const uint8_t *x, size_t len) +{ + size_t i; + char y; + + for (i = 0, y = 0; i < len - 1; i++) { + y |= x[i]; + } + + return (y | (x[len - 1] - 1)); +} + +/** + * proto_crypt_dh_validate(yh_r, dhmac_r, requirefps): * Return non-zero if the value ${yh_r} received from the remote party is not * correctly MACed using the diffie-hellman parameter MAC key ${dhmac_r}, or - * if the included y value is >= the diffie-hellman group modulus. + * if the included y value is >= the diffie-hellman group modulus, or if + * ${requirefps} is non-zero and the included y value is 1. */ int proto_crypt_dh_validate(const uint8_t yh_r[PCRYPT_YH_LEN], - const uint8_t dhmac_r[PCRYPT_DHMAC_LEN]) + const uint8_t dhmac_r[PCRYPT_DHMAC_LEN], int requirefps) { uint8_t hbuf[32]; @@ -165,7 +183,11 @@ proto_crypt_dh_validate(const uint8_t yh_r[PCRYPT_YH_LEN], return (1); /* Sanity-check the diffie-hellman value. */ - return (crypto_dh_sanitycheck(&yh_r[0])); + if (crypto_dh_sanitycheck(&yh_r[0])) + return (1); + + /* Enforce that the diffie-hellman value != 1 if necessary. */ + return ((requirefps != 0) && ! is_not_one(&yh_r[0], CRYPTO_DH_PUBLEN)); } /** diff --git a/proto/proto_crypt.h b/proto/proto_crypt.h index 8801aa7..be1d3dc 100644 --- a/proto/proto_crypt.h +++ b/proto/proto_crypt.h @@ -39,13 +39,14 @@ void proto_crypt_dhmac(const struct proto_secret *, uint8_t[PCRYPT_DHMAC_LEN], uint8_t[PCRYPT_DHMAC_LEN], int); /** - * proto_crypt_dh_validate(yh_r, dhmac_r): + * proto_crypt_dh_validate(yh_r, dhmac_r, requirefps): * Return non-zero if the value ${yh_r} received from the remote party is not * correctly MACed using the diffie-hellman parameter MAC key ${dhmac_r}, or - * if the included y value is >= the diffie-hellman group modulus. + * if the included y value is >= the diffie-hellman group modulus, or if + * ${requirefps} is non-zero and the included y value is 1. */ int proto_crypt_dh_validate(const uint8_t[PCRYPT_YH_LEN], - const uint8_t[PCRYPT_DHMAC_LEN]); + const uint8_t[PCRYPT_DHMAC_LEN], int); /** * proto_crypt_dh_generate(yh_l, x, dhmac_l, nofps): diff --git a/proto/proto_handshake.c b/proto/proto_handshake.c index 0c87caa..d34591d 100644 --- a/proto/proto_handshake.c +++ b/proto/proto_handshake.c @@ -16,6 +16,7 @@ struct handshake_cookie { int s; int decr; int nofps; + int requirefps; const struct proto_secret * K; uint8_t nonce_local[PCRYPT_NONCE_LEN]; uint8_t nonce_remote[PCRYPT_NONCE_LEN]; @@ -60,18 +61,20 @@ handshakefail(struct handshake_cookie * H) } /** - * proto_handshake(s, decr, nofps, K, callback, cookie): + * proto_handshake(s, decr, nofps, requirefps, K, callback, cookie): * Perform a protocol handshake on socket ${s}. If ${decr} is non-zero we are * at the receiving end of the connection; otherwise at the sending end. If * ${nofps} is non-zero, perform a "weak" handshake without forward perfect - * secrecy. The shared protocol secret is ${K}. Upon completion, invoke - * ${callback}(${cookie}, f, r) where f contains the keys needed for the - * forward direction and r contains the keys needed for the reverse direction; - * or w = r = NULL if the handshake failed. Return a cookie which can be - * passed to proto_handshake_cancel to cancel the handshake. + * secrecy. If ${requirefps} is non-zero, drop the connection if the other end + * attempts to do a "weak" handshake. The shared protocol secret is ${K}. + * Upon completion, invoke ${callback}(${cookie}, f, r) where f contains the + * keys needed for the forward direction and r contains the keys needed for + * the reverse direction; or w = r = NULL if the handshake failed. Return a + * cookie which can be passed to proto_handshake_cancel to cancel the handshake. */ void * -proto_handshake(int s, int decr, int nofps, const struct proto_secret * K, +proto_handshake(int s, int decr, int nofps, int requirefps, + const struct proto_secret * K, int (* callback)(void *, struct proto_keys *, struct proto_keys *), void * cookie) { @@ -85,6 +88,7 @@ proto_handshake(int s, int decr, int nofps, const struct proto_secret * K, H->s = s; H->decr = decr; H->nofps = nofps; + H->requirefps = requirefps; H->K = K; /* Generate a 32-byte connection nonce. */ @@ -209,7 +213,8 @@ callback_dh_read(void * cookie, ssize_t len) return (handshakefail(H)); /* Is the value we read valid? */ - if (proto_crypt_dh_validate(H->yh_remote, H->dhmac_remote)) + if (proto_crypt_dh_validate(H->yh_remote, H->dhmac_remote, + H->requirefps)) return (handshakefail(H)); /* diff --git a/proto/proto_handshake.h b/proto/proto_handshake.h index 80d3813..925ad97 100644 --- a/proto/proto_handshake.h +++ b/proto/proto_handshake.h @@ -6,17 +6,18 @@ struct proto_keys; struct proto_secret; /** - * proto_handshake(s, decr, nofps, K, callback, cookie): + * proto_handshake(s, decr, nofps, requirefps, K, callback, cookie): * Perform a protocol handshake on socket ${s}. If ${decr} is non-zero we are * at the receiving end of the connection; otherwise at the sending end. If * ${nofps} is non-zero, perform a "weak" handshake without forward perfect - * secrecy. The shared protocol secret is ${K}. Upon completion, invoke - * ${callback}(${cookie}, f, r) where f contains the keys needed for the - * forward direction and r contains the keys needed for the reverse direction; - * or w = r = NULL if the handshake failed. Return a cookie which can be - * passed to proto_handshake_cancel to cancel the handshake. + * secrecy. If ${requirefps} is non-zero, drop the connection if the other end + * attempts to do a "weak" handshake. The shared protocol secret is ${K}. + * Upon completion, invoke ${callback}(${cookie}, f, r) where f contains the + * keys needed for the forward direction and r contains the keys needed for + * the reverse direction; or w = r = NULL if the handshake failed. Return a + * cookie which can be passed to proto_handshake_cancel to cancel the handshake. */ -void * proto_handshake(int, int, int, const struct proto_secret *, +void * proto_handshake(int, int, int, int, const struct proto_secret *, int (*)(void *, struct proto_keys *, struct proto_keys *), void *); /** diff --git a/spipe/main.c b/spipe/main.c index 960f421..e55304b 100644 --- a/spipe/main.c +++ b/spipe/main.c @@ -32,7 +32,7 @@ usage(void) { fprintf(stderr, "usage: spipe -t <target socket> -k <key file>" - " [-fj] [-o <connection timeout>]\n"); + " [-{f | g}j] [-o <connection timeout>]\n"); exit(1); } @@ -47,6 +47,7 @@ main(int argc, char * argv[]) { /* Command-line parameters. */ int opt_f = 0; + int opt_g = 0; int opt_j = 0; const char * opt_k = NULL; double opt_o = 0.0; @@ -61,13 +62,18 @@ main(int argc, char * argv[]) WARNP_INIT; /* Parse the command line. */ - while ((ch = getopt(argc, argv, "fjk:o:t:")) != -1) { + while ((ch = getopt(argc, argv, "fgjk:o:t:")) != -1) { switch (ch) { case 'f': - if (opt_f) + if (opt_f || opt_g) usage(); opt_f = 1; break; + case 'g': + if (opt_f || opt_g) + usage(); + opt_g = 1; + break; case 'j': if (opt_j) usage(); @@ -142,7 +148,7 @@ main(int argc, char * argv[]) } /* Set up a connection. */ - if (proto_conn_create(s[1], sas_t, 0, opt_f, opt_j, K, opt_o, + if (proto_conn_create(s[1], sas_t, 0, opt_f, opt_g, opt_j, K, opt_o, callback_conndied, NULL)) { warnp("Could not set up connection"); exit(1); diff --git a/spipe/spipe.1 b/spipe/spipe.1 index 5086d6b..f6708a5 100644 --- a/spipe/spipe.1 +++ b/spipe/spipe.1 @@ -28,7 +28,7 @@ spipe \- spiped client utility .B spipe \-t <target socket> \-k <key file> -[\-fj] +[\-{f | g}j] [\-o <connection timeout>] .SH OPTIONS .TP @@ -43,6 +43,10 @@ Use fast/weak handshaking: This reduces the CPU time spent in the initial connection setup, at the expense of losing perfect forward secrecy. .TP +.B \-g +Require perfect forward secrecy by dropping connections if the other +host is using the -f option. +.TP .B \-j Disable transport layer keep-alives. (By default they are enabled.) diff --git a/spiped/dispatch.c b/spiped/dispatch.c index e21540b..8316298 100644 --- a/spiped/dispatch.c +++ b/spiped/dispatch.c @@ -20,6 +20,7 @@ struct accept_state { double rtime; int decr; int nofps; + int requirefps; int nokeepalive; const struct proto_secret * K; size_t nconn; @@ -124,8 +125,8 @@ callback_gotconn(void * cookie, int s) goto err1; /* Create a new connection. */ - if (proto_conn_create(s, sas, A->decr, A->nofps, A->nokeepalive, - A->K, A->timeo, callback_conndied, A)) { + if (proto_conn_create(s, sas, A->decr, A->nofps, A->requirefps, + A->nokeepalive, A->K, A->timeo, callback_conndied, A)) { warnp("Failure setting up new connection"); goto err2; } @@ -148,22 +149,23 @@ err0: } /** - * dispatch_accept(s, tgt, rtime, sas, decr, nofps, nokeepalive, K, nconn_max, - * timeo): + * dispatch_accept(s, tgt, rtime, sas, decr, nofps, requirefps, nokeepalive, K, + * nconn_max, timeo): * Start accepting connections on the socket ${s}. Connect to the target * ${tgt}, re-resolving it every ${rtime} seconds if ${rtime} > 0; on address * resolution failure use the most recent successfully obtained addresses, or * the addresses ${sas}. If ${decr} is 0, encrypt the outgoing connections; if * ${decr} is non-zero, decrypt the incoming connections. Don't accept more * than ${nconn_max} connections. If ${nofps} is non-zero, don't use perfect - * forward secrecy. Enable transport layer keep-alives (if applicable) if and - * only if ${nokeepalive} is zero. Drop connections if the handshake or + * forward secrecy. if {$requirefps} is non-zero, require that both ends use + * perfect forward secrecy. Enable transport layer keep-alives (if applicable) + * if and only if ${nokeepalive} is zero. Drop connections if the handshake or * connecting to the target takes more than ${timeo} seconds. */ int dispatch_accept(int s, const char * tgt, double rtime, struct sock_addr ** sas, - int decr, int nofps, int nokeepalive, const struct proto_secret * K, - size_t nconn_max, double timeo) + int decr, int nofps, int requirefps, int nokeepalive, + const struct proto_secret * K, size_t nconn_max, double timeo) { struct accept_state * A; @@ -176,6 +178,7 @@ dispatch_accept(int s, const char * tgt, double rtime, struct sock_addr ** sas, A->rtime = rtime; A->decr = decr; A->nofps = nofps; + A->requirefps = requirefps; A->nokeepalive = nokeepalive; A->K = K; A->nconn = 0; diff --git a/spiped/dispatch.h b/spiped/dispatch.h index e2a1680..4bd8d04 100644 --- a/spiped/dispatch.h +++ b/spiped/dispatch.h @@ -8,19 +8,20 @@ struct proto_secret; struct sock_addr; /** - * dispatch_accept(s, tgt, rtime, sas, decr, nofps, nokeepalive, K, nconn_max, - * timeo): + * dispatch_accept(s, tgt, rtime, sas, decr, nofps, requirefps, nokeepalive, K, + * nconn_max, timeo): * Start accepting connections on the socket ${s}. Connect to the target * ${tgt}, re-resolving it every ${rtime} seconds if ${rtime} > 0; on address * resolution failure use the most recent successfully obtained addresses, or * the addresses ${sas}. If ${decr} is 0, encrypt the outgoing connections; if * ${decr} is non-zero, decrypt the incoming connections. Don't accept more * than ${nconn_max} connections. If ${nofps} is non-zero, don't use perfect - * forward secrecy. Enable transport layer keep-alives (if applicable) if and - * only if ${nokeepalive} is zero. Drop connections if the handshake or + * forward secrecy. if {$requirefps} is non-zero, require that both ends use + * perfect forward secrecy. Enable transport layer keep-alives (if applicable) + * if and only if ${nokeepalive} is zero. Drop connections if the handshake or * connecting to the target takes more than ${timeo} seconds. */ int dispatch_accept(int, const char *, double, struct sock_addr **, int, int, - int, const struct proto_secret *, size_t, double); + int, int, const struct proto_secret *, size_t, double); #endif /* !_DISPATCH_H_ */ diff --git a/spiped/main.c b/spiped/main.c index 3cbf51c..070805d 100644 --- a/spiped/main.c +++ b/spiped/main.c @@ -19,7 +19,7 @@ usage(void) fprintf(stderr, "usage: spiped {-e | -d} -s <source socket> " "-t <target socket> -k <key file>\n" - " [-DfFj] [-n <max # connections>] " + " [-D{f | g}Fj] [-n <max # connections>] " "[-o <connection timeout>] [-p <pidfile>]\n" " [{-r <rtime> | -R}]\n"); exit(1); @@ -39,6 +39,7 @@ main(int argc, char * argv[]) int opt_D = 0; int opt_e = 0; int opt_f = 0; + int opt_g = 0; int opt_F = 0; int opt_j = 0; const char * opt_k = NULL; @@ -60,7 +61,7 @@ main(int argc, char * argv[]) WARNP_INIT; /* Parse the command line. */ - while ((ch = getopt(argc, argv, "dDefFjk:n:o:r:Rp:s:t:")) != -1) { + while ((ch = getopt(argc, argv, "dDefFgjk:n:o:r:Rp:s:t:")) != -1) { switch (ch) { case 'd': if (opt_d || opt_e) @@ -78,7 +79,7 @@ main(int argc, char * argv[]) opt_e = 1; break; case 'f': - if (opt_f) + if (opt_f || opt_g) usage(); opt_f = 1; break; @@ -87,6 +88,11 @@ main(int argc, char * argv[]) usage(); opt_F = 1; break; + case 'g': + if (opt_f || opt_g) + usage(); + opt_g = 1; + break; case 'j': if (opt_j) usage(); @@ -238,7 +244,7 @@ main(int argc, char * argv[]) /* Start accepting connections. */ if (dispatch_accept(s, opt_t, opt_R ? 0.0 : opt_r, sas_t, opt_d, - opt_f, opt_j, K, opt_n, opt_o)) { + opt_f, opt_g, opt_j, K, opt_n, opt_o)) { warnp("Failed to initialize connection acceptor"); exit(1); } diff --git a/spiped/spiped.1 b/spiped/spiped.1 index 09d5abe..3fba263 100644 --- a/spiped/spiped.1 +++ b/spiped/spiped.1 @@ -30,7 +30,7 @@ spiped \- secure pipe daemon \-t <target socket> \-k <key file> .br -[\-DfFj] +[\-D{f | g}Fj] [\-n <max # connections>] [\-o <connection timeout>] [\-p <pidfile>] @@ -75,6 +75,10 @@ Use fast/weak handshaking: This reduces the CPU time spent in the initial connection setup, at the expense of losing perfect forward secrecy. .TP +.B \-g +Require perfect forward secrecy by dropping connections if the other +host is using the -f option. +.TP .B \-F Run in foreground. This can be useful with systems like daemontools. .TP