CryptoKeyECGCrypt.cpp [plain text]
#include "config.h"
#include "CryptoKeyEC.h"
#if ENABLE(SUBTLE_CRYPTO)
#include "CryptoKeyPair.h"
#include "GCryptUtilities.h"
#include "JsonWebKey.h"
#include "NotImplemented.h"
#include <array>
#include <pal/crypto/gcrypt/Handle.h>
#include <pal/crypto/gcrypt/Utilities.h>
#include <pal/crypto/tasn1/Utilities.h>
#include <wtf/text/Base64.h>
namespace WebCore {
static size_t curveSize(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 256;
case CryptoKeyEC::NamedCurve::P384:
return 384;
}
ASSERT_NOT_REACHED();
return 0;
}
static const char* curveName(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return "NIST P-256";
case CryptoKeyEC::NamedCurve::P384:
return "NIST P-384";
}
ASSERT_NOT_REACHED();
return nullptr;
}
static const char* curveIdentifier(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return "1.2.840.10045.3.1.7";
case CryptoKeyEC::NamedCurve::P384:
return "1.3.132.0.34";
}
ASSERT_NOT_REACHED();
return nullptr;
}
static unsigned uncompressedPointSizeForCurve(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 65;
case CryptoKeyEC::NamedCurve::P384:
return 97;
}
ASSERT_NOT_REACHED();
return 0;
}
static unsigned uncompressedFieldElementSizeForCurve(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 32;
case CryptoKeyEC::NamedCurve::P384:
return 48;
}
ASSERT_NOT_REACHED();
return 0;
}
CryptoKeyEC::~CryptoKeyEC()
{
if (m_platformKey)
PAL::GCrypt::HandleDeleter<gcry_sexp_t>()(m_platformKey);
}
size_t CryptoKeyEC::keySizeInBits() const
{
size_t size = curveSize(m_curve);
ASSERT(size == gcry_pk_get_nbits(m_platformKey));
return size;
}
std::optional<CryptoKeyPair> CryptoKeyEC::platformGeneratePair(CryptoAlgorithmIdentifier identifier, NamedCurve curve, bool extractable, CryptoKeyUsageBitmap usages)
{
PAL::GCrypt::Handle<gcry_sexp_t> genkeySexp;
gcry_error_t error = gcry_sexp_build(&genkeySexp, nullptr, "(genkey(ecc(curve %s)))", curveName(curve));
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return std::nullopt;
}
PAL::GCrypt::Handle<gcry_sexp_t> keyPairSexp;
error = gcry_pk_genkey(&keyPairSexp, genkeySexp);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return std::nullopt;
}
PAL::GCrypt::Handle<gcry_sexp_t> publicKeySexp(gcry_sexp_find_token(keyPairSexp, "public-key", 0));
PAL::GCrypt::Handle<gcry_sexp_t> privateKeySexp(gcry_sexp_find_token(keyPairSexp, "private-key", 0));
if (!publicKeySexp || !privateKeySexp)
return std::nullopt;
auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, publicKeySexp.release(), true, usages);
auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, privateKeySexp.release(), extractable, usages);
return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) };
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
if (keyData.size() != uncompressedPointSizeForCurve(curve))
return nullptr;
PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(public-key(ecc(curve %s)(q %b)))",
curveName(curve), keyData.size(), keyData.data());
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
return create(identifier, curve, CryptoKeyType::Public, platformKey.release(), extractable, usages);
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, bool extractable, CryptoKeyUsageBitmap usages)
{
unsigned uncompressedFieldElementSize = uncompressedFieldElementSizeForCurve(curve);
if (x.size() != uncompressedFieldElementSize || y.size() != uncompressedFieldElementSize)
return nullptr;
Vector<uint8_t> q;
q.reserveInitialCapacity(1 + 2 * uncompressedFieldElementSize);
q.append(0x04);
q.appendVector(x);
q.appendVector(y);
PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(public-key(ecc(curve %s)(q %b)))",
curveName(curve), q.size(), q.data());
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
return create(identifier, curve, CryptoKeyType::Public, platformKey.release(), extractable, usages);
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPrivate(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, Vector<uint8_t>&& d, bool extractable, CryptoKeyUsageBitmap usages)
{
unsigned uncompressedFieldElementSize = uncompressedFieldElementSizeForCurve(curve);
if (x.size() != uncompressedFieldElementSize || y.size() != uncompressedFieldElementSize || d.size() != uncompressedFieldElementSize)
return nullptr;
Vector<uint8_t> q;
q.reserveInitialCapacity(1 + 2 * uncompressedFieldElementSize);
q.append(0x04);
q.appendVector(x);
q.appendVector(y);
PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(private-key(ecc(curve %s)(q %b)(d %b)))",
curveName(curve), q.size(), q.data(), d.size(), d.data());
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
return create(identifier, curve, CryptoKeyType::Private, platformKey.release(), extractable, usages);
}
static bool supportedAlgorithmIdentifier(CryptoAlgorithmIdentifier keyIdentifier, const Vector<uint8_t>& identifier)
{
static const std::array<uint8_t, 18> s_id_ecPublicKey { { "1.2.840.10045.2.1" } };
static const std::array<uint8_t, 13> s_id_ecDH { { "1.3.132.1.12" } };
auto size = identifier.size();
auto* data = identifier.data();
switch (keyIdentifier) {
case CryptoAlgorithmIdentifier::ECDSA:
if (size == s_id_ecPublicKey.size() && !std::memcmp(data, s_id_ecPublicKey.data(), size))
return true;
return false;
case CryptoAlgorithmIdentifier::ECDH:
if (size == s_id_ecPublicKey.size() && !std::memcmp(data, s_id_ecPublicKey.data(), size))
return true;
if (size == s_id_ecDH.size() && !std::memcmp(data, s_id_ecDH.data(), size))
return true;
return false;
default:
ASSERT_NOT_REACHED();
break;
}
return false;
}
static std::optional<CryptoKeyEC::NamedCurve> curveForIdentifier(const Vector<uint8_t>& identifier)
{
static const std::array<uint8_t, 20> s_secp256r1 { { "1.2.840.10045.3.1.7" } };
static const std::array<uint8_t, 13> s_secp384r1 { { "1.3.132.0.34" } };
static const std::array<uint8_t, 13> s_secp521r1 { { "1.3.132.0.35" } };
auto size = identifier.size();
auto* data = identifier.data();
if (size == s_secp256r1.size() && !std::memcmp(data, s_secp256r1.data(), size))
return CryptoKeyEC::NamedCurve::P256;
if (size == s_secp384r1.size() && !std::memcmp(data, s_secp384r1.data(), size))
return CryptoKeyEC::NamedCurve::P384;
if (size == s_secp521r1.size() && !std::memcmp(data, s_secp521r1.data(), size))
return std::nullopt;
return std::nullopt;
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
PAL::TASN1::Structure spki;
if (!PAL::TASN1::decodeStructure(&spki, "WebCrypto.SubjectPublicKeyInfo", keyData))
return nullptr;
{
auto algorithm = PAL::TASN1::elementData(spki, "algorithm.algorithm");
if (!algorithm)
return nullptr;
if (!supportedAlgorithmIdentifier(identifier, *algorithm))
return nullptr;
}
{
auto parameters = PAL::TASN1::elementData(spki, "algorithm.parameters");
if (!parameters)
return nullptr;
PAL::TASN1::Structure ecParameters;
if (!PAL::TASN1::decodeStructure(&ecParameters, "WebCrypto.ECParameters", *parameters))
return nullptr;
auto namedCurve = PAL::TASN1::elementData(ecParameters, "namedCurve");
if (!namedCurve)
return nullptr;
auto parameterCurve = curveForIdentifier(*namedCurve);
if (!parameterCurve || *parameterCurve != curve)
return nullptr;
}
PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
{
auto subjectPublicKey = PAL::TASN1::elementData(spki, "subjectPublicKey");
if (!subjectPublicKey)
return nullptr;
if (subjectPublicKey->size() != uncompressedPointSizeForCurve(curve) || subjectPublicKey->at(0) != 0x04)
return nullptr;
unsigned coordinateSize = uncompressedFieldElementSizeForCurve(curve);
PAL::GCrypt::Handle<gcry_mpi_t> xMPI, yMPI;
{
gcry_error_t error = gcry_mpi_scan(&xMPI, GCRYMPI_FMT_USG, &subjectPublicKey->at(1), coordinateSize, nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
error = gcry_mpi_scan(&yMPI, GCRYMPI_FMT_USG, &subjectPublicKey->at(1 + coordinateSize), coordinateSize, nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
}
PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_point_set(nullptr, xMPI, yMPI, GCRYMPI_CONST_ONE));
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, nullptr, curveName(curve));
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
if (!gcry_mpi_ec_curve_point(point, context))
return nullptr;
error = gcry_sexp_build(&platformKey, nullptr, "(public-key(ecc(curve %s)(q %b)))",
curveName(curve), subjectPublicKey->size(), subjectPublicKey->data());
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
}
return create(identifier, curve, CryptoKeyType::Public, platformKey.release(), extractable, usages);
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier, NamedCurve, Vector<uint8_t>&&, bool, CryptoKeyUsageBitmap)
{
notImplemented();
return nullptr;
}
Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
{
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, m_platformKey, nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return { };
}
PAL::GCrypt::Handle<gcry_mpi_t> qMPI(gcry_mpi_ec_get_mpi("q", context, 0));
if (!qMPI)
return { };
auto q = mpiData(qMPI);
if (!q || q->size() != uncompressedPointSizeForCurve(m_curve))
return { };
return WTFMove(q.value());
}
void CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const
{
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, m_platformKey, nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return;
}
unsigned uncompressedFieldElementSize = uncompressedFieldElementSizeForCurve(m_curve);
PAL::GCrypt::Handle<gcry_mpi_t> qMPI(gcry_mpi_ec_get_mpi("q", context, 0));
if (qMPI) {
auto q = mpiData(qMPI);
if (q && q->size() == uncompressedPointSizeForCurve(m_curve)) {
Vector<uint8_t> a;
a.append(q->data() + 1, uncompressedFieldElementSize);
jwk.x = base64URLEncode(a);
Vector<uint8_t> b;
b.append(q->data() + 1 + uncompressedFieldElementSize, uncompressedFieldElementSize);
jwk.y = base64URLEncode(b);
}
}
if (type() == Type::Private) {
PAL::GCrypt::Handle<gcry_mpi_t> dMPI(gcry_mpi_ec_get_mpi("d", context, 0));
if (dMPI) {
auto d = mpiData(dMPI);
if (d && d->size() == uncompressedFieldElementSize)
jwk.d = base64URLEncode(*d);
}
}
}
Vector<uint8_t> CryptoKeyEC::platformExportSpki() const
{
PAL::TASN1::Structure ecParameters;
{
if (!PAL::TASN1::createStructure("WebCrypto.ECParameters", &ecParameters))
return { };
if (!PAL::TASN1::writeElement(ecParameters, "", "namedCurve", 1))
return { };
if (!PAL::TASN1::writeElement(ecParameters, "namedCurve", curveIdentifier(m_curve), 1))
return { };
}
PAL::TASN1::Structure spki;
{
if (!PAL::TASN1::createStructure("WebCrypto.SubjectPublicKeyInfo", &spki))
return { };
if (!PAL::TASN1::writeElement(spki, "algorithm.algorithm", "1.2.840.10045.2.1", 1))
return { };
{
auto data = PAL::TASN1::encodedData(ecParameters, "");
if (!data || !PAL::TASN1::writeElement(spki, "algorithm.parameters", data->data(), data->size()))
return { };
}
PAL::GCrypt::Handle<gcry_sexp_t> qSexp(gcry_sexp_find_token(m_platformKey, "q", 0));
if (!qSexp)
return { };
auto qData = mpiData(qSexp);
if (!qData || qData->size() != uncompressedPointSizeForCurve(m_curve) || qData->at(0) != 0x04)
return { };
if (!PAL::TASN1::writeElement(spki, "subjectPublicKey", qData->data(), qData->size() * 8))
return { };
}
auto result = PAL::TASN1::encodedData(spki, "");
if (!result)
return { };
return WTFMove(result.value());
}
Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const
{
PAL::TASN1::Structure ecParameters;
{
if (!PAL::TASN1::createStructure("WebCrypto.ECParameters", &ecParameters))
return { };
if (!PAL::TASN1::writeElement(ecParameters, "", "namedCurve", 1))
return { };
if (!PAL::TASN1::writeElement(ecParameters, "namedCurve", curveIdentifier(m_curve), 1))
return { };
}
PAL::TASN1::Structure ecPrivateKey;
{
if (!PAL::TASN1::createStructure("WebCrypto.ECPrivateKey", &ecPrivateKey))
return { };
if (!PAL::TASN1::writeElement(ecPrivateKey, "version", "1", 0))
return { };
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, m_platformKey, nullptr);
if (error != GPG_ERR_NO_ERROR)
return { };
{
PAL::GCrypt::Handle<gcry_mpi_t> dMPI(gcry_mpi_ec_get_mpi("d", context, 0));
if (!dMPI)
return { };
auto data = mpiData(dMPI);
if (!data || !PAL::TASN1::writeElement(ecPrivateKey, "privateKey", data->data(), data->size()))
return { };
}
if (!PAL::TASN1::writeElement(ecPrivateKey, "parameters", nullptr, 0))
return { };
{
PAL::GCrypt::Handle<gcry_mpi_t> qMPI(gcry_mpi_ec_get_mpi("q", context, 0));
if (!qMPI)
return { };
auto data = mpiData(qMPI);
if (!data || !PAL::TASN1::writeElement(ecPrivateKey, "publicKey", data->data(), data->size() * 8))
return { };
}
}
PAL::TASN1::Structure pkcs8;
{
if (!PAL::TASN1::createStructure("WebCrypto.PrivateKeyInfo", &pkcs8))
return { };
if (!PAL::TASN1::writeElement(pkcs8, "version", "0", 0))
return { };
if (!PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.algorithm", "1.2.840.10045.2.1", 1))
return { };
{
auto data = PAL::TASN1::encodedData(ecParameters, "");
if (!data || !PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.parameters", data->data(), data->size()))
return { };
}
{
auto data = PAL::TASN1::encodedData(ecPrivateKey, "");
if (!data || !PAL::TASN1::writeElement(pkcs8, "privateKey", data->data(), data->size()))
return { };
}
if (!PAL::TASN1::writeElement(pkcs8, "attributes", nullptr, 0))
return { };
}
auto result = PAL::TASN1::encodedData(pkcs8, "");
if (!result)
return { };
return WTFMove(result.value());
}
}
#endif // ENABLE(SUBTLE_CRYPTO)