Skip to content
Draft
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
env:
AUTH_KEY: ${{ secrets.AUTH_KEY }}
run: |
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h

- name: Build MCUBoot
run: |
Expand Down Expand Up @@ -158,7 +158,7 @@ jobs:
env:
AUTH_KEY: ${{ secrets.AUTH_KEY }}
run: |
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h

- name: Install Framer Plugin
run: |
Expand Down Expand Up @@ -417,7 +417,7 @@ jobs:
env:
AUTH_KEY: ${{ secrets.AUTH_KEY }}
run: |
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h
echo "#define AUTH_DEFAULT_KEY \"$AUTH_KEY\"" > PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h

- name: Install Framer Plugin
run: |
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ After compiling, upload the sequence through GDS for execution on the board.

### Authentication & Security

Commands can be HMAC-authenticated using the `TcSecurityDeframer` component. The authentication key is stored in `PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h`.
Commands can be HMAC-authenticated using the `TcSecurityDecryptor` component. The authentication key is stored in `PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h`.

```bash
make generate-auth-key # Generate a new random HMAC key (only if file doesn't exist)
Expand Down
4 changes: 1 addition & 3 deletions Framing/src/authenticate_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ def get_default_auth_key_from_header() -> str:
ValueError: If AuthDefaultKey.h does not contain a valid key
IOError: If there is an error reading the file
"""
path = (
"PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h"
)
path = "PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h"

if not os.path.exists(path):
raise FileNotFoundError(
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ docs-sync: ## Sync SDD files from components to docs-site
@cp PROVESFlightControllerReference/Components/FsSpace/docs/sdd.md docs-site/components/FsSpace.md
@cp PROVESFlightControllerReference/Components/NullPrmDb/docs/sdd.md docs-site/components/NullPrmDb.md
@# Copy Security Components
@cp PROVESFlightControllerReference/Components/TcSecurityDeframer/docs/sdd.md docs-site/components/TcSecurityDeframer.md
@cp PROVESFlightControllerReference/Components/TcSecurityDecryptor/docs/sdd.md docs-site/components/TcSecurityDecryptor.md
@cp PROVESFlightControllerReference/Components/ProvesRouter/docs/sdd.md docs-site/components/ProvesRouter.md
@# Copy images
@find PROVESFlightControllerReference -path "*/docs/img/*" -type f -exec cp {} docs-site/components/img/ \; 2>/dev/null || true
Expand Down Expand Up @@ -155,7 +155,7 @@ check-console-disabled: uv ## Fail if the Zephyr UART console is enabled (it cor

##@ Authentication Keys

AUTH_DEFAULT_KEY_HEADER ?= PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h
AUTH_DEFAULT_KEY_HEADER ?= PROVESFlightControllerReference/Components/TcSecurityDecryptor/AuthDefaultKey.h
AUTH_KEY_TEMPLATE ?= scripts/generate_auth_default_key.h

.PHONY: generate-auth-key
Expand Down Expand Up @@ -466,7 +466,7 @@ copy-secrets:
@mkdir -p ./keys/
@cp $(SECRETS_DIR)/proves.pem ./keys/
@cp $(SECRETS_DIR)/proves.pub.pem ./keys/
@cp $(SECRETS_DIR)/AuthDefaultKey.h ./PROVESFlightControllerReference/Components/TcSecurityDeframer/
@cp $(SECRETS_DIR)/AuthDefaultKey.h ./PROVESFlightControllerReference/Components/TcSecurityDecryptor/
@echo "Copied secret files 🤫"

.PHONY: make-ci-spacecraft-id
Expand Down
24 changes: 15 additions & 9 deletions PROVESFlightControllerReference/ComCcsdsLora/ComCcsds.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,17 @@ module ComCcsdsLora {

instance apidManager: Svc.Ccsds.ApidManager base id ComCcsdsConfig.BASE_ID_LORA + 0x09000

instance tcSecurityDeframer: Components.TcSecurityDeframer base id ComCcsdsConfig.BASE_ID_LORA + 0x0B000 \
instance tcSecurityDecryptor: Components.TcSecurityDecryptor base id ComCcsdsConfig.BASE_ID_LORA + 0x0B000 \
{
phase Fpp.ToCpp.Phases.startTasks """
// configure() reads parameters, so it must run after loadParameters();
// the startTasks phase is the first phase after parameters are loaded.
ComCcsdsLora::tcSecurityDeframer.configure();
ComCcsdsLora::tcSecurityDecryptor.configure();
"""
}

instance sdlsDeframer: Svc.Ccsds.CcsdsSdlsDeframer base id ComCcsdsConfig.BASE_ID_LORA + 0x0C000

topology Subtopology {
# Usage Note:
#
Expand Down Expand Up @@ -135,7 +137,8 @@ module ComCcsdsLora {
instance spacePacketFramer
instance apidManager
instance aggregator
instance tcSecurityDeframer
instance tcSecurityDecryptor
instance sdlsDeframer

connections Downlink {
# ComQueue <-> SpacePacketFramer
Expand Down Expand Up @@ -167,12 +170,15 @@ module ComCcsdsLora {
# FrameAccumulator <-> TcDeframer
frameAccumulator.dataOut -> tcDeframer.dataIn
tcDeframer.dataReturnOut -> frameAccumulator.dataReturnIn
# TcSecurityDeframer <-> SpacePacketDeframer
tcSecurityDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> tcSecurityDeframer.dataReturnIn
# TcDeframer <-> TcSecurityDeframer
tcDeframer.dataOut -> tcSecurityDeframer.dataIn
tcSecurityDeframer.dataReturnOut -> tcDeframer.dataReturnIn
# TcDeframer <-> SdlsDeframer <-> SpacePacketDeframer
tcDeframer.dataOut -> sdlsDeframer.dataIn
sdlsDeframer.dataReturnOut -> tcDeframer.dataReturnIn
sdlsDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> sdlsDeframer.dataReturnIn
sdlsDeframer.decryptOut -> tcSecurityDecryptor.decryptIn
tcSecurityDecryptor.decryptOut -> sdlsDeframer.decryptIn
sdlsDeframer.decryptReturnOut -> tcSecurityDecryptor.decryptReturnIn
tcSecurityDecryptor.bufferReturnOut -> sdlsDeframer.bufferReturnIn
# SpacePacketDeframer APID validation
spacePacketDeframer.validateApidSeqCount -> apidManager.validateApidSeqCountIn
# SpacePacketDeframer <-> ProvesRouter (routes both commands and files)
Expand Down
24 changes: 15 additions & 9 deletions PROVESFlightControllerReference/ComCcsdsSband/ComCcsds.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,17 @@ module ComCcsdsSband {

instance apidManager: Svc.Ccsds.ApidManager base id ComCcsdsConfig.BASE_ID_SBAND + 0x09000

instance tcSecurityDeframer: Components.TcSecurityDeframer base id ComCcsdsConfig.BASE_ID_SBAND + 0x0B000 \
instance tcSecurityDecryptor: Components.TcSecurityDecryptor base id ComCcsdsConfig.BASE_ID_SBAND + 0x0B000 \
{
phase Fpp.ToCpp.Phases.startTasks """
// configure() reads parameters, so it must run after loadParameters();
// the startTasks phase is the first phase after parameters are loaded.
ComCcsdsSband::tcSecurityDeframer.configure();
ComCcsdsSband::tcSecurityDecryptor.configure();
"""
}

instance sdlsDeframer: Svc.Ccsds.CcsdsSdlsDeframer base id ComCcsdsConfig.BASE_ID_SBAND + 0x0C000

topology Subtopology {
# Usage Note:
#
Expand Down Expand Up @@ -135,7 +137,8 @@ module ComCcsdsSband {
instance spacePacketFramer
instance apidManager
instance aggregator
instance tcSecurityDeframer
instance tcSecurityDecryptor
instance sdlsDeframer

connections Downlink {
# ComQueue <-> SpacePacketFramer
Expand Down Expand Up @@ -167,12 +170,15 @@ module ComCcsdsSband {
# FrameAccumulator <-> TcDeframer
frameAccumulator.dataOut -> tcDeframer.dataIn
tcDeframer.dataReturnOut -> frameAccumulator.dataReturnIn
# TcSecurityDeframer <-> SpacePacketDeframer
tcSecurityDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> tcSecurityDeframer.dataReturnIn
# TcDeframer <-> TcSecurityDeframer
tcDeframer.dataOut -> tcSecurityDeframer.dataIn
tcSecurityDeframer.dataReturnOut -> tcDeframer.dataReturnIn
# TcDeframer <-> SdlsDeframer <-> SpacePacketDeframer
tcDeframer.dataOut -> sdlsDeframer.dataIn
sdlsDeframer.dataReturnOut -> tcDeframer.dataReturnIn
sdlsDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> sdlsDeframer.dataReturnIn
sdlsDeframer.decryptOut -> tcSecurityDecryptor.decryptIn
tcSecurityDecryptor.decryptOut -> sdlsDeframer.decryptIn
sdlsDeframer.decryptReturnOut -> tcSecurityDecryptor.decryptReturnIn
tcSecurityDecryptor.bufferReturnOut -> sdlsDeframer.bufferReturnIn
# SpacePacketDeframer APID validation
spacePacketDeframer.validateApidSeqCount -> apidManager.validateApidSeqCountIn
# SpacePacketDeframer <-> ProvesRouter (routes both commands and files)
Expand Down
25 changes: 15 additions & 10 deletions PROVESFlightControllerReference/ComCcsdsUart/ComCcsds.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,17 @@ module ComCcsdsUart {

instance comStub: Svc.ComStub base id ComCcsdsConfig.BASE_ID_UART + 0x0A000

instance tcSecurityDeframer: Components.TcSecurityDeframer base id ComCcsdsConfig.BASE_ID_UART + 0x0B000 \
instance tcSecurityDecryptor: Components.TcSecurityDecryptor base id ComCcsdsConfig.BASE_ID_UART + 0x0B000 \
{
phase Fpp.ToCpp.Phases.startTasks """
// configure() reads parameters, so it must run after loadParameters();
// the startTasks phase is the first phase after parameters are loaded.
ComCcsdsUart::tcSecurityDeframer.configure();
ComCcsdsUart::tcSecurityDecryptor.configure();
"""
}

instance sdlsDeframer: Svc.Ccsds.CcsdsSdlsDeframer base id ComCcsdsConfig.BASE_ID_UART + 0x0C000

topology FramingSubtopology {
# Usage Note:
#
Expand Down Expand Up @@ -147,7 +149,8 @@ module ComCcsdsUart {
instance spacePacketFramer
instance apidManager
instance aggregator
instance tcSecurityDeframer
instance tcSecurityDecryptor
instance sdlsDeframer

connections Downlink {
# ComQueue <-> SpacePacketFramer
Expand Down Expand Up @@ -181,13 +184,15 @@ module ComCcsdsUart {
frameAccumulator.dataOut -> tcDeframer.dataIn
tcDeframer.dataReturnOut -> frameAccumulator.dataReturnIn

# TcSecurityDeframer <-> SpacePacketDeframer
tcSecurityDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> tcSecurityDeframer.dataReturnIn

# TcDeframer <-> TcSecurityDeframer
tcDeframer.dataOut -> tcSecurityDeframer.dataIn
tcSecurityDeframer.dataReturnOut -> tcDeframer.dataReturnIn
# TcDeframer <-> SdlsDeframer <-> SpacePacketDeframer
tcDeframer.dataOut -> sdlsDeframer.dataIn
sdlsDeframer.dataReturnOut -> tcDeframer.dataReturnIn
sdlsDeframer.dataOut -> spacePacketDeframer.dataIn
spacePacketDeframer.dataReturnOut -> sdlsDeframer.dataReturnIn
sdlsDeframer.decryptOut -> tcSecurityDecryptor.decryptIn
tcSecurityDecryptor.decryptOut -> sdlsDeframer.decryptIn
sdlsDeframer.decryptReturnOut -> tcSecurityDecryptor.decryptReturnIn
tcSecurityDecryptor.bufferReturnOut -> sdlsDeframer.bufferReturnIn

# SpacePacketDeframer APID validation
spacePacketDeframer.validateApidSeqCount -> apidManager.validateApidSeqCountIn
Expand Down
2 changes: 1 addition & 1 deletion PROVESFlightControllerReference/Components/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PowerMonitor/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ResetManager/")
#add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SBand/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/StartupManager/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TcSecurityDeframer/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TcSecurityDecryptor/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ThermalManager/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Watchdog")
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ FieldParseResult<uint32_t> parseOpCode(const uint8_t* buffer, const size_t size)
//! then convert the opcode field value from decimal to hexadecimal
static constexpr uint32_t kBypassOpCodes[] = {
0x01000000, //!< CdhCore.cmdDisp.CMD_NO_OP
0x2100B000, //!< ComCcsdsUart.tcSecurityDeframer.GET_SEQ_NUM
0x2200B000, //!< ComCcsdsLora.tcSecurityDeframer.GET_SEQ_NUM
0x2300B000, //!< ComCcsdsSband.tcSecurityDeframer.GET_SEQ_NUM
0x2100B000, //!< ComCcsdsUart.tcSecurityDecryptor.GET_SEQ_NUM
0x2200B000, //!< ComCcsdsLora.tcSecurityDecryptor.GET_SEQ_NUM
0x2300B000, //!< ComCcsdsSband.tcSecurityDecryptor.GET_SEQ_NUM
0x10065000, //!< ReferenceDeployment.amateurRadio.TELL_JOKE
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ The `Svc::ProvesRouter` component supports `Fw::ComPacketType::FW_PACKET_COMMAND

