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