CryptoKeyECGCrypt.cpp [plain text]
#include "config.h"
#include "CryptoKeyEC.h"
#if ENABLE(WEB_CRYPTO)
#include "CryptoKeyPair.h"
#include "GCryptUtilities.h"
#include "JsonWebKey.h"
#include <pal/crypto/gcrypt/Utilities.h>
#include <pal/crypto/tasn1/Utilities.h>
#include <wtf/text/Base64.h>
namespace WebCore {
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";
case CryptoKeyEC::NamedCurve::P521:
return "NIST P-521";
}
ASSERT_NOT_REACHED();
return nullptr;
}
static const uint8_t* curveIdentifier(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return CryptoConstants::s_secp256r1Identifier.data();
case CryptoKeyEC::NamedCurve::P384:
return CryptoConstants::s_secp384r1Identifier.data();
case CryptoKeyEC::NamedCurve::P521:
return CryptoConstants::s_secp521r1Identifier.data();
}
ASSERT_NOT_REACHED();
return nullptr;
}
static size_t curveSize(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 256;
case CryptoKeyEC::NamedCurve::P384:
return 384;
case CryptoKeyEC::NamedCurve::P521:
return 521;
}
ASSERT_NOT_REACHED();
return 0;
}
static unsigned curveUncompressedFieldElementSize(CryptoKeyEC::NamedCurve curve)
{
switch (curve) {
case CryptoKeyEC::NamedCurve::P256:
return 32;
case CryptoKeyEC::NamedCurve::P384:
return 48;
case CryptoKeyEC::NamedCurve::P521:
return 66;
}
ASSERT_NOT_REACHED();
return 0;
}
static unsigned curveUncompressedPointSize(CryptoKeyEC::NamedCurve curve)
{
return 2 * curveUncompressedFieldElementSize(curve) + 1;
}
size_t CryptoKeyEC::keySizeInBits() const
{
size_t size = curveSize(m_curve);
ASSERT(size == gcry_pk_get_nbits(m_platformKey.get()));
return size;
}
bool CryptoKeyEC::platformSupportedCurve(NamedCurve curve)
{
return curve == NamedCurve::P256 || curve == NamedCurve::P384 || curve == NamedCurve::P521;
}
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 WTF::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 WTF::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 WTF::nullopt;
auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, PlatformECKeyContainer(publicKeySexp.release()), true, usages);
auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, PlatformECKeyContainer(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() != curveUncompressedPointSize(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, PlatformECKeyContainer(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 = curveUncompressedFieldElementSize(curve);
if (x.size() != uncompressedFieldElementSize || y.size() != uncompressedFieldElementSize)
return nullptr;
Vector<uint8_t> q;
q.reserveInitialCapacity(curveUncompressedPointSize(curve));
q.append(CryptoConstants::s_ecUncompressedFormatLeadingByte.data(), CryptoConstants::s_ecUncompressedFormatLeadingByte.size());
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, PlatformECKeyContainer(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 = curveUncompressedFieldElementSize(curve);
if (x.size() != uncompressedFieldElementSize || y.size() != uncompressedFieldElementSize || d.size() != uncompressedFieldElementSize)
return nullptr;
Vector<uint8_t> q;
q.reserveInitialCapacity(curveUncompressedPointSize(curve));
q.append(CryptoConstants::s_ecUncompressedFormatLeadingByte.data(), CryptoConstants::s_ecUncompressedFormatLeadingByte.size());
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, PlatformECKeyContainer(platformKey.release()), extractable, usages);
}
static bool supportedAlgorithmIdentifier(CryptoAlgorithmIdentifier keyIdentifier, const Vector<uint8_t>& identifier)
{
auto* data = identifier.data();
auto size = identifier.size();
switch (keyIdentifier) {
case CryptoAlgorithmIdentifier::ECDSA:
if (CryptoConstants::matches(data, size, CryptoConstants::s_ecPublicKeyIdentifier))
return true;
return false;
case CryptoAlgorithmIdentifier::ECDH:
if (CryptoConstants::matches(data, size, CryptoConstants::s_ecPublicKeyIdentifier))
return true;
if (CryptoConstants::matches(data, size, CryptoConstants::s_ecDHIdentifier))
return true;
return false;
default:
ASSERT_NOT_REACHED();
break;
}
return false;
}
static Optional<CryptoKeyEC::NamedCurve> curveForIdentifier(const Vector<uint8_t>& identifier)
{
auto* data = identifier.data();
auto size = identifier.size();
if (CryptoConstants::matches(data, size, CryptoConstants::s_secp256r1Identifier))
return CryptoKeyEC::NamedCurve::P256;
if (CryptoConstants::matches(data, size, CryptoConstants::s_secp384r1Identifier))
return CryptoKeyEC::NamedCurve::P384;
if (CryptoConstants::matches(data, size, CryptoConstants::s_secp521r1Identifier))
return CryptoKeyEC::NamedCurve::P521;
return WTF::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() != curveUncompressedPointSize(curve)
|| !CryptoConstants::matches(subjectPublicKey->data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
return nullptr;
unsigned coordinateSize = curveUncompressedFieldElementSize(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, PlatformECKeyContainer(platformKey.release()), extractable, usages);
}
RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
PAL::TASN1::Structure pkcs8;
if (!PAL::TASN1::decodeStructure(&pkcs8, "WebCrypto.PrivateKeyInfo", keyData))
return nullptr;
{
auto version = PAL::TASN1::elementData(pkcs8, "version");
if (!version)
return nullptr;
if (!CryptoConstants::matches(version->data(), version->size(), CryptoConstants::s_asn1Version0))
return nullptr;
}
{
auto algorithm = PAL::TASN1::elementData(pkcs8, "privateKeyAlgorithm.algorithm");
if (!algorithm)
return nullptr;
if (!supportedAlgorithmIdentifier(identifier, *algorithm))
return nullptr;
}
{
auto parameters = PAL::TASN1::elementData(pkcs8, "privateKeyAlgorithm.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::TASN1::Structure ecPrivateKey;
{
auto privateKey = PAL::TASN1::elementData(pkcs8, "privateKey");
if (!privateKey)
return nullptr;
if (!PAL::TASN1::decodeStructure(&ecPrivateKey, "WebCrypto.ECPrivateKey", *privateKey))
return nullptr;
}
{
auto version = PAL::TASN1::elementData(ecPrivateKey, "version");
if (!version)
return nullptr;
if (!CryptoConstants::matches(version->data(), version->size(), CryptoConstants::s_asn1Version1))
return nullptr;
}
{
auto namedCurve = PAL::TASN1::elementData(ecPrivateKey, "parameters.namedCurve");
if (namedCurve) {
auto parameterCurve = curveForIdentifier(*namedCurve);
if (!parameterCurve || *parameterCurve != curve)
return nullptr;
}
}
PAL::GCrypt::Handle<gcry_mpi_t> publicKeyMPI;
{
auto publicKey = PAL::TASN1::elementData(ecPrivateKey, "publicKey");
if (publicKey) {
if (publicKey->size() != curveUncompressedPointSize(curve)
|| !CryptoConstants::matches(publicKey->data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
return nullptr;
gcry_error_t error = gcry_mpi_scan(&publicKeyMPI, GCRYMPI_FMT_USG, publicKey->data(), publicKey->size(), nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
}
}
PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
{
auto privateKey = PAL::TASN1::elementData(ecPrivateKey, "privateKey");
if (!privateKey)
return nullptr;
if (privateKey->size() != (curveSize(curve) + 7) / 8)
return nullptr;
gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(private-key(ecc(curve %s)(d %b)))",
curveName(curve), privateKey->size(), privateKey->data());
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
PAL::GCrypt::Handle<gcry_ctx_t> context;
error = gcry_mpi_ec_new(&context, platformKey, nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
if (publicKeyMPI) {
error = gcry_mpi_ec_set_mpi("q", publicKeyMPI, context);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return nullptr;
}
}
PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_ec_get_point("q", context, 1));
if (!point)
return nullptr;
if (!gcry_mpi_ec_curve_point(point, context))
return nullptr;
}
return create(identifier, curve, CryptoKeyType::Private, PlatformECKeyContainer(platformKey.release()), extractable, usages);
}
Vector<uint8_t> CryptoKeyEC::platformExportRaw() const
{
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, m_platformKey.get(), 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() != curveUncompressedPointSize(m_curve))
return { };
return WTFMove(q.value());
}
bool CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const
{
PAL::GCrypt::Handle<gcry_ctx_t> context;
gcry_error_t error = gcry_mpi_ec_new(&context, m_platformKey.get(), nullptr);
if (error != GPG_ERR_NO_ERROR) {
PAL::GCrypt::logError(error);
return false;
}
unsigned uncompressedFieldElementSize = curveUncompressedFieldElementSize(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() == curveUncompressedPointSize(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) {
if (d->size() < uncompressedFieldElementSize) {
Vector<uint8_t> paddedData(uncompressedFieldElementSize - d->size(), 0);
paddedData.appendVector(*d);
*d = WTFMove(paddedData);
}
jwk.d = base64URLEncode(*d);
}
}
}
return true;
}
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", CryptoConstants::s_ecPublicKeyIdentifier.data(), 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.get(), "q", 0));
if (!qSexp)
return { };
auto qData = mpiData(qSexp);
if (!qData || qData->size() != curveUncompressedPointSize(m_curve)
|| !CryptoConstants::matches(qData->data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
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.get(), 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 { };
unsigned uncompressedFieldElementSize = curveUncompressedFieldElementSize(m_curve);
auto data = mpiData(dMPI);
if (!data || data->size() > uncompressedFieldElementSize)
return { };
if (data->size() < uncompressedFieldElementSize) {
Vector<uint8_t> paddedData(uncompressedFieldElementSize - data->size(), 0);
paddedData.appendVector(*data);
*data = WTFMove(paddedData);
}
if (!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", CryptoConstants::s_ecPublicKeyIdentifier.data(), 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(WEB_CRYPTO)