[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Forward secrecy in spiped
Okay, replies inline, followed by updated patch.
On 4/29/14, Colin Percival <cperciva@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 <cperciva@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