crypto-helper: add authenticated GCM 128 encryption

Change-Id: I8bf0bdd25658e076a9f732f836bc7b07a767fec9
diff --git a/src/crypto-support/crypto-helper.cpp b/src/crypto-support/crypto-helper.cpp
index 0792bfc..297bbef 100644
--- a/src/crypto-support/crypto-helper.cpp
+++ b/src/crypto-support/crypto-helper.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2017-2019, Regents of the University of California.
+/**
+ * Copyright (c) 2017-2020, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -259,6 +259,127 @@
   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)
 {
diff --git a/src/crypto-support/crypto-helper.hpp b/src/crypto-support/crypto-helper.hpp
index 7bf6572..f75396b 100644
--- a/src/crypto-support/crypto-helper.hpp
+++ b/src/crypto-support/crypto-helper.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2019, Regents of the University of California.
+ * Copyright (c) 2017-2020, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -55,7 +55,6 @@
 
   uint8_t*
   deriveSecret(const std::string& peerKeyStr);
-  //unique_ptr<ECDH_CTX_T> context;
   unique_ptr<ECDH_CTX> context;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
@@ -71,9 +70,44 @@
      int saltLen, uint8_t* okm, int okm_len,
      const uint8_t* info=INFO, int info_len=INFO_LEN);
 
-int ndn_compute_hmac_sha256 (const uint8_t *data, const unsigned  data_length,
-                                    const uint8_t *key, const unsigned key_length,
-                                    uint8_t *prk);
+int
+ndn_compute_hmac_sha256(const uint8_t *data, const unsigned data_length,
+                        const uint8_t *key, const unsigned key_length,
+                        uint8_t *prk);
+
+/**
+ * Authentication GCM 128 Encryption
+ * @p plaintext, input, plaintext
+ * @p plaintext_len, input, size of plaintext
+ * @p associated, input, associated authentication data
+ * @p associated_len, input, size of associated authentication data
+ * @p key, input, 16 bytes AES key
+ * @p iv, input, 12 bytes IV
+ * @p ciphertext, output
+ * @p tag, output, 16 bytes tag
+ * @return the size of ciphertext
+ * @throw CryptoError when there is an error in the process of encryption
+ */
+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);
+
+/**
+ * Authentication GCM 128 Decryption
+ * @p ciphertext, input, ciphertext
+ * @p ciphertext_len, input, size of ciphertext
+ * @p associated, input, associated authentication data
+ * @p associated_len, input, size of associated authentication data
+ * @p tag, input, 16 bytes tag
+ * @p key, input, 16 bytes AES key
+ * @p iv, input, 12 bytes IV
+ * @p plaintext, output
+ * @return the size of plaintext or -1 if the verification fails
+ * @throw CryptoError when there is an error in the process of encryption
+ */
+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);
 
 void
 handleErrors(const std::string& errorInfo);