diff options
author | 2022-10-23 20:25:18 -0700 | |
---|---|---|
committer | 2022-10-23 20:25:18 -0700 | |
commit | 76652ac3cad64dbc2fd54e976ce4bad0a37caa03 (patch) | |
tree | 179865bc417dc6bf2f224dd310b77b931ee45c73 /src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp | |
parent | 14cec299f5170b8ed35eed28e53b88724b8cc04f (diff) | |
download | bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.gz bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.zst bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.zip |
Add Web Crypto API (#1384)
* Add Web Crypto API
* Duplicate symbols
* Update c_cpp_properties.json
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp')
-rw-r--r-- | src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp new file mode 100644 index 000000000..bb5dc5e62 --- /dev/null +++ b/src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2020 Sony Interactive Entertainment Inc. + * + * 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 "JsonWebKey.h" +#include "OpenSSLUtilities.h" +#include <wtf/text/Base64.h> + +namespace WebCore { + +static int curveIdentifier(CryptoKeyEC::NamedCurve curve) +{ + switch (curve) { + case CryptoKeyEC::NamedCurve::P256: + return NID_X9_62_prime256v1; + case CryptoKeyEC::NamedCurve::P384: + return NID_secp384r1; + case CryptoKeyEC::NamedCurve::P521: + return NID_secp521r1; + } + + ASSERT_NOT_REACHED(); + return NID_undef; +} + +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 ECKeyPtr createECKey(CryptoKeyEC::NamedCurve curve) +{ + auto key = ECKeyPtr(EC_KEY_new_by_curve_name(curveIdentifier(curve))); + if (key) { + // OPENSSL_EC_NAMED_CURVE needs to be set to export the key with the curve name, not with the curve parameters. + EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); + } + return key; +} + +// This function verifies that the group represents the named curve. +static bool verifyCurve(const EC_GROUP* group, CryptoKeyEC::NamedCurve curve) +{ + if (!group) + return false; + + auto key = createECKey(curve); + if (!key) + return false; + + return !EC_GROUP_cmp(group, EC_KEY_get0_group(key.get()), nullptr); +} + +size_t CryptoKeyEC::keySizeInBits() const +{ + // EVP_PKEY_size() returns the size of DER-encoded key and cannot be used for this function's purpose. + // Instead we resolve the key size by CryptoKeyEC::NamedCurve. + size_t size = curveSize(m_curve); + 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) +{ + // To generate a key pair, we generate a private key and extract the public key from the private key. + auto privateECKey = createECKey(curve); + if (!privateECKey) + return std::nullopt; + + if (EC_KEY_generate_key(privateECKey.get()) <= 0) + return std::nullopt; + + auto point = ECPointPtr(EC_POINT_dup(EC_KEY_get0_public_key(privateECKey.get()), EC_KEY_get0_group(privateECKey.get()))); + if (!point) + return std::nullopt; + + auto publicECKey = createECKey(curve); + if (!publicECKey) + return std::nullopt; + + if (EC_KEY_set_public_key(publicECKey.get(), point.get()) <= 0) + return std::nullopt; + + auto privatePKey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(privatePKey.get(), privateECKey.get()) <= 0) + return std::nullopt; + + auto publicPKey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(publicPKey.get(), publicECKey.get()) <= 0) + return std::nullopt; + + auto publicKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Public, WTFMove(publicPKey), true, usages); + auto privateKey = CryptoKeyEC::create(identifier, curve, CryptoKeyType::Private, WTFMove(privatePKey), extractable, usages); + return CryptoKeyPair { WTFMove(publicKey), WTFMove(privateKey) }; +} + +RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages) +{ + auto key = createECKey(curve); + if (!key) + return nullptr; + + auto group = EC_KEY_get0_group(key.get()); + auto point = ECPointPtr(EC_POINT_new(group)); + // Load an EC point from the keyData. This point is used as a public key. + if (EC_POINT_oct2point(group, point.get(), keyData.data(), keyData.size(), nullptr) <= 0) + return nullptr; + + if (EC_KEY_set_public_key(key.get(), point.get()) <= 0) + return nullptr; + + if (EC_KEY_check_key(key.get()) <= 0) + return nullptr; + + auto pkey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()) <= 0) + return nullptr; + + return create(identifier, curve, CryptoKeyType::Public, WTFMove(pkey), extractable, usages); +} + +RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& x, Vector<uint8_t>&& y, bool extractable, CryptoKeyUsageBitmap usages) +{ + auto key = createECKey(curve); + if (!key) + return nullptr; + + auto group = EC_KEY_get0_group(key.get()); + auto point = ECPointPtr(EC_POINT_new(group)); + + // Currently we only support elliptic curves over GF(p). + if (EC_POINT_set_affine_coordinates_GFp(group, point.get(), convertToBigNumber(x).get(), convertToBigNumber(y).get(), nullptr) <= 0) + return nullptr; + + if (EC_KEY_set_public_key(key.get(), point.get()) <= 0) + return nullptr; + + if (EC_KEY_check_key(key.get()) <= 0) + return nullptr; + + auto pkey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()) <= 0) + return nullptr; + + return create(identifier, curve, CryptoKeyType::Public, WTFMove(pkey), 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) +{ + auto key = createECKey(curve); + if (!key) + return nullptr; + + auto group = EC_KEY_get0_group(key.get()); + auto point = ECPointPtr(EC_POINT_new(group)); + + // Currently we only support elliptic curves over GF(p). + if (EC_POINT_set_affine_coordinates_GFp(group, point.get(), convertToBigNumber(x).get(), convertToBigNumber(y).get(), nullptr) <= 0) + return nullptr; + + if (EC_KEY_set_public_key(key.get(), point.get()) <= 0) + return nullptr; + + if (EC_KEY_set_private_key(key.get(), convertToBigNumber(d).get()) <= 0) + return nullptr; + + if (EC_KEY_check_key(key.get()) <= 0) + return nullptr; + + auto pkey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()) <= 0) + return nullptr; + + return create(identifier, curve, CryptoKeyType::Private, WTFMove(pkey), extractable, usages); +} + +static const ASN1_OBJECT* ecPublicKeyIdentifier() +{ + static ASN1_OBJECT* oid = OBJ_txt2obj("1.2.840.10045.2.1", 1); + return oid; +} + +static const ASN1_OBJECT* ecDHIdentifier() +{ + static ASN1_OBJECT* oid = OBJ_txt2obj("1.3.132.1.12", 1); + return oid; +} + +static bool supportedAlgorithmIdentifier(CryptoAlgorithmIdentifier identifier, const ASN1_OBJECT* oid) +{ + switch (identifier) { + case CryptoAlgorithmIdentifier::ECDSA: + // ECDSA only supports id-ecPublicKey algorithms for imported keys. + if (!OBJ_cmp(oid, ecPublicKeyIdentifier())) + return true; + return false; + case CryptoAlgorithmIdentifier::ECDH: + // ECDH supports both id-ecPublicKey and id-ecDH algorithms for imported keys. + if (!OBJ_cmp(oid, ecPublicKeyIdentifier())) + return true; + if (!OBJ_cmp(oid, ecDHIdentifier())) + return true; + return false; + default: + ASSERT_NOT_REACHED(); + return false; + } +} + +RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages) +{ + // In this function we extract the subjectPublicKey after verifying that the algorithm in the SPKI data + // match the given identifier and curve. Then construct an EC key with the named curve and set the public key. + + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING + // } + + const uint8_t* ptr = keyData.data(); + auto subjectPublicKeyInfo = ASN1SequencePtr(d2i_ASN1_SEQUENCE_ANY(nullptr, &ptr, keyData.size())); + if (!subjectPublicKeyInfo) + return nullptr; + if (ptr - keyData.data() != (ptrdiff_t)keyData.size()) + return nullptr; + + if (sk_ASN1_TYPE_num(subjectPublicKeyInfo.get()) != 2) + return nullptr; + + ASN1_TYPE* value = sk_ASN1_TYPE_value(subjectPublicKeyInfo.get(), 0); + if (value->type != V_ASN1_SEQUENCE) + return nullptr; + + // AlgorithmIdentifier ::= SEQUENCE { + // algorithm OBJECT IDENTIFIER, + // parameters ANY DEFINED BY algorithm OPTIONAL + // } + + ptr = value->value.sequence->data; + auto algorithm = ASN1SequencePtr(d2i_ASN1_SEQUENCE_ANY(nullptr, &ptr, value->value.sequence->length)); + if (!algorithm) + return nullptr; + + if (sk_ASN1_TYPE_num(algorithm.get()) != 2) + return nullptr; + + value = sk_ASN1_TYPE_value(algorithm.get(), 0); + if (value->type != V_ASN1_OBJECT) + return nullptr; + + if (!supportedAlgorithmIdentifier(identifier, value->value.object)) + return nullptr; + + // ECParameters ::= CHOICE { + // namedCurve OBJECT IDENTIFIER + // -- implicitCurve null + // -- specifiedCurve SpecifiedECDomain + // } + // + // Only "namedCurve" is supported. + value = sk_ASN1_TYPE_value(algorithm.get(), 1); + if (value->type != V_ASN1_OBJECT) + return nullptr; + + int curveNID = OBJ_obj2nid(value->value.object); + if (curveNID != curveIdentifier(curve)) + return nullptr; + + // subjectPublicKey must be a BIT STRING. + value = sk_ASN1_TYPE_value(subjectPublicKeyInfo.get(), 1); + if (value->type != V_ASN1_BIT_STRING) + return nullptr; + + ASN1_BIT_STRING* bitString = value->value.bit_string; + + // The SPKI data has been verified at this point. We prepare platform data next. + auto key = createECKey(curve); + if (!key) + return nullptr; + + auto group = EC_KEY_get0_group(key.get()); + if (!group) + return nullptr; + + auto point = ECPointPtr(EC_POINT_new(group)); + if (!point) + return nullptr; + + if (EC_POINT_oct2point(group, point.get(), bitString->data, bitString->length, 0) <= 0) + return nullptr; + + if (EC_KEY_set_public_key(key.get(), point.get()) <= 0) + return nullptr; + + if (EC_KEY_check_key(key.get()) <= 0) + return nullptr; + + EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); + + auto pkey = EvpPKeyPtr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(pkey.get(), key.get()) <= 0) + return nullptr; + + return adoptRef(new CryptoKeyEC(identifier, curve, CryptoKeyType::Public, WTFMove(pkey), extractable, usages)); +} + +RefPtr<CryptoKeyEC> CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector<uint8_t>&& keyData, bool extractable, CryptoKeyUsageBitmap usages) +{ + // We need a local pointer variable to pass to d2i (DER to internal) functions(). + const uint8_t* ptr = keyData.data(); + + // We use d2i_PKCS8_PRIV_KEY_INFO() to import a private key. + auto p8inf = PKCS8PrivKeyInfoPtr(d2i_PKCS8_PRIV_KEY_INFO(nullptr, &ptr, keyData.size())); + if (!p8inf) + return nullptr; + if (ptr - keyData.data() != (ptrdiff_t)keyData.size()) + return nullptr; + + auto pkey = EvpPKeyPtr(EVP_PKCS82PKEY(p8inf.get())); + if (!pkey || EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_EC) + return nullptr; + + auto ecKey = EVP_PKEY_get0_EC_KEY(pkey.get()); + if (!ecKey) + return nullptr; + + if (EC_KEY_check_key(ecKey) <= 0) + return nullptr; + + if (!verifyCurve(EC_KEY_get0_group(ecKey), curve)) + return nullptr; + + EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE); + + return adoptRef(new CryptoKeyEC(identifier, curve, CryptoKeyType::Private, WTFMove(pkey), extractable, usages)); +} + +Vector<uint8_t> CryptoKeyEC::platformExportRaw() const +{ + EC_KEY* key = EVP_PKEY_get0_EC_KEY(platformKey()); + if (!key) + return { }; + + const EC_POINT* point = EC_KEY_get0_public_key(key); + const EC_GROUP* group = EC_KEY_get0_group(key); + size_t keyDataSize = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + if (!keyDataSize) + return { }; + + Vector<uint8_t> keyData(keyDataSize); + if (EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, keyData.data(), keyData.size(), nullptr) != keyDataSize) + return { }; + + return keyData; +} + +bool CryptoKeyEC::platformAddFieldElements(JsonWebKey& jwk) const +{ + size_t keySizeInBytes = (keySizeInBits() + 7) / 8; + + EC_KEY* key = EVP_PKEY_get0_EC_KEY(platformKey()); + if (!key) + return false; + + const EC_POINT* publicKey = EC_KEY_get0_public_key(key); + if (publicKey) { + auto ctx = BNCtxPtr(BN_CTX_new()); + auto x = BIGNUMPtr(BN_new()); + auto y = BIGNUMPtr(BN_new()); + if (1 == EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(key), publicKey, x.get(), y.get(), ctx.get())) { + jwk.x = base64URLEncodeToString(convertToBytesExpand(x.get(), keySizeInBytes)); + jwk.y = base64URLEncodeToString(convertToBytesExpand(y.get(), keySizeInBytes)); + } + } + + if (type() == Type::Private) { + const BIGNUM* privateKey = EC_KEY_get0_private_key(key); + if (privateKey) + jwk.d = base64URLEncodeToString(convertToBytes(privateKey)); + } + return true; +} + +Vector<uint8_t> CryptoKeyEC::platformExportSpki() const +{ + if (type() != CryptoKeyType::Public) + return { }; + + int len = i2d_PUBKEY(platformKey(), nullptr); + if (len < 0) + return { }; + + Vector<uint8_t> keyData(len); + auto ptr = keyData.data(); + if (i2d_PUBKEY(platformKey(), &ptr) < 0) + return { }; + + return keyData; +} + +Vector<uint8_t> CryptoKeyEC::platformExportPkcs8() const +{ + if (type() != CryptoKeyType::Private) + return { }; + + auto p8inf = PKCS8PrivKeyInfoPtr(EVP_PKEY2PKCS8(platformKey())); + if (!p8inf) + return { }; + + int len = i2d_PKCS8_PRIV_KEY_INFO(p8inf.get(), nullptr); + if (len < 0) + return { }; + + Vector<uint8_t> keyData(len); + auto ptr = keyData.data(); + if (i2d_PKCS8_PRIV_KEY_INFO(p8inf.get(), &ptr) < 0) + return { }; + + return keyData; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_CRYPTO) |