Skip to main content

script_webcrypto/subtlecrypto/
rsa_pss_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::pss::{Signature, SigningKey, VerifyingKey};
7use rsa::signature::{RandomizedSigner, SignatureEncoding, Verifier};
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, RsaPssParams,
22};
23use crate::traits::Equivalence;
24
25/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-sign>
26pub(crate) fn sign<D: Equivalence>(
27    normalized_algorithm: &RsaPssParams,
28    key: &CryptoKey<D>,
29    message: &[u8],
30) -> Result<Vec<u8>, Error> {
31    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
32    // InvalidAccessError.
33    key.ensure_type(KeyType::Private)?;
34
35    // Step 2. Perform the signature generation operation defined in Section 8.1 of [RFC3447] with
36    // the key represented by the [[handle]] internal slot of key as the signer's private key, K,
37    // and message as the message to be signed, M, and using the hash function specified by the
38    // hash attribute of the [[algorithm]] internal slot of key as the Hash option, MGF1 (defined
39    // in Section B.2.1 of [RFC3447]) as the MGF option and the saltLength member of
40    // normalizedAlgorithm as the salt length option for the EMSA-PSS-ENCODE operation.
41    // Step 3. If performing the operation results in an error, then throw an OperationError.
42    // Step 4. Let signature be the signature, S, that results from performing the operation.
43    let Handle::RsaPrivateKey(private_key) = key.handle() else {
44        return Err(Error::Operation(Some(
45            "[[handle]] internal slot of key is not an RSA private key".to_string(),
46        )));
47    };
48    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
49        return Err(Error::Operation(Some(
50            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
51        )));
52    };
53    let mut rng = rand::rng();
54    let signature = match algorithm.hash.name() {
55        CryptoAlgorithm::Sha1 => {
56            let signing_key = SigningKey::<Sha1>::new_with_salt_len(
57                private_key.clone(),
58                normalized_algorithm.salt_length as usize,
59            );
60            signing_key.try_sign_with_rng(&mut rng, message)
61        },
62        CryptoAlgorithm::Sha256 => {
63            let signing_key = SigningKey::<Sha256>::new_with_salt_len(
64                private_key.clone(),
65                normalized_algorithm.salt_length as usize,
66            );
67            signing_key.try_sign_with_rng(&mut rng, message)
68        },
69        CryptoAlgorithm::Sha384 => {
70            let signing_key = SigningKey::<Sha384>::new_with_salt_len(
71                private_key.clone(),
72                normalized_algorithm.salt_length as usize,
73            );
74            signing_key.try_sign_with_rng(&mut rng, message)
75        },
76        CryptoAlgorithm::Sha512 => {
77            let signing_key = SigningKey::<Sha512>::new_with_salt_len(
78                private_key.clone(),
79                normalized_algorithm.salt_length as usize,
80            );
81            signing_key.try_sign_with_rng(&mut rng, message)
82        },
83        _ => {
84            return Err(Error::Operation(Some(format!(
85                "Unsupported \"{}\" hash for RSA-PSS",
86                algorithm.hash.name().as_str()
87            ))));
88        },
89    }
90    .map_err(|_| Error::Operation(Some("RSA-PSS failed to sign message".to_string())))?;
91
92    // Step 5. Return signature.
93    Ok(signature.to_vec())
94}
95
96/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-verify>
97pub(crate) fn verify<D: Equivalence>(
98    normalized_algorithm: &RsaPssParams,
99    key: &CryptoKey<D>,
100    message: &[u8],
101    signature: &[u8],
102) -> Result<bool, Error> {
103    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
104    // InvalidAccessError.
105    key.ensure_type(KeyType::Public)?;
106
107    // Step 2. Perform the signature verification operation defined in Section 8.1 of [RFC3447]
108    // with the key represented by the [[handle]] internal slot of key as the signer's RSA public
109    // key and message as M and signature as S and using the hash function specified by the hash
110    // attribute of the [[algorithm]] internal slot of key as the Hash option, MGF1 (defined in
111    // Section B.2.1 of [RFC3447]) as the MGF option and the saltLength member of
112    // normalizedAlgorithm as the salt length option for the EMSA-PSS-VERIFY operation.
113    // Step 3. Let result be a boolean with the value true if the result of the operation was
114    // "valid signature" and the value false otherwise.
115    let Handle::RsaPublicKey(public_key) = key.handle() else {
116        return Err(Error::Operation(Some(
117            "[[handle]] internal slot of key is not an RSA public key".to_string(),
118        )));
119    };
120    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
121        return Err(Error::Operation(Some(
122            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
123        )));
124    };
125    let signature = Signature::try_from(signature)
126        .map_err(|_| Error::Operation(Some("Failed to parse RSA signature".to_string())))?;
127    let result = match algorithm.hash.name() {
128        CryptoAlgorithm::Sha1 => {
129            let verifying_key = VerifyingKey::<Sha1>::new_with_salt_len(
130                public_key.clone(),
131                normalized_algorithm.salt_length as usize,
132            );
133            verifying_key.verify(message, &signature)
134        },
135        CryptoAlgorithm::Sha256 => {
136            let verifying_key = VerifyingKey::<Sha256>::new_with_salt_len(
137                public_key.clone(),
138                normalized_algorithm.salt_length as usize,
139            );
140            verifying_key.verify(message, &signature)
141        },
142        CryptoAlgorithm::Sha384 => {
143            let verifying_key = VerifyingKey::<Sha384>::new_with_salt_len(
144                public_key.clone(),
145                normalized_algorithm.salt_length as usize,
146            );
147            verifying_key.verify(message, &signature)
148        },
149        CryptoAlgorithm::Sha512 => {
150            let verifying_key = VerifyingKey::<Sha512>::new_with_salt_len(
151                public_key.clone(),
152                normalized_algorithm.salt_length as usize,
153            );
154            verifying_key.verify(message, &signature)
155        },
156        _ => {
157            return Err(Error::Operation(Some(format!(
158                "Unsupported \"{}\" hash for RSASSA-PKCS1-v1_5",
159                algorithm.hash.name().as_str()
160            ))));
161        },
162    }
163    .is_ok();
164
165    // Step 4. Return result.
166    Ok(result)
167}
168
169/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-generate-key>
170pub(crate) fn generate_key<D: Equivalence>(
171    cx: &mut JSContext,
172    global: &D::GlobalScope,
173    normalized_algorithm: &RsaHashedKeyGenParams,
174    extractable: bool,
175    usages: Vec<KeyUsage>,
176) -> Result<CryptoKeyPair<D>, Error> {
177    rsa_common::generate_key(
178        cx,
179        global,
180        RsaAlgorithm::RsaPss,
181        normalized_algorithm,
182        extractable,
183        usages,
184    )
185}
186
187/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-import-key>
188pub(crate) fn import_key<D: Equivalence>(
189    cx: &mut JSContext,
190    global: &D::GlobalScope,
191    normalized_algorithm: &RsaHashedImportParams,
192    format: KeyFormat,
193    key_data: &[u8],
194    extractable: bool,
195    usages: Vec<KeyUsage>,
196) -> Result<DomRoot<CryptoKey<D>>, Error> {
197    rsa_common::import_key(
198        cx,
199        global,
200        RsaAlgorithm::RsaPss,
201        normalized_algorithm,
202        format,
203        key_data,
204        extractable,
205        usages,
206    )
207}
208
209/// <https://w3c.github.io/webcrypto/#rsa-pss-operations-export-key>
210pub(crate) fn export_key<D: Equivalence>(
211    format: KeyFormat,
212    key: &CryptoKey<D>,
213) -> Result<ExportedKey, Error> {
214    rsa_common::export_key(RsaAlgorithm::RsaPss, format, key)
215}
216
217/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
218/// Step 9 - 15, for RSA-PSS
219pub(crate) fn get_public_key<D: Equivalence>(
220    cx: &mut JSContext,
221    global: &D::GlobalScope,
222    key: &CryptoKey<D>,
223    algorithm: &KeyAlgorithmAndDerivatives,
224    usages: Vec<KeyUsage>,
225) -> Result<DomRoot<CryptoKey<D>>, Error> {
226    rsa_common::get_public_key(cx, global, RsaAlgorithm::RsaPss, key, algorithm, usages)
227}