From 56853bc1cab88a893a5f58add417d867b646c363 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Mon, 6 May 2019 15:55:54 +0200 Subject: [PATCH 1/4] Transport: add support for encryption key logging. This is the format the wireshark lightning-dissector uses to be able to decrypt lightning messages. --- .../acinq/eclair/crypto/ChaCha20Poly1305.scala | 18 ++++++++++++++++-- eclair-node/src/main/resources/logback.xml | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala index b71fe8d1ee..db74feaf64 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.crypto import java.nio.ByteOrder import fr.acinq.bitcoin.{ByteVector32, Protocol} +import grizzled.slf4j.Logger import grizzled.slf4j.Logging import org.spongycastle.crypto.engines.ChaCha7539Engine import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV} @@ -31,8 +32,8 @@ import scodec.bits.ByteVector object Poly1305 { /** * - * @param key input key - * @param data input data + * @param key input key + * @param datas input data * @return a 16 byte authentication tag */ def mac(key: ByteVector, datas: ByteVector*): ByteVector = { @@ -50,6 +51,7 @@ object Poly1305 { * see https://tools.ietf.org/html/rfc7539#section-2.5 */ object ChaCha20 { + val NonceLength = 12 def encrypt(plaintext: ByteVector, key: ByteVector, nonce: ByteVector, counter: Int = 0): ByteVector = { val engine = new ChaCha7539Engine() @@ -94,6 +96,8 @@ object ChaCha20 { */ object ChaCha20Poly1305 extends Logging { + val keyLogger = Logger("keylog") + /** * * @param key 32 bytes encryption key @@ -106,7 +110,12 @@ object ChaCha20Poly1305 extends Logging { val polykey = ChaCha20.encrypt(ByteVector32.Zeroes, key, nonce) val ciphertext = ChaCha20.encrypt(plaintext, key, nonce, 1) val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN)) + logger.debug(s"encrypt($key, $nonce, $aad, $plaintext) = ($ciphertext, $tag)") + if (nonce === ByteVector.fill(ChaCha20.NonceLength)(0.byteValue)) { + keyLogger.debug(s"${tag.toHex} ${key.toHex}") + } + (ciphertext, tag) } @@ -124,7 +133,12 @@ object ChaCha20Poly1305 extends Logging { val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN)) require(tag == mac, "invalid mac") val plaintext = ChaCha20.decrypt(ciphertext, key, nonce, 1) + logger.debug(s"decrypt($key, $nonce, $aad, $ciphertext, $mac) = $plaintext") + if (nonce === ByteVector.fill(ChaCha20.NonceLength)(0.byteValue)) { + keyLogger.debug(s"${mac.toHex} ${key.toHex}") + } + plaintext } diff --git a/eclair-node/src/main/resources/logback.xml b/eclair-node/src/main/resources/logback.xml index 59f3871af9..ae7ccbb8a7 100644 --- a/eclair-node/src/main/resources/logback.xml +++ b/eclair-node/src/main/resources/logback.xml @@ -39,6 +39,13 @@ + + ${eclair.datadir:-${user.home}/.eclair}/keys.log + + %msg%n + + + @@ -52,6 +59,16 @@ + + + + + From bddd8a26f747b1f5830f79dfe1aa2a0f66e21693 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Mon, 6 May 2019 15:56:32 +0200 Subject: [PATCH 2/4] Noise: a few nit static analyzer fixes --- .../main/scala/fr/acinq/eclair/crypto/Noise.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala index fb85cf0216..10470c062b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala @@ -82,7 +82,7 @@ object Noise { trait CipherFunctions { def name: String - //Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique + // Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique // for the key k. Returns the ciphertext. Encryption must be done with an "AEAD" encryption mode with the associated // data ad (using the terminology from [1]) and returns a ciphertext that is the same size as the plaintext // plus 16 bytes for authentication data. The entire ciphertext must be indistinguishable from random if the key is secret. @@ -99,7 +99,7 @@ object Noise { // as specified in BOLT #8 def nonce(n: Long): ByteVector = ByteVector.fill(4)(0) ++ Protocol.writeUInt64(n, ByteOrder.LITTLE_ENDIAN) - //Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique + // Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte unsigned integer nonce n which must be unique. override def encrypt(k: ByteVector, n: Long, ad: ByteVector, plaintext: ByteVector): ByteVector = { val (ciphertext, mac) = ChaCha20Poly1305.encrypt(k, nonce(n), plaintext, ad) ciphertext ++ mac @@ -329,7 +329,7 @@ object Noise { * contain 2 cipherstates than can be used to encrypt/decrypt further communication */ def write(payload: ByteVector): (HandshakeStateReader, ByteVector, Option[(CipherState, CipherState, ByteVector)]) = { - require(!messages.isEmpty) + require(messages.nonEmpty) logger.debug(s"write($payload)") val (writer1, buffer1) = messages.head.foldLeft(this -> ByteVector.empty) { @@ -377,7 +377,7 @@ object Noise { * * @param message input message * @return a (writer, payload, Option[(cipherstate, cipherstate)] tuple. - * The payload contains the original payload used be the sender and a writer that will be used to create the + * The payload contains the original payload used by the sender and a writer that will be used to create the * next message. When the handshake is over (i.e. there are no more handshake patterns to process) the last item will * contain 2 cipherstates than can be used to encrypt/decrypt further communication */ @@ -433,12 +433,12 @@ object Noise { def initializeWriter(handshakePattern: HandshakePattern, prologue: ByteVector, s: KeyPair, e: KeyPair, rs: ByteVector, re: ByteVector, dh: DHFunctions, cipher: CipherFunctions, hash: HashFunctions, byteStream: ByteStream = RandomBytes): HandshakeStateWriter = { val symmetricState = makeSymmetricState(handshakePattern, prologue, dh, cipher, hash) - val symmetricState1 = (handshakePattern.initiatorPreMessages).foldLeft(symmetricState) { + val symmetricState1 = handshakePattern.initiatorPreMessages.foldLeft(symmetricState) { case (state, E) => state.mixHash(e.pub) case (state, S) => state.mixHash(s.pub) case _ => throw new RuntimeException("invalid pre-message") } - val symmetricState2 = (handshakePattern.responderPreMessages).foldLeft(symmetricState1) { + val symmetricState2 = handshakePattern.responderPreMessages.foldLeft(symmetricState1) { case (state, E) => state.mixHash(re) case (state, S) => state.mixHash(rs) case _ => throw new RuntimeException("invalid pre-message") From 26336044dc91824a7dbc5df49c1bb11c02022c5c Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Mon, 6 May 2019 17:58:09 +0200 Subject: [PATCH 3/4] ChaCha20: add comment on the keylogger val --- .../main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala index db74feaf64..553de252d2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala @@ -96,6 +96,9 @@ object ChaCha20 { */ object ChaCha20Poly1305 extends Logging { + // This logger is used to dump encryption keys to enable traffic analysis by the lightning-dissector. + // See https://github.com/nayutaco/lightning-dissector for more details. + // It is disabled by default (in the logback.xml configuration file). val keyLogger = Logger("keylog") /** From 9af2c452c580cf2cbe65f2aeacfb6a00c9db2b49 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Tue, 7 May 2019 09:49:57 +0200 Subject: [PATCH 4/4] ChaCha20: use a static zero nonce value --- .../scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala index 553de252d2..da93d4cd7a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala @@ -51,7 +51,8 @@ object Poly1305 { * see https://tools.ietf.org/html/rfc7539#section-2.5 */ object ChaCha20 { - val NonceLength = 12 + // Whenever key rotation happens, we start with a nonce value of 0 and increment it for each message. + val ZeroNonce = ByteVector.fill(12)(0.byteValue) def encrypt(plaintext: ByteVector, key: ByteVector, nonce: ByteVector, counter: Int = 0): ByteVector = { val engine = new ChaCha7539Engine() @@ -115,7 +116,7 @@ object ChaCha20Poly1305 extends Logging { val tag = Poly1305.mac(polykey, aad, pad16(aad), ciphertext, pad16(ciphertext), Protocol.writeUInt64(aad.length, ByteOrder.LITTLE_ENDIAN), Protocol.writeUInt64(ciphertext.length, ByteOrder.LITTLE_ENDIAN)) logger.debug(s"encrypt($key, $nonce, $aad, $plaintext) = ($ciphertext, $tag)") - if (nonce === ByteVector.fill(ChaCha20.NonceLength)(0.byteValue)) { + if (nonce === ChaCha20.ZeroNonce) { keyLogger.debug(s"${tag.toHex} ${key.toHex}") } @@ -138,7 +139,7 @@ object ChaCha20Poly1305 extends Logging { val plaintext = ChaCha20.decrypt(ciphertext, key, nonce, 1) logger.debug(s"decrypt($key, $nonce, $aad, $ciphertext, $mac) = $plaintext") - if (nonce === ByteVector.fill(ChaCha20.NonceLength)(0.byteValue)) { + if (nonce === ChaCha20.ZeroNonce) { keyLogger.debug(s"${mac.toHex} ${key.toHex}") }