summaryrefslogtreecommitdiffstats
path: root/crypt_algo.cpp
blob: bf46c4e386cda5ac2c56bb908cfe30b946b01b3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include "crypt_algo.hpp"

#include "message_parsers.hpp"

#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>

#include <algorithm>
#include <numeric>

namespace cipher
{

namespace crypt
{

constexpr std::array<uint8_t, AlgoAES128::AESCBC128BlockSize - 1>
    AlgoAES128::confPadBytes;

std::vector<uint8_t>
    AlgoAES128::decryptPayload(const std::vector<uint8_t>& packet,
                               const size_t sessHeaderLen,
                               const size_t payloadLen) const
{
    auto plainPayload =
        decryptData(packet.data() + sessHeaderLen,
                    packet.data() + sessHeaderLen + AESCBC128ConfHeader,
                    payloadLen - AESCBC128ConfHeader);

    /*
     * The confidentiality pad length is the last byte in the payload, it would
     * tell the number of pad bytes in the payload. We added a condition, so
     * that buffer overrun doesn't happen.
     */
    size_t confPadLength = plainPayload.back();
    auto padLength = std::min(plainPayload.size() - 1, confPadLength);

    auto plainPayloadLen = plainPayload.size() - padLength - 1;

    // Additional check if the confidentiality pad bytes are as expected
    if (!std::equal(plainPayload.begin() + plainPayloadLen,
                    plainPayload.begin() + plainPayloadLen + padLength,
                    confPadBytes.begin()))
    {
        throw std::runtime_error("Confidentiality pad bytes check failed");
    }

    plainPayload.resize(plainPayloadLen);

    return plainPayload;
}

std::vector<uint8_t>
    AlgoAES128::encryptPayload(std::vector<uint8_t>& payload) const
{
    auto payloadLen = payload.size();

    /*
     * The following logic calculates the number of padding bytes to be added to
     * the payload data. This would ensure that the length is a multiple of the
     * block size of algorithm being used. For the AES algorithm, the block size
     * is 16 bytes.
     */
    auto paddingLen = AESCBC128BlockSize - ((payloadLen + 1) & 0xF);

    /*
     * The additional field is for the Confidentiality Pad Length field. For the
     * AES algorithm, this number will range from 0 to 15 bytes. This field is
     * mandatory.
     */
    payload.resize(payloadLen + paddingLen + 1);

    /*
     * If no Confidentiality Pad bytes are required, the Confidentiality Pad
     * Length field is set to 00h. If present, the value of the first byte of
     * Confidentiality Pad shall be one (01h) and all subsequent bytes shall
     * have a monotonically increasing value (e.g., 02h, 03h, 04h, etc).
     */
    if (0 != paddingLen)
    {
        std::iota(payload.begin() + payloadLen,
                  payload.begin() + payloadLen + paddingLen, 1);
    }

    payload.back() = paddingLen;

    return encryptData(payload.data(), payload.size());
}

std::vector<uint8_t> AlgoAES128::decryptData(const uint8_t* iv,
                                             const uint8_t* input,
                                             const int inputLen) const
{
    // Initializes Cipher context
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();

    auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };

    std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(ctx,
                                                                  cleanupFunc);

    /*
     * EVP_DecryptInit_ex sets up cipher context ctx for encryption with type
     * AES-CBC-128. ctx must be initialized before calling this function. K2 is
     * the symmetric key used and iv is the initialization vector used.
     */
    if (!EVP_DecryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
                            iv))
    {
        throw std::runtime_error("EVP_DecryptInit_ex failed for type "
                                 "AES-CBC-128");
    }

    /*
     * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
     * parameter is zero then no padding is performed. This function always
     * returns 1.
     */
    EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);

    std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);

    int outputLen = 0;

    /*
     * If padding is disabled then EVP_DecryptFinal_ex() will not encrypt any
     * more data and it will return an error if any data remains in a partial
     * block: that is if the total data length is not a multiple of the block
     * size. Since AES-CBC-128 encrypted payload format adds padding bytes and
     * ensures that payload is a multiple of block size, we are not making the
     * call to  EVP_DecryptFinal_ex().
     */
    if (!EVP_DecryptUpdate(ctxPtr.get(), output.data(), &outputLen, input,
                           inputLen))
    {
        throw std::runtime_error("EVP_DecryptUpdate failed");
    }

    output.resize(outputLen);

    return output;
}

std::vector<uint8_t> AlgoAES128::encryptData(const uint8_t* input,
                                             const int inputLen) const
{
    std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);

    // Generate the initialization vector
    if (!RAND_bytes(output.data(), AESCBC128ConfHeader))
    {
        throw std::runtime_error("RAND_bytes failed");
    }

    // Initializes Cipher context
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();

    auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };

    std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(ctx,
                                                                  cleanupFunc);

    /*
     * EVP_EncryptInit_ex sets up cipher context ctx for encryption with type
     * AES-CBC-128. ctx must be initialized before calling this function. K2 is
     * the symmetric key used and iv is the initialization vector used.
     */
    if (!EVP_EncryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
                            output.data()))
    {
        throw std::runtime_error("EVP_EncryptInit_ex failed for type "
                                 "AES-CBC-128");
    }

    /*
     * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
     * parameter is zero then no padding is performed. This function always
     * returns 1.
     */
    EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);

    int outputLen = 0;

    /*
     * If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any
     * more data and it will return an error if any data remains in a partial
     * block: that is if the total data length is not a multiple of the block
     * size. Since we are adding padding bytes and ensures that payload is a
     * multiple of block size, we are not making the call to
     * EVP_DecryptFinal_ex()
     */
    if (!EVP_EncryptUpdate(ctxPtr.get(), output.data() + AESCBC128ConfHeader,
                           &outputLen, input, inputLen))
    {
        throw std::runtime_error("EVP_EncryptUpdate failed for type "
                                 "AES-CBC-128");
    }

    output.resize(AESCBC128ConfHeader + outputLen);

    return output;
}

} // namespace crypt

} // namespace cipher
OpenPOWER on IntegriCloud