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