move crypto support files into protocol details

Change-Id: Ia36e2f7a5c0b74a1b0117611b32df04a88d883b4
diff --git a/src/protocol-detail/crypto-helper.cpp b/src/protocol-detail/crypto-helper.cpp
new file mode 100644
index 0000000..b83d13a
--- /dev/null
+++ b/src/protocol-detail/crypto-helper.cpp
@@ -0,0 +1,396 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "crypto-helper.hpp"
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+#include <openssl/pem.h>
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <ndn-cxx/encoding/buffer-stream.hpp>
+#include <ndn-cxx/security/transform/base64-decode.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/private-key.hpp>
+#include <ndn-cxx/security/transform/signer-filter.hpp>
+#include <ndn-cxx/security/transform/step-source.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+const size_t HASH_SIZE = 32;
+
+NDN_LOG_INIT(ndncert.cryptosupport);
+
+struct ECDHState::ECDH_CTX {
+  int EC_NID;
+  EVP_PKEY_CTX* ctx_params;
+  EVP_PKEY_CTX* ctx_keygen;
+  EVP_PKEY* privkey;
+  EVP_PKEY* peerkey;
+  EVP_PKEY* params;
+};
+
+ECDHState::ECDHState()
+  : m_publicKeyLen(0)
+  , m_sharedSecretLen(0)
+{
+  OpenSSL_add_all_algorithms();
+  context = std::make_unique<ECDH_CTX>();
+  context->EC_NID = NID_X9_62_prime256v1;
+
+  // Create the context for parameter generation
+  if (nullptr == (context->ctx_params = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr))) {
+    handleErrors("Could not create context contexts.");
+    return;
+  }
+
+  // Initialise the parameter generation
+  if (EVP_PKEY_paramgen_init(context->ctx_params) != 1) {
+    handleErrors("Could not initialize parameter generation.");
+    return;
+  }
+
+  // We're going to use the ANSI X9.62 Prime 256v1 curve
+  if (1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context->ctx_params, context->EC_NID)) {
+    handleErrors("Likely unknown elliptical curve ID specified.");
+    return;
+  }
+
+  // Create the parameter object params
+  if (!EVP_PKEY_paramgen(context->ctx_params, &context->params)) {
+    // the generated key is written to context->params
+    handleErrors("Could not create parameter object parameters.");
+    return;
+  }
+
+  // Create the context for the key generation
+  if (nullptr == (context->ctx_keygen = EVP_PKEY_CTX_new(context->params, nullptr))) {
+    //The EVP_PKEY_CTX_new() function allocates public key algorithm context using
+    //the algorithm specified in pkey and ENGINE e (in this case nullptr).
+    handleErrors("Could not create the context for the key generation");
+    return;
+  }
+
+  // initializes a public key algorithm context
+  if (1 != EVP_PKEY_keygen_init(context->ctx_keygen)) {
+    handleErrors("Could not init context for key generation.");
+    return;
+  }
+  if (1 != EVP_PKEY_keygen(context->ctx_keygen, &context->privkey)) {
+    //performs a key generation operation, the generated key is written to context->privkey.
+    handleErrors("Could not generate DHE keys in final step");
+    return;
+  }
+}
+
+ECDHState::~ECDHState()
+{
+  // Contexts
+  if (context->ctx_params != nullptr) {
+    EVP_PKEY_CTX_free(context->ctx_params);
+  }
+  if (context->ctx_keygen != nullptr) {
+    EVP_PKEY_CTX_free(context->ctx_keygen);
+  }
+
+  // Keys
+  if (context->privkey != nullptr) {
+    EVP_PKEY_free(context->privkey);
+  }
+  if (context->peerkey != nullptr) {
+    EVP_PKEY_free(context->peerkey);
+  }
+  if (context->params != nullptr) {
+    EVP_PKEY_free(context->params);
+  }
+}
+
+uint8_t*
+ECDHState::getRawSelfPubKey()
+{
+  auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
+
+  if (privECKey == nullptr) {
+    handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY().");
+    return nullptr;
+  }
+
+  auto ecPoint = EC_KEY_get0_public_key(privECKey);
+  const EC_GROUP* group = EC_KEY_get0_group(privECKey);
+  m_publicKeyLen = EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_COMPRESSED,
+                                    m_publicKey, 256, nullptr);
+  EC_KEY_free(privECKey);
+  if (m_publicKeyLen == 0) {
+    handleErrors("Could not convert EC_POINTS to octet string when calling EC_POINT_point2oct.");
+    return nullptr;
+  }
+
+  return m_publicKey;
+}
+
+std::string
+ECDHState::getBase64PubKey()
+{
+  namespace t = ndn::security::transform;
+
+  if (m_publicKeyLen == 0) {
+    this->getRawSelfPubKey();
+  }
+  std::ostringstream os;
+  t::bufferSource(m_publicKey, m_publicKeyLen) >> t::base64Encode(false) >> t::streamSink(os);
+  return os.str();
+}
+
+uint8_t*
+ECDHState::deriveSecret(const uint8_t* peerkey, int peerKeySize)
+{
+  auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
+
+  if (privECKey == nullptr) {
+    handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY()");
+    return nullptr;
+  }
+
+  auto group = EC_KEY_get0_group(privECKey);
+  auto peerPoint = EC_POINT_new(group);
+  int result = EC_POINT_oct2point(group, peerPoint, peerkey, peerKeySize, nullptr);
+  if (result == 0) {
+    EC_POINT_free(peerPoint);
+    EC_KEY_free(privECKey);
+    handleErrors("Cannot convert peer's key into a EC point when calling EC_POINT_oct2point()");
+  }
+
+  if (-1 == (m_sharedSecretLen = ECDH_compute_key(m_sharedSecret, 256, peerPoint, privECKey, nullptr))) {
+    EC_POINT_free(peerPoint);
+    EC_KEY_free(privECKey);
+    handleErrors("Cannot generate ECDH secret when calling ECDH_compute_key()");
+  }
+  EC_POINT_free(peerPoint);
+  EC_KEY_free(privECKey);
+  return m_sharedSecret;
+}
+
+uint8_t*
+ECDHState::deriveSecret(const std::string& peerKeyStr)
+{
+  namespace t = ndn::security::transform;
+
+  OBufferStream os;
+  t::bufferSource(peerKeyStr) >> t::base64Decode(false) >> t::streamSink(os);
+  auto result = os.buf();
+
+  return this->deriveSecret(result->data(), result->size());
+}
+
+int
+hmac_sha256(const uint8_t* data, const unsigned data_length,
+            const uint8_t* key, const unsigned key_length,
+            uint8_t* result)
+{
+  HMAC(EVP_sha256(), key, key_length,
+       (unsigned char*)data, data_length,
+       (unsigned char*)result, nullptr);
+  return 0;
+}
+
+// avoid dependency on OpenSSL >= 1.1
+int
+hkdf(const uint8_t* secret, int secret_len, const uint8_t* salt,
+     int salt_len, uint8_t* output, int output_len,
+     const uint8_t* info, int info_len)
+{
+  namespace t = ndn::security::transform;
+
+  // hkdf generate prk
+  uint8_t prk[HASH_SIZE];
+  if (salt_len == 0) {
+    uint8_t realSalt[HASH_SIZE] = {0};
+    hmac_sha256(secret, secret_len, realSalt, HASH_SIZE, prk);
+  }
+  else {
+    hmac_sha256(secret, secret_len, salt, salt_len, prk);
+  }
+
+  // hkdf expand
+  uint8_t prev[HASH_SIZE] = {0};
+  int done_len = 0, dig_len = HASH_SIZE, n = output_len / dig_len;
+  if (output_len % dig_len)
+    n++;
+  if (n > 255 || output == nullptr)
+    return 0;
+
+  for (int i = 1; i <= n; i++) {
+    size_t copy_len;
+    const uint8_t ctr = i;
+
+    t::StepSource source;
+    t::PrivateKey privKey;
+    privKey.loadRaw(KeyType::HMAC, prk, dig_len);
+    OBufferStream os;
+    source >> t::signerFilter(DigestAlgorithm::SHA256, privKey) >> t::streamSink(os);
+
+    if (i > 1) {
+      source.write(prev, dig_len);
+    }
+    source.write(info, info_len);
+    source.write(&ctr, 1);
+    source.end();
+
+    auto result = os.buf();
+    memcpy(prev, result->data(), dig_len);
+    copy_len = (done_len + dig_len > output_len) ? output_len - done_len : dig_len;
+    memcpy(output + done_len, prev, copy_len);
+    done_len += copy_len;
+  }
+  return done_len;
+}
+
+int
+aes_gcm_128_encrypt(const uint8_t* plaintext, size_t plaintext_len, const uint8_t* associated, size_t associated_len,
+                    const uint8_t* key, const uint8_t* iv, uint8_t* ciphertext, uint8_t* tag)
+{
+  EVP_CIPHER_CTX* ctx;
+  int len;
+  int ciphertext_len;
+
+  // Create and initialise the context
+  if (!(ctx = EVP_CIPHER_CTX_new())) {
+    handleErrors("Cannot create and initialise the context when calling EVP_CIPHER_CTX_new()");
+  }
+
+  // Initialise the encryption operation.
+  if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
+    handleErrors("Cannot initialise the encryption operation when calling EVP_EncryptInit_ex()");
+  }
+
+  // Set IV length if default 12 bytes (96 bits) is not appropriate
+  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr)) {
+    handleErrors("Cannot set IV length when calling EVP_CIPHER_CTX_ctrl()");
+  }
+
+  // Initialise key and IV
+  if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, key, iv)) {
+    handleErrors("Cannot initialize key and IV when calling EVP_EncryptInit_ex()");
+  }
+
+  // Provide any AAD data. This can be called zero or more times as required
+  if (1 != EVP_EncryptUpdate(ctx, nullptr, &len, associated, associated_len)) {
+    handleErrors("Cannot set associated authentication data when calling EVP_EncryptUpdate()");
+  }
+
+  // Provide the message to be encrypted, and obtain the encrypted output.
+  // EVP_EncryptUpdate can be called multiple times if necessary
+  if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
+    handleErrors("Cannot encrypt when calling EVP_EncryptUpdate()");
+  }
+  ciphertext_len = len;
+
+  // Finalise the encryption. Normally ciphertext bytes may be written at
+  // this stage, but this does not occur in GCM mode
+  if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
+    handleErrors("Cannot finalise the encryption when calling EVP_EncryptFinal_ex()");
+  }
+  ciphertext_len += len;
+
+  // Get the tag
+  if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) {
+    handleErrors("Cannot get tag when calling EVP_CIPHER_CTX_ctrl()");
+  }
+
+  // Clean up
+  EVP_CIPHER_CTX_free(ctx);
+  return ciphertext_len;
+}
+
+int
+aes_gcm_128_decrypt(const uint8_t* ciphertext, size_t ciphertext_len, const uint8_t* associated, size_t associated_len,
+                    const uint8_t* tag, const uint8_t* key, const uint8_t* iv, uint8_t* plaintext)
+{
+  EVP_CIPHER_CTX* ctx;
+  int len;
+  int plaintext_len;
+  int ret;
+
+  // Create and initialise the context
+  if (!(ctx = EVP_CIPHER_CTX_new())) {
+    handleErrors("Cannot create and initialise the context when calling EVP_CIPHER_CTX_new()");
+  }
+
+  // Initialise the decryption operation.
+  if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
+    handleErrors("Cannot initialise the decryption operation when calling EVP_DecryptInit_ex()");
+  }
+
+  // Set IV length. Not necessary if this is 12 bytes (96 bits)
+  if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr)) {
+    handleErrors("Cannot set IV length when calling EVP_CIPHER_CTX_ctrl");
+  }
+
+  // Initialise key and IV
+  if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, key, iv)) {
+    handleErrors("Cannot initialise key and IV when calling EVP_DecryptInit_ex()");
+  }
+
+  // Provide any AAD data. This can be called zero or more times as required
+  if (!EVP_DecryptUpdate(ctx, nullptr, &len, associated, associated_len)) {
+    handleErrors("Cannot set associated authentication data when calling EVP_EncryptUpdate()");
+  }
+
+  // Provide the message to be decrypted, and obtain the plaintext output.
+  // EVP_DecryptUpdate can be called multiple times if necessary
+  if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
+    handleErrors("Cannot decrypt when calling EVP_DecryptUpdate()");
+  }
+  plaintext_len = len;
+
+  // Set expected tag value. Works in OpenSSL 1.0.1d and later
+  if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag)) {
+    handleErrors("Cannot set tag value when calling EVP_CIPHER_CTX_ctrl");
+  }
+
+  // Finalise the decryption. A positive return value indicates success,
+  // anything else is a failure - the plaintext is not trustworthy.
+  ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
+
+  // Clean up
+  EVP_CIPHER_CTX_free(ctx);
+
+  if (ret > 0) {
+    // Success
+    plaintext_len += len;
+    return plaintext_len;
+  }
+  else {
+    // Verify failed
+    return -1;
+  }
+}
+
+void
+handleErrors(const std::string& errorInfo)
+{
+  NDN_LOG_DEBUG("Error in CRYPTO SUPPORT " << errorInfo);
+  NDN_THROW(CryptoError("Error in CRYPTO SUPPORT: " + errorInfo));
+}
+
+} // namespace ndncert
+} // namespace ndn