## Security Policy Enforcement

`Svc::ProvesRouter` is the policy point of the uplink security design. Upstream, `Components::TcSecurityDeframer` verifies each frame (SPI, anti-replay sequence number, MAC) and records the result in the `ComCfg::FrameContext` `authenticated` flag without dropping anything. The router then enforces:
`Svc::ProvesRouter` is the policy point of the uplink security design. Upstream, `Components::TcSecurityDecryptor` (a decryptor behind the upstream `Svc::Ccsds::CcsdsSdlsDeframer`) verifies each frame (security association index, anti-replay sequence number, MAC) and records the result in the `ComCfg::FrameContext` `authenticated` flag without dropping anything. The router then enforces:

- Packets with `authenticated == true` are routed normally.
- Unauthenticated packets are routed only if their opcode is on the hardcoded bypass allowlist (`Components::PacketBypasser::bypassPacket` in `Bypasser.cpp`), which permits public commands such as `CMD_NO_OP`, `GET_SEQ_NUM`, and `TELL_JOKE`.
- All other unauthenticated packets are rejected: ownership is returned via `dataReturnOut` and the packet is not routed.

Because bypassed packets never pass through the authenticated-accept path in TcSecurityDeframer, they cannot advance the anti-replay sequence number.
Because bypassed packets never pass through the authenticated-accept path in TcSecurityDecryptor, they cannot advance the anti-replay sequence number.

