Skip to main content

script/dom/webcrypto/subtlecrypto/
ecdsa_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 digest::Digest;
6use ecdsa::signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
7use ecdsa::{Signature, SigningKey, VerifyingKey};
8use elliptic_curve::rand_core::OsRng;
9use js::context::JSContext;
10use p256::NistP256;
11use p384::NistP384;
12use p521::NistP521;
13use sha1::Sha1;
14use sha2::{Sha256, Sha384, Sha512};
15
16use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
17    CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
18};
19use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::KeyFormat;
20use crate::dom::bindings::error::Error;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::cryptokey::{CryptoKey, Handle};
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::subtlecrypto::ec_common::EcAlgorithm;
25use crate::dom::subtlecrypto::{
26    CryptoAlgorithm, ExportedKey, KeyAlgorithmAndDerivatives, NAMED_CURVE_P256, NAMED_CURVE_P384,
27    NAMED_CURVE_P521, NormalizedAlgorithm, SubtleEcKeyGenParams, SubtleEcKeyImportParams,
28    SubtleEcdsaParams, ec_common,
29};
30
31const P256_PREHASH_LENGTH: usize = 32;
32const P384_PREHASH_LENGTH: usize = 48;
33const P521_PREHASH_LENGTH: usize = 66;
34
35/// <https://w3c.github.io/webcrypto/#ecdsa-operations-sign>
36pub(crate) fn sign(
37    normalized_algorithm: &SubtleEcdsaParams,
38    key: &CryptoKey,
39    message: &[u8],
40) -> Result<Vec<u8>, Error> {
41    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
42    // InvalidAccessError.
43    if key.Type() != KeyType::Private {
44        return Err(Error::InvalidAccess(None));
45    }
46
47    // Step 2. Let hashAlgorithm be the hash member of normalizedAlgorithm.
48    let hash_algorithm = &normalized_algorithm.hash;
49
50    // Step 3. Let M be the result of performing the digest operation specified by hashAlgorithm
51    // using message.
52    let m = match hash_algorithm.name() {
53        CryptoAlgorithm::Sha1 => Sha1::digest(message).to_vec(),
54        CryptoAlgorithm::Sha256 => Sha256::digest(message).to_vec(),
55        CryptoAlgorithm::Sha384 => Sha384::digest(message).to_vec(),
56        CryptoAlgorithm::Sha512 => Sha512::digest(message).to_vec(),
57        hash_algorithm_name => {
58            return Err(Error::NotSupported(Some(format!(
59                "Unsupported hash algorithm for ECDSA: {}",
60                hash_algorithm_name.as_str()
61            ))));
62        },
63    };
64
65    // Step 4. Let d be the ECDSA private key associated with key.
66    // Step 5. Let params be the EC domain parameters associated with key.
67    // Step 6.
68    // If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or
69    // "P-521":
70    //     1. Perform the ECDSA signing process, as specified in [RFC6090], Section 5.4.2, with M
71    //        as the message, using params as the EC domain parameters, and with d as the private
72    //        key.
73    //     2. Let r and s be the pair of integers resulting from performing the ECDSA signing
74    //        process.
75    //     3. Let result be an empty byte sequence.
76    //     4. Let n be the smallest integer such that n * 8 is greater than the logarithm to base 2
77    //        of the order of the base point of the elliptic curve identified by params.
78    //     5. Convert r to a byte sequence of length n and append it to result.
79    //     6. Convert s to a byte sequence of length n and append it to result.
80    // Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value
81    // specified in an applicable specification:
82    //     Perform the ECDSA signature steps specified in that specification, passing in M, params
83    //     and d and resulting in result.
84    // NOTE: We currently do not support other applicable specifications.
85    let KeyAlgorithmAndDerivatives::EcKeyAlgorithm(algorithm) = key.algorithm() else {
86        return Err(Error::Operation(None));
87    };
88    let result = match algorithm.named_curve.as_str() {
89        NAMED_CURVE_P256 => {
90            // P-256 expects prehash with length at least 32 bytes. If M is shorter than 32 bytes,
91            // expand it by left padding with zeros.
92            let m = expand_prehash(m, P256_PREHASH_LENGTH);
93
94            let Handle::P256PrivateKey(d) = key.handle() else {
95                return Err(Error::Operation(None));
96            };
97            let signing_key = SigningKey::<NistP256>::from(d);
98            let signature: Signature<NistP256> = signing_key
99                .sign_prehash_with_rng(&mut OsRng, m.as_slice())
100                .map_err(|_| Error::Operation(None))?;
101            signature.to_vec()
102        },
103        NAMED_CURVE_P384 => {
104            // P-384 expects prehash with length at least 48 bytes. If M is shorter than 48 bytes,
105            // expand it by left padding with zeros.
106            let m = expand_prehash(m, P384_PREHASH_LENGTH);
107
108            let Handle::P384PrivateKey(d) = key.handle() else {
109                return Err(Error::Operation(None));
110            };
111            let signing_key = SigningKey::<NistP384>::from(d);
112            let signature: Signature<NistP384> = signing_key
113                .sign_prehash_with_rng(&mut OsRng, m.as_slice())
114                .map_err(|_| Error::Abort(None))?;
115            signature.to_vec()
116        },
117        NAMED_CURVE_P521 => {
118            // P-521 expects prehash with length at least 66 bytes. If M is shorter than 66 bytes,
119            // expand it by left padding with zeros.
120            let m = expand_prehash(m, P521_PREHASH_LENGTH);
121
122            let Handle::P521PrivateKey(d) = key.handle() else {
123                return Err(Error::Operation(None));
124            };
125            let signing_key = p521::ecdsa::SigningKey::from_slice(d.to_bytes().as_slice())
126                .map_err(|_| Error::Operation(None))?;
127            let signature: Signature<NistP521> = signing_key
128                .sign_prehash_with_rng(&mut OsRng, m.as_slice())
129                .map_err(|_| Error::Operation(None))?;
130            signature.to_vec()
131        },
132        _ => return Err(Error::NotSupported(None)),
133    };
134
135    // Step 7. Return result.
136    Ok(result)
137}
138
139/// <https://w3c.github.io/webcrypto/#ecdsa-operations-verify>
140pub(crate) fn verify(
141    normalized_algorithm: &SubtleEcdsaParams,
142    key: &CryptoKey,
143    message: &[u8],
144    signature: &[u8],
145) -> Result<bool, Error> {
146    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
147    // InvalidAccessError.
148    if key.Type() != KeyType::Public {
149        return Err(Error::InvalidAccess(None));
150    }
151
152    // Step 2. Let hashAlgorithm be the hash member of normalizedAlgorithm.
153    let hash_algorithm = &normalized_algorithm.hash;
154
155    // Step 3. Let M be the result of performing the digest operation specified by hashAlgorithm
156    // using message.
157    let m = match hash_algorithm.name() {
158        CryptoAlgorithm::Sha1 => Sha1::new_with_prefix(message).finalize().to_vec(),
159        CryptoAlgorithm::Sha256 => Sha256::new_with_prefix(message).finalize().to_vec(),
160        CryptoAlgorithm::Sha384 => Sha384::new_with_prefix(message).finalize().to_vec(),
161        CryptoAlgorithm::Sha512 => Sha512::new_with_prefix(message).finalize().to_vec(),
162        _ => return Err(Error::NotSupported(None)),
163    };
164
165    // Step 4. Let Q be the ECDSA public key associated with key.
166    // Step 5. Let params be the EC domain parameters associated with key.
167    // Step 6.
168    // If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or
169    // "P-521":
170    //     1. Let n be the smallest integer such that n * 8 is greater than the logarithm to base 2
171    //        of the order of the base point of the elliptic curve identified by params.
172    //     2. If signature does not have a length of n * 2 bytes, then return false.
173    //     3. Let r be the result of converting the first n bytes of signature to an integer.
174    //     4. Let s be the result of converting the last n bytes of signature to an integer.
175    //     5. Perform the ECDSA verifying process, as specified in [RFC6090], Section 5.4.3, with M
176    //        as the received message, (r, s) as the signature and using params as the EC domain
177    //        parameters, and Q as the public key.
178    // Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value
179    // specified in an applicable specification:
180    //     Perform the ECDSA verification steps specified in that specification passing in M,
181    //     signature, params and Q and resulting in an indication of whether or not the purported
182    //     signature is valid.
183    // Step 7. Let result be a boolean with the value true if the signature is valid and the value
184    // false otherwise.
185    // NOTE: We currently do not support other applicable specifications.
186    let KeyAlgorithmAndDerivatives::EcKeyAlgorithm(algorithm) = key.algorithm() else {
187        return Err(Error::Operation(None));
188    };
189    let result = match algorithm.named_curve.as_str() {
190        NAMED_CURVE_P256 => {
191            // P-256 expects prehash with length at least 32 bytes. If M is shorter than 32 bytes,
192            // expand it by left padding with zeros.
193            let m = expand_prehash(m, P256_PREHASH_LENGTH);
194
195            let Handle::P256PublicKey(q) = key.handle() else {
196                return Err(Error::Operation(None));
197            };
198            match Signature::<NistP256>::from_slice(signature) {
199                Ok(signature) => {
200                    let verifying_key = VerifyingKey::<NistP256>::from(q);
201                    verifying_key.verify_prehash(&m, &signature).is_ok()
202                },
203                Err(_) => false,
204            }
205        },
206        NAMED_CURVE_P384 => {
207            // P-384 expects prehash with length at least 48 bytes. If M is shorter than 48 bytes,
208            // expand it by left padding with zeros.
209            let m = expand_prehash(m, P384_PREHASH_LENGTH);
210
211            let Handle::P384PublicKey(q) = key.handle() else {
212                return Err(Error::Operation(None));
213            };
214            match Signature::<NistP384>::from_slice(signature) {
215                Ok(signature) => {
216                    let verifying_key = VerifyingKey::<NistP384>::from(q);
217                    verifying_key.verify_prehash(&m, &signature).is_ok()
218                },
219                Err(_) => false,
220            }
221        },
222        NAMED_CURVE_P521 => {
223            // P-521 expects prehash with length at least 66 bytes. If M is shorter than 66 bytes,
224            // expand it by left padding with zeros.
225            let m = expand_prehash(m, P521_PREHASH_LENGTH);
226
227            let Handle::P521PublicKey(q) = key.handle() else {
228                return Err(Error::Operation(None));
229            };
230            match (
231                Signature::<NistP521>::from_slice(signature),
232                p521::ecdsa::VerifyingKey::from_sec1_bytes(q.to_sec1_bytes().to_vec().as_slice()),
233            ) {
234                (Ok(signature), Ok(verifying_key)) => {
235                    verifying_key.verify_prehash(&m, &signature).is_ok()
236                },
237                _ => false,
238            }
239        },
240        _ => return Err(Error::NotSupported(None)),
241    };
242
243    // Step 8. Return result.
244    Ok(result)
245}
246
247/// <https://w3c.github.io/webcrypto/#ecdsa-operations-generate-key>
248pub(crate) fn generate_key(
249    cx: &mut JSContext,
250    global: &GlobalScope,
251    normalized_algorithm: &SubtleEcKeyGenParams,
252    extractable: bool,
253    usages: Vec<KeyUsage>,
254) -> Result<CryptoKeyPair, Error> {
255    ec_common::generate_key(
256        EcAlgorithm::Ecdsa,
257        cx,
258        global,
259        normalized_algorithm,
260        extractable,
261        usages,
262    )
263}
264
265/// <https://w3c.github.io/webcrypto/#ecdsa-operations-import-key>
266pub(crate) fn import_key(
267    cx: &mut JSContext,
268    global: &GlobalScope,
269    normalized_algorithm: &SubtleEcKeyImportParams,
270    format: KeyFormat,
271    key_data: &[u8],
272    extractable: bool,
273    usages: Vec<KeyUsage>,
274) -> Result<DomRoot<CryptoKey>, Error> {
275    ec_common::import_key(
276        EcAlgorithm::Ecdsa,
277        cx,
278        global,
279        normalized_algorithm,
280        format,
281        key_data,
282        extractable,
283        usages,
284    )
285}
286
287/// <https://w3c.github.io/webcrypto/#ecdsa-operations-export-key>
288pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
289    ec_common::export_key(format, key)
290}
291
292/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
293/// Step 9 - 15, for ECDSA
294pub(crate) fn get_public_key(
295    cx: &mut JSContext,
296    global: &GlobalScope,
297    key: &CryptoKey,
298    algorithm: &KeyAlgorithmAndDerivatives,
299    usages: Vec<KeyUsage>,
300) -> Result<DomRoot<CryptoKey>, Error> {
301    ec_common::get_public_key(cx, global, key, algorithm, usages)
302}
303
304/// A helper function that expand a prehash to a specified length if the prehash is shorter than
305/// the specified length.
306///
307/// If the length of `prehash` is less than `length`, return a prehash with length `length`
308/// constructed by left-padding `prehash` with zeros. Otherwire, return the unmodified `prehash`.
309fn expand_prehash(prehash: Vec<u8>, length: usize) -> Vec<u8> {
310    if prehash.len() < length {
311        let mut left_padded_prehash = vec![0u8; length];
312        left_padded_prehash[length - prehash.len()..].copy_from_slice(&prehash);
313        left_padded_prehash
314    } else {
315        prehash
316    }
317}