/*
 * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#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 ASCIILiteral curveName(CryptoKeyEC::NamedCurve curve)
{
    switch (curve) {
    case CryptoKeyEC::NamedCurve::P256:
        return "NIST P-256"_s;
    case CryptoKeyEC::NamedCurve::P384:
        return "NIST P-384"_s;
    case CryptoKeyEC::NamedCurve::P521:
        return "NIST P-521"_s;
    }

    ASSERT_NOT_REACHED();
    return { };
}

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

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).characters());
    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, 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).characters(), keyData.size(), keyData.span().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;

    // Construct the Vector that represents the EC point in uncompressed format.
    Vector<uint8_t> q;
    q.reserveInitialCapacity(curveUncompressedPointSize(curve));
    q.append(std::span { CryptoConstants::s_ecUncompressedFormatLeadingByte });
    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).characters(), q.size(), q.span().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;

    // Construct the Vector that represents the EC point in uncompressed format.
    Vector<uint8_t> q;
    q.reserveInitialCapacity(curveUncompressedPointSize(curve));
    q.append(std::span { CryptoConstants::s_ecUncompressedFormatLeadingByte });
    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).characters(), q.size(), q.span().data(), d.size(), d.span().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.span().data();
    auto size = identifier.size();

    switch (keyIdentifier) {
    case CryptoAlgorithmIdentifier::ECDSA:
        // ECDSA only supports id-ecPublicKey algorithms for imported keys.
        if (CryptoConstants::matches(data, size, CryptoConstants::s_ecPublicKeyIdentifier))
            return true;
        return false;
    case CryptoAlgorithmIdentifier::ECDH:
        // ECDH supports both id-ecPublicKey and ic-ecDH algorithms for imported keys.
        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 std::optional<CryptoKeyEC::NamedCurve> curveForIdentifier(const Vector<uint8_t>& identifier)
{
    auto* data = identifier.span().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 std::nullopt;
}

RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages)
{
    // Decode the `SubjectPublicKeyInfo` structure using the provided key data.
    PAL::TASN1::Structure spki;
    if (!PAL::TASN1::decodeStructure(&spki, "WebCrypto.SubjectPublicKeyInfo", keyData))
        return nullptr;

    // Validate `algorithm.algorithm`.
    {
        auto algorithm = PAL::TASN1::elementData(spki, "algorithm.algorithm");
        if (!algorithm)
            return nullptr;

        if (!supportedAlgorithmIdentifier(identifier, *algorithm))
            return nullptr;
    }

    // Validate `algorithm.parameters` and therein embedded `ECParameters`.
    {
        auto parameters = PAL::TASN1::elementData(spki, "algorithm.parameters");
        if (!parameters)
            return nullptr;

        // Decode the `ECParameters` structure using the `algorithm.parameters` data.
        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;
    }

    // Retrieve the `subjectPublicKey` data and embed it into the `public-key` s-expression.
    PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
    {
        auto subjectPublicKey = PAL::TASN1::elementData(spki, "subjectPublicKey");
        if (!subjectPublicKey)
            return nullptr;

        // Bail if the `subjectPublicKey` data size doesn't match the size of an uncompressed point
        // for this curve, or if the first byte in the `subjectPublicKey` data isn't 0x04, as required
        // for an uncompressed EC point encoded in an octet string.
        if (subjectPublicKey->size() != curveUncompressedPointSize(curve)
            || !CryptoConstants::matches(subjectPublicKey->span().data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
            return nullptr;

        // Convert X and Y coordinate data into MPIs.
        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;
            }
        }

        // Construct an MPI point from the X and Y coordinates and using 1 as the Z coordinate.
        // This always allocates the gcry_mpi_point_t object.
        PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_point_set(nullptr, xMPI, yMPI, GCRYMPI_CONST_ONE));

        // Create an EC context for the specified curve.
        PAL::GCrypt::Handle<gcry_ctx_t> context;
        gcry_error_t error = gcry_mpi_ec_new(&context, nullptr, curveName(curve).characters());
        if (error != GPG_ERR_NO_ERROR) {
            PAL::GCrypt::logError(error);
            return nullptr;
        }

        // Bail if the constructed MPI point is not on the specified EC curve.
        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).characters(), subjectPublicKey->size(), subjectPublicKey->span().data());
        if (error != GPG_ERR_NO_ERROR) {
            PAL::GCrypt::logError(error);
            return nullptr;
        }
    }

    // Finally create a new CryptoKeyEC object, transferring to it ownership of the `public-key` s-expression.
    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)
{
    // Decode the `PrivateKeyInfo` structure using the provided key data.
    PAL::TASN1::Structure pkcs8;
    if (!PAL::TASN1::decodeStructure(&pkcs8, "WebCrypto.PrivateKeyInfo", keyData))
        return nullptr;

    // Validate `version`.
    {
        auto version = PAL::TASN1::elementData(pkcs8, "version");
        if (!version)
            return nullptr;

        if (!CryptoConstants::matches(version->span().data(), version->size(), CryptoConstants::s_asn1Version0))
            return nullptr;
    }

    // Validate `privateKeyAlgorithm.algorithm`.
    {
        auto algorithm = PAL::TASN1::elementData(pkcs8, "privateKeyAlgorithm.algorithm");
        if (!algorithm)
            return nullptr;

        if (!supportedAlgorithmIdentifier(identifier, *algorithm))
            return nullptr;
    }

    // Validate `privateKeyAlgorithm.parameters` and therein embedded `ECParameters`.
    {
        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;
    }

    // Decode the `ECPrivateKey` structure using the `privateKey` data.
    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;
    }

    // Validate `privateKey.version`.
    {
        auto version = PAL::TASN1::elementData(ecPrivateKey, "version");
        if (!version)
            return nullptr;

        if (!CryptoConstants::matches(version->span().data(), version->size(), CryptoConstants::s_asn1Version1))
            return nullptr;
    }

    // Validate `privateKey.parameters.namedCurve`, if any.
    {
        auto namedCurve = PAL::TASN1::elementData(ecPrivateKey, "parameters.namedCurve");
        if (namedCurve) {
            auto parameterCurve = curveForIdentifier(*namedCurve);
            if (!parameterCurve || *parameterCurve != curve)
                return nullptr;
        }
    }

    // Validate `privateKey.publicKey`, if any, and scan the data into an MPI.
    PAL::GCrypt::Handle<gcry_mpi_t> publicKeyMPI;
    {
        auto publicKey = PAL::TASN1::elementData(ecPrivateKey, "publicKey");
        if (publicKey) {
            if (publicKey->size() != curveUncompressedPointSize(curve)
                || !CryptoConstants::matches(publicKey->span().data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
                return nullptr;

            gcry_error_t error = gcry_mpi_scan(&publicKeyMPI, GCRYMPI_FMT_USG, publicKey->span().data(), publicKey->size(), nullptr);
            if (error != GPG_ERR_NO_ERROR) {
                PAL::GCrypt::logError(error);
                return nullptr;
            }
        }
    }

    // Retrieve the `privateKey.privateKey` data and embed it into the `private-key` s-expression.
    PAL::GCrypt::Handle<gcry_sexp_t> platformKey;
    {
        auto privateKey = PAL::TASN1::elementData(ecPrivateKey, "privateKey");
        if (!privateKey)
            return nullptr;

        // Validate the size of `privateKey`, making sure it fits the byte-size of the specified EC curve.
        if (privateKey->size() != (curveSize(curve) + 7) / 8)
            return nullptr;

        // Construct the `private-key` expression that will also be used for the EC context.
        gcry_error_t error = gcry_sexp_build(&platformKey, nullptr, "(private-key(ecc(curve %s)(d %b)))",
            curveName(curve).characters(), privateKey->size(), privateKey->span().data());
        if (error != GPG_ERR_NO_ERROR) {
            PAL::GCrypt::logError(error);
            return nullptr;
        }

        // Create an EC context for the specified curve.
        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;
        }

        // Set the 'q' value on the EC context if public key data was provided through the import.
        if (publicKeyMPI) {
            error = gcry_mpi_ec_set_mpi("q", publicKeyMPI, context);
            if (error != GPG_ERR_NO_ERROR) {
                PAL::GCrypt::logError(error);
                return nullptr;
            }
        }

        // Retrieve the `q` point. If the public key was provided through the PKCS#8 import, that
        // key value will be retrieved as an gcry_mpi_point_t. Otherwise, the `q` point value will
        // be computed on-the-fly by libgcrypt for the specified elliptic curve.
        PAL::GCrypt::Handle<gcry_mpi_point_t> point(gcry_mpi_ec_get_point("q", context, 1));
        if (!point)
            return nullptr;

        // Bail if the retrieved `q` MPI point is not on the specified EC curve.
        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, 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, 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)) {
            jwk.x = base64URLEncodeToString(q->subspan(1, uncompressedFieldElementSize));
            jwk.y = base64URLEncodeToString(q->subspan(1 + uncompressedFieldElementSize, uncompressedFieldElementSize));
        }
    }

    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) {
                // Zero-pad the private key data up to the field element size, if necessary.
                if (d->size() < uncompressedFieldElementSize) {
                    Vector<uint8_t> paddedData(uncompressedFieldElementSize - d->size(), 0);
                    paddedData.appendVector(*d);
                    *d = WTFMove(paddedData);
                }

                jwk.d = base64URLEncodeToString(*d);
            }
        }
    }

    return true;
}

Vector<uint8_t> CryptoKeyEC::platformExportSpki() const
{
    PAL::TASN1::Structure ecParameters;
    {
        // Create the `ECParameters` structure.
        if (!PAL::TASN1::createStructure("WebCrypto.ECParameters", &ecParameters))
            return { };

        // Select the `namedCurve` object identifier as the target `ECParameters` choice.
        if (!PAL::TASN1::writeElement(ecParameters, "", "namedCurve", 1))
            return { };

        // Write out the EC curve identifier under `namedCurve`.
        if (!PAL::TASN1::writeElement(ecParameters, "namedCurve", curveIdentifier(m_curve), 1))
            return { };
    }

    PAL::TASN1::Structure spki;
    {
        // Create the `SubjectPublicKeyInfo` structure.
        if (!PAL::TASN1::createStructure("WebCrypto.SubjectPublicKeyInfo", &spki))
            return { };

        // Write out the id-ecPublicKey identifier under `algorithm.algorithm`.
        // FIXME: Per specification this should write out id-ecDH when the ECDH algorithm
        // is specified for this CryptoKeyEC object, but not even the W3C tests expect that.
        if (!PAL::TASN1::writeElement(spki, "algorithm.algorithm", CryptoConstants::s_ecPublicKeyIdentifier.data(), 1))
            return { };

        // Write out the `ECParameters` data under `algorithm.parameters`.
        {
            auto data = PAL::TASN1::encodedData(ecParameters, "");
            if (!data || !PAL::TASN1::writeElement(spki, "algorithm.parameters", data->span().data(), data->size()))
                return { };
        }

        // Retrieve the `q` s-expression, which should contain the public key data.
        PAL::GCrypt::Handle<gcry_sexp_t> qSexp(gcry_sexp_find_token(platformKey().get(), "q", 0));
        if (!qSexp)
            return { };

        // Retrieve the `q` data, which should be in the uncompressed point format.
        // Validate the data size and the first byte (which should be 0x04).
        auto qData = mpiData(qSexp);
        if (!qData || qData->size() != curveUncompressedPointSize(m_curve)
            || !CryptoConstants::matches(qData->span().data(), 1, CryptoConstants::s_ecUncompressedFormatLeadingByte))
            return { };

        // Write out the public key data under `subjectPublicKey`. Because this is a
        // bit string parameter, the data size has to be multiplied by 8.
        if (!PAL::TASN1::writeElement(spki, "subjectPublicKey", qData->span().data(), qData->size() * 8))
            return { };
    }

    // Retrieve the encoded `SubjectPublicKeyInfo` data and return it.
    auto result = PAL::TASN1::encodedData(spki, "");
    if (!result)
        return { };

    return WTFMove(result.value());
}

Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const
{
    PAL::TASN1::Structure ecParameters;
    {
        // Create the `ECParameters` structure.
        if (!PAL::TASN1::createStructure("WebCrypto.ECParameters", &ecParameters))
            return { };

        // Select the `namedCurve` object identifier as the target `ECParameters` choice.
        if (!PAL::TASN1::writeElement(ecParameters, "", "namedCurve", 1))
            return { };

        // Write out the EC curve identifier under `namedCurve`.
        if (!PAL::TASN1::writeElement(ecParameters, "namedCurve", curveIdentifier(m_curve), 1))
            return { };
    }

    PAL::TASN1::Structure ecPrivateKey;
    {
        // Create the `ECPrivateKey` structure.
        if (!PAL::TASN1::createStructure("WebCrypto.ECPrivateKey", &ecPrivateKey))
            return { };

        // Write out '1' under `version`.
        if (!PAL::TASN1::writeElement(ecPrivateKey, "version", "1", 0))
            return { };

        // Construct the EC context that we'll use to retrieve private and public key data.
        PAL::GCrypt::Handle<gcry_ctx_t> context;
        gcry_error_t error = gcry_mpi_ec_new(&context, platformKey().get(), nullptr);
        if (error != GPG_ERR_NO_ERROR)
            return { };

        {
            // Retrieve the `d` MPI that holds the private key data.
            PAL::GCrypt::Handle<gcry_mpi_t> dMPI(gcry_mpi_ec_get_mpi("d", context, 0));
            if (!dMPI)
                return { };

            unsigned uncompressedFieldElementSize = curveUncompressedFieldElementSize(m_curve);

            // Retrieve the `d` MPI data.
            auto data = mpiData(dMPI);
            if (!data || data->size() > uncompressedFieldElementSize)
                return { };

            // Zero-pad the private key data up to the field element size, if necessary.
            if (data->size() < uncompressedFieldElementSize) {
                Vector<uint8_t> paddedData(uncompressedFieldElementSize - data->size(), 0);
                paddedData.appendVector(*data);
                *data = WTFMove(paddedData);
            }

            // Write out the data under `privateKey`.
            if (!PAL::TASN1::writeElement(ecPrivateKey, "privateKey", data->span().data(), data->size()))
                return { };
        }

        // Eliminate the optional `parameters` element.
        if (!PAL::TASN1::writeElement(ecPrivateKey, "parameters", nullptr, 0))
            return { };

        {
            // Retrieve the `q` MPI that holds the public key data.
            PAL::GCrypt::Handle<gcry_mpi_t> qMPI(gcry_mpi_ec_get_mpi("q", context, 0));
            if (!qMPI)
                return { };

            // Retrieve the MPI data and write it out under `publicKey`. Because this is a
            // bit string parameter, the data size has to be multiplied by 8.
            auto data = mpiData(qMPI);
            if (!data || !PAL::TASN1::writeElement(ecPrivateKey, "publicKey", data->span().data(), data->size() * 8))
                return { };
        }
    }

    PAL::TASN1::Structure pkcs8;
    {
        // Create the `PrivateKeyInfo` structure.
        if (!PAL::TASN1::createStructure("WebCrypto.PrivateKeyInfo", &pkcs8))
            return { };

        // Write out '0' under `version`.
        if (!PAL::TASN1::writeElement(pkcs8, "version", "0", 0))
            return { };

        // Write out the id-ecPublicKey identifier under `privateKeyAlgorithm.algorithm`.
        // FIXME: Per specification this should write out id-ecDH when the ECDH algorithm
        // is specified for this CryptoKeyEC object, but not even the W3C tests expect that.
        if (!PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.algorithm", CryptoConstants::s_ecPublicKeyIdentifier.data(), 1))
            return { };

        // Write out the `ECParameters` data under `privateKeyAlgorithm.parameters`.
        {
            auto data = PAL::TASN1::encodedData(ecParameters, "");
            if (!data || !PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.parameters", data->span().data(), data->size()))
                return { };
        }

        // Write out the `ECPrivateKey` data under `privateKey`.
        {
            auto data = PAL::TASN1::encodedData(ecPrivateKey, "");
            if (!data || !PAL::TASN1::writeElement(pkcs8, "privateKey", data->span().data(), data->size()))
                return { };
        }

        // Eliminate the optional `attributes` element.
        if (!PAL::TASN1::writeElement(pkcs8, "attributes", nullptr, 0))
            return { };
    }

    // Retrieve the encoded `PrivateKeyInfo` data and return it.
    auto result = PAL::TASN1::encodedData(pkcs8, "");
    if (!result)
        return { };

    return WTFMove(result.value());
}

} // namespace WebCore

#endif // ENABLE(WEB_CRYPTO)
