Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
fetch-depth: 100
- name: Meson build & test
run: |
meson setup builddir && ninja -C builddir test
meson setup builddir -Dcsp:packet_padding_bytes=38 && ninja -C builddir test
10 changes: 6 additions & 4 deletions include/cblk/csp_if_cblk.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ typedef struct __attribute__((packed))
uint8_t reserved1 : 1;

/* Byte 2 & 3*/
uint16_t data_length :16; //! Data length in RS frame in bytes
uint16_t packet_length :16; //! Total CSP packet length in bytes including csp header and crypto overhead if encrypted

} cblk_hdr_t;

Expand All @@ -33,7 +33,10 @@ typedef struct __attribute__((packed))
#define CCSDS_FRAME_LEN 223
#define CBLK_DATA_LEN (CCSDS_FRAME_LEN-sizeof(cblk_hdr_t))
#define CRYPTO_PREAMP 16 /* crypto_secretbox_BOXZEROBYTES */
#define CRYPTO_POSTAMP 32+9 /* crypto_secretbox_KEYBYTES + NOUNCE_SIZE */
#define CRYPTO_POSTAMP (16+9) /* 16 zero fill + NONCE_SIZE */
#define CRYPTO_MAC_SIZE 16
/* ccsds frame index is 4 bits */
#define CBLK_MAX_FRAMES_PER_PACKET 15

