mirror of
https://github.com/logos-storage/logos-storage-nim-dht.git
synced 2026-01-05 23:13:10 +00:00
* encodeMessagePacket: expose haskey encodeMessagePacket checks for session and behaves differently based on that. Exposing this difference in behavior. Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> * improve tracing of message exchange run e.g. as ``` nim c -r -d:debug -d:chronicles_enabled=on -d:chronicles_log_level=TRACE -d:chronicles_sinks=textlines[nocolors,stdout] tests/dht/test_providers.nim >err ``` Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> * add debug on Handshake timeour Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> * queue messages during handshake and send later If a handshake was already in progress, messages were dropped. Instead of this, it is better to queue these and send as soon as the handshake is finished and thus the encryption key is known. Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> * rename handshakeInProgress to keyexchangeInProgress Handshake is also a name of a message, which makes previous name less clear. Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> * keyexchangeInProgress: do not remove on handshake received This is the wrong direction, not needed Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> --------- Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>
657 lines
25 KiB
Nim
657 lines
25 KiB
Nim
{.used.}
|
|
|
|
import
|
|
std/[options, sequtils, tables],
|
|
asynctest/unittest2,
|
|
bearssl/rand,
|
|
chronos,
|
|
libp2p/crypto/secp,
|
|
codexdht/discv5/[messages, messages_encoding, encoding, spr, node, sessions],
|
|
codexdht/discv5/crypto,
|
|
stew/byteutils,
|
|
stew/shims/net,
|
|
stint,
|
|
../dht/test_helper
|
|
|
|
from secp256k1 import toRaw
|
|
|
|
suite "Discovery v5.1 Protocol Message Encodings":
|
|
test "Ping Request":
|
|
let
|
|
sprSeq = 1'u64
|
|
p = PingMessage(sprSeq: sprSeq)
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(p, reqId)
|
|
check byteutils.toHex(encoded) == "010a010112020801"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == ping
|
|
message.ping.sprSeq == sprSeq
|
|
|
|
test "Pong Response":
|
|
let
|
|
sprSeq = 1'u64
|
|
ip = IpAddress(family: IpAddressFamily.IPv4, address_v4: [127.byte, 0, 0, 1])
|
|
port = 5000'u16
|
|
p = PongMessage(sprSeq: sprSeq, ip: ip, port: port)
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(p, reqId)
|
|
check byteutils.toHex(encoded) == "020a01011211080112090a010112047f0000011a021388"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == pong
|
|
message.pong.sprSeq == sprSeq
|
|
message.pong.ip == ip
|
|
message.pong.port == port
|
|
|
|
test "FindNode Request":
|
|
let
|
|
distances = @[0x0100'u16]
|
|
fn = FindNodeMessage(distances: distances)
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(fn, reqId)
|
|
check byteutils.toHex(encoded) == "030a010112040a020100"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == findNode
|
|
message.findNode.distances == distances
|
|
|
|
test "Nodes Response (empty)":
|
|
let
|
|
total = 0x1'u32
|
|
n = NodesMessage(total: total)
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(n, reqId)
|
|
check byteutils.toHex(encoded) == "040a010112020801"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == MessageKind.nodes
|
|
message.nodes.total == total
|
|
message.nodes.sprs.len() == 0
|
|
|
|
test "Nodes Response (multiple)":
|
|
var s1, s2: SignedPeerRecord
|
|
check s1.fromURI("spr:CiUIAhIhAjOdSH7SNzktg3kZUNyJHwY23mmMH6BR6gGuP6WL14WAEgIDARpWCicAJQgCEiECM51IftI3OS2DeRlQ3IkfBjbeaYwfoFHqAa4_pYvXhYAQnP2JkgYaCwoJBAAAAACRAgABGgsKCQQAAAAAkQIAAhoLCgkEAAAAAJECAAMqRzBFAiEAjMd_0mXjPJVRdLn0ligEiy1ypjlayzDwup2QU2-hpdUCIH-o5bq46N3umISo4kSwmQIo41RrWptoSGMqvZJHluV2")
|
|
check s2.fromURI("spr:CiUIAhIhAmvtpc_d8c2JEw57W7YJK6wj20oES_hHMoqgMQ3RI6RFEgIDARpWCicAJQgCEiECa-2lz93xzYkTDntbtgkrrCPbSgRL-EcyiqAxDdEjpEUQnP2JkgYaCwoJBAAAAACRAgABGgsKCQQAAAAAkQIAAhoLCgkEAAAAAJECAAMqRjBEAiA9QbGnjF5tmMm08_yyE9wWrk3lChyHFaspxRav5kiLTgIgWEHQnpKz0vGtcse8Bm5WHatXMgiG8_u_Jy0s8XMsolk")
|
|
let
|
|
total = 0x1'u32
|
|
n = NodesMessage(total: total, sprs: @[s1, s2])
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(n, reqId)
|
|
check byteutils.toHex(encoded) == "040a0101129f03080112cc010a250802122102339d487ed237392d83791950dc891f0636de698c1fa051ea01ae3fa58bd78580120203011a560a2700250802122102339d487ed237392d83791950dc891f0636de698c1fa051ea01ae3fa58bd78580109cfd8992061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a4730450221008cc77fd265e33c955174b9f49628048b2d72a6395acb30f0ba9d90536fa1a5d502207fa8e5bab8e8ddee9884a8e244b0990228e3546b5a9b6848632abd924796e57612cb010a2508021221026beda5cfddf1cd89130e7b5bb6092bac23db4a044bf847328aa0310dd123a445120203011a560a27002508021221026beda5cfddf1cd89130e7b5bb6092bac23db4a044bf847328aa0310dd123a445109cfd8992061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a46304402203d41b1a78c5e6d98c9b4f3fcb213dc16ae4de50a1c8715ab29c516afe6488b4e02205841d09e92b3d2f1ad72c7bc066e561dab57320886f3fbbf272d2cf1732ca259"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == MessageKind.nodes
|
|
message.nodes.total == total
|
|
message.nodes.sprs.len() == 2
|
|
message.nodes.sprs[0] == s1
|
|
message.nodes.sprs[1] == s2
|
|
|
|
test "Talk Request":
|
|
let
|
|
tr = TalkReqMessage(protocol: "echo".toBytes(), request: "hi".toBytes())
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(tr, reqId)
|
|
check byteutils.toHex(encoded) == "050a0101120a0a046563686f12026869"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == talkReq
|
|
message.talkReq.protocol == "echo".toBytes()
|
|
message.talkReq.request == "hi".toBytes()
|
|
|
|
test "Talk Response":
|
|
let
|
|
tr = TalkRespMessage(response: "hi".toBytes())
|
|
reqId = RequestId(id: @[1.byte])
|
|
|
|
let encoded = encodeMessage(tr, reqId)
|
|
check byteutils.toHex(encoded) == "060a0101120412026869"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isOk()
|
|
|
|
let message = decoded.get()
|
|
check:
|
|
message.reqId == reqId
|
|
message.kind == talkResp
|
|
message.talkResp.response == "hi".toBytes()
|
|
|
|
test "Ping with too large RequestId":
|
|
let
|
|
sprSeq = 1'u64
|
|
p = PingMessage(sprSeq: sprSeq)
|
|
# 1 byte too large
|
|
reqId = RequestId(id: @[0.byte, 1, 2, 3, 4, 5, 6, 7, 8])
|
|
let encoded = encodeMessage(p, reqId)
|
|
check byteutils.toHex(encoded) == "010a0900010203040506070812020801"
|
|
|
|
let decoded = decodeMessage(encoded)
|
|
check decoded.isErr()
|
|
|
|
test "Pong with invalid IP address size":
|
|
# pong message with ip field of 5 bytes
|
|
let encodedPong = "02cb0101857f00000102821388"
|
|
|
|
let decoded = decodeMessage(hexToSeqByte(encodedPong))
|
|
check decoded.isErr()
|
|
|
|
# According to test vectors:
|
|
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md#cryptographic-primitives
|
|
suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
|
test "ECDH":
|
|
const
|
|
# input
|
|
publicKey = "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231"
|
|
secretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
|
# expected output
|
|
sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e"
|
|
|
|
let
|
|
pub = PublicKey.fromHex(publicKey).expect("Valid public key hex")
|
|
priv = PrivateKey.fromHex(secretKey).expect("Valid private key hex")
|
|
eph = ecdhRaw(priv, pub).expect("Valid public and private keys")
|
|
check:
|
|
eph.data == hexToSeqByte(sharedSecret)
|
|
|
|
test "Key Derivation":
|
|
const
|
|
# input
|
|
ephemeralKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
|
destPubkey = "0x0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91"
|
|
nodeIdA = "0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"
|
|
nodeIdB = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
|
challengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
|
# expected output
|
|
initiatorKey = "0xdccc82d81bd610f4f76d3ebe97a40571"
|
|
recipientKey = "0xac74bb8773749920b0d3a8881c173ec5"
|
|
|
|
let secrets = deriveKeys(
|
|
NodeId.fromHex(nodeIdA),
|
|
NodeId.fromHex(nodeIdB),
|
|
PrivateKey.fromHex(ephemeralKey).expect("Valid private key hex"),
|
|
PublicKey.fromHex(destPubkey).expect("Valid public key hex"),
|
|
hexToSeqByte(challengeData)
|
|
).expect("Valid key structure")
|
|
|
|
check:
|
|
secrets.initiatorKey == hexToByteArray[aesKeySize](initiatorKey)
|
|
secrets.recipientKey == hexToByteArray[aesKeySize](recipientKey)
|
|
|
|
test "Nonce Signing":
|
|
const
|
|
# input
|
|
staticKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
|
challengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
|
ephemeralPubkey = "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231"
|
|
nodeIdB = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
|
# expected output
|
|
idSignature = "0xdb0ae930a460fd767cb26a519221e6be5edc3501865406d8af6d215f87ebf35b07563d891082d97147d9499f49bb86ee399f57367af1b866674f9e54760e3a21"
|
|
|
|
let
|
|
privKey = PrivateKey.fromHex(staticKey).expect("Valid private key hex")
|
|
signature = createIdSignature(
|
|
privKey,
|
|
hexToSeqByte(challengeData),
|
|
hexToSeqByte(ephemeralPubkey),
|
|
NodeId.fromHex(nodeIdB)
|
|
).expect("Valid signature data")
|
|
libp2pSig = SkSignature.init(signature.data).expect("Valid sig data")
|
|
skSig = secp256k1.SkSignature(libp2pSig)
|
|
check:
|
|
skSig.toRaw() == hexToByteArray[64](idSignature)
|
|
verifyIdSignature(signature, hexToSeqByte(challengeData),
|
|
hexToSeqByte(ephemeralPubkey), NodeId.fromHex(nodeIdB),
|
|
privKey.getPublicKey.expect("Valid private key for public key"))
|
|
|
|
test "Encryption/Decryption":
|
|
const
|
|
# input
|
|
encryptionKey = "0x9f2d77db7004bf8a1a85107ac686990b"
|
|
nonce = "0x27b5af763c446acd2749fe8e"
|
|
pt = "0x01c20101"
|
|
ad = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903"
|
|
# expected output
|
|
messageCiphertext = "0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648"
|
|
|
|
let encrypted = encryptGCM(hexToByteArray[aesKeySize](encryptionKey),
|
|
hexToByteArray[gcmNonceSize](nonce),
|
|
hexToSeqByte(pt),
|
|
hexToByteArray[32](ad))
|
|
check encrypted == hexToSeqByte(messageCiphertext)
|
|
|
|
# According to test vectors:
|
|
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md#packet-encodings
|
|
suite "Discovery v5.1 Packet Encodings Test Vectors":
|
|
const
|
|
nodeAKey = "0xfe5f08c842aa946659b266ce68faa5d2fd982634594dccdf7f916e3fcf0541a3"
|
|
nodeBKey = "0x00064765abe9a4e63b068b5af99c26c61c8ade9bfdae6494873b137ec8152578"
|
|
|
|
var
|
|
codecA, codecB: Codec
|
|
nodeA, nodeB: Node
|
|
privKeyA, privKeyB: PrivateKey
|
|
|
|
setup:
|
|
# sender -> encode
|
|
privKeyA = PrivateKey.fromHex(nodeAKey).expect("Valid private key hex")
|
|
# receive -> decode
|
|
privKeyB = PrivateKey.fromHex(nodeBKey).expect("Valid private key hex")
|
|
|
|
let
|
|
enrRecA = SignedPeerRecord.init(1, privKeyA,
|
|
some(ValidIpAddress.init("127.0.0.1")), some(Port(9001)),
|
|
some(Port(9001))).expect("Properly intialized private key")
|
|
|
|
enrRecB = SignedPeerRecord.init(1, privKeyB,
|
|
some(ValidIpAddress.init("127.0.0.1")), some(Port(9001)),
|
|
some(Port(9001))).expect("Properly intialized private key")
|
|
|
|
nodeA = newNode(enrRecA).expect("Properly initialized record")
|
|
nodeB = newNode(enrRecB).expect("Properly initialized record")
|
|
codecA = Codec(localNode: nodeA, privKey: privKeyA,
|
|
sessions: Sessions.init(5))
|
|
codecB = Codec(localNode: nodeB, privKey: privKeyB,
|
|
sessions: Sessions.init(5))
|
|
|
|
test "Ping Ordinary Message Packet":
|
|
const
|
|
readKey = "0x00000000000000000000000000000000"
|
|
pingReqId = "0x00000001"
|
|
pingSprSeq = 2'u64
|
|
|
|
encodedPacket =
|
|
"000000000000000000000000000000003788c1e1079e89374c4beac74d76364d" &
|
|
"bd9e8cd1847adc2f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" &
|
|
"7afd279a5fe4ffdd21ed1a6d388207f00b48115b8ee4e8eaf675b0865821e126" &
|
|
"36bc"
|
|
|
|
let dummyKey = "0x00000000000000000000000000000001" # of no importance
|
|
codecA.sessions.store(nodeB.id, nodeB.address.get(),
|
|
hexToByteArray[aesKeySize](dummyKey), hexToByteArray[aesKeySize](readKey))
|
|
codecB.sessions.store(nodeA.id, nodeA.address.get(),
|
|
hexToByteArray[aesKeySize](readKey), hexToByteArray[aesKeySize](dummyKey))
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket))
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().messageOpt.isSome()
|
|
decoded.get().messageOpt.get().reqId.id == hexToSeqByte(pingReqId)
|
|
decoded.get().messageOpt.get().kind == ping
|
|
decoded.get().messageOpt.get().ping.sprSeq == pingSprSeq
|
|
|
|
test "Whoareyou Packet":
|
|
const
|
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
|
whoareyouSprSeq = 0
|
|
|
|
encodedPacket =
|
|
"000000000000000000000000000000003788c1e1079e89374d4beac74d76364d" &
|
|
"bd9e8cd1847ae48d2f96e595c7a904454033dd25eaefc076a4537f17e8a43a"
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket))
|
|
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().flag == Flag.Whoareyou
|
|
decoded.get().whoareyou.requestNonce == hexToByteArray[gcmNonceSize](whoareyouRequestNonce)
|
|
decoded.get().whoareyou.idNonce == hexToByteArray[idNonceSize](whoareyouIdNonce)
|
|
decoded.get().whoareyou.recordSeq == whoareyouSprSeq
|
|
decoded.get().whoareyou.challengeData == hexToSeqByte(whoareyouChallengeData)
|
|
|
|
codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket & "00")).isErr()
|
|
|
|
test "Ping Handshake Message Packet":
|
|
const
|
|
pingReqId = "0x00000001"
|
|
pingSprSeq = 1'u64
|
|
#
|
|
# handshake inputs:
|
|
#
|
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001"
|
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
|
whoareyouSprSeq = 1'u64
|
|
|
|
encodedPacket =
|
|
"000000000000000000000000000000003788c1e1079e89374e4beac74d76364d" &
|
|
"bd9e8cd1847a712f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" &
|
|
"7afd279a5fe4ff441af3b17ec840104be4fae12da6828a752f1f7df615c4c757" &
|
|
"9558b0b537760549eb0e5ada50545b066734abb4b7bddbd503fad873e981b485" &
|
|
"dc9f8eee725661ebf766ce027edc9dcb55f6160b06d9c764481fb520b7581678" &
|
|
"e5fce2ab0d5fa2edaa92f252db6d6ea30d52a78e4114fb9dcf26fb1690e65ca0" &
|
|
"079386331f311bfc0aef4a771155ed"
|
|
|
|
let
|
|
whoareyouData = WhoareyouData(
|
|
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
|
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
|
recordSeq: whoareyouSprSeq,
|
|
challengeData: hexToSeqByte(whoareyouChallengeData))
|
|
pubkey = privKeyA.getPublicKey
|
|
.expect("Valid private key for public key")
|
|
.some
|
|
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
|
key = HandshakeKey(nodeId: nodeA.id, address: nodeA.address.get())
|
|
|
|
check: not codecB.handshakes.hasKeyOrPut(key, challenge)
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket))
|
|
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().message.reqId.id == hexToSeqByte(pingReqId)
|
|
decoded.get().message.kind == ping
|
|
decoded.get().message.ping.sprSeq == pingSprSeq
|
|
decoded.get().node.isNone()
|
|
|
|
codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket & "00")).isErr()
|
|
|
|
test "Ping Handshake Message Packet with SPR":
|
|
const
|
|
pingReqId = "0x00000001"
|
|
pingSprSeq = 1'u64
|
|
#
|
|
# handshake inputs:
|
|
#
|
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
|
whoareyouSprSeq = 0'u64
|
|
|
|
encodedPacket =
|
|
"000000000000000000000000000000003788c1e1079e89374e4beac74d76364d" &
|
|
"bd9e8cd1847bc72f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" &
|
|
"7afd279a5fe4ff441af3b17ec96b7e0586467988bd0c4784575e02a32d0e7594" &
|
|
"b65e939a8490bd71813894c9f4e8796f8c3d3fce29171fc3c568ace28d89ea80" &
|
|
"a9707693d2ee9388e74f89a5a62bc3cb55f6160bae865f0779b75c989805232d" &
|
|
"55b73645bbd797a7d550db7233300b3d1de011fbde92783b4d813ddabff9c96a" &
|
|
"a0361139ec10df16cf2c136caf44d064194cc6777202952dc24c6fccf04f026d" &
|
|
"6d58c1c43dcf643d211afe4ff8d9f9727fb45380862d47978fd015a4f9a7ff39" &
|
|
"28158c38173fd1192164e714ad5454446b681ed2130a349fdffd4b81f814700c" &
|
|
"5b04551e621b65432d3e1beca05dc8e801a4c21da44d4cb29b1eddb92417aedd" &
|
|
"de855ba1016e93eb25d55879a0d27d170dc7904c6dfa06ce813b0455b3258c66" &
|
|
"1de55dcd349397bc5b8eb8e8ef5b4ed2115e968197effe44a0f5fb2b7d"
|
|
|
|
let
|
|
whoareyouData = WhoareyouData(
|
|
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
|
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
|
recordSeq: whoareyouSprSeq,
|
|
challengeData: hexToSeqByte(whoareyouChallengeData))
|
|
pubkey = none(PublicKey)
|
|
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
|
key = HandshakeKey(nodeId: nodeA.id, address: nodeA.address.get())
|
|
|
|
check: not codecB.handshakes.hasKeyOrPut(key, challenge)
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket))
|
|
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().message.reqId.id == hexToSeqByte(pingReqId)
|
|
decoded.get().message.kind == ping
|
|
decoded.get().message.ping.sprSeq == pingSprSeq
|
|
decoded.get().node.isSome()
|
|
|
|
codecB.decodePacket(nodeA.address.get(),
|
|
hexToSeqByte(encodedPacket & "00")).isErr()
|
|
|
|
suite "Discovery v5.1 Additional Encode/Decode":
|
|
var rng = newRng()
|
|
|
|
test "Encryption/Decryption":
|
|
let
|
|
encryptionKey = hexToByteArray[aesKeySize]("0x9f2d77db7004bf8a1a85107ac686990b")
|
|
nonce = hexToByteArray[gcmNonceSize]("0x27b5af763c446acd2749fe8e")
|
|
ad = hexToByteArray[32]("0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903")
|
|
pt = hexToSeqByte("0xa1")
|
|
|
|
let
|
|
ct = encryptGCM(encryptionKey, nonce, pt, ad)
|
|
decrypted = decryptGCM(encryptionKey, nonce, ct, ad)
|
|
|
|
check decrypted.get() == pt
|
|
|
|
test "Decryption":
|
|
let
|
|
encryptionKey = hexToByteArray[aesKeySize]("0x9f2d77db7004bf8a1a85107ac686990b")
|
|
nonce = hexToByteArray[gcmNonceSize]("0x27b5af763c446acd2749fe8e")
|
|
ad = hexToByteArray[32]("0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903")
|
|
pt = hexToSeqByte("0x01c20101")
|
|
ct = hexToSeqByte("0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648")
|
|
|
|
# valid case
|
|
check decryptGCM(encryptionKey, nonce, ct, ad).get() == pt
|
|
|
|
# invalid tag/data sizes
|
|
var invalidCipher: seq[byte] = @[]
|
|
check decryptGCM(encryptionKey, nonce, invalidCipher, ad).isNone()
|
|
|
|
invalidCipher = repeat(byte(4), gcmTagSize)
|
|
check decryptGCM(encryptionKey, nonce, invalidCipher, ad).isNone()
|
|
|
|
# invalid tag/data itself
|
|
invalidCipher = repeat(byte(4), gcmTagSize + 1)
|
|
check decryptGCM(encryptionKey, nonce, invalidCipher, ad).isNone()
|
|
|
|
test "Encrypt / Decrypt header":
|
|
var nonce: AESGCMNonce
|
|
hmacDrbgGenerate(rng[], nonce)
|
|
let
|
|
nodeId = NodeId.example(rng)
|
|
authdata = newSeq[byte](32)
|
|
staticHeader = encodeStaticHeader(Flag.OrdinaryMessage, nonce,
|
|
authdata.len())
|
|
header = staticHeader & authdata
|
|
|
|
var iv: array[128 div 8, byte]
|
|
hmacDrbgGenerate(rng[], iv)
|
|
|
|
let
|
|
encrypted = encryptHeader(nodeId, iv, header)
|
|
decoded = decodeHeader(nodeId, iv, encrypted)
|
|
|
|
check decoded.isOk()
|
|
|
|
var
|
|
codecA, codecB: Codec
|
|
nodeA, nodeB: Node
|
|
privKeyA, privKeyB: PrivateKey
|
|
|
|
setup:
|
|
privKeyA = PrivateKey.example(rng) # sender -> encode
|
|
privKeyB = PrivateKey.example(rng) # receiver -> decode
|
|
|
|
let
|
|
enrRecA = SignedPeerRecord.init(1, privKeyA,
|
|
some(ValidIpAddress.init("127.0.0.1")), some(Port(9001)),
|
|
some(Port(9001))).expect("Properly intialized private key")
|
|
|
|
enrRecB = SignedPeerRecord.init(1, privKeyB,
|
|
some(ValidIpAddress.init("127.0.0.1")), some(Port(9001)),
|
|
some(Port(9001))).expect("Properly intialized private key")
|
|
|
|
nodeA = newNode(enrRecA).expect("Properly initialized record")
|
|
nodeB = newNode(enrRecB).expect("Properly initialized record")
|
|
codecA = Codec(localNode: nodeA, privKey: privKeyA, sessions: Sessions.init(5))
|
|
codecB = Codec(localNode: nodeB, privKey: privKeyB, sessions: Sessions.init(5))
|
|
|
|
test "Encode / Decode Ordinary Random Message Packet":
|
|
let
|
|
m = PingMessage(sprSeq: 0)
|
|
reqId = RequestId.init(rng[])
|
|
message = encodeMessage(m, reqId)
|
|
|
|
let (data, nonce, _) = encodeMessagePacket(rng[], codecA, nodeB.id,
|
|
nodeB.address.get(), message)
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
|
check:
|
|
decoded.isOk()
|
|
decoded[].flag == OrdinaryMessage
|
|
decoded[].messageOpt.isNone()
|
|
decoded[].requestNonce == nonce
|
|
|
|
test "Encode / Decode Whoareyou Packet":
|
|
var requestNonce: AESGCMNonce
|
|
hmacDrbgGenerate(rng[], requestNonce)
|
|
let recordSeq = 0'u64
|
|
|
|
let data = encodeWhoareyouPacket(rng[], codecA, nodeB.id,
|
|
nodeB.address.get(), requestNonce, recordSeq, none(PublicKey))
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
|
|
|
let key = HandshakeKey(nodeId: nodeB.id, address: nodeB.address.get())
|
|
var challenge: Challenge
|
|
|
|
check:
|
|
codecA.handshakes.pop(key, challenge)
|
|
decoded.isOk()
|
|
decoded[].flag == Flag.Whoareyou
|
|
decoded[].whoareyou.requestNonce == requestNonce
|
|
decoded[].whoareyou.idNonce == challenge.whoareyouData.idNonce
|
|
decoded[].whoareyou.recordSeq == recordSeq
|
|
|
|
test "Encode / Decode Handshake Message Packet":
|
|
var requestNonce: AESGCMNonce
|
|
hmacDrbgGenerate(rng[], requestNonce)
|
|
let
|
|
recordSeq = 1'u64
|
|
m = PingMessage(sprSeq: 0)
|
|
reqId = RequestId.init(rng[])
|
|
message = encodeMessage(m, reqId)
|
|
pubkey = privKeyA.getPublicKey
|
|
.expect("Valid private key for public key")
|
|
.some
|
|
|
|
# Encode/decode whoareyou packet to get the handshake stored and the
|
|
# whoareyou data returned. It's either that or construct the header for the
|
|
# whoareyouData manually.
|
|
let
|
|
encodedDummy = encodeWhoareyouPacket(rng[], codecB, nodeA.id,
|
|
nodeA.address.get(), requestNonce, recordSeq, pubkey)
|
|
decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy)
|
|
|
|
let
|
|
pubKeyB = privKeyB.getPublicKey.expect("Valid private key for public key")
|
|
data = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
|
nodeB.address.get(), message, decodedDummy[].whoareyou,
|
|
pubKeyB
|
|
).expect("Valid handshake packet data")
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
|
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().message.reqId == reqId
|
|
decoded.get().message.kind == ping
|
|
decoded.get().message.ping.sprSeq == 0
|
|
decoded.get().node.isNone()
|
|
|
|
test "Encode / Decode Handshake Message Packet with SPR":
|
|
var requestNonce: AESGCMNonce
|
|
hmacDrbgGenerate(rng[], requestNonce)
|
|
let
|
|
recordSeq = 0'u64
|
|
m = PingMessage(sprSeq: 0)
|
|
reqId = RequestId.init(rng[])
|
|
message = encodeMessage(m, reqId)
|
|
pubkey = none(PublicKey)
|
|
|
|
# Encode/decode whoareyou packet to get the handshake stored and the
|
|
# whoareyou data returned. It's either that or construct the header for the
|
|
# whoareyouData manually.
|
|
let
|
|
encodedDummy = encodeWhoareyouPacket(rng[], codecB, nodeA.id,
|
|
nodeA.address.get(), requestNonce, recordSeq, pubkey)
|
|
decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy)
|
|
|
|
let
|
|
pubKeyB = privKeyB.getPublicKey.expect("Valid private key for public key")
|
|
encoded = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
|
nodeB.address.get(), message, decodedDummy[].whoareyou,
|
|
pubKeyB
|
|
).expect("Valid handshake packet data")
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(), encoded)
|
|
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().message.reqId == reqId
|
|
decoded.get().message.kind == ping
|
|
decoded.get().message.ping.sprSeq == 0
|
|
decoded.get().node.isSome()
|
|
decoded.get().node.get().record.seqNum == 1
|
|
|
|
test "Encode / Decode Ordinary Message Packet":
|
|
let
|
|
m = PingMessage(sprSeq: 0)
|
|
reqId = RequestId.init(rng[])
|
|
message = encodeMessage(m, reqId)
|
|
|
|
# Need to manually add the secrets that normally get negotiated in the
|
|
# handshake packet.
|
|
var secrets: HandshakeSecrets
|
|
codecA.sessions.store(nodeB.id, nodeB.address.get(), secrets.recipientKey,
|
|
secrets.initiatorKey)
|
|
codecB.sessions.store(nodeA.id, nodeA.address.get(), secrets.initiatorKey,
|
|
secrets.recipientKey)
|
|
|
|
let (data, nonce, _) = encodeMessagePacket(rng[], codecA, nodeB.id,
|
|
nodeB.address.get(), message)
|
|
|
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
|
check:
|
|
decoded.isOk()
|
|
decoded.get().flag == OrdinaryMessage
|
|
decoded.get().messageOpt.isSome()
|
|
decoded.get().messageOpt.get().reqId == reqId
|
|
decoded.get().messageOpt.get().kind == ping
|
|
decoded.get().messageOpt.get().ping.sprSeq == 0
|
|
decoded[].requestNonce == nonce
|