blob: a7b187591574d4abd4f571076bb1571868f7a37d [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webcrypto/algorithms/ml_dsa.h"
#include "components/webcrypto/algorithms/asymmetric_key_util.h"
#include "components/webcrypto/algorithms/util.h"
#include "components/webcrypto/blink_key_handle.h"
#include "components/webcrypto/generate_key_result.h"
#include "components/webcrypto/jwk.h"
#include "components/webcrypto/status.h"
#include "crypto/evp.h"
#include "crypto/openssl_util.h"
#include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
#include "third_party/blink/public/platform/web_crypto_key_algorithm.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/mldsa.h"
namespace webcrypto {
namespace {
const EVP_PKEY_ALG* GetEvpPkeyAlg(blink::WebCryptoAlgorithmId id) {
switch (id) {
case blink::kWebCryptoAlgorithmIdMlDsa44:
return EVP_pkey_ml_dsa_44();
case blink::kWebCryptoAlgorithmIdMlDsa65:
return EVP_pkey_ml_dsa_65();
case blink::kWebCryptoAlgorithmIdMlDsa87:
return EVP_pkey_ml_dsa_87();
default:
NOTREACHED();
}
}
int GetEvpPkeyId(blink::WebCryptoAlgorithmId id) {
switch (id) {
case blink::kWebCryptoAlgorithmIdMlDsa44:
return EVP_PKEY_ML_DSA_44;
case blink::kWebCryptoAlgorithmIdMlDsa65:
return EVP_PKEY_ML_DSA_65;
case blink::kWebCryptoAlgorithmIdMlDsa87:
return EVP_PKEY_ML_DSA_87;
default:
NOTREACHED();
}
}
const char* GetJwkAlg(blink::WebCryptoAlgorithmId id) {
switch (id) {
case blink::kWebCryptoAlgorithmIdMlDsa44:
return "ML-DSA-44";
case blink::kWebCryptoAlgorithmIdMlDsa65:
return "ML-DSA-65";
case blink::kWebCryptoAlgorithmIdMlDsa87:
return "ML-DSA-87";
default:
NOTREACHED();
}
}
// Synthesizes an import algorithm given a key algorithm, so that
// deserialization can reuse the ImportKey*() methods.
blink::WebCryptoAlgorithm SynthesizeImportAlgorithmForClone(
const blink::WebCryptoKeyAlgorithm& algorithm) {
return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(algorithm.Id(),
nullptr);
}
// Set the context for sign/verify, if present. Returns false if context was
// provided and an error was encountered setting the context, true otherwise.
bool SetContext(const blink::WebCryptoAlgorithm& algorithm,
EVP_PKEY_CTX* pkctx) {
const blink::WebCryptoContextParams* params = algorithm.ContextParams();
if (params && params->Context()) {
if (!EVP_PKEY_CTX_set1_signature_context_string(
pkctx, params->Context()->data(), params->Context()->size())) {
return false;
}
}
return true;
}
} // namespace
Status MlDsaImplementation::GenerateKey(
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask combined_usages,
GenerateKeyResult* result) const {
blink::WebCryptoKeyUsageMask public_usages = 0;
blink::WebCryptoKeyUsageMask private_usages = 0;
Status status = GetUsagesForGenerateAsymmetricKey(
combined_usages, all_public_key_usages_, all_private_key_usages_,
&public_usages, &private_usages);
if (status.IsError()) {
return status;
}
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
const EVP_PKEY_ALG* alg = GetEvpPkeyAlg(algorithm.Id());
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_generate_from_alg(alg));
if (!pkey) {
return Status::OperationError();
}
blink::WebCryptoKeyAlgorithm key_algorithm =
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id());
blink::WebCryptoKey public_key;
bssl::UniquePtr<EVP_PKEY> pkey_public(EVP_PKEY_copy_public(pkey.get()));
// Note that extractable is unconditionally set to true. This is because per
// the WebCrypto spec generated public keys are always extractable.
status = CreateWebCryptoPublicKey(std::move(pkey_public), key_algorithm,
/*extractable=*/true, public_usages,
&public_key);
if (status.IsError()) {
return status;
}
blink::WebCryptoKey private_key;
status = CreateWebCryptoPrivateKey(std::move(pkey), key_algorithm,
extractable, private_usages, &private_key);
if (status.IsError()) {
return status;
}
result->AssignKeyPair(public_key, private_key);
return Status::Success();
}
Status MlDsaImplementation::ImportKey(
blink::WebCryptoKeyFormat format,
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
switch (format) {
case blink::kWebCryptoKeyFormatRawPublic:
return ImportKeyRawPublic(key_data, algorithm, extractable, usages, key);
case blink::kWebCryptoKeyFormatRawSeed:
return ImportKeyRawSeed(key_data, algorithm, extractable, usages, key);
case blink::kWebCryptoKeyFormatPkcs8:
return ImportKeyPkcs8(key_data, algorithm, extractable, usages, key);
case blink::kWebCryptoKeyFormatSpki:
return ImportKeySpki(key_data, algorithm, extractable, usages, key);
case blink::kWebCryptoKeyFormatJwk:
return ImportKeyJwk(key_data, algorithm, extractable, usages, key);
default:
return Status::ErrorUnsupportedImportKeyFormat();
}
}
Status MlDsaImplementation::ExportKey(blink::WebCryptoKeyFormat format,
const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
switch (format) {
case blink::kWebCryptoKeyFormatRawPublic:
return ExportKeyRawPublic(key, buffer);
case blink::kWebCryptoKeyFormatRawSeed:
return ExportKeyRawSeed(key, buffer);
case blink::kWebCryptoKeyFormatPkcs8:
return ExportKeyPkcs8(key, buffer);
case blink::kWebCryptoKeyFormatSpki:
return ExportKeySpki(key, buffer);
case blink::kWebCryptoKeyFormatJwk:
return ExportKeyJwk(key, buffer);
default:
return Status::ErrorUnsupportedExportKeyFormat();
}
}
Status MlDsaImplementation::Sign(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& key,
base::span<const uint8_t> message,
std::vector<uint8_t>* signature) const {
if (key.GetType() != blink::kWebCryptoKeyTypePrivate) {
return Status::ErrorUnexpectedKeyType();
}
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::ScopedEVP_MD_CTX ctx;
EVP_PKEY_CTX* pkctx;
if (!EVP_DigestSignInit(ctx.get(), &pkctx, /*type=*/nullptr,
/*e=*/nullptr, GetEVP_PKEY(key))) {
return Status::OperationError();
}
if (!SetContext(algorithm, pkctx)) {
return Status::OperationError();
}
// Get the length of the signature.
size_t sig_len = 0;
if (!EVP_DigestSign(ctx.get(), nullptr, &sig_len, message.data(),
message.size())) {
return Status::OperationError();
}
// Now get the signature.
signature->resize(sig_len);
if (!EVP_DigestSign(ctx.get(), signature->data(), &sig_len, message.data(),
message.size())) {
return Status::OperationError();
}
signature->resize(sig_len);
return Status::Success();
}
Status MlDsaImplementation::Verify(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& key,
base::span<const uint8_t> signature,
base::span<const uint8_t> message,
bool* signature_match) const {
if (key.GetType() != blink::kWebCryptoKeyTypePublic) {
return Status::ErrorUnexpectedKeyType();
}
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::ScopedEVP_MD_CTX ctx;
EVP_PKEY_CTX* pkctx;
if (!EVP_DigestVerifyInit(ctx.get(), &pkctx, /*type=*/nullptr,
/*e=*/nullptr, GetEVP_PKEY(key))) {
return Status::OperationError();
}
if (!SetContext(algorithm, pkctx)) {
return Status::OperationError();
}
*signature_match =
1 == EVP_DigestVerify(ctx.get(), signature.data(), signature.size(),
message.data(), message.size());
return Status::Success();
}
Status MlDsaImplementation::ImportKeyRawPublic(
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
Status status = CheckKeyCreationUsages(all_public_key_usages_, usages);
if (status.IsError()) {
return status;
}
const EVP_PKEY_ALG* alg = GetEvpPkeyAlg(algorithm.Id());
bssl::UniquePtr<EVP_PKEY> pkey(
EVP_PKEY_from_raw_public_key(alg, key_data.data(), key_data.size()));
if (!pkey) {
return Status::DataError();
}
return CreateWebCryptoPublicKey(
std::move(pkey),
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id()),
extractable, usages, key);
}
Status MlDsaImplementation::ImportKeyRawSeed(
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
Status status = CheckKeyCreationUsages(all_private_key_usages_, usages);
if (status.IsError()) {
return status;
}
const EVP_PKEY_ALG* alg = GetEvpPkeyAlg(algorithm.Id());
bssl::UniquePtr<EVP_PKEY> pkey(
EVP_PKEY_from_private_seed(alg, key_data.data(), key_data.size()));
if (!pkey) {
return Status::DataError();
}
return CreateWebCryptoPrivateKey(
std::move(pkey),
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id()),
extractable, usages, key);
}
Status MlDsaImplementation::ImportKeyPkcs8(
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
Status status = CheckKeyCreationUsages(all_private_key_usages_, usages);
if (status.IsError()) {
return status;
}
bssl::UniquePtr<EVP_PKEY> private_key;
status = ImportUnverifiedPkeyFromPkcs8(key_data, GetEvpPkeyId(algorithm.Id()),
&private_key);
if (status.IsError()) {
return status;
}
return CreateWebCryptoPrivateKey(
std::move(private_key),
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id()),
extractable, usages, key);
}
Status MlDsaImplementation::ImportKeySpki(
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
Status status = CheckKeyCreationUsages(all_public_key_usages_, usages);
if (status.IsError()) {
return status;
}
bssl::UniquePtr<EVP_PKEY> public_key;
status = ImportUnverifiedPkeyFromSpki(key_data, GetEvpPkeyId(algorithm.Id()),
&public_key);
if (status.IsError()) {
return status;
}
return CreateWebCryptoPublicKey(
std::move(public_key),
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id()),
extractable, usages, key);
}
Status MlDsaImplementation::ImportKeyJwk(
base::span<const uint8_t> key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
JwkReader jwk;
const char* expected_alg = GetJwkAlg(algorithm.Id());
Status status = jwk.Init(key_data, extractable, usages, "AKP", expected_alg);
if (status.IsError()) {
return status;
}
bool is_private_key = jwk.HasMember("priv");
status = is_private_key
? CheckKeyCreationUsages(all_private_key_usages_, usages)
: CheckKeyCreationUsages(all_public_key_usages_, usages);
if (status.IsError()) {
return status;
}
std::vector<uint8_t> raw_public_key;
status = jwk.GetBytes("pub", &raw_public_key);
if (status.IsError()) {
return status;
}
blink::WebCryptoKeyAlgorithm key_algorithm =
blink::WebCryptoKeyAlgorithm::CreateWithoutParams(algorithm.Id());
const EVP_PKEY_ALG* alg = GetEvpPkeyAlg(algorithm.Id());
bssl::UniquePtr<EVP_PKEY> public_evp_pkey(EVP_PKEY_from_raw_public_key(
alg, raw_public_key.data(), raw_public_key.size()));
if (!public_evp_pkey) {
return Status::DataError();
}
if (!is_private_key) {
// No need to check for a private key, just return the public key.
return CreateWebCryptoPublicKey(std::move(public_evp_pkey), key_algorithm,
extractable, usages, key);
}
std::vector<uint8_t> raw_private_key;
status = jwk.GetBytes("priv", &raw_private_key);
if (status.IsError()) {
return status;
}
bssl::UniquePtr<EVP_PKEY> private_evp_pkey(EVP_PKEY_from_private_seed(
alg, raw_private_key.data(), raw_private_key.size()));
if (!private_evp_pkey) {
return Status::DataError();
}
// Check the public key matches the private key by comparing the JWK's public
// key to the JWK's private key, which ensures the public key generated from
// the private key matches.
if (!EVP_PKEY_cmp(private_evp_pkey.get(), public_evp_pkey.get())) {
return Status::DataError();
}
return CreateWebCryptoPrivateKey(std::move(private_evp_pkey), key_algorithm,
extractable, usages, key);
}
Status MlDsaImplementation::ExportKeyRawPublic(
const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.GetType() != blink::kWebCryptoKeyTypePublic) {
return Status::ErrorUnexpectedKeyType();
}
size_t len = 0;
if (!EVP_PKEY_get_raw_public_key(GetEVP_PKEY(key), nullptr, &len)) {
return Status::OperationError();
}
buffer->resize(len);
if (!EVP_PKEY_get_raw_public_key(GetEVP_PKEY(key), buffer->data(), &len)) {
return Status::OperationError();
}
buffer->resize(len);
return Status::Success();
}
Status MlDsaImplementation::ExportKeyRawSeed(
const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.GetType() != blink::kWebCryptoKeyTypePrivate) {
return Status::ErrorUnexpectedKeyType();
}
size_t len = 0;
if (!EVP_PKEY_get_private_seed(GetEVP_PKEY(key), nullptr, &len)) {
return Status::OperationError();
}
buffer->resize(len);
if (!EVP_PKEY_get_private_seed(GetEVP_PKEY(key), buffer->data(), &len)) {
return Status::OperationError();
}
buffer->resize(len);
return Status::Success();
}
Status MlDsaImplementation::ExportKeyPkcs8(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.GetType() != blink::kWebCryptoKeyTypePrivate) {
return Status::ErrorUnexpectedKeyType();
}
*buffer = crypto::evp::PrivateKeyToBytes(GetEVP_PKEY(key));
return Status::Success();
}
Status MlDsaImplementation::ExportKeySpki(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.GetType() != blink::kWebCryptoKeyTypePublic) {
return Status::ErrorUnexpectedKeyType();
}
*buffer = crypto::evp::PublicKeyToBytes(GetEVP_PKEY(key));
return Status::Success();
}
Status MlDsaImplementation::ExportKeyJwk(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
EVP_PKEY* pkey = GetEVP_PKEY(key);
size_t keylen = 0;
if (!EVP_PKEY_get_raw_public_key(pkey, nullptr, &keylen)) {
return Status::OperationError();
}
std::vector<uint8_t> raw_public_key(keylen);
if (!EVP_PKEY_get_raw_public_key(pkey, raw_public_key.data(), &keylen)) {
return Status::OperationError();
}
raw_public_key.resize(keylen);
const char* jwk_alg = GetJwkAlg(key.Algorithm().Id());
JwkWriter jwk(jwk_alg, key.Extractable(), key.Usages(), "AKP");
jwk.SetBytes("pub", raw_public_key);
if (key.GetType() == blink::kWebCryptoKeyTypePrivate) {
if (!EVP_PKEY_get_private_seed(pkey, nullptr, &keylen)) {
return Status::OperationError();
}
std::vector<uint8_t> raw_private_key(keylen);
if (!EVP_PKEY_get_private_seed(pkey, raw_private_key.data(), &keylen)) {
return Status::OperationError();
}
raw_private_key.resize(keylen);
jwk.SetBytes("priv", raw_private_key);
}
jwk.ToJson(buffer);
return Status::Success();
}
Status MlDsaImplementation::DeserializeKeyForClone(
const blink::WebCryptoKeyAlgorithm& algorithm,
blink::WebCryptoKeyType type,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
base::span<const uint8_t> key_data,
blink::WebCryptoKey* key) const {
blink::WebCryptoAlgorithm import_algorithm =
SynthesizeImportAlgorithmForClone(algorithm);
Status status;
switch (type) {
case blink::kWebCryptoKeyTypePublic:
status =
ImportKeySpki(key_data, import_algorithm, extractable, usages, key);
break;
case blink::kWebCryptoKeyTypePrivate:
status =
ImportKeyPkcs8(key_data, import_algorithm, extractable, usages, key);
break;
default:
return Status::ErrorUnexpected();
}
if (!status.IsSuccess()) {
return status;
}
if (algorithm.Id() != key->Algorithm().Id()) {
return Status::ErrorUnexpected();
}
if (type != key->GetType()) {
return Status::ErrorUnexpected();
}
return Status::Success();
}
std::unique_ptr<AlgorithmImplementation> CreateMlDsaImplementation() {
return std::make_unique<MlDsaImplementation>();
}
} // namespace webcrypto