Discussion:
Forward secrecy in spiped
Frederick Akalin
2014-04-24 22:27:16 UTC
Permalink
Hey all,
From reading through the protocol, it seems there are two modes for
forward secrecy:

A) Use forward secrecy, but allow the other side to turn it off. (default)
B) Turn off forward secrecy.

However, there could conceivably be a third mode:

C) Use forward secrecy, and terminate any connection that tries to turn it off.

The only problem is that if an endpoint receives 1 for the y value of
the other side, it doesn't necessarily know that the other side has 0
for its x value. (I'd have to check whether it's possible, given the
specific modulus and the possible range of x, to rule out a non-zero x
for a zero y value, unless someone already knows the answer to
this...)

This third mode would make it possible to guard against
misconfigurations, e.g. I might want forward secrecy to be always
used, and for stuff to blow up / complain if that changes. Is this a
valid use case?

-- Fred
Colin Percival
2014-04-24 23:03:18 UTC
Permalink
Post by Frederick Akalin
From reading through the protocol, it seems there are two modes for
A) Use forward secrecy, but allow the other side to turn it off. (default)
B) Turn off forward secrecy.
Correct.
Post by Frederick Akalin
C) Use forward secrecy, and terminate any connection that tries to turn it off.
The only problem is that if an endpoint receives 1 for the y value of the
other side, it doesn't necessarily know that the other side has 0 for its x
value. (I'd have to check whether it's possible, given the specific modulus
and the possible range of x, to rule out a non-zero x for a zero y value,
unless someone already knows the answer to this...)
If you receive y=1 from a protocol-compliant endpoint, it is running with
FPS turned off.

Protocol non-compliant endpoints could hardcode other values, e.g., y=2,
which would also have the effect of breaking FPS, but of course non-compliant
endpoints could do all sorts of things to deliberately leak keys.
Post by Frederick Akalin
This third mode would make it possible to guard against misconfigurations,
e.g. I might want forward secrecy to be always used, and for stuff to blow
up / complain if that changes. Is this a valid use case?
It's certainly plausible as an anti-foot-shooting mechanism. It doesn't gain
you any theoretical security (since it can be circumvented), but it might still
be useful in practice.

Want to send me a patch?
--
Colin Percival
Security Officer Emeritus, FreeBSD | The power to serve
Founder, Tarsnap | www.tarsnap.com | Online backups for the truly paranoid
Frederick Akalin
2014-04-25 00:08:56 UTC
Permalink
Post by Colin Percival
If you receive y=1 from a protocol-compliant endpoint, it is running with
FPS turned off.
You're right. I had to think for a bit to come up with a proof -- for
anyone else who is wondering, it follows from there being no
non-trivial square roots of unity mod p for prime p, and from the fact
that we're working in the group of quadratic residues mod p.
Post by Colin Percival
Protocol non-compliant endpoints could hardcode other values, e.g., y=2,
which would also have the effect of breaking FPS, but of course non-compliant
endpoints could do all sorts of things to deliberately leak keys.
Yeah, there's not much we can do to prevent that.
Post by Colin Percival
It's certainly plausible as an anti-foot-shooting mechanism. It doesn't gain
you any theoretical security (since it can be circumvented), but it might still
be useful in practice.
Want to send me a patch?
Sure, I can take a crack at it. I'll let you (and the list) know when
I have something.

-- Fred
Colin Percival
2014-04-25 07:13:34 UTC
Permalink
Post by Frederick Akalin
Post by Colin Percival
If you receive y=1 from a protocol-compliant endpoint, it is running with
FPS turned off.
You're right. I had to think for a bit to come up with a proof -- for
anyone else who is wondering, it follows from there being no
non-trivial square roots of unity mod p for prime p, and from the fact
that we're working in the group of quadratic residues mod p.
Much simpler proof: The standard MODP groups, including group #14 which we use,
are selected such that q = (p - 1) / 2 is prime. Consequently the group of
quadratic residues is cyclic and 2^x = 1 mod p means that x = 0 mod q. :-)
--
Colin Percival
Security Officer Emeritus, FreeBSD | The power to serve
Founder, Tarsnap | www.tarsnap.com | Online backups for the truly paranoid
Frederick Akalin
2014-04-29 11:38:07 UTC
Permalink
Post by Colin Percival
Want to send me a patch?
Okay, here is my first attempt. I found it to be simplest to mandate
that both sides use -f to turn off forward secrecy, but I don't know
if that rules out valid use cases. Thoughts?

