Skip to main content

script_webcrypto/subtlecrypto/
rsa_oaep_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 js::context::JSContext;
6use rsa::oaep::{DecryptingKey, EncryptingKey};
7use rsa::traits::{Decryptor, RandomizedEncryptor};
8use script_bindings::codegen::GenericBindings::CryptoKeyBinding::{
9    CryptoKeyPair, KeyType, KeyUsage,
10};
11use script_bindings::codegen::GenericBindings::SubtleCryptoBinding::KeyFormat;
12use script_bindings::error::Error;
13use script_bindings::root::DomRoot;
14use sha1::Sha1;
15use sha2::{Sha256, Sha384, Sha512};
16
17use crate::cryptokey::{CryptoKey, Handle};
18use crate::subtlecrypto::rsa_common::{self, RsaAlgorithm};
19use crate::subtlecrypto::{
20    CryptoAlgorithm, ExportedKey, KeyAlgorithmAndDerivatives, NormalizedAlgorithm,
21    RsaHashedImportParams, RsaHashedKeyGenParams, RsaOaepParams,
22};
23use crate::traits::Equivalence;
24
25/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-encrypt>
26pub(crate) fn encrypt<D: Equivalence>(
27    normalized_algorithm: &RsaOaepParams,
28    key: &CryptoKey<D>,
29    plaintext: &[u8],
30) -> Result<Vec<u8>, Error> {
31    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
32    // InvalidAccessError.
33    key.ensure_type(KeyType::Public)?;
34
35    // Step 2. Let label be the label member of normalizedAlgorithm or the empty byte sequence if
36    // the label member of normalizedAlgorithm is not present.
37    let label = normalized_algorithm.label.as_deref().unwrap_or_default();
38
39    // Step 3. Perform the encryption operation defined in Section 7.1 of [RFC3447] with the key
40    // represented by key as the recipient's RSA public key, plaintext as the message to be
41    // encrypted, M and label as the label, L, and with the hash function specified by the hash
42    // attribute of the [[algorithm]] internal slot of key as the Hash option and MGF1 (defined in
43    // Section B.2.1 of [RFC3447]) as the MGF option.
44    // Step 4. If performing the operation results in an error, then throw an OperationError.
45    // Step 5. Let ciphertext be the value C that results from performing the operation.
46    let Handle::RsaPublicKey(public_key) = key.handle() else {
47        return Err(Error::Operation(Some(
48            "[[handle]] internal slot of key is not an RSA public key".to_string(),
49        )));
50    };
51    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
52        return Err(Error::Operation(Some(
53            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
54        )));
55    };
56    let mut rng = rand::rng();
57    let ciphertext = match algorithm.hash.name() {
58        CryptoAlgorithm::Sha1 => {
59            let encryption_key = EncryptingKey::<Sha1>::new_with_label(public_key.clone(), label);
60            encryption_key.encrypt_with_rng(&mut rng, plaintext)
61        },
62        CryptoAlgorithm::Sha256 => {
63            let encryption_key = EncryptingKey::<Sha256>::new_with_label(public_key.clone(), label);
64            encryption_key.encrypt_with_rng(&mut rng, plaintext)
65        },
66        CryptoAlgorithm::Sha384 => {
67            let encryption_key = EncryptingKey::<Sha384>::new_with_label(public_key.clone(), label);
68            encryption_key.encrypt_with_rng(&mut rng, plaintext)
69        },
70        CryptoAlgorithm::Sha512 => {
71            let encryption_key = EncryptingKey::<Sha512>::new_with_label(public_key.clone(), label);
72            encryption_key.encrypt_with_rng(&mut rng, plaintext)
73        },
74        algorithm_hash_name => {
75            return Err(Error::Operation(Some(format!(
76                "Unsupported hash for RSASSA-PKCS1-v1_5: {}",
77                algorithm_hash_name.as_str()
78            ))));
79        },
80    }
81    .map_err(|_| Error::Operation(Some("RSA-OAEP failed to encrypt plaintext".into())))?;
82
83    // Step 6. Return ciphertext.
84    Ok(ciphertext)
85}
86
87/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-decrypt>
88pub(crate) fn decrypt<D: Equivalence>(
89    normalized_algorithm: &RsaOaepParams,
90    key: &CryptoKey<D>,
91    ciphertext: &[u8],
92) -> Result<Vec<u8>, Error> {
93    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
94    // InvalidAccessError.
95    key.ensure_type(KeyType::Private)?;
96
97    // Step 2. Let label be the label member of normalizedAlgorithm or the empty byte sequence if
98    // the label member of normalizedAlgorithm is not present.
99    let label = normalized_algorithm.label.as_deref().unwrap_or_default();
100
101    // Step 3. Perform the decryption operation defined in Section 7.1 of [RFC3447] with the key
102    // represented by key as the recipient's RSA private key, ciphertext as the ciphertext to be
103    // decrypted, C, and label as the label, L, and with the hash function specified by the hash
104    // attribute of the [[algorithm]] internal slot of key as the Hash option and MGF1 (defined in
105    // Section B.2.1 of [RFC3447]) as the MGF option.
106    // Step 4. If performing the operation results in an error, then throw an OperationError.
107    // Step 5. Let plaintext the value M that results from performing the operation.
108    let Handle::RsaPrivateKey(private_key) = key.handle() else {
109        return Err(Error::Operation(Some(
110            "[[handle]] internal slot of key is not an RSA private key".to_string(),
111        )));
112    };
113    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
114        return Err(Error::Operation(Some(
115            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
116        )));
117    };
118    let plaintext = match algorithm.hash.name() {
119        CryptoAlgorithm::Sha1 => {
120            let decrypting_key = DecryptingKey::<Sha1>::new_with_label(private_key.clone(), label);
121            decrypting_key.decrypt(ciphertext)
122        },
123        CryptoAlgorithm::Sha256 => {
124            let decrypting_key =
125                DecryptingKey::<Sha256>::new_with_label(private_key.clone(), label);
126            decrypting_key.decrypt(ciphertext)
127        },
128        CryptoAlgorithm::Sha384 => {
129            let decrypting_key =
130                DecryptingKey::<Sha384>::new_with_label(private_key.clone(), label);
131            decrypting_key.decrypt(ciphertext)
132        },
133        CryptoAlgorithm::Sha512 => {
134            let decrypting_key =
135                DecryptingKey::<Sha512>::new_with_label(private_key.clone(), label);
136            decrypting_key.decrypt(ciphertext)
137        },
138        algorithm_hash_name => {
139            return Err(Error::Operation(Some(format!(
140                "Unsupported hash for RSASSA-PKCS1-v1_5: {}",
141                algorithm_hash_name.as_str()
142            ))));
143        },
144    }
145    .map_err(|_| Error::Operation(Some("RSA-OAEP failed to decrypt ciphertext".into())))?;
146
147    // Step 6. Return plaintext.
148    Ok(plaintext)
149}
150
151/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-generate-key>
152pub(crate) fn generate_key<D: Equivalence>(
153    cx: &mut JSContext,
154    global: &D::GlobalScope,
155    normalized_algorithm: &RsaHashedKeyGenParams,
156    extractable: bool,
157    usages: Vec<KeyUsage>,
158) -> Result<CryptoKeyPair<D>, Error> {
159    rsa_common::generate_key(
160        cx,
161        global,
162        RsaAlgorithm::RsaOaep,
163        normalized_algorithm,
164        extractable,
165        usages,
166    )
167}
168
169/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-import-key>
170pub(crate) fn import_key<D: Equivalence>(
171    cx: &mut JSContext,
172    global: &D::GlobalScope,
173    normalized_algorithm: &RsaHashedImportParams,
174    format: KeyFormat,
175    key_data: &[u8],
176    extractable: bool,
177    usages: Vec<KeyUsage>,
178) -> Result<DomRoot<CryptoKey<D>>, Error> {
179    rsa_common::import_key(
180        cx,
181        global,
182        RsaAlgorithm::RsaOaep,
183        normalized_algorithm,
184        format,
185        key_data,
186        extractable,
187        usages,
188    )
189}
190
191/// <https://w3c.github.io/webcrypto/#rsa-oaep-operations-export-key>
192pub(crate) fn export_key<D: Equivalence>(
193    format: KeyFormat,
194    key: &CryptoKey<D>,
195) -> Result<ExportedKey, Error> {
196    rsa_common::export_key(RsaAlgorithm::RsaOaep, format, key)
197}
198
199/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
200/// Step 9 - 15, for RSA-OAEP
201pub(crate) fn get_public_key<D: Equivalence>(
202    cx: &mut JSContext,
203    global: &D::GlobalScope,
204    key: &CryptoKey<D>,
205    algorithm: &KeyAlgorithmAndDerivatives,
206    usages: Vec<KeyUsage>,
207) -> Result<DomRoot<CryptoKey<D>>, Error> {
208    rsa_common::get_public_key(cx, global, RsaAlgorithm::RsaOaep, key, algorithm, usages)
209}