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