RLPx encryption/decryption module with tests.
This commit is contained in:
parent
47657ac096
commit
c07b9f4457
|
@ -9,7 +9,7 @@ skipDirs = @["tests", "Nim"]
|
|||
|
||||
requires "nim > 0.18.0",
|
||||
"rlp >= 1.0.1",
|
||||
"nimcrypto >= 0.1.0",
|
||||
"nimcrypto >= 0.3.0",
|
||||
"secp256k1 >= 0.1.0",
|
||||
"eth_keys",
|
||||
"ranges",
|
||||
|
@ -21,5 +21,5 @@ proc runTest(name: string, lang = "c") = exec "nim " & lang & " -r tests/" & nam
|
|||
task test, "Runs the test suite":
|
||||
runTest "testecies"
|
||||
runTest "testauth"
|
||||
|
||||
runTest "testcrypt"
|
||||
runTest("tdiscovery", "cpp")
|
||||
|
|
|
@ -75,15 +75,15 @@ type
|
|||
ConnectionSecret* = object
|
||||
aesKey*: array[aes256.sizeKey, byte]
|
||||
macKey*: array[KeyLength, byte]
|
||||
egressMac*: array[keccak256.sizeDigest, byte]
|
||||
ingressMac*: array[keccak256.sizeDigest, byte]
|
||||
egressMac*: keccak256
|
||||
ingressMac*: keccak256
|
||||
|
||||
AuthException* = object of Exception
|
||||
|
||||
template toa(a, b, c: untyped): untyped =
|
||||
toOpenArray((a), (b), (b) + (c) - 1)
|
||||
|
||||
proc sxor[T](a: var openarray[T], b: openarray[T]) =
|
||||
proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} =
|
||||
assert(len(a) == len(b))
|
||||
for i in 0 ..< len(a):
|
||||
a[i] = a[i] xor b[i]
|
||||
|
@ -476,7 +476,6 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
|
|||
ctx0: keccak256
|
||||
ctx1: keccak256
|
||||
mac1: MDigest[256]
|
||||
mac2: MDigest[256]
|
||||
xornonce: Nonce
|
||||
|
||||
# ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
|
||||
|
@ -516,26 +515,22 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
|
|||
ctx0.init()
|
||||
ctx0.update(xornonce)
|
||||
ctx0.update(authmsg)
|
||||
mac1 = ctx0.finish()
|
||||
|
||||
# ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack)
|
||||
xornonce = secret.macKey
|
||||
xornonce.sxor(h.initiatorNonce)
|
||||
ctx0.init()
|
||||
ctx0.update(xornonce)
|
||||
ctx0.update(ackmsg)
|
||||
mac2 = ctx0.finish()
|
||||
|
||||
ctx0.init() # clean keccak256 context
|
||||
zeroMem(addr xornonce[0], sizeof(Nonce)) # clean xornonce
|
||||
ctx1.init()
|
||||
ctx1.update(xornonce)
|
||||
ctx1.update(ackmsg)
|
||||
burnMem(xornonce)
|
||||
|
||||
if Initiator in h.flags:
|
||||
secret.egressMac = mac1.data
|
||||
secret.ingressMac = mac2.data
|
||||
secret.egressMac = ctx0
|
||||
secret.ingressMac = ctx1
|
||||
else:
|
||||
secret.ingressMac = mac1.data
|
||||
secret.egressMac = mac2.data
|
||||
secret.ingressMac = ctx0
|
||||
secret.egressMac = ctx1
|
||||
|
||||
burnMem(mac1)
|
||||
burnMem(mac2)
|
||||
ctx0.clear()
|
||||
ctx1.clear()
|
||||
result = Success
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
#
|
||||
# Ethereum P2P
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# See the file "LICENSE", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module implements RLPx cryptography
|
||||
|
||||
import nimcrypto/rijndael, nimcrypto/bcmode, nimcrypto/keccak,
|
||||
nimcrypto/utils
|
||||
from auth import ConnectionSecret
|
||||
|
||||
const
|
||||
RlpHeaderLength* = 16
|
||||
RlpMacLength* = 16
|
||||
|
||||
type
|
||||
SecretState* = object
|
||||
## Object represents current encryption/decryption context.
|
||||
aesenc*: CTR[aes256]
|
||||
aesdec*: CTR[aes256]
|
||||
macenc*: ECB[aes256]
|
||||
emac*: keccak256
|
||||
imac*: keccak256
|
||||
|
||||
RlpxStatus* = enum
|
||||
Success, ## Operation was successful
|
||||
IncorrectMac, ## MAC verification failed
|
||||
BufferOverrun, ## Buffer overrun error
|
||||
IncompleteError, ## Data incomplete error
|
||||
IncorrectArgs ## Incorrect arguments
|
||||
|
||||
proc roundup16*(x: int): int {.inline.} =
|
||||
## Procedure aligns `x` to
|
||||
let rem = x and 15
|
||||
if rem != 0:
|
||||
result = x + 16 - rem
|
||||
else:
|
||||
result = x
|
||||
|
||||
template toa(a, b, c: untyped): untyped =
|
||||
toOpenArray((a), (b), (b) + (c) - 1)
|
||||
|
||||
proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} =
|
||||
assert(len(a) == len(b))
|
||||
for i in 0 ..< len(a):
|
||||
a[i] = a[i] xor b[i]
|
||||
|
||||
proc initSecretState*(secrets: ConnectionSecret, context: var SecretState) =
|
||||
## Initialized `context` with values from `secrets`.
|
||||
|
||||
# FIXME: Yes, the encryption is insecure,
|
||||
# see: https://github.com/ethereum/devp2p/issues/32
|
||||
# https://github.com/ethereum/py-evm/blob/master/p2p/peer.py#L159-L160
|
||||
var iv: array[context.aesenc.sizeBlock, byte]
|
||||
context.aesenc.init(secrets.aesKey, iv)
|
||||
context.aesdec = context.aesenc
|
||||
context.macenc.init(secrets.macKey)
|
||||
context.emac = secrets.egressMac
|
||||
context.imac = secrets.ingressMac
|
||||
|
||||
template encryptedLength*(size: int): int =
|
||||
## Returns size of encrypted message for frame with length `size`.
|
||||
RlpHeaderLength + roundup16(size) + 2 * RlpMacLength
|
||||
|
||||
template decryptedLength*(size: int): int =
|
||||
## Returns size of decrypted message for body with length `size`.
|
||||
roundup16(size)
|
||||
|
||||
proc encrypt*(c: var SecretState, header: openarray[byte],
|
||||
frame: openarray[byte],
|
||||
output: var openarray[byte]): RlpxStatus =
|
||||
## Encrypts `header` and `frame` using SecretState `c` context and store
|
||||
## result into `output`.
|
||||
##
|
||||
## `header` must be exactly `RlpHeaderLength` length.
|
||||
## `frame` must not be zero length.
|
||||
## `output` must be at least `encryptedLength(len(frame))` length.
|
||||
var
|
||||
tmpmac: keccak256
|
||||
aes: array[RlpHeaderLength, byte]
|
||||
let length = encryptedLength(len(frame))
|
||||
let frameLength = roundup16(len(frame))
|
||||
let headerMacPos = RlpHeaderLength
|
||||
let framePos = RlpHeaderLength + RlpMacLength
|
||||
let frameMacPos = RlpHeaderLength * 2 + frameLength
|
||||
if len(header) != RlpHeaderLength or len(frame) == 0 or length > len(output):
|
||||
return IncorrectArgs
|
||||
# header_ciphertext = self.aes_enc.update(header)
|
||||
c.aesenc.encrypt(header, toa(output, 0, RlpHeaderLength))
|
||||
# mac_secret = self.egress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.emac
|
||||
var macsec = tmpmac.finish()
|
||||
# self.egress_mac.update(sxor(self.mac_enc(mac_secret), header_ciphertext))
|
||||
c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes)
|
||||
sxor(aes, toa(output, 0, RlpHeaderLength))
|
||||
c.emac.update(aes)
|
||||
burnMem(aes)
|
||||
# header_mac = self.egress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.emac
|
||||
var headerMac = tmpmac.finish()
|
||||
# frame_ciphertext = self.aes_enc.update(frame)
|
||||
copyMem(addr output[framePos], unsafeAddr frame[0], len(frame))
|
||||
c.aesenc.encrypt(toa(output, 32, frameLength), toa(output, 32, frameLength))
|
||||
# self.egress_mac.update(frame_ciphertext)
|
||||
c.emac.update(toa(output, 32, frameLength))
|
||||
# fmac_seed = self.egress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.emac
|
||||
var seed = tmpmac.finish()
|
||||
# mac_secret = self.egress_mac.digest()[:HEADER_LEN]
|
||||
macsec = seed
|
||||
# self.egress_mac.update(sxor(self.mac_enc(mac_secret), fmac_seed))
|
||||
c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes)
|
||||
sxor(aes, toa(seed.data, 0, RlpHeaderLength))
|
||||
c.emac.update(aes)
|
||||
burnMem(aes)
|
||||
# frame_mac = self.egress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.emac
|
||||
var frameMac = tmpmac.finish()
|
||||
tmpmac.clear()
|
||||
# return header_ciphertext + header_mac + frame_ciphertext + frame_mac
|
||||
copyMem(addr output[headerMacPos], addr headerMac.data[0], RlpHeaderLength)
|
||||
copyMem(addr output[frameMacPos], addr frameMac.data[0], RlpHeaderLength)
|
||||
result = Success
|
||||
|
||||
proc decryptHeader*(c: var SecretState, data: openarray[byte],
|
||||
output: var openarray[byte]): RlpxStatus =
|
||||
## Decrypts header `data` using SecretState `c` context and store
|
||||
## result into `output`.
|
||||
##
|
||||
## `header` must be exactly `RlpHeaderLength + RlpMacLength` length.
|
||||
## `output` must be at least `RlpHeaderLength` length.
|
||||
var
|
||||
tmpmac: keccak256
|
||||
aes: array[RlpHeaderLength, byte]
|
||||
|
||||
if len(data) != RlpHeaderLength + RlpMacLength:
|
||||
return IncompleteError
|
||||
if len(output) < RlpHeaderLength:
|
||||
return IncorrectArgs
|
||||
# mac_secret = self.ingress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.imac
|
||||
var macsec = tmpmac.finish()
|
||||
# aes = self.mac_enc(mac_secret)[:HEADER_LEN]
|
||||
c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes)
|
||||
# self.ingress_mac.update(sxor(aes, header_ciphertext))
|
||||
sxor(aes, toa(data, 0, RlpHeaderLength))
|
||||
c.imac.update(aes)
|
||||
burnMem(aes)
|
||||
# expected_header_mac = self.ingress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.imac
|
||||
var expectMac = tmpmac.finish()
|
||||
# if not bytes_eq(expected_header_mac, header_mac):
|
||||
let headerMacPos = RlpHeaderLength
|
||||
if not equalMem(cast[pointer](unsafeAddr data[headerMacPos]),
|
||||
cast[pointer](addr expectMac.data[0]), RlpMacLength):
|
||||
result = IncorrectMac
|
||||
else:
|
||||
# return self.aes_dec.update(header_ciphertext)
|
||||
c.aesdec.decrypt(toa(data, 0, RlpHeaderLength), output)
|
||||
result = Success
|
||||
|
||||
proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
|
||||
output: var openarray[byte], outlen: var int): RlpxStatus =
|
||||
## Decrypts body `data` using SecretState `c` context and store
|
||||
## result into `output`.
|
||||
##
|
||||
## `data` must be at least `roundup16(bodysize) + RlpMacLength` length.
|
||||
## `output` must be at least `roundup16(bodysize)` length.
|
||||
##
|
||||
## On success completion `outlen` will hold actual size of decrypted body.
|
||||
var
|
||||
tmpmac: keccak256
|
||||
aes: array[RlpHeaderLength, byte]
|
||||
outlen = 0
|
||||
let rsize = roundup16(bodysize)
|
||||
if len(data) < rsize + RlpMacLength:
|
||||
return IncompleteError
|
||||
if len(output) < rsize:
|
||||
return IncorrectArgs
|
||||
# self.ingress_mac.update(frame_ciphertext)
|
||||
c.imac.update(toa(data, 0, rsize))
|
||||
tmpmac = c.imac
|
||||
# fmac_seed = self.ingress_mac.digest()[:MAC_LEN]
|
||||
var seed = tmpmac.finish()
|
||||
# self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed))
|
||||
c.macenc.encrypt(toa(seed.data, 0, RlpHeaderLength), aes)
|
||||
sxor(aes, toa(seed.data, 0, RlpHeaderLength))
|
||||
c.imac.update(aes)
|
||||
# expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN]
|
||||
tmpmac = c.imac
|
||||
var expectMac = tmpmac.finish()
|
||||
let bodyMacPos = rsize
|
||||
if not equalMem(cast[pointer](unsafeAddr data[bodyMacPos]),
|
||||
cast[pointer](addr expectMac.data[0]), RlpMacLength):
|
||||
result = IncorrectMac
|
||||
else:
|
||||
c.aesdec.decrypt(toa(data, 0, rsize), output)
|
||||
outlen = bodysize
|
||||
result = Success
|
|
@ -8,7 +8,8 @@
|
|||
#
|
||||
|
||||
import unittest
|
||||
import eth_keys, ethp2p/auth, nimcrypto/utils
|
||||
import eth_keys, ethp2p/auth, nimcrypto/utils, nimcrypto/keccak,
|
||||
ethp2p/hexdump
|
||||
|
||||
# This was generated by `print` actual auth message generated by
|
||||
# https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_auth.py
|
||||
|
@ -317,10 +318,15 @@ suite "Ethereum P2P handshake test suite":
|
|||
csecInitiator.macKey == csecResponder.macKey
|
||||
taes[0..^1] == csecInitiator.aesKey[0..^1]
|
||||
tmac[0..^1] == csecInitiator.macKey[0..^1]
|
||||
csecInitiator.egressMac[0..^1] == temac[0..^1]
|
||||
csecInitiator.ingressMac[0..^1] == timac[0..^1]
|
||||
csecResponder.egressMac[0..^1] == timac[0..^1]
|
||||
csecResponder.ingressMac[0..^1] == temac[0..^1]
|
||||
let iemac = csecInitiator.egressMac.finish()
|
||||
let iimac = csecInitiator.ingressMac.finish()
|
||||
let remac = csecResponder.egressMac.finish()
|
||||
let rimac = csecResponder.ingressMac.finish()
|
||||
check:
|
||||
iemac.data[0..^1] == temac[0..^1]
|
||||
iimac.data[0..^1] == timac[0..^1]
|
||||
remac.data[0..^1] == timac[0..^1]
|
||||
rimac.data[0..^1] == temac[0..^1]
|
||||
|
||||
block:
|
||||
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
#
|
||||
# Ethereum P2P
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# See the file "LICENSE", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import unittest
|
||||
import eth_keys, ethp2p/auth, ethp2p/rlpxcrypt, nimcrypto/utils, ethp2p/hexdump
|
||||
import nimcrypto/sysrand, nimcrypto/keccak
|
||||
|
||||
const data = [
|
||||
("initiator_private_key",
|
||||
"5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"),
|
||||
("receiver_private_key",
|
||||
"c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"),
|
||||
("initiator_ephemeral_private_key",
|
||||
"19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"),
|
||||
("receiver_ephemeral_private_key",
|
||||
"d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"),
|
||||
("auth_plaintext",
|
||||
"""884c36f7ae6b406637c1f61b2f57e1d2cab813d24c6559aaf843c3f48962f32f
|
||||
46662c066d39669b7b2e3ba14781477417600e7728399278b1b5d801a519aa57
|
||||
0034fdb5419558137e0d44cd13d319afe5629eeccb47fd9dfe55cc6089426e46
|
||||
cc762dd8a0636e07a54b31169eba0c7a20a1ac1ef68596f1f283b5c676bae406
|
||||
4abfcce24799d09f67e392632d3ffdc12e3d6430dcb0ea19c318343ffa7aae74
|
||||
d4cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb
|
||||
1100"""),
|
||||
("authresp_plaintext",
|
||||
"""802b052f8b066640bba94a4fc39d63815c377fced6fcb84d27f791c9921ddf3e
|
||||
9bf0108e298f490812847109cbd778fae393e80323fd643209841a3b7f110397
|
||||
f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7
|
||||
00"""),
|
||||
("auth_ciphertext",
|
||||
"""04a0274c5951e32132e7f088c9bdfdc76c9d91f0dc6078e848f8e3361193dbdc
|
||||
43b94351ea3d89e4ff33ddcefbc80070498824857f499656c4f79bbd97b6c51a
|
||||
514251d69fd1785ef8764bd1d262a883f780964cce6a14ff206daf1206aa073a
|
||||
2d35ce2697ebf3514225bef186631b2fd2316a4b7bcdefec8d75a1025ba2c540
|
||||
4a34e7795e1dd4bc01c6113ece07b0df13b69d3ba654a36e35e69ff9d482d88d
|
||||
2f0228e7d96fe11dccbb465a1831c7d4ad3a026924b182fc2bdfe016a6944312
|
||||
021da5cc459713b13b86a686cf34d6fe6615020e4acf26bf0d5b7579ba813e77
|
||||
23eb95b3cef9942f01a58bd61baee7c9bdd438956b426a4ffe238e61746a8c93
|
||||
d5e10680617c82e48d706ac4953f5e1c4c4f7d013c87d34a06626f498f34576d
|
||||
c017fdd3d581e83cfd26cf125b6d2bda1f1d56"""),
|
||||
("authresp_ciphertext",
|
||||
"""049934a7b2d7f9af8fd9db941d9da281ac9381b5740e1f64f7092f3588d4f87f
|
||||
5ce55191a6653e5e80c1c5dd538169aa123e70dc6ffc5af1827e546c0e958e42
|
||||
dad355bcc1fcb9cdf2cf47ff524d2ad98cbf275e661bf4cf00960e74b5956b79
|
||||
9771334f426df007350b46049adb21a6e78ab1408d5e6ccde6fb5e69f0f4c92b
|
||||
b9c725c02f99fa72b9cdc8dd53cff089e0e73317f61cc5abf6152513cb7d833f
|
||||
09d2851603919bf0fbe44d79a09245c6e8338eb502083dc84b846f2fee1cc310
|
||||
d2cc8b1b9334728f97220bb799376233e113"""),
|
||||
("ecdhe_shared_secret",
|
||||
"e3f407f83fc012470c26a93fdff534100f2c6f736439ce0ca90e9914f7d1c381"),
|
||||
("initiator_nonce",
|
||||
"cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb11"),
|
||||
("receiver_nonce",
|
||||
"f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7"),
|
||||
("aes_secret",
|
||||
"c0458fa97a5230830e05f4f20b7c755c1d4e54b1ce5cf43260bb191eef4e418d"),
|
||||
("mac_secret",
|
||||
"48c938884d5067a1598272fcddaa4b833cd5e7d92e8228c0ecdfabbe68aef7f1"),
|
||||
("token",
|
||||
"3f9ec2592d1554852b1f54d228f042ed0a9310ea86d038dc2b401ba8cd7fdac4"),
|
||||
("initial_egress_MAC",
|
||||
"09771e93b1a6109e97074cbe2d2b0cf3d3878efafe68f53c41bb60c0ec49097e"),
|
||||
("initial_ingress_MAC",
|
||||
"75823d96e23136c89666ee025fb21a432be906512b3dd4a3049e898adb433847"),
|
||||
("initiator_hello_packet",
|
||||
"""6ef23fcf1cec7312df623f9ae701e63b550cdb8517fefd8dd398fc2acd1d935e
|
||||
6e0434a2b96769078477637347b7b01924fff9ff1c06df2f804df3b0402bbb9f
|
||||
87365b3c6856b45e1e2b6470986813c3816a71bff9d69dd297a5dbd935ab578f
|
||||
6e5d7e93e4506a44f307c332d95e8a4b102585fd8ef9fc9e3e055537a5cec2e9"""),
|
||||
("receiver_hello_packet",
|
||||
"""6ef23fcf1cec7312df623f9ae701e63be36a1cdd1b19179146019984f3625d4a
|
||||
6e0434a2b96769050577657247b7b02bc6c314470eca7e3ef650b98c83e9d7dd
|
||||
4830b3f718ff562349aead2530a8d28a8484604f92e5fced2c6183f304344ab0
|
||||
e7c301a0c05559f4c25db65e36820b4b909a226171a60ac6cb7beea09376d6d8""")
|
||||
]
|
||||
|
||||
proc testValue(s: string): string =
|
||||
for item in data:
|
||||
if item[0] == s:
|
||||
result = item[1]
|
||||
break
|
||||
|
||||
template getBodySize(a: openarray[byte]): int =
|
||||
(int(a[0]) shl 16) or (int(a[1]) shl 8) or int(a[2])
|
||||
|
||||
suite "Ethereum RLPx encryption/decryption test suite":
|
||||
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
|
||||
result = newHandshake(flags)
|
||||
if Initiator in flags:
|
||||
result.host.seckey = initPrivateKey(testValue("initiator_private_key"))
|
||||
result.host.pubkey = result.host.seckey.getPublicKey()
|
||||
let epki = testValue("initiator_ephemeral_private_key")
|
||||
result.ephemeral.seckey = initPrivateKey(epki)
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
|
||||
let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
|
||||
result.initiatorNonce[0..^1] = nonce[0..^1]
|
||||
elif Responder in flags:
|
||||
result.host.seckey = initPrivateKey(testValue("receiver_private_key"))
|
||||
result.host.pubkey = result.host.seckey.getPublicKey()
|
||||
let epkr = testValue("receiver_ephemeral_private_key")
|
||||
result.ephemeral.seckey = initPrivateKey(epkr)
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
|
||||
let nonce = fromHex(stripSpaces(testValue("receiver_nonce")))
|
||||
result.responderNonce[0..^1] = nonce[0..^1]
|
||||
|
||||
test "Encrypt/Decrypt Hello packet test vectors":
|
||||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var authm = fromHex(stripSpaces(testValue("auth_ciphertext")))
|
||||
var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext")))
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var stateInitiator0, stateInitiator1: SecretState
|
||||
var stateResponder0, stateResponder1: SecretState
|
||||
check:
|
||||
responder.decodeAuthMessage(authm) == AuthStatus.Success
|
||||
initiator.decodeAckMessage(ackm) == AuthStatus.Success
|
||||
initiator.getSecrets(authm, ackm, csecInitiator) == AuthStatus.Success
|
||||
responder.getSecrets(authm, ackm, csecResponder) == AuthStatus.Success
|
||||
initSecretState(csecInitiator, stateInitiator0)
|
||||
initSecretState(csecResponder, stateResponder0)
|
||||
initSecretState(csecInitiator, stateInitiator1)
|
||||
initSecretState(csecResponder, stateResponder1)
|
||||
var packet0 = testValue("initiator_hello_packet")
|
||||
var initiatorHello = fromHex(stripSpaces(packet0))
|
||||
var packet1 = testValue("receiver_hello_packet")
|
||||
var responderHello = fromHex(stripSpaces(packet1))
|
||||
var header: array[RlpHeaderLength, byte]
|
||||
|
||||
block:
|
||||
check stateResponder0.decryptHeader(toOpenArray(initiatorHello, 0, 31),
|
||||
header) == RlpxStatus.Success
|
||||
let bodysize = getBodySize(header)
|
||||
check bodysize == 79
|
||||
# we need body size to be rounded to 16 bytes boundary to properly
|
||||
# encrypt/decrypt it.
|
||||
var body = newSeq[byte](decryptedLength(bodysize))
|
||||
var decrsize = 0
|
||||
check:
|
||||
stateResponder0.decryptBody(
|
||||
toOpenArray(initiatorHello, 32, len(initiatorHello) - 1),
|
||||
getBodySize(header), body, decrsize) == RlpxStatus.Success
|
||||
decrsize == 79
|
||||
body.setLen(decrsize)
|
||||
var hello = newSeq[byte](encryptedLength(bodysize))
|
||||
check:
|
||||
stateInitiator1.encrypt(header, body, hello) == RlpxStatus.Success
|
||||
hello == initiatorHello
|
||||
block:
|
||||
check stateInitiator0.decryptHeader(toOpenArray(responderHello, 0, 31),
|
||||
header) == RlpxStatus.Success
|
||||
let bodysize = getBodySize(header)
|
||||
check bodysize == 79
|
||||
# we need body size to be rounded to 16 bytes boundary to properly
|
||||
# encrypt/decrypt it.
|
||||
var body = newSeq[byte](decryptedLength(bodysize))
|
||||
var decrsize = 0
|
||||
check:
|
||||
stateInitiator0.decryptBody(
|
||||
toOpenArray(responderHello, 32, len(initiatorHello) - 1),
|
||||
getBodySize(header), body, decrsize) == RlpxStatus.Success
|
||||
decrsize == 79
|
||||
body.setLen(decrsize)
|
||||
var hello = newSeq[byte](encryptedLength(bodysize))
|
||||
check:
|
||||
stateResponder1.encrypt(header, body, hello) == RlpxStatus.Success
|
||||
hello == responderHello
|
||||
|
||||
test "Continuous stream of different lengths (1000 times)":
|
||||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = newSeq[byte](initiator.authSize())
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var k0 = 0
|
||||
var k1 = 0
|
||||
check initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
m0.setLen(k0)
|
||||
check responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
var m1 = newSeq[byte](responder.ackSize())
|
||||
check responder.ackMessage(m1, k1) == AuthStatus.Success
|
||||
m1.setLen(k1)
|
||||
check initiator.decodeAckMessage(m1) == AuthStatus.Success
|
||||
|
||||
check:
|
||||
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
|
||||
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
|
||||
var stateInitiator: SecretState
|
||||
var stateResponder: SecretState
|
||||
var iheader, rheader: array[16, byte]
|
||||
initSecretState(csecInitiator, stateInitiator)
|
||||
initSecretState(csecResponder, stateResponder)
|
||||
burnMem(iheader)
|
||||
burnMem(rheader)
|
||||
for i in 1..1000:
|
||||
# initiator -> responder
|
||||
block:
|
||||
var ibody = newSeq[byte](i)
|
||||
var encrypted = newSeq[byte](encryptedLength(len(ibody)))
|
||||
iheader[0] = byte((len(ibody) shr 16) and 0xFF)
|
||||
iheader[1] = byte((len(ibody) shr 8) and 0xFF)
|
||||
iheader[2] = byte(len(ibody) and 0xFF)
|
||||
check:
|
||||
randomBytes(ibody) == len(ibody)
|
||||
stateInitiator.encrypt(iheader, ibody,
|
||||
encrypted) == RlpxStatus.Success
|
||||
stateResponder.decryptHeader(toOpenArray(encrypted, 0, 31),
|
||||
rheader) == RlpxStatus.Success
|
||||
var length = getBodySize(rheader)
|
||||
check length == len(ibody)
|
||||
var rbody = newSeq[byte](decryptedLength(length))
|
||||
var decrsize = 0
|
||||
check:
|
||||
stateResponder.decryptBody(
|
||||
toOpenArray(encrypted, 32, len(encrypted) - 1),
|
||||
length, rbody, decrsize) == RlpxStatus.Success
|
||||
decrsize == length
|
||||
rbody.setLen(decrsize)
|
||||
check:
|
||||
iheader == rheader
|
||||
ibody == rbody
|
||||
burnMem(iheader)
|
||||
burnMem(rheader)
|
||||
# responder -> initiator
|
||||
block:
|
||||
var ibody = newSeq[byte](i * 3)
|
||||
var encrypted = newSeq[byte](encryptedLength(len(ibody)))
|
||||
iheader[0] = byte((len(ibody) shr 16) and 0xFF)
|
||||
iheader[1] = byte((len(ibody) shr 8) and 0xFF)
|
||||
iheader[2] = byte(len(ibody) and 0xFF)
|
||||
check:
|
||||
randomBytes(ibody) == len(ibody)
|
||||
stateResponder.encrypt(iheader, ibody,
|
||||
encrypted) == RlpxStatus.Success
|
||||
stateInitiator.decryptHeader(toOpenArray(encrypted, 0, 31),
|
||||
rheader) == RlpxStatus.Success
|
||||
var length = getBodySize(rheader)
|
||||
check length == len(ibody)
|
||||
var rbody = newSeq[byte](decryptedLength(length))
|
||||
var decrsize = 0
|
||||
check:
|
||||
stateInitiator.decryptBody(
|
||||
toOpenArray(encrypted, 32, len(encrypted) - 1),
|
||||
length, rbody, decrsize) == RlpxStatus.Success
|
||||
decrsize == length
|
||||
rbody.setLen(length)
|
||||
check:
|
||||
iheader == rheader
|
||||
ibody == rbody
|
||||
burnMem(iheader)
|
||||
burnMem(rheader)
|
Loading…
Reference in New Issue