Final version for review.
This commit is contained in:
parent
b89d42b633
commit
59d65df3f4
686
ethp2p/auth.nim
686
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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue