Skip to main content

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