Skip to main content

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