Storing Data Objects on NitroKey HSM 2

Hi,

as part of a thesis, I’m looking into whether it’s possible to use USB or smartcard tokens for database encryption instead of a larger HSM. Considering the mutual PKCS#11 interface I expected this to be quite feasible and bought a NitroKey HSM 2. However, it has since become clear that the provided interfaces don’t fully implement PKCS#11, and therefore won’t work out of the box.

I’ve decided to build onto https://github.com/CardContact/sc-hsm-embedded/tree/aes as it seems to provide most of the requirements, including AES key generation, encryption, and decryption. Still, it lacks the ability to create and manage data objects. I’ve attempted to implement this feature in sc_hsm_C_CreateObject, but this is difficult with my limited knowledge of the token’s inner workings. So far, I’ve only succeeded in creating what appear to be a session objects despite CKA_TOKEN being set to true. I hope that with correct driver code, it will be possible to store data objects on the token.

It seems like the problem lies in the following code segment and that either the generated FID is not accepted or writeEF expects the data to be encoded in a particular way or written somewhere else.

fid = determineFreeKeyId(slot, DATA_PREFIX);
if (fid < 0)
	FUNC_FAILS(CKR_DEVICE_ERROR, "Determine free id failed");
fid = (DATA_PREFIX << 8) | (fid & 0xFF);
rc = writeEF(slot, fid, val, vallen);

For reference, this is the entire function pertaining to data objects:

static int sc_hsm_C_CreateObject(
	struct p11Slot_t* slot,
	CK_ATTRIBUTE_PTR pTemplate,
	CK_ULONG ulCount,
	struct p11Object_t** pp11o)
{
	int pos, rc, vallen, idlen;
	unsigned short fid, certfid;
	CK_CERTIFICATE_TYPE ct;
	
	CK_ATTRIBUTE idattr = { CKA_ID, NULL, 0 };
	CK_OBJECT_CLASS objclass;
	unsigned char* val, * po, * id;
	struct p11Object_t* p11Key, * p11o;
	struct p15CertificateDescription* p15cert;
	unsigned char buff[512];
	struct bytebuffer_s bb = { buff, 0, sizeof(buff) };

	pos = findAttributeInTemplate(CKA_CLASS, pTemplate, ulCount);
	if (pos == -1)
		FUNC_FAILS(CKR_TEMPLATE_INCOMPLETE, "CKA_CLASS not found in template");

	rc = validateAttribute(&pTemplate[pos], sizeof(CK_OBJECT_CLASS));
	if (rc != CKR_OK)
		FUNC_FAILS(rc, "CKA_CLASS");

	objclass = *(CK_OBJECT_CLASS*)pTemplate[pos].pValue;
	if ((objclass != CKO_CERTIFICATE) && (objclass != CKO_DATA))
		FUNC_FAILS(CKR_TEMPLATE_INCONSISTENT, "CKA_CLASS must be CKO_CERTIFICATE or CKO_DATA");

	pos = findAttributeInTemplate(CKA_VALUE, pTemplate, ulCount);
	if (pos == -1)
		FUNC_FAILS(CKR_TEMPLATE_INCOMPLETE, "CKA_VALUE not found in template");

	rc = validateAttribute(&pTemplate[pos], 0);
	if (rc != CKR_OK)
		FUNC_FAILS(rc, "CKA_VALUE");

	val = (unsigned char*)pTemplate[pos].pValue;
	vallen = pTemplate[pos].ulValueLen;

	id = NULL;
	p11Key = NULL;
	p11o = NULL;
	idlen = 0;

	if (objclass == CKO_CERTIFICATE) {
		// Long and not relevant here
	}
	else if (objclass == CKO_DATA) {

		if (p11Key != NULL) {
			fid = p11Key->tokenid;
			rc = writeEF(slot, (DATA_PREFIX << 8) | p11Key->tokenid, val, vallen);
			if (rc < 0)
				FUNC_FAILS(CKR_DEVICE_ERROR, "Error writing data object");
		}
		else {
			fid = determineFreeKeyId(slot, DATA_PREFIX);
			if (fid < 0)
				FUNC_FAILS(CKR_DEVICE_ERROR, "Determine free id failed");
			
			fid = (DATA_PREFIX << 8) | (fid & 0xFF);

			rc = writeEF(slot, fid, val, vallen);
			if (rc < 0)
				FUNC_FAILS(CKR_DEVICE_ERROR, "Error writing data object");

			if (p11o != NULL) {
				removeTokenObject(slot->token, p11o->handle, TRUE);
			}

			p11o = calloc(sizeof(struct p11Object_t), 1);
			if (p11o == NULL) {
				FUNC_FAILS(CKR_HOST_MEMORY, "Out of memory");
			}

			rc = createDataObject(pTemplate, ulCount, p11o);
			if (rc != CKR_OK) {
				free(p11o);
				FUNC_FAILS(rc, "Could not create data object");
			}
			p11o->tokenid = (int)fid;
		}
	}

	addObject(slot->token, p11o, TRUE);
	*pp11o = p11o;

	FUNC_RETURNS(CKR_OK);
}

Perhaps someone more experienced can point me towards the problem and/or relevant resources on developing drivers for the token.

The sc-hsm-embedded PKCS#11 module does indeed not support data objects, OpenSC does and the main use case is to store the key file from Truecrypt. Other than for this specific purpose, storing data objects on the token is rarely used.

That said, I’d suggest to implement data objects similar to the approach taken in OpenSC. You need an encoder/decoder for PKSC#15 DCOD objects and persist PKCS#11 objects into the token. During startup you need to read the PKCS#15 DCOD meta-data and recreate the PKCS#11 data objects. When reading the CKA_VALUE from data objects you just use readEF to retrieve data from the token.

Chapter 6 in the user manual describes how PKCS#15 objects are used to discover token objects. I’d suggest to start by writing an object to the token using OpenSC and then implement code to discover and read it.

But once again, this is not a trivial task and it is a rarely used function.

Why would you want to store data objects (other than keys) on the token anyway ?

Thanks for the information. With your pointers I’ve managed to implement basic reading and writing of data objects. The reason why I needed it, trivial as it sounds, is that the database encryption requires it to function. Keys and metadata are both stored on the HSM, or, in this case, the token.

However, I’ve stumbled onto the next problem: The database expects the CKM_AES_CBC_PAD mechanism instead of CKM_AES_CBC which the token supports. From what I gathered, the sole difference between these mechanisms is that CKM_AES_CBC_PAD adds padding to support arbitrary block sizes, which I expect to be doable. Is there a caveat to adding the padding in the driver and using the token’s CKM_AES_CBC mechanism?

The only difference are the additional bytes added to pad to the full block length. You can do that in the application.

The SmartCard-HSM has a limit of 1216 bytes in C_Encrypt. It does not yet split a larger input into chunks.