| 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 |