Subject: Re: [bitcoin-dev] p2p authentication and encryption BIPs

From: Lee Clagett <[email-masked]>
Date: Sat, 9 Apr 2016 15:40:38 -0400

On Fri, 1 Apr 2016 23:09:47 +0200
Jonas Schnelli via bitcoin-dev <[email-masked]>
wrote:
> > I have just PRed a draft version of two BIPs I recently wrote.
> > https://github.com/bitcoin/bips/pull/362  
> 
> Hi.
> I just updated the PR above with another overhaul of the BIP.
> It's still under heavy review/work, nevertheless – at this point – any
> feedback is highly welcome.
> 
> Changes since last update:
> -> Removed AES256-GCM as cipher suite
> -> Focusing on Chacha20-Poly1305 (implementation size ~300L)
> -> Two symmetric cipher keys must be calculated by HMAC_SHA512 from
> the ecdh secret
> -> A session-ID (both directions) must be calculated (HMAC_SHA256)
> for linking an identity authentication (ecdsa sig of the session-ID)
> with the encryption
> -> Re-Keying ('=hash(old_key)') can be announced by the responding
> peer (after x minutes and/or after x GB, local peer policy but not
> shorter then 10mins).
> -> AEAD tag is now the last element in the new message format  
> 
> It is very likely that the encrypted message format performs slightly
> better than the current message format (removing the SHA256 checksum).
> 
> ---
> </jonas>
> 

The quotes below are from the BIPs and not the email chain ...

> Rejecting the <code>auth</code> request will not reveal the
> responding peers identity that could lead to fingerprinting the node,
> however this BIP does not cover protection against fingerprinting the
> requesting node from the perspective of the responding node.

In many use cases the requesting node will want to make a connection to
a peer with a specific identity. After encryption initialization, the
requesting node could generate an ECDH secret from the long-term public
key of the expected peer and its own session private-key to encrypt (no
MAC) the signature with the same symmetric cipher agreed upon
previously. The requesting node will not reveal its identity if the
connection has been MitM'ed, while still being the first to provide
authentication. And since this would be "inside" the session-key
crypto, it still has forward-secrecy if the responding-peers longterm
private-key is later compromised.

*Key Revocation*
This is probably too complicated, but an additional public key would
allow for cold-storage key revocation. Spreading the knowledge of such
an event is always painful, but it could be stored in the blockchain. I
think this is likely too complicated, but having these long-term keys
constantly in memory/disk is unfortunate.

> Responding peers must ignore the requesting peer after a
> unsuccessfully authentication initialization to avoid resource
> attacks (banning would lead to fingerprinting of peers that support
> authentication). 

Once the responding peer has read the `auth` message, a TCP ACK can be
sent. From the requesting peer perspective, a TCP ACK of the `auth`
request indicates that it was read by the process or some
intermediary buffer (TOE, proxy, etc) has successfully forwarded it to
the next step. If the requesting peer waits RTT * some constant from
the ACK and gets no response, then either: a failed `auth` occurred,
`auth` is not supported, or the machine was suddenly overloaded. The
requesting peer can then send another message; a response message
indicates the responding peer does not support `auth`, and another no
response wait period indicates an overloaded peer or an `auth` enabled
peer. Initiating a new connection (no banning has occurred) indicates
either `auth` is enabled or a load-balancer re-directed the new
connection to another machine under less load. I think the latter case
is going to be rare, so you should be able to identify with high
probability nodes that support `auth` and what message types require
`auth`. And if this is process repeated multiple times, it will increase
the chances of a correct fingerprint.

Should encryption enabled peers who do _not_ support `auth` ignore all
subsequent messages after an `auth` attempt too? Fingerprinting on
`auth` required message types would still be possible. I do not see a
reliable way to prevent this from occurring.

> To request encrypted communication, the requesting peer generates an
> EC ephemeral-session-keypair and sends an <code>encinit</code>
> message to the responding peer and waits for a <code>encack</code>
> message. The responding node must do the same
> <code>encinit</code>/<code>encack</code> interaction for the opposite
> communication direction.

Why are there two key exchanges? A single shared-secret could be used
to generate keys for each direction. And it would reinforce the single
symmetric cipher rule.

