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