Skip to main content

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