Implemented AES256-GCM encryption of all network packets. Requires some more refactoring to remove redundant code and make it cleaner.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import random
|
||||
import nimcrypto
|
||||
|
||||
import ./[utils, types]
|
||||
|
||||
proc generateSessionKey*(): Key =
|
||||
# Generate a random 256-bit (32-byte) session key for AES-256 encryption
|
||||
var key: array[32, byte]
|
||||
for i in 0 ..< 32:
|
||||
key[i] = byte(rand(255))
|
||||
return key
|
||||
|
||||
proc generateIV*(): Iv =
|
||||
# Generate a random 98-bit (12-byte) initialization vector for AES-256 GCM mode
|
||||
var iv: array[12, byte]
|
||||
for i in 0 ..< 12:
|
||||
iv[i] = byte(rand(255))
|
||||
return iv
|
||||
|
||||
proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) =
|
||||
|
||||
# Encrypt data using AES-256 GCM
|
||||
var encData = newSeq[byte](data.len)
|
||||
var tag: AuthenticationTag
|
||||
|
||||
var ctx: GCM[aes256]
|
||||
ctx.init(key, iv, sequenceNumber.toBytes())
|
||||
|
||||
ctx.encrypt(data, encData)
|
||||
ctx.getTag(tag)
|
||||
ctx.clear()
|
||||
|
||||
return (encData, tag)
|
||||
|
||||
proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) =
|
||||
|
||||
# Decrypt data using AES-256 GCM
|
||||
var data = newSeq[byte](encData.len)
|
||||
var tag: AuthenticationTag
|
||||
|
||||
var ctx: GCM[aes256]
|
||||
ctx.init(key, iv, sequenceNumber.toBytes())
|
||||
|
||||
ctx.decrypt(encData, data)
|
||||
ctx.getTag(tag)
|
||||
ctx.clear()
|
||||
|
||||
return (data, tag)
|
||||
@@ -99,6 +99,39 @@ proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
|
||||
if bytesRead != length:
|
||||
raise newException(IOError, "Not enough data to read")
|
||||
|
||||
proc getKey*(unpacker: Unpacker): Key =
|
||||
var key: Key
|
||||
|
||||
let bytesRead = unpacker.stream.readData(key[0].unsafeAddr, 32)
|
||||
unpacker.position += bytesRead
|
||||
|
||||
if bytesRead != 32:
|
||||
raise newException(IOError, "Not enough data to read key")
|
||||
|
||||
return key
|
||||
|
||||
proc getIv*(unpacker: Unpacker): Iv =
|
||||
var iv: Iv
|
||||
|
||||
let bytesRead = unpacker.stream.readData(iv[0].unsafeAddr, 12)
|
||||
unpacker.position += bytesRead
|
||||
|
||||
if bytesRead != 12:
|
||||
raise newException(IOError, "Not enough data to read IV")
|
||||
|
||||
return iv
|
||||
|
||||
proc getAuthenticationTag*(unpacker: Unpacker): AuthenticationTag =
|
||||
var tag: AuthenticationTag
|
||||
|
||||
let bytesRead = unpacker.stream.readData(tag[0].unsafeAddr, 16)
|
||||
unpacker.position += bytesRead
|
||||
|
||||
if bytesRead != 16:
|
||||
raise newException(IOError, "Not enough data to read authentication tag")
|
||||
|
||||
return tag
|
||||
|
||||
proc getArgument*(unpacker: Unpacker): TaskArg =
|
||||
result.argType = unpacker.getUint8()
|
||||
|
||||
@@ -133,8 +166,23 @@ proc packHeader*(packer: Packer, header: Header, bodySize: uint32): seq[byte] =
|
||||
.add(header.version)
|
||||
.add(header.packetType)
|
||||
.add(header.flags)
|
||||
.add(header.seqNr)
|
||||
.add(bodySize)
|
||||
.addData(header.hmac)
|
||||
.add(header.agentId)
|
||||
.add(header.seqNr)
|
||||
.addData(header.iv)
|
||||
.addData(header.gmac)
|
||||
|
||||
return packer.pack()
|
||||
return packer.pack()
|
||||
|
||||
proc unpackHeader*(unpacker: Unpacker): Header=
|
||||
return Header(
|
||||
magic: unpacker.getUint32(),
|
||||
version: unpacker.getUint8(),
|
||||
packetType: unpacker.getUint8(),
|
||||
flags: unpacker.getUint16(),
|
||||
size: unpacker.getUint32(),
|
||||
agentId: unpacker.getUint32(),
|
||||
seqNr: unpacker.getUint64(),
|
||||
iv: unpacker.getIv(),
|
||||
gmac: unpacker.getAuthenticationTag()
|
||||
)
|
||||
@@ -7,7 +7,7 @@ import streams
|
||||
const
|
||||
MAGIC* = 0x514E3043'u32 # Magic value: C0NQ
|
||||
VERSION* = 1'u8 # Version 1
|
||||
HEADER_SIZE* = 32'u8 # 32 bytes fixed packet header size
|
||||
HEADER_SIZE* = 52'u8 # 48 bytes fixed packet header size
|
||||
|
||||
type
|
||||
PacketType* = enum
|
||||
@@ -49,14 +49,24 @@ type
|
||||
RESULT_BINARY = 1'u8
|
||||
RESULT_NO_OUTPUT = 2'u8
|
||||
|
||||
# Encryption
|
||||
type
|
||||
Key* = array[32, byte]
|
||||
Iv* = array[12, byte]
|
||||
AuthenticationTag* = array[16, byte]
|
||||
|
||||
# Packet structure
|
||||
type
|
||||
Header* = object
|
||||
magic*: uint32 # [4 bytes ] magic value
|
||||
version*: uint8 # [1 byte ] protocol version
|
||||
packetType*: uint8 # [1 byte ] message type
|
||||
flags*: uint16 # [2 bytes ] message flags
|
||||
seqNr*: uint32 # [4 bytes ] sequence number / nonce
|
||||
size*: uint32 # [4 bytes ] size of the payload body
|
||||
hmac*: array[16, byte] # [16 bytes] hmac for message integrity
|
||||
magic*: uint32 # [4 bytes ] magic value
|
||||
version*: uint8 # [1 byte ] protocol version
|
||||
packetType*: uint8 # [1 byte ] message type
|
||||
flags*: uint16 # [2 bytes ] message flags
|
||||
size*: uint32 # [4 bytes ] size of the payload body
|
||||
agentId*: uint32 # [4 bytes ] agent id, used as AAD for encryptio
|
||||
seqNr*: uint64 # [8 bytes ] sequence number, used as AAD for encryption
|
||||
iv*: Iv # [12 bytes] random IV for AES256 GCM encryption
|
||||
gmac*: AuthenticationTag # [16 bytes] authentication tag for AES256 GCM encryption
|
||||
|
||||
TaskArg* = object
|
||||
argType*: uint8 # [1 byte ] argument type
|
||||
@@ -66,7 +76,6 @@ type
|
||||
header*: Header
|
||||
|
||||
taskId*: uint32 # [4 bytes ] task id
|
||||
agentId*: uint32 # [4 bytes ] agent id
|
||||
listenerId*: uint32 # [4 bytes ] listener id
|
||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||
command*: uint16 # [2 bytes ] command id
|
||||
@@ -77,7 +86,6 @@ type
|
||||
header*: Header
|
||||
|
||||
taskId*: uint32 # [4 bytes ] task id
|
||||
agentId*: uint32 # [4 bytes ] agent id
|
||||
listenerId*: uint32 # [4 bytes ] listener id
|
||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||
command*: uint16 # [2 bytes ] command id
|
||||
@@ -104,16 +112,14 @@ type
|
||||
# Checkin binary structure
|
||||
type
|
||||
Heartbeat* = object
|
||||
header*: Header
|
||||
agentId*: uint32 # [4 bytes ] agent id
|
||||
listenerId*: uint32 # [4 bytes ] listener id
|
||||
timestamp*: uint32
|
||||
header*: Header # [48 bytes ] fixed header
|
||||
listenerId*: uint32 # [4 bytes ] listener id
|
||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||
|
||||
# Registration binary structure
|
||||
type
|
||||
# All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data
|
||||
AgentMetadata* = object
|
||||
agentId*: uint32
|
||||
listenerId*: uint32
|
||||
username*: seq[byte]
|
||||
hostname*: seq[byte]
|
||||
@@ -127,7 +133,7 @@ type
|
||||
|
||||
AgentRegistrationData* = object
|
||||
header*: Header
|
||||
# encMaterial*: seq[byte] # Encryption material for the agent registration
|
||||
sessionKey*: Key # [32 bytes ] AES 256 session key
|
||||
metadata*: AgentMetadata
|
||||
|
||||
# Agent structure
|
||||
@@ -148,6 +154,7 @@ type
|
||||
tasks*: seq[Task]
|
||||
firstCheckin*: DateTime
|
||||
latestCheckin*: DateTime
|
||||
sessionKey*: Key
|
||||
|
||||
# Listener structure
|
||||
type
|
||||
@@ -176,4 +183,5 @@ type
|
||||
listenerId*: string
|
||||
ip*: string
|
||||
port*: int
|
||||
sleep*: int
|
||||
sleep*: int
|
||||
sessionKey*: Key
|
||||
@@ -50,4 +50,16 @@ proc toBytes*(value: uint32): seq[byte] =
|
||||
byte((value shr 8) and 0xFF),
|
||||
byte((value shr 16) and 0xFF),
|
||||
byte((value shr 24) and 0xFF)
|
||||
]
|
||||
|
||||
proc toBytes*(value: uint64): seq[byte] =
|
||||
return @[
|
||||
byte(value and 0xFF),
|
||||
byte((value shr 8) and 0xFF),
|
||||
byte((value shr 16) and 0xFF),
|
||||
byte((value shr 24) and 0xFF),
|
||||
byte((value shr 32) and 0xFF),
|
||||
byte((value shr 40) and 0xFF),
|
||||
byte((value shr 48) and 0xFF),
|
||||
byte((value shr 56) and 0xFF)
|
||||
]
|
||||
Reference in New Issue
Block a user