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 @@
-
-
-
-
-