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:
parent
e14c40d7c3
commit
62dd3932ff
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user