script/dom/subtlecrypto/
rsassa_pkcs1_v1_5_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::pkcs1v15::{Signature, SigningKey, VerifyingKey};
7use rsa::signature::{SignatureEncoding, Signer, 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    ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512, ExportedKey, KeyAlgorithmAndDerivatives,
22    SubtleRsaHashedImportParams, SubtleRsaHashedKeyGenParams,
23};
24
25/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-sign>
26pub(crate) fn sign(key: &CryptoKey, message: &[u8]) -> Result<Vec<u8>, Error> {
27    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
28    // InvalidAccessError.
29    if key.Type() != KeyType::Private {
30        return Err(Error::InvalidAccess(Some(
31            "[[type]] internal slot of key is not \"private\"".to_string(),
32        )));
33    }
34
35    // Step 2. Perform the signature generation operation defined in Section 8.2 of [RFC3447] with
36    // the key represented by the [[handle]] internal slot of key as the signer's private key and
37    // message as M and using the hash function specified in the hash attribute of the
38    // [[algorithm]] internal slot of key as the Hash option for the EMSA-PKCS1-v1_5 encoding
39    // method.
40    // Step 3. If performing the operation results in an error, then throw an OperationError.
41    // Step 4. Let signature be the value S that results from performing the operation.
42    let Handle::RsaPrivateKey(private_key) = key.handle() else {
43        return Err(Error::Operation(Some(
44            "[[handle]] internal slot of key is not an RSA private key".to_string(),
45        )));
46    };
47    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
48        return Err(Error::Operation(Some(
49            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
50        )));
51    };
52    let signature = match algorithm.hash.name() {
53        ALG_SHA1 => {
54            let signing_key = SigningKey::<Sha1>::new(private_key.clone());
55            signing_key.try_sign(message)
56        },
57        ALG_SHA256 => {
58            let signing_key = SigningKey::<Sha256>::new(private_key.clone());
59            signing_key.try_sign(message)
60        },
61        ALG_SHA384 => {
62            let signing_key = SigningKey::<Sha384>::new(private_key.clone());
63            signing_key.try_sign(message)
64        },
65        ALG_SHA512 => {
66            let signing_key = SigningKey::<Sha512>::new(private_key.clone());
67            signing_key.try_sign(message)
68        },
69        _ => {
70            return Err(Error::Operation(Some(format!(
71                "Unsupported \"{}\" hash for RSASSA-PKCS1-v1_5",
72                algorithm.hash.name()
73            ))));
74        },
75    }
76    .map_err(|_| Error::Operation(Some("RSASSA-PKCS1-v1_5 failed to sign message".to_string())))?;
77
78    // Step 5. Return signature.
79    Ok(signature.to_vec())
80}
81
82/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-verify>
83pub(crate) fn verify(key: &CryptoKey, message: &[u8], signature: &[u8]) -> Result<bool, Error> {
84    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
85    // InvalidAccessError.
86    if key.Type() != KeyType::Public {
87        return Err(Error::InvalidAccess(Some(
88            "[[type]] internal slot of key is not \"public\"".to_string(),
89        )));
90    }
91
92    // Step 2. Perform the signature verification operation defined in Section 8.2 of [RFC3447]
93    // with the key represented by the [[handle]] internal slot of key as the signer's RSA public
94    // key and message as M and signature as S and using the hash function specified in the hash
95    // attribute of the [[algorithm]] internal slot of key as the Hash option for the
96    // EMSA-PKCS1-v1_5 encoding method.
97    // Step 3. Let result be a boolean with value true if the result of the operation was "valid
98    // signature" and the value false otherwise.
99    let Handle::RsaPublicKey(public_key) = key.handle() else {
100        return Err(Error::Operation(Some(
101            "[[handle]] internal slot of key is not an RSA public key".to_string(),
102        )));
103    };
104    let KeyAlgorithmAndDerivatives::RsaHashedKeyAlgorithm(algorithm) = key.algorithm() else {
105        return Err(Error::Operation(Some(
106            "[[algorithm]] internal slot of key is not an RsaHashedKeyAlgorithm".to_string(),
107        )));
108    };
109    let signature = Signature::try_from(signature)
110        .map_err(|_| Error::Operation(Some("Failed to parse RSA signature".to_string())))?;
111    let result = match algorithm.hash.name() {
112        ALG_SHA1 => {
113            let verifying_key = VerifyingKey::<Sha1>::new(public_key.clone());
114            verifying_key.verify(message, &signature)
115        },
116        ALG_SHA256 => {
117            let verifying_key = VerifyingKey::<Sha256>::new(public_key.clone());
118            verifying_key.verify(message, &signature)
119        },
120        ALG_SHA384 => {
121            let verifying_key = VerifyingKey::<Sha384>::new(public_key.clone());
122            verifying_key.verify(message, &signature)
123        },
124        ALG_SHA512 => {
125            let verifying_key = VerifyingKey::<Sha512>::new(public_key.clone());
126            verifying_key.verify(message, &signature)
127        },
128        _ => {
129            return Err(Error::Operation(Some(format!(
130                "Unsupported \"{}\" hash for RSASSA-PKCS1-v1_5",
131                algorithm.hash.name()
132            ))));
133        },
134    }
135    .is_ok();
136
137    // Step 4. Return result.
138    Ok(result)
139}
140
141/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-generate-key>
142pub(crate) fn generate_key(
143    cx: &mut JSContext,
144    global: &GlobalScope,
145    normalized_algorithm: &SubtleRsaHashedKeyGenParams,
146    extractable: bool,
147    usages: Vec<KeyUsage>,
148) -> Result<CryptoKeyPair, Error> {
149    rsa_common::generate_key(
150        RsaAlgorithm::RsassaPkcs1v1_5,
151        cx,
152        global,
153        normalized_algorithm,
154        extractable,
155        usages,
156    )
157}
158
159/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-import-key>
160pub(crate) fn import_key(
161    cx: &mut JSContext,
162    global: &GlobalScope,
163    normalized_algorithm: &SubtleRsaHashedImportParams,
164    format: KeyFormat,
165    key_data: &[u8],
166    extractable: bool,
167    usages: Vec<KeyUsage>,
168) -> Result<DomRoot<CryptoKey>, Error> {
169    rsa_common::import_key(
170        RsaAlgorithm::RsassaPkcs1v1_5,
171        cx,
172        global,
173        normalized_algorithm,
174        format,
175        key_data,
176        extractable,
177        usages,
178    )
179}
180
181/// <https://w3c.github.io/webcrypto/#rsassa-pkcs1-operations-export-key>
182pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
183    rsa_common::export_key(RsaAlgorithm::RsassaPkcs1v1_5, format, key)
184}