typedef struct {

Expand All @@ -52,8 +55,7 @@ typedef struct {
/* Variables for internal use */
uint8_t rx_packet_idx;
uint8_t rx_frame_idx;
uint8_t packet_enc[CRYPTO_PREAMP+CSP_PACKET_PADDING_BYTES+CSP_BUFFER_SIZE+CRYPTO_POSTAMP];
uint8_t packet_dec[CRYPTO_PREAMP+CSP_PACKET_PADDING_BYTES+CSP_BUFFER_SIZE+CRYPTO_POSTAMP];
csp_packet_t* rx_packet;

} csp_cblk_interface_data_t;

Expand Down
5 changes: 3 additions & 2 deletions include/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
#include <param/param.h>

#define CRYPTO_NUM_KEYS 3
#define CSP_ID2_HEADER_SIZE 6

extern param_t tx_encrypt;
extern param_t rx_decrypt;

void crypto_key_generate(param_t * param, int idx);
int16_t crypto_decrypt(uint8_t * msg_out, uint8_t * ciphertext_in, uint16_t ciphertext_len, uint8_t crypto_key);
int16_t crypto_encrypt(uint8_t * msg_out, uint8_t * msg_in, uint16_t msg_len);
int16_t crypto_decrypt(csp_packet_t * packet, uint8_t crypto_key);
int16_t crypto_encrypt(csp_packet_t * packet);
void crypto_init();
174 changes: 134 additions & 40 deletions src/crypto/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@
#include <csp/arch/csp_time.h>
#include <stdlib.h>
#include <stdio.h>
#include "csp/csp.h"

#ifdef USE_TWEETNACL
#include "tweetnacl.h"

/* Required to link tweetnacl.c */
void randombytes(unsigned char * a, unsigned long long c) {
// Note: Pseudo random since we are not initializing random!
unsigned int seed = csp_get_ms();
while(c > 0) {
*a = rand_r(&seed) & 0xFF;
a++;
c--;
}
}
#endif

#ifdef USE_SODIUM
Expand All @@ -16,6 +28,8 @@

#define NONCE_SIZE (sizeof(uint64_t) + sizeof(uint8_t))

_Static_assert(CSP_PACKET_PADDING_BYTES >= crypto_secretbox_ZEROBYTES + CSP_ID2_HEADER_SIZE, "Not enough padding before csp packet for in-place encryption!");

uint8_t _crypto_beforenm[CRYPTO_NUM_KEYS][crypto_secretbox_KEYBYTES];

void crypto_key_generate(param_t * param, int idx) {
Expand All @@ -25,86 +39,166 @@ void crypto_key_generate(param_t * param, int idx) {
param_get_data(&crypto_key3, _crypto_beforenm[2], sizeof(_crypto_beforenm[2]));
}

/*
There is a 32-octet padding requirement on the plaintext buffer that you pass to crypto_box.
Internally, the NaCl implementation uses this space to avoid having to allocate memory or
use static memory that might involve a cache hit (see Bernstein's paper on cache timing
side-channel attacks for the juicy details).
/**
* @brief Decrypts a CSP packet payload in-place using NaCl/libsodium.
*
* This function verifies the Message Authentication Code (MAC) and decrypts the
* payload contained in the packet structure. It also performs replay protection
* by validating the received Nonce against a stored high-water mark.
*
* The function modifies the packet's data buffer directly, stripping the
* protocol overhead (MAC, padding, Nonce) to restore the original plaintext
* payload.
*
* **Memory Layout Transformation:**
*
* **Before:**
* @code
* [ MAC (16) ] [ Encrypted Payload (N) ] [ 0x00 (16) ] [ Nonce (8) ] [ TX ID (1) ]
* ^
* frame_begin
* @endcode
*
* **After:**
* @code
* [ Headroom ] [ Plaintext Payload (N) ] [ Tailroom ]
* ^
* frame_begin
* @endcode
*
* **Validation Steps:**
* 1. Checks if the packet is long enough to contain the necessary crypto overhead.
* 2. Extracts the Nonce from the end of the packet.
* 3. Prepends the required zerofill padding (`crypto_secretbox_BOXZEROBYTES`) to the start.
* 4. Performs authenticated decryption using `crypto_box_open_afternm`.
* 5. Verifies the Nonce counter is strictly greater than the last received Nonce for that group (Replay Protection).
*
* @param[in,out] packet Pointer to the CSP packet structure containing the encrypted frame.
* On success, `frame_begin` and `frame_length` are updated to point
* to the decrypted plaintext.
* @param[in] crypto_key The index of the pre-shared key to use for decryption (1-based index).
*
* @return
* - \b CSP_ERR_NONE: Decryption successful and Nonce is valid.
* - \b -1: Decryption failed (Authentication error, invalid key, packet too short, or replay detected).
*/
int16_t crypto_decrypt(csp_packet_t * packet, uint8_t crypto_key) {

if(crypto_key == 0 || crypto_key > CRYPTO_NUM_KEYS) {
return -1;
}

Similarly, the crypto_box_open call requires 16 octets of zero padding before the start
of the actual ciphertext. This is used in a similar fashion. These padding octets are not
part of either the plaintext or the ciphertext, so if you are sending ciphertext across the
network, don't forget to remove them!
*/
uint8_t decrypt_out[CSP_PACKET_PADDING_BYTES+CSP_BUFFER_SIZE+crypto_secretbox_ZEROBYTES];
int16_t crypto_decrypt(uint8_t * msg_out, uint8_t * decrypt_in, uint16_t ciphertext_len, uint8_t crypto_key) {
if(packet->frame_length < NONCE_SIZE + crypto_secretbox_ZEROBYTES) {
return -1;
}

ciphertext_len = ciphertext_len - NONCE_SIZE;
packet->frame_length -= NONCE_SIZE;

/* Receive nonce */
uint8_t decrypt_nonce[crypto_box_NONCEBYTES] = {};
memcpy(&decrypt_nonce, &decrypt_in[crypto_secretbox_BOXZEROBYTES+ciphertext_len], NONCE_SIZE);
memcpy(&decrypt_nonce, &packet->frame_begin[packet->frame_length], NONCE_SIZE);

/* Make room for zerofill at the beginning of message */
memset(decrypt_in, 0, crypto_secretbox_BOXZEROBYTES);

/* Make room for zerofill at the beginning of message */
memset(decrypt_out, 0, crypto_secretbox_ZEROBYTES);
packet->frame_begin -= crypto_secretbox_BOXZEROBYTES;
memset(packet->frame_begin, 0, crypto_secretbox_BOXZEROBYTES);

/* Decryption */
if(crypto_box_open_afternm(decrypt_out, decrypt_in, ciphertext_len, decrypt_nonce, _crypto_beforenm[crypto_key-1]) != 0) {
if(crypto_box_open_afternm(packet->frame_begin, packet->frame_begin, packet->frame_length, decrypt_nonce, _crypto_beforenm[crypto_key-1]) != 0) {
param_set_uint16(&crypto_fail_auth_count, param_get_uint16(&crypto_fail_auth_count) + 1);
return -1;
}

/* Message successfully decrypted, check for valid nonce */
uint64_t nonce_counter;
memcpy(&nonce_counter, decrypt_nonce, sizeof(uint64_t));
uint8_t nounce_group = decrypt_nonce[sizeof(uint64_t)];
uint64_t nonce_rx = param_get_uint64_array(&crypto_nonce_rx_count, nounce_group);
uint8_t nonce_group = decrypt_nonce[sizeof(uint64_t)];
uint64_t nonce_rx = param_get_uint64_array(&crypto_nonce_rx_count, nonce_group);
if(nonce_counter <= nonce_rx) {
param_set_uint16(&crypto_fail_nonce_count, param_get_uint16(&crypto_fail_nonce_count) + 1);
return -1;
}

/* Copy encrypted data back to msgbuffer */
memcpy(msg_out, &decrypt_out[crypto_secretbox_ZEROBYTES], ciphertext_len - crypto_secretbox_KEYBYTES);
/* Remove prepended MAC and Zero fill 16 bytes after message */
packet->frame_length -= crypto_secretbox_ZEROBYTES;
packet->frame_begin += crypto_secretbox_ZEROBYTES;

/* Update counter with received value so that next sent value is higher */
param_set_uint64_array(&crypto_nonce_rx_count, nounce_group, nonce_counter);
param_set_uint64_array(&crypto_nonce_rx_count, nonce_group, nonce_counter);

/* Return useable length */
return ciphertext_len - crypto_secretbox_KEYBYTES;
return CSP_ERR_NONE;
}

uint8_t encrypt_in[crypto_secretbox_ZEROBYTES+CSP_PACKET_PADDING_BYTES+CSP_BUFFER_SIZE];
int16_t crypto_encrypt(uint8_t * msg_out, uint8_t * msg_in, uint16_t msg_len) {
/**
* @brief Encrypts a CSP packet payload in-place using NaCl/libsodium.
*
* This function performs authenticated encryption on the data contained in the
* packet structure. It modifies the packet's data buffer directly, adjusting
* the `frame_begin` pointer and `frame_length` to account for the prepended
* Message Authentication Code (MAC) and the appended Nonce.
*
* **Memory Layout Transformation:**
*
* **Before:**
* @code
* [ Headroom ] [ Payload (N) ] [ Tailroom ]
* ^
* frame_begin
* @endcode
*
* **After:**
* @code
* [ MAC (16) ] [ Encrypted Payload (N) ] [ 0x00 (16) ] [ Nonce (8) ] [ TX ID (1) ]
* ^
* frame_begin
* @endcode
*
* @pre **Compile-time Check:** `CSP_PACKET_PADDING_BYTES` must be greater than
* `crypto_secretbox_ZEROBYTES + CSP_ID2_HEADER_SIZE`. This is enforced by a
* `_Static_assert` to ensure safe headroom padding.
*
* @param[in,out] packet Pointer to the CSP packet structure. The `frame_begin`,
* `frame_length`, and buffer contents will be modified.
*
* @return
* - \b CSP_ERR_NONE: Encryption successful.
* - \b CSP_ERR_INVAL: Packet buffer too small for prepending nonce and zerofill.
*/
int16_t crypto_encrypt(csp_packet_t * packet) {

/* Check that there is enough space to postpend nonce and 16 byte zerofill */
if(packet->length + NONCE_SIZE + crypto_secretbox_BOXZEROBYTES > CSP_BUFFER_SIZE) {
return CSP_ERR_INVAL;
}

/* Update and get transmit nonce */
uint64_t tx_nonce = param_get_uint64(&crypto_nonce_tx_count) + 1;
param_set_uint64(&crypto_nonce_tx_count, tx_nonce);

/* Pack nonce into 24-bytes format, expected by NaCl */
unsigned char nonce[crypto_box_NONCEBYTES] = {};
memcpy(nonce, &tx_nonce, sizeof(uint64_t));
/* Add nonce ID to nonce */
nonce[sizeof(uint64_t)] = param_get_uint8(&crypto_nonce_tx_id);

/* Copy msg to new buffer, to make room for zerofill */
memcpy(&encrypt_in[crypto_secretbox_ZEROBYTES], msg_in, msg_len);

/* Make room for zerofill at the beginning of message */
memset(encrypt_in, 0, crypto_secretbox_ZEROBYTES);
uint8_t * padding_begin = packet->frame_begin - crypto_secretbox_ZEROBYTES;
memset(padding_begin, 0, crypto_secretbox_ZEROBYTES);

/* Make room for zerofill at the beginning of message */
memset(msg_out, 0, crypto_secretbox_BOXZEROBYTES);
/* Encryption only returns -1 if mlen < 32 */
crypto_box_afternm(padding_begin, padding_begin, packet->frame_length + crypto_secretbox_ZEROBYTES, nonce, _crypto_beforenm[param_get_uint8(&tx_encrypt)-1]);

if (crypto_box_afternm(msg_out, encrypt_in, crypto_secretbox_KEYBYTES + msg_len, nonce, _crypto_beforenm[param_get_uint8(&tx_encrypt)-1]) != 0) {
return -1;
}
/* Adjust packet pointers and length for the prepended MAC */
packet->frame_begin -= crypto_secretbox_BOXZEROBYTES;
packet->frame_length += crypto_secretbox_BOXZEROBYTES;

/* Zero out the 16 bytes between the end of the encrypted data and the nonce for backwards compatibility */
memset(packet->frame_begin + packet->frame_length, 0, crypto_secretbox_BOXZEROBYTES);

/* Add nonce at the end of the packet */
memcpy(&msg_out[crypto_secretbox_BOXZEROBYTES + msg_len + crypto_secretbox_KEYBYTES], nonce, NONCE_SIZE);
/* Add nonce at the end of the packet plus 16 bytes for backwards compatibility */
memcpy(packet->frame_begin + (crypto_secretbox_BOXZEROBYTES + packet->frame_length), nonce, NONCE_SIZE);
packet->frame_length += NONCE_SIZE + crypto_secretbox_BOXZEROBYTES;

return msg_len + crypto_secretbox_KEYBYTES + NONCE_SIZE;
return CSP_ERR_NONE;
}

void crypto_init() {
Expand Down
Loading