mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-01 03:57:15 +02:00
LibCrypto: Add AES-OCB support
Implement AES-OCB AEAD using OpenSSL and expose it through the WebCrypto API, including key management and AEAD parameters. Add WPT: /encrypt_decrypt/aes_ocb.tentative.https.any.html
This commit is contained in:
committed by
Jelle Raaijmakers
parent
d163c11612
commit
50ec070017
Notes:
github-actions[bot]
2026-02-27 11:04:51 +00:00
Author: https://github.com/mikiubo Commit: https://github.com/LadybirdBrowser/ladybird/commit/50ec070017d Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7801 Reviewed-by: https://github.com/gmta
@@ -10020,4 +10020,355 @@ WebIDL::ExceptionOr<JS::Value> ChaCha20Poly1305::get_key_length(AlgorithmParams
|
||||
return JS::Value(256);
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-encrypt
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> AesOcb::encrypt(AlgorithmParams const& params, GC::Ref<CryptoKey> key, ByteBuffer const& plaintext)
|
||||
{
|
||||
auto const& normalized_algorithm = static_cast<AeadParams const&>(params);
|
||||
|
||||
// 1. If the iv member of normalizedAlgorithm has a length greater than 15 bytes, then throw an OperationError.
|
||||
if (normalized_algorithm.iv.size() > 15)
|
||||
return WebIDL::OperationError::create(m_realm, "IV must have a length of 15 bytes"_utf16);
|
||||
|
||||
size_t tag_length = 0;
|
||||
auto constexpr valid_tag_lengths = Array { 64, 96, 128 };
|
||||
|
||||
// 2. If the tagLength member of normalizedAlgorithm is not present: Let tagLength be 128.
|
||||
if (!normalized_algorithm.tag_length.has_value())
|
||||
tag_length = 128;
|
||||
|
||||
// 2. If the tagLength member of normalizedAlgorithm is one of 64, 96 or 128: Let tagLength be equal to the tagLength member of normalizedAlgorithm
|
||||
else if (valid_tag_lengths.contains_slow(normalized_algorithm.tag_length.value()))
|
||||
tag_length = normalized_algorithm.tag_length.value();
|
||||
|
||||
// 2. Otherwise: throw an OperationError.
|
||||
else
|
||||
return WebIDL::OperationError::create(m_realm, "Invalid tag length"_utf16);
|
||||
|
||||
// 3. Let additionalData be the additionalData member of normalizedAlgorithm if present or the empty octet string otherwise.
|
||||
auto const& additional_data = normalized_algorithm.additional_data.value_or(ByteBuffer {});
|
||||
|
||||
// 4. Let C be the output that results from performing the OCB-ENCRYPT function described in Section 4.2
|
||||
// of [RFC7253] using AES as the block cipher,
|
||||
// using the key represented by [[handle]] internal slot of key as the K input parameter,
|
||||
// the iv member of normalizedAlgorithm as the N input parameter,
|
||||
// additionalData as the A input parameter,
|
||||
// plaintext as the P input parameter,
|
||||
// and tagLength as the TAGLEN global parameter.
|
||||
VERIFY(key->handle().has<ByteBuffer>());
|
||||
::Crypto::Cipher::AESOCBCipher cipher(key->handle().get<ByteBuffer>());
|
||||
auto const maybe_ciphertext = cipher.encrypt(
|
||||
plaintext,
|
||||
normalized_algorithm.iv,
|
||||
additional_data,
|
||||
tag_length);
|
||||
|
||||
if (maybe_ciphertext.is_error())
|
||||
return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Encryption failed: {}", maybe_ciphertext.error()));
|
||||
|
||||
// 5. Return C
|
||||
return JS::ArrayBuffer::create(m_realm, maybe_ciphertext.value());
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-decrypt
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> AesOcb::decrypt(AlgorithmParams const& params, GC::Ref<CryptoKey> key, ByteBuffer const& ciphertext)
|
||||
{
|
||||
auto const& normalized_algorithm = static_cast<AeadParams const&>(params);
|
||||
|
||||
// 1. If the iv member of normalizedAlgorithm has a length greater than 15 bytes, then throw an OperationError.
|
||||
if (normalized_algorithm.iv.size() > 15)
|
||||
return WebIDL::OperationError::create(m_realm, "IV must have a length of 15 bytes"_utf16);
|
||||
|
||||
size_t tag_length = 0;
|
||||
auto constexpr valid_tag_lengths = Array { 64, 96, 128 };
|
||||
|
||||
// 2. If the tagLength member of normalizedAlgorithm is not present: Let tagLength be 128.
|
||||
if (!normalized_algorithm.tag_length.has_value())
|
||||
tag_length = 128;
|
||||
|
||||
// 2. If the tagLength member of normalizedAlgorithm is one of 64, 96 or 128: Let tagLength be equal to the tagLength member of normalizedAlgorithm
|
||||
else if (valid_tag_lengths.contains_slow(normalized_algorithm.tag_length.value()))
|
||||
tag_length = normalized_algorithm.tag_length.value();
|
||||
|
||||
// 2. Otherwise: throw an OperationError.
|
||||
else
|
||||
return WebIDL::OperationError::create(m_realm, "Invalid tag length"_utf16);
|
||||
|
||||
// 3. If ciphertext has a length less than tagLength bits, then throw an OperationError.
|
||||
if (ciphertext.size() < tag_length / 8)
|
||||
return WebIDL::OperationError::create(m_realm, "Ciphertext is less than tagLength bits"_utf16);
|
||||
|
||||
// 4. Let additionalData be the additionalData member of normalizedAlgorithm if present or the empty octet string otherwise.
|
||||
auto const& additional_data = normalized_algorithm.additional_data.value_or(ByteBuffer {});
|
||||
|
||||
// 5. Perform the OCB-DECRYPT function described in Section 4.3 of [RFC7253]
|
||||
// using AES as the block cipher,
|
||||
// using the key represented by [[handle]] internal slot of key as the K input parameter,
|
||||
// the iv member of normalizedAlgorithm as the N input parameter,
|
||||
// additionalData as the A input parameter,
|
||||
// ciphertext as the C input parameter,
|
||||
// and tagLength as the TAGLEN global parameter.
|
||||
VERIFY(key->handle().has<ByteBuffer>());
|
||||
::Crypto::Cipher::AESOCBCipher cipher(key->handle().get<ByteBuffer>());
|
||||
auto const maybe_plaintext = cipher.decrypt(
|
||||
ciphertext,
|
||||
normalized_algorithm.iv,
|
||||
additional_data,
|
||||
tag_length);
|
||||
|
||||
// 5. If the result of the algorithm is the indication of authentication failure:
|
||||
// throw an OperationError
|
||||
// Otherwise:
|
||||
// Let plaintext be the resulting plaintext.
|
||||
if (maybe_plaintext.is_error())
|
||||
return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Decryption failed: {}", maybe_plaintext.error()));
|
||||
|
||||
// 6. Return plaintext.
|
||||
return JS::ArrayBuffer::create(m_realm, maybe_plaintext.value());
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-generate-key
|
||||
WebIDL::ExceptionOr<Variant<GC::Ref<CryptoKey>, GC::Ref<CryptoKeyPair>>> AesOcb::generate_key(AlgorithmParams const& params, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
|
||||
{
|
||||
// 1. If usages contains any entry which is not one of "encrypt", "decrypt", "wrapKey" or "unwrapKey", then throw a SyntaxError.
|
||||
for (auto const& usage : key_usages) {
|
||||
if (usage != Bindings::KeyUsage::Encrypt && usage != Bindings::KeyUsage::Decrypt && usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) {
|
||||
return WebIDL::SyntaxError::create(m_realm, Utf16String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If the length member of normalizedAlgorithm is not equal to one of 128, 192 or 256, then throw an OperationError.
|
||||
auto const& normalized_algorithm = static_cast<AesKeyGenParams const&>(params);
|
||||
auto const bits = normalized_algorithm.length;
|
||||
if (bits != 128 && bits != 192 && bits != 256) {
|
||||
return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Cannot create AES-OCB key with unusual amount of {} bits", bits));
|
||||
}
|
||||
|
||||
// 3. Generate an AES key of length equal to the length member of normalizedAlgorithm.
|
||||
// 4. If the key generation step fails, then throw an OperationError.
|
||||
auto key_buffer = TRY(generate_random_key(m_realm->vm(), bits));
|
||||
|
||||
// 5. Let key be a new CryptoKey object representing the generated AES key.
|
||||
auto key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_buffer });
|
||||
|
||||
// 6. Set the [[type]] internal slot of key to "secret".
|
||||
key->set_type(Bindings::KeyType::Secret);
|
||||
|
||||
// 7. Let algorithm be a new AesKeyAlgorithm.
|
||||
auto algorithm = AesKeyAlgorithm::create(m_realm);
|
||||
|
||||
// 8. Set the name attribute of algorithm to "AES-OCB".
|
||||
algorithm->set_name("AES-OCB"_string);
|
||||
|
||||
// 9. Set the length attribute of algorithm to equal the length member of normalizedAlgorithm.
|
||||
algorithm->set_length(bits);
|
||||
|
||||
// 10. Set the [[algorithm]] internal slot of key to algorithm.
|
||||
key->set_algorithm(algorithm);
|
||||
|
||||
// 11. Set the [[extractable]] internal slot of key to be extractable.
|
||||
key->set_extractable(extractable);
|
||||
|
||||
// 12. Set the [[usages]] internal slot of key to be usages.
|
||||
key->set_usages(key_usages);
|
||||
|
||||
// 13. Return key.
|
||||
return { key };
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-import-key
|
||||
WebIDL::ExceptionOr<GC::Ref<CryptoKey>> AesOcb::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& usages)
|
||||
{
|
||||
// 1. Let keyData be the key data to be imported.
|
||||
|
||||
// 2. If usages contains an entry which is not one of "encrypt", "decrypt", "wrapKey" or "unwrapKey", then throw a SyntaxError.
|
||||
for (auto const& usage : usages) {
|
||||
if (usage != Bindings::KeyUsage::Encrypt && usage != Bindings::KeyUsage::Decrypt && usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) {
|
||||
return WebIDL::SyntaxError::create(m_realm, Utf16String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)));
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer data;
|
||||
|
||||
// 3. If format is "raw-secret":
|
||||
if (format == Bindings::KeyFormat::RawSecret) {
|
||||
// 1. Let data be keyData.
|
||||
data = move(key_data.get<ByteBuffer>());
|
||||
|
||||
// 2. If the length in bits of data is not 128, 192 or 256 then throw a DataError.
|
||||
auto length_in_bits = data.size() * 8;
|
||||
if (length_in_bits != 128 && length_in_bits != 192 && length_in_bits != 256) {
|
||||
return WebIDL::DataError::create(m_realm, Utf16String::formatted("Invalid key length '{}' bits (must be either 128, 192, or 256 bits)", length_in_bits));
|
||||
}
|
||||
}
|
||||
// 3. If format is "jwk":
|
||||
else if (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, "Invalid JWK key data"_utf16);
|
||||
|
||||
auto const& jwk = key_data.get<Bindings::JsonWebKey>();
|
||||
|
||||
// 2. If the kty field of jwk is not "oct", then throw a DataError.
|
||||
if (jwk.kty != "oct"_string)
|
||||
return WebIDL::DataError::create(m_realm, "Invalid key type"_utf16);
|
||||
|
||||
// 3. If jwk does not meet the requirements of Section 6.4 of JSON Web Algorithms [JWA], then throw a DataError.
|
||||
// NOTE: "k" is already checked in step 4.
|
||||
if (!jwk.alg.has_value())
|
||||
return WebIDL::DataError::create(m_realm, "Missing 'alg' field"_utf16);
|
||||
|
||||
// 4. Let data be the byte sequence obtained by decoding the k field of jwk.
|
||||
data = TRY(parse_jwk_symmetric_key(m_realm, jwk));
|
||||
|
||||
// 5. -> If data has length 128 bits:
|
||||
// If the alg field of jwk is present, and is not "A128OCB", then throw a DataError.
|
||||
// -> If data has length 192 bits:
|
||||
// If the alg field of jwk is present, and is not "A192OCB", then throw a DataError.
|
||||
// -> If data has length 256 bits:
|
||||
// If the alg field of jwk is present, and is not "A256OCB", then throw a DataError.
|
||||
// -> Otherwise:
|
||||
// throw a DataError.
|
||||
auto data_bits = data.size() * 8;
|
||||
auto const& alg = jwk.alg;
|
||||
if (data_bits == 128) {
|
||||
if (alg.has_value() && alg != "A128OCB")
|
||||
return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_utf16);
|
||||
} else if (data_bits == 192) {
|
||||
if (alg.has_value() && alg != "A192OCB")
|
||||
return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_utf16);
|
||||
} else if (data_bits == 256) {
|
||||
if (alg.has_value() && alg != "A256OCB")
|
||||
return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_utf16);
|
||||
} else {
|
||||
return WebIDL::DataError::create(m_realm, Utf16String::formatted("Invalid key size: {} bits", data_bits));
|
||||
}
|
||||
|
||||
// 6. If usages is non-empty and the use field of jwk is present and is not "enc", then throw a DataError.
|
||||
if (!usages.is_empty() && jwk.use.has_value() && *jwk.use != "enc"_string)
|
||||
return WebIDL::DataError::create(m_realm, "Invalid use field"_utf16);
|
||||
|
||||
// 7. If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK]
|
||||
// or does not contain all of the specified usages values, then throw a DataError.
|
||||
TRY(validate_jwk_key_ops(m_realm, jwk, usages));
|
||||
|
||||
// 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 && extractable)
|
||||
return WebIDL::DataError::create(m_realm, "Invalid ext field"_utf16);
|
||||
}
|
||||
// 2. Otherwise:
|
||||
else {
|
||||
// 1. throw a NotSupportedError.
|
||||
return WebIDL::NotSupportedError::create(m_realm, "Only raw-secret and jwk formats are supported"_utf16);
|
||||
}
|
||||
|
||||
auto data_bits = data.size() * 8;
|
||||
|
||||
// 4. Let key be a new CryptoKey object representing an AES key with value data.
|
||||
auto key = CryptoKey::create(m_realm, move(data));
|
||||
|
||||
// 5. Set the [[type]] internal slot of key to "secret".
|
||||
key->set_type(Bindings::KeyType::Secret);
|
||||
|
||||
// 6. Let algorithm be a new AesKeyAlgorithm.
|
||||
auto algorithm = AesKeyAlgorithm::create(m_realm);
|
||||
|
||||
// 7. Set the name attribute of algorithm to "AES-OCB".
|
||||
algorithm->set_name("AES-OCB"_string);
|
||||
|
||||
// 8. Set the length attribute of algorithm to the length, in bits, of data.
|
||||
algorithm->set_length(data_bits);
|
||||
|
||||
// 9. Set the [[algorithm]] internal slot of key to algorithm.
|
||||
key->set_algorithm(algorithm);
|
||||
|
||||
// 10. Return key.
|
||||
return key;
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-export-key
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::Object>> AesOcb::export_key(Bindings::KeyFormat format, GC::Ref<CryptoKey> key)
|
||||
{
|
||||
|
||||
// 1. If the underlying cryptographic key material represented by the [[handle]] internal slot of key cannot be accessed, then throw an OperationError.
|
||||
// Note: In our impl this is always accessible
|
||||
|
||||
GC::Ptr<JS::Object> result = nullptr;
|
||||
|
||||
// 2. If format is "raw-secret":
|
||||
if (format == Bindings::KeyFormat::RawSecret) {
|
||||
// 1. Let data be the raw octets of the key represented by [[handle]] internal slot of key.
|
||||
auto data = key->handle().get<ByteBuffer>();
|
||||
|
||||
// 2. Let result be data.
|
||||
result = JS::ArrayBuffer::create(m_realm, data);
|
||||
}
|
||||
// 2. If format is "jwk":
|
||||
else if (format == Bindings::KeyFormat::Jwk) {
|
||||
// 1. Let jwk be a new JsonWebKey dictionary.
|
||||
Bindings::JsonWebKey jwk = {};
|
||||
|
||||
// 2. Set the kty attribute of jwk to the string "oct".
|
||||
jwk.kty = "oct"_string;
|
||||
|
||||
// 3. Set the k attribute of jwk to be a string containing the raw octets of the key represented by [[handle]] internal slot of key,
|
||||
// encoded according to Section 6.4 of JSON Web Algorithms [JWA].
|
||||
auto const& key_bytes = key->handle().get<ByteBuffer>();
|
||||
jwk.k = TRY_OR_THROW_OOM(m_realm->vm(), encode_base64url(key_bytes, AK::OmitPadding::Yes));
|
||||
|
||||
// 4. -> If the length attribute of key is 128:
|
||||
// Set the alg attribute of jwk to the string "A128OCB".
|
||||
// -> If the length attribute of key is 192:
|
||||
// Set the alg attribute of jwk to the string "A192OCB".
|
||||
// -> If the length attribute of key is 256:
|
||||
// Set the alg attribute of jwk to the string "A256OCB".
|
||||
auto key_bits = key_bytes.size() * 8;
|
||||
if (key_bits == 128) {
|
||||
jwk.alg = "A128OCB"_string;
|
||||
} else if (key_bits == 192) {
|
||||
jwk.alg = "A192OCB"_string;
|
||||
} else if (key_bits == 256) {
|
||||
jwk.alg = "A256OCB"_string;
|
||||
}
|
||||
|
||||
// 5. Set the key_ops attribute of jwk to the usages attribute of key.
|
||||
jwk.key_ops = Vector<String> {};
|
||||
jwk.key_ops->ensure_capacity(key->internal_usages().size());
|
||||
for (auto const& usage : key->internal_usages()) {
|
||||
jwk.key_ops->unchecked_append(Bindings::idl_enum_to_string(usage));
|
||||
}
|
||||
|
||||
// 6. Set the ext attribute of jwk to equal the [[extractable]] internal slot of key.
|
||||
jwk.ext = key->extractable();
|
||||
|
||||
// 7. Let result be jwk.
|
||||
return TRY(jwk.to_object(m_realm));
|
||||
}
|
||||
// 2. Otherwise:
|
||||
else {
|
||||
// 1. throw a NotSupportedError.
|
||||
return WebIDL::NotSupportedError::create(m_realm, "Cannot export to unsupported format"_utf16);
|
||||
}
|
||||
|
||||
// 3. Return result.
|
||||
return GC::Ref { *result };
|
||||
}
|
||||
|
||||
// https://wicg.github.io/webcrypto-modern-algos/#aes-ocb-operations-get-key-length
|
||||
WebIDL::ExceptionOr<JS::Value> AesOcb::get_key_length(AlgorithmParams const& params)
|
||||
{
|
||||
auto const& normalized_algorithm = static_cast<AesKeyGenParams const&>(params);
|
||||
|
||||
// 1. If the length member of normalizedDerivedKeyAlgorithm is not 128, 192 or 256, then throw an OperationError.
|
||||
auto const length = normalized_algorithm.length;
|
||||
if (length != 128 && length != 192 && length != 256) {
|
||||
return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Invalid key length: {}", length));
|
||||
}
|
||||
|
||||
// 2. Return the length member of normalizedDerivedKeyAlgorithm.
|
||||
return JS::Value(length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user