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}