open62541/examples/encryption/client_encryption_tpm_keystore.c
Divya Prasanth Prabhakaran 379ee51171 feat(ex): Add client server example to use keys stored in TPM
- Encrypt the private key using the key stored in TPM
   and remove the unencrypted private key from the filesystem
 - Used the encrypted key intermittently for software-based
   encryption/decryption

Change-Id: I46fc24102365292d9af6b51c582e3a3f74b2af5e
2021-10-02 12:23:09 +02:00

435 lines
19 KiB
C

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*
* Copyright 2021 (c) Kalycito Infotech Private Limited
*
*/
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/securitypolicy.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <pkcs11.h>
#include "common.h"
#define MIN_ARGS 7
static void get_MAC(const uint8_t *message, size_t message_len, unsigned char **message_digest,
unsigned int *message_digest_len)
{
EVP_MD_CTX *md_ctx;
if((md_ctx = EVP_MD_CTX_new()) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_MD_CTX_new");
return;
}
if(1 != EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestInit_ex");
return;
}
if(1 != EVP_DigestUpdate(md_ctx, message, message_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestUpdate");
return;
}
if((*message_digest = (unsigned char *)OPENSSL_malloc((size_t)EVP_MD_size(EVP_sha256()))) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while OPENSSL_malloc");
return;
}
if(1 != EVP_DigestFinal_ex(md_ctx, *message_digest, message_digest_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestFinal_ex");
return;
}
EVP_MD_CTX_free(md_ctx);
}
/* If object is found the object_handle is set */
static UA_Boolean
find_object_by_label(CK_SESSION_HANDLE hSession, char *label, unsigned long *object_handle) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_Boolean rtnval = UA_FALSE;
unsigned long foundCount = 0;
do
{
CK_OBJECT_HANDLE hObject = 0;
rv = (UA_StatusCode)C_FindObjects(hSession, &hObject, 1, &foundCount );
if (rv == UA_STATUSCODE_GOOD) {
/* This will show the labels and values */
CK_ATTRIBUTE attrTemplate[] = {
{CKA_LABEL, NULL_PTR, 0}
};
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0)
attrTemplate[0].pValue = (char *)UA_malloc(attrTemplate[0].ulValueLen);
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0) {
char * val = (char *)UA_malloc(attrTemplate[0].ulValueLen + 1);
strncpy(val, (const char*)attrTemplate[0].pValue, attrTemplate[0].ulValueLen);
val[attrTemplate[0].ulValueLen] = '\0';
if (strcasecmp(val, (char *)label) == 0) rtnval = true;
UA_free(val);
*object_handle = hObject;
}
if (attrTemplate[0].pValue)
UA_free(attrTemplate[0].pValue);
} else {
rtnval = UA_FALSE;
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjects failed = 0x%.8lx", (long unsigned int)rv);
}
} while( rv == UA_STATUSCODE_GOOD && foundCount > 0 && !rtnval);
return rtnval;
}
static UA_StatusCode
decrypt_data(unsigned long slotNum, unsigned char *pin, char *label, UA_ByteString *in_data,
UA_ByteString **decrypted_data, UA_ByteString * iv_data, uint64_t orginal_data_length) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_ByteString *out_data = *decrypted_data;
unsigned long *pSlotList = NULL;
unsigned long slotID = 0;
unsigned long int ulSlotCount = 0;
unsigned long hSession;
CK_SESSION_INFO sessionInfo;
unsigned long hObject = 0;
unsigned char *ptr_encrypted_data = NULL;
unsigned long encrypted_data_length = 0;
unsigned char *data_clear = NULL;
unsigned long clear_data_length = 0;
unsigned long declen = 16;
unsigned char iv[iv_data->length];
UA_Boolean key_object_found = UA_FALSE;
CK_OBJECT_CLASS key_class = CKO_SECRET_KEY;
CK_KEY_TYPE key_type = CKK_AES;
CK_ATTRIBUTE attrTemplate[] = {
{CKA_CLASS, &key_class, sizeof(key_class)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
{CKA_LABEL, (void *)label, strlen(label)}
};
if (iv_data && iv_data->length > 0 && iv_data->data) {
rv = (UA_StatusCode)C_Initialize(NULL_PTR);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Initialize failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"The initializtion vector is not valid");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, NULL, &ulSlotCount);
if ((rv == UA_STATUSCODE_GOOD) && (ulSlotCount > 0)) {
pSlotList = (unsigned long*)UA_malloc(ulSlotCount * sizeof (unsigned long));
if (pSlotList == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"System error: Unable to allocate memory");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot list = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot count = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (unsigned long int i = 0; i < ulSlotCount; i++) {
slotID = pSlotList[i];
if (slotID == slotNum) {
CK_TOKEN_INFO token_info;
rv = (UA_StatusCode)C_GetTokenInfo(slotID, &token_info);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetTokenInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
break;
}
}
rv = (UA_StatusCode)C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, (CK_VOID_PTR) NULL, NULL, &hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_OpenSession failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
rv = (UA_StatusCode)C_GetSessionInfo(hSession, &sessionInfo);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetSessionInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if(sessionInfo.state != CKS_RW_USER_FUNCTIONS) {
/* Logs a user into a token */
rv = (UA_StatusCode)C_Login(hSession, CKU_USER, pin, strlen((const char *)pin));
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Login failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
}
rv = (UA_StatusCode)C_FindObjectsInit(hSession, attrTemplate, sizeof(attrTemplate)/sizeof (CK_ATTRIBUTE));
if (rv == UA_STATUSCODE_GOOD) {
key_object_found = find_object_by_label(hSession, label, &hObject);
if (key_object_found == UA_FALSE){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error: key object not found");
goto cleanup;
}
rv = (UA_StatusCode)C_FindObjectsFinal(hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (size_t i=0; i < iv_data->length; i++) {
iv[i] = *((CK_BYTE *)(iv_data->data + i));
}
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, sizeof(iv)};
encrypted_data_length = in_data->length;
clear_data_length = encrypted_data_length;
ptr_encrypted_data = (CK_BYTE *)(UA_malloc(encrypted_data_length * sizeof(CK_BYTE)));
memset(ptr_encrypted_data, 0, encrypted_data_length);
memcpy(ptr_encrypted_data, (CK_BYTE *)(in_data->data), in_data->length);
rv = (UA_StatusCode)C_DecryptInit(hSession, &mechanism, hObject);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
data_clear = (CK_BYTE *)UA_malloc(clear_data_length * sizeof(CK_BYTE));
memset(data_clear, 0, clear_data_length);
unsigned long part_number = 0;
while (rv == UA_STATUSCODE_GOOD && part_number * 16 < clear_data_length - 16) {
rv = (UA_StatusCode)C_DecryptUpdate(hSession, ptr_encrypted_data + part_number * 16,
16, &data_clear[part_number*16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Decryptupdate failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
part_number++;
}
rv = (UA_StatusCode)C_DecryptFinal(hSession, &data_clear[part_number *16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if (orginal_data_length > clear_data_length)
orginal_data_length = 0;
if (out_data->data) {
UA_free(out_data->data);
out_data->length = 0;
}
out_data->data = (UA_Byte *)UA_malloc(orginal_data_length);
memcpy(out_data->data, data_clear, orginal_data_length);
out_data->length = orginal_data_length;
C_Logout(hSession);
C_CloseSession(hSession);
C_Finalize(NULL);
cleanup:
if (data_clear)
UA_free(data_clear);
if (pSlotList)
UA_free(pSlotList);
if (ptr_encrypted_data)
UA_free(ptr_encrypted_data);
return rv;
}
static UA_StatusCode
decrypt(unsigned long slotNum, unsigned char *pin, char *label,
UA_ByteString *in_data, UA_ByteString **decrypted_data) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
/* For decrypt
Calculate the HMAC of the output without the 32 byte (256 bit) md_value
Check the calcualted md with the md from the input
if they match continue to decrypt the input */
unsigned char *md_value = NULL;
unsigned int md_len;
unsigned int expected_md_len = 32;
UA_ByteString *enc_data = UA_ByteString_new();
UA_ByteString *iv_data = UA_ByteString_new();
/* Get the HMAC */
get_MAC(in_data->data, in_data->length - expected_md_len, &md_value, &md_len);
if (memcmp(in_data->data + in_data->length - expected_md_len, md_value, md_len) == 0) {
/* Get the iv that was appended to the encrypted data
find the iv. It was packed in the first 16 bytes of the last 56 in the file */
UA_ByteString_allocBuffer(iv_data, 16);
uint8_t * ptr_in_data = (uint8_t *)(in_data->data);
memcpy(iv_data->data, ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length, iv_data->length);
/* Find the data length in output */
uint64_t clear_data_length;
clear_data_length = *((uint64_t *)(ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len));
/* Remove the extra 56 bytes that were added at the end of the encrypted data */
UA_ByteString_allocBuffer(enc_data, in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length);
memcpy(enc_data->data, ptr_in_data, enc_data->length);
rv = (UA_StatusCode)decrypt_data(slotNum, pin, label, enc_data, decrypted_data, iv_data, clear_data_length);
if(rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "decrypt_data failed");
}
UA_ByteString_delete(iv_data);
UA_ByteString_delete(enc_data);
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "HMAC does not match");
return EXIT_FAILURE;
}
if(md_value) OPENSSL_free(md_value);
return rv;
}
int main(int argc, char* argv[]) {
if(argc < MIN_ARGS) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Arguments are missing. The required arguments are "
"<opc.tcp://host:port> "
"<client-certificate.der> <client-private-key.der> "
"<slotId> <userPin> <keyLable> "
"[<trustlist1.crl>, ...]");
return EXIT_FAILURE;
}
char *keyLabel = NULL;
unsigned long slotId = 0;
unsigned char *userpin = NULL;
UA_ByteString privateKey = UA_BYTESTRING_NULL;
UA_ByteString certificate = UA_BYTESTRING_NULL;
UA_ByteString *encrypt_out_data = NULL;
UA_ByteString *certificate_out_data = NULL;
const char *endpointUrl = argv[1];
/* Load certificate and private key */
UA_ByteString certificate_in_date = loadFile(argv[2]);
UA_ByteString encrypt_in_data = loadFile(argv[3]);
slotId = (unsigned long)atoi(argv[4]);
userpin = (unsigned char*)argv[5];
keyLabel = argv[6];
encrypt_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
encrypt_out_data->data = NULL;
encrypt_out_data->length = 0;
UA_StatusCode rv = decrypt(slotId, userpin, keyLabel, &encrypt_in_data, &encrypt_out_data);
if (rv != UA_STATUSCODE_GOOD) {
fprintf(stderr, "Decrypt failed for RSA privare key");
return EXIT_FAILURE;
}
privateKey.data = encrypt_out_data->data;
privateKey.length = encrypt_out_data->length;
certificate_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
certificate_out_data->data = NULL;
certificate_out_data->length = 0;
rv = decrypt(slotId, userpin, keyLabel, &certificate_in_date, &certificate_out_data);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Decrypt failed for certificate");
return EXIT_FAILURE;
}
certificate.data = certificate_out_data->data;
certificate.length = certificate_out_data->length;
/* Load the trustList. Load revocationList is not supported now */
size_t trustListSize = 0;
if(argc > MIN_ARGS)
trustListSize = (size_t)argc-MIN_ARGS;
UA_STACKARRAY(UA_ByteString, trustList, trustListSize+1);
for(size_t trustListCount = 0; trustListCount < trustListSize; trustListCount++)
trustList[trustListCount] = loadFile(argv[trustListCount+7]);
UA_ByteString *revocationList = NULL;
size_t revocationListSize = 0;
UA_Client *client = UA_Client_new();
UA_ClientConfig *cc = UA_Client_getConfig(client);
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
trustList, trustListSize,
revocationList, revocationListSize);
if (encrypt_out_data) {
if (encrypt_out_data->data)
UA_free(encrypt_out_data->data);
UA_free(encrypt_out_data);
}
UA_ByteString_clear(&certificate_in_date);
UA_ByteString_clear(&encrypt_in_data);
for(size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++) {
UA_ByteString_clear(&trustList[deleteCount]);
}
/* Secure client connect */
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; /* require encryption */
UA_StatusCode retval = UA_Client_connect(client, endpointUrl);
if(retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
return EXIT_FAILURE;
}
UA_Variant value;
UA_Variant_init(&value);
/* NodeId of the variable holding the current time */
const UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
retval = UA_Client_readValueAttribute(client, nodeId, &value);
if(retval == UA_STATUSCODE_GOOD &&
UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME])) {
UA_DateTime raw_date = *(UA_DateTime *) value.data;
UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "date is: %u-%u-%u %u:%u:%u.%03u\n",
dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
}
/* Clean up */
UA_Variant_clear(&value);
UA_Client_delete(client);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}