About memory management, all buffers sent by `Svc::ProvesRouter` on the `fileOut` and `unknownDataOut` ports are expected to be returned to the router through the `fileBufferReturnIn` port for deallocation.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,44 @@ PacketAuthenticator::KeyImportResult importHmacKey(const char* key, uint32_t& ke
return {PacketAuthenticator::KeyImportStatus::Success, PSA_SUCCESS};
}

PacketAuthenticator::AuthenticationResult authenticatePacket(const uint8_t* dataBuffer,
size_t dataSize,
const Mac& hmac,
uint32_t& keyId) {
PacketAuthenticator::AuthenticationResult authenticateFrame(uint16_t saIndex,
const uint8_t* dataBuffer,
size_t dataSize,
const Mac& hmac,
uint32_t& keyId) {
// Basic input validation: buffer present and at least trailer-sized
if (!dataBuffer || dataSize < Ccsds355_0_B_2::kTCSecurityTrailer) {
return {PacketAuthenticator::AuthenticationStatus::VerifyError, PSA_ERROR_INVALID_ARGUMENT};
}

// Verify the HMAC on the packet data
// The MAC covers the SA index (stripped from the buffer by the upstream deframer) followed by
// the frame data, so it must be verified via the PSA multipart API rather than a single call.
const size_t authenticatedDataSize = dataSize - Ccsds355_0_B_2::kTCSecurityTrailer;
const psa_status_t status =
psa_mac_verify(keyId, PSA_ALG_TRUNCATED_MAC(PSA_ALG_HMAC(PSA_ALG_SHA_256), Ccsds355_0_B_2::kTCSecurityTrailer),
dataBuffer, authenticatedDataSize, hmac.data(), hmac.size());
const uint8_t saIndexBytes[2] = {static_cast<uint8_t>(saIndex >> 8), static_cast<uint8_t>(saIndex & 0xFF)};

psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT;
psa_status_t status = psa_mac_verify_setup(
&operation, keyId, PSA_ALG_TRUNCATED_MAC(PSA_ALG_HMAC(PSA_ALG_SHA_256), Ccsds355_0_B_2::kTCSecurityTrailer));
if (status != PSA_SUCCESS) {
return {PacketAuthenticator::AuthenticationStatus::VerifyError, status};
}

status = psa_mac_update(&operation, saIndexBytes, sizeof(saIndexBytes));
if (status != PSA_SUCCESS) {
psa_mac_abort(&operation);
return {PacketAuthenticator::AuthenticationStatus::VerifyError, status};
}

status = psa_mac_update(&operation, dataBuffer, authenticatedDataSize);
if (status != PSA_SUCCESS) {
psa_mac_abort(&operation);
return {PacketAuthenticator::AuthenticationStatus::VerifyError, status};
}

// psa_mac_verify_finish aborts the operation internally on both success and failure
status = psa_mac_verify_finish(&operation, hmac.data(), hmac.size());
if (status != PSA_SUCCESS) {
return PacketAuthenticator::AuthenticationResult{PacketAuthenticator::AuthenticationStatus::VerifyError,
status};
return {PacketAuthenticator::AuthenticationStatus::VerifyError, status};
}

return {PacketAuthenticator::AuthenticationStatus::Authenticated, PSA_SUCCESS};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ PacketAuthenticator::KeyImportResult importHmacKey(const char* key, //!< The he
uint32_t& keyId //!< The key ID to use for the imported key
);

//! Check the validity of the packet HMAC
PacketAuthenticator::AuthenticationResult authenticatePacket(
const uint8_t* buffer, //!< The packet data buffer
//! Check the validity of the frame HMAC. The MAC covers the security association index (2 bytes,
//! big-endian) followed by the frame data; the SA index is supplied separately because the upstream
//! Svc.Ccsds.CcsdsSdlsDeframer has already stripped it out of the buffer.
PacketAuthenticator::AuthenticationResult authenticateFrame(
uint16_t saIndex, //!< The security association index (participates in the MAC but not in the buffer)
const uint8_t* buffer, //!< The frame data buffer
size_t size, //!< The size of the data buffer
const Mac& hmac, //!< The HMAC extracted from the packet to validate against
const Mac& hmac, //!< The HMAC extracted from the frame to validate against
uint32_t& keyId //!< The hex-encoded authentication key to use for validation
);

Expand Down
Loading
Loading