LibWeb: Implement X25519.importKey

This commit is contained in:
stelar7 2024-10-26 21:12:11 +02:00 committed by Andreas Kling
parent 5e98c3f763
commit b281fa2b24
Notes: github-actions[bot] 2024-10-31 08:53:30 +00:00
3 changed files with 247 additions and 0 deletions

View file

@ -7,6 +7,7 @@
#include <AK/Base64.h>
#include <AK/QuickSort.h>
#include <LibCrypto/ASN1/ASN1.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/Cipher/AES.h>
@ -168,6 +169,11 @@ static WebIDL::ExceptionOr<Structure> 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<Structure, StringView>) {
auto read_result = decoder.read<StringView>(::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<Structure>, "Don't know how to parse ASN.1 structure type");
}
@ -2464,4 +2470,243 @@ WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<Crypto
return Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>> { CryptoKeyPair::create(m_realm, public_key, private_key) };
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> X25519::import_key([[maybe_unused]] Web::Crypto::AlgorithmParams const& params, Bindings::KeyFormat key_format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> 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<CryptoKey> 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<ByteBuffer>()));
// 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<u16>(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<ByteBuffer>()));
// 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<u16>(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<StringView>(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<Bindings::JsonWebKey>())
return WebIDL::DataError::create(m_realm, "keyData is not a JsonWebKey dictionary"_string);
auto& jwk = key_data.get<Bindings::JsonWebKey>();
// 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 };
}
}

View file

@ -441,6 +441,7 @@ class X25519 : public AlgorithmMethods {
public:
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> derive_bits(AlgorithmParams const&, JS::NonnullGCPtr<CryptoKey>, Optional<u32>) override;
virtual WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>>> generate_key(AlgorithmParams const&, bool, Vector<Bindings::KeyUsage> const&) override;
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new X25519(realm)); }

View file

@ -814,6 +814,7 @@ SupportedAlgorithmsMap supported_algorithms()
// https://wicg.github.io/webcrypto-secure-curves/#x25519
define_an_algorithm<X25519, EcdhKeyDerivePrams>("deriveBits"_string, "X25519"_string);
define_an_algorithm<X25519>("generateKey"_string, "X25519"_string);
define_an_algorithm<X25519>("importKey"_string, "X25519"_string);
return internal_object;
}