diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 0e68b9f1d55..9457cbc93ba 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -168,6 +169,11 @@ static WebIDL::ExceptionOr parse_an_ASN1_structure(JS::Realm& realm, if (maybe_private_key.is_error()) return WebIDL::DataError::create(realm, MUST(String::formatted("Error parsing privateKeyInfo: {}", maybe_private_key.release_error()))); structure = maybe_private_key.release_value(); + } else if constexpr (IsSame) { + auto read_result = decoder.read(::Crypto::ASN1::Class::Universal, ::Crypto::ASN1::Kind::OctetString); + if (read_result.is_error()) + return WebIDL::DataError::create(realm, MUST(String::formatted("Read of kind OctetString failed: {}", read_result.error()))); + structure = read_result.release_value(); } else { static_assert(DependentFalse, "Don't know how to parse ASN.1 structure type"); } @@ -2464,4 +2470,243 @@ WebIDL::ExceptionOr, JS::NonnullGCPtr, JS::NonnullGCPtr> { CryptoKeyPair::create(m_realm, public_key, private_key) }; } +WebIDL::ExceptionOr> X25519::import_key([[maybe_unused]] Web::Crypto::AlgorithmParams const& params, Bindings::KeyFormat key_format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& usages) +{ + // NOTE: This is a parameter to the function + // 1. Let keyData be the key data to be imported. + + auto& vm = m_realm->vm(); + JS::GCPtr key = nullptr; + + // 2. If format is "spki": + if (key_format == Bindings::KeyFormat::Spki) { + // 1. If usages is not empty then throw a SyntaxError. + if (!usages.is_empty()) + return WebIDL::SyntaxError::create(m_realm, "Usages must be empty"_string); + + // 2. Let spki be the result of running the parse a subjectPublicKeyInfo algorithm over keyData. + // 3. If an error occurred while parsing, then throw a DataError. + auto spki = TRY(parse_a_subject_public_key_info(m_realm, key_data.get())); + + // 4. If the algorithm object identifier field of the algorithm AlgorithmIdentifier field of spki + // is not equal to the id-X25519 object identifier defined in [RFC8410], then throw a DataError. + if (spki.algorithm.identifier != TLS::x25519_oid) + return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); + + // 5. If the parameters field of the algorithm AlgorithmIdentifier field of spki is present, then throw a DataError. + if (static_cast(spki.algorithm.ec_parameters) != 0) + return WebIDL::DataError::create(m_realm, "Invalid algorithm parameters"_string); + + // 6. Let publicKey be the X25519 public key identified by the subjectPublicKey field of spki. + auto public_key = spki.raw_key; + + // 7. Let key be a new CryptoKey associated with the relevant global object of this [HTML], and that represents publicKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); + + // 8. Set the [[type]] internal slot of key to "public" + key->set_type(Bindings::KeyType::Public); + + // 9. Let algorithm be a new KeyAlgorithm. + auto algorithm = KeyAlgorithm::create(m_realm); + + // 10. Set the name attribute of algorithm to "X25519". + algorithm->set_name("X25519"_string); + + // 11. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + } + + // 2. If format is "pkcs8": + else if (key_format == Bindings::KeyFormat::Pkcs8) { + // 1. If usages contains an entry which is not "deriveKey" or "deriveBits" then throw a SyntaxError. + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + // 2. Let privateKeyInfo be the result of running the parse a privateKeyInfo algorithm over keyData. + // 3. If an error occurred while parsing, then throw a DataError. + auto private_key_info = TRY(parse_a_private_key_info(m_realm, key_data.get())); + + // 4. If the algorithm object identifier field of the privateKeyAlgorithm PrivateKeyAlgorithm field of privateKeyInfo + // is not equal to the id-X25519 object identifier defined in [RFC8410], then throw a DataError. + if (private_key_info.algorithm.identifier != TLS::x25519_oid) + return WebIDL::DataError::create(m_realm, "Invalid algorithm"_string); + + // 5. If the parameters field of the privateKeyAlgorithm PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a DataError. + if (static_cast(private_key_info.algorithm.ec_parameters) != 0) + return WebIDL::DataError::create(m_realm, "Invalid algorithm parameters"_string); + + // 6. Let curvePrivateKey be the result of performing the parse an ASN.1 structure algorithm, + // with data as the privateKey field of privateKeyInfo, + // structure as the ASN.1 CurvePrivateKey structure specified in Section 7 of [RFC8410], and + // exactData set to true. + // 7. If an error occurred while parsing, then throw a DataError. + auto curve_private_key = TRY(parse_an_ASN1_structure(m_realm, private_key_info.raw_key, true)); + auto curve_private_key_bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::copy(curve_private_key.bytes())); + + // 8. Let key be a new CryptoKey associated with the relevant global object of this [HTML], + // and that represents the X25519 private key identified by curvePrivateKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { curve_private_key_bytes }); + + // 9. Set the [[type]] internal slot of key to "private" + key->set_type(Bindings::KeyType::Private); + + // 10. Let algorithm be a new KeyAlgorithm. + auto algorithm = KeyAlgorithm::create(m_realm); + + // 11. Set the name attribute of algorithm to "X25519". + algorithm->set_name("X25519"_string); + + // 12. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + } + + // 2. If format is "jwk": + else if (key_format == Bindings::KeyFormat::Jwk) { + // 1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData. + // Otherwise: Throw a DataError. + if (!key_data.has()) + return WebIDL::DataError::create(m_realm, "keyData is not a JsonWebKey dictionary"_string); + auto& jwk = key_data.get(); + + // 2. If the d field is present and if usages contains an entry which is not "deriveKey" or "deriveBits" then throw a SyntaxError. + if (jwk.d.has_value() && !usages.is_empty()) { + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + } + + // 3. If the d field is not present and if usages is not empty then throw a SyntaxError. + if (!jwk.d.has_value() && !usages.is_empty()) + return WebIDL::SyntaxError::create(m_realm, "Usages must be empty if d is missing"_string); + + // 4. If the kty field of jwk is not "OKP", then throw a DataError. + if (jwk.kty != "OKP"sv) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // 5. If the crv field of jwk is not "X25519", then throw a DataError. + if (jwk.crv != "X25519"sv) + return WebIDL::DataError::create(m_realm, "Invalid curve"_string); + + // 6. If usages is non-empty and the use field of jwk is present and is not equal to "enc" then throw a DataError. + if (!usages.is_empty() && jwk.use.has_value() && jwk.use.value() != "enc"sv) + return WebIDL::DataError::create(m_realm, "Invalid use"_string); + + // 7. If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK], + // or it does not contain all of the specified usages values, then throw a DataError. + if (jwk.key_ops.has_value()) { + for (auto const& usage : usages) { + if (!jwk.key_ops->contains_slow(Bindings::idl_enum_to_string(usage))) + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Missing key_ops field: {}", Bindings::idl_enum_to_string(usage)))); + } + } + + // 8. If the ext field of jwk is present and has the value false and extractable is true, then throw a DataError. + if (jwk.ext.has_value() && !jwk.ext.value() && extractable) + return WebIDL::DataError::create(m_realm, "Invalid extractable"_string); + + // 9. If the d field is present: + if (jwk.d.has_value()) { + // 1. If jwk does not meet the requirements of the JWK private key format described in Section 2 of [RFC8037], then throw a DataError. + // o The parameter "kty" MUST be "OKP". + if (jwk.kty != "OKP"sv) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // // https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve + // o The parameter "crv" MUST be present and contain the subtype of the key (from the "JSON Web Elliptic Curve" registry). + if (jwk.crv != "X25519"sv) + return WebIDL::DataError::create(m_realm, "Invalid curve"_string); + + // o The parameter "x" MUST be present and contain the public key encoded using the base64url [RFC4648] encoding. + if (!jwk.x.has_value()) + return WebIDL::DataError::create(m_realm, "Missing x field"_string); + + // o The parameter "d" MUST be present for private keys and contain the private key encoded using the base64url encoding. + // This parameter MUST NOT be present for public keys. + if (!jwk.d.has_value()) + return WebIDL::DataError::create(m_realm, "Missing d field"_string); + + // 2. Let key be a new CryptoKey object that represents the X25519 private key identified by interpreting jwk according to Section 2 of [RFC8037]. + auto private_key_base_64 = jwk.d.value(); + auto private_key = TRY_OR_THROW_OOM(vm, decode_base64(private_key_base_64)); + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key }); + + // 3. Set the [[type]] internal slot of Key to "private". + key->set_type(Bindings::KeyType::Private); + } + // 9. Otherwise: + else { + // 1. If jwk does not meet the requirements of the JWK public key format described in Section 2 of [RFC8037], then throw a DataError. + // o The parameter "kty" MUST be "OKP". + if (jwk.kty != "OKP"sv) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve + // o The parameter "crv" MUST be present and contain the subtype of the key (from the "JSON Web Elliptic Curve" registry). + if (jwk.crv != "X25519"sv) + return WebIDL::DataError::create(m_realm, "Invalid curve"_string); + + // o The parameter "x" MUST be present and contain the public key encoded using the base64url [RFC4648] encoding. + if (!jwk.x.has_value()) + return WebIDL::DataError::create(m_realm, "Missing x field"_string); + + // o The parameter "d" MUST be present for private keys and contain the private key encoded using the base64url encoding. + // This parameter MUST NOT be present for public keys. + if (jwk.d.has_value()) + return WebIDL::DataError::create(m_realm, "Present d field"_string); + + // 2. Let key be a new CryptoKey object that represents the X25519 public key identified by interpreting jwk according to Section 2 of [RFC8037]. + auto public_key_base_64 = jwk.x.value(); + auto public_key = TRY_OR_THROW_OOM(vm, decode_base64(public_key_base_64)); + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); + + // 3. Set the [[type]] internal slot of Key to "public". + key->set_type(Bindings::KeyType::Public); + } + + // 10. Let algorithm be a new instance of a KeyAlgorithm object. + auto algorithm = KeyAlgorithm::create(m_realm); + + // 11. Set the name attribute of algorithm to "X25519". + algorithm->set_name("X25519"_string); + + // 12. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + } + + // 2. If format is "raw": + else if (key_format == Bindings::KeyFormat::Raw) { + // 1. If usages is not empty then throw a SyntaxError. + if (!usages.is_empty()) + return WebIDL::SyntaxError::create(m_realm, "Usages must be empty"_string); + + // 2. Let algorithm be a new KeyAlgorithm object. + auto algorithm = KeyAlgorithm::create(m_realm); + + // 3. Set the name attribute of algorithm to "X25519". + algorithm->set_name("X25519"_string); + + // 4. Let key be a new CryptoKey associated with the relevant global object of this [HTML], and representing the key data provided in keyData. + key = CryptoKey::create(m_realm, key_data); + + // 5. Set the [[type]] internal slot of key to "public" + key->set_type(Bindings::KeyType::Public); + + // 6. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + } + + // 2. Otherwise: throw a NotSupportedError. + else { + return WebIDL::NotSupportedError::create(m_realm, "Invalid key format"_string); + } + + // 3. Return key + return JS::NonnullGCPtr { *key }; +} + } diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 319d33042f2..39e21e4b757 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -441,6 +441,7 @@ class X25519 : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr> derive_bits(AlgorithmParams const&, JS::NonnullGCPtr, Optional) override; virtual WebIDL::ExceptionOr, JS::NonnullGCPtr>> generate_key(AlgorithmParams const&, bool, Vector const&) override; + virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new X25519(realm)); } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index c02742b5de6..1ff67bd238f 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -814,6 +814,7 @@ SupportedAlgorithmsMap supported_algorithms() // https://wicg.github.io/webcrypto-secure-curves/#x25519 define_an_algorithm("deriveBits"_string, "X25519"_string); define_an_algorithm("generateKey"_string, "X25519"_string); + define_an_algorithm("importKey"_string, "X25519"_string); return internal_object; }