19e801a2bSVernon Mauery #include "crypt_algo.hpp"
29e801a2bSVernon Mauery
39e801a2bSVernon Mauery #include "message_parsers.hpp"
49e801a2bSVernon Mauery
5d08b5235STom Joseph #include <openssl/evp.h>
6d08b5235STom Joseph #include <openssl/hmac.h>
7d08b5235STom Joseph #include <openssl/rand.h>
89e801a2bSVernon Mauery
9518ecceaSTom Joseph #include <algorithm>
10d08b5235STom Joseph #include <numeric>
11d08b5235STom Joseph
12d08b5235STom Joseph namespace cipher
13d08b5235STom Joseph {
14d08b5235STom Joseph
15d08b5235STom Joseph namespace crypt
16d08b5235STom Joseph {
17d08b5235STom Joseph
18518ecceaSTom Joseph constexpr std::array<uint8_t, AlgoAES128::AESCBC128BlockSize - 1>
19518ecceaSTom Joseph AlgoAES128::confPadBytes;
20518ecceaSTom Joseph
decryptPayload(const std::vector<uint8_t> & packet,const size_t sessHeaderLen,const size_t payloadLen) const21*8425624aSPatrick Williams std::vector<uint8_t> AlgoAES128::decryptPayload(
22*8425624aSPatrick Williams const std::vector<uint8_t>& packet, const size_t sessHeaderLen,
23518ecceaSTom Joseph const size_t payloadLen) const
24518ecceaSTom Joseph {
252b1edef0SZhikui Ren // verify packet size minimal: sessHeaderLen + payloadLen
262b1edef0SZhikui Ren // and payloadLen is more than AESCBC128ConfHeader
272b1edef0SZhikui Ren if (packet.size() < (sessHeaderLen + payloadLen) ||
282b1edef0SZhikui Ren payloadLen < AESCBC128ConfHeader)
292b1edef0SZhikui Ren {
302b1edef0SZhikui Ren throw std::runtime_error("Invalid data length");
312b1edef0SZhikui Ren }
322b1edef0SZhikui Ren
339e801a2bSVernon Mauery auto plainPayload =
349e801a2bSVernon Mauery decryptData(packet.data() + sessHeaderLen,
35518ecceaSTom Joseph packet.data() + sessHeaderLen + AESCBC128ConfHeader,
36518ecceaSTom Joseph payloadLen - AESCBC128ConfHeader);
37518ecceaSTom Joseph
38518ecceaSTom Joseph /*
39518ecceaSTom Joseph * The confidentiality pad length is the last byte in the payload, it would
40518ecceaSTom Joseph * tell the number of pad bytes in the payload. We added a condition, so
4162ec622eSGunnar Mills * that buffer overrun doesn't happen.
42518ecceaSTom Joseph */
43518ecceaSTom Joseph size_t confPadLength = plainPayload.back();
44518ecceaSTom Joseph auto padLength = std::min(plainPayload.size() - 1, confPadLength);
45518ecceaSTom Joseph
46518ecceaSTom Joseph auto plainPayloadLen = plainPayload.size() - padLength - 1;
47518ecceaSTom Joseph
48518ecceaSTom Joseph // Additional check if the confidentiality pad bytes are as expected
49518ecceaSTom Joseph if (!std::equal(plainPayload.begin() + plainPayloadLen,
50518ecceaSTom Joseph plainPayload.begin() + plainPayloadLen + padLength,
51518ecceaSTom Joseph confPadBytes.begin()))
52518ecceaSTom Joseph {
53518ecceaSTom Joseph throw std::runtime_error("Confidentiality pad bytes check failed");
54518ecceaSTom Joseph }
55518ecceaSTom Joseph
56518ecceaSTom Joseph plainPayload.resize(plainPayloadLen);
57518ecceaSTom Joseph
58518ecceaSTom Joseph return plainPayload;
59518ecceaSTom Joseph }
60518ecceaSTom Joseph
619e801a2bSVernon Mauery std::vector<uint8_t>
encryptPayload(std::vector<uint8_t> & payload) const629e801a2bSVernon Mauery AlgoAES128::encryptPayload(std::vector<uint8_t>& payload) const
63518ecceaSTom Joseph {
64518ecceaSTom Joseph auto payloadLen = payload.size();
65518ecceaSTom Joseph
66518ecceaSTom Joseph /*
67518ecceaSTom Joseph * The following logic calculates the number of padding bytes to be added to
68518ecceaSTom Joseph * the payload data. This would ensure that the length is a multiple of the
69518ecceaSTom Joseph * block size of algorithm being used. For the AES algorithm, the block size
70518ecceaSTom Joseph * is 16 bytes.
71518ecceaSTom Joseph */
72518ecceaSTom Joseph auto paddingLen = AESCBC128BlockSize - ((payloadLen + 1) & 0xF);
73518ecceaSTom Joseph
74518ecceaSTom Joseph /*
75518ecceaSTom Joseph * The additional field is for the Confidentiality Pad Length field. For the
76518ecceaSTom Joseph * AES algorithm, this number will range from 0 to 15 bytes. This field is
77518ecceaSTom Joseph * mandatory.
78518ecceaSTom Joseph */
79518ecceaSTom Joseph payload.resize(payloadLen + paddingLen + 1);
80518ecceaSTom Joseph
81518ecceaSTom Joseph /*
82518ecceaSTom Joseph * If no Confidentiality Pad bytes are required, the Confidentiality Pad
83518ecceaSTom Joseph * Length field is set to 00h. If present, the value of the first byte of
84518ecceaSTom Joseph * Confidentiality Pad shall be one (01h) and all subsequent bytes shall
85518ecceaSTom Joseph * have a monotonically increasing value (e.g., 02h, 03h, 04h, etc).
86518ecceaSTom Joseph */
87518ecceaSTom Joseph if (0 != paddingLen)
88518ecceaSTom Joseph {
89518ecceaSTom Joseph std::iota(payload.begin() + payloadLen,
909e801a2bSVernon Mauery payload.begin() + payloadLen + paddingLen, 1);
91518ecceaSTom Joseph }
92518ecceaSTom Joseph
93518ecceaSTom Joseph payload.back() = paddingLen;
94518ecceaSTom Joseph
95518ecceaSTom Joseph return encryptData(payload.data(), payload.size());
96518ecceaSTom Joseph }
97518ecceaSTom Joseph
decryptData(const uint8_t * iv,const uint8_t * input,const int inputLen) const98*8425624aSPatrick Williams std::vector<uint8_t> AlgoAES128::decryptData(
99*8425624aSPatrick Williams const uint8_t* iv, const uint8_t* input, const int inputLen) const
100518ecceaSTom Joseph {
101518ecceaSTom Joseph // Initializes Cipher context
102584fa887SAdriana Kobylak EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
103518ecceaSTom Joseph
1049e801a2bSVernon Mauery auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
105f6c97e11STom Joseph
106*8425624aSPatrick Williams std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(
107*8425624aSPatrick Williams ctx, cleanupFunc);
108f6c97e11STom Joseph
109518ecceaSTom Joseph /*
110518ecceaSTom Joseph * EVP_DecryptInit_ex sets up cipher context ctx for encryption with type
111518ecceaSTom Joseph * AES-CBC-128. ctx must be initialized before calling this function. K2 is
112518ecceaSTom Joseph * the symmetric key used and iv is the initialization vector used.
113518ecceaSTom Joseph */
114f6c97e11STom Joseph if (!EVP_DecryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
115f6c97e11STom Joseph iv))
116518ecceaSTom Joseph {
117518ecceaSTom Joseph throw std::runtime_error("EVP_DecryptInit_ex failed for type "
118518ecceaSTom Joseph "AES-CBC-128");
119518ecceaSTom Joseph }
120518ecceaSTom Joseph
121518ecceaSTom Joseph /*
122518ecceaSTom Joseph * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
123518ecceaSTom Joseph * parameter is zero then no padding is performed. This function always
124518ecceaSTom Joseph * returns 1.
125518ecceaSTom Joseph */
126f6c97e11STom Joseph EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
127518ecceaSTom Joseph
12870fd29cfSVernon Mauery std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
129518ecceaSTom Joseph
130518ecceaSTom Joseph int outputLen = 0;
131518ecceaSTom Joseph
132518ecceaSTom Joseph /*
133518ecceaSTom Joseph * If padding is disabled then EVP_DecryptFinal_ex() will not encrypt any
134518ecceaSTom Joseph * more data and it will return an error if any data remains in a partial
135518ecceaSTom Joseph * block: that is if the total data length is not a multiple of the block
136518ecceaSTom Joseph * size. Since AES-CBC-128 encrypted payload format adds padding bytes and
137518ecceaSTom Joseph * ensures that payload is a multiple of block size, we are not making the
138518ecceaSTom Joseph * call to EVP_DecryptFinal_ex().
139518ecceaSTom Joseph */
140f6c97e11STom Joseph if (!EVP_DecryptUpdate(ctxPtr.get(), output.data(), &outputLen, input,
141f6c97e11STom Joseph inputLen))
142518ecceaSTom Joseph {
143518ecceaSTom Joseph throw std::runtime_error("EVP_DecryptUpdate failed");
144518ecceaSTom Joseph }
145518ecceaSTom Joseph
146518ecceaSTom Joseph output.resize(outputLen);
147518ecceaSTom Joseph
148518ecceaSTom Joseph return output;
149518ecceaSTom Joseph }
150518ecceaSTom Joseph
151*8425624aSPatrick Williams std::vector<uint8_t>
encryptData(const uint8_t * input,const int inputLen) const152*8425624aSPatrick Williams AlgoAES128::encryptData(const uint8_t* input, const int inputLen) const
153518ecceaSTom Joseph {
15470fd29cfSVernon Mauery std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
155518ecceaSTom Joseph
156518ecceaSTom Joseph // Generate the initialization vector
157518ecceaSTom Joseph if (!RAND_bytes(output.data(), AESCBC128ConfHeader))
158518ecceaSTom Joseph {
159518ecceaSTom Joseph throw std::runtime_error("RAND_bytes failed");
160518ecceaSTom Joseph }
161518ecceaSTom Joseph
162518ecceaSTom Joseph // Initializes Cipher context
163584fa887SAdriana Kobylak EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
164518ecceaSTom Joseph
1659e801a2bSVernon Mauery auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
166f6c97e11STom Joseph
167*8425624aSPatrick Williams std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(
168*8425624aSPatrick Williams ctx, cleanupFunc);
169f6c97e11STom Joseph
170518ecceaSTom Joseph /*
171518ecceaSTom Joseph * EVP_EncryptInit_ex sets up cipher context ctx for encryption with type
172518ecceaSTom Joseph * AES-CBC-128. ctx must be initialized before calling this function. K2 is
173518ecceaSTom Joseph * the symmetric key used and iv is the initialization vector used.
174518ecceaSTom Joseph */
175f6c97e11STom Joseph if (!EVP_EncryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
176518ecceaSTom Joseph output.data()))
177518ecceaSTom Joseph {
178518ecceaSTom Joseph throw std::runtime_error("EVP_EncryptInit_ex failed for type "
179518ecceaSTom Joseph "AES-CBC-128");
180518ecceaSTom Joseph }
181518ecceaSTom Joseph
182518ecceaSTom Joseph /*
183518ecceaSTom Joseph * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
184518ecceaSTom Joseph * parameter is zero then no padding is performed. This function always
185518ecceaSTom Joseph * returns 1.
186518ecceaSTom Joseph */
187f6c97e11STom Joseph EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
188518ecceaSTom Joseph
189518ecceaSTom Joseph int outputLen = 0;
190518ecceaSTom Joseph
191518ecceaSTom Joseph /*
192518ecceaSTom Joseph * If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any
193518ecceaSTom Joseph * more data and it will return an error if any data remains in a partial
194518ecceaSTom Joseph * block: that is if the total data length is not a multiple of the block
195518ecceaSTom Joseph * size. Since we are adding padding bytes and ensures that payload is a
196518ecceaSTom Joseph * multiple of block size, we are not making the call to
197518ecceaSTom Joseph * EVP_DecryptFinal_ex()
198518ecceaSTom Joseph */
1999e801a2bSVernon Mauery if (!EVP_EncryptUpdate(ctxPtr.get(), output.data() + AESCBC128ConfHeader,
2009e801a2bSVernon Mauery &outputLen, input, inputLen))
201518ecceaSTom Joseph {
202518ecceaSTom Joseph throw std::runtime_error("EVP_EncryptUpdate failed for type "
203518ecceaSTom Joseph "AES-CBC-128");
204518ecceaSTom Joseph }
205518ecceaSTom Joseph
206518ecceaSTom Joseph output.resize(AESCBC128ConfHeader + outputLen);
207518ecceaSTom Joseph
208518ecceaSTom Joseph return output;
209518ecceaSTom Joseph }
210518ecceaSTom Joseph
211d08b5235STom Joseph } // namespace crypt
212d08b5235STom Joseph
213d08b5235STom Joseph } // namespace cipher
214