Skip to main content

script/dom/webcrypto/subtlecrypto/
ecdh_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 elliptic_curve::Curve;
6use elliptic_curve::array::typenum::Unsigned;
7use js::context::JSContext;
8use p256::NistP256;
9use p256::ecdh::diffie_hellman as p256_diffie_hellman;
10use p384::NistP384;
11use p384::ecdh::diffie_hellman as p384_diffie_hellman;
12use p521::NistP521;
13use p521::ecdh::diffie_hellman as p521_diffie_hellman;
14
15use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
16    CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
17};
18use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::KeyFormat;
19use crate::dom::bindings::error::Error;
20use crate::dom::bindings::root::DomRoot;
21use crate::dom::cryptokey::{CryptoKey, Handle};
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::subtlecrypto::ec_common::EcAlgorithm;
24use crate::dom::subtlecrypto::{
25    EcKeyGenParams, EcKeyImportParams, EcdhKeyDeriveParams, ExportedKey,
26    KeyAlgorithmAndDerivatives, NAMED_CURVE_P256, NAMED_CURVE_P384, NAMED_CURVE_P521, ec_common,
27};
28
29/// <https://w3c.github.io/webcrypto/#ecdh-operations-generate-key>
30pub(crate) fn generate_key(
31    cx: &mut JSContext,
32    global: &GlobalScope,
33    normalized_algorithm: &EcKeyGenParams,
34    extractable: bool,
35    usages: Vec<KeyUsage>,
36) -> Result<CryptoKeyPair, Error> {
37    ec_common::generate_key(
38        EcAlgorithm::Ecdh,
39        cx,
40        global,
41        normalized_algorithm,
42        extractable,
43        usages,
44    )
45}
46
47/// <https://w3c.github.io/webcrypto/#ecdh-operations-derive-bits>
48pub(crate) fn derive_bits(
49    normalized_algorithm: &EcdhKeyDeriveParams,
50    key: &CryptoKey,
51    length: Option<u32>,
52) -> Result<Vec<u8>, Error> {
53    // Step 1. Let publicKey be the public member of normalizedAlgorithm.
54    let public_key = normalized_algorithm.public.root();
55
56    // Step 2. If the [[type]] internal slot of publicKey is not "public", then throw an
57    // InvalidAccessError.
58    if public_key.Type() != KeyType::Public {
59        return Err(Error::InvalidAccess(Some(
60            "[[type]] internal slot of publicKey is not \"public\"".into(),
61        )));
62    }
63
64    // Step 3. If the name attribute of the [[algorithm]] internal slot of publicKey is not equal to
65    // the name member of normalizedAlgorithm, then throw an InvalidAccessError.
66    if public_key.algorithm().name() != normalized_algorithm.name {
67        return Err(Error::InvalidAccess(Some(
68            "The name attribute of the [[algorithm]] internal slot of publicKey does not match \
69                the name member of normalizedAlgorithm"
70                .into(),
71        )));
72    }
73
74    // Step 4. Let maximumLength be the length in bits of the output of the field element to octet
75    // string conversion defined in Section 6.2 of [RFC6090] for the EC domain parameters associated
76    // with publicKey.
77    let maximum_length = maximum_length(&public_key)?;
78
79    // Step 5. If length is not null and is greater than maximumLength, then throw an
80    // OperationError.
81    if length.is_some_and(|length| length > maximum_length) {
82        return Err(Error::Operation(Some(
83            "Required length is greater than the maximum length supported by the elliptic curve"
84                .into(),
85        )));
86    }
87
88    // Step 6. If the [[type]] internal slot of key is not "private", then throw an
89    // InvalidAccessError.
90    if key.Type() != KeyType::Private {
91        return Err(Error::InvalidAccess(Some(
92            "[[type]] internal slot of key is not \"private\"".to_string(),
93        )));
94    }
95
96    // Step 7. If the name attribute of the [[algorithm]] internal slot of publicKey is not equal
97    // to the name property of the [[algorithm]] internal slot of key, then throw an
98    // InvalidAccessError.
99    if public_key.algorithm().name() != key.algorithm().name() {
100        return Err(Error::InvalidAccess(Some(
101            "public key [[algorithm]] internal slot name does not match that of private key"
102                .to_string(),
103        )));
104    }
105
106    // Step 8. If the namedCurve attribute of the [[algorithm]] internal slot of publicKey is not
107    // equal to the namedCurve property of the [[algorithm]] internal slot of key, then throw an
108    // InvalidAccessError.
109    let (
110        KeyAlgorithmAndDerivatives::EcKeyAlgorithm(public_key_algorithm),
111        KeyAlgorithmAndDerivatives::EcKeyAlgorithm(key_algorithm),
112    ) = (public_key.algorithm(), key.algorithm())
113    else {
114        return Err(Error::Operation(Some(
115            "Public or private key [[algorithm]] internal slot is not an elliptic curve algorithm"
116                .into(),
117        )));
118    };
119    if public_key_algorithm.named_curve != key_algorithm.named_curve {
120        return Err(Error::InvalidAccess(Some(
121            "Public and private keys [[algorithm]] internal slots namedCurves do not match".into(),
122        )));
123    }
124
125    // Step 9.
126    // If the namedCurve property of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521":
127    //     Step 9.1. Perform the ECDH primitive specified in [RFC6090] Section 4 with key as the EC
128    //     private key d and the EC public key represented by the [[handle]] internal slot of
129    //     publicKey as the EC public key.
130    //
131    //     Step 9.2. Let secret be a byte sequence containing the result of applying the field
132    //     element to octet string conversion defined in Section 6.2 of [RFC6090] to the output of
133    //     the ECDH primitive.
134    //
135    // If the namedCurve property of the [[algorithm]] internal slot of key is a value specified in
136    // an applicable specification that specifies the use of that value with ECDH:
137    //     Perform the ECDH derivation steps specified in that specification, passing in key and
138    //     publicKey and resulting in secret.
139    //
140    // Otherwise:
141    //     throw a NotSupportedError
142    //
143    // Step 10. If performing the operation results in an error, then throw a OperationError.
144    let secret = match key_algorithm.named_curve.as_str() {
145        NAMED_CURVE_P256 => {
146            let Handle::P256PrivateKey(private_key) = key.handle() else {
147                return Err(Error::Operation(Some(
148                    "Private key is not a P-256 private key".to_string(),
149                )));
150            };
151            let Handle::P256PublicKey(public_key) = public_key.handle() else {
152                return Err(Error::Operation(Some(
153                    "Public key is not a P-256 public key".to_string(),
154                )));
155            };
156            p256_diffie_hellman(private_key.to_nonzero_scalar(), public_key.as_affine())
157                .raw_secret_bytes()
158                .to_vec()
159        },
160        NAMED_CURVE_P384 => {
161            let Handle::P384PrivateKey(private_key) = key.handle() else {
162                return Err(Error::Operation(Some(
163                    "Private key is not a P-384 private key".to_string(),
164                )));
165            };
166            let Handle::P384PublicKey(public_key) = public_key.handle() else {
167                return Err(Error::Operation(Some(
168                    "Public key is not a P384 public key".to_string(),
169                )));
170            };
171            p384_diffie_hellman(private_key.to_nonzero_scalar(), public_key.as_affine())
172                .raw_secret_bytes()
173                .to_vec()
174        },
175        NAMED_CURVE_P521 => {
176            let Handle::P521PrivateKey(private_key) = key.handle() else {
177                return Err(Error::Operation(Some(
178                    "Private key is not a P-521 private key".to_string(),
179                )));
180            };
181            let Handle::P521PublicKey(public_key) = public_key.handle() else {
182                return Err(Error::Operation(Some(
183                    "Public key is not a P-521 public key".to_string(),
184                )));
185            };
186            p521_diffie_hellman(private_key.to_nonzero_scalar(), public_key.as_affine())
187                .raw_secret_bytes()
188                .to_vec()
189        },
190        _ => {
191            return Err(Error::NotSupported(Some(format!(
192                "Unsupported namedCurve: {}",
193                key_algorithm.named_curve
194            ))));
195        },
196    };
197
198    // Step 11.
199    // If length is null:
200    //     Return secret
201    // Otherwise:
202    //     If the length in bits of secret is less than length:
203    //         throw an OperationError.
204    //     Otherwise:
205    //         Return a byte sequence containing the first length bits of secret.
206    match length {
207        None => Ok(secret),
208        Some(length) => {
209            if secret.len() * 8 < length as usize {
210                Err(Error::Operation(Some(
211                    "Derived secret is too short".to_string(),
212                )))
213            } else {
214                let mut secret = secret[..length.div_ceil(8) as usize].to_vec();
215                if length % 8 != 0 {
216                    // Clean excess bits in last byte of secret.
217                    let mask = u8::MAX << (8 - length % 8);
218                    if let Some(last_byte) = secret.last_mut() {
219                        *last_byte &= mask;
220                    }
221                }
222                Ok(secret)
223            }
224        },
225    }
226}
227
228/// <https://w3c.github.io/webcrypto/#ecdh-operations-import-key>
229pub(crate) fn import_key(
230    cx: &mut JSContext,
231    global: &GlobalScope,
232    normalized_algorithm: &EcKeyImportParams,
233    format: KeyFormat,
234    key_data: &[u8],
235    extractable: bool,
236    usages: Vec<KeyUsage>,
237) -> Result<DomRoot<CryptoKey>, Error> {
238    ec_common::import_key(
239        EcAlgorithm::Ecdh,
240        cx,
241        global,
242        normalized_algorithm,
243        format,
244        key_data,
245        extractable,
246        usages,
247    )
248}
249
250/// <https://w3c.github.io/webcrypto/#ecdh-operations-export-key>
251pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
252    ec_common::export_key(format, key)
253}
254
255/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
256/// Step 9 - 15, for ECDH
257pub(crate) fn get_public_key(
258    cx: &mut JSContext,
259    global: &GlobalScope,
260    key: &CryptoKey,
261    algorithm: &KeyAlgorithmAndDerivatives,
262    usages: Vec<KeyUsage>,
263) -> Result<DomRoot<CryptoKey>, Error> {
264    ec_common::get_public_key(cx, global, key, algorithm, usages)
265}
266
267/// Given an elliptic curve key, returns the length in bits of the output of the field element to
268/// octet string conversion defined in Section 6.2 of [RFC6090] for the EC domain parameters
269/// associated with key.
270pub(crate) fn maximum_length(key: &CryptoKey) -> Result<u32, Error> {
271    let KeyAlgorithmAndDerivatives::EcKeyAlgorithm(algorithm) = key.algorithm() else {
272        return Err(Error::Operation(Some(
273            "The key is not an elliptic curve algorithm key".to_string(),
274        )));
275    };
276
277    let maximum_length_in_bytes = match algorithm.named_curve.as_str() {
278        NAMED_CURVE_P256 => <NistP256 as Curve>::FieldBytesSize::to_u32(),
279        NAMED_CURVE_P384 => <NistP384 as Curve>::FieldBytesSize::to_u32(),
280        NAMED_CURVE_P521 => <NistP521 as Curve>::FieldBytesSize::to_u32(),
281        named_curve => {
282            return Err(Error::NotSupported(Some(format!(
283                "Unsupported namedCurve: {}",
284                named_curve
285            ))));
286        },
287    };
288
289    Ok(maximum_length_in_bytes * 8)
290}