1
0
mirror of https://github.com/ACINQ/eclair.git synced 2025-01-19 05:33:59 +01:00

Use bouncycastle instead of spongycastle (#1772)

* Use bouncycastle instead of spongycastle
* Reformat a few files
* Remove wireshark dissector support

Fixes #1375
This commit is contained in:
Bastien Teinturier 2021-04-22 11:39:45 +02:00 committed by GitHub
parent e14c40d7c3
commit 62dd3932ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 155 deletions

View File

@ -236,6 +236,11 @@
<artifactId>HikariCP</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<!-- This is to get rid of '[WARNING] warning: Class javax.annotation.Nonnull not found - continuing with a stub.' compile errors -->
<groupId>com.google.code.findbugs</groupId>

View File

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

View File

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

View File

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

View File

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

View File

@ -39,13 +39,6 @@
</encoder>
</appender>
<appender name="KEYLOG" class="ch.qos.logback.core.FileAppender">
<file>${eclair.datadir:-${user.home}/.eclair}/keys.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<if condition='isDefined("eclair.printToConsole")'>
<then>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@ -59,16 +52,6 @@
</then>
</if>
<!--
This logger can be used to dump encryption keys for traffic analysis.
It is used by the wireshark lightning dissector to troubleshoot issues between nodes.
To enable it, set the log level to "DEBUG" for this logger (no need to set it for the root logger).
See https://github.com/nayutaco/lightning-dissector for details.
-->
<logger level="OFF" name="keylog" additivity="false">
<appender-ref ref="KEYLOG"/>
</logger>
<root level="INFO">
<appender-ref ref="ROLLING"/>
</root>