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