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);
diff --git a/tests/unit-tests/crypto-helper.t.cpp b/tests/unit-tests/crypto-helper.t.cpp
index b6f8bd3..8f8d7c0 100644
--- a/tests/unit-tests/crypto-helper.t.cpp
+++ b/tests/unit-tests/crypto-helper.t.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.
  *
@@ -187,6 +187,113 @@
                                 expected + sizeof(expected));
 }
 
+BOOST_AUTO_TEST_CASE(AesGcm1)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 0
+  // Key = cf063a34d4a9a76c2c86787d3f96db71
+  // IV = 113b9785971864c83b01c787
+  // CT =
+  // AAD =
+  // Tag = 72ac8493e3a5228b5d130a69d2510e42
+  // PT =
+  const uint8_t key[] = {0xcf, 0x06, 0x3a, 0x34, 0xd4, 0xa9, 0xa7, 0x6c,
+                         0x2c, 0x86, 0x78, 0x7d, 0x3f, 0x96, 0xdb, 0x71};
+  const uint8_t iv[] = {0x11, 0x3b, 0x97, 0x85, 0x97, 0x18,
+                        0x64, 0xc8, 0x3b, 0x01, 0xc7, 0x87};
+  const uint8_t expected_tag[] = {0x72, 0xac, 0x84, 0x93, 0xe3, 0xa5,
+                                  0x22, 0x8b, 0x5d, 0x13, 0x0a, 0x69,
+                                  0xd2, 0x51, 0x0e, 0x42};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  int size = aes_gcm_128_encrypt(nullptr, 0, nullptr, 0, key, iv, ciphertext, tag);
+  BOOST_CHECK(size == 0);
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aes_gcm_128_decrypt(ciphertext, size, nullptr, 0, tag, key, iv, decrypted);
+  BOOST_CHECK(size == 0);
+}
+
+BOOST_AUTO_TEST_CASE(AesGcm2)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 1
+  // Key = 2370e320d4344208e0ff5683f243b213
+  // IV = 04dbb82f044d30831c441228
+  // CT =
+  // AAD = d43a8e5089eea0d026c03a85178b27da
+  // Tag = 2a049c049d25aa95969b451d93c31c6e
+  // PT =
+  const uint8_t key[] = {0x23, 0x70, 0xe3, 0x20, 0xd4, 0x34, 0x42, 0x08,
+                         0xe0, 0xff, 0x56, 0x83, 0xf2, 0x43, 0xb2, 0x13};
+  const uint8_t iv[] = {0x04, 0xdb, 0xb8, 0x2f, 0x04, 0x4d,
+                        0x30, 0x83, 0x1c, 0x44, 0x12, 0x28};
+  const uint8_t aad[] = {0xd4, 0x3a, 0x8e, 0x50, 0x89, 0xee, 0xa0, 0xd0,
+                         0x26, 0xc0, 0x3a, 0x85, 0x17, 0x8b, 0x27, 0xda};
+  const uint8_t expected_tag[] = {0x2a, 0x04, 0x9c, 0x04, 0x9d, 0x25,
+                                  0xaa, 0x95, 0x96, 0x9b, 0x45, 0x1d,
+                                  0x93, 0xc3, 0x1c, 0x6e};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  int size = aes_gcm_128_encrypt(nullptr, 0, aad, sizeof(aad), key, iv, ciphertext, tag);
+  BOOST_CHECK(size == 0);
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aes_gcm_128_decrypt(ciphertext, size, aad, sizeof(aad), tag, key, iv, decrypted);
+  BOOST_CHECK(size == 0);
+}
+
+BOOST_AUTO_TEST_CASE(AesGcm3)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 0
+  // Key = bc22f3f05cc40db9311e4192966fee92
+  // IV = 134988e662343c06d3ab83db
+  // CT = 4c0168ab95d3a10ef25e5924108389365c67d97778995892d9fd46897384af61fc559212b3267e90fe4df7bfd1fbed46f4b9ee
+  // AAD = 10087e6ed81049b509c31d12fee88c64
+  // Tag = 771357958a316f166bd0dacc98ea801a
+  // PT = 337c1bc992386cf0f957617fe4d5ec1218ae1cc40369305518eb177e9b15c1646b142ff71237efaa58790080cd82e8848b295c
+  const uint8_t key[] = {0xbc, 0x22, 0xf3, 0xf0, 0x5c, 0xc4, 0x0d, 0xb9,
+                         0x31, 0x1e, 0x41, 0x92, 0x96, 0x6f, 0xee, 0x92};
+  const uint8_t iv[] = {0x13, 0x49, 0x88, 0xe6, 0x62, 0x34,
+                        0x3c, 0x06, 0xd3, 0xab, 0x83, 0xdb};
+  const uint8_t aad[] = {0x10, 0x08, 0x7e, 0x6e, 0xd8, 0x10, 0x49, 0xb5,
+                         0x09, 0xc3, 0x1d, 0x12, 0xfe, 0xe8, 0x8c, 0x64};
+  const uint8_t expected_ciphertext[] = {
+      0x4c, 0x01, 0x68, 0xab, 0x95, 0xd3, 0xa1, 0x0e, 0xf2, 0x5e, 0x59,
+      0x24, 0x10, 0x83, 0x89, 0x36, 0x5c, 0x67, 0xd9, 0x77, 0x78, 0x99,
+      0x58, 0x92, 0xd9, 0xfd, 0x46, 0x89, 0x73, 0x84, 0xaf, 0x61, 0xfc,
+      0x55, 0x92, 0x12, 0xb3, 0x26, 0x7e, 0x90, 0xfe, 0x4d, 0xf7, 0xbf,
+      0xd1, 0xfb, 0xed, 0x46, 0xf4, 0xb9, 0xee};
+  const uint8_t expected_tag[] = {0x77, 0x13, 0x57, 0x95, 0x8a, 0x31,
+                                  0x6f, 0x16, 0x6b, 0xd0, 0xda, 0xcc,
+                                  0x98, 0xea, 0x80, 0x1a};
+  const uint8_t plaintext[] = {
+      0x33, 0x7c, 0x1b, 0xc9, 0x92, 0x38, 0x6c, 0xf0, 0xf9, 0x57, 0x61,
+      0x7f, 0xe4, 0xd5, 0xec, 0x12, 0x18, 0xae, 0x1c, 0xc4, 0x03, 0x69,
+      0x30, 0x55, 0x18, 0xeb, 0x17, 0x7e, 0x9b, 0x15, 0xc1, 0x64, 0x6b,
+      0x14, 0x2f, 0xf7, 0x12, 0x37, 0xef, 0xaa, 0x58, 0x79, 0x00, 0x80,
+      0xcd, 0x82, 0xe8, 0x84, 0x8b, 0x29, 0x5c};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  int size = aes_gcm_128_encrypt(plaintext, sizeof(plaintext), aad, sizeof(aad), key, iv, ciphertext, tag);
+  BOOST_CHECK_EQUAL_COLLECTIONS(ciphertext, ciphertext + size,
+                                expected_ciphertext, expected_ciphertext + sizeof(expected_ciphertext));
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aes_gcm_128_decrypt(ciphertext, size, aad, sizeof(aad), tag, key, iv, decrypted);
+  BOOST_CHECK(memcmp(decrypted, plaintext, sizeof(plaintext)) == 0);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests