/* 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 "blapi.h" #include "kem.h" #include "pkcs11i.h" #include "pkcs11n.h" #include "secerr.h" #include "secitem.h" #include "secport.h" #include "softoken.h" /* change to the largest KEM Secret Bytes value supported */ #define MAX_SHARED_SECRET_BYTES KYBER_SHARED_SECRET_BYTES KyberParams sftk_kyber_PK11ParamToInternal(CK_ML_KEM_PARAMETER_SET_TYPE pk11ParamSet) { switch (pk11ParamSet) { #ifndef NSS_DISABLE_KYBER case CKP_NSS_KYBER_768_ROUND3: return params_kyber768_round3; #endif case CKP_NSS_ML_KEM_768: case CKP_ML_KEM_768: return params_ml_kem768; case CKP_ML_KEM_1024: return params_ml_kem1024; default: return params_kyber_invalid; } } SECItem * sftk_kyber_AllocPubKeyItem(KyberParams params, SECItem *pubkey) { switch (params) { #ifndef NSS_DISABLE_KYBER case params_kyber768_round3: case params_kyber768_round3_test_mode: #endif case params_ml_kem768: case params_ml_kem768_test_mode: return SECITEM_AllocItem(NULL, pubkey, KYBER768_PUBLIC_KEY_BYTES); case params_ml_kem1024: case params_ml_kem1024_test_mode: return SECITEM_AllocItem(NULL, pubkey, MLKEM1024_PUBLIC_KEY_BYTES); default: return NULL; } } SECItem * sftk_kyber_AllocPrivKeyItem(KyberParams params, SECItem *privkey) { switch (params) { #ifndef NSS_DISABLE_KYBER case params_kyber768_round3: case params_kyber768_round3_test_mode: #endif case params_ml_kem768: case params_ml_kem768_test_mode: return SECITEM_AllocItem(NULL, privkey, KYBER768_PRIVATE_KEY_BYTES); case params_ml_kem1024: case params_ml_kem1024_test_mode: return SECITEM_AllocItem(NULL, privkey, MLKEM1024_PRIVATE_KEY_BYTES); default: return NULL; } } SECItem * sftk_kyber_AllocCiphertextItem(KyberParams params, SECItem *ciphertext) { switch (params) { #ifndef NSS_DISABLE_KYBER case params_kyber768_round3: case params_kyber768_round3_test_mode: #endif case params_ml_kem768: case params_ml_kem768_test_mode: return SECITEM_AllocItem(NULL, ciphertext, KYBER768_CIPHERTEXT_BYTES); case params_ml_kem1024: case params_ml_kem1024_test_mode: return SECITEM_AllocItem(NULL, ciphertext, MLKEM1024_CIPHERTEXT_BYTES); default: return NULL; } } static PRBool sftk_kem_ValidateMechanism(CK_MECHANISM_PTR pMechanism) { if (!pMechanism) { return PR_FALSE; } switch (pMechanism->mechanism) { #ifndef NSS_DISABLE_KYBER case CKM_NSS_KYBER: #endif case CKM_NSS_ML_KEM: case CKM_ML_KEM: return PR_TRUE; default: return PR_FALSE; } } /* this is a generic call the returns the paramSet as a CKU_LONG. if the * param set is in a different attribute or mechanism structure, that would * be based on the mechanism. The meaning of the paramter set is alway * mechanism specific */ static CK_RV sftk_kem_getParamSet(CK_MECHANISM_PTR pMechanism, SFTKObject *key, CK_ULONG *paramSet) { CK_RV crv = CKR_MECHANISM_INVALID; switch (pMechanism->mechanism) { #ifndef NSS_DISABLE_KYBER case CKM_NSS_KYBER: #endif case CKM_NSS_ML_KEM: /* CKM_NSS_KYBER and CKM_NSS_ML_KEM has cases were we were * given parameters form the application. Retain that symantic for * compatibility */ if ((pMechanism->pParameter) && pMechanism->ulParameterLen == sizeof(CK_ML_KEM_PARAMETER_SET_TYPE)) { PR_STATIC_ASSERT(sizeof(CK_ML_KEM_PARAMETER_SET_TYPE) == sizeof(CK_LONG)); *paramSet = *(CK_ULONG *)pMechanism->pParameter; crv = CKR_OK; break; } /* fall through to the official PKCS #11 way of getting the * parameter set */ case CKM_ML_KEM: crv = sftk_GetULongAttribute(key, CKA_PARAMETER_SET, paramSet); if (crv == CKR_OK) { break; } /* we accept all key types a equivalent here, so the parameter * set could be attached to the old vendor specific attribute. * We might use the vendor specific key when bypassing pairwise * consistency checks */ crv = sftk_GetULongAttribute(key, CKA_NSS_PARAMETER_SET, paramSet); break; default: break; } return crv; } static CK_ULONG sftk_kem_CiphertextLen(CK_MECHANISM_PTR pMechanism, CK_ULONG paramSet) { /* the switch here is redundant now, but we will eventually have * other unrelated KEM operations and the meaning of paramSet * is dependent of the mechanism type (in general). Since there * is not overlap between the Vendor specific mechanisms here * and the stand ones, we'll just accept them all here. */ switch (pMechanism->mechanism) { #ifndef NSS_DISABLE_KYBER case CKM_NSS_KYBER: #endif case CKM_NSS_ML_KEM: case CKM_ML_KEM: switch (paramSet) { #ifndef NSS_DISABLE_KYBER case CKP_NSS_KYBER_768_ROUND3: #endif case CKP_NSS_ML_KEM_768: case CKP_ML_KEM_768: return KYBER768_CIPHERTEXT_BYTES; case CKP_ML_KEM_1024: return MLKEM1024_CIPHERTEXT_BYTES; default: break; } default: break; } return 0; } /* C_Encapsulate takes a public encapsulation key hPublicKey, a secret * phKey, and outputs a ciphertext (i.e. encapsulaton) of this secret in * pCiphertext. */ CK_RV NSC_EncapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, /* out */ CK_BYTE_PTR pCiphertext, /* out */ CK_ULONG_PTR pulCiphertextLen, /* out */ CK_OBJECT_HANDLE_PTR phKey) { SFTKSession *session = NULL; SFTKSlot *slot = NULL; SFTKObject *key = NULL; SFTKObject *encapsulationKeyObject = NULL; SFTKAttribute *encapsulationKey = NULL; CK_RV crv; SFTKFreeStatus status; CK_ULONG paramSet = 0; /* use a generic U_LONG so we can handle * different param set types based on the * Mechanism value */ KyberParams kyberParams; CHECK_FORK(); if (!pMechanism || !phKey || !pulCiphertextLen) { return CKR_ARGUMENTS_BAD; } if (!sftk_kem_ValidateMechanism(pMechanism)) { return CKR_MECHANISM_INVALID; } *phKey = CK_INVALID_HANDLE; session = sftk_SessionFromHandle(hSession); if (session == NULL) { return CKR_SESSION_HANDLE_INVALID; } slot = sftk_SlotFromSessionHandle(hSession); if (slot == NULL) { crv = CKR_SESSION_HANDLE_INVALID; goto cleanup; } key = sftk_NewObject(slot); if (key == NULL) { crv = CKR_HOST_MEMORY; goto cleanup; } for (unsigned long int i = 0; i < ulAttributeCount; i++) { crv = sftk_AddAttributeType(key, sftk_attr_expand(&pTemplate[i])); if (crv != CKR_OK) { goto cleanup; } } encapsulationKeyObject = sftk_ObjectFromHandle(hPublicKey, session); if (encapsulationKeyObject == NULL) { crv = CKR_KEY_HANDLE_INVALID; goto cleanup; } encapsulationKey = sftk_FindAttribute(encapsulationKeyObject, CKA_VALUE); if (encapsulationKey == NULL) { crv = CKR_KEY_HANDLE_INVALID; goto cleanup; } crv = sftk_kem_getParamSet(pMechanism, encapsulationKeyObject, ¶mSet); if (crv != CKR_OK) { goto cleanup; } CK_ULONG ciphertextLen = sftk_kem_CiphertextLen(pMechanism, paramSet); if (!pCiphertext || *pulCiphertextLen < ciphertextLen || ciphertextLen == 0) { *pulCiphertextLen = ciphertextLen; crv = CKR_BUFFER_TOO_SMALL; goto cleanup; } SECItem ciphertext = { siBuffer, pCiphertext, ciphertextLen }; SECItem pubKey = { siBuffer, encapsulationKey->attrib.pValue, encapsulationKey->attrib.ulValueLen }; /* The length of secretBuf can be increased if we ever support other KEMs * by changing the define at the top of this file */ uint8_t secretBuf[MAX_SHARED_SECRET_BYTES] = { 0 }; SECItem secret = { siBuffer, secretBuf, sizeof secretBuf }; switch (pMechanism->mechanism) { #ifndef NSS_DISABLE_KYBER case CKM_NSS_KYBER: #endif case CKM_NSS_ML_KEM: case CKM_ML_KEM: PORT_Assert(secret.len >= KYBER_SHARED_SECRET_BYTES); kyberParams = sftk_kyber_PK11ParamToInternal(paramSet); SECStatus rv = Kyber_Encapsulate(kyberParams, /* seed */ NULL, &pubKey, &ciphertext, &secret); if (rv != SECSuccess) { crv = (PORT_GetError() == SEC_ERROR_INVALID_ARGS) ? CKR_ARGUMENTS_BAD : CKR_FUNCTION_FAILED; goto cleanup; } crv = sftk_forceAttribute(key, CKA_VALUE, sftk_item_expand(&secret)); if (crv != CKR_OK) { goto cleanup; } crv = sftk_handleObject(key, session); if (crv != CKR_OK) { goto cleanup; } /* We wrote the ciphertext out directly in Kyber_Encapsulate */ *phKey = key->handle; *pulCiphertextLen = ciphertext.len; break; default: crv = CKR_MECHANISM_INVALID; goto cleanup; } cleanup: if (session) { sftk_FreeSession(session); } if (key) { status = sftk_FreeObject(key); if (status == SFTK_DestroyFailure) { return CKR_DEVICE_ERROR; } } if (encapsulationKeyObject) { status = sftk_FreeObject(encapsulationKeyObject); if (status == SFTK_DestroyFailure) { return CKR_DEVICE_ERROR; } } if (encapsulationKey) { sftk_FreeAttribute(encapsulationKey); } return crv; } CK_RV NSC_DecapsulateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPrivateKey, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, CK_BYTE_PTR pCiphertext, CK_ULONG ulCiphertextLen, /* out */ CK_OBJECT_HANDLE_PTR phKey) { SFTKSession *session = NULL; SFTKSlot *slot = NULL; SFTKObject *key = NULL; SFTKObject *decapsulationKeyObject = NULL; SFTKAttribute *decapsulationKey = NULL; CK_ULONG paramSet = 0; /* use a generic U_LONG so we can handle * different param set types based on the * Mechanism value */ CK_RV crv; SFTKFreeStatus status; KyberParams kyberParams; CHECK_FORK(); if (!pMechanism || !pCiphertext || !pTemplate || !phKey) { return CKR_ARGUMENTS_BAD; } if (!sftk_kem_ValidateMechanism(pMechanism)) { return CKR_MECHANISM_INVALID; } *phKey = CK_INVALID_HANDLE; session = sftk_SessionFromHandle(hSession); if (session == NULL) { return CKR_SESSION_HANDLE_INVALID; } slot = sftk_SlotFromSessionHandle(hSession); if (slot == NULL) { crv = CKR_SESSION_HANDLE_INVALID; goto cleanup; } key = sftk_NewObject(slot); if (key == NULL) { crv = CKR_HOST_MEMORY; goto cleanup; } for (unsigned long int i = 0; i < ulAttributeCount; i++) { crv = sftk_AddAttributeType(key, sftk_attr_expand(&pTemplate[i])); if (crv != CKR_OK) { goto cleanup; } } decapsulationKeyObject = sftk_ObjectFromHandle(hPrivateKey, session); if (decapsulationKeyObject == NULL) { crv = CKR_KEY_HANDLE_INVALID; goto cleanup; } decapsulationKey = sftk_FindAttribute(decapsulationKeyObject, CKA_VALUE); if (decapsulationKey == NULL) { crv = CKR_KEY_HANDLE_INVALID; goto cleanup; } crv = sftk_kem_getParamSet(pMechanism, decapsulationKeyObject, ¶mSet); if (crv != CKR_OK) { goto cleanup; } CK_ULONG ciphertextLen = sftk_kem_CiphertextLen(pMechanism, paramSet); if (!pCiphertext || ulCiphertextLen != ciphertextLen || ciphertextLen == 0) { crv = CKR_ARGUMENTS_BAD; goto cleanup; } SECItem privKey = { siBuffer, decapsulationKey->attrib.pValue, decapsulationKey->attrib.ulValueLen }; SECItem ciphertext = { siBuffer, pCiphertext, ulCiphertextLen }; /* The length of secretBuf can be increased if we ever support other KEMs * by changing the define at the top of this file */ uint8_t secretBuf[MAX_SHARED_SECRET_BYTES] = { 0 }; SECItem secret = { siBuffer, secretBuf, sizeof secretBuf }; switch (pMechanism->mechanism) { #ifndef NSS_DISABLE_KYBER case CKM_NSS_KYBER: #endif case CKM_NSS_ML_KEM: case CKM_ML_KEM: kyberParams = sftk_kyber_PK11ParamToInternal(paramSet); PORT_Assert(secret.len >= KYBER_SHARED_SECRET_BYTES); SECStatus rv = Kyber_Decapsulate(kyberParams, &privKey, &ciphertext, &secret); if (rv != SECSuccess) { crv = (PORT_GetError() == SEC_ERROR_INVALID_ARGS) ? CKR_ARGUMENTS_BAD : CKR_FUNCTION_FAILED; goto cleanup; } crv = sftk_forceAttribute(key, CKA_VALUE, sftk_item_expand(&secret)); if (crv != CKR_OK) { goto cleanup; } crv = sftk_handleObject(key, session); if (crv != CKR_OK) { goto cleanup; } *phKey = key->handle; break; default: crv = CKR_MECHANISM_INVALID; goto cleanup; } cleanup: if (session) { sftk_FreeSession(session); } if (key) { status = sftk_FreeObject(key); if (status == SFTK_DestroyFailure) { return CKR_DEVICE_ERROR; } } if (decapsulationKeyObject) { status = sftk_FreeObject(decapsulationKeyObject); if (status == SFTK_DestroyFailure) { return CKR_DEVICE_ERROR; } } if (decapsulationKey) { sftk_FreeAttribute(decapsulationKey); } return crv; } /* PKCS #11 final spec moved som the the arguments around (to make * NSC_EncapsulateKey and NSC_DecapsulateKey match, keep the old version for * the vendor defined for backward compatibility */ CK_RV NSC_Encapsulate(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPublicKey, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, /* out */ CK_OBJECT_HANDLE_PTR phKey, /* out */ CK_BYTE_PTR pCiphertext, /* out */ CK_ULONG_PTR pulCiphertextLen) { return NSC_EncapsulateKey(hSession, pMechanism, hPublicKey, pTemplate, ulAttributeCount, pCiphertext, pulCiphertextLen, phKey); } CK_RV NSC_Decapsulate(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hPrivateKey, CK_BYTE_PTR pCiphertext, CK_ULONG ulCiphertextLen, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, /* out */ CK_OBJECT_HANDLE_PTR phKey) { return NSC_DecapsulateKey(hSession, pMechanism, hPrivateKey, pTemplate, ulAttributeCount, pCiphertext, ulCiphertextLen, phKey); }