script: Refactoring of algorithm normalization in SubtleCrypto (#39431)

In our current implementation, we have multiple functions such as
`normalize_algoirthm_for_encrypt_or_decrypt` and
`normalize_algorithm_for_sign_or_verify` to normalize an algorithm, and
each of them works slightly differently. However, the spec defines a
single normalization procedure to handle all normalization.

This patch tries to consolidate our functions into a single
spec-compliant normalization function named `normalize_algorithm`.

The refactoring involves many existing code, so this patch only
introduces the new infrastructure without touching the existing. When
this patch gets approved and merged, we can then start migrating the
existing to the new infrastructure. (Note that SHA's digestion and
AES_CTR's encryption are also copied to the new infrastructure as
demonstration.)

More details about the refactoring can be found in the comment:
https://github.com/servo/servo/issues/39368#issuecomment-3316943206

Testing: The new code is not in used right now. No test is needed.
Fixes: Part of #39368

---------

Signed-off-by: Kingsley Yung <kingsley@kkoyung.dev>
This commit is contained in:
Kingsley Yung 2025-09-25 01:05:34 +08:00 committed by GitHub
parent 2ccaf86ff6
commit c15495b3e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 288 additions and 9 deletions

View file

@ -0,0 +1,52 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use aes::cipher::generic_array::GenericArray;
use aes::cipher::{KeyIvInit, StreamCipher};
use aes::{Aes128, Aes192, Aes256};
use crate::dom::bindings::error::Error;
use crate::dom::cryptokey::{CryptoKey, Handle};
use crate::dom::subtlecrypto::SubtleAesCtrParams;
/// <https://w3c.github.io/webcrypto/#aes-ctr-operations-encrypt>
pub(crate) fn encrypt_aes_ctr(
params: &SubtleAesCtrParams,
key: &CryptoKey,
data: &[u8],
) -> Result<Vec<u8>, Error> {
// Step 1. If the counter member of normalizedAlgorithm does not have a length of 16 bytes,
// then throw an OperationError.
// Step 2. If the length member of normalizedAlgorithm is zero or is greater than 128, then
// throw an OperationError.
if params.counter.len() != 16 || params.length == 0 || params.length > 128 {
return Err(Error::Operation);
}
// Step 3. Let ciphertext be the result of performing the CTR Encryption operation described in
// Section 6.5 of [NIST-SP800-38A] using AES as the block cipher, the counter member of
// normalizedAlgorithm as the initial value of the counter block, the length member of
// normalizedAlgorithm as the input parameter m to the standard counter block incrementing
// function defined in Appendix B.1 of [NIST-SP800-38A] and plaintext as the input plaintext.
let mut ciphertext = Vec::from(data);
let counter = GenericArray::from_slice(&params.counter);
match key.handle() {
Handle::Aes128(data) => {
let key_data = GenericArray::from_slice(data);
ctr::Ctr64BE::<Aes128>::new(key_data, counter).apply_keystream(&mut ciphertext)
},
Handle::Aes192(data) => {
let key_data = GenericArray::from_slice(data);
ctr::Ctr64BE::<Aes192>::new(key_data, counter).apply_keystream(&mut ciphertext)
},
Handle::Aes256(data) => {
let key_data = GenericArray::from_slice(data);
ctr::Ctr64BE::<Aes256>::new(key_data, counter).apply_keystream(&mut ciphertext)
},
_ => return Err(Error::Data),
};
// Step 3. Return ciphertext.
Ok(ciphertext)
}

View file

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use aws_lc_rs::digest;
use crate::dom::bindings::error::Error;
use crate::dom::subtlecrypto::{ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512, SubtleAlgorithm};
/// <https://w3c.github.io/webcrypto/#sha-operations-digest>
pub(crate) fn digest(
nomrmalized_algorithm: &SubtleAlgorithm,
message: &[u8],
) -> Result<Vec<u8>, Error> {
// Step 1.
// If the name member of normalizedAlgorithm is a cases-sensitive string match for "SHA-1":
// Let result be the result of performing the SHA-1 hash function defined in Section 6.1 of
// [FIPS-180-4] using message as the input message, M.
// If the name member of normalizedAlgorithm is a cases-sensitive string match for "SHA-256":
// Let result be the result of performing the SHA-256 hash function defined in Section 6.2
// of [FIPS-180-4] using message as the input message, M.
// If the name member of normalizedAlgorithm is a cases-sensitive string match for "SHA-384":
// Let result be the result of performing the SHA-384 hash function defined in Section 6.5
// of [FIPS-180-4] using message as the input message, M.
// If the name member of normalizedAlgorithm is a cases-sensitive string match for "SHA-512":
// Let result be the result of performing the SHA-512 hash function defined in Section 6.4
// of [FIPS-180-4] using message as the input message, M.
// Step 2. If performing the operation results in an error, then throw an OperationError.
let result = match nomrmalized_algorithm.name.as_str() {
ALG_SHA1 => digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, message)
.as_ref()
.to_vec(),
ALG_SHA256 => digest::digest(&digest::SHA256, message).as_ref().to_vec(),
ALG_SHA384 => digest::digest(&digest::SHA384, message).as_ref().to_vec(),
ALG_SHA512 => digest::digest(&digest::SHA512, message).as_ref().to_vec(),
_ => {
return Err(Error::NotSupported);
},
};
// Step 3. Return result.
Ok(result)
}