GCC Code Coverage Report


Directory: ./
File: Svc/Ccsds/AesGcmDecryptor/AesGcmDecryptor.cpp
Date: 2026-09-23 21:11:01
Exec Total Coverage
Lines: 59 61 96.7%
Functions: 4 4 100.0%
Branches: 47 57 82.5%

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 16 AesGcmDecryptor ::AesGcmDecryptor(const char* const compName)
31 : AesGcmDecryptorComponentBase(compName),
32 16 m_cipher(nullptr),
33 16 m_ctx(nullptr),
34 16 m_aad(0, 0),
35 16 m_aadVcId(0),
36 32 m_aadSaIndex(0) {
37
1/1
✓ Branch 3 taken 16 times.
16 this->m_cipher = EVP_CIPHER_fetch(nullptr, "AES-256-GCM", nullptr);
38 16 FW_ASSERT(this->m_cipher != nullptr);
39
1/1
✓ Branch 3 taken 16 times.
16 this->m_ctx = EVP_CIPHER_CTX_new();
40 16 FW_ASSERT(this->m_ctx != nullptr);
41
1/1
✓ Branch 9 taken 16 times.
16 int status = EVP_DecryptInit_ex(this->m_ctx, this->m_cipher, nullptr, nullptr, nullptr);
42 16 FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
43
1/1
✓ Branch 5 taken 16 times.
16 status = EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(GCM_IV_LEN), nullptr);
44 16 FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
45 16 }
46
47 32 AesGcmDecryptor ::~AesGcmDecryptor() {
48 32 EVP_CIPHER_CTX_free(this->m_ctx);
49 32 EVP_CIPHER_free(this->m_cipher);
50 32 }
51
52 // ----------------------------------------------------------------------
53 // Handler implementations for typed input ports
54 // ----------------------------------------------------------------------
55
56 16 void AesGcmDecryptor ::decryptIn_handler(FwIndexType portNum,
57 U16 securityAssociationIndex,
58 Fw::Buffer& data,
59 const ComCfg::FrameContext& context) {
60 16 FW_ASSERT(this->m_ctx != nullptr);
61
62 // Layout after CcsdsSdlsDeframer strips the SPI: IV (12) | ciphertext (N) | MAC (16).
63
64
3/3
✓ Branch 4 taken 16 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 15 times.
16 if (data.getSize() < GCM_IV_LEN + GCM_TAG_LEN) {
65 // Too short to hold an IV and a MAC
66
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::DECRYPTION_FAILURE, data, context);
67 1 return;
68 }
69
70
1/1
✓ Branch 2 taken 15 times.
15 Svc::Ccsds::SdlsKeyBuffer key;
71
1/1
✓ Branch 3 taken 15 times.
15 const Svc::Ccsds::SdlsStatus keyStatus = this->keyGet_out(0, securityAssociationIndex, key);
72
7/7
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 1 times.
✓ Branch 6 taken 14 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 13 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 13 times.
15 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
2/2
✓ Branch 6 taken 2 times.
✓ Branch 10 taken 2 times.
2 this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::KEY_ERROR, data, context);
75 2 return;
76 }
77
78
1/1
✓ Branch 4 taken 13 times.
13 U8* const iv = data.getData();
79
1/1
✓ Branch 4 taken 13 times.
13 U8* const ciphertext = data.getData() + GCM_IV_LEN;
80
1/1
✓ Branch 4 taken 13 times.
13 const U32 cipherLen = static_cast<U32>(data.getSize()) - GCM_IV_LEN - GCM_TAG_LEN;
81 13 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 13 const U8 vcId = context.get_vcId();
87
3/4
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 times.
13 if ((vcId != this->m_aadVcId) || (securityAssociationIndex != this->m_aadSaIndex)) {
88 12 this->m_aad = Svc::Ccsds::Utils::SdlsTcAuthMask(vcId, securityAssociationIndex);
89 12 this->m_aadVcId = vcId;
90 12 this->m_aadSaIndex = securityAssociationIndex;
91 }
92
93 13 int len = 0;
94 13 int plainLen = 0;
95
96
1/1
✓ Branch 7 taken 13 times.
13 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
1/1
✓ Branch 5 taken 13 times.
13 OPENSSL_cleanse(key.getBuffAddr(), key.getCapacity());
100
3/5
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 9 taken 13 times.
✓ Branch 11 taken 13 times.
✗ Branch 12 not taken.
13 const bool aadAbsorbed = rekeyed && (EVP_DecryptUpdate(this->m_ctx, nullptr, &len, this->m_aad.bytes,
101 13 static_cast<int>(sizeof(this->m_aad.bytes))) == 1);
102 const bool decrypted =
103
3/5
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 7 taken 13 times.
✓ Branch 9 taken 13 times.
✗ Branch 10 not taken.
13 aadAbsorbed && (EVP_DecryptUpdate(this->m_ctx, ciphertext, &len, ciphertext, static_cast<int>(cipherLen)) == 1);
104 const bool tagSet =
105
3/5
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 7 taken 13 times.
✓ Branch 9 taken 13 times.
✗ Branch 10 not taken.
13 decrypted && (EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(GCM_TAG_LEN), tag) == 1);
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!tagSet) {
107 ✗ this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::DECRYPTION_FAILURE, data, context);
108 ✗ return;
109 }
110 13 plainLen = len;
111
112 // Verify the MAC
113
3/3
✓ Branch 6 taken 13 times.
✓ Branch 8 taken 7 times.
✓ Branch 9 taken 6 times.
13 if (EVP_DecryptFinal_ex(this->m_ctx, ciphertext + plainLen, &len) != 1) {
114
2/2
✓ Branch 6 taken 7 times.
✓ Branch 10 taken 7 times.
7 this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::MAC_VERIFICATION_FAILURE, data, context);
115 7 return;
116 }
117 6 FW_ASSERT(len == 0, static_cast<FwAssertArgType>(len));
118
119 // Move to the plaintext
120
1/1
✓ Branch 4 taken 6 times.
6 data.advance(static_cast<FwSignedSizeType>(GCM_IV_LEN));
121
1/1
✓ Branch 4 taken 6 times.
6 data.setSize(static_cast<Fw::Buffer::SizeType>(plainLen));
122
2/2
✓ Branch 6 taken 6 times.
✓ Branch 10 taken 6 times.
6 this->decryptOut_out(0, Svc::Ccsds::SdlsStatus::SUCCESS, data, context);
123 24 }
124
125 1 void AesGcmDecryptor ::decryptReturnIn_handler(FwIndexType portNum,
126 Fw::Buffer& data,
127 const ComCfg::FrameContext& context) {
128 1 this->bufferReturnOut_out(0, data, context);
129 1 }
130
131 } // namespace Ccsds
132
133 } // namespace Svc
134