From 62dd3932ff1d60d37280db2b7a91320436ba67a8 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Thu, 22 Apr 2021 11:39:45 +0200 Subject: [PATCH] Use bouncycastle instead of spongycastle (#1772) * Use bouncycastle instead of spongycastle * Reformat a few files * Remove wireshark dissector support Fixes #1375 --- eclair-core/pom.xml | 5 + .../eclair/crypto/ChaCha20Poly1305.scala | 90 ++++++-------- .../scala/fr/acinq/eclair/crypto/Mac.scala | 14 +-- .../scala/fr/acinq/eclair/crypto/Noise.scala | 115 ++++++++---------- .../fr/acinq/eclair/crypto/NoiseSpec.scala | 27 ++-- eclair-node/src/main/resources/logback.xml | 17 --- 6 files changed, 113 insertions(+), 155 deletions(-) diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index 17b00b0d4..5ac249218 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -236,6 +236,11 @@ HikariCP 3.4.2 + + org.bouncycastle + bcprov-jdk15on + 1.68 + com.google.code.findbugs 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 28e496d7f..ecdca6e40 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 @@ -16,30 +16,28 @@ package fr.acinq.eclair.crypto -import java.nio.ByteOrder - import fr.acinq.bitcoin.{ByteVector32, Protocol} import fr.acinq.eclair.crypto.ChaCha20Poly1305.{DecryptionError, EncryptionError, InvalidCounter} -import grizzled.slf4j.Logger import grizzled.slf4j.Logging -import org.spongycastle.crypto.engines.ChaCha7539Engine -import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV} +import org.bouncycastle.crypto.engines.ChaCha7539Engine +import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV} import scodec.bits.ByteVector +import java.nio.ByteOrder + /** - * Poly1305 authenticator - * see https://tools.ietf.org/html/rfc7539#section-2.5 - */ + * Poly1305 authenticator + * see https://tools.ietf.org/html/rfc7539#section-2.5 + */ object Poly1305 { /** - * - * @param key input key - * @param datas input data - * @return a 16 byte authentication tag - */ + * @param key input key + * @param datas input data + * @return a 16 byte authentication tag + */ def mac(key: ByteVector, datas: ByteVector*): ByteVector = { val out = new Array[Byte](16) - val poly = new org.spongycastle.crypto.macs.Poly1305() + val poly = new org.bouncycastle.crypto.macs.Poly1305() poly.init(new KeyParameter(key.toArray)) datas.foreach(data => poly.update(data.toArray, 0, data.length.toInt)) poly.doFinal(out, 0) @@ -48,13 +46,10 @@ object Poly1305 { } /** - * ChaCha20 block cipher - * see https://tools.ietf.org/html/rfc7539#section-2.5 - */ + * ChaCha20 block cipher + * see https://tools.ietf.org/html/rfc7539#section-2.5 + */ object ChaCha20 { - // 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() engine.init(true, new ParametersWithIV(new KeyParameter(key.toArray), nonce.toArray)) @@ -91,65 +86,50 @@ object ChaCha20 { } /** - * ChaCha20Poly1305 AEAD (Authenticated Encryption with Additional Data) algorithm - * see https://tools.ietf.org/html/rfc7539#section-2.5 - * - * This what we should be using (see BOLT #8) - */ + * ChaCha20Poly1305 AEAD (Authenticated Encryption with Additional Data) algorithm + * see https://tools.ietf.org/html/rfc7539#section-2.5 + * + * This what we should be using (see BOLT #8) + */ object ChaCha20Poly1305 extends Logging { + // @formatter:off abstract class ChaCha20Poly1305Error(msg: String) extends RuntimeException(msg) case class InvalidMac() extends ChaCha20Poly1305Error("invalid mac") case class DecryptionError() extends ChaCha20Poly1305Error("decryption error") case class EncryptionError() extends ChaCha20Poly1305Error("encryption error") case class InvalidCounter() extends ChaCha20Poly1305Error("chacha20 counter must be 0 or 1") - - // 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") + // @formatter:on /** - * - * @param key 32 bytes encryption key - * @param nonce 12 bytes nonce - * @param plaintext plain text - * @param aad additional authentication data. can be empty - * @return a (ciphertext, mac) tuple - */ + * @param key 32 bytes encryption key + * @param nonce 12 bytes nonce + * @param plaintext plain text + * @param aad additional authentication data. can be empty + * @return a (ciphertext, mac) tuple + */ def encrypt(key: ByteVector, nonce: ByteVector, plaintext: ByteVector, aad: ByteVector): (ByteVector, ByteVector) = { 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 === ChaCha20.ZeroNonce) { - keyLogger.debug(s"${tag.toHex} ${key.toHex}") - } - (ciphertext, tag) } /** - * - * @param key 32 bytes decryption key - * @param nonce 12 bytes nonce - * @param ciphertext ciphertext - * @param aad additional authentication data. can be empty - * @param mac authentication mac - * @return the decrypted plaintext if the mac is valid. - */ + * @param key 32 bytes decryption key + * @param nonce 12 bytes nonce + * @param ciphertext ciphertext + * @param aad additional authentication data. can be empty + * @param mac authentication mac + * @return the decrypted plaintext if the mac is valid. + */ def decrypt(key: ByteVector, nonce: ByteVector, ciphertext: ByteVector, aad: ByteVector, mac: ByteVector): ByteVector = { val polykey = ChaCha20.encrypt(ByteVector32.Zeroes, key, nonce) 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)) if (tag != mac) throw InvalidMac() val plaintext = ChaCha20.decrypt(ciphertext, key, nonce, 1) - logger.debug(s"decrypt($key, $nonce, $aad, $ciphertext, $mac) = $plaintext") - if (nonce === ChaCha20.ZeroNonce) { - keyLogger.debug(s"${mac.toHex} ${key.toHex}") - } - plaintext } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala index 494cd1020..011a2b9ef 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala @@ -17,18 +17,18 @@ package fr.acinq.eclair.crypto import fr.acinq.bitcoin.ByteVector32 -import org.spongycastle.crypto.digests.SHA256Digest -import org.spongycastle.crypto.macs.HMac -import org.spongycastle.crypto.params.KeyParameter +import org.bouncycastle.crypto.digests.SHA256Digest +import org.bouncycastle.crypto.macs.HMac +import org.bouncycastle.crypto.params.KeyParameter import scodec.bits.ByteVector /** - * Created by t-bast on 04/07/19. - */ + * Created by t-bast on 04/07/19. + */ /** - * Create and verify message authentication codes. - */ + * Create and verify message authentication codes. + */ trait Mac32 { def mac(message: ByteVector): ByteVector32 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 ceed60483..053912712 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 @@ -16,28 +16,28 @@ package fr.acinq.eclair.crypto -import java.math.BigInteger -import java.nio.ByteOrder - import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{Crypto, Protocol} import fr.acinq.eclair.randomBytes import grizzled.slf4j.Logging -import org.spongycastle.crypto.digests.SHA256Digest -import org.spongycastle.crypto.macs.HMac -import org.spongycastle.crypto.params.KeyParameter +import org.bouncycastle.crypto.digests.SHA256Digest +import org.bouncycastle.crypto.macs.HMac +import org.bouncycastle.crypto.params.KeyParameter import scodec.bits.ByteVector +import java.math.BigInteger +import java.nio.ByteOrder + /** - * see http://noiseprotocol.org/ - */ + * see http://noiseprotocol.org/ + */ object Noise { case class KeyPair(pub: ByteVector, priv: ByteVector) /** - * Diffie-Helmann functions - */ + * Diffie-Helmann functions + */ trait DHFunctions { def name: String @@ -59,12 +59,10 @@ object Noise { } /** - * this is what secp256k1's secp256k1_ecdh() returns - * - * @param keyPair - * @param publicKey - * @return sha256(publicKey * keyPair.priv in compressed format) - */ + * this is what secp256k1's secp256k1_ecdh() returns + * + * @return sha256(publicKey * keyPair.priv in compressed format) + */ override def dh(keyPair: KeyPair, publicKey: ByteVector): ByteVector = { val point = Crypto.curve.getCurve.decodePoint(publicKey.toArray) val scalar = new BigInteger(1, keyPair.priv.take(32).toArray) @@ -78,8 +76,8 @@ object Noise { } /** - * Cipher functions - */ + * Cipher functions + */ trait CipherFunctions { def name: String @@ -115,8 +113,8 @@ object Noise { } /** - * Hash functions - */ + * Hash functions + */ trait HashFunctions extends Logging { def name: String @@ -167,8 +165,8 @@ object Noise { } /** - * Cipher state - */ + * Cipher state + */ trait CipherState { def cipher: CipherFunctions @@ -182,10 +180,10 @@ object Noise { } /** - * Uninitialized cipher state. Encrypt and decrypt do nothing (ciphertext = plaintext) - * - * @param cipher cipher functions - */ + * Uninitialized cipher state. Encrypt and decrypt do nothing (ciphertext = plaintext) + * + * @param cipher cipher functions + */ case class UninitializedCipherState(cipher: CipherFunctions) extends CipherState { override val hasKey = false @@ -195,12 +193,12 @@ object Noise { } /** - * Initialized cipher state - * - * @param k key - * @param n nonce - * @param cipher cipher functions - */ + * Initialized cipher state + * + * @param k key + * @param n nonce + * @param cipher cipher functions + */ case class InitializedCipherState(k: ByteVector, n: Long, cipher: CipherFunctions) extends CipherState { require(k.length == 32) @@ -223,12 +221,11 @@ object Noise { } /** - * - * @param cipherState cipher state - * @param ck chaining key - * @param h hash - * @param hashFunctions hash functions - */ + * @param cipherState cipher state + * @param ck chaining key + * @param h hash + * @param hashFunctions hash functions + */ case class SymmetricState(cipherState: CipherState, ck: ByteVector, h: ByteVector, hashFunctions: HashFunctions) extends Logging { def mixKey(inputKeyMaterial: ByteVector): SymmetricState = { logger.debug(s"ss = 0x$inputKeyMaterial") @@ -270,19 +267,15 @@ object Noise { } } + // @formatter:off sealed trait MessagePattern - case object S extends MessagePattern - case object E extends MessagePattern - case object EE extends MessagePattern - case object ES extends MessagePattern - case object SE extends MessagePattern - case object SS extends MessagePattern + // @formatter:off type MessagePatterns = List[MessagePattern] @@ -301,8 +294,8 @@ object Noise { } /** - * standard handshake patterns - */ + * standard handshake patterns + */ val handshakePatternNN = HandshakePattern("NN", initiatorPreMessages = Nil, responderPreMessages = Nil, messages = List(E :: Nil, E :: EE :: Nil)) val handshakePatternXK = HandshakePattern("XK", initiatorPreMessages = Nil, responderPreMessages = S :: Nil, messages = List(E :: ES :: Nil, E :: EE :: Nil, S :: SE :: Nil)) @@ -313,7 +306,7 @@ object Noise { object RandomBytes extends ByteStream { - override def nextBytes(length: Int) = randomBytes(length) + override def nextBytes(length: Int): ByteVector = randomBytes(length) } sealed trait HandshakeState @@ -322,13 +315,12 @@ object Noise { def toReader: HandshakeStateReader = HandshakeStateReader(messages, state, s, e, rs, re, dh, byteStream) /** - * - * @param payload input message (can be empty) - * @return a (reader, output, Option[(cipherstate, cipherstate)] tuple. - * The output will be sent to the other side, and we will read its answer using the returned reader instance - * 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 - */ + * @param payload input message (can be empty) + * @return a (reader, output, Option[(cipherstate, cipherstate)] tuple. + * The output will be sent to the other side, and we will read its answer using the returned reader instance + * 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 + */ def write(payload: ByteVector): (HandshakeStateReader, ByteVector, Option[(CipherState, CipherState, ByteVector)]) = { require(messages.nonEmpty) logger.debug(s"write($payload)") @@ -374,14 +366,13 @@ object Noise { case class HandshakeStateReader(messages: List[MessagePatterns], state: SymmetricState, s: KeyPair, e: KeyPair, rs: ByteVector, re: ByteVector, dh: DHFunctions, byteStream: ByteStream) extends HandshakeState with Logging { def toWriter: HandshakeStateWriter = HandshakeStateWriter(messages, state, s, e, rs, re, dh, byteStream) - /** * - * - * @param message input message - * @return a (writer, payload, Option[(cipherstate, cipherstate)] tuple. - * 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 - */ + /** + * @param message input message + * @return a (writer, payload, Option[(cipherstate, cipherstate)] tuple. + * 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 + */ def read(message: ByteVector): (HandshakeStateWriter, ByteVector, Option[(CipherState, CipherState, ByteVector)]) = { logger.debug(s"input: 0x$message") val (reader1, buffer1) = messages.head.foldLeft(this -> message) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala index 359ba623b..b102566af 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala @@ -17,11 +17,10 @@ package fr.acinq.eclair.crypto import fr.acinq.eclair.crypto.Noise._ +import org.bouncycastle.crypto.ec.CustomNamedCurves import org.scalatest.funsuite.AnyFunSuite -import org.spongycastle.crypto.ec.CustomNamedCurves import scodec.bits._ - class NoiseSpec extends AnyFunSuite { import Noise._ @@ -298,23 +297,23 @@ object NoiseSpec { } /** - * ByteStream implementation that always returns the same data. - */ + * ByteStream implementation that always returns the same data. + */ case class FixedStream(data: ByteVector) extends ByteStream { override def nextBytes(length: Int): ByteVector = data } /** - * Performs a Noise handshake. Initiator and responder must use the same handshake pattern. - * - * @param init initiator - * @param resp responder - * @param inputs inputs messages (can all be empty, but the number of input messages must be equal to the number of - * remaining handshake patterns) - * @param outputs accumulator, for internal use only - * @return the list of output messages produced during the handshake, and the pair of cipherstates produced during the - * final stage of the handshake - */ + * Performs a Noise handshake. Initiator and responder must use the same handshake pattern. + * + * @param init initiator + * @param resp responder + * @param inputs inputs messages (can all be empty, but the number of input messages must be equal to the number of + * remaining handshake patterns) + * @param outputs accumulator, for internal use only + * @return the list of output messages produced during the handshake, and the pair of cipherstates produced during the + * final stage of the handshake + */ def handshake(init: HandshakeStateWriter, resp: HandshakeStateReader, inputs: List[ByteVector], outputs: List[ByteVector] = Nil): (List[ByteVector], (CipherState, CipherState)) = { assert(init.messages == resp.messages) assert(init.messages.length == inputs.length) diff --git a/eclair-node/src/main/resources/logback.xml b/eclair-node/src/main/resources/logback.xml index 434633725..f3f71bdc5 100644 --- a/eclair-node/src/main/resources/logback.xml +++ b/eclair-node/src/main/resources/logback.xml @@ -39,13 +39,6 @@ - - ${eclair.datadir:-${user.home}/.eclair}/keys.log - - %msg%n - - - @@ -59,16 +52,6 @@ - - - - -