From e444594791f657df2c035f259f2098d6db6029aa Mon Sep 17 00:00:00 2001 From: s1fr0 Date: Wed, 6 Apr 2022 13:19:57 +0000 Subject: [PATCH] deploy: d2fccb5220144893f994a67f2cc26661247f101f --- tests/v2/test_waku_noise.nim | 101 +++++++++- .../vendor/libbacktrace-upstream/libtool | 2 +- waku/v2/protocol/waku_noise/noise.nim | 183 ++++++++++++++++-- 3 files changed, 267 insertions(+), 19 deletions(-) diff --git a/tests/v2/test_waku_noise.nim b/tests/v2/test_waku_noise.nim index d0b53028c..482b37fc9 100644 --- a/tests/v2/test_waku_noise.nim +++ b/tests/v2/test_waku_noise.nim @@ -2,22 +2,115 @@ import testutils/unittests, + std/random, + stew/byteutils, ../../waku/v2/protocol/waku_noise/noise, ../test_helpers - procSuite "Waku Noise": + # We initialize the RNG in test_helpers let rng = rng() + # We initialize the RNG in std/random + randomize() - test "ChaChaPoly Encryption/Decryption": + test "ChaChaPoly Encryption/Decryption: random byte sequences": let cipherState = randomChaChaPolyCipherState(rng[]) + # We encrypt/decrypt random byte sequences let - plaintext: seq[byte] = randomSeqByte(rng[], 128) + plaintext: seq[byte] = randomSeqByte(rng[], rand(1..128)) ciphertext: ChaChaPolyCiphertext = encrypt(cipherState, plaintext) decryptedCiphertext: seq[byte] = decrypt(cipherState, ciphertext) check: - plaintext == decryptedCiphertext \ No newline at end of file + plaintext == decryptedCiphertext + + test "ChaChaPoly Encryption/Decryption: random strings": + + let cipherState = randomChaChaPolyCipherState(rng[]) + + # We encrypt/decrypt random strings + var plaintext: string + for _ in 1..rand(1..128): + add(plaintext, char(rand(int('A') .. int('z')))) + + let + ciphertext: ChaChaPolyCiphertext = encrypt(cipherState, plaintext.toBytes()) + decryptedCiphertext: seq[byte] = decrypt(cipherState, ciphertext) + + check: + plaintext.toBytes() == decryptedCiphertext + + test "Encrypt and decrypt Noise public keys": + + let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + + let + cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) + encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) + decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, encryptedPk) + + check: + noisePublicKey == decryptedPk + + test "Decrypt unencrypted public key": + + let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + + let + cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) + decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, noisePublicKey) + + check: + noisePublicKey == decryptedPk + + test "Encrypt encrypted public key": + + let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + + let + cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) + encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) + encryptedPk2: NoisePublicKey = encryptNoisePublicKey(cs, encryptedPk) + + check: + encryptedPk == encryptedPk2 + + test "Encrypt, decrypt and decrypt public key": + + let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + + let + cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) + encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) + decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, encryptedPk) + decryptedPk2: NoisePublicKey = decryptNoisePublicKey(cs, decryptedPk) + + check: + decryptedPk == decryptedPk2 + + test "Serialize and deserialize unencrypted public key": + + let + noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + serializedNoisePublicKey: seq[byte] = serializeNoisePublicKey(noisePublicKey) + deserializedNoisePublicKey: NoisePublicKey = intoNoisePublicKey(serializedNoisePublicKey) + + check: + noisePublicKey == deserializedNoisePublicKey + + test "Encrypt, serialize, deserialize and decrypt public key": + + let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) + + let + cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) + encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) + serializedNoisePublicKey: seq[byte] = serializeNoisePublicKey(encryptedPk) + deserializedNoisePublicKey: NoisePublicKey = intoNoisePublicKey(serializedNoisePublicKey) + decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, deserializedNoisePublicKey) + + check: + noisePublicKey == decryptedPk \ No newline at end of file diff --git a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool index 5f7c48786..fe48de1cf 100755 --- a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool +++ b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (libbacktrace) version-unused -# Libtool was configured on host fv-az453-68: +# Libtool was configured on host fv-az129-796: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, diff --git a/waku/v2/protocol/waku_noise/noise.nim b/waku/v2/protocol/waku_noise/noise.nim index cdf72330d..16651670d 100644 --- a/waku/v2/protocol/waku_noise/noise.nim +++ b/waku/v2/protocol/waku_noise/noise.nim @@ -5,7 +5,6 @@ ## Implementation partially inspired by noise-libp2p: ## https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/noise.nim - {.push raises: [Defect].} import std/[oids, options] @@ -15,76 +14,138 @@ import bearssl import nimcrypto/[utils, sha2, hmac] import libp2p/stream/[connection] +import libp2p/peerid +import libp2p/peerinfo import libp2p/protobuf/minprotobuf import libp2p/utility import libp2p/errors -import libp2p/crypto/[crypto, chacha20poly1305] +import libp2p/crypto/[crypto, chacha20poly1305, curve25519] + logScope: topics = "wakunoise" +################################################################# + +# Constants and data structures + const - # EmptyKey is a special value which indicates a ChaChaPolyKey has not yet been initialized. + # EmptyKey represents a non-initialized ChaChaPolyKey EmptyKey = default(ChaChaPolyKey) + # The maximum ChaChaPoly allowed nonce in Noise Handshakes + NonceMax = uint64.high - 1 type + # Default underlying elliptic curve arithmetic (useful for switching to multiple ECs) + # Current default is Curve25519 + EllipticCurveKey = Curve25519Key + + # An EllipticCurveKey (public, private) key pair + KeyPair* = object + privateKey: EllipticCurveKey + publicKey: EllipticCurveKey + + # A Noise public key is a public key exchanged during Noise handshakes (no private part) + # This follows https://rfc.vac.dev/spec/35/#public-keys-serialization + # pk contains the X coordinate of the public key, if unencrypted (this implies flag = 0) + # or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1) + NoisePublicKey* = object + flag: uint8 + pk: seq[byte] + + # A ChaChaPoly ciphertext (data) + authorization tag (tag) ChaChaPolyCiphertext* = object - data: seq[byte] - tag: ChaChaPolyTag + data*: seq[byte] + tag*: ChaChaPolyTag + # A ChaChaPoly Cipher State containing key (k), nonce (nonce) and associated data (ad) ChaChaPolyCipherState* = object - k*: ChaChaPolyKey - nonce*: ChaChaPolyNonce - ad*: seq[byte] + k: ChaChaPolyKey + nonce: ChaChaPolyNonce + ad: seq[byte] + # Some useful error types NoiseError* = object of LPError + NoiseHandshakeError* = object of NoiseError + NoiseEmptyChaChaPolyInput* = object of NoiseError NoiseDecryptTagError* = object of NoiseError + NoiseNonceMaxError* = object of NoiseError + NoisePublicKeyError* = object of NoiseError + NoiseMalformedHandshake* = object of NoiseError + ################################################################# # Utilities # Generates random byte sequences of given size -proc randomSeqByte*(rng: var BrHmacDrbgContext, size: uint32): seq[byte] = - var output = newSeq[byte](size) +proc randomSeqByte*(rng: var BrHmacDrbgContext, size: int): seq[byte] = + var output = newSeq[byte](size.uint32) brHmacDrbgGenerate(rng, output) return output +# Generate random Curve25519 (public, private) key pairs +proc genKeyPair*(rng: var BrHmacDrbgContext): KeyPair = + var keyPair: KeyPair + keyPair.privateKey = EllipticCurveKey.random(rng) + keyPair.publicKey = keyPair.privateKey.public() + return keyPair + + ################################################################# # ChaChaPoly Symmetric Cipher # ChaChaPoly encryption # It takes a Cipher State (with key, nonce, and associated data) and encrypts a plaintext +# The cipher state in not changed proc encrypt*( state: ChaChaPolyCipherState, plaintext: openArray[byte]): ChaChaPolyCiphertext - {.noinit, raises: [Defect].} = - #TODO: add padding + {.noinit, raises: [Defect, NoiseEmptyChaChaPolyInput].} = + # If plaintext is empty, we raise an error + if plaintext == @[]: + raise newException(NoiseEmptyChaChaPolyInput, "Tried to encrypt empty plaintext") var ciphertext: ChaChaPolyCiphertext + # Since ChaChaPoly's library "encrypt" primitive directly changes the input plaintext to the ciphertext, + # we copy the plaintext into the ciphertext variable and we pass the latter to encrypt ciphertext.data.add plaintext + #TODO: add padding + # ChaChaPoly.encrypt takes as input: the key (k), the nonce (nonce), a data structure for storing the computed authorization tag (tag), + # the plaintext (overwritten to ciphertext) (data), the associated data (ad) ChaChaPoly.encrypt(state.k, state.nonce, ciphertext.tag, ciphertext.data, state.ad) return ciphertext # ChaChaPoly decryption # It takes a Cipher State (with key, nonce, and associated data) and decrypts a ciphertext +# The cipher state is not changed proc decrypt*( state: ChaChaPolyCipherState, ciphertext: ChaChaPolyCiphertext): seq[byte] - {.raises: [Defect, NoiseDecryptTagError].} = + {.raises: [Defect, NoiseEmptyChaChaPolyInput, NoiseDecryptTagError].} = + # If ciphertext is empty, we raise an error + if ciphertext.data == @[]: + raise newException(NoiseEmptyChaChaPolyInput, "Tried to decrypt empty ciphertext") var + # The input authorization tag tagIn = ciphertext.tag + # The authorization tag computed during decryption tagOut: ChaChaPolyTag + # Since ChaChaPoly's library "decrypt" primitive directly changes the input ciphertext to the plaintext, + # we copy the ciphertext into the plaintext variable and we pass the latter to decrypt var plaintext = ciphertext.data + # ChaChaPoly.decrypt takes as input: the key (k), the nonce (nonce), a data structure for storing the computed authorization tag (tag), + # the ciphertext (overwritten to plaintext) (data), the associated data (ad) ChaChaPoly.decrypt(state.k, state.nonce, tagOut, plaintext, state.ad) #TODO: add unpadding trace "decrypt", tagIn = tagIn.shortLog, tagOut = tagOut.shortLog, nonce = state.nonce + # We check if the authorization tag computed while decrypting is the same as the input tag if tagIn != tagOut: debug "decrypt failed", plaintext = shortLog(plaintext) raise newException(NoiseDecryptTagError, "decrypt tag authentication failed.") return plaintext -# Generates a random Cipher Test for testing encryption/decryption +# Generates a random ChaChaPoly Cipher State for testing encryption/decryption proc randomChaChaPolyCipherState*(rng: var BrHmacDrbgContext): ChaChaPolyCipherState = var randomCipherState: ChaChaPolyCipherState brHmacDrbgGenerate(rng, randomCipherState.k) @@ -92,3 +153,97 @@ proc randomChaChaPolyCipherState*(rng: var BrHmacDrbgContext): ChaChaPolyCipherS randomCipherState.ad = newSeq[byte](32) brHmacDrbgGenerate(rng, randomCipherState.ad) return randomCipherState + + +################################################################# + +# Noise Public keys + +# Checks equality between two Noise public keys +proc `==`(k1, k2: NoisePublicKey): bool = + return (k1.flag == k2.flag) and (k1.pk == k2.pk) + +# Converts a (public, private) Elliptic Curve keypair to an unencrypted Noise public key (only public part) +proc keyPairToNoisePublicKey*(keyPair: KeyPair): NoisePublicKey = + var noisePublicKey: NoisePublicKey + noisePublicKey.flag = 0 + noisePublicKey.pk = getBytes(keyPair.publicKey) + return noisePublicKey + +# Generates a random Noise public key +proc genNoisePublicKey*(rng: var BrHmacDrbgContext): NoisePublicKey = + var noisePublicKey: NoisePublicKey + # We generate a random key pair + let keyPair: KeyPair = genKeyPair(rng) + # Since it is unencrypted, flag is 0 + noisePublicKey.flag = 0 + # We copy the public X coordinate of the key pair to the output Noise public key + noisePublicKey.pk = getBytes(keyPair.publicKey) + return noisePublicKey + +# Converts a Noise public key to a stream of bytes as in +# https://rfc.vac.dev/spec/35/#public-keys-serialization +proc serializeNoisePublicKey*(noisePublicKey: NoisePublicKey): seq[byte] = + var serializedNoisePublicKey: seq[byte] + # Public key is serialized as (flag || pk) + # Note that pk contains the X coordinate of the public key if unencrypted + # or the encryption concatenated with the authorization tag if encrypted + serializedNoisePublicKey.add noisePublicKey.flag + serializedNoisePublicKey.add noisePublicKey.pk + return serializedNoisePublicKey + +# Converts a serialized Noise public key to a NoisePublicKey object as in +# https://rfc.vac.dev/spec/35/#public-keys-serialization +proc intoNoisePublicKey*(serializedNoisePublicKey: seq[byte]): NoisePublicKey + {.raises: [Defect, NoisePublicKeyError].} = + var noisePublicKey: NoisePublicKey + # We retrieve the encryption flag + noisePublicKey.flag = serializedNoisePublicKey[0] + # If not 0 or 1 we raise a new exception + if not (noisePublicKey.flag == 0 or noisePublicKey.flag == 1): + raise newException(NoisePublicKeyError, "Invalid flag in serialized public key") + # We set the remaining sequence to the pk value (this may be an encrypted or not encrypted X coordinate) + noisePublicKey.pk = serializedNoisePublicKey[1..