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