From 59d65df3f405a6eedf220b50e6564833c49fc265 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Mon, 2 Apr 2018 10:15:16 +0300 Subject: [PATCH] Final version for review. --- ethp2p/auth.nim | 686 +++++++++++++++++++++++++-------------------- tests/testauth.nim | 270 ++++++++++-------- 2 files changed, 533 insertions(+), 423 deletions(-) diff --git a/ethp2p/auth.nim b/ethp2p/auth.nim index 6116aea..11664da 100644 --- a/ethp2p/auth.nim +++ b/ethp2p/auth.nim @@ -13,27 +13,31 @@ import endians import ecc, ecies, rlp import nimcrypto/sysrand, nimcrypto/hash, nimcrypto/utils, nimcrypto/hmac import nimcrypto/rijndael, nimcrypto/keccak, nimcrypto/sha2 -import hexdump const SupportedRlpxVersion* = 4 - # REVIEW: If these messages have fixed lenghts, they will be - # better described by an object type (see my similar comments - # in the ecies module. - PlainAuthMessageLength* = 194 - PlainAuthAckMessageLength* = 97 - AuthMessageLength* = 307 - AuthAckMessageLength* = 210 + PlainAuthMessageV4Length* = 194 + AuthMessageV4Length* = 307 + PlainAuthMessageEIP8Length = 169 + PlainAuthMessageMaxEIP8 = PlainAuthMessageEIP8Length + 255 + AuthMessageEIP8Length* = 282 + 2 + AuthMessageMaxEIP8* = AuthMessageEIP8Length + 255 + PlainAckMessageV4Length* = 97 + AckMessageV4Length* = 210 + PlainAckMessageEIP8Length = 102 + PlainAckMessageMaxEIP8 = PlainAckMessageEIP8Length + 255 + AckMessageEIP8Length = 215 + 2 + AckMessageMaxEIP8 = AckMessageEIP8Length + 255 type - PlainAuthMessage* = object {.packed.} + AuthMessageV4* = object {.packed.} signature: RawSignature keyhash: array[keccak256.sizeDigest, byte] pubkey: PublicKey nonce: array[keccak256.sizeDigest, byte] flag: byte - PlainAuthAckMessage* = object {.packed.} + AckMessageV4* = object {.packed.} pubkey: PublicKey nonce: array[keccak256.sizeDigest, byte] flag: byte @@ -47,6 +51,7 @@ type Success, ## Operation was successful RandomError, ## Could not obtain random data EcdhError, ## ECDH shared secret could not be calculated + BufferOverrun, ## Buffer overrun error SignatureError, ## Signature could not be obtained EciesError, ## ECIES encryption/decryption error InvalidPubKey, ## Invalid public key @@ -56,56 +61,37 @@ type IncompleteError ## Data incomplete error Handshake* = object - version: uint8 - flags: set[HandshakeFlag] - host*: KeyPair - ephemeral*: KeyPair - remoteHPubkey*: PublicKey - remoteEPubkey*: PublicKey - initiatorNonce*: Nonce - responderNonce*: Nonce + version*: uint8 ## protocol version + flags*: set[HandshakeFlag] ## handshake flags + host*: KeyPair ## host keypair + ephemeral*: KeyPair ## ephemeral host keypair + remoteHPubkey*: PublicKey ## remote host public key + remoteEPubkey*: PublicKey ## remote host ephemeral public key + initiatorNonce*: Nonce ## initiator nonce + responderNonce*: Nonce ## responder nonce ConnectionSecret* = object - # REVIEW: it would be nice if Nimcrypto defines distinct or - # alias types such as `aes256.key` instead of having to spell - # out the full array type everywhere. aesKey*: array[aes256.sizeKey, byte] macKey*: array[KeyLength, byte] egressMac*: array[keccak256.sizeDigest, byte] ingressMac*: array[keccak256.sizeDigest, byte] - # PlainAuthMessage* = array[PlainAuthMessageLength, byte] - # PlainAuthAckMessage* = array[PlainAuthAckMessageLength, byte] - AuthMessage* = array[AuthMessageLength, byte] - AuthAckMessage* = array[AuthAckMessageLength, byte] - 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]) = assert(len(a) == len(b)) for i in 0 ..< len(a): a[i] = a[i] xor b[i] -proc empty[T](v: openarray[T]): bool = - var r: T - for item in v: - r = r + item - result = (r == T(0)) - -proc move[T](dst: var openarray[T], src: openarray[T], - dstx: int = 0, dsty: int = -1, srcx: int = 0, srcy: int = -1) = - let sx = if srcx < 0: (len(src) + srcx) else: srcx - let sy = if srcy < 0: (len(src) + srcy) else: srcy - let dx = if dstx < 0: (len(dst) + dstx) else: dstx - let dy = if dsty < 0: (len(dst) + dsty) else: dsty - assert(sy - sx == dy - dx) - moveMem(addr dst[dstx], unsafeAddr src[srcx], (dy - dx + 1) * sizeof(T)) - -proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}): Handshake = - var p: ptr byte +proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}, + version: int = SupportedRlpxVersion): Handshake = + ## Create new `Handshake` object. + result.version = byte(version and 0xFF) result.flags = flags result.ephemeral = newKeyPair() - if Initiator in flags: if randomBytes(result.initiatorNonce) != len(result.initiatorNonce): raise newException(AuthException, "Could not obtain random data!") @@ -113,240 +99,252 @@ proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}): Handshake = if randomBytes(result.responderNonce) != len(result.responderNonce): raise newException(AuthException, "Could not obtain random data!") -proc authMessagePreEIP8*(h: var Handshake, - pubkey: PublicKey, - output: var PlainAuthMessage, - flag: int = 0): AuthStatus = - ## Create plain preEIP8 authentication message. - var secret: SharedSecret - var signature: Signature - var flagb = byte(flag) - +proc authMessagePreEIP8(h: var Handshake, + pubkey: PublicKey, + output: var openarray[byte], + outlen: var int, + flag: int = 0, + encrypt: bool = true): AuthStatus = + ## Create plain pre-EIP8 authentication message. + var + secret: SharedSecret + signature: Signature + buffer: array[PlainAuthMessageV4Length, byte] + flagb: byte + header: ptr AuthMessageV4 + outlen = 0 + flagb = byte(flag) + header = cast[ptr AuthMessageV4](addr buffer[0]) if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: return(EcdhError) - var xornonce = h.initiatorNonce xornonce.sxor(secret) - if signMessage(h.ephemeral.seckey, xornonce, signature) != EccStatus.Success: return(SignatureError) - h.remoteHPubkey = pubkey - - output.signature = signature.getRaw() - output.keyhash = keccak256.digest(h.ephemeral.pubkey.getRaw().data).data - output.pubkey = cast[PublicKey](h.host.pubkey.getRaw().data) - output.nonce = h.initiatorNonce - output.flag = flagb + header.signature = signature.getRaw() + header.keyhash = keccak256.digest(h.ephemeral.pubkey.getRaw().data).data + header.pubkey = cast[PublicKey](h.host.pubkey.getRaw().data) + header.nonce = h.initiatorNonce + header.flag = flagb + if encrypt: + if len(output) < AuthMessageV4Length: + return(BufferOverrun) + if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success: + return(EciesError) + outlen = AuthMessageV4Length + result = Success + else: + if len(output) < PlainAuthMessageV4Length: + return(BufferOverrun) + copyMem(addr output[0], addr buffer[0], PlainAuthMessageV4Length) + outlen = PlainAuthMessageV4Length + result = Success -proc authAckMessagePreEIP8*(h: var Handshake, - output: var PlainAuthAckMessage, - flag: int = 0): AuthStatus = - output.pubkey = cast[PublicKey](h.ephemeral.pubkey.getRaw().data) - output.nonce = h.responderNonce - output.flag = byte(flag) - -proc encryptAuthMessage*(input: ptr byte, inputlen: int, - output: ptr byte, outputlen: int, - pubkey: PublicKey, shmac: ptr byte = nil, - shlen: int = 0): AuthStatus = - result = Success - if eciesEncrypt(input, output, inputlen, outputlen, - pubkey, shmac, shlen) != EciesStatus.Success: - result = EciesError - -proc encryptAuthMessage*(input: PlainAuthMessage, - output: var AuthMessage, - pubkey: PublicKey): AuthStatus = - result = Success - result = encryptAuthMessage(unsafeAddr input[0], PlainAuthMessageLength, - addr output[0], AuthMessageLength, pubkey) - -proc decryptAuthMessage*(input: ptr byte, inputlen: int, - output: ptr byte, outputlen: int, - seckey: PrivateKey, shmac: ptr byte = nil, - shlen: int = 0): AuthStatus = - result = Success - if eciesDecrypt(input, output, inputlen, outputlen, - seckey, shmac, shlen) != EciesStatus.Success: - result = EciesError - -proc decryptAuthMessage*(input: AuthMessage, output: var PlainAuthMessage, - seckey: PrivateKey): AuthStatus = - result = decryptAuthMessage(unsafeAddr input[0], AuthMessageLength, - addr output[0], PlainAuthMessageLength, - seckey) - -proc encryptAuthAckMessage*(input: ptr byte, inputlen: int, - output: ptr byte, outputlen: int, - pubkey: PublicKey, shmac: ptr byte = nil, - shlen: int = 0): AuthStatus = - result = Success - if eciesEncrypt(input, output, inputlen, outputlen, - pubkey, shmac, shlen) != EciesStatus.Success: - result = EciesError - -proc encryptAuthAckMessage*(input: PlainAuthAckMessage, - output: var AuthAckMessage, - pubkey: PublicKey): AuthStatus = - result = encryptAuthAckMessage(unsafeAddr input[0], PlainAuthAckMessageLength, - addr output[0], AuthAckMessageLength, - pubkey) - -proc decryptAuthAckMessage*(input: ptr byte, inputlen: int, - output: ptr byte, outputlen: int, - seckey: PrivateKey, shmac: ptr byte = nil, - shlen: int = 0): AuthStatus = - result = Success - if eciesDecrypt(input, output, inputlen, outputlen, - seckey, shmac, shlen) != EciesStatus.Success: - result = EciesError - -proc decryptAuthAckMessage*(input: AuthAckMessage, - output: var PlainAuthAckMessage, - seckey: PrivateKey): AuthStatus = - result = decryptAuthAckMessage(unsafeAddr input[0], AuthAckMessageLength, - addr output[0], PlainAuthAckMessageLength, - seckey) - -proc decodePlainAuthMessage(h: var Handshake, m: PlainAuthMessage): AuthStatus = - var secret: SharedSecret - var nonce: array[32, byte] - var pubkey: PublicKey - - copyMem(addr nonce[0], unsafeAddr m[161], KeyLength) - if recoverPublicKey(unsafeAddr m[97], sizeof(PublicKey), - pubkey) != EccStatus.Success: - return(InvalidPubKey) +proc authMessageEIP8(h: var Handshake, + pubkey: PublicKey, + output: var openarray[byte], + outlen: var int, + flag: int = 0, + encrypt: bool = true): AuthStatus = + ## Create EIP8 authentication message. + var + secret: SharedSecret + signature: Signature + buffer: array[PlainAuthMessageMaxEIP8, byte] + padsize: byte + assert(EIP8 in h.flags) + outlen = 0 if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: return(EcdhError) - - var xornonce = nonce + var xornonce = h.initiatorNonce xornonce.sxor(secret) + if signMessage(h.ephemeral.seckey, xornonce, signature) != EccStatus.Success: + return(SignatureError) + h.remoteHPubkey = pubkey + var payload = rlp.encodeList(signature.getRaw().data, + h.host.pubkey.getRaw().data, + h.initiatorNonce, + [byte(h.version)]) + assert(len(payload) == PlainAuthMessageEIP8Length) + let pencsize = eciesEncryptedLength(len(payload)) + while true: + if randomBytes(addr padsize, 1) != 1: + return(RandomError) + if int(padsize) > (AuthMessageV4Length - (pencsize + 2)): + break + # It is possible to make packet size constant by uncommenting this line + # padsize = 24 + var wosize = pencsize + int(padsize) + let fullsize = wosize + 2 + if randomBytes(toa(buffer, PlainAuthMessageEIP8Length, + int(padsize))) != int(padsize): + return(RandomError) + if encrypt: + copyMem(addr buffer[0], payload.baseAddr, len(payload)) + if len(output) < fullsize: + return(BufferOverrun) + bigEndian16(addr output, addr wosize) + if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)), + toa(output, 2, wosize), pubkey, + toa(output, 0, 2)) != EciesStatus.Success: + return(EciesError) + outlen = fullsize + else: + let plainsize = len(payload) + int(padsize) + if len(output) < plainsize: + return(BufferOverrun) + copyMem(addr output[0], addr buffer[0], plainsize) + outlen = plainsize + result = Success - if recoverSignatureKey(unsafeAddr m[0], SignatureLength, addr xornonce[0], +proc ackMessagePreEIP8(h: var Handshake, + output: var openarray[byte], + outlen: var int, + flag: int = 0, + encrypt: bool = true): AuthStatus = + ## Create plain pre-EIP8 authentication ack message. + var buffer: array[PlainAckMessageV4Length, byte] + outlen = 0 + var header = cast[ptr AckMessageV4](addr buffer[0]) + header.pubkey = cast[PublicKey](h.ephemeral.pubkey.getRaw().data) + header.nonce = h.responderNonce + header.flag = byte(flag) + if encrypt: + if len(output) < AckMessageV4Length: + return(BufferOverrun) + if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success: + return(EciesError) + outlen = AckMessageV4Length + else: + if len(output) < PlainAckMessageV4Length: + return(BufferOverrun) + copyMem(addr output[0], addr buffer[0], PlainAckMessageV4Length) + outlen = PlainAckMessageV4Length + result = Success + +proc ackMessageEIP8(h: var Handshake, + output: var openarray[byte], + outlen: var int, + flag: int = 0, + encrypt: bool = true): AuthStatus = + ## Create EIP8 authentication ack message. + var + buffer: array[PlainAckMessageMaxEIP8, byte] + padsize: byte + assert(EIP8 in h.flags) + var payload = rlp.encodeList(h.ephemeral.pubkey.getRaw().data, + h.responderNonce, + [byte(h.version)]) + assert(len(payload) == PlainAckMessageEIP8Length) + outlen = 0 + let pencsize = eciesEncryptedLength(len(payload)) + while true: + if randomBytes(addr padsize, 1) != 1: + return(RandomError) + if int(padsize) > (AckMessageV4Length - (pencsize + 2)): + break + # It is possible to make packet size constant by uncommenting this line + # padsize = 0 + var wosize = pencsize + int(padsize) + let fullsize = wosize + 2 + if int(padsize) > 0: + if randomBytes(toa(buffer, PlainAuthMessageEIP8Length, + int(padsize))) != int(padsize): + return(RandomError) + copyMem(addr buffer[0], payload.baseAddr, len(payload)) + if encrypt: + if len(output) < fullsize: + return(BufferOverrun) + bigEndian16(addr output, addr wosize) + if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)), + toa(output, 2, wosize), h.remoteHPubkey, + toa(output, 0, 2)) != EciesStatus.Success: + return(EciesError) + outlen = fullsize + else: + let plainsize = len(payload) + int(padsize) + if len(output) < plainsize: + return(BufferOverrun) + copyMem(addr output[0], addr buffer[0], plainsize) + outlen = plainsize + result = Success + +template authSize*(h: Handshake, encrypt: bool = true): int = + ## Get number of bytes needed to store AuthMessage. + if EIP8 in h.flags: + if encrypt: (AuthMessageMaxEIP8) else: (PlainAuthMessageMaxEIP8) + else: + if encrypt: (AuthMessageV4Length) else: (PlainAuthMessageV4Length) + +template ackSize*(h: Handshake, encrypt: bool = true): int = + ## Get number of bytes needed to store AckMessage. + if EIP8 in h.flags: + if encrypt: (AckMessageMaxEIP8) else: (PlainAckMessageMaxEIP8) + else: + if encrypt: (AckMessageV4Length) else: (PlainAckMessageV4Length) + +proc authMessage*(h: var Handshake, pubkey: PublicKey, + output: var openarray[byte], + outlen: var int, flag: int = 0, + encrypt: bool = true): AuthStatus {.inline.} = + ## Create new AuthMessage for specified `pubkey` and store it inside + ## of `output`, size of generated AuthMessage will stored in `outlen`. + if EIP8 in h.flags: + result = authMessageEIP8(h, pubkey, output, outlen, flag, encrypt) + else: + result = authMessagePreEIP8(h, pubkey, output, outlen, flag, encrypt) + +proc ackMessage*(h: var Handshake, output: var openarray[byte], + outlen: var int, flag: int = 0, + encrypt: bool = true): AuthStatus = + ## Create new AckMessage and store it inside of `output`, size of generated + ## AckMessage will stored in `outlen`. + if EIP8 in h.flags: + result = ackMessageEIP8(h, output, outlen, flag, encrypt) + else: + result = ackMessagePreEIP8(h, output, outlen, flag, encrypt) + +proc decodeAuthMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus = + ## Decodes V4 AuthMessage. + var + secret: SharedSecret + buffer: array[PlainAuthMessageV4Length, byte] + pubkey: PublicKey + assert(Responder in h.flags) + if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success: + return(EciesError) + var header = cast[ptr AuthMessageV4](addr buffer[0]) + if recoverPublicKey(header.pubkey.data, pubkey) != EccStatus.Success: + return(InvalidPubKey) + if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: + return(EcdhError) + var xornonce = header.nonce + xornonce.sxor(secret) + if recoverSignatureKey(header.signature.data, xornonce, h.remoteEPubkey) != EccStatus.Success: return(SignatureError) - - h.initiatorNonce = nonce + h.initiatorNonce = header.nonce h.remoteHPubkey = pubkey result = Success -proc decodePlainAuthAckMessage*(h: var Handshake, - m: PlainAuthAckMessage): AuthStatus = - if recoverPublicKey(m, h.remoteEPubkey, 0, 63) != EccStatus.Success: - return(InvalidPubKey) - - h.responderNonce[0..31] = m[64..95] - result = Success - -proc getSecrets*(h: var Handshake, - msg: ptr byte, msglen: int, - ack: ptr byte, acklen: int, - secret: var ConnectionSecret): AuthStatus = - - var - shsec: SharedSecret - ctx0: keccak256 - ctx1: keccak256 - digest: array[keccak256.sizeDigest, byte] - mac1: array[keccak256.sizeDigest, byte] - mac2: array[keccak256.sizeDigest, byte] - xornonce: Nonce - - # ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) - if ecdhAgree(h.ephemeral.seckey, h.remoteEPubkey, shsec) != EccStatus.Success: - return(EcdhError) - - # shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce)) - ctx0.init() - ctx1.init() - ctx1.update(addr h.responderNonce[0], uint(len(h.responderNonce))) - ctx1.update(addr h.initiatorNonce[0], uint(len(h.initiatorNonce))) - digest = ctx1.finish().data - - ctx1.init() # clean keccak256 context - ctx0.update(addr shsec[0], uint(sizeof(SharedSecret))) - ctx0.update(addr digest[0], uint(keccak256.sizeDigest)) - digest = ctx0.finish().data - - # aes-secret = keccak(ecdhe-secret || shared-secret) - ctx0.init() - ctx0.update(addr shsec[0], uint(sizeof(SharedSecret))) - ctx0.update(addr digest[0], uint(keccak256.sizeDigest)) - secret.aesKey = ctx0.finish().data - - # mac-secret = keccak(ecdhe-secret || aes-secret) - ctx0.init() - ctx0.update(addr shsec[0], uint(sizeof(SharedSecret))) - ctx0.update(addr secret.aesKey[0], uint(keccak256.sizeDigest)) - secret.macKey = ctx0.finish().data - - zeroMem(addr shsec[0], sizeof(SharedSecret)) # clean ecdhe-secret - - # egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init) - xornonce = secret.macKey - xornonce.sxor(h.responderNonce) - ctx0.init() - ctx0.update(addr xornonce[0], uint(sizeof(Nonce))) - ctx0.update(msg, uint(msglen)) - mac1 = ctx0.finish().data - - # ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack) - xornonce = secret.macKey - xornonce.sxor(h.initiatorNonce) - ctx0.init() - ctx0.update(addr xornonce[0], uint(sizeof(Nonce))) - ctx0.update(ack, uint(acklen)) - mac2 = ctx0.finish().data - - ctx0.init() # clean keccak256 context - zeroMem(addr xornonce[0], sizeof(Nonce)) # clean xornonce - - if Initiator in h.flags: - secret.egressMac = mac1 - secret.ingressMac = mac2 - else: - secret.ingressMac = mac1 - secret.egressMac = mac2 - - zeroMem(addr mac1[0], keccak256.sizeDigest) # clean temporary mac1 - zeroMem(addr mac2[0], keccak256.sizeDigest) # clean temporary mac2 - - result = Success - -proc getSecrets*(h: var Handshake, msg: AuthMessage, ack: AuthAckMessage, - secret: var ConnectionSecret): AuthStatus = - result = getSecrets(h, unsafeAddr msg[0], AuthMessageLength, - unsafeAddr ack[0], AuthAckMessageLength, - secret) - -proc decodeAuthEip8Message*(h: var Handshake, msg: ptr byte, - msglen: int): AuthStatus = +proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthStatus = + ## Decodes EIP-8 AuthMessage. var pubkey: PublicKey nonce: Nonce size: uint16 secret: SharedSecret - if msglen < 2: - return(InvalidAuth) - bigEndian16(addr size, msg) - - if (2 + int(size)) > msglen: - return(InvalidAuth) - - # Maximum `size` value is 65535 bytes - var outlen = eciesDecryptedLength(int(size)) - var output = newSeq[byte](outlen) - var input = cast[ptr UncheckedArray[byte]](msg) - if decryptAuthMessage(addr input[2], int(size), addr output[0], - outlen, h.host.seckey, - addr input[0], 2) != Success: + bigEndian16(addr size, unsafeAddr m[0]) + if 2 + int(size) > len(m): + return(IncompleteError) + var buffer = newSeq[byte](eciesDecryptedLength(int(size))) + if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey, + toa(m, 0, 2)) != EciesStatus.Success: return(EciesError) - try: - var reader = rlpFromBytes(output.toRange()) + var reader = rlpFromBytes(buffer.toRange()) if not reader.isList() or reader.listLen() < 4: return(InvalidAuth) if reader.listElem(0).blobLen != SignatureLength: @@ -357,56 +355,42 @@ proc decodeAuthEip8Message*(h: var Handshake, msg: ptr byte, return(InvalidAuth) if reader.listElem(3).blobLen != 1: return(InvalidAuth) - var signatureBr = reader.listElem(0).toBytes() var pubkeyBr = reader.listElem(1).toBytes() var nonceBr = reader.listElem(2).toBytes() var versionBr = reader.listElem(3).toBytes() - if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength, pubkey) != EccStatus.Success: return(InvalidPubKey) copyMem(addr nonce[0], nonceBr.baseAddr, KeyLength) - if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: return(EcdhError) - var xornonce = nonce xornonce.sxor(secret) - if recoverSignatureKey(signatureBr.baseAddr, SignatureLength, addr xornonce[0], h.remoteEPubkey) != EccStatus.Success: return(SignatureError) - h.initiatorNonce = nonce h.remoteHPubkey = pubkey h.version = cast[ptr byte](versionBr.baseAddr)[] result = Success except: - return(RlpError) + result = RlpError -proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte, - msglen: int): AuthStatus = +proc decodeAckMessageEip8*(h: var Handshake, m: openarray[byte]): AuthStatus = + ## Decodes EIP-8 AckMessage. var size: uint16 - if msglen < 2: + assert(len(m) > 2) + bigEndian16(addr size, unsafeAddr m[0]) + if 2 + int(size) > len(m): return(IncompleteError) - bigEndian16(addr size, msg) - - if (2 + int(size)) > msglen: - return(IncompleteError) - - # Maximum `size` value is 65535 bytes - var outlen = eciesDecryptedLength(int(size)) - var output = newSeq[byte](outlen) - var input = cast[ptr UncheckedArray[byte]](msg) - if decryptAuthMessage(addr input[2], int(size), addr output[0], - outlen, h.host.seckey, - addr input[0], 2) != Success: + var buffer = newSeq[byte](eciesDecryptedLength(int(size))) + if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey, + toa(m, 0, 2)) != EciesStatus.Success: return(EciesError) - try: - var reader = rlpFromBytes(output.toRange()) + var reader = rlpFromBytes(buffer.toRange()) if not reader.isList() or reader.listLen() < 3: return(InvalidAck) if reader.listElem(0).blobLen != PublicKeyLength: @@ -418,7 +402,6 @@ proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte, let pubkeyBr = reader.listElem(0).toBytes() let nonceBr = reader.listElem(1).toBytes() let versionBr = reader.listElem(2).toBytes() - if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength, h.remoteEPubkey) != EccStatus.Success: return(InvalidPubKey) @@ -426,43 +409,128 @@ proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte, h.version = cast[ptr byte](versionBr.baseAddr)[] result = Success except: - return(RlpError) + result = RlpError -proc decodeAuthMessage*(h: var Handshake, msg: ptr byte, - msglen: int): AuthStatus = - if msglen < AuthMessageLength: - return(IncompleteError) - elif msglen == AuthMessageLength: - # Decoding plain authentication message - var plain: PlainAuthMessage - result = decryptAuthMessage(msg, msglen, addr plain[0], - sizeof(PlainAuthMessage), h.host.seckey) - if result == Success: - result = decodePlainAuthMessage(h, plain) +proc decodeAckMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus = + ## Decodes V4 AckMessage. + var + buffer: array[PlainAckMessageV4Length, byte] + assert(Initiator in h.flags) + if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success: + return(EciesError) + var header = cast[ptr AckMessageV4](addr buffer[0]) + if recoverPublicKey(header.pubkey.data, h.remoteEPubkey) != EccStatus.Success: + return(InvalidPubKey) + h.responderNonce = header.nonce + result = Success + +proc decodeAuthMessage*(h: var Handshake, input: openarray[byte]): AuthStatus = + ## Decodes AuthMessage from `input`. + if len(input) < AuthMessageV4Length: + result = IncompleteError + elif len(input) == AuthMessageV4Length: + let res = h.decodeAuthMessageV4(input) + if res != Success: + if h.decodeAuthMessageEip8(input) != Success: + result = res + else: + h.flags.incl(EIP8) + result = Success + else: + result = Success else: - # Decoding EIP-8 authentication message - result = decodeAuthEip8Message(h, msg, msglen) + result = h.decodeAuthMessageEip8(input) if result == Success: h.flags.incl(EIP8) -proc decodeAckMessage*(h: var Handshake, msg: ptr byte, - msglen: int): AuthStatus = - if msglen < AuthAckMessageLength: +proc decodeAckMessage*(h: var Handshake, input: openarray[byte]): AuthStatus = + ## Decodes AckMessage from `input`. + if len(input) < AckMessageV4Length: return(IncompleteError) - elif msglen == AuthAckMessageLength: - # Decoding plain authentication ACK message - var plain: PlainAuthAckMessage - result = decryptAuthAckMessage(msg, msglen, addr plain[0], - PlainAuthAckMessageLength, - h.host.seckey) - if result == Success: - result = decodePlainAuthAckMessage(h, plain) + elif len(input) == AckMessageV4Length: + let res = h.decodeAckMessageV4(input) + if res != Success: + if h.decodeAckMessageEip8(input) != Success: + result = res + else: + h.flags.incl(EIP8) + result = Success + else: + result = Success else: - # Decoding EIP-8 ACK authentication message - result = decodeAuthAckEip8Message(h, msg, msglen) + result = h.decodeAckMessageEip8(input) + if result == Success: + h.flags.incl(EIP8) -proc decodeAuthMessage*(h: var Handshake, msg: openarray[byte]): AuthStatus = - result = decodeAuthMessage(h, unsafeAddr msg[0], len(msg)) +proc getSecrets*(h: Handshake, authmsg: openarray[byte], + ackmsg: openarray[byte], + secret: var ConnectionSecret): AuthStatus = + ## Derive secrets from handshake `h` using encrypted AuthMessage `authmsg` and + ## encrypted AckMessage `ackmsg`. + var + shsec: SharedSecret + ctx0: keccak256 + ctx1: keccak256 + mac1: MDigest[256] + mac2: MDigest[256] + xornonce: Nonce -proc decodeAckMessage*(h: var Handshake, msg: openarray[byte]): AuthStatus = - result = decodeAckMessage(h, unsafeAddr msg[0], len(msg)) + # ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) + if ecdhAgree(h.ephemeral.seckey, h.remoteEPubkey, shsec) != EccStatus.Success: + return(EcdhError) + + # shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce)) + ctx0.init() + ctx1.init() + ctx1.update(h.responderNonce) + ctx1.update(h.initiatorNonce) + mac1 = ctx1.finish() + ctx1.clear() + ctx0.update(shsec) + ctx0.update(mac1.data) + mac1 = ctx0.finish() + + # aes-secret = keccak(ecdhe-secret || shared-secret) + ctx0.init() + ctx0.update(shsec) + ctx0.update(mac1.data) + mac1 = ctx0.finish() + + # mac-secret = keccak(ecdhe-secret || aes-secret) + ctx0.init() + ctx0.update(shsec) + ctx0.update(mac1.data) + secret.aesKey = mac1.data + mac1 = ctx0.finish() + secret.macKey = mac1.data + + burnMem(shsec) + # egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init) + xornonce = mac1.data + xornonce.sxor(h.responderNonce) + 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 + + if Initiator in h.flags: + secret.egressMac = mac1.data + secret.ingressMac = mac2.data + else: + secret.ingressMac = mac1.data + secret.egressMac = mac2.data + + burnMem(mac1) + burnMem(mac2) + result = Success diff --git a/tests/testauth.nim b/tests/testauth.nim index 63bf194..615b6f6 100644 --- a/tests/testauth.nim +++ b/tests/testauth.nim @@ -213,96 +213,106 @@ proc testE8Value(s: string): string = suite "Ethereum P2P handshake test suite": block: - var initiator: Handshake - var receiver: Handshake - var m0, dm0: PlainAuthMessage - var em0: AuthMessage - - initiator = newHandshake({Initiator}) - receiver = newHandshake({Responder}) - initiator.host.seckey = getPrivateKey(testValue("initiator_private_key")) - initiator.host.pubkey = initiator.host.seckey.getPublicKey() - var epki = testValue("initiator_ephemeral_private_key") - initiator.ephemeral.seckey = getPrivateKey(epki) - initiator.ephemeral.pubkey = initiator.ephemeral.seckey.getPublicKey() - receiver.host.seckey = getPrivateKey(testValue("receiver_private_key")) - receiver.host.pubkey = receiver.host.seckey.getPublicKey() - var epkr = testValue("receiver_ephemeral_private_key") - receiver.ephemeral.seckey = getPrivateKey(epkr) - receiver.ephemeral.pubkey = receiver.ephemeral.seckey.getPublicKey() - var n0 = fromHex(stripSpaces(testValue("initiator_nonce"))) - initiator.initiatorNonce[0..^1] = n0[0..^1] - var n1 = fromHex(stripSpaces(testValue("receiver_nonce"))) - receiver.responderNonce[0..^1] = n1[0..^1] + proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = + result = newHandshake(flags) + if Initiator in flags: + result.host.seckey = getPrivateKey(testValue("initiator_private_key")) + result.host.pubkey = result.host.seckey.getPublicKey() + let epki = testValue("initiator_ephemeral_private_key") + result.ephemeral.seckey = getPrivateKey(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 = getPrivateKey(testValue("receiver_private_key")) + result.host.pubkey = result.host.seckey.getPublicKey() + let epkr = testValue("receiver_ephemeral_private_key") + result.ephemeral.seckey = getPrivateKey(epkr) + result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() + let nonce = fromHex(stripSpaces(testValue("receiver_nonce"))) + result.responderNonce[0..^1] = nonce[0..^1] test "Create plain auth message": - check authMessage(initiator, receiver.host.pubkey, - m0) == AuthStatus.Success - var m1 = fromHex(stripSpaces(testValue("auth_plaintext"))) - var m2 = fromHex(stripSpaces(pyevmAuth)) + var initiator = newTestHandshake({Initiator}) + var responder = newTestHandshake({Responder}) + var m0 = newSeq[byte](initiator.authSize(false)) + var k0 = 0 check: - m0[65..^1] == m1[65..^1] - m0[0..^1] == m2[0..^1] - - test "Auth message encrypt/decrypt": - # Check that encrypting and decrypting the auth_init gets us the orig msg. + initiator.authMessage(responder.host.pubkey, + m0, k0, 0, false) == AuthStatus.Success + var expect1 = fromHex(stripSpaces(testValue("auth_plaintext"))) + var expect2 = fromHex(stripSpaces(pyevmAuth)) check: - encryptAuthMessage(m0, em0, receiver.host.pubkey) == AuthStatus.Success - decryptAuthMessage(em0, dm0, receiver.host.seckey) == AuthStatus.Success - m0[0..^1] == dm0[0..^1] + m0[65..^1] == expect1[65..^1] + m0[0..^1] == expect2[0..^1] - test "Auth message decode": - # Check that the responder correctly decodes the auth msg. - check receiver.decodeAuthMessage(em0) == AuthStatus.Success + test "Auth message decoding": + var initiator = newTestHandshake({Initiator}) + var responder = newTestHandshake({Responder}) + var m0 = newSeq[byte](initiator.authSize()) + var k0 = 0 let remoteEPubkey0 = initiator.ephemeral.pubkey.data let remoteHPubkey0 = initiator.host.pubkey.data check: - receiver.initiatorNonce[0..^1] == n0[0..^1] - receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] - receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] + initiator.authMessage(responder.host.pubkey, + m0, k0) == AuthStatus.Success + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] + responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] + responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] - var k0: PlainAuthAckMessage - var ek0: AuthAckMessage - - test "Auth ACK expectation": - # Check that the auth_ack msg generated by the responder is what we - # expect. - check receiver.authAckMessage(k0) == AuthStatus.Success - var ac0 = fromHex(stripSpaces(testValue("authresp_plaintext"))) + test "ACK message expectation": + var initiator = newTestHandshake({Initiator}) + var responder = newTestHandshake({Responder}) + var m0 = newSeq[byte](initiator.authSize()) + var m1 = newSeq[byte](responder.ackSize(false)) + var k0 = 0 + var k1 = 0 + var expect0 = fromHex(stripSpaces(testValue("authresp_plaintext"))) check: - k0[0..^1] == ac0[0..^1] - receiver.initiatorNonce[0..^1] == n0[0..^1] - encryptAuthAckMessage(k0, ek0, - receiver.remoteHPubkey) == AuthStatus.Success + initiator.authMessage(responder.host.pubkey, + m0, k0) == AuthStatus.Success + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.ackMessage(m1, k1, 0, false) == AuthStatus.Success + m1 == expect0 + responder.initiatorNonce == initiator.initiatorNonce - test "Initiator decode Auth ACK message": - # Check if initiator correctly decodes the auth ack msg. - check initiator.decodeAckMessage(ek0) == AuthStatus.Success - let remoteEPubkey1 = receiver.ephemeral.pubkey.data - let remoteHPubkey1 = receiver.host.pubkey.data + test "ACK message decoding": + var initiator = newTestHandshake({Initiator}) + var responder = newTestHandshake({Responder}) + var m0 = newSeq[byte](initiator.authSize()) + var m1 = newSeq[byte](responder.ackSize()) + var k0 = 0 + var k1 = 0 check: - initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] - initiator.remoteHPubkey.data[0..^1] == remoteHPubkey1[0..^1] - initiator.responderNonce[0..^1] == n1[0..^1] + initiator.authMessage(responder.host.pubkey, + m0, k0) == AuthStatus.Success + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.ackMessage(m1, k1) == AuthStatus.Success + initiator.decodeAckMessage(m1) == AuthStatus.Success + let remoteEPubkey0 = responder.ephemeral.pubkey.data + let remoteHPubkey0 = responder.host.pubkey.data + check: + initiator.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] + initiator.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] + initiator.responderNonce == responder.responderNonce test "Check derived secrets": - # Check that the secrets derived from ephemeral key agreements match - # the expected values. + var initiator = newTestHandshake({Initiator}) + var responder = newTestHandshake({Responder}) var authm = fromHex(stripSpaces(testValue("auth_ciphertext"))) var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext"))) var taes = fromHex(stripSpaces(testValue("aes_secret"))) var tmac = fromHex(stripSpaces(testValue("mac_secret"))) var temac = fromHex(stripSpaces(testValue("initial_egress_MAC"))) var timac = fromHex(stripSpaces(testValue("initial_ingress_MAC"))) - var csecInitiator: ConnectionSecret var csecResponder: ConnectionSecret - check: - initiator.getSecrets(addr authm[0], len(authm), addr ackm[0], - len(ackm), csecInitiator) == AuthStatus.Success - receiver.getSecrets(addr authm[0], len(authm), addr ackm[0], - len(ackm), csecResponder) == AuthStatus.Success + responder.decodeAuthMessage(authm) == AuthStatus.Success + initiator.decodeAckMessage(ackm) == AuthStatus.Success + initiator.getSecrets(authm, ackm, csecInitiator) == AuthStatus.Success + responder.getSecrets(authm, ackm, csecResponder) == AuthStatus.Success csecInitiator.aesKey == csecResponder.aesKey csecInitiator.macKey == csecResponder.macKey taes[0..^1] == csecInitiator.aesKey[0..^1] @@ -322,7 +332,7 @@ suite "Ethereum P2P handshake test suite": result.ephemeral.seckey = getPrivateKey(esec) result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce"))) - result.initiatorNonce[0..(KeyLength - 1)] = nonce[0..(KeyLength - 1)] + result.initiatorNonce[0..^1] = nonce[0..^1] elif Responder in flags: result.host.seckey = getPrivateKey(testE8Value("receiver_private_key")) result.host.pubkey = result.host.seckey.getPublicKey() @@ -330,67 +340,53 @@ suite "Ethereum P2P handshake test suite": result.ephemeral.seckey = getPrivateKey(esec) result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() let nonce = fromHex(stripSpaces(testE8Value("receiver_nonce"))) - result.responderNonce[0..(KeyLength - 1)] = nonce[0..(KeyLength - 1)] + result.responderNonce[0..^1] = nonce[0..^1] test "AUTH/ACK v4 test vectors": # auth/ack v4 var initiator = newTestHandshake({Initiator}) - var receiver = newTestHandshake({Responder}) - - # Check that the responder correctly decodes the auth msg. + var responder = newTestHandshake({Responder}) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4"))) check: - receiver.decodeAuthMessage(m0) == AuthStatus.Success - receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] let remoteEPubkey0 = initiator.ephemeral.pubkey.data - check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] let remoteHPubkey0 = initiator.host.pubkey.data - check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] - - # Check that the initiator correctly decodes the auth ack msg. + check: + responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] + responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_v4"))) check initiator.decodeAckMessage(m1) == AuthStatus.Success - - let remoteEPubkey1 = receiver.ephemeral.pubkey.data + let remoteEPubkey1 = responder.ephemeral.pubkey.data check: initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] - initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] + initiator.responderNonce[0..^1] == responder.responderNonce[0..^1] test "AUTH/ACK EIP-8 test vectors": var initiator = newTestHandshake({Initiator}) - var receiver = newTestHandshake({Responder}) - - # Check that the responder correctly decodes the auth msg. + var responder = newTestHandshake({Responder}) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8"))) check: - receiver.decodeAuthMessage(m0) == AuthStatus.Success - receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] let remoteEPubkey0 = initiator.ephemeral.pubkey.data - check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] + check responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] let remoteHPubkey0 = initiator.host.pubkey.data - check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] - - # Check that the initiator correctly decodes the auth ack msg. + check responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8"))) check initiator.decodeAckMessage(m1) == AuthStatus.Success - - let remoteEPubkey1 = receiver.ephemeral.pubkey.data + let remoteEPubkey1 = responder.ephemeral.pubkey.data check: initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] - initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] - - # Check that the secrets derived from ephemeral key agreements match - # the expected values. + initiator.responderNonce[0..^1] == responder.responderNonce[0..^1] var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_secret"))) var tmac = fromHex(stripSpaces(testE8Value("auth2ack2_mac_secret"))) - var csecInitiator: ConnectionSecret var csecResponder: ConnectionSecret - check: - initiator.getSecrets(addr m0[0], len(m0), addr m1[0], - len(m1), csecInitiator) == AuthStatus.Success - receiver.getSecrets(addr m0[0], len(m0), addr m1[0], - len(m1), csecResponder) == AuthStatus.Success + int(initiator.version) == 4 + int(responder.version) == 4 + initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success + responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success csecInitiator.aesKey == csecResponder.aesKey csecInitiator.macKey == csecResponder.macKey taes[0..^1] == csecInitiator.aesKey[0..^1] @@ -398,23 +394,69 @@ suite "Ethereum P2P handshake test suite": test "AUTH/ACK EIP-8 with additional fields test vectors": var initiator = newTestHandshake({Initiator}) - var receiver = newTestHandshake({Responder}) - - # Check that the responder correctly decodes the auth msg. + var responder = newTestHandshake({Responder}) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f"))) check: - receiver.decodeAuthMessage(m0) == AuthStatus.Success - receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] + responder.decodeAuthMessage(m0) == AuthStatus.Success + responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] let remoteEPubkey0 = initiator.ephemeral.pubkey.data - check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] let remoteHPubkey0 = initiator.host.pubkey.data - check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] - - # Check that the initiator correctly decodes the auth ack msg. + check: + responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] + responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8_3f"))) check initiator.decodeAckMessage(m1) == AuthStatus.Success - - let remoteEPubkey1 = receiver.ephemeral.pubkey.data + let remoteEPubkey1 = responder.ephemeral.pubkey.data check: + int(initiator.version) == 57 + int(responder.version) == 56 initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] - initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] + initiator.responderNonce[0..^1] == responder.responderNonce[0..^1] + + test "100 AUTH/ACK EIP-8 handshakes": + for i in 1..100: + var initiator = newTestHandshake({Initiator, EIP8}) + 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 + check (EIP8 in responder.flags) == true + 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 + csecInitiator.aesKey == csecResponder.aesKey + csecInitiator.macKey == csecResponder.macKey + + test "100 AUTH/ACK V4 handshakes": + for i in 1..100: + 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 + csecInitiator.aesKey == csecResponder.aesKey + csecInitiator.macKey == csecResponder.macKey