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