F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
AesGcmEncryptor.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title AesGcmEncryptor.cpp
3 // \author cadena and claradavisb
4 // \brief cpp file for AesGcmEncryptor component implementation class
5 // ======================================================================
6 
9 
10 #include <openssl/crypto.h>
11 #include <openssl/evp.h>
12 #include <openssl/rand.h>
13 
14 namespace Svc {
15 
16 namespace Ccsds {
17 
19 static constexpr U32 GCM_IV_LEN = 12;
21 static constexpr U32 GCM_TAG_LEN = 16;
23 static constexpr FwSizeType AES_256_KEY_LEN = 32;
24 
25 // ----------------------------------------------------------------------
26 // Component construction and destruction
27 // ----------------------------------------------------------------------
28 
29 // Build the cipher state once so that encrypting a frame allocates nothing.
30 // Only the key and the IV change, and those are supplied per
31 // frame by a single EVP_EncryptInit_ex.
32 AesGcmEncryptor ::AesGcmEncryptor(const char* const compName)
33  : AesGcmEncryptorComponentBase(compName),
34  m_outBuf(),
35  m_bufferState(BufferOwnershipState::OWNED),
36  m_cipher(nullptr),
37  m_ctx(nullptr),
38  m_aad(0, 0),
39  m_aadVcId(0),
40  m_aadSaIndex(0) {
41  this->m_cipher = EVP_CIPHER_fetch(nullptr, "AES-256-GCM", nullptr);
42  FW_ASSERT(this->m_cipher != nullptr);
43  this->m_ctx = EVP_CIPHER_CTX_new();
44  FW_ASSERT(this->m_ctx != nullptr);
45 
46  int status = EVP_EncryptInit_ex(this->m_ctx, this->m_cipher, nullptr, nullptr, nullptr);
47  FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
48  status = EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(GCM_IV_LEN), nullptr);
49  FW_ASSERT(status == 1, static_cast<FwAssertArgType>(status));
50 }
51 
53  EVP_CIPHER_CTX_free(this->m_ctx);
54  EVP_CIPHER_free(this->m_cipher);
55 }
56 
57 // ----------------------------------------------------------------------
58 // Handler implementations for typed input ports
59 // ----------------------------------------------------------------------
60 
61 void AesGcmEncryptor ::encryptIn_handler(FwIndexType portNum,
62  U16 securityAssociationIndex,
63  Fw::Buffer& data,
64  const ComCfg::FrameContext& context) {
65  FW_ASSERT(this->m_ctx != nullptr);
66 
67  // The previous frame is still downstream and m_outBuf holds ciphertext that has not been
68  // sent. Overwriting it would substitute this frame's contents into the one already in
69  // flight, and the MAC would cover the substitution, so the ground would accept it. Drop
70  // this frame instead
71  if (this->m_bufferState != BufferOwnershipState::OWNED) {
72  this->log_WARNING_HI_OutputBufferBusy();
73  this->failFrame(data, context, Svc::Ccsds::SdlsStatus::ENCRYPTION_FAILURE);
74  return;
75  }
76 
77  // m_outBuf layout: IV (12) | ciphertext (N) | MAC (16)
78  const FwSizeType requiredSize = static_cast<FwSizeType>(data.getSize()) + GCM_IV_LEN + GCM_TAG_LEN;
79  if (requiredSize > MAX_OUTPUT_SIZE) {
80  this->failFrame(data, context, Svc::Ccsds::SdlsStatus::ENCRYPTION_FAILURE);
81  return;
82  }
83 
85  const Svc::Ccsds::SdlsStatus keyStatus = this->keyGet_out(0, securityAssociationIndex, key);
86  if ((keyStatus != Svc::Ccsds::SdlsStatus::SUCCESS) || (key.getSize() != AES_256_KEY_LEN)) {
87  this->failFrame(data, context, Svc::Ccsds::SdlsStatus::KEY_ERROR);
88  return;
89  }
90 
91  U8* const iv = this->m_outBuf;
92  U8* const ciphertext = this->m_outBuf + GCM_IV_LEN;
93 
94  // A repeated IV under one key breaks GCM, so this generates a new one per frame
95  if (RAND_bytes(iv, static_cast<int>(GCM_IV_LEN)) != 1) {
96  this->failFrame(data, context, Svc::Ccsds::SdlsStatus::ENCRYPTION_FAILURE);
97  return;
98  }
99 
100  int len = 0;
101  int cipherLen = 0;
102  // The AAD mask depends only on the VC and the SA, so it is rebuilt when either changes
103  const U8 vcId = context.get_vcId();
104  if ((vcId != this->m_aadVcId) || (securityAssociationIndex != this->m_aadSaIndex)) {
105  this->m_aad = Svc::Ccsds::Utils::SdlsTmAuthMask(vcId, securityAssociationIndex);
106  this->m_aadVcId = vcId;
107  this->m_aadSaIndex = securityAssociationIndex;
108  }
109 
110  // Re-keying the context the constructor built. Passing a null cipher here reuses the
111  // algorithm and IV length already set, which is what keeps this path allocation-free.
112  // The first EVP_EncryptUpdate has a null output, so its input is absorbed as AAD; it must precede the plaintext
113  bool encryptSucceeded =
114  (EVP_EncryptInit_ex(this->m_ctx, nullptr, nullptr, key.getBuffAddr(), iv) == 1) &&
115  (EVP_EncryptUpdate(this->m_ctx, nullptr, &len, this->m_aad.bytes,
116  static_cast<int>(sizeof(this->m_aad.bytes))) == 1) &&
117  (EVP_EncryptUpdate(this->m_ctx, ciphertext, &len, data.getData(), static_cast<int>(data.getSize())) == 1);
118  // The cipher context holds the key schedule now, so the stack copy is dead.
119  // OPENSSL_cleanse wipes the stack copy to prevent it from being recovered by a memory dump or other attack.
120  OPENSSL_cleanse(key.getBuffAddr(), key.getCapacity());
121  if (encryptSucceeded) {
122  cipherLen = len;
123  encryptSucceeded = (EVP_EncryptFinal_ex(this->m_ctx, ciphertext + cipherLen, &len) == 1);
124  }
125  if (encryptSucceeded) {
126  cipherLen += len;
127  // The MAC is written straight into its place in m_outBuf, after the ciphertext
128  encryptSucceeded = (EVP_CIPHER_CTX_ctrl(this->m_ctx, EVP_CTRL_GCM_GET_TAG, static_cast<int>(GCM_TAG_LEN),
129  ciphertext + cipherLen) == 1);
130  }
131 
132  if (!encryptSucceeded) {
133  this->failFrame(data, context, Svc::Ccsds::SdlsStatus::ENCRYPTION_FAILURE);
134  return;
135  }
136 
137  const U32 outLen = GCM_IV_LEN + static_cast<U32>(cipherLen) + GCM_TAG_LEN;
138  Fw::Buffer cipherBuf(this->m_outBuf, outLen);
139 
140  // Marked in flight before it is emitted,
141  // so that a subsequent frame arriving before the return of this one is dropped
142  this->m_bufferState = BufferOwnershipState::NOT_OWNED;
143 
144  // The plaintext has been copied; hand it back before passing the ciphertext downstream
145  this->bufferReturnOut_out(0, data, context);
146  this->encryptOut_out(0, Svc::Ccsds::SdlsStatus::SUCCESS, cipherBuf, context);
147 }
148 
149 void AesGcmEncryptor ::encryptReturnIn_handler(FwIndexType portNum,
150  Fw::Buffer& data,
151  const ComCfg::FrameContext& context) {
152  // A failed frame reported its status with an empty buffer
153  if (!data.isValid()) {
154  return;
155  }
156  // Only m_outBuf is ever emitted on encryptOut, so anything else is a wiring error
157  FW_ASSERT(data.getData() == this->m_outBuf);
158  FW_ASSERT(this->m_bufferState == BufferOwnershipState::NOT_OWNED,
159  static_cast<FwAssertArgType>(this->m_bufferState));
160  this->m_bufferState = BufferOwnershipState::OWNED;
161 }
162 
163 // ----------------------------------------------------------------------
164 // Helpers
165 // ----------------------------------------------------------------------
166 
167 void AesGcmEncryptor ::failFrame(Fw::Buffer& data, const ComCfg::FrameContext& context, Svc::Ccsds::SdlsStatus status) {
168  // Return the plaintext to its sender
169  this->bufferReturnOut_out(0, data, context);
170  // Svc::Ccsds::CcsdsSdlsFramer still needs a status on encryptIn to release the com status
171  // So a failed frame reports one carrying no data rather than nothing.
172  Fw::Buffer empty;
173  this->encryptOut_out(0, status, empty, context);
174 }
175 
176 } // namespace Ccsds
177 
178 } // namespace Svc
AesGcmEncryptor(const char *const compName)
Construct AesGcmEncryptor object.
PlatformSizeType FwSizeType
Serializable::SizeType getSize() const override
Get current buffer size.
U8 * getData() const
Definition: Buffer.cpp:82
static constexpr U32 GCM_IV_LEN
Length of the AES-GCM initialization vector, in bytes.
Serializable::SizeType getCapacity() const override
Get buffer capacity.
Status of an SDLS (Space Data Link Security) encryption/decryption request.
static constexpr FwSizeType AES_256_KEY_LEN
Length of an AES-256 key, in bytes.
U8 * getBuffAddr()
Get buffer address for data filling (non-const version)
U8 get_vcId() const
Get member vcId.
bool isValid() const
Definition: Buffer.cpp:78
~AesGcmEncryptor()
Destroy AesGcmEncryptor object.
static constexpr U32 GCM_TAG_LEN
Length of the AES-GCM authentication tag (the SDLS MAC), in bytes.
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
FwSizeType getSize() const
Definition: Buffer.cpp:90
PlatformIndexType FwIndexType
Type used to pass context info between components during framing/deframing.
RateGroupDivider component implementation.
static constexpr FwSizeType MAX_OUTPUT_SIZE
Largest output frame the downstream chain carries: the TM data field less the 2-byte SA index...
Request completed successfully.
#define FW_ASSERT(...)
Definition: Assert.hpp:14