michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "seccomon.h" michael@0: #include "secoid.h" michael@0: #include "secasn1.h" michael@0: #include "pkcs11.h" michael@0: #include "pk11func.h" michael@0: #include "pk11sdr.h" michael@0: michael@0: /* michael@0: * Data structure and template for encoding the result of an SDR operation michael@0: * This is temporary. It should include the algorithm ID of the encryption mechanism michael@0: */ michael@0: struct SDRResult michael@0: { michael@0: SECItem keyid; michael@0: SECAlgorithmID alg; michael@0: SECItem data; michael@0: }; michael@0: typedef struct SDRResult SDRResult; michael@0: michael@0: SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) michael@0: michael@0: static SEC_ASN1Template template[] = { michael@0: { SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) }, michael@0: { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) }, michael@0: { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg), michael@0: SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, michael@0: { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) }, michael@0: { 0 } michael@0: }; michael@0: michael@0: static unsigned char keyID[] = { michael@0: 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, michael@0: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 michael@0: }; michael@0: michael@0: static SECItem keyIDItem = { michael@0: 0, michael@0: keyID, michael@0: sizeof keyID michael@0: }; michael@0: michael@0: /* local utility function for padding an incoming data block michael@0: * to the mechanism block size. michael@0: */ michael@0: static SECStatus michael@0: padBlock(SECItem *data, int blockSize, SECItem *result) michael@0: { michael@0: SECStatus rv = SECSuccess; michael@0: int padLength; michael@0: unsigned int i; michael@0: michael@0: result->data = 0; michael@0: result->len = 0; michael@0: michael@0: /* This algorithm always adds to the block (to indicate the number michael@0: * of pad bytes). So allocate a block large enough. michael@0: */ michael@0: padLength = blockSize - (data->len % blockSize); michael@0: result->len = data->len + padLength; michael@0: result->data = (unsigned char *)PORT_Alloc(result->len); michael@0: michael@0: /* Copy the data */ michael@0: PORT_Memcpy(result->data, data->data, data->len); michael@0: michael@0: /* Add the pad values */ michael@0: for(i = data->len; i < result->len; i++) michael@0: result->data[i] = (unsigned char)padLength; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static SECStatus michael@0: unpadBlock(SECItem *data, int blockSize, SECItem *result) michael@0: { michael@0: SECStatus rv = SECSuccess; michael@0: int padLength; michael@0: unsigned int i; michael@0: michael@0: result->data = 0; michael@0: result->len = 0; michael@0: michael@0: /* Remove the padding from the end if the input data */ michael@0: if (data->len == 0 || data->len % blockSize != 0) { rv = SECFailure; goto loser; } michael@0: michael@0: padLength = data->data[data->len-1]; michael@0: if (padLength > blockSize) { rv = SECFailure; goto loser; } michael@0: michael@0: /* verify padding */ michael@0: for (i=data->len - padLength; i < data->len; i++) { michael@0: if (data->data[i] != padLength) { michael@0: rv = SECFailure; michael@0: goto loser; michael@0: } michael@0: } michael@0: michael@0: result->len = data->len - padLength; michael@0: result->data = (unsigned char *)PORT_Alloc(result->len); michael@0: if (!result->data) { rv = SECFailure; goto loser; } michael@0: michael@0: PORT_Memcpy(result->data, data->data, result->len); michael@0: michael@0: if (padLength < 2) { michael@0: return SECWouldBlock; michael@0: } michael@0: michael@0: loser: michael@0: return rv; michael@0: } michael@0: michael@0: static PRLock *pk11sdrLock = NULL; michael@0: michael@0: void michael@0: pk11sdr_Init (void) michael@0: { michael@0: pk11sdrLock = PR_NewLock(); michael@0: } michael@0: michael@0: void michael@0: pk11sdr_Shutdown(void) michael@0: { michael@0: if (pk11sdrLock) { michael@0: PR_DestroyLock(pk11sdrLock); michael@0: pk11sdrLock = NULL; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * PK11SDR_Encrypt michael@0: * Encrypt a block of data using the symmetric key identified. The result michael@0: * is an ASN.1 (DER) encoded block of keyid, params and data. michael@0: */ michael@0: SECStatus michael@0: PK11SDR_Encrypt(SECItem *keyid, SECItem *data, SECItem *result, void *cx) michael@0: { michael@0: SECStatus rv = SECSuccess; michael@0: PK11SlotInfo *slot = 0; michael@0: PK11SymKey *key = 0; michael@0: SECItem *params = 0; michael@0: PK11Context *ctx = 0; michael@0: CK_MECHANISM_TYPE type; michael@0: SDRResult sdrResult; michael@0: SECItem paddedData; michael@0: SECItem *pKeyID; michael@0: PLArenaPool *arena = 0; michael@0: michael@0: /* Initialize */ michael@0: paddedData.len = 0; michael@0: paddedData.data = 0; michael@0: michael@0: arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); michael@0: if (!arena) { rv = SECFailure; goto loser; } michael@0: michael@0: /* 1. Locate the requested keyid, or the default key (which has a keyid) michael@0: * 2. Create an encryption context michael@0: * 3. Encrypt michael@0: * 4. Encode the results (using ASN.1) michael@0: */ michael@0: michael@0: slot = PK11_GetInternalKeySlot(); michael@0: if (!slot) { rv = SECFailure; goto loser; } michael@0: michael@0: /* Use triple-DES */ michael@0: type = CKM_DES3_CBC; michael@0: michael@0: /* michael@0: * Login to the internal token before we look for the key, otherwise we michael@0: * won't find it. michael@0: */ michael@0: rv = PK11_Authenticate(slot, PR_TRUE, cx); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: /* Find the key to use */ michael@0: pKeyID = keyid; michael@0: if (pKeyID->len == 0) { michael@0: pKeyID = &keyIDItem; /* Use default value */ michael@0: michael@0: /* put in a course lock to prevent a race between not finding the michael@0: * key and creating one. michael@0: */ michael@0: michael@0: if (pk11sdrLock) PR_Lock(pk11sdrLock); michael@0: michael@0: /* Try to find the key */ michael@0: key = PK11_FindFixedKey(slot, type, pKeyID, cx); michael@0: michael@0: /* If the default key doesn't exist yet, try to create it */ michael@0: if (!key) key = PK11_GenDES3TokenKey(slot, pKeyID, cx); michael@0: if (pk11sdrLock) PR_Unlock(pk11sdrLock); michael@0: } else { michael@0: key = PK11_FindFixedKey(slot, type, pKeyID, cx); michael@0: } michael@0: michael@0: if (!key) { rv = SECFailure; goto loser; } michael@0: michael@0: params = PK11_GenerateNewParam(type, key); michael@0: if (!params) { rv = SECFailure; goto loser; } michael@0: michael@0: ctx = PK11_CreateContextBySymKey(type, CKA_ENCRYPT, key, params); michael@0: if (!ctx) { rv = SECFailure; goto loser; } michael@0: michael@0: rv = padBlock(data, PK11_GetBlockSize(type, 0), &paddedData); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: sdrResult.data.len = paddedData.len; michael@0: sdrResult.data.data = (unsigned char *)PORT_ArenaAlloc(arena, sdrResult.data.len); michael@0: michael@0: rv = PK11_CipherOp(ctx, sdrResult.data.data, (int*)&sdrResult.data.len, sdrResult.data.len, michael@0: paddedData.data, paddedData.len); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: PK11_Finalize(ctx); michael@0: michael@0: sdrResult.keyid = *pKeyID; michael@0: michael@0: rv = PK11_ParamToAlgid(SEC_OID_DES_EDE3_CBC, params, arena, &sdrResult.alg); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: if (!SEC_ASN1EncodeItem(0, result, &sdrResult, template)) { rv = SECFailure; goto loser; } michael@0: michael@0: loser: michael@0: SECITEM_ZfreeItem(&paddedData, PR_FALSE); michael@0: if (arena) PORT_FreeArena(arena, PR_TRUE); michael@0: if (ctx) PK11_DestroyContext(ctx, PR_TRUE); michael@0: if (params) SECITEM_ZfreeItem(params, PR_TRUE); michael@0: if (key) PK11_FreeSymKey(key); michael@0: if (slot) PK11_FreeSlot(slot); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* decrypt a block */ michael@0: static SECStatus michael@0: pk11Decrypt(PK11SlotInfo *slot, PLArenaPool *arena, michael@0: CK_MECHANISM_TYPE type, PK11SymKey *key, michael@0: SECItem *params, SECItem *in, SECItem *result) michael@0: { michael@0: PK11Context *ctx = 0; michael@0: SECItem paddedResult; michael@0: SECStatus rv; michael@0: michael@0: paddedResult.len = 0; michael@0: paddedResult.data = 0; michael@0: michael@0: ctx = PK11_CreateContextBySymKey(type, CKA_DECRYPT, key, params); michael@0: if (!ctx) { rv = SECFailure; goto loser; } michael@0: michael@0: paddedResult.len = in->len; michael@0: paddedResult.data = PORT_ArenaAlloc(arena, paddedResult.len); michael@0: michael@0: rv = PK11_CipherOp(ctx, paddedResult.data, michael@0: (int*)&paddedResult.len, paddedResult.len, michael@0: in->data, in->len); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: PK11_Finalize(ctx); michael@0: michael@0: /* Remove the padding */ michael@0: rv = unpadBlock(&paddedResult, PK11_GetBlockSize(type, 0), result); michael@0: if (rv) goto loser; michael@0: michael@0: loser: michael@0: if (ctx) PK11_DestroyContext(ctx, PR_TRUE); michael@0: return rv; michael@0: } michael@0: michael@0: /* michael@0: * PK11SDR_Decrypt michael@0: * Decrypt a block of data produced by PK11SDR_Encrypt. The key used is identified michael@0: * by the keyid field within the input. michael@0: */ michael@0: SECStatus michael@0: PK11SDR_Decrypt(SECItem *data, SECItem *result, void *cx) michael@0: { michael@0: SECStatus rv = SECSuccess; michael@0: PK11SlotInfo *slot = 0; michael@0: PK11SymKey *key = 0; michael@0: CK_MECHANISM_TYPE type; michael@0: SDRResult sdrResult; michael@0: SECItem *params = 0; michael@0: SECItem possibleResult = { 0, NULL, 0 }; michael@0: PLArenaPool *arena = 0; michael@0: michael@0: arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); michael@0: if (!arena) { rv = SECFailure; goto loser; } michael@0: michael@0: /* Decode the incoming data */ michael@0: memset(&sdrResult, 0, sizeof sdrResult); michael@0: rv = SEC_QuickDERDecodeItem(arena, &sdrResult, template, data); michael@0: if (rv != SECSuccess) goto loser; /* Invalid format */ michael@0: michael@0: /* Find the slot and key for the given keyid */ michael@0: slot = PK11_GetInternalKeySlot(); michael@0: if (!slot) { rv = SECFailure; goto loser; } michael@0: michael@0: rv = PK11_Authenticate(slot, PR_TRUE, cx); michael@0: if (rv != SECSuccess) goto loser; michael@0: michael@0: /* Get the parameter values from the data */ michael@0: params = PK11_ParamFromAlgid(&sdrResult.alg); michael@0: if (!params) { rv = SECFailure; goto loser; } michael@0: michael@0: /* Use triple-DES (Should look up the algorithm) */ michael@0: type = CKM_DES3_CBC; michael@0: key = PK11_FindFixedKey(slot, type, &sdrResult.keyid, cx); michael@0: if (!key) { michael@0: rv = SECFailure; michael@0: } else { michael@0: rv = pk11Decrypt(slot, arena, type, key, params, michael@0: &sdrResult.data, result); michael@0: } michael@0: michael@0: /* michael@0: * if the pad value was too small (1 or 2), then it's statistically michael@0: * 'likely' that (1 in 256) that we may not have the correct key. michael@0: * Check the other keys for a better match. If we find none, use michael@0: * this result. michael@0: */ michael@0: if (rv == SECWouldBlock) { michael@0: possibleResult = *result; michael@0: } michael@0: michael@0: /* michael@0: * handle the case where your key indicies may have been broken michael@0: */ michael@0: if (rv != SECSuccess) { michael@0: PK11SymKey *keyList = PK11_ListFixedKeysInSlot(slot, NULL, cx); michael@0: PK11SymKey *testKey = NULL; michael@0: PK11SymKey *nextKey = NULL; michael@0: michael@0: for (testKey = keyList; testKey; michael@0: testKey = PK11_GetNextSymKey(testKey)) { michael@0: rv = pk11Decrypt(slot, arena, type, testKey, params, michael@0: &sdrResult.data, result); michael@0: if (rv == SECSuccess) { michael@0: break; michael@0: } michael@0: /* found a close match. If it's our first remember it */ michael@0: if (rv == SECWouldBlock) { michael@0: if (possibleResult.data) { michael@0: /* this is unlikely but possible. If we hit this condition, michael@0: * we have no way of knowing which possibility to prefer. michael@0: * in this case we just match the key the application michael@0: * thought was the right one */ michael@0: SECITEM_ZfreeItem(result, PR_FALSE); michael@0: } else { michael@0: possibleResult = *result; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* free the list */ michael@0: for (testKey = keyList; testKey; testKey = nextKey) { michael@0: nextKey = PK11_GetNextSymKey(testKey); michael@0: PK11_FreeSymKey(testKey); michael@0: } michael@0: } michael@0: michael@0: /* we didn't find a better key, use the one with a small pad value */ michael@0: if ((rv != SECSuccess) && (possibleResult.data)) { michael@0: *result = possibleResult; michael@0: possibleResult.data = NULL; michael@0: rv = SECSuccess; michael@0: } michael@0: michael@0: loser: michael@0: if (arena) PORT_FreeArena(arena, PR_TRUE); michael@0: if (key) PK11_FreeSymKey(key); michael@0: if (params) SECITEM_ZfreeItem(params, PR_TRUE); michael@0: if (slot) PK11_FreeSlot(slot); michael@0: if (possibleResult.data) SECITEM_ZfreeItem(&possibleResult, PR_FALSE); michael@0: michael@0: return rv; michael@0: }