blob: e28da4c6d7b9c5dd642185b320cba0f47090d7fd [file] [log] [blame]
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Zhiyi Zhanga67fa462020-04-19 13:48:03 -07002/**
3 * Copyright (c) 2017-2020, Regents of the University of California.
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -07004 *
5 * This file is part of ndncert, a certificate management system based on NDN.
6 *
7 * ndncert is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License along with
16 * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * See AUTHORS.md for complete list of ndncert authors and contributors.
19 */
20
21#include "crypto-helper.hpp"
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070022#include <openssl/err.h>
Zhiyi Zhanga2ce5992019-08-14 17:35:00 -070023#include <openssl/hmac.h>
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -070024#include <openssl/pem.h>
Davide Pesavento368341b2019-08-13 23:57:50 -040025#include <ndn-cxx/encoding/buffer-stream.hpp>
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070026#include <ndn-cxx/security/transform/base64-decode.hpp>
27#include <ndn-cxx/security/transform/base64-encode.hpp>
28#include <ndn-cxx/security/transform/buffer-source.hpp>
Davide Pesavento368341b2019-08-13 23:57:50 -040029#include <ndn-cxx/security/transform/private-key.hpp>
30#include <ndn-cxx/security/transform/signer-filter.hpp>
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070031#include <ndn-cxx/security/transform/step-source.hpp>
32#include <ndn-cxx/security/transform/stream-sink.hpp>
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070033
34namespace ndn {
35namespace ndncert {
36
37const size_t HASH_SIZE = 32;
38
39_LOG_INIT(crypto-support);
40
41ECDHState::ECDHState()
42{
43 OpenSSL_add_all_algorithms();
44 context = std::make_unique<ECDH_CTX>();
45 context->EC_NID = NID_X9_62_prime256v1;
46
47 // Create the context for parameter generation
48 if (nullptr == (context->ctx_params = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr))) {
49 handleErrors("Could not create context contexts.");
50 return;
51 }
52
53 // Initialise the parameter generation
54 if (EVP_PKEY_paramgen_init(context->ctx_params) != 1) {
55 handleErrors("Could not initialize parameter generation.");
56 return;
57 }
58
59 // We're going to use the ANSI X9.62 Prime 256v1 curve
60 if (1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context->ctx_params, context->EC_NID)) {
61 handleErrors("Likely unknown elliptical curve ID specified.");
62 return;
63 }
64
65 // Create the parameter object params
66 if (!EVP_PKEY_paramgen(context->ctx_params, &context->params)) {
67 // the generated key is written to context->params
68 handleErrors("Could not create parameter object parameters.");
69 return;
70 }
71
72 // Create the context for the key generation
73 if (nullptr == (context->ctx_keygen = EVP_PKEY_CTX_new(context->params, nullptr))) {
74 //The EVP_PKEY_CTX_new() function allocates public key algorithm context using
75 //the algorithm specified in pkey and ENGINE e (in this case nullptr).
76 handleErrors("Could not create the context for the key generation");
77 return;
78 }
79
80 // initializes a public key algorithm context
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -070081 if (1 != EVP_PKEY_keygen_init(context->ctx_keygen)) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070082 handleErrors("Could not init context for key generation.");
83 return;
84 }
85 if (1 != EVP_PKEY_keygen(context->ctx_keygen, &context->privkey)) {
86 //performs a key generation operation, the generated key is written to context->privkey.
87 handleErrors("Could not generate DHE keys in final step");
88 return;
89 }
90}
91
92ECDHState::~ECDHState()
93{
94 // Contexts
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -070095 if (context->ctx_params != nullptr) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070096 EVP_PKEY_CTX_free(context->ctx_params);
97 }
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -070098 if (context->ctx_keygen != nullptr) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070099 EVP_PKEY_CTX_free(context->ctx_keygen);
100 }
101
102 // Keys
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700103 if (context->privkey != nullptr) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700104 EVP_PKEY_free(context->privkey);
105 }
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700106 if (context->peerkey != nullptr) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700107 EVP_PKEY_free(context->peerkey);
108 }
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700109 if (context->params != nullptr) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700110 EVP_PKEY_free(context->params);
111 }
112}
113
114uint8_t*
115ECDHState::getRawSelfPubKey()
116{
117 auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
118
119 if (privECKey == nullptr) {
120 handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY().");
121 return nullptr;
122 }
123
124 auto ecPoint = EC_KEY_get0_public_key(privECKey);
125 const EC_GROUP* group = EC_KEY_get0_group(privECKey);
126 context->publicKeyLen = EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_COMPRESSED,
127 context->publicKey, 256, nullptr);
128 EC_KEY_free(privECKey);
129 if (context->publicKeyLen == 0) {
130 handleErrors("Could not convert EC_POINTS to octet string when calling EC_POINT_point2oct.");
131 return nullptr;
132 }
133
134 return context->publicKey;
135}
136
137std::string
138ECDHState::getBase64PubKey()
139{
Davide Pesavento368341b2019-08-13 23:57:50 -0400140 namespace t = ndn::security::transform;
141
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700142 if (context->publicKeyLen == 0) {
143 this->getRawSelfPubKey();
144 }
Davide Pesavento368341b2019-08-13 23:57:50 -0400145
146 std::ostringstream os;
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700147 t::bufferSource(context->publicKey, context->publicKeyLen) >> t::base64Encode(false) >> t::streamSink(os);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700148 return os.str();
149}
150
151uint8_t*
152ECDHState::deriveSecret(const uint8_t* peerkey, int peerKeySize)
153{
154 auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
155
156 if (privECKey == nullptr) {
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800157 handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY()");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700158 return nullptr;
159 }
160
161 auto group = EC_KEY_get0_group(privECKey);
162 auto peerPoint = EC_POINT_new(group);
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800163 int result = EC_POINT_oct2point(group, peerPoint, peerkey, peerKeySize, nullptr);
164 if (result == 0) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700165 EC_POINT_free(peerPoint);
166 EC_KEY_free(privECKey);
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800167 handleErrors("Cannot convert peer's key into a EC point when calling EC_POINT_oct2point()");
168 }
169
170 if (-1 == (context->sharedSecretLen = ECDH_compute_key(context->sharedSecret, 256,
171 peerPoint, privECKey, nullptr))) {
172 EC_POINT_free(peerPoint);
173 EC_KEY_free(privECKey);
174 handleErrors("Cannot generate ECDH secret when calling ECDH_compute_key()");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700175 }
176 EC_POINT_free(peerPoint);
177 EC_KEY_free(privECKey);
178 return context->sharedSecret;
179}
180
181uint8_t*
182ECDHState::deriveSecret(const std::string& peerKeyStr)
183{
184 namespace t = ndn::security::transform;
Davide Pesavento368341b2019-08-13 23:57:50 -0400185
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700186 OBufferStream os;
Zhiyi Zhang8da54d62019-11-21 00:03:05 -0800187 t::bufferSource(peerKeyStr) >> t::base64Decode(false) >> t::streamSink(os);
Davide Pesavento368341b2019-08-13 23:57:50 -0400188 auto result = os.buf();
189
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700190 return this->deriveSecret(result->data(), result->size());
191}
192
Davide Pesavento368341b2019-08-13 23:57:50 -0400193int
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700194hmac_sha256(const uint8_t* data, const unsigned data_length,
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700195 const uint8_t* key, const unsigned key_length,
Zhiyi Zhanga2c39f72020-10-06 16:45:55 -0700196 uint8_t* result)
Davide Pesavento368341b2019-08-13 23:57:50 -0400197{
Zhiyi Zhanga2ce5992019-08-14 17:35:00 -0700198 HMAC(EVP_sha256(), key, key_length,
199 (unsigned char*)data, data_length,
Zhiyi Zhanga2c39f72020-10-06 16:45:55 -0700200 (unsigned char*)result, nullptr);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700201 return 0;
202}
203
Davide Pesavento368341b2019-08-13 23:57:50 -0400204// avoid dependency on OpenSSL >= 1.1
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700205int
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700206hkdf(const uint8_t* secret, int secret_len, const uint8_t* salt,
207 int salt_len, uint8_t* output, int output_len,
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700208 const uint8_t* info, int info_len)
209{
Davide Pesavento368341b2019-08-13 23:57:50 -0400210 namespace t = ndn::security::transform;
211
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700212 // hkdf generate prk
213 uint8_t prk[HASH_SIZE];
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700214 if (salt_len == 0) {
Zhiyi Zhanga2ce5992019-08-14 17:35:00 -0700215 uint8_t realSalt[HASH_SIZE] = {0};
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700216 hmac_sha256(secret, secret_len, realSalt, HASH_SIZE, prk);
Zhiyi Zhanga2ce5992019-08-14 17:35:00 -0700217 }
218 else {
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700219 hmac_sha256(secret, secret_len, salt, salt_len, prk);
Zhiyi Zhanga2ce5992019-08-14 17:35:00 -0700220 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700221
222 // hkdf expand
223 uint8_t prev[HASH_SIZE] = {0};
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700224 int done_len = 0, dig_len = HASH_SIZE, n = output_len / dig_len;
225 if (output_len % dig_len)
Davide Pesavento368341b2019-08-13 23:57:50 -0400226 n++;
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700227 if (n > 255 || output == nullptr)
Davide Pesavento368341b2019-08-13 23:57:50 -0400228 return 0;
229
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700230 for (int i = 1; i <= n; i++) {
231 size_t copy_len;
232 const uint8_t ctr = i;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700233
Davide Pesavento368341b2019-08-13 23:57:50 -0400234 t::StepSource source;
235 t::PrivateKey privKey;
236 privKey.loadRaw(KeyType::HMAC, prk, dig_len);
237 OBufferStream os;
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700238 source >> t::signerFilter(DigestAlgorithm::SHA256, privKey) >> t::streamSink(os);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700239
240 if (i > 1) {
241 source.write(prev, dig_len);
242 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700243 source.write(info, info_len);
244 source.write(&ctr, 1);
245 source.end();
246
247 auto result = os.buf();
248 memcpy(prev, result->data(), dig_len);
Zhiyi Zhang97bedb82020-10-10 11:11:35 -0700249 copy_len = (done_len + dig_len > output_len) ? output_len - done_len : dig_len;
250 memcpy(output + done_len, prev, copy_len);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700251 done_len += copy_len;
252 }
253 return done_len;
254}
255
Zhiyi Zhanga67fa462020-04-19 13:48:03 -0700256int
257aes_gcm_128_encrypt(const uint8_t* plaintext, size_t plaintext_len, const uint8_t* associated, size_t associated_len,
258 const uint8_t* key, const uint8_t* iv, uint8_t* ciphertext, uint8_t* tag)
259{
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700260 EVP_CIPHER_CTX* ctx;
Zhiyi Zhanga67fa462020-04-19 13:48:03 -0700261 int len;
262 int ciphertext_len;
263
264 // Create and initialise the context
265 if (!(ctx = EVP_CIPHER_CTX_new())) {
266 handleErrors("Cannot create and initialise the context when calling EVP_CIPHER_CTX_new()");
267 }
268
269 // Initialise the encryption operation.
270 if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
Zhiyi Zhangb8cb0472020-05-05 20:55:05 -0700271 handleErrors("Cannot initialise the encryption operation when calling EVP_EncryptInit_ex()");
Zhiyi Zhanga67fa462020-04-19 13:48:03 -0700272 }
273
274 // Set IV length if default 12 bytes (96 bits) is not appropriate
275 if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr)) {
276 handleErrors("Cannot set IV length when calling EVP_CIPHER_CTX_ctrl()");
277 }
278
279 // Initialise key and IV
280 if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, key, iv)) {
281 handleErrors("Cannot initialize key and IV when calling EVP_EncryptInit_ex()");
282 }
283
284 // Provide any AAD data. This can be called zero or more times as required
285 if (1 != EVP_EncryptUpdate(ctx, nullptr, &len, associated, associated_len)) {
286 handleErrors("Cannot set associated authentication data when calling EVP_EncryptUpdate()");
287 }
288
289 // Provide the message to be encrypted, and obtain the encrypted output.
290 // EVP_EncryptUpdate can be called multiple times if necessary
291 if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
292 handleErrors("Cannot encrypt when calling EVP_EncryptUpdate()");
293 }
294 ciphertext_len = len;
295
296 // Finalise the encryption. Normally ciphertext bytes may be written at
297 // this stage, but this does not occur in GCM mode
298 if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
299 handleErrors("Cannot finalise the encryption when calling EVP_EncryptFinal_ex()");
300 }
301 ciphertext_len += len;
302
303 // Get the tag
304 if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) {
305 handleErrors("Cannot get tag when calling EVP_CIPHER_CTX_ctrl()");
306 }
307
308 // Clean up
309 EVP_CIPHER_CTX_free(ctx);
310 return ciphertext_len;
311}
312
313int
314aes_gcm_128_decrypt(const uint8_t* ciphertext, size_t ciphertext_len, const uint8_t* associated, size_t associated_len,
315 const uint8_t* tag, const uint8_t* key, const uint8_t* iv, uint8_t* plaintext)
316{
317 EVP_CIPHER_CTX* ctx;
318 int len;
319 int plaintext_len;
320 int ret;
321
322 // Create and initialise the context
323 if (!(ctx = EVP_CIPHER_CTX_new())) {
324 handleErrors("Cannot create and initialise the context when calling EVP_CIPHER_CTX_new()");
325 }
326
327 // Initialise the decryption operation.
328 if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
329 handleErrors("Cannot initialise the decryption operation when calling EVP_DecryptInit_ex()");
330 }
331
332 // Set IV length. Not necessary if this is 12 bytes (96 bits)
333 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr)) {
334 handleErrors("Cannot set IV length when calling EVP_CIPHER_CTX_ctrl");
335 }
336
337 // Initialise key and IV
338 if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, key, iv)) {
339 handleErrors("Cannot initialise key and IV when calling EVP_DecryptInit_ex()");
340 }
341
342 // Provide any AAD data. This can be called zero or more times as required
343 if (!EVP_DecryptUpdate(ctx, nullptr, &len, associated, associated_len)) {
344 handleErrors("Cannot set associated authentication data when calling EVP_EncryptUpdate()");
345 }
346
347 // Provide the message to be decrypted, and obtain the plaintext output.
348 // EVP_DecryptUpdate can be called multiple times if necessary
349 if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
350 handleErrors("Cannot decrypt when calling EVP_DecryptUpdate()");
351 }
352 plaintext_len = len;
353
354 // Set expected tag value. Works in OpenSSL 1.0.1d and later
355 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag)) {
356 handleErrors("Cannot set tag value when calling EVP_CIPHER_CTX_ctrl");
357 }
358
359 // Finalise the decryption. A positive return value indicates success,
360 // anything else is a failure - the plaintext is not trustworthy.
361 ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
362
363 // Clean up
364 EVP_CIPHER_CTX_free(ctx);
365
366 if (ret > 0) {
367 // Success
368 plaintext_len += len;
369 return plaintext_len;
370 }
371 else {
372 // Verify failed
373 return -1;
374 }
375}
376
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700377void
378handleErrors(const std::string& errorInfo)
379{
380 _LOG_DEBUG("Error in CRYPTO SUPPORT " << errorInfo);
381 BOOST_THROW_EXCEPTION(CryptoError("Error in CRYPTO SUPPORT: " + errorInfo));
382}
383
Zhiyi Zhange4891b72020-10-10 15:11:57 -0700384} // namespace ndncert
385} // namespace ndn