// 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;
}