xref: /openbmc/phosphor-net-ipmid/crypt_algo.cpp (revision 33503e2a90d5615a11ec2d27961ffdefc3a5cd10)
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  
encryptPayload(std::vector<uint8_t> & payload) const61  std::vector<uint8_t> AlgoAES128::encryptPayload(
62      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  
encryptData(const uint8_t * input,const int inputLen) const151  std::vector<uint8_t> AlgoAES128::encryptData(const uint8_t* input,
152                                               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