GCC Code Coverage Report


Directory: /home/runner/work/fprime/fprime/Svc/Ccsds/AesGcmDecryptor/
File: AesGcmDecryptor.cpp
Date: 2026-09-23 22:12:33
Exec Total Coverage
Lines: 0 61 0.0%
Functions: 0 4 0.0%
Branches: 0 57 0.0%

Line Branch Exec Source
1 // ======================================================================
2 // \title AesGcmDecryptor.cpp
3 // \author cadena and claradavisb
4 // \brief cpp file for AesGcmDecryptor component implementation class
5 // ======================================================================
6
7 #include "Svc/Ccsds/AesGcmDecryptor/AesGcmDecryptor.hpp"
8 #include <openssl/crypto.h>
9 #include <openssl/evp.h>
10 #include "Svc/Ccsds/Utils/SdlsAuthMask.hpp"
11
12 namespace Svc {
13
14 namespace Ccsds {
15
16 // ----------------------------------------------------------------------
17 // Component construction and destruction
18 // ----------------------------------------------------------------------
19
20 //! Length of the AES-GCM initialization vector, in bytes
21 static constexpr U32 GCM_IV_LEN = 12;
22 //! Length of the AES-GCM authentication tag (the SDLS MAC), in bytes
23 static constexpr U32 GCM_TAG_LEN = 16;
24 //! Length of an AES-256 key, in bytes
25 static constexpr FwSizeType AES_256_KEY_LEN = 32;
26
27 // Build the cipher state once so that decrypting a frame allocates nothing.
28 // Only the key and the IV change and those are supplied per
29 // frame by a single EVP_DecryptInit_ex.
30 ✗ AesGcmDecryptor ::AesGcmDecryptor(const char* const compName)
31 : AesGcmDecryptorComponentBase(compName),
32 ✗ m_cipher(nullptr),
33 ✗ m_ctx(nullptr),
34 ✗ m_aad(0, 0),
35 ✗ m_aadVcId(0),
36 ✗ m_aadSaIndex(0) {
37 ✗ this->m_cipher = EVP_CIPHER_fetch(nullptr, "AES-256-GCM", nullptr);
38 ✗ FW_ASSERT(this->m_cipher != nullptr);
39 ✗ this->m_ctx = EVP_CIPHER_CTX_new();
40 ✗ FW_ASSERT(this->m_ctx != nullptr);
41 ✗ int status = EVP_DecryptInit_ex(this->m_ctx, this->m_cipher, nullptr, nullptr, nullptr);
42 ✗ FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
43 ✗ status = EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(GCM_IV_LEN), nullptr);
44 ✗ FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
45 ✗ }
46
47 ✗ AesGcmDecryptor ::~AesGcmDecryptor() {
48 ✗ EVP_CIPHER_CTX_free(this->m_ctx);
49 ✗ EVP_CIPHER_free(this->m_cipher);
50 ✗ }
51
52 // ----------------------------------------------------------------------
53 // Handler implementations for typed input ports
54 // ----------------------------------------------------------------------
55
56 ✗ void AesGcmDecryptor ::decryptIn_handler(FwIndexType portNum,
57 U16 securityAssociationIndex,
58 Fw::Buffer& data,
59 const ComCfg::FrameContext& context) {
60 ✗ FW_ASSERT(this->m_ctx != nullptr);
61
62 // Layout after CcsdsSdlsDeframer strips the SPI: IV (12) | ciphertext (N) | MAC (16).
63
64 ✗ if (data.getSize() < GCM_IV_LEN + GCM_TAG_LEN) {
65 // Too short to hold an IV and a MAC
66 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::DECRYPTION_FAILURE, data, context);
67 ✗ return;
68 }
69
70 ✗ Svc::Ccsds::SdlsKeyBuffer key;
71 ✗ const Svc::Ccsds::SdlsStatus keyStatus = this->keyGet_out(0, securityAssociationIndex, key);
72 ✗ if ((keyStatus != Svc::Ccsds::SdlsStatus::SUCCESS) || (key.getSize() != AES_256_KEY_LEN)) {
73 // A wrong-sized key would decrypt under the wrong material rather than failing
74 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::KEY_ERROR, data, context);
75 ✗ return;
76 }
77
78 ✗ U8* const iv = data.getData();
79 ✗ U8* const ciphertext = data.getData() + GCM_IV_LEN;
80 ✗ const U32 cipherLen = static_cast<U32>(data.getSize()) - GCM_IV_LEN - GCM_TAG_LEN;
81 ✗ U8* const tag = ciphertext + cipherLen;
82
83 // Authenticated but not encrypted; the VC travels in the context
84 // The mask depends only on the VC and the SA, so it is rebuilt when either changes
85 // rather than per frame
86 ✗ const U8 vcId = context.get_vcId();
87 ✗ if ((vcId != this->m_aadVcId) || (securityAssociationIndex != this->m_aadSaIndex)) {
88 ✗ this->m_aad = Svc::Ccsds::Utils::SdlsTcAuthMask(vcId, securityAssociationIndex);
89 ✗ this->m_aadVcId = vcId;
90 ✗ this->m_aadSaIndex = securityAssociationIndex;
91 }
92
93 ✗ int len = 0;
94 ✗ int plainLen = 0;
95
96 ✗ const bool rekeyed = EVP_DecryptInit_ex(this->m_ctx, nullptr, nullptr, key.getBuffAddr(), iv) == 1;
97 // The cipher context holds the key schedule now, so the stack copy is dead.
98 // OPENSSL_cleanse wipes it so the key cannot be recovered from a memory dump.
99 ✗ OPENSSL_cleanse(key.getBuffAddr(), key.getCapacity());
100 ✗ const bool aadAbsorbed = rekeyed && (EVP_DecryptUpdate(this->m_ctx, nullptr, &len, this->m_aad.bytes,
101 ✗ static_cast<int>(sizeof(this->m_aad.bytes))) == 1);
102 const bool decrypted =
103 ✗ aadAbsorbed && (EVP_DecryptUpdate(this->m_ctx, ciphertext, &len, ciphertext, static_cast<int>(cipherLen)) == 1);
104 const bool tagSet =
105 ✗ decrypted && (EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(GCM_TAG_LEN), tag) == 1);
106 ✗ if (!tagSet) {
107 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::DECRYPTION_FAILURE, data, context);
108 ✗ return;
109 }
110 ✗ plainLen = len;
111
112 // Verify the MAC
113 ✗ if (EVP_DecryptFinal_ex(this->m_ctx, ciphertext + plainLen, &len) != 1) {
114 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::MAC_VERIFICATION_FAILURE, data, context);
115 ✗ return;
116 }
117 ✗ FW_ASSERT(len == 0, static_cast<FwAssertArgType>(len));
118
119 // Move to the plaintext
120 ✗ data.advance(static_cast<FwSignedSizeType>(GCM_IV_LEN));
121 ✗ data.setSize(static_cast<Fw::Buffer::SizeType>(plainLen));
122 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::SUCCESS, data, context);
123 ✗ }
124
125 ✗ void AesGcmDecryptor ::decryptReturnIn_handler(FwIndexType portNum,
126 Fw::Buffer& data,
127 const ComCfg::FrameContext& context) {
128 ✗ this->bufferReturnOut_out(0, data, context);
129 ✗ }
130
131 } // namespace Ccsds
132
133 } // namespace Svc
134