script/dom/subtlecrypto/
argon2_operation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use argon2::{Argon2, AssociatedData, ParamsBuilder, Version};
6
7use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{KeyType, KeyUsage};
8use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::KeyFormat;
9use crate::dom::bindings::error::Error;
10use crate::dom::bindings::root::DomRoot;
11use crate::dom::cryptokey::{CryptoKey, Handle};
12use crate::dom::globalscope::GlobalScope;
13use crate::dom::subtlecrypto::{
14    ALG_ARGON2D, ALG_ARGON2I, ALG_ARGON2ID, KeyAlgorithmAndDerivatives, SubtleAlgorithm,
15    SubtleArgon2Params, SubtleKeyAlgorithm,
16};
17use crate::script_runtime::CanGc;
18
19/// <https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-derive-bits>
20pub(crate) fn derive_bits(
21    normalized_algorithm: &SubtleArgon2Params,
22    key: &CryptoKey,
23    length: Option<u32>,
24) -> Result<Vec<u8>, Error> {
25    // Step 1. If length is null, or is less than 32 (4*8), then throw an OperationError.
26    let length = length.ok_or(Error::Operation(Some(
27        "Length for deriving bits is null".to_string(),
28    )))?;
29    if length < 32 {
30        return Err(Error::Operation(Some(
31            "Length for deriving bits is less than 32".to_string(),
32        )));
33    }
34
35    // Step 2. If the version member of normalizedAlgorithm is present and is not 19 (0x13), then
36    // throw an OperationError.
37    if normalized_algorithm
38        .version
39        .is_some_and(|version| version != 19)
40    {
41        return Err(Error::Operation(Some(
42            "Argon2 version is not 19 (0x13)".to_string(),
43        )));
44    }
45
46    // Step 3. If the parallelism member of normalizedAlgorithm is zero, or greater than 16777215
47    // (2^24-1), then throw an OperationError.
48    if normalized_algorithm.parallelism == 0 || normalized_algorithm.parallelism > 16777215 {
49        return Err(Error::Operation(Some(
50            "Argon2 parallelism is zero, or greater than 16777215 (2^24-1)".to_string(),
51        )));
52    }
53
54    // Step 4. If the memory member of normalizedAlgorithm is less than 8 times the parallelism
55    // member of normalizedAlgorithm, then throw an OperationError.
56    if normalized_algorithm.memory < 8 * normalized_algorithm.parallelism {
57        return Err(Error::Operation(Some(
58            "Argon2 memory is less than 8 times the parallelism".to_string(),
59        )));
60    }
61
62    // Step 5. If the passes member of normalizedAlgorithm is zero, then throw an OperationError.
63    if normalized_algorithm.passes == 0 {
64        return Err(Error::Operation(Some("Argon2 passes is zero".to_string())));
65    }
66
67    // Step 6.
68    // If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2d":
69    //     Let type be 0.
70    // If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2i":
71    //     Let type be 1.
72    // If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2id":
73    //     Let type be 2.
74    let type_ = match normalized_algorithm.name.as_str() {
75        ALG_ARGON2D => argon2::Algorithm::Argon2d,
76        ALG_ARGON2I => argon2::Algorithm::Argon2i,
77        ALG_ARGON2ID => argon2::Algorithm::Argon2id,
78        _ => {
79            return Err(Error::NotSupported(Some(format!(
80                "Unknown Argon2 algorithm name: {}",
81                normalized_algorithm.name
82            ))));
83        },
84    };
85
86    // Step 7. Let secretValue be the secretValue member of normalizedAlgorithm, if present.
87    // Step 8. Let associatedData be the associatedData member of normalizedAlgorithm, if present.
88    // Step 9. Let result be the result of performing the Argon2 function defined in Section 3 of
89    // [RFC9106] using the password represented by [[handle]] internal slot of key as the message,
90    // P, the nonce attribute of normalizedAlgorithm as the nonce, S, the value of the parallelism
91    // attribute of normalizedAlgorithm as the degree of parallelism, p, the value of the memory
92    // attribute of normalizedAlgorithm as the memory size, m, the value of the passes attribute of
93    // normalizedAlgorithm as the number of passes, t, 0x13 as the version number, v, secretValue
94    // (if present) as the secret value, K, associatedData (if present) as the associated data, X,
95    // type as the type, y, and length divided by 8 as the tag length, T.
96    // Step 10. If the key derivation operation fails, then throw an OperationError.
97    let Handle::Argon2Password(password) = key.handle() else {
98        return Err(Error::Operation(Some(
99            "Key handle is not an Argon2 password".to_string(),
100        )));
101    };
102    let mut params_builder = ParamsBuilder::new();
103    if let Some(associated_data) = &normalized_algorithm.associated_data {
104        let _ = params_builder.data(AssociatedData::new(associated_data).map_err(|_| {
105            Error::Operation(Some(
106                "Argon2 fails to add associated data to parameter builder".to_string(),
107            ))
108        })?);
109    }
110    let params = params_builder
111        .p_cost(normalized_algorithm.parallelism)
112        .m_cost(normalized_algorithm.memory)
113        .t_cost(normalized_algorithm.passes)
114        .build()
115        .map_err(|_| Error::Operation(Some("Argon2 fails to build parameters".to_string())))?;
116    let argon2_context = match &normalized_algorithm.secret_value {
117        Some(secret) => Argon2::new_with_secret(secret, type_, Version::V0x13, params)
118            .map_err(|_| Error::Operation(Some("Argon2 fails to create context".to_string())))?,
119        None => Argon2::new(type_, Version::V0x13, params),
120    };
121    let mut result = vec![0u8; length as usize / 8];
122    argon2_context
123        .hash_password_into(password, &normalized_algorithm.nonce, &mut result)
124        .map_err(|_| Error::Operation(Some("Argon2 fails to hash the password".to_string())))?;
125
126    // Step 11. Return result.
127    Ok(result)
128}
129
130/// <https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-import-key>
131pub(crate) fn import_key(
132    global: &GlobalScope,
133    normalized_algorithm: &SubtleAlgorithm,
134    format: KeyFormat,
135    key_data: &[u8],
136    extractable: bool,
137    usages: Vec<KeyUsage>,
138    can_gc: CanGc,
139) -> Result<DomRoot<CryptoKey>, Error> {
140    // Step 1. Let keyData be the key data to be imported.
141
142    // Step 2. If format is not "raw-secret", throw a NotSupportedError
143    if format != KeyFormat::Raw_secret {
144        return Err(Error::NotSupported(Some(
145            "Import key format is not \"raw-secret\"".to_string(),
146        )));
147    }
148
149    // Step 3. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a
150    // SyntaxError.
151    if usages
152        .iter()
153        .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
154    {
155        return Err(Error::Syntax(Some(
156            "Usages contains a value that is not \"deriveKey\" or \"deriveBits\"".to_string(),
157        )));
158    }
159
160    // Step 4. If extractable is not false, then throw a SyntaxError.
161    if extractable {
162        return Err(Error::Syntax(Some("Extrabctable is not false".to_string())));
163    }
164
165    // Step 5. Let key be a new CryptoKey representing keyData.
166    // Step 6. Set the [[type]] internal slot of key to "secret".
167    // Step 7. Set the [[extractable]] internal slot of key to false.
168    // Step 8. Let algorithm be a new KeyAlgorithm object.
169    // Step 9. Set the name attribute of algorithm to the name member of normalizedAlgorithm.
170    // Step 10. Set the [[algorithm]] internal slot of key to algorithm.
171    let algorithm = SubtleKeyAlgorithm {
172        name: normalized_algorithm.name.clone(),
173    };
174    let key = CryptoKey::new(
175        global,
176        KeyType::Secret,
177        false,
178        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
179        usages,
180        Handle::Argon2Password(key_data.to_vec()),
181        can_gc,
182    );
183
184    // Step 11. Return key.
185    Ok(key)
186}
187
188/// <https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-get-key-length>
189pub(crate) fn get_key_length() -> Result<Option<u32>, Error> {
190    // Step 1. Return null.
191    Ok(None)
192}