aboutsummaryrefslogtreecommitdiffstats
path: root/libparc/parc/security/parc_SymmetricKeyStore.c
diff options
context:
space:
mode:
Diffstat (limited to 'libparc/parc/security/parc_SymmetricKeyStore.c')
-rw-r--r--libparc/parc/security/parc_SymmetricKeyStore.c477
1 files changed, 477 insertions, 0 deletions
diff --git a/libparc/parc/security/parc_SymmetricKeyStore.c b/libparc/parc/security/parc_SymmetricKeyStore.c
new file mode 100644
index 00000000..38996dba
--- /dev/null
+++ b/libparc/parc/security/parc_SymmetricKeyStore.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ */
+/**
+ * To compute an HMAC, we need to interfaceement our own CryptoHasher so we can
+ * initialize it with the secret key
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/safestack.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+#include <openssl/aes.h>
+#include <openssl/hmac.h>
+
+#include <LongBow/runtime.h>
+#include <LongBow/longBow_Compiler.h>
+
+#include <parc/algol/parc_Object.h>
+#include <parc/algol/parc_Memory.h>
+
+#include <parc/security/parc_Security.h>
+#include <parc/security/parc_KeyStore.h>
+#include <parc/security/parc_SymmetricKeyStore.h>
+
+#define AES_KEYSTORE_VERSION 1L
+#define IV_SIZE 16
+#define AES_MAX_DIGEST_SIZE 128
+#define AES_DEFAULT_DIGEST_ALGORITHM "SHA256"
+
+struct parc_symmetric_keystore {
+ PARCBuffer *secretKey;
+};
+
+static PARCBuffer *
+_createDerivedKey(const char *key, size_t keylength, unsigned char *salt, unsigned int saltlen)
+{
+ unsigned char buffer[SHA256_DIGEST_LENGTH];
+ HMAC(EVP_sha256(), key, (int) keylength, salt, saltlen, buffer, NULL);
+ return parcBuffer_PutArray(parcBuffer_Allocate(SHA256_DIGEST_LENGTH), SHA256_DIGEST_LENGTH, buffer);
+}
+
+static PARCCryptoHash *
+_getSecretKeyDigest(PARCSymmetricKeyStore *keyStore)
+{
+ PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256);
+ parcCryptoHasher_Init(hasher);
+
+ parcCryptoHasher_UpdateBuffer(hasher, keyStore->secretKey);
+ PARCCryptoHash *result = parcCryptoHasher_Finalize(hasher);
+ parcCryptoHasher_Release(&hasher);
+
+ return result;
+}
+
+static bool
+_parcSymmetricKeyStore_Finalize(PARCSymmetricKeyStore **interfacePtr)
+{
+ PARCSymmetricKeyStore *keyStore = *interfacePtr;
+ if (keyStore->secretKey != NULL) {
+ parcBuffer_Release(&keyStore->secretKey);
+ }
+ return true;
+}
+
+PARCKeyStoreInterface *PARCSymmetricKeyStoreAsKeyStore = &(PARCKeyStoreInterface) {
+ .getVerifierKeyDigest = (PARCKeyStoreGetVerifierKeyDigest *) _getSecretKeyDigest,
+ .getCertificateDigest = NULL,
+ .getDEREncodedCertificate = NULL,
+ .getDEREncodedPublicKey = NULL,
+ .getDEREncodedPrivateKey = NULL,
+};
+
+parcObject_ImplementAcquire(parcSymmetricKeyStore, PARCSymmetricKeyStore);
+parcObject_ImplementRelease(parcSymmetricKeyStore, PARCSymmetricKeyStore);
+
+parcObject_Override(PARCSymmetricKeyStore, PARCObject,
+ .destructor = (PARCObjectDestructor *) _parcSymmetricKeyStore_Finalize);
+
+// =============================================================
+LONGBOW_STOP_DEPRECATED_WARNINGS
+// =============================================================
+
+/**
+ * The openssl ASN1 representation of the PARC symmetric key keystore.
+ * It will be written to disk in DER format with an i2d call
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+typedef struct PARCAESKeystore_info_st {
+ ASN1_INTEGER *version;
+ ASN1_OBJECT *algorithm_oid;
+ ASN1_OCTET_STRING *encrypted_key;
+} _PARCSymmeticSignerFileStoreInfo;
+
+// This generates a name that is not compliant with the PARC Naming conventions.
+DECLARE_ASN1_FUNCTIONS(_PARCSymmeticSignerFileStoreInfo)
+
+ASN1_SEQUENCE(_PARCSymmeticSignerFileStoreInfo) = {
+ ASN1_SIMPLE(_PARCSymmeticSignerFileStoreInfo, version, ASN1_INTEGER),
+ ASN1_SIMPLE(_PARCSymmeticSignerFileStoreInfo, algorithm_oid, ASN1_OBJECT),
+ ASN1_SIMPLE(_PARCSymmeticSignerFileStoreInfo, encrypted_key, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(_PARCSymmeticSignerFileStoreInfo)
+
+IMPLEMENT_ASN1_FUNCTIONS(_PARCSymmeticSignerFileStoreInfo)
+
+static int
+_i2d_AESKeystore_fp(FILE *fp, _PARCSymmeticSignerFileStoreInfo *aki)
+{
+ return ASN1_item_i2d_fp(ASN1_ITEM_rptr(_PARCSymmeticSignerFileStoreInfo), fp, aki);
+}
+
+static _PARCSymmeticSignerFileStoreInfo *
+_d2iAESKeystoreFp(FILE *fp, _PARCSymmeticSignerFileStoreInfo *aki)
+{
+ return ASN1_item_d2i_fp(ASN1_ITEM_rptr(_PARCSymmeticSignerFileStoreInfo), fp, aki);
+}
+
+static bool
+_createKeyStore(const char *filename, const char *password, PARCBuffer *key)
+{
+ FILE *fp = NULL;
+ int fd = -1;
+ int ans = -1;
+ int nid;
+
+ _PARCSymmeticSignerFileStoreInfo *keystore = NULL;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+#else
+ EVP_CIPHER_CTX ctx;
+#endif
+ unsigned char *encrypted_key = NULL;
+
+ int ekl = IV_SIZE + (int) parcBuffer_Remaining(key) + SHA256_DIGEST_LENGTH + AES_BLOCK_SIZE;
+ int encrypt_length;
+
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
+
+ if (fd == -1) {
+ goto Bail;
+ }
+ fp = fdopen(fd, "wb");
+ if (fp == NULL) {
+ goto Bail;
+ }
+
+ PARCBuffer *aes_key = _createDerivedKey(password, strlen(password), (unsigned char *) "\0", 1);
+ PARCBuffer *mac_key = _createDerivedKey(password, strlen(password), (unsigned char *) "\1", 1);
+
+ encrypted_key = malloc(ekl);
+ if (!encrypted_key) {
+ goto Bail;
+ }
+ RAND_bytes(encrypted_key, IV_SIZE);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX_init(ctx);
+ if (!EVP_EncryptInit(ctx, EVP_aes_256_cbc(), parcByteArray_Array(parcBuffer_Array(aes_key)), encrypted_key)) {
+ goto Bail;
+ }
+
+ unsigned char *p;
+ p = encrypted_key + IV_SIZE;
+ if (!EVP_EncryptUpdate(ctx, p, &encrypt_length, parcByteArray_Array(parcBuffer_Array(key)), (int) parcBuffer_Remaining(key))) {
+ goto Bail;
+ }
+ p += encrypt_length;
+ if (!EVP_EncryptFinal(ctx, p, &encrypt_length)) {
+ goto Bail;
+ }
+#else
+ EVP_CIPHER_CTX_init(&ctx);
+ if (!EVP_EncryptInit(&ctx, EVP_aes_256_cbc(), parcByteArray_Array(parcBuffer_Array(aes_key)), encrypted_key)) {
+ goto Bail;
+ }
+
+ unsigned char *p;
+ p = encrypted_key + IV_SIZE;
+ if (!EVP_EncryptUpdate(&ctx, p, &encrypt_length, parcByteArray_Array(parcBuffer_Array(key)), (int) parcBuffer_Remaining(key))) {
+ goto Bail;
+ }
+ p += encrypt_length;
+ if (!EVP_EncryptFinal(&ctx, p, &encrypt_length)) {
+ goto Bail;
+ }
+#endif
+ p += encrypt_length;
+ HMAC(EVP_sha256(), parcByteArray_Array(parcBuffer_Array(mac_key)), SHA256_DIGEST_LENGTH, encrypted_key, p - encrypted_key, p, NULL);
+
+ if (!(keystore = _PARCSymmeticSignerFileStoreInfo_new())) {
+ goto Bail;
+ }
+ if (!(keystore->version = ASN1_INTEGER_new())) {
+ goto Bail;
+ }
+ if (!ASN1_INTEGER_set(keystore->version, AES_KEYSTORE_VERSION)) {
+ goto Bail;
+ }
+
+ keystore->algorithm_oid = OBJ_txt2obj(AES_DEFAULT_DIGEST_ALGORITHM, 0);
+ nid = OBJ_obj2nid(keystore->algorithm_oid);
+ if (nid == NID_undef) {
+ goto Bail; // Shouldn't happen now but could later if we support more algorithms
+ }
+ if (!ASN1_OCTET_STRING_set(keystore->encrypted_key, encrypted_key, ekl)) {
+ goto Bail;
+ }
+ _i2d_AESKeystore_fp(fp, keystore);
+ ans = 0;
+ goto cleanup;
+
+ Bail:
+ ans = -1;
+ cleanup:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX_free(ctx);
+#endif
+ parcBuffer_Release(&aes_key);
+ parcBuffer_Release(&mac_key);
+
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (encrypted_key) {
+ free(encrypted_key);
+ }
+ if (keystore) {
+ _PARCSymmeticSignerFileStoreInfo_free(keystore);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+ return (ans);
+}
+
+static PARCBuffer *
+_AESKeyStoreInit(const char *filename, const char *password)
+{
+ PARCBuffer *secret_key = NULL;
+
+ FILE *fp = NULL;
+ _PARCSymmeticSignerFileStoreInfo *ki = NULL;
+ int version;
+ char oidstr[80];
+
+ PARCBuffer *aes_key = NULL;
+ PARCBuffer *mac_key = NULL;
+
+ unsigned char check[SHA256_DIGEST_LENGTH];
+ unsigned char *keybuf = NULL;
+ int check_start;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+#else
+ EVP_CIPHER_CTX ctx;
+#endif
+ int length = 0;
+ int final_length = 0;
+
+ fp = fopen(filename, "rb");
+ if (fp == NULL) {
+ goto Bail;
+ }
+
+ ki = _d2iAESKeystoreFp(fp, NULL);
+ fclose(fp);
+ if (ki == NULL) {
+ goto Bail;
+ }
+
+ version = (int) ASN1_INTEGER_get(ki->version);
+ if (version != AES_KEYSTORE_VERSION) {
+ goto Bail;
+ }
+
+ OBJ_obj2txt(oidstr, sizeof(oidstr), ki->algorithm_oid, 0);
+ if (strcasecmp(oidstr, AES_DEFAULT_DIGEST_ALGORITHM)) {
+ goto Bail;
+ }
+
+ if (ki->encrypted_key->length < IV_SIZE + (SHA256_DIGEST_LENGTH * 2) + AES_BLOCK_SIZE) {
+ goto Bail;
+ }
+
+ aes_key = _createDerivedKey(password, strlen(password), (unsigned char *) "\0", 1);
+ mac_key = _createDerivedKey(password, strlen(password), (unsigned char *) "\1", 1);
+
+ check_start = ki->encrypted_key->length - SHA256_DIGEST_LENGTH;
+ HMAC(EVP_sha256(),
+ parcByteArray_Array(parcBuffer_Array(mac_key)),
+ SHA256_DIGEST_LENGTH,
+ ki->encrypted_key->data,
+ check_start,
+ check,
+ NULL);
+
+ if (memcmp(&ki->encrypted_key->data[check_start], check, SHA256_DIGEST_LENGTH)) {
+ goto Bail;
+ }
+ keybuf = malloc(SHA256_DIGEST_LENGTH + AES_BLOCK_SIZE);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX_init(ctx);
+ if (!EVP_DecryptInit(ctx, EVP_aes_256_cbc(), parcByteArray_Array(parcBuffer_Array(aes_key)), ki->encrypted_key->data)) {
+ goto Bail;
+ }
+ if (!EVP_DecryptUpdate(ctx, keybuf, &length, &ki->encrypted_key->data[IV_SIZE],
+ ki->encrypted_key->length - IV_SIZE - SHA256_DIGEST_LENGTH)) {
+ goto Bail;
+ }
+
+ if (!EVP_DecryptFinal(ctx, keybuf + length, &final_length)) {
+ goto Bail;
+ }
+#else
+ EVP_CIPHER_CTX_init(&ctx);
+ if (!EVP_DecryptInit(&ctx, EVP_aes_256_cbc(), parcByteArray_Array(parcBuffer_Array(aes_key)), ki->encrypted_key->data)) {
+ goto Bail;
+ }
+ if (!EVP_DecryptUpdate(&ctx, keybuf, &length, &ki->encrypted_key->data[IV_SIZE],
+ ki->encrypted_key->length - IV_SIZE - SHA256_DIGEST_LENGTH)) {
+ goto Bail;
+ }
+
+ if (!EVP_DecryptFinal(&ctx, keybuf + length, &final_length)) {
+ goto Bail;
+ }
+#endif
+ secret_key = parcBuffer_CreateFromArray(keybuf, length);
+ parcBuffer_Flip(secret_key);
+
+ goto out;
+
+ Bail:
+ free(keybuf);
+
+ out:
+ if (aes_key) {
+ parcBuffer_Release(&aes_key);
+ }
+
+ if (mac_key) {
+ parcBuffer_Release(&mac_key);
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ EVP_CIPHER_CTX_free(ctx);
+#endif
+ return secret_key;
+}
+
+/**
+ * Create a symmetric (secret) key of the given bit length (e.g. 256)
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+PARCBuffer *
+parcSymmetricKeyStore_CreateKey(unsigned bits)
+{
+ assertTrue((bits & 0x07) == 0, "bits must be a multiple of 8");
+
+ unsigned keylength = bits / 8;
+ uint8_t buffer[keylength];
+ RAND_bytes(buffer, keylength);
+ return parcBuffer_Flip(parcBuffer_PutArray(parcBuffer_Allocate(keylength), keylength, buffer));
+}
+
+PARCBuffer *
+parcSymmetricKeyStore_GetKey(PARCSymmetricKeyStore *keyStore)
+{
+ return keyStore->secretKey;
+}
+
+PARCCryptoHash *
+parcSymmetricKeyStore_GetVerifierKeyDigest(PARCSymmetricKeyStore *keyStore)
+{
+ return _getSecretKeyDigest(keyStore);
+}
+
+/**
+ * Creates a PARC format symmetric keystore. It only contains a single key.
+ *
+ * The final filename will be "file_prefix.
+ *
+ * Returns 0 on success, -1 on error.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+bool
+parcSymmetricKeyStore_CreateFile(const char *filename, const char *password, PARCBuffer *secret_key)
+{
+ assertTrue(parcBuffer_Remaining(secret_key) > 0, "The secret_key buffer is not flipped. See parcBuffer_Flip()");
+ return _createKeyStore(filename, password, secret_key) == 0;
+}
+
+/**
+ * Create a PKCS12 signing context for use in ccnx_Signing. It is destroyed
+ * by ccnx_Signing when the signing context is destroyed.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+PARCSymmetricKeyStore *
+parcSymmetricKeyStore_OpenFile(const char *filename, const char *password, PARCCryptoHashType hmacHashType)
+{
+ PARCBuffer *secretKey = _AESKeyStoreInit(filename, password);
+ assertNotNull(secretKey, "Could not read AES keystore %s", filename);
+
+ PARCSymmetricKeyStore *keyStore = parcSymmetricKeyStore_Create(secretKey);
+ parcBuffer_Release(&secretKey);
+
+ return keyStore;
+}
+
+/**
+ * Create a PKCS12 signing context for use in ccnx_Signing from the provided key. It is destroyed
+ * by parc_Signing when the signing context is destroyed.
+ *
+ * Example:
+ * @code
+ * <#example#>
+ * @endcode
+ */
+PARCSymmetricKeyStore *
+parcSymmetricKeyStore_Create(PARCBuffer *secret_key)
+{
+ PARCSymmetricKeyStore *keyStore = parcObject_CreateAndClearInstance(PARCSymmetricKeyStore);
+ assertNotNull(keyStore, "parcObject_CreateAndClearInstance returned NULL, cannot allocate keystore");
+
+ keyStore->secretKey = parcBuffer_Acquire(secret_key);
+
+ return keyStore;
+}
+
+// =============================================================
+LONGBOW_START_DEPRECATED_WARNINGS
+// =============================================================