> Possible symmetric key ciphers types
> {|class="wikitable"
> ! Number !! symmetric key ciphers type !! Comments
> |-
> | 0 || Chacha20-Poly1305 [3] || encrypted message length must be used
> as AAD. |}
>

Chacha20-Poly1305 defined in an IETF draft [0] and RFC 7539 both
include the ciphertext length in the authentication tag generation. Is
this a unique authentication construction? Or one of the previously
mentioned designs?

*Symmetric Cipher Negotiation*
Should the symmetric cipher choices be removed? I am mainly asking for
the intended use-case. If the intent is to replace a weakened cipher
then leave the cipher negotiation. If the intent is to give
implementations multiple options, then I would remove this negotiation.

> <code>K_1</code> must be used to only encrypt the payload size of the
> encrypted message to avoid leaking information by revealing the
> message size. 
> 
> <code>K_2</code> must be used in conjunction with poly1305 to build
> an AEAD.

Chacha20 is a stream cipher, so only a single encryption key is needed.
The first 32 bytes of the keystream would be used for the Poly1305 key,
the next 4 bytes would be used to encrypt the length field, and the
remaining keystream would be used to encrypt the payload. Poly1305
would then generate a tag over the length and payload. The receiver
would generate the same keystream to decrypt the length which
identifies the length of the message and the MAC offset, then
authenticate the length and payload, then decypt with the remaining
keystream.

Is it safer to define two keys to prevent implementations from screwing
this up? You have to split the decryption and authentication, so the
basic modes of libsodium cannot be used for instance. If a custom tag
generation scheme is being used, then the basic modes are already
unusable ...

*Failed Authentication*
What happens on a failed MAC attempt? Connection closure is the
easiest way to handle the situation.

> After a successful <code>encinit</code>/<code>encack</code>
> interaction from both sides, the messages format must use the
> "encrypted messages structure". Non-encrypted messages from the
> requesting peer must lead to a connection termination (can be
> detected by the 4 byte network magic in the unencrypted message
> structure).

The magic bytes are at the same offset and size as the encrypted length
field in the encrypted messages structure. So the magic bytes are not a
reliable way to identify unencrypted messages, although the probability
of collision is low.

> {|class="wikitable"
> ! Field Size !! Description !! Data type !! Comments
> |-
> | 4 || length || uint32_t || Length of ciphertext payload in number
> of bytes
> |-
> | ? || ciphertext payload || ? || One or many ciphertext command &
> message data
> |-
> | 8 || MAC tag || ? || MAC-tag truncated to 8 bytes
> |}

Why have a fixed MAC length? I think the MAC length should be inferred
from the cipher + authentication mode. And the Poly1305 tag is 16 bytes.

*Unauthenticated Buffering*
Implementations are unlikely to (i.e. should not) process the payload
until authentication succeeds. Since the length field is 4 bytes, this
means an implementation may have to buffer up to 4 GiB of data _per
connection_ before it can authenticate the length field. If the outter
length field were reduced to 2 or 3 bytes, the unauthenticated
buffering requirements drop to 64 KiB and 16 MiB respectively. Inner
messages already have their own length, so they can span multiple
encrypted blocks without other changes. This will increase the
bandwidth requirements when the size of a single message exceeds 64 KiB
or 16 MiB, since it will require multiple authentication tags for that
message. I think an additional 16 bytes per 16 MiB seems like a good
tradeoff.

> A responding peer can inform the requesting peer over a re-keying
> with a <code>encack</code> message containing 33byte of zeros to
> indicate that all encrypted message following after this
> <code>encack</code> message will be encrypted with ''the next
> symmetric cipher key''.
>
> The new symmetric cipher key will be calculated by
> <code>SHA256(SHA256(old_symetric_cipher_key))</code>.
>
> Re-Keying interval is a peer policy with a minimum timespan of 600
> seconds.

Should the int64_t message count be reset to 0 on a re-key? Or should
the value reset to zero after 2^63-1? Hopefully the peer re-keys before
that rollover, or keystream reusage will occur. Unlikely that many
messages are sent on a single connection though. And presumably this
only re-keys the senders side? Bi-directional re-keying would be racy.

Lee

[0]https://tools.ietf.org/html/draft-ietf-tls-chacha20-poly1305-04