Is it ASCII or Protobuf? The importance of types in cryptographic signatures
blog.sandstorm.io
blog.sandstorm.io
[...] all those web sites must use exactly the same format for
authentication challenges.
[...] the key cannot be used for any automated purpose [...]
[...] all applications which use the key for signing must include
such a context string, and context strings must be chosen to avoid conflicts.
[...] better than either of the above is to use a different key for each purpose.
That's shifting protocol problems to standardization and human caution. It may work, but I'm skeptic.Maybe a change in the cryptographic protocol can fix the problem? For example, instead of
Server: server_value // domain + action + nonce + ...
Client: sign(server_value)
we use Server: server_value
Client: sign(client_random) + sign(server_value XOR client_random)
and the server verifies that the two signed components, when XOR'd, result in the same value sent. This way the client only signs random data but can still authenticate.This is not supposed to be a final solution, and is likely to have flaws, but I think it's a better direction to pursue.
And keep in mind this is not a complete authentication protocol. I'm just proposing a small change to fix the signing-not-random-nonce problem, so I assume the rest of the protocol context is kept.
Barring stupid mistakes from my part, if this is vulnerable to impersonation so is the article's.
A E B
-hello->
-hello->
<-nonce-
<-nonce-
-random->
-S(rand)>
-S(n^r)->
-random->
-S(rand)>
-S(n^r)->(nice diagram, by the way)
To prevent this, Alice must also sign the domain she's authenticating to. The article mentioned this, and maybe you had meant it to be implicit in your protocol, in which case: my bad.
Server: server_nonce
Client: random_key + sign(hmac(server_nonce, random_key))
That's a nice alternative. Though generating random data and XOR'ing is as known and as primitive as you can get, and none of it is ever reused, so I have no preferences of one over the other.(if you are thinking "why not just sign the hash of the nonce without bothering with a key?", that's exactly how we sign documents, because they are long. You could sign the hash of the hash to avoid this conflict, but that's a hack)
Consider the original protocol: server sends a t-bit nonce N, client signs it.
Suppose that Eve can passively observe Alice's authentications to Bob's server, but she can't actively MitM the connection. Her best generic attack is a birthday attack:
1. Eve observes 2^b authentications and remembers each (N, S(N)) pair.
2. Eve attempts to authenticate as Alice. If Bob sends an N she has seen before, she's in. If he sends a fresh N, she aborts and tries again.
Eve will succeed in step 2 after roughly 2^(t-b) attempts.
Now consider the amended protocol. In each authentication, Eve observes two signatures: S(R) and S(R^N). Each is a signature over what is essentially a random t-bit number. This allows Eve to improve her attack:
1. Eve observes 2^b authentications. She stores two (X, S(X)) pairs per authentication: one for X = R and one for X = R^N. The size of the set is 2^(b+1).
2. Eve attempts to authenticate as Alice. Bob sends N. For each X in her set of observations, Eve calculates X' = X^N. If X' is also in the set, Eve wins. If not, she aborts and tries again.
Notice that Eve is now colliding N with the set of (X, X') pairs. Since this set grows with the square of Alice's authentication attempts, Eve will succeed after roughly 2^(t-2(b+1)) attempts.
This attack is plausible for sufficiently small t (say, 64) and sufficiently large b (say, 16-20). While b will be very small in web login scenarios, large b may be plausible in automated authentication scenarios.
This can obviously be defeated by choosing a sufficiently large t.
EDIT: (I'm not 100% certain about all the math. Corrections are welcome.)
(By the way, if you still think it's broken, I'm ball to improve it. "Wrestling with pig"...)
Of course, the context string is also vulnerable to, say, a colliding protocol that happens to ignore leading strings (which is not unheard of). So ideally you specify that "this key shall only sign messages that begin with a context string", and then you're pretty solid. This is effectively saying "the type of things signed by this key are arbitrarily-typed messages prefixed with a NUL-terminated human-readable string identifying their type", which satisfies my "only sign unambiguously-typed data" rule. :)
(If you're referring to my edit earlier: I had previously thought I saw an attack that wasn't there, and had noticed other people claiming brokenness, so I assumed they were pointing it out. But then I realized the problem I imagined wasn't there. Sorry about that.)
Here it is (still under development): https://bitbucket.org/binarno/goingthere
Can be embedded into streams, but it has a "strict" flag that forces the parser to throw an error if unexpected data is found in the stream. Optional tags not specified in the schema simply cannot be there, and all the tags must be encoded following a specific order.
Still looking for a simple protocol that allows to have a normalized representation that is always the same for the same set of data; I hate to develop my own things and prefer to steal ready made things :-)
Though this may be something of a tangent, since as kentonv says, canonicalization is only part of the dance; it all depends on what other actors do as well. :)
In SSH what gets signed in publickey (and hostkey) authentication is not controlled by server (client). Instead of some random value provided by other side, signed data include session id that is derived from result of first DH key exchange and thus unpredictable to either side of connection.
The unpredictability of DH output depends on the group parameters and public key validation performed by the peer. If the group contains small subgroups, Eve can send an element of small order and predict the DH output with high probability.
For example, this is one of the attack vectors for the triple handshake attack on TLS: https://www.secure-resumption.com/tlsauth.pdf.
1) The DH group is negotiated by both peers from fixed list. 2) output of key exchange is hashed with various values that are not all controlled by same side of connection
Also after reading the relevant part of RFC4253 I've found out that the session id is conceptually an output of key exchange, but in the DH case it's derived from contents of key exchange messages and not from the result itself (not that it makes much of an difference).
The server has the public key and verifies.
In fact, even if the server itself uses some sort of challenge-response, it can use the response anywhere that implements your policy.
The message you generate will only work for the server you generate it for, and likewise, you must ensure you are securely delivering the message to the correct server. It's often useful to treat these as two distinct problems solved at different layers in the stack (1) establish secure channel to shared public server, (2) authenticate.
Or are the rules of the game this has to work over an unencrypted channel? In which case you need two keys and you do DH.
But this is beside the point. You still have the problem that you are signing a thing, and if the signing key isn't restricted to signing only this type of thing, there is a risk that the thing you are signing could have a different meaning when interpreted as a different type. Even if the client fully-controls the content of the thing to be signed, this is still possible. Perhaps you can dismiss it as "unlikely", but I'm not sure I'm comfortable doing so.