/* * 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 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 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::platformImportRaw(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector&& 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::platformImportJWKPublic(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector&& x, Vector&& 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::platformImportJWKPrivate(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector&& x, Vector&& y, Vector&& 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::platformImportSpki(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector&& 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::platformImportPkcs8(CryptoAlgorithmIdentifier identifier, NamedCurve curve, Vector&& 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 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 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 CryptoKeyEC::platformExportSpki() const { if (type() != CryptoKeyType::Public) return { }; int len = i2d_PUBKEY(platformKey(), nullptr); if (len < 0) return { }; Vector keyData(len); auto ptr = keyData.data(); if (i2d_PUBKEY(platformKey(), &ptr) < 0) return { }; return keyData; } Vector 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 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)