(Also, let me know if the diff below doesn't work. Gmail's handling of
plain text content is...special.)

-- Fred

diff --git a/proto/proto_crypt.c b/proto/proto_crypt.c
index d674bfc..092f57b 100644
--- a/proto/proto_crypt.c
+++ b/proto/proto_crypt.c
@@ -145,14 +145,29 @@ void proto_crypt_dhmac(const struct proto_secret * K,
}

/**
- * proto_crypt_dh_validate(yh_r, dhmac_r):
+ * is_zero_or_one(x, len):
+ * Returns non-zero if the big-endian value stored at (${x}, ${len}) is equal
+ * to either 0 or 1.
+ */
+static int is_zero_or_one(const uint8_t *x, size_t len) {
+ for (size_t i = 0; i < len - 1; i++) {
+ if (x[i] != 0)
+ return (0);
+ }
+
+ return ((x[len - 1] == 0) || (x[len - 1] == 1));
+}
+
+/**
+ * proto_crypt_dh_validate(yh_r, dhmac_r, nofps):
* 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
+ * "${nofps} is zero" does not equal "the included 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 nofps)
{
uint8_t hbuf[32];

@@ -165,7 +180,15 @@ 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);
+
+ /* Check that forward perfect secrecy is used if and only if the
+ * diffie-hellman value is > 1. */
+ if ((nofps != 0) != (is_zero_or_one(&yh_r[0], CRYPTO_DH_PUBLEN) != 0))
+ return (1);
+
+ return (0);
}

