// Convert a Private Key object into an opaque key handle, using AES Key Wrap // with the long-lived aPersistentKey mixed with aAppParam to convert aPrivKey. // The key handle's format is version || saltLen || salt || wrappedPrivateKey static UniqueSECItem KeyHandleFromPrivateKey(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey, uint8_t* aAppParam, uint32_t aAppParamLen, const UniqueSECKEYPrivateKey& aPrivKey, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aPersistentKey); MOZ_ASSERT(aAppParam); MOZ_ASSERT(aPrivKey); if (NS_WARN_IF(!aSlot || !aPersistentKey || !aPrivKey || !aAppParam)) { return nullptr; } // Generate a random salt uint8_t saltParam[kSaltByteLen]; SECStatus srv = PK11_GenerateRandomOnSlot(aSlot.get(), saltParam, sizeof(saltParam)); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to generate a salt, NSS error #%d", PORT_GetError())); return nullptr; } // Prepare the HKDF (https://tools.ietf.org/html/rfc5869) CK_NSS_HKDFParams hkdfParams = { true, saltParam, sizeof(saltParam), true, aAppParam, aAppParamLen }; SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams, sizeof(hkdfParams) }; // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam. // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the // derived symmetric key and don't matter because we ignore them anyway. UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen)); if (NS_WARN_IF(!wrapKey.get())) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError())); return nullptr; } UniqueSECItem wrappedKey(::SECITEM_AllocItem(/* default arena */ nullptr, /* no buffer */ nullptr, kWrappedKeyBufLen)); if (NS_WARN_IF(!wrappedKey)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); srv = PK11_WrapPrivKey(aSlot.get(), wrapKey.get(), aPrivKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(), /* wincx */ nullptr); if (NS_WARN_IF(srv != SECSuccess)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to wrap U2F key, NSS error #%d", PORT_GetError())); return nullptr; } // Concatenate the salt and the wrapped Private Key together mozilla::dom::CryptoBuffer keyHandleBuf; if (NS_WARN_IF(!keyHandleBuf.SetCapacity(wrappedKey.get()->len + sizeof(saltParam) + 2, mozilla::fallible))) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } // It's OK to ignore the return values here because we're writing into // pre-allocated space keyHandleBuf.AppendElement(SoftTokenHandle::Version1, mozilla::fallible); keyHandleBuf.AppendElement(sizeof(saltParam), mozilla::fallible); keyHandleBuf.AppendElements(saltParam, sizeof(saltParam), mozilla::fallible); keyHandleBuf.AppendSECItem(wrappedKey.get()); UniqueSECItem keyHandle(::SECITEM_AllocItem(nullptr, nullptr, 0)); if (NS_WARN_IF(!keyHandle)) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } if (NS_WARN_IF(!keyHandleBuf.ToSECItem(/* default arena */ nullptr, keyHandle.get()))) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory")); return nullptr; } return keyHandle; }
// Convert an opaque key handle aKeyHandle back into a Private Key object, using // the long-lived aPersistentKey mixed with aAppParam and the AES Key Wrap // algorithm. static UniqueSECKEYPrivateKey PrivateKeyFromKeyHandle(const UniquePK11SlotInfo& aSlot, const UniquePK11SymKey& aPersistentKey, uint8_t* aKeyHandle, uint32_t aKeyHandleLen, uint8_t* aAppParam, uint32_t aAppParamLen, const nsNSSShutDownPreventionLock&) { MOZ_ASSERT(aSlot); MOZ_ASSERT(aPersistentKey); MOZ_ASSERT(aKeyHandle); MOZ_ASSERT(aAppParam); MOZ_ASSERT(aAppParamLen == SHA256_LENGTH); if (NS_WARN_IF(!aSlot || !aPersistentKey || !aKeyHandle || !aAppParam || aAppParamLen != SHA256_LENGTH)) { return nullptr; } // As we only support one key format ourselves (right now), fail early if // we aren't that length if (NS_WARN_IF(aKeyHandleLen != kVersion1KeyHandleLen)) { return nullptr; } if (NS_WARN_IF(aKeyHandle[0] != SoftTokenHandle::Version1)) { // Unrecognized version return nullptr; } uint8_t saltLen = aKeyHandle[1]; uint8_t* saltPtr = aKeyHandle + 2; if (NS_WARN_IF(saltLen != kSaltByteLen)) { return nullptr; } // Prepare the HKDF (https://tools.ietf.org/html/rfc5869) CK_NSS_HKDFParams hkdfParams = { true, saltPtr, saltLen, true, aAppParam, aAppParamLen }; SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams, sizeof(hkdfParams) }; // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam. // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the // derived symmetric key and don't matter because we ignore them anyway. UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256, &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP, kWrappingKeyByteLen)); if (NS_WARN_IF(!wrapKey.get())) { MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError())); return nullptr; } uint8_t wrappedLen = aKeyHandleLen - saltLen - 2; uint8_t* wrappedPtr = aKeyHandle + saltLen + 2; ScopedAutoSECItem wrappedKeyItem(wrappedLen); memcpy(wrappedKeyItem.data, wrappedPtr, wrappedKeyItem.len); ScopedAutoSECItem pubKey(kPublicKeyLen); UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD, /* default IV */ nullptr )); CK_ATTRIBUTE_TYPE usages[] = { CKA_SIGN }; int usageCount = 1; UniqueSECKEYPrivateKey unwrappedKey( PK11_UnwrapPrivKey(aSlot.get(), wrapKey.get(), CKM_NSS_AES_KEY_WRAP_PAD, param.get(), &wrappedKeyItem, /* no nickname */ nullptr, /* discard pubkey */ &pubKey, /* not permanent */ false, /* non-exportable */ true, CKK_EC, usages, usageCount, /* wincx */ nullptr)); if (NS_WARN_IF(!unwrappedKey)) { // Not our key. MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Could not unwrap key handle, NSS Error #%d", PORT_GetError())); return nullptr; } return unwrappedKey; }
static int doTest(CSSM_CSP_HANDLE cspHand, CSSM_KEY_PTR encrKey, // we wrap this one CSSM_KEY_PTR decrKey, // ...or this one, depending on WRAP_DECR_KEY CSSM_KEY_PTR wrappingKey, // ...using this key, NULL for null wrap CSSM_KEY_PTR unwrappingKey, CSSM_ALGORITHMS wrapAlg, CSSM_ENCRYPT_MODE wrapMode, CSSM_KEYBLOB_FORMAT wrapFormat, // NONE, PKCS7, PKCS8 CSSM_PADDING wrapPad, CSSM_ALGORITHMS encrAlg, CSSM_ENCRYPT_MODE encrMode, CSSM_PADDING encrPad, CSSM_BOOL wrapOnly, uint32 maxPtextSize, // max size to encrypt CSSM_BOOL quiet) { CSSM_DATA ptext; CSSM_DATA ctext; CSSM_DATA rptext; CSSM_KEY wrappedKey; CSSM_KEY unwrappedKey; CSSM_RETURN crtn; CSSM_KEY_PTR realEncrKey; // encrKey or &unwrappedKey CSSM_KEY_PTR realDecrKey; // decrKey or &unwrappedKey /* wrap decrKey or encrKey using wrappingKey ==> wrappedKey */ if((wrappingKey == NULL) && !NULL_WRAP_DECR_KEY) { /* NULL wrap of pub key */ crtn = wrapKey(cspHand, encrKey, wrappingKey, wrapAlg, wrapMode, wrapFormat, wrapPad, &wrappedKey); realEncrKey = &unwrappedKey; realDecrKey = decrKey; } else { /* normal case, wrap priv key (may be NULL if NULL_WRAP_DECR_KEY) */ crtn = wrapKey(cspHand, decrKey, wrappingKey, wrapAlg, wrapMode, wrapFormat, wrapPad, &wrappedKey); realEncrKey = encrKey; realDecrKey = &unwrappedKey; } if(crtn) { return testError(quiet); } if((wrappingKey != NULL) && // skip for NULL wrap (wrapFormat != CSSM_KEYBLOB_WRAPPED_FORMAT_NONE)) { /* don't want default, verify we got what we want */ if(wrappedKey.KeyHeader.Format != wrapFormat) { printf("wrapped key format mismatch: expect %u; got %u\n", (unsigned)wrapFormat, (unsigned)wrappedKey.KeyHeader.Format); if(testError(quiet)) { return 1; } } } if(wrapOnly) { cspFreeKey(cspHand, &wrappedKey); goto done; } /* unwrap wrappedKey using unwrappingKey ==> unwrappedKey; */ crtn = unwrapKey(cspHand, &wrappedKey, unwrappingKey, wrapAlg, wrapMode, wrapPad, &unwrappedKey, (uint8 *)UNWRAPPED_LABEL, 15); if(crtn) { return testError(quiet); } /* cook up ptext */ ptext.Data = (uint8 *)CSSM_MALLOC(maxPtextSize); simpleGenData(&ptext, 1, maxPtextSize); /* encrypt using realEncrKey ==> ctext */ ctext.Data = NULL; ctext.Length = 0; crtn = cspEncrypt(cspHand, encrAlg, encrMode, encrPad, realEncrKey, NULL, // no 2nd key 0, // effectiveKeySize 0, // rounds &initVector, &ptext, &ctext, CSSM_TRUE); // mallocCtext if(crtn) { return testError(quiet); } /* decrypt ctext with realDecrKey ==> rptext; */ rptext.Data = NULL; rptext.Length = 0; crtn = cspDecrypt(cspHand, encrAlg, encrMode, encrPad, realDecrKey, NULL, // no 2nd key 0, // effectiveKeySize 0, // rounds &initVector, &ctext, &rptext, CSSM_TRUE); if(crtn) { return testError(quiet); } /* compare ptext vs. rptext; */ if(ptext.Length != rptext.Length) { printf("ptext length mismatch\n"); return testError(quiet); } if(memcmp(ptext.Data, rptext.Data, ptext.Length)) { printf("***data miscompare\n"); return testError(quiet); } /* free resources */ cspFreeKey(cspHand, &wrappedKey); cspFreeKey(cspHand, &unwrappedKey); CSSM_FREE(ptext.Data); CSSM_FREE(ctext.Data); CSSM_FREE(rptext.Data); done: return 0; }