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