/**
diff --git a/proto/proto_crypt.h b/proto/proto_crypt.h
index 8801aa7..cd8fbcb 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, nofps):
* 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
+ * "${nofps} is zero" does not equal "the included 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..c677e07 100644
--- a/proto/proto_handshake.c
+++ b/proto/proto_handshake.c
@@ -209,7 +209,7 @@ 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->nofps))
return (handshakefail(H));

/*
diff --git a/spipe/spipe.1 b/spipe/spipe.1
index 5086d6b..c39f2bf 100644
--- a/spipe/spipe.1
+++ b/spipe/spipe.1
@@ -41,7 +41,7 @@ Use the provided key file to authenticate and encrypt.
.B \-f
Use fast/weak handshaking: This reduces the CPU time spent in the
initial connection setup, at the expense of losing perfect forward
-secrecy.
+secrecy. The other party must also use this flag.
.TP
.B \-j
Disable transport layer keep-alives.
diff --git a/spiped/spiped.1 b/spiped/spiped.1
index 09d5abe..82ca7b3 100644
--- a/spiped/spiped.1
+++ b/spiped/spiped.1
@@ -73,7 +73,7 @@ finished launching it will be ready to create pipes.
.B \-f
Use fast/weak handshaking: This reduces the CPU time spent in the
initial connection setup, at the expense of losing perfect forward
-secrecy.
+secrecy. The other party must also use this flag.
.TP
.B \-F
Run in foreground. This can be useful with systems like daemontools.
Colin Percival
2014-04-30 06:52:15 UTC
Permalink
Post by Frederick Akalin
Post by Colin Percival
Want to send me a patch?
Okay, here is my first attempt.
Code review follows. If you prefer I can fix things myself, but since you did
the first draft I figure I should give you the option. :-)
Post by Frederick Akalin
I found it to be simplest to mandate
that both sides use -f to turn off forward secrecy, but I don't know
if that rules out valid use cases. Thoughts?
Can't do that -- it would break backwards compatibility. (I don't know if
anyone is running with -f on one endpoint and not on the other, and if they
are it's probably a mistake... but we still have to avoid any possibility
that upgrading to a newer version of spiped will turn a working setup into
a non-working setup.)

Let's add a new option instead:
-g Require perfect forward secrecy by dropping connections if the
other host is using the -f option.
Post by Frederick Akalin
+ * Returns non-zero if the big-endian value stored at (${x}, ${len}) is equal
+ * to either 0 or 1.
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.)
Post by Frederick Akalin
+ */
+static int is_zero_or_one(const uint8_t *x, size_t len) {
+ for (size_t i = 0; i < len - 1; i++) {
+ if (x[i] != 0)
+ return (0);
+ }
+
+ return ((x[len - 1] == 0) || (x[len - 1] == 1));
+}
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.)

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".
Post by Frederick Akalin
+/**
* 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
+ * "${nofps} is zero" does not equal "the included 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 nofps)
{
uint8_t hbuf[32];
@@ -165,7 +180,15 @@ 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);
+
+ /* Check that forward perfect secrecy is used if and only if the
+ * diffie-hellman value is > 1. */
+ if ((nofps != 0) != (is_zero_or_one(&yh_r[0], CRYPTO_DH_PUBLEN) != 0))
+ return (1);
+
+ return (0);
}
/**
diff --git a/proto/proto_crypt.h b/proto/proto_crypt.h
index 8801aa7..cd8fbcb 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);
/**
* 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
+ * "${nofps} is zero" does not equal "the included 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);
/**
diff --git a/proto/proto_handshake.c b/proto/proto_handshake.c
index 0c87caa..c677e07 100644
--- a/proto/proto_handshake.c
+++ b/proto/proto_handshake.c
@@ -209,7 +209,7 @@ 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->nofps))
return (handshakefail(H));
/*
diff --git a/spipe/spipe.1 b/spipe/spipe.1
index 5086d6b..c39f2bf 100644
--- a/spipe/spipe.1
+++ b/spipe/spipe.1
@@ -41,7 +41,7 @@ Use the provided key file to authenticate and encrypt.
.B \-f
Use fast/weak handshaking: This reduces the CPU time spent in the
initial connection setup, at the expense of losing perfect forward
-secrecy.
+secrecy. The other party must also use this flag.
.TP
.B \-j
Disable transport layer keep-alives.
diff --git a/spiped/spiped.1 b/spiped/spiped.1
index 09d5abe..82ca7b3 100644
--- a/spiped/spiped.1
+++ b/spiped/spiped.1
@@ -73,7 +73,7 @@ finished launching it will be ready to create pipes.
.B \-f
Use fast/weak handshaking: This reduces the CPU time spent in the
initial connection setup, at the expense of losing perfect forward
-secrecy.
+secrecy. The other party must also use this flag.
.TP
.B \-F
Run in foreground. This can be useful with systems like daemontools.
This will all need to be revised to use the new -g / 'requirefps' option
instead of overloading the -f / 'nofps' option.
--
Colin Percival
Security Officer Emeritus, FreeBSD | The power to serve
Founder, Tarsnap | www.tarsnap.com | Online backups for the truly paranoid
Frederick Akalin
2014-04-30 07:28:19 UTC
Permalink
Post by Colin Percival
Code review follows. If you prefer I can fix things myself, but since you
did
the first draft I figure I should give you the option. :-)
Thanks. A question before I start revising...
Post by Colin Percival
Can't do that -- it would break backwards compatibility. (I don't know if
anyone is running with -f on one endpoint and not on the other, and if they
are it's probably a mistake... but we still have to avoid any possibility
that upgrading to a newer version of spiped will turn a working setup into
a non-working setup.)
-g Require perfect forward secrecy by dropping connections if
the
other host is using the -f option.
Ok.
Post by Colin Percival
Post by Frederick Akalin
+ * Returns non-zero if the big-endian value stored at (${x}, ${len}) is
equal
Post by Frederick Akalin
+ * to either 0 or 1.
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.)
Isn't that an argument for detecting 0 even if -g isn't specified? It seems
to be to be better to drop connections which are detected to not be
conforming.
Colin Percival
2014-04-30 08:33:15 UTC
Permalink
Post by Colin Percival
Post by Frederick Akalin
+ * Returns non-zero if the big-endian value stored at (${x}, ${len}) is equal
+ * to either 0 or 1.
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.)
Isn't that an argument for detecting 0 even if -g isn't specified? It seems to
be to be better to drop connections which are detected to not be conforming.
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.
--
Colin Percival
Security Officer Emeritus, FreeBSD | The power to serve
Founder, Tarsnap | www.tarsnap.com | Online backups for the truly paranoid
Frederick Akalin
2014-04-30 23:41:32 UTC
Permalink
Okay, replies inline, followed by updated patch.
Post by Colin Percival
-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.
Post by Colin Percival
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).
Post by Colin Percival
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.
Post by Colin Percival
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.
Post by Colin Percival
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?
Post by Colin Percival
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
Colin Percival
2014-05-10 09:04:18 UTC
Permalink
Sorry about the delay, I've been triaging more urgent vs. less urgent emails...
Post by Frederick Akalin
Post by Colin Percival
-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.
I think showing it as alternatives in the synopsis is good enough. If someone
gets confused and offers improved documentation I can always accept it later.
Post by Frederick Akalin
Post by Colin Percival
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).
Post by Colin Percival
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.
Well... the sanity checking for y < N isn't really about detecting harmless
protocol noncompliance. That's me being paranoid about the possibility of
breakage in crypto libraries -- it *should* be fine, but OpenSSL demonstrates
that assuming that crypto libraries get anything at all right is not a very
good idea.
Post by Frederick Akalin
Post by Colin Percival
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?
Good point. I guess I was being less paranoid when I wrote _dh_sanitycheck;
either that or I just got lazy. Considering that this is a value which goes
out over the wire, it's not really a problem.
Post by Frederick Akalin
+static int is_not_one(const uint8_t *x, size_t len)
There should be a line break after 'static int'; function implementations
always have their names at the start of a line (unlike function declarations)
so that "grep -R ^functionname .' will find them. Spaces on both sides of
the '*' for consistency with other spiped code.
Post by Frederick Akalin
+ return ((requirefps != 0) && ! is_not_one(&yh_r[0], CRYPTO_DH_PUBLEN));
This is confusingly complicated -- I've broken this out to
/* If necessary, enforce that the diffie-hellman value is != 1. */
if (requirefps) {
if (! is_not_one(&yh_r[0], CRYPTO_DH_PUBLEN))
return (1);
}

/* Everything is good. */
return (0);
Post by Frederick Akalin
+ * 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}.
For consistency with my pretentious language, s/do/perform/.
Post by Frederick Akalin
+ * don't use perfect forward secrecy. if ${requirefps} is non-zero, drop
s/if/If/.
Post by Frederick Akalin
-[\-fj]
+[\-{f | g}j]
This should be [-f | -g] [-j], since braces are used only to offer a mandatory
choice (as with spiped {-e | -d}).

You forgot to update the README files.
Post by Frederick Akalin
- if (opt_f)
+ if (opt_f || opt_g)
usage();
Checking for contradictory options goes under "Sanity-check options", after the
end of the getopt loop. The tests inside the loop are just to catch specifying
the same option multiple times.

Committed, thanks!
--
Colin Percival
Security Officer Emeritus, FreeBSD | The power to serve
Founder, Tarsnap | www.tarsnap.com | Online backups for the truly paranoid
Frederick Akalin
2014-05-11 19:22:20 UTC
Permalink
Post by Colin Percival
Sorry about the delay, I've been triaging more urgent vs. less urgent emails...
No problem -- following security news, I figured you had more important
things to take care of. :)
Post by Colin Percival
Committed, thanks!
Thanks for the review and committing!

-- Fred

Loading...