1 #include "crypt_algo.hpp"
2 
3 #include "message_parsers.hpp"
4 
5 #include <openssl/evp.h>
6 #include <openssl/hmac.h>
7 #include <openssl/rand.h>
8 
9 #include <algorithm>
10 #include <numeric>
11 
12 namespace cipher
13 {
14 
15 namespace crypt
16 {
17 
18 constexpr std::array<uint8_t, AlgoAES128::AESCBC128BlockSize - 1>
19     AlgoAES128::confPadBytes;
20 
21 std::vector<uint8_t>
decryptPayload(const std::vector<uint8_t> & packet,const size_t sessHeaderLen,const size_t payloadLen) const22     AlgoAES128::decryptPayload(const std::vector<uint8_t>& packet,
23                                const size_t sessHeaderLen,
24                                const size_t payloadLen) const
25 {
26     // verify packet size minimal: sessHeaderLen + payloadLen
27     // and payloadLen is more than AESCBC128ConfHeader
28     if (packet.size() < (sessHeaderLen + payloadLen) ||
29         payloadLen < AESCBC128ConfHeader)
30     {
31         throw std::runtime_error("Invalid data length");
32     }
33 
34     auto plainPayload =
35         decryptData(packet.data() + sessHeaderLen,
36                     packet.data() + sessHeaderLen + AESCBC128ConfHeader,
37                     payloadLen - AESCBC128ConfHeader);
38 
39     /*
40      * The confidentiality pad length is the last byte in the payload, it would
41      * tell the number of pad bytes in the payload. We added a condition, so
42      * that buffer overrun doesn't happen.
43      */
44     size_t confPadLength = plainPayload.back();
45     auto padLength = std::min(plainPayload.size() - 1, confPadLength);
46 
47     auto plainPayloadLen = plainPayload.size() - padLength - 1;
48 
49     // Additional check if the confidentiality pad bytes are as expected
50     if (!std::equal(plainPayload.begin() + plainPayloadLen,
51                     plainPayload.begin() + plainPayloadLen + padLength,
52                     confPadBytes.begin()))
53     {
54         throw std::runtime_error("Confidentiality pad bytes check failed");
55     }
56 
57     plainPayload.resize(plainPayloadLen);
58 
59     return plainPayload;
60 }
61 
62 std::vector<uint8_t>
encryptPayload(std::vector<uint8_t> & payload) const63     AlgoAES128::encryptPayload(std::vector<uint8_t>& payload) const
64 {
65     auto payloadLen = payload.size();
66 
67     /*
68      * The following logic calculates the number of padding bytes to be added to
69      * the payload data. This would ensure that the length is a multiple of the
70      * block size of algorithm being used. For the AES algorithm, the block size
71      * is 16 bytes.
72      */
73     auto paddingLen = AESCBC128BlockSize - ((payloadLen + 1) & 0xF);
74 
75     /*
76      * The additional field is for the Confidentiality Pad Length field. For the
77      * AES algorithm, this number will range from 0 to 15 bytes. This field is
78      * mandatory.
79      */
80     payload.resize(payloadLen + paddingLen + 1);
81 
82     /*
83      * If no Confidentiality Pad bytes are required, the Confidentiality Pad
84      * Length field is set to 00h. If present, the value of the first byte of
85      * Confidentiality Pad shall be one (01h) and all subsequent bytes shall
86      * have a monotonically increasing value (e.g., 02h, 03h, 04h, etc).
87      */
88     if (0 != paddingLen)
89     {
90         std::iota(payload.begin() + payloadLen,
91                   payload.begin() + payloadLen + paddingLen, 1);
92     }
93 
94     payload.back() = paddingLen;
95 
96     return encryptData(payload.data(), payload.size());
97 }
98 
decryptData(const uint8_t * iv,const uint8_t * input,const int inputLen) const99 std::vector<uint8_t> AlgoAES128::decryptData(const uint8_t* iv,
100                                              const uint8_t* input,
101                                              const int inputLen) const
102 {
103     // Initializes Cipher context
104     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
105 
106     auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
107 
108     std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(ctx,
109                                                                   cleanupFunc);
110 
111     /*
112      * EVP_DecryptInit_ex sets up cipher context ctx for encryption with type
113      * AES-CBC-128. ctx must be initialized before calling this function. K2 is
114      * the symmetric key used and iv is the initialization vector used.
115      */
116     if (!EVP_DecryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
117                             iv))
118     {
119         throw std::runtime_error("EVP_DecryptInit_ex failed for type "
120                                  "AES-CBC-128");
121     }
122 
123     /*
124      * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
125      * parameter is zero then no padding is performed. This function always
126      * returns 1.
127      */
128     EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
129 
130     std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
131 
132     int outputLen = 0;
133 
134     /*
135      * If padding is disabled then EVP_DecryptFinal_ex() will not encrypt any
136      * more data and it will return an error if any data remains in a partial
137      * block: that is if the total data length is not a multiple of the block
138      * size. Since AES-CBC-128 encrypted payload format adds padding bytes and
139      * ensures that payload is a multiple of block size, we are not making the
140      * call to  EVP_DecryptFinal_ex().
141      */
142     if (!EVP_DecryptUpdate(ctxPtr.get(), output.data(), &outputLen, input,
143                            inputLen))
144     {
145         throw std::runtime_error("EVP_DecryptUpdate failed");
146     }
147 
148     output.resize(outputLen);
149 
150     return output;
151 }
152 
encryptData(const uint8_t * input,const int inputLen) const153 std::vector<uint8_t> AlgoAES128::encryptData(const uint8_t* input,
154                                              const int inputLen) const
155 {
156     std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
157 
158     // Generate the initialization vector
159     if (!RAND_bytes(output.data(), AESCBC128ConfHeader))
160     {
161         throw std::runtime_error("RAND_bytes failed");
162     }
163 
164     // Initializes Cipher context
165     EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
166 
167     auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
168 
169     std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(ctx,
170                                                                   cleanupFunc);
171 
172     /*
173      * EVP_EncryptInit_ex sets up cipher context ctx for encryption with type
174      * AES-CBC-128. ctx must be initialized before calling this function. K2 is
175      * the symmetric key used and iv is the initialization vector used.
176      */
177     if (!EVP_EncryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
178                             output.data()))
179     {
180         throw std::runtime_error("EVP_EncryptInit_ex failed for type "
181                                  "AES-CBC-128");
182     }
183 
184     /*
185      * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
186      * parameter is zero then no padding is performed. This function always
187      * returns 1.
188      */
189     EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
190 
191     int outputLen = 0;
192 
193     /*
194      * If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any
195      * more data and it will return an error if any data remains in a partial
196      * block: that is if the total data length is not a multiple of the block
197      * size. Since we are adding padding bytes and ensures that payload is a
198      * multiple of block size, we are not making the call to
199      * EVP_DecryptFinal_ex()
200      */
201     if (!EVP_EncryptUpdate(ctxPtr.get(), output.data() + AESCBC128ConfHeader,
202                            &outputLen, input, inputLen))
203     {
204         throw std::runtime_error("EVP_EncryptUpdate failed for type "
205                                  "AES-CBC-128");
206     }
207 
208     output.resize(AESCBC128ConfHeader + outputLen);
209 
210     return output;
211 }
212 
213 } // namespace crypt
214 
215 } // namespace cipher
216