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