Skip to content

Latest commit

 

History

History
95 lines (79 loc) · 3.86 KB

PROTOCOL.md

File metadata and controls

95 lines (79 loc) · 3.86 KB

Protocol

This document describes how snc works internally so that it feels safer to use for those who want a TLDR instead of going through the source code.

Algorithms

AES

For encryption, snc utilizes Rijndael, the AES (Advanced Encryption Standard). The code is a clone of my own AES implementation. The default key size is set to 128 as it provides better performance. The key size can be safely altered to 192 or 256 bits, but it should be noted that bigger isn't necessarily better; larger AES key sizes exist mostly due to regulations, not because they are harder to break. 128 bits are more than enough for today's technology. That being said, the AES key size can be changed in src/include/aes.h.

SHA3

The SHA3 algorithm is used for key hashing as it allows for flexible digest length and thus enabling the AES key size to be changed without further changes to the source code.

CRC32

CRC32 is used for packet integrity checking.

Authentication

Authentication starts with the client to harden against service fingerprinting. If the first message received from the client has unexpected size, the connection is simply closed. Effectively, snc's authentication works in a 3-way handshake and inherently provides mutual authentication.

  1. Client generates 2 random 16-byte AES IVs and a random 16-byte challenge.
  2. Client initializes two AES states with it's key and generated IVs. One for encryption and another one for decryption.
  3. Client encrypts the challenge.
  4. Client concatenates IVs and challenge and sends the corresponding 48 bytes to the server.
  5. Server receives IVs and challenge.
  6. Server initializes two AES states with it's key and received IVs. One for decryption and another one for encryption (the client's encryption state is the server's decryption state and vice-versa).
  7. Server decrypts the challenge and stores the result.
  8. Server encrypts the challenge.
  9. Server sends the challenge to the client.
  10. Client receives the challenge from the server.
  11. Client decrypts the challenge and stores the result.
  12. Client encrypts the challenge.
  13. Client sends the challenge to the server.
  14. Server receives the challenge.
  15. Server decrypts the challenge.
  16. Both sides now have the necessary information authenticate each other.

Data Transfer

Data transfer is a symmetrical process (same routine for both sides) and happens asynchronously. The data polling loop continues until EOF is read on either side.

Data Packet

Below is a snippet from src/include/net.h describing snc's packet structure.

// Make header size divisible by AES_SIZE_BLOCK
#define HDR_SIZE_PADDING 8

// Up to 32Kb per packet (also divisible by AES_SIZE_BLOCK)
#define PKT_SIZE_PAYLOAD 32768

// The snc header data
struct SNCHeader {
	uint32_t size;
	uint32_t crc32;
	uint8_t padding[HDR_SIZE_PADDING];
};

// The snc packet data
struct SNCPacket {
	struct SNCHeader hdr;
	uint8_t payload[PKT_SIZE_PAYLOAD];
};

Sender

  1. Read at most PKT_SIZE_PAYLOAD bytes from it's input into snc_packet.payload.
  2. Pad snc_packet.payload with random bytes until it's size is divisible by AES_SIZE_BLOCK.
  3. Set snc_packet.snc_header.size to the original data size.
  4. Set snc_packet.snc_header.crc32 to the CRC32 of the original data.
  5. Encrypt snc_packet.snc_header.
  6. Encrypt snc_packet.payload.
  7. Send packet.

Receiver

  1. Read sizeof(snc_header) bytes from the socket into snc_packet.snc_header.
  2. Decrypt snc_packet.snc_header.
  3. Read snc_packet.snc_header.size bytes plus padding from the socket into snc_packet.payload.
  4. Decrypt snc_packet.payload.
  5. Verify the integrity of snc_packet.payload by checking it's CRC32 against snc_header.crc32.
  6. If the integrity check fails, close the connection with the appropriate error.