fix(servo): gate vulnerable RSA private operations

This commit is contained in:
2026-07-10 01:06:48 -04:00
parent 670f7abe60
commit e085174b89
67 changed files with 10535 additions and 21 deletions
Generated
-2
View File
@@ -7632,8 +7632,6 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.10.0-rc.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
dependencies = [
"const-oid 0.10.2",
"crypto-bigint",
+2
View File
@@ -8,6 +8,7 @@ members = [
"crates/ely_sync_client",
"third_party/wayland-scanner",
]
exclude = ["third_party/rsa"]
default-members = ["crates/ely_app"]
resolver = "2"
@@ -44,6 +45,7 @@ gpui = { path = "third_party/gpui" }
gpui_http_client = { path = "third_party/gpui_support/gpui_http_client" }
gpui_sum_tree = { path = "third_party/gpui_support/gpui_sum_tree" }
gpui_util = { path = "third_party/gpui_support/gpui_util" }
rsa = { path = "third_party/rsa" }
wayland-scanner = { path = "third_party/wayland-scanner" }
zed-sum-tree = { path = "third_party/gpui_support/zed-sum-tree" }
+18
View File
@@ -15,6 +15,8 @@ use serde_json::{Value, json};
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
#[path = "sidecar/mach_receiver.rs"]
mod mach_receiver;
#[path = "sidecar/pages.rs"]
mod pages;
#[path = "sidecar/support.rs"]
mod support;
@@ -160,6 +162,22 @@ fn live_sidecar_delivers_a_valid_white_page() -> Result<(), Box<dyn Error>> {
Ok(())
}
#[test]
fn live_sidecar_gates_rsa_private_operations() -> Result<(), Box<dyn Error>> {
let server = TestServer::start()?;
let root = TestDirectory::new()?;
let mut sidecar = Sidecar::spawn(root.path())?;
sidecar.ensure_and_wait(
&ProfileId::new(),
&server.url("/rsa-private-operations"),
"rsa-private-operations-gated",
)?;
sidecar.shutdown()?;
Ok(())
}
#[test]
fn live_sidecar_rejects_an_incompatible_protocol() -> Result<(), Box<dyn Error>> {
let root = TestDirectory::new()?;
@@ -0,0 +1,81 @@
pub(super) const SET_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>Profile persistence</body><script>localStorage.setItem('ely_storage','persisted');const cookie=document.cookie.includes('ely_cookie=persisted')?'yes':'no';const storage=localStorage.getItem('ely_storage')==='persisted'?'yes':'no';document.title=`stored-cookie-${cookie}-storage-${storage}`;</script>"#;
pub(super) const READ_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>Profile persistence</body><script>const cookie=document.cookie.includes('ely_cookie=persisted')?'yes':'no';const storage=localStorage.getItem('ely_storage')==='persisted'?'yes':'no';document.title=`read-cookie-${cookie}-storage-${storage}`;</script>"#;
pub(super) const HISTORY_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>History mutation</body><script>history.replaceState({},'', '/history?state=1');document.title='history-ready';</script>"#;
pub(super) const OVERSIZED_HISTORY_PAGE: &str = r#"<!doctype html><title>oversized-history</title><script>history.replaceState({},'', '/oversized?' + 'a'.repeat(32768));</script>"#;
pub(super) const WHITE_PAGE: &str = r#"<!doctype html><title>white-ready</title><style>html,body{margin:0;width:100%;height:100%;background:#fff}</style>"#;
pub(super) const RSA_PRIVATE_OPERATION_PAGE: &str = r#"<!doctype html><title>rsa-private-operations-loading</title><body>RSA private-operation gate</body><script>
(async () => {
const subtle = crypto.subtle;
const data = new TextEncoder().encode('ELY');
const digest = await subtle.digest('SHA-256', data);
if (digest.byteLength !== 32) throw new Error('digest-failed');
const loadKey = async path => {
const response = await fetch(path);
if (!response.ok) throw new Error(`key-fetch-${response.status}`);
return response.arrayBuffer();
};
const [privateKeyData, publicKeyData] = await Promise.all([
loadKey('/rsa-private.der'),
loadKey('/rsa-public.der'),
]);
const expectOperationError = async (label, operation) => {
try {
await operation();
throw new Error(`${label}-succeeded`);
} catch (error) {
if (error.name === 'OperationError') return;
throw error;
}
};
const oaep = { name: 'RSA-OAEP', hash: 'SHA-256' };
const oaepPrivate = await subtle.importKey(
'pkcs8', privateKeyData, oaep, false, ['decrypt', 'unwrapKey']);
const oaepPublic = await subtle.importKey(
'spki', publicKeyData, oaep, false, ['encrypt', 'wrapKey']);
const ciphertext = await subtle.encrypt({ name: 'RSA-OAEP' }, oaepPublic, data);
await expectOperationError('oaep-decrypt', () =>
subtle.decrypt({ name: 'RSA-OAEP' }, oaepPrivate, ciphertext));
const wrappingTarget = await subtle.importKey(
'raw', new Uint8Array(16), { name: 'AES-GCM' }, true, ['encrypt']);
const wrapped = await subtle.wrapKey(
'raw', wrappingTarget, oaepPublic, { name: 'RSA-OAEP' });
await expectOperationError('oaep-unwrap', () =>
subtle.unwrapKey(
'raw', wrapped, oaepPrivate, { name: 'RSA-OAEP' },
{ name: 'AES-GCM' }, false, ['encrypt']));
const pss = { name: 'RSA-PSS', hash: 'SHA-256' };
const pssPrivate = await subtle.importKey('pkcs8', privateKeyData, pss, false, ['sign']);
await expectOperationError('pss-sign', () =>
subtle.sign({ name: 'RSA-PSS', saltLength: 32 }, pssPrivate, data));
const pkcs = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
const pkcsPrivate = await subtle.importKey('pkcs8', privateKeyData, pkcs, false, ['sign']);
await expectOperationError('pkcs-sign', () =>
subtle.sign({ name: 'RSASSA-PKCS1-v1_5' }, pkcsPrivate, data));
const pkcsPublic = await subtle.importKey('spki', publicKeyData, pkcs, false, ['verify']);
const verified = await subtle.verify(
{ name: 'RSASSA-PKCS1-v1_5' }, pkcsPublic, new Uint8Array(256), data);
if (verified) throw new Error('pkcs-invalid-signature-verified');
document.title = 'rsa-private-operations-gated';
})().catch(error => {
document.title = `rsa-private-operations-failed-${error.name}`;
});
</script>"#;
pub(super) const RSA_PRIVATE_KEY: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../third_party/rsa/tests/examples/pkcs8/rsa2048-priv.der"
));
pub(super) const RSA_PUBLIC_KEY: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../third_party/rsa/tests/examples/pkcs8/rsa2048-pub.der"
));
+21 -19
View File
@@ -16,6 +16,11 @@ use std::{
use ely_domain::{ProfileId, TabId};
use serde_json::{Value, json};
use super::pages::{
HISTORY_PAGE, OVERSIZED_HISTORY_PAGE, READ_PAGE, RSA_PRIVATE_KEY, RSA_PRIVATE_OPERATION_PAGE,
RSA_PUBLIC_KEY, SET_PAGE, WHITE_PAGE,
};
pub(super) const WIDTH: u32 = 360;
pub(super) const HEIGHT: u32 = 240;
pub(super) const RESPONSE_TIMEOUT: Duration = Duration::from_secs(20);
@@ -461,33 +466,30 @@ fn serve_connection(
}
let path = request_line.split_whitespace().nth(1).unwrap_or("/");
let is_set = path == "/set";
let body = if is_set {
SET_PAGE
let (body, content_type): (&[u8], &str) = if is_set {
(SET_PAGE.as_bytes(), "text/html; charset=utf-8")
} else if path.starts_with("/history") {
HISTORY_PAGE
(HISTORY_PAGE.as_bytes(), "text/html; charset=utf-8")
} else if path == "/oversized-history" {
OVERSIZED_HISTORY_PAGE
(OVERSIZED_HISTORY_PAGE.as_bytes(), "text/html; charset=utf-8")
} else if path == "/white" {
WHITE_PAGE
(WHITE_PAGE.as_bytes(), "text/html; charset=utf-8")
} else if path == "/rsa-private-operations" {
(RSA_PRIVATE_OPERATION_PAGE.as_bytes(), "text/html; charset=utf-8")
} else if path == "/rsa-private.der" {
(RSA_PRIVATE_KEY, "application/octet-stream")
} else if path == "/rsa-public.der" {
(RSA_PUBLIC_KEY, "application/octet-stream")
} else {
READ_PAGE
(READ_PAGE.as_bytes(), "text/html; charset=utf-8")
};
let cookie_header =
if is_set { "Set-Cookie: ely_cookie=persisted; Path=/; SameSite=Lax\r\n" } else { "" };
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nCache-Control: no-store\r\n{cookie_header}Connection: close\r\n\r\n{body}",
let headers = format!(
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nCache-Control: no-store\r\n{cookie_header}Connection: close\r\n\r\n",
body.len()
);
reader.get_mut().write_all(response.as_bytes())?;
reader.get_mut().write_all(headers.as_bytes())?;
reader.get_mut().write_all(body)?;
reader.get_mut().flush()
}
const SET_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>Profile persistence</body><script>localStorage.setItem('ely_storage','persisted');const cookie=document.cookie.includes('ely_cookie=persisted')?'yes':'no';const storage=localStorage.getItem('ely_storage')==='persisted'?'yes':'no';document.title=`stored-cookie-${cookie}-storage-${storage}`;</script>"#;
const READ_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>Profile persistence</body><script>const cookie=document.cookie.includes('ely_cookie=persisted')?'yes':'no';const storage=localStorage.getItem('ely_storage')==='persisted'?'yes':'no';document.title=`read-cookie-${cookie}-storage-${storage}`;</script>"#;
const HISTORY_PAGE: &str = r#"<!doctype html><title>loading</title><style>body{font:24px sans-serif;color:#111;background:#fff}</style><body>History mutation</body><script>history.replaceState({},'', '/history?state=1');document.title='history-ready';</script>"#;
const OVERSIZED_HISTORY_PAGE: &str = r#"<!doctype html><title>oversized-history</title><script>history.replaceState({},'', '/oversized?' + 'a'.repeat(32768));</script>"#;
const WHITE_PAGE: &str = r#"<!doctype html><title>white-ready</title><style>html,body{margin:0;width:100%;height:100%;background:#fff}</style>"#;
+238
View File
@@ -0,0 +1,238 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.9.8 (2025-03-12)
### Added
- Doc comments to specify the `rand` version ([#473])
[#473]: https://github.com/RustCrypto/RSA/pull/473
## 0.9.7 (2024-11-26)
### Fixed
- Always validate keys in `RsaPrivateKey::from_components` ([#459])
- Do not crash when handling tiny keys in PKCS1v15 ([#459])
[#459]: https://github.com/RustCrypto/RSA/pull/459
## 0.9.6 (2023-12-01)
### Added
- expose a `pss::get_default_pss_signature_algo_id` helper ([#393])
- expose `pkcs1v15::RsaSignatureAssociatedOid` ([#392])
[#392]: https://github.com/RustCrypto/RSA/pull/392
[#393]: https://github.com/RustCrypto/RSA/pull/393
## 0.9.5 (2023-11-27)
### Added
- Adds `RsaPrivateKey::from_primes` and `RsaPrivateKey::from_p_q` methods ([#386])
[#386]: https://github.com/RustCrypto/RSA/pull/386
## 0.9.4 (2023-11-20)
### Added
- Deterministic implementation of prime factors recovery ([#380])
[#380]: https://github.com/RustCrypto/RSA/pull/380
## 0.9.3 (2023-10-26)
### Added
- PKCS#8/SPKI decoding trait impls for `pkcs1v15` keys ([#346])
- `hazmat` feature as a replacement for `expose-internals` ([#352])
### Changed
- Bump `serde` dependency to 1.0.184 ([#360])
### Removed
- Unused dependencies ([#357])
[#346]: https://github.com/RustCrypto/RSA/pull/346
[#352]: https://github.com/RustCrypto/RSA/pull/352
[#357]: https://github.com/RustCrypto/RSA/pull/357
[#360]: https://github.com/RustCrypto/RSA/pull/360
## 0.9.2 (2023-05-08)
### Fixed
- pkcs1v15: have `fmt` impls call `SignatureEncoding::to_bytes` ([#330])
[#330]: https://github.com/RustCrypto/RSA/pull/330
## 0.9.1 (2023-05-03)
### Fixed
- Left pad signatures when encoding ([#325])
[#325]: https://github.com/RustCrypto/RSA/pull/325
## 0.9.0 (2023-04-27)
### Added
- Function to get salt length from RSA PSS keys ([#277])
- `AssociatedAlgorithmIdentifier` implementation ([#278])
- Random key generation for `pss::BlindedSigningKey` ([#295])
- Impl `Signer` for `pss::SigningKey` ([#297])
- Impl `core::hash::Hash` for `RsaPrivateKey` ([#308])
- Impl `ZeroizeOnDrop` for `RsaPrivateKey`, `SigningKey`, `DecryptingKey` ([#311])
- `u64_digit` feature; on-by-default ([#313])
- `AsRef<RsaPublicKey>` impl on `RsaPrivateKey` ([#317])
### Changed
- Use namespaced features for `serde` ([#268])
- Bump `pkcs1` to v0.7, `pkcs8` to v0.10; MSRV 1.65 ([#270])
- Rename PKCS#1v1.5 `*_with_prefix` methods ([#290])
- `SigningKey::new` => `SigningKey::new_unprefixed`
- `SigningKey::new_with_prefix` => `SigningKey::new`
- `VerifyingKey::new` => `VerifyingKey::new_unprefixed`
- `VerifyingKey::new_with_prefix` => `VerifyingKey::new`
- Rename `Pkcs1v15Sign::new_raw` to `Pkcs1v15Sign::new_unprefixed` ([#293])
- Use digest output size as default PSS salt length ([#294])
- Specify `salt_len` when verifying PSS signatures ([#294])
- Ensure signatures have the expected length and don't overflow the modulus ([#306])
- Improved public key checks ([#307])
- Rename `CRTValue` => `CrtValue` ([#314])
- Traits under `padding` module now located under `traits` module ([#315])
- `PublicKeyParts`/`PrivateKeyParts` now located under `traits` module ([#315])
### Removed
- "Unsalted" PSS support ([#294])
- `EncryptionPrimitive`/`DescriptionPrimitive` traits ([#300])
- `PublicKey`/`PrivateKey` traits ([#300])
- `Zeroize` impl on `RsaPrivateKey`; automatically zeroized on drop ([#311])
- `Deref<Target=RsaPublicKey>` impl on `RsaPrivateKey`; use `AsRef` instead ([#317])
- `expose-internals` feature and public access to all functions it gated ([#304])
[#268]: https://github.com/RustCrypto/RSA/pull/268
[#270]: https://github.com/RustCrypto/RSA/pull/270
[#277]: https://github.com/RustCrypto/RSA/pull/277
[#278]: https://github.com/RustCrypto/RSA/pull/278
[#290]: https://github.com/RustCrypto/RSA/pull/290
[#293]: https://github.com/RustCrypto/RSA/pull/293
[#294]: https://github.com/RustCrypto/RSA/pull/294
[#295]: https://github.com/RustCrypto/RSA/pull/295
[#297]: https://github.com/RustCrypto/RSA/pull/297
[#300]: https://github.com/RustCrypto/RSA/pull/300
[#306]: https://github.com/RustCrypto/RSA/pull/306
[#307]: https://github.com/RustCrypto/RSA/pull/307
[#308]: https://github.com/RustCrypto/RSA/pull/308
[#311]: https://github.com/RustCrypto/RSA/pull/311
[#313]: https://github.com/RustCrypto/RSA/pull/313
[#314]: https://github.com/RustCrypto/RSA/pull/314
[#315]: https://github.com/RustCrypto/RSA/pull/315
[#317]: https://github.com/RustCrypto/RSA/pull/317
## 0.8.2 (2023-03-01)
### Added
- Encryption-related traits ([#259])
### Fixed
- Possible panic in `internals::left_pad` ([#262])
- Correct PSS sign/verify when key length is multiple of 8+1 bits ([#263])
[#259]: https://github.com/RustCrypto/RSA/pull/259
[#262]: https://github.com/RustCrypto/RSA/pull/262
[#263]: https://github.com/RustCrypto/RSA/pull/263
## 0.8.1 (2023-01-20)
### Added
- `sha2` feature with `oid` subfeature enabled ([#255])
[#255]: https://github.com/RustCrypto/RSA/pull/255
## 0.8.0 (2023-01-17)
### Changed
- Bump `signature` crate dependency to v2 ([#217], [#249])
- Switch to `CryptoRngCore` marker trait ([#237])
- Make `padding` module private ([#243])
- Refactor `PaddingScheme` into a trait ([#244])
### Fixed
- Benchmark build ([#225])
[#217]: https://github.com/RustCrypto/RSA/pull/217
[#225]: https://github.com/RustCrypto/RSA/pull/225
[#237]: https://github.com/RustCrypto/RSA/pull/237
[#243]: https://github.com/RustCrypto/RSA/pull/243
[#244]: https://github.com/RustCrypto/RSA/pull/244
[#249]: https://github.com/RustCrypto/RSA/pull/249
## 0.7.2 (2022-11-14)
### Added
- Public accessor methods for `PrecomputedValues` ([#221])
- Re-export `signature` crate ([#223])
[#221]: https://github.com/RustCrypto/RSA/pull/221
[#223]: https://github.com/RustCrypto/RSA/pull/223
## 0.7.1 (2022-10-31)
### Added
- Documentation improvements ([#216])
### Changed
- Ensure `PaddingScheme` is `Send` and `Sync` ([#215])
[#215]: https://github.com/RustCrypto/RSA/pull/215
[#216]: https://github.com/RustCrypto/RSA/pull/216
## 0.7.0 (2022-10-10) [YANKED]
NOTE: when computing signatures with this release, make sure to enable the
`oid` crate feature of the digest crate you are using when computing the
signature (e.g. `sha2`, `sha3`). If the `oid` feature doesn't exist, make sure
you're using the latest versions.
### Added
- `pkcs1v15` and `pss` modules with `SigningKey`/`VerifyingKey` types
([#174], [#195], [#202], [#207], [#208])
- 4096-bit default max `RsaPublicKey` size ([#176])
- `RsaPublicKey::new_with_max_size` ([#176])
- `RsaPublicKey::new_unchecked` ([#206])
### Changed
- MSRV 1.57 ([#162])
- Bump `pkcs1` to 0.4 ([#162])
- Bump `pkcs8` to 0.9 ([#162])
- `RsaPrivateKey::from_components` is now fallible ([#167])
- pkcs1v15: use `AssociatedOid` for getting the RSA prefix ([#183])
### Removed
- `rng` member from PSS padding scheme ([#173])
- `Hash` removed in favor of using OIDs defined in digest crates ([#183])
[#162]: https://github.com/RustCrypto/RSA/pull/162
[#167]: https://github.com/RustCrypto/RSA/pull/167
[#173]: https://github.com/RustCrypto/RSA/pull/173
[#174]: https://github.com/RustCrypto/RSA/pull/174
[#176]: https://github.com/RustCrypto/RSA/pull/176
[#183]: https://github.com/RustCrypto/RSA/pull/183
[#195]: https://github.com/RustCrypto/RSA/pull/195
[#202]: https://github.com/RustCrypto/RSA/pull/202
[#206]: https://github.com/RustCrypto/RSA/pull/206
[#207]: https://github.com/RustCrypto/RSA/pull/207
[#208]: https://github.com/RustCrypto/RSA/pull/208
## 0.6.1 (2022-04-11)
## 0.6.0 (2022-04-08)
## 0.5.0 (2021-07-27)
## 0.4.1 (2021-07-26)
## 0.4.0 (2021-03-28)
## 0.3.0 (2020-06-11)
## 0.2.0 (2019-12-11)
## 0.1.4 (2019-10-13)
## 0.1.3 (2019-03-26)
## 0.1.2 (2019-02-25)
## 0.1.1 (2019-02-20)
## 0.1.0 (2018-12-05)
+255
View File
@@ -0,0 +1,255 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.85"
name = "rsa"
version = "0.10.0-rc.18"
authors = [
"RustCrypto Developers",
"dignifiedquire <dignifiedquire@gmail.com>",
]
build = false
exclude = [
"marvin_toolkit/",
"thirdparty/",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Pure Rust RSA implementation"
documentation = "https://docs.rs/rsa"
readme = "README.md"
keywords = [
"rsa",
"encryption",
"security",
"crypto",
]
categories = ["cryptography"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/RustCrypto/RSA"
[package.metadata.docs.rs]
features = [
"std",
"serde",
"hazmat",
"sha2",
]
[features]
default = [
"std",
"encoding",
"private-key-operations-disabled",
]
encoding = [
"dep:pkcs1",
"dep:pkcs8",
"dep:spki",
]
getrandom = [
"crypto-bigint/getrandom",
"crypto-common",
]
hazmat = []
private-key-operations-disabled = []
pkcs5 = ["pkcs8/encryption"]
serde = [
"encoding",
"dep:serde",
"dep:serdect",
"crypto-bigint/serde",
]
std = [
"pkcs1?/std",
"pkcs8?/std",
]
[lib]
name = "rsa"
path = "src/lib.rs"
[[test]]
name = "pkcs1"
path = "tests/pkcs1.rs"
[[test]]
name = "pkcs1v15"
path = "tests/pkcs1v15.rs"
[[test]]
name = "pkcs8"
path = "tests/pkcs8.rs"
[[test]]
name = "proptests"
path = "tests/proptests.rs"
[[test]]
name = "wycheproof"
path = "tests/wycheproof.rs"
[[bench]]
name = "key"
path = "benches/key.rs"
[dependencies.const-oid]
version = "0.10"
default-features = false
[dependencies.crypto-bigint]
version = "0.7"
features = [
"zeroize",
"alloc",
]
default-features = false
[dependencies.crypto-common]
version = "0.2"
features = ["getrandom"]
optional = true
[dependencies.crypto-primes]
version = "0.7"
default-features = false
[dependencies.digest]
version = "0.11"
features = [
"alloc",
"oid",
]
default-features = false
[dependencies.pkcs1]
version = "0.8.0-rc.4"
features = [
"alloc",
"pem",
]
optional = true
default-features = false
[dependencies.pkcs8]
version = "0.11"
features = [
"alloc",
"pem",
]
optional = true
default-features = false
[dependencies.rand_core]
version = "0.10"
default-features = false
[dependencies.serde]
version = "1.0.184"
features = ["derive"]
optional = true
default-features = false
[dependencies.serdect]
version = "0.4"
optional = true
[dependencies.sha1]
version = "0.11"
features = ["oid"]
optional = true
default-features = false
[dependencies.sha2]
version = "0.11"
features = ["oid"]
optional = true
default-features = false
[dependencies.signature]
version = "3.0.0-rc.10"
features = [
"alloc",
"digest",
"rand_core",
]
default-features = false
[dependencies.spki]
version = "0.8"
features = ["alloc"]
optional = true
default-features = false
[dependencies.zeroize]
version = "1.8"
features = ["alloc"]
[dev-dependencies.base64ct]
version = "1"
features = ["alloc"]
[dev-dependencies.hex]
version = "0.4.3"
features = ["serde"]
[dev-dependencies.hex-literal]
version = "1"
[dev-dependencies.proptest]
version = "1"
[dev-dependencies.rand]
version = "0.10"
features = ["chacha"]
[dev-dependencies.rand_core]
version = "0.10"
default-features = false
[dev-dependencies.rstest]
version = "0.26.1"
[dev-dependencies.serde]
version = "1.0.184"
features = ["derive"]
[dev-dependencies.serde_json]
version = "1.0.138"
[dev-dependencies.serde_test]
version = "1.0.89"
[dev-dependencies.sha1]
version = "0.11"
features = ["oid"]
default-features = false
[dev-dependencies.sha2]
version = "0.11"
features = ["oid"]
default-features = false
[dev-dependencies.sha3]
version = "0.11"
features = ["oid"]
default-features = false
[profile.bench]
debug = 2
[profile.dev]
opt-level = 2
+71
View File
@@ -0,0 +1,71 @@
[package]
name = "rsa"
version = "0.10.0-rc.18"
authors = ["RustCrypto Developers", "dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2021"
description = "Pure Rust RSA implementation"
license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/rsa"
repository = "https://github.com/RustCrypto/RSA"
keywords = ["rsa", "encryption", "security", "crypto"]
categories = ["cryptography"]
readme = "README.md"
rust-version = "1.85"
exclude = ["marvin_toolkit/", "thirdparty/"]
[dependencies]
const-oid = { version = "0.10", default-features = false }
crypto-bigint = { version = "0.7", default-features = false, features = ["zeroize", "alloc"] }
crypto-primes = { version = "0.7", default-features = false }
digest = { version = "0.11", default-features = false, features = ["alloc", "oid"] }
rand_core = { version = "0.10", default-features = false }
signature = { version = "3.0.0-rc.10", default-features = false, features = ["alloc", "digest", "rand_core"] }
zeroize = { version = "1.8", features = ["alloc"] }
# optional dependencies
crypto-common = { version = "0.2", optional = true, features = ["getrandom"] }
pkcs1 = { version = "0.8.0-rc.4", optional = true, default-features = false, features = ["alloc", "pem"] }
pkcs8 = { version = "0.11", optional = true, default-features = false, features = ["alloc", "pem"] }
serdect = { version = "0.4", optional = true }
sha1 = { version = "0.11", optional = true, default-features = false, features = ["oid"] }
sha2 = { version = "0.11", optional = true, default-features = false, features = ["oid"] }
spki = { version = "0.8", optional = true, default-features = false, features = ["alloc"] }
serde = { version = "1.0.184", optional = true, default-features = false, features = ["derive"] }
[dev-dependencies]
base64ct = { version = "1", features = ["alloc"] }
hex-literal = "1"
proptest = "1"
serde_test = "1.0.89"
rand = { version = "0.10", features = ["chacha"] }
rand_core = { version = "0.10", default-features = false }
sha1 = { version = "0.11", default-features = false, features = ["oid"] }
sha2 = { version = "0.11", default-features = false, features = ["oid"] }
sha3 = { version = "0.11", default-features = false, features = ["oid"] }
hex = { version = "0.4.3", features = ["serde"] }
serde_json = "1.0.138"
serde = { version = "1.0.184", features = ["derive"] }
rstest = "0.26.1"
[[bench]]
name = "key"
[features]
default = ["std", "encoding", "private-key-operations-disabled"]
std = ["pkcs1?/std", "pkcs8?/std"]
encoding = ["dep:pkcs1", "dep:pkcs8", "dep:spki"]
hazmat = []
private-key-operations-disabled = []
getrandom = ["crypto-bigint/getrandom", "crypto-common"]
serde = ["encoding", "dep:serde", "dep:serdect", "crypto-bigint/serde"]
pkcs5 = ["pkcs8/encryption"]
[package.metadata.docs.rs]
features = ["std", "serde", "hazmat", "sha2"]
[profile.dev]
opt-level = 2
[profile.bench]
debug = true
+13
View File
@@ -0,0 +1,13 @@
# ELY private-operation gate
Source: `rsa 0.10.0-rc.18` from crates.io.
Source archive SHA-256: `30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf`.
ELY adds and enables the `private-key-operations-disabled` default feature. The shared
`rsa_decrypt` primitive returns before every private exponent operation. This gates RSA
decrypt and signing while preserving public-key encryption and verification.
The vendored test source also removes one trailing space for repository hygiene.
The gate mitigates `RUSTSEC-2023-0071` while the advisory has no patched release.
Remove this vendor patch after RustCrypto publishes a complete constant-time fix.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+123
View File
@@ -0,0 +1,123 @@
# [RustCrypto]: RSA
[![crates.io][crate-image]][crate-link]
[![Documentation][doc-image]][doc-link]
[![Build Status][build-image]][build-link]
[![Dependency Status][deps-image]][deps-link]
![Apache2/MIT licensed][license-image]
![MSRV][msrv-image]
[![Project Chat][chat-image]][chat-link]
A portable RSA implementation in pure Rust.
## Example
```rust
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
let mut rng = rand::rng();
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);
// Encrypt
let data = b"hello world";
let enc_data = pub_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]).expect("failed to encrypt");
assert_ne!(&data[..], &enc_data[..]);
// Decrypt
let dec_data = priv_key.decrypt(Pkcs1v15Encrypt, &enc_data).expect("failed to decrypt");
assert_eq!(&data[..], &dec_data[..]);
```
> **Note:** If you encounter unusually slow key generation time while using `RsaPrivateKey::new` you can try to compile in release mode or add the following to your `Cargo.toml`. Key generation is much faster when building with higher optimization levels, but this will increase the compile time a bit.
> ```toml
> [profile.debug]
> opt-level = 3
> ```
## Status
Currently at Phase 1 (v) 🚧
There will be three phases before `1.0` 🚢 can be released.
1. 🚧 Make it work
- [x] Prime generation ✅
- [x] Key generation ✅
- [x] PKCS1v1.5: Encryption & Decryption ✅
- [x] PKCS1v1.5: Sign & Verify ✅
- [ ] PKCS1v1.5 (session key): Encryption & Decryption
- [x] OAEP: Encryption & Decryption
- [x] PSS: Sign & Verify
- [x] Key import & export
2. 🚀 Make it fast
- [x] Benchmarks ✅
- [ ] compare to other implementations 🚧
- [ ] optimize 🚧
3. 🔐 Make it secure
- [ ] Fuzz testing
- [ ] Security Audits
## ⚠️Security Warning
This crate has received one [security audit by Include Security][audit], with
only one minor finding which has since been addressed.
See the [open security issues] on our issue tracker for other known problems.
~~Notably the implementation of [modular exponentiation is not constant time],
but timing variability is masked using [random blinding], a commonly used
technique.~~ This crate is vulnerable to the [Marvin Attack] which could enable
private key recovery by a network attacker (see [RUSTSEC-2023-0071]).
You can follow our work on mitigating this issue in [#390].
## Minimum Supported Rust Version (MSRV)
This crate supports Rust 1.85 or higher.
In the future MSRV can be changed, but it will be done with a minor version bump.
## License
Licensed under either of
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/rsa?logo=rust
[crate-link]: https://crates.io/crates/rsa
[doc-image]: https://docs.rs/rsa/badge.svg
[doc-link]: https://docs.rs/rsa
[build-image]: https://github.com/RustCrypto/RSA/actions/workflows/ci.yml/badge.svg
[build-link]: https://github.com/RustCrypto/RSA/actions/workflows/ci.yml
[build-image]: https://github.com/RustCrypto/RSA/actions/workflows/ci.yml/badge.svg?branch=master
[build-link]: https://github.com/RustCrypto/RSA/actions/workflows/ci.yml?query=branch:master
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[msrv-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260047-RSA
[deps-image]: https://deps.rs/repo/github/RustCrypto/RSA/status.svg
[deps-link]: https://deps.rs/repo/github/RustCrypto/RSA
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto/
[audit]: https://public.opentech.fund/documents/1907_OTF_DeltaChat_RPGP_RustRSA_GB_Report_v1.pdf
[open security issues]: https://github.com/RustCrypto/RSA/issues?q=is%3Aissue+is%3Aopen+label%3Asecurity
[modular exponentiation is not constant time]: https://github.com/RustCrypto/RSA/issues/19
[random blinding]: https://en.wikipedia.org/wiki/Blinding_(cryptography)
[Marvin Attack]: https://people.redhat.com/~hkario/marvin/
[RUSTSEC-2023-0071]: https://rustsec.org/advisories/RUSTSEC-2023-0071.html
[#390]: https://github.com/RustCrypto/RSA/issues/390
+17
View File
@@ -0,0 +1,17 @@
# Security Policy
## Supported Versions
Security updates are applied only to the most recent release.
## Reporting a Vulnerability
If you have discovered a security vulnerability in this project, please report
it privately. **Do not disclose it as a public issue.** This gives us time to
work with you to fix the issue before public exposure, reducing the chance that
the exploit will be used before a patch is released.
Please disclose it at [security advisory](https://github.com/RustCrypto/RSA/security/advisories/new).
This project is maintained by a team of volunteers on a reasonable-effort basis.
As such, please give us at least 90 days to work on a fix before public exposure.
+122
View File
@@ -0,0 +1,122 @@
#![feature(test)]
extern crate test;
use base64ct::{Base64, Encoding};
use crypto_bigint::BoxedUint;
use hex_literal::hex;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use rsa::{Pkcs1v15Encrypt, Pkcs1v15Sign, RsaPrivateKey};
use sha2::{Digest, Sha256};
use test::Bencher;
const DECRYPT_VAL: &str = "\
XW4qfrpQDarEMBfPyIYE9UvuOFkbBi0tiGYbIOJPLMNe/LWuPD0BQ7ceqlOlPPcK\
LinYz0DlnqW3It/V7ae59zw9afA3YIWdq0Ut2BnYL+aJixnqaP+PjsQNcHg6axCF\
11iNQ4jpXrZDiQcI+q9EEzZDTMsiMxtjfgBQUd8LHT87YoQXDWaFPCVpliACMc8a\
Uk442kH1tc4jEuXwjEjFErvAM/J7VizCdU/dnKrlq2mBDzvZ6hxY9TYHFB/zY6DZ\
PJAgEMUxYWCR9xPJ7X256DV1Kt0Ht33DWoFcgh/pPLM1q9pK0HVxCdclXfZOeCql\
rLgZ5Gxv5DM4BtV7Z4m85w==";
fn get_key() -> RsaPrivateKey {
// 2048 bits
let n = hex!(
"7163c842b2190a8970942b2764aed42d4124647b6f30e09a2da1c0e2"
"56aa2ee24e790c40c96a4bd66d75c371a915e0703c476b4e1a06f1bd"
"38c5a3c10ae3bd30f4ef62a5aa4f512ad145a06c48e96469a22ce8e6"
"21e052f0669a8c34155512d82e55447f0b7e18da94bd911ac7b3aabe"
"706843668964593ee71b2e5e484bcf0c7834101ab5d61bba1e63e623"
"7af40489ce36a260dab70add4fbec24d659db0f7cac099b0a3aa4549"
"acde7fc858a793a975e6cf65ca276b743525f0883980f6ad069bec34"
"6d787797386d50fe0c9734be967c7d84ae5b8f349b094079457c0c0c"
"6fee34c42a0b832603804f71e49f3320081637512c6cbf2bb81b6f6b"
"e239846d"
);
let d = hex!(
"4b97dad7216607064b0d721a431f381e2b6d98524a2095bc1e6bd5ec"
"39c6c9ec3450b2d5db9c328ef3a3d7a11b63eaf57d84f2341159f67e"
"25d917d607427e20a34a41c3c6df8b71e0d9159d85f0ed9bc17345ee"
"c140374aef11b2cd638e0c901ee382ff5cfebb3c63290b672fcd1c7e"
"f59ad799b0ed90d49a121ee98587df5cc161c584bc5887ae2a15e787"
"e86ab1e803366150561e0b3b3ae28ebdcf32cd46dff317ed3e1b7590"
"cc300d1d57c9288462d06d9fbe097e52b70dc4fca313ae09906e5fab"
"0c24729b54fe35cc38fe1496419a902f35f08460952bd4783e0e930b"
"a8b520f83eafe6fa6589bbab6e4f4bc5c285672c99f5055eec6a2a30"
"b06e786b"
);
let primes = [
hex!(
"ba69948f830c296242da6bf9ae3fddb76a63dbf0761ed3f644bc"
"a96a2e1eb75fd1bbd9cd93c72330bcc2a97cfafd12ee27bfde0f"
"b6ac152df2ec4ab12b11265b41bcb531e39f347fdf09e9562a6e"
"5a7c020c6534df61c955dd772cc7b9d461fdeea2f3b83663302c"
"fe5656c235d4ac94c81658ad179919cded8ab1be1e9aa369"
),
hex!(
"9bb7d344184526d29c689eddf0141bf65f013477e36b260e32ae"
"42c680b2c5ada9181bff32b9f1bfbdd3c29f59fcc3f4b9ee4ce6"
"766d18ca2fa4fe5c19d24b436c39a781f7a2972e59e616f58cab"
"bb6132084008fe10ff4dddd054fd2e91cd7d043b8f9795a07881"
"6cdb5f2e895394e29c37c3e12de41d4f67f17e64baf92c65"
),
];
RsaPrivateKey::from_components(
BoxedUint::from_be_slice(&n, 2048).unwrap(),
BoxedUint::from(3u32),
BoxedUint::from_be_slice(&d, 2048).unwrap(),
vec![
BoxedUint::from_be_slice(&primes[0], 1024).unwrap(),
BoxedUint::from_be_slice(&primes[1], 1024).unwrap(),
],
)
.unwrap()
}
#[bench]
fn bench_rsa_1024_gen_key(b: &mut Bencher) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
b.iter(|| {
let key = RsaPrivateKey::new(&mut rng, 1024).unwrap();
test::black_box(key);
});
}
#[bench]
fn bench_rsa_2048_gen_key(b: &mut Bencher) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
b.iter(|| {
let key = RsaPrivateKey::new(&mut rng, 2048).unwrap();
test::black_box(key);
});
}
#[bench]
fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) {
let priv_key = get_key();
let x = Base64::decode_vec(DECRYPT_VAL).unwrap();
b.iter(|| {
let res = priv_key.decrypt(Pkcs1v15Encrypt, &x).unwrap();
test::black_box(res);
});
}
#[bench]
fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) {
let priv_key = get_key();
let digest = Sha256::digest(b"testing").to_vec();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
b.iter(|| {
let res = priv_key
.sign_with_rng(&mut rng, Pkcs1v15Sign::new::<Sha256>(), &digest)
.unwrap();
test::black_box(res);
});
}
+10
View File
@@ -0,0 +1,10 @@
//! Useful algorithms related to RSA.
mod mgf;
pub(crate) mod generate;
pub(crate) mod oaep;
pub(crate) mod pad;
pub(crate) mod pkcs1v15;
pub(crate) mod pss;
pub(crate) mod rsa;
+183
View File
@@ -0,0 +1,183 @@
//! Generate prime components for the RSA Private Key
use alloc::vec::Vec;
use crypto_bigint::{BoxedUint, Odd};
use crypto_primes::{
hazmat::{SetBits, SmallFactorsSieveFactory},
is_prime, sieve_and_find, Flavor,
};
use rand_core::CryptoRng;
use crate::{
algorithms::rsa::{compute_modulus, compute_private_exponent_euler_totient},
errors::{Error, Result},
};
pub struct RsaPrivateKeyComponents {
pub n: Odd<BoxedUint>,
pub e: BoxedUint,
pub d: BoxedUint,
pub primes: Vec<BoxedUint>,
}
/// Generates a multi-prime RSA keypair of the given bit size, public exponent,
/// and the given random source, as suggested in [1]. Although the public
/// keys are compatible (actually, indistinguishable) from the 2-prime case,
/// the private keys are not. Thus it may not be possible to export multi-prime
/// private keys in certain formats or to subsequently import them into other
/// code.
///
/// Table 1 in [2] suggests maximum numbers of primes for a given size.
///
/// [1]: https://patents.google.com/patent/US4405829A/en
/// [2]: http://www.cacr.math.uwaterloo.ca/techreports/2006/cacr2006-16.pdf
pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRng + ?Sized>(
rng: &mut R,
nprimes: usize,
bit_size: usize,
exp: BoxedUint,
) -> Result<RsaPrivateKeyComponents> {
if nprimes < 2 {
return Err(Error::NprimesTooSmall);
}
if bit_size < 64 {
let prime_limit = (1u64 << (bit_size / nprimes) as u64) as f64;
// pi approximates the number of primes less than prime_limit
// Calculate `log(prime_limit)` as `log(x) = log2(x) / log2(e) = log2(x) * log(2)`.
let mut pi = prime_limit / ((bit_size / nprimes) as f64 * core::f64::consts::LN_2 - 1.);
// Generated primes start with 0b11, so we can only use a quarter of them.
pi /= 4f64;
// Use a factor of two to ensure that key generation terminates in a
// reasonable amount of time.
pi /= 2f64;
if pi < nprimes as f64 {
return Err(Error::TooFewPrimes);
}
}
let mut primes = vec![BoxedUint::zero(); nprimes];
let n_final: Odd<BoxedUint>;
let d_final: BoxedUint;
'next: loop {
let mut todo = bit_size;
// `generate_prime_with_rng` should set the top two bits in each prime.
// Thus each prime has the form
// p_i = 2^bitlen(p_i) × 0.11... (in base 2).
// And the product is:
// P = 2^todo × α
// where α is the product of nprimes numbers of the form 0.11...
//
// If α < 1/2 (which can happen for nprimes > 2), we need to
// shift todo to compensate for lost bits: the mean value of 0.11...
// is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2
// will give good results.
if nprimes >= 7 {
todo += (nprimes - 2) / 5;
}
for (i, prime) in primes.iter_mut().enumerate() {
let bits = (todo / (nprimes - i)) as u32;
*prime = generate_prime_with_rng(rng, bits);
todo -= prime.bits() as usize;
}
// Makes sure that primes is pairwise unequal.
for (i, prime1) in primes.iter().enumerate() {
for prime2 in primes.iter().take(i) {
if prime1 == prime2 {
continue 'next;
}
}
}
let n = compute_modulus(&primes);
if n.bits() as usize != bit_size {
// This should never happen for nprimes == 2 because
// generate_prime_with_rng should set the top two bits in each prime.
// For nprimes > 2 we hope it does not happen often.
continue 'next;
}
if let Ok(d) = compute_private_exponent_euler_totient(&primes, &exp) {
n_final = n;
d_final = d;
break;
}
}
Ok(RsaPrivateKeyComponents {
n: n_final,
e: exp,
d: d_final,
primes,
})
}
fn generate_prime_with_rng<R: CryptoRng + ?Sized>(rng: &mut R, bit_length: u32) -> BoxedUint {
let factory = SmallFactorsSieveFactory::new(Flavor::Any, bit_length, SetBits::TwoMsb)
.unwrap_or_else(|err| panic!("Error creating the sieve: {err}"));
sieve_and_find(rng, factory, |_rng, candidate| {
is_prime(Flavor::Any, candidate)
})
.unwrap_or_else(|err| panic!("Error generating random candidates: {}", err))
.expect("will produce a result eventually")
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
const EXP: u64 = 65537;
#[test]
fn test_impossible_keys() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let exp = BoxedUint::from(EXP);
for i in 0..32 {
let _ = generate_multi_prime_key_with_exp(&mut rng, 2, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 3, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 4, i, exp.clone());
let _ = generate_multi_prime_key_with_exp(&mut rng, 5, i, exp.clone());
}
}
macro_rules! key_generation {
($name:ident, $multi:expr, $size:expr) => {
#[test]
fn $name() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let exp = BoxedUint::from(EXP);
for _ in 0..10 {
let components =
generate_multi_prime_key_with_exp(&mut rng, $multi, $size, exp.clone())
.unwrap();
assert_eq!(components.n.bits(), $size);
assert_eq!(components.primes.len(), $multi);
}
}
};
}
key_generation!(key_generation_128, 2, 128);
key_generation!(key_generation_1024, 2, 1024);
key_generation!(key_generation_multi_3_256, 3, 256);
key_generation!(key_generation_multi_4_64, 4, 64);
key_generation!(key_generation_multi_5_64, 5, 64);
key_generation!(key_generation_multi_8_576, 8, 576);
// TODO: reenable, currently slow
// key_generation!(key_generation_multi_16_1024, 16, 1024);
}
+78
View File
@@ -0,0 +1,78 @@
//! Mask generation function common to both PSS and OAEP padding
use digest::{Digest, FixedOutputReset};
/// Mask generation function.
///
/// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1
pub(crate) fn mgf1_xor<D>(out: &mut [u8], digest: &mut D, seed: &[u8])
where
D: Digest + FixedOutputReset,
{
let mut counter = [0u8; 4];
let mut i = 0;
const MAX_LEN: u64 = u32::MAX as u64 + 1;
assert!(out.len() as u64 <= MAX_LEN);
while i < out.len() {
let mut digest_input = vec![0u8; seed.len() + 4];
digest_input[0..seed.len()].copy_from_slice(seed);
digest_input[seed.len()..].copy_from_slice(&counter);
Digest::update(digest, digest_input.as_slice());
let digest_output = &*digest.finalize_reset();
let mut j = 0;
loop {
if j >= digest_output.len() || i >= out.len() {
break;
}
out[i] ^= digest_output[j];
j += 1;
i += 1;
}
inc_counter(&mut counter);
}
}
/// Mask generation function.
///
/// Panics if out is larger than 2**32. This is in accordance with RFC 8017 - PKCS #1 B.2.1
pub(crate) fn mgf1_xor_digest<D>(out: &mut [u8], digest: &mut D, seed: &[u8])
where
D: Digest + FixedOutputReset,
{
let mut counter = [0u8; 4];
let mut i = 0;
const MAX_LEN: u64 = u32::MAX as u64 + 1;
assert!(out.len() as u64 <= MAX_LEN);
while i < out.len() {
Digest::update(digest, seed);
Digest::update(digest, counter);
let digest_output = digest.finalize_reset();
let mut j = 0;
loop {
if j >= digest_output.len() || i >= out.len() {
break;
}
out[i] ^= digest_output[j];
j += 1;
i += 1;
}
inc_counter(&mut counter);
}
}
fn inc_counter(counter: &mut [u8; 4]) {
for i in (0..4).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 {
// No overflow
return;
}
}
}
+260
View File
@@ -0,0 +1,260 @@
//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
//!
use alloc::boxed::Box;
use alloc::vec::Vec;
use crypto_bigint::{Choice, CtAssign, CtEq, CtOption};
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use zeroize::Zeroizing;
use super::mgf::{mgf1_xor, mgf1_xor_digest};
use crate::errors::{Error, Result};
/// Maximum label size (2^64 bits) for SHA-1 and SHA-256 hash functions.
///
/// In theory, other hash functions (e.g. SHA-512 and SHA-3) can process longer labels,
/// but such huge inputs are practically impossible on one machine, so we use this limit
/// for all hash functions.
const MAX_LABEL_LEN: u64 = 1 << 61;
#[inline]
fn encrypt_internal<R: TryCryptoRng + ?Sized, MGF: FnMut(&mut [u8], &mut [u8])>(
rng: &mut R,
msg: &[u8],
p_hash: &[u8],
h_size: usize,
k: usize,
mut mgf: MGF,
) -> Result<Zeroizing<Vec<u8>>> {
if msg.len() + 2 * h_size + 2 > k {
return Err(Error::MessageTooLong);
}
let mut em = Zeroizing::new(vec![0u8; k]);
let (_, payload) = em.split_at_mut(1);
let (seed, db) = payload.split_at_mut(h_size);
rng.try_fill_bytes(seed).map_err(|_| Error::Rng)?;
// Data block DB = pHash || PS || 01 || M
let db_len = k - h_size - 1;
db[0..h_size].copy_from_slice(p_hash);
db[db_len - msg.len() - 1] = 1;
db[db_len - msg.len()..].copy_from_slice(msg);
mgf(seed, db);
Ok(em)
}
/// Encrypts the given message with RSA and the padding scheme from
/// [PKCS#1 OAEP].
///
/// The message must be no longer than the length of the public modulus minus
/// `2 + (2 * hash.size())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_encrypt<R, D, MGD>(
rng: &mut R,
msg: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
Digest::update(digest, &label);
let p_hash = digest.finalize_reset();
encrypt_internal(rng, msg, &p_hash, h_size, k, |seed, db| {
mgf1_xor(db, mgf_digest, seed);
mgf1_xor(seed, mgf_digest, db);
})
}
/// Encrypts the given message with RSA and the padding scheme from
/// [PKCS#1 OAEP].
///
/// The message must be no longer than the length of the public modulus minus
/// `2 + (2 * hash.size())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_encrypt_digest<R, D, MGD>(
rng: &mut R,
msg: &[u8],
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
let p_hash = D::digest(&label);
encrypt_internal(rng, msg, &p_hash, h_size, k, |seed, db| {
let mut mgf_digest = MGD::new();
mgf1_xor_digest(db, &mut mgf_digest, seed);
mgf1_xor_digest(seed, &mut mgf_digest, db);
})
}
///Decrypts OAEP padding.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key.
///
/// See `decrypt_session_key` for a way of solving this problem.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_decrypt<D, MGD>(
em: &mut [u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::Decryption);
}
Digest::update(digest, &label);
let expected_p_hash = digest.finalize_reset();
let res = decrypt_inner(em, h_size, &expected_p_hash, k, |seed, db| {
mgf1_xor(seed, mgf_digest, db);
mgf1_xor(db, mgf_digest, seed);
})?;
if res.is_none().into() {
return Err(Error::Decryption);
}
let index = res.unwrap();
Ok(em[index as usize..].to_vec())
}
///Decrypts OAEP padding.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key.
///
/// See `decrypt_session_key` for a way of solving this problem.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
pub(crate) fn oaep_decrypt_digest<D, MGD>(
em: &mut [u8],
label: Option<Box<[u8]>>,
k: usize,
) -> Result<Vec<u8>>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
let h_size = <D as Digest>::output_size();
let label = label.unwrap_or_default();
if label.len() as u64 >= MAX_LABEL_LEN {
return Err(Error::LabelTooLong);
}
let expected_p_hash = D::digest(&label);
let res = decrypt_inner(em, h_size, &expected_p_hash, k, |seed, db| {
let mut mgf_digest = MGD::new();
mgf1_xor_digest(seed, &mut mgf_digest, db);
mgf1_xor_digest(db, &mut mgf_digest, seed);
})?;
if res.is_none().into() {
return Err(Error::Decryption);
}
let index = res.unwrap();
Ok(em[index as usize..].to_vec())
}
/// Decrypts OAEP padding. It returns one or zero in valid that indicates whether the
/// plaintext was correctly structured.
#[inline]
fn decrypt_inner<MGF: FnMut(&mut [u8], &mut [u8])>(
em: &mut [u8],
h_size: usize,
expected_p_hash: &[u8],
k: usize,
mut mgf: MGF,
) -> Result<CtOption<u32>> {
if k < 11 {
return Err(Error::Decryption);
}
if k < h_size * 2 + 2 {
return Err(Error::Decryption);
}
let first_byte_is_zero = em[0].ct_eq(&0u8);
let (_, payload) = em.split_at_mut(1);
let (seed, db) = payload.split_at_mut(h_size);
mgf(seed, db);
let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash);
// The remainder of the plaintext must be zero or more 0x00, followed
// by 0x01, followed by the message.
// looking_for_index: 1 if we are still looking for the 0x01
// index: the offset of the first 0x01 byte
// zero_before_one: 1 if we saw a non-zero byte before the 1
let mut looking_for_index = Choice::TRUE;
let mut index = 0u32;
let mut nonzero_before_one = Choice::FALSE;
for (i, el) in db.iter().skip(h_size).enumerate() {
let equals0 = el.ct_eq(&0u8);
let equals1 = el.ct_eq(&1u8);
index.ct_assign(&(i as u32), looking_for_index & equals1);
looking_for_index &= !equals1;
nonzero_before_one |= looking_for_index & !equals0;
}
let valid = first_byte_is_zero & hash_are_equal & !nonzero_before_one & !looking_for_index;
Ok(CtOption::new(index + 2 + (h_size * 2) as u32, valid))
}
+63
View File
@@ -0,0 +1,63 @@
//! Special handling for converting the BigUint to u8 vectors
use alloc::vec::Vec;
use crypto_bigint::BoxedUint;
use zeroize::Zeroizing;
use crate::errors::{Error, Result};
/// Returns a new vector of the given length, with 0s left padded.
#[inline]
fn left_pad(input: &[u8], padded_len: usize) -> Result<Vec<u8>> {
if input.len() > padded_len {
return Err(Error::InvalidPadLen);
}
let mut out = vec![0u8; padded_len];
out[padded_len - input.len()..].copy_from_slice(input);
Ok(out)
}
/// Converts input to the new vector of the given length, using BE and with 0s left padded.
/// In some cases BoxedUint might already have leading zeroes, this function removes them
/// before padding again.
#[inline]
pub(crate) fn uint_to_be_pad(input: BoxedUint, padded_len: usize) -> Result<Vec<u8>> {
let leading_zeros = input.leading_zeros() as usize / 8;
left_pad(&input.to_be_bytes()[leading_zeros..], padded_len)
}
/// Converts input to the new vector of the given length, using BE and with 0s left padded.
/// In some cases BoxedUint might already have leading zeroes, this function removes them
/// before padding again.
#[inline]
pub(crate) fn uint_to_zeroizing_be_pad(input: BoxedUint, padded_len: usize) -> Result<Vec<u8>> {
let leading_zeros = input.leading_zeros() as usize / 8;
let m = Zeroizing::new(input);
let m = Zeroizing::new(m.to_be_bytes());
left_pad(&m[leading_zeros..], padded_len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_pad() {
const INPUT_LEN: usize = 3;
let input = vec![0u8; INPUT_LEN];
// input len < padded len
let padded = left_pad(&input, INPUT_LEN + 1).unwrap();
assert_eq!(padded.len(), INPUT_LEN + 1);
// input len == padded len
let padded = left_pad(&input, INPUT_LEN).unwrap();
assert_eq!(padded.len(), INPUT_LEN);
// input len > padded len
let padded = left_pad(&input, INPUT_LEN - 1);
assert!(padded.is_err());
}
}
+213
View File
@@ -0,0 +1,213 @@
//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures).
//!
//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use crypto_bigint::{Choice, CtAssign, CtEq, CtSelect};
use digest::Digest;
use rand_core::TryCryptoRng;
use zeroize::Zeroizing;
use crate::errors::{Error, Result};
/// Fills the provided slice with random values, which are guaranteed
/// to not be zero.
#[inline]
fn non_zero_random_bytes<R: TryCryptoRng + ?Sized>(
rng: &mut R,
data: &mut [u8],
) -> core::result::Result<(), R::Error> {
rng.try_fill_bytes(data)?;
for el in data {
if *el == 0u8 {
// TODO: break after a certain amount of time
while *el == 0u8 {
rng.try_fill_bytes(core::slice::from_mut(el))?;
}
}
}
Ok(())
}
/// Applied the padding scheme from PKCS#1 v1.5 for encryption. The message must be no longer than
/// the length of the public modulus minus 11 bytes.
pub(crate) fn pkcs1v15_encrypt_pad<R>(
rng: &mut R,
msg: &[u8],
k: usize,
) -> Result<Zeroizing<Vec<u8>>>
where
R: TryCryptoRng + ?Sized,
{
if msg.len() + 11 > k {
return Err(Error::MessageTooLong);
}
// EM = 0x00 || 0x02 || PS || 0x00 || M
let mut em = Zeroizing::new(vec![0u8; k]);
em[1] = 2;
non_zero_random_bytes(rng, &mut em[2..k - msg.len() - 1]).map_err(|_: R::Error| Error::Rng)?;
em[k - msg.len() - 1] = 0;
em[k - msg.len()..].copy_from_slice(msg);
Ok(em)
}
/// Removes the encryption padding scheme from PKCS#1 v1.5.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key. See
/// `decrypt_session_key` for a way of solving this problem.
#[inline]
pub(crate) fn pkcs1v15_encrypt_unpad(em: Vec<u8>, k: usize) -> Result<Vec<u8>> {
let (valid, out, index) = decrypt_inner(em, k)?;
if valid == 0 {
return Err(Error::Decryption);
}
Ok(out[index as usize..].to_vec())
}
/// Removes the PKCS1v15 padding It returns one or zero in valid that indicates whether the
/// plaintext was correctly structured. In either case, the plaintext is
/// returned in em so that it may be read independently of whether it was valid
/// in order to maintain constant memory access patterns. If the plaintext was
/// valid then index contains the index of the original message in em.
#[inline]
fn decrypt_inner(em: Vec<u8>, k: usize) -> Result<(u8, Vec<u8>, u32)> {
if k < 11 {
return Err(Error::Decryption);
}
let first_byte_is_zero = em[0].ct_eq(&0u8);
let second_byte_is_two = em[1].ct_eq(&2u8);
// The remainder of the plaintext must be a string of non-zero random
// octets, followed by a 0, followed by the message.
// looking_for_index: 1 iff we are still looking for the zero.
// index: the offset of the first zero byte.
let mut looking_for_index = Choice::TRUE;
let mut index = 0u32;
for (i, el) in em.iter().enumerate().skip(2) {
let equals0 = el.ct_eq(&0u8);
index.ct_assign(&(i as u32), looking_for_index & equals0);
looking_for_index &= !equals0;
}
// The PS padding must be at least 8 bytes long, and it starts two
// bytes into em.
// TODO: WARNING: THIS MUST BE CONSTANT TIME CHECK:
// Ref: https://github.com/dalek-cryptography/subtle/issues/20
// This is currently copy & paste from the constant time impl in
// go, but very likely not sufficient.
let valid_ps = Choice::from_u8_lsb((((2i32 + 8i32 - index as i32 - 1i32) >> 31) & 1) as u8);
let valid = first_byte_is_zero & second_byte_is_two & !looking_for_index & valid_ps;
index = u32::ct_select(&0, &(index + 1), valid);
Ok((valid.to_u8(), em, index))
}
#[inline]
pub(crate) fn pkcs1v15_sign_pad(prefix: &[u8], hashed: &[u8], k: usize) -> Result<Vec<u8>> {
let hash_len = hashed.len();
let t_len = prefix.len() + hashed.len();
if k < t_len + 11 {
return Err(Error::MessageTooLong);
}
// EM = 0x00 || 0x01 || PS || 0x00 || T
let mut em = vec![0xff; k];
em[0] = 0;
em[1] = 1;
em[k - t_len - 1] = 0;
em[k - t_len..k - hash_len].copy_from_slice(prefix);
em[k - hash_len..k].copy_from_slice(hashed);
Ok(em)
}
#[inline]
pub(crate) fn pkcs1v15_sign_unpad(prefix: &[u8], hashed: &[u8], em: &[u8], k: usize) -> Result<()> {
let hash_len = hashed.len();
let t_len = prefix.len() + hashed.len();
if k < t_len + 11 {
return Err(Error::Verification);
}
// EM = 0x00 || 0x01 || PS || 0x00 || T
let mut ok = em[0].ct_eq(&0u8);
ok &= em[1].ct_eq(&1u8);
ok &= em[k - hash_len..k].ct_eq(hashed);
ok &= em[k - t_len..k - hash_len].ct_eq(prefix);
ok &= em[k - t_len - 1].ct_eq(&0u8);
for el in em.iter().skip(2).take(k - t_len - 3) {
ok &= el.ct_eq(&0xff)
}
// TODO(tarcieri): avoid branching here by e.g. using a pseudorandom rejection symbol
if !ok.to_bool() {
return Err(Error::Verification);
}
Ok(())
}
/// prefix = 0x30 <oid_len + 8 + digest_len> 0x30 <oid_len + 4> 0x06 <oid_len> oid 0x05 0x00 0x04 <digest_len>
#[inline]
pub(crate) fn pkcs1v15_generate_prefix<D>() -> Vec<u8>
where
D: Digest + AssociatedOid,
{
let oid = D::OID.as_bytes();
let oid_len = oid.len() as u8;
let digest_len = <D as Digest>::output_size() as u8;
let mut v = vec![
0x30,
oid_len + 8 + digest_len,
0x30,
oid_len + 4,
0x6,
oid_len,
];
v.extend_from_slice(oid);
v.extend_from_slice(&[0x05, 0x00, 0x04, digest_len]);
v
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
#[test]
fn test_non_zero_bytes() {
for _ in 0..10 {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let mut b = vec![0u8; 512];
non_zero_random_bytes(&mut rng, &mut b).unwrap();
for el in &b {
assert_ne!(*el, 0u8);
}
}
}
#[test]
fn test_encrypt_tiny_no_crash() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = 8;
let message = vec![1u8; 4];
let res = pkcs1v15_encrypt_pad(&mut rng, &message, k);
assert_eq!(res, Err(Error::MessageTooLong));
}
}
+383
View File
@@ -0,0 +1,383 @@
//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS.
//!
//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pss-signatures).
//!
//! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme
//! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
use alloc::vec::Vec;
use crypto_bigint::{Choice, CtEq, CtSelect};
use digest::{Digest, FixedOutputReset};
use super::mgf::{mgf1_xor, mgf1_xor_digest};
use crate::errors::{Error, Result};
pub(crate) fn emsa_pss_encode<D>(
m_hash: &[u8],
em_bits: usize,
salt: &[u8],
hash: &mut D,
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
{
// See [1], section 9.1.1
let h_len = <D as Digest>::output_size();
let s_len = salt.len();
let em_len = em_bits.div_ceil(8);
// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "message too
// long" and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen.
if m_hash.len() != h_len {
return Err(Error::InputNotHashed);
}
// 3. If em_len < h_len + s_len + 2, output "encoding error" and stop.
if em_len < h_len + s_len + 2 {
// TODO: Key size too small
return Err(Error::Internal);
}
let mut em = vec![0; em_len];
let (db, h) = em.split_at_mut(em_len - h_len - 1);
let h = &mut h[..(em_len - 1) - db.len()];
// 4. Generate a random octet string salt of length s_len; if s_len = 0,
// then salt is the empty string.
//
// 5. Let
// M' = (0x)00 00 00 00 00 00 00 00 || m_hash || salt;
//
// M' is an octet string of length 8 + h_len + s_len with eight
// initial zero octets.
//
// 6. Let H = Hash(M'), an octet string of length h_len.
let prefix = [0u8; 8];
Digest::update(hash, prefix);
Digest::update(hash, m_hash);
Digest::update(hash, salt);
let hashed = hash.finalize_reset();
h.copy_from_slice(&hashed);
// 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2
// zero octets. The length of PS may be 0.
//
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
// emLen - hLen - 1.
db[em_len - s_len - h_len - 2] = 0x01;
db[em_len - s_len - h_len - 1..].copy_from_slice(salt);
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
//
// 10. Let maskedDB = DB \xor dbMask.
mgf1_xor(db, hash, h);
// 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in
// maskedDB to zero.
db[0] &= 0xFF >> (8 * em_len - em_bits);
// 12. Let EM = maskedDB || H || 0xbc.
em[em_len - 1] = 0xBC;
Ok(em)
}
pub(crate) fn emsa_pss_encode_digest<D>(
m_hash: &[u8],
em_bits: usize,
salt: &[u8],
) -> Result<Vec<u8>>
where
D: Digest + FixedOutputReset,
{
// See [1], section 9.1.1
let h_len = <D as Digest>::output_size();
let s_len = salt.len();
let em_len = em_bits.div_ceil(8);
// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "message too
// long" and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen.
if m_hash.len() != h_len {
return Err(Error::InputNotHashed);
}
// 3. If em_len < h_len + s_len + 2, output "encoding error" and stop.
if em_len < h_len + s_len + 2 {
// TODO: Key size too small
return Err(Error::Internal);
}
let mut em = vec![0; em_len];
let (db, h) = em.split_at_mut(em_len - h_len - 1);
let h = &mut h[..(em_len - 1) - db.len()];
// 4. Generate a random octet string salt of length s_len; if s_len = 0,
// then salt is the empty string.
//
// 5. Let
// M' = (0x)00 00 00 00 00 00 00 00 || m_hash || salt;
//
// M' is an octet string of length 8 + h_len + s_len with eight
// initial zero octets.
//
// 6. Let H = Hash(M'), an octet string of length h_len.
let prefix = [0u8; 8];
let mut hash = D::new();
Digest::update(&mut hash, prefix);
Digest::update(&mut hash, m_hash);
Digest::update(&mut hash, salt);
let hashed = hash.finalize_reset();
h.copy_from_slice(&hashed);
// 7. Generate an octet string PS consisting of em_len - s_len - h_len - 2
// zero octets. The length of PS may be 0.
//
// 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
// emLen - hLen - 1.
db[em_len - s_len - h_len - 2] = 0x01;
db[em_len - s_len - h_len - 1..].copy_from_slice(salt);
// 9. Let dbMask = MGF(H, emLen - hLen - 1).
//
// 10. Let maskedDB = DB \xor dbMask.
mgf1_xor_digest(db, &mut hash, h);
// 11. Set the leftmost 8 * em_len - em_bits bits of the leftmost octet in
// maskedDB to zero.
db[0] &= 0xFF >> (8 * em_len - em_bits);
// 12. Let EM = maskedDB || H || 0xbc.
em[em_len - 1] = 0xBC;
Ok(em)
}
fn emsa_pss_verify_pre<'a>(
m_hash: &[u8],
em: &'a mut [u8],
em_bits: usize,
s_len: Option<usize>,
h_len: usize,
) -> Result<(&'a mut [u8], &'a mut [u8])> {
// 1. If the length of M is greater than the input limitation for the
// hash function (2^61 - 1 octets for SHA-1), output "inconsistent"
// and stop.
//
// 2. Let mHash = Hash(M), an octet string of length hLen
if m_hash.len() != h_len {
return Err(Error::Verification);
}
let em_len = em.len(); //(em_bits + 7) / 8;
if let Some(s_len) = s_len {
// 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
if em_len < h_len + s_len + 2 {
return Err(Error::Verification);
}
}
// 4. If the rightmost octet of EM does not have hexadecimal value
// 0xbc, output "inconsistent" and stop.
if em[em.len() - 1] != 0xBC {
return Err(Error::Verification);
}
// 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
// let H be the next hLen octets.
let (db, h) = em.split_at_mut(em_len - h_len - 1);
let h = &mut h[..h_len];
// 6. If the leftmost 8 * em_len - em_bits bits of the leftmost octet in
// maskedDB are not all equal to zero, output "inconsistent" and
// stop.
if db[0]
& (0xFF_u8
.checked_shl(8 - (8 * em_len - em_bits) as u32)
.unwrap_or(0))
!= 0
{
return Err(Error::Verification);
}
Ok((db, h))
}
fn emsa_pss_verify_salt(db: &[u8], em_len: usize, s_len: usize, h_len: usize) -> Choice {
// 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
// or if the octet at position emLen - hLen - sLen - 1 (the leftmost
// position is "position 1") does not have hexadecimal value 0x01,
// output "inconsistent" and stop.
let (zeroes, rest) = db.split_at(em_len - h_len - s_len - 2);
let valid: Choice = zeroes.iter().fold(Choice::TRUE, |a, e| a & e.ct_eq(&0x00));
valid & rest[0].ct_eq(&0x01)
}
/// Detect salt length by scanning DB for the 0x01 separator byte.
/// Returns (s_len, valid) where s_len is 0 on failure.
fn emsa_pss_get_salt_len(db: &[u8], em_len: usize, h_len: usize) -> (usize, Choice) {
let em_len = em_len as u32;
let h_len = h_len as u32;
let max_scan_len = em_len - h_len - 2;
let mut separator_pos = 0u32;
let mut found_separator = Choice::FALSE;
let mut padding_valid = Choice::TRUE;
// Single forward scan to find separator and validate padding
for i in 0..=max_scan_len {
let byte_val = db[i as usize];
let is_zero = byte_val.ct_eq(&0x00);
let is_separator = byte_val.ct_eq(&0x01);
let is_invalid = !(is_zero | is_separator);
// Update separator position if we found one and haven't found one before
let should_update_pos = is_separator & !found_separator;
separator_pos = u32::ct_select(&separator_pos, &i, should_update_pos);
found_separator = Choice::ct_select(&found_separator, &Choice::TRUE, should_update_pos);
// Padding is invalid if we see a non-zero, non-separator byte before finding separator
let corrupts_padding = is_invalid & !found_separator;
padding_valid &= !corrupts_padding;
}
let salt_len = max_scan_len.wrapping_sub(separator_pos);
let final_valid = found_separator & padding_valid;
// Return 0 length on failure
let result_len = u32::ct_select(&0u32, &salt_len, final_valid);
(result_len as usize, final_valid)
}
pub(crate) fn emsa_pss_verify<D>(
m_hash: &[u8],
em: &mut [u8],
s_len: Option<usize>,
hash: &mut D,
key_bits: usize,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let em_bits = key_bits - 1;
let em_len = em_bits.div_ceil(8);
let key_len = key_bits.div_ceil(8);
let h_len = <D as Digest>::output_size();
let em = &mut em[key_len - em_len..];
let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?;
// 7. Let dbMask = MGF(H, em_len - h_len - 1)
//
// 8. Let DB = maskedDB \xor dbMask
mgf1_xor(db, hash, &*h);
// 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB
// to zero.
db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits);
let (s_len, salt_valid) = match s_len {
Some(s_len) => (s_len, emsa_pss_verify_salt(db, em_len, s_len, h_len)),
None => emsa_pss_get_salt_len(db, em_len, h_len),
};
// 11. Let salt be the last s_len octets of DB.
let salt = &db[db.len() - s_len..];
// 12. Let
// M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
// M' is an octet string of length 8 + hLen + sLen with eight
// initial zero octets.
//
// 13. Let H' = Hash(M'), an octet string of length hLen.
let prefix = [0u8; 8];
Digest::update(hash, &prefix[..]);
Digest::update(hash, m_hash);
Digest::update(hash, salt);
let h0 = hash.finalize_reset();
// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
if (salt_valid & h0.as_slice().ct_eq(h)).into() {
Ok(())
} else {
Err(Error::Verification)
}
}
pub(crate) fn emsa_pss_verify_digest<D>(
m_hash: &[u8],
em: &mut [u8],
s_len: Option<usize>,
key_bits: usize,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let em_bits = key_bits - 1;
let em_len = em_bits.div_ceil(8);
let key_len = key_bits.div_ceil(8);
let h_len = <D as Digest>::output_size();
let em = &mut em[key_len - em_len..];
let (db, h) = emsa_pss_verify_pre(m_hash, em, em_bits, s_len, h_len)?;
let mut hash = D::new();
// 7. Let dbMask = MGF(H, em_len - h_len - 1)
//
// 8. Let DB = maskedDB \xor dbMask
mgf1_xor_digest::<D>(db, &mut hash, &*h);
// 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB
// to zero.
db[0] &= 0xFF >> /*uint*/(8 * em_len - em_bits);
let (s_len, salt_valid) = match s_len {
Some(s_len) => (s_len, emsa_pss_verify_salt(db, em_len, s_len, h_len)),
None => emsa_pss_get_salt_len(db, em_len, h_len),
};
// 11. Let salt be the last s_len octets of DB.
let salt = &db[db.len() - s_len..];
// 12. Let
// M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
// M' is an octet string of length 8 + hLen + sLen with eight
// initial zero octets.
//
// 13. Let H' = Hash(M'), an octet string of length hLen.
let prefix = [0u8; 8];
Digest::update(&mut hash, &prefix[..]);
Digest::update(&mut hash, m_hash);
Digest::update(&mut hash, salt);
let h0 = hash.finalize_reset();
// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
if (salt_valid & h0.as_slice().ct_eq(h)).into() {
Ok(())
} else {
Err(Error::Verification)
}
}
+484
View File
@@ -0,0 +1,484 @@
//! Generic RSA implementation
use core::cmp::Ordering;
use crypto_bigint::{
modular::{BoxedMontyForm, BoxedMontyParams},
BoxedUint, ConcatenatingMul, ConcatenatingSquare, Gcd, NonZero, Odd, RandomMod, Resize,
};
use rand_core::TryCryptoRng;
use zeroize::Zeroize;
use crate::errors::{Error, Result};
use crate::traits::keys::{PrivateKeyParts, PublicKeyParts};
/// ⚠️ Raw RSA encryption of m with the public key. No padding is performed.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_encrypt<K: PublicKeyParts>(key: &K, m: &BoxedUint) -> Result<BoxedUint> {
let e = key.e();
let res = pow_mod_params_vartime_exp_bits(m, e, e.bits(), key.n_params());
Ok(res)
}
/// ⚠️ Performs raw RSA decryption with no padding or error checking.
///
/// Returns a plaintext `BoxedUint`. Performs RSA blinding if an `Rng` is passed.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &impl PrivateKeyParts,
c: &BoxedUint,
) -> Result<BoxedUint> {
// ELY gates every private exponent operation until RUSTSEC-2023-0071 has a patched release.
if cfg!(feature = "private-key-operations-disabled") {
return Err(Error::Decryption);
}
let n = priv_key.n();
let d = priv_key.d();
if c.bits_precision() != n.as_ref().bits_precision() {
return Err(Error::Decryption);
}
if c >= n.as_ref() {
return Err(Error::Decryption);
}
let mut ir = None;
let n_params = priv_key.n_params();
let bits = d.bits_precision();
let c = if let Some(rng) = rng {
let (blinded, unblinder) = blind(rng, priv_key, c, n_params)?;
ir = Some(unblinder);
blinded.try_resize(bits).ok_or(Error::Internal)?
} else {
c.try_resize(bits).ok_or(Error::Internal)?
};
let is_multiprime = priv_key.primes().len() > 2;
let m = match (
priv_key.dp(),
priv_key.dq(),
priv_key.qinv(),
priv_key.p_params(),
priv_key.q_params(),
) {
(Some(dp), Some(dq), Some(qinv), Some(p_params), Some(q_params)) if !is_multiprime => {
// We have the precalculated values needed for the CRT.
let p = &priv_key.primes()[0];
let q = &priv_key.primes()[1];
// precomputed: dP = (1/e) mod (p-1) = d mod (p-1)
// precomputed: dQ = (1/e) mod (q-1) = d mod (q-1)
// TODO: it may be faster to convert to and from Montgomery with prepared parameters
// (modulo `p` and `q`) rather than calculating the remainder directly.
// m1 = c^dP mod p
let p_wide = p_params.modulus().resize_unchecked(c.bits_precision());
let c_mod_dp = (&c % p_wide.as_nz_ref()).resize_unchecked(dp.bits_precision());
let cp = BoxedMontyForm::new(c_mod_dp, p_params);
let mut m1 = cp.pow(dp);
// m2 = c^dQ mod q
let q_wide = q_params.modulus().resize_unchecked(c.bits_precision());
let c_mod_dq = (&c % q_wide.as_nz_ref()).resize_unchecked(dq.bits_precision());
let cq = BoxedMontyForm::new(c_mod_dq, q_params);
let m2 = cq.pow(dq).retrieve();
// Note that since `p` and `q` may have different `bits_precision`,
// it may be different for `m1` and `m2` as well.
// (m1 - m2) mod p = (m1 mod p) - (m2 mod p) mod p
let m2_mod_p = match p_params.bits_precision().cmp(&q_params.bits_precision()) {
Ordering::Less => {
let p_wide = NonZero::new(p.clone())
.expect("`p` is non-zero")
.resize_unchecked(q_params.bits_precision());
(&m2 % p_wide).resize_unchecked(p_params.bits_precision())
}
Ordering::Greater => (&m2).resize_unchecked(p_params.bits_precision()),
Ordering::Equal => m2.clone(),
};
let m2r = BoxedMontyForm::new(m2_mod_p, p_params);
m1 -= &m2r;
// precomputed: qInv = (1/q) mod p
// h = qInv.(m1 - m2) mod p
let h = (qinv * m1).retrieve();
// m = m2 + h.q
let m2 = m2.try_resize(n.bits_precision()).ok_or(Error::Internal)?;
let hq = h
.concatenating_mul(&q)
.try_resize(n.bits_precision())
.ok_or(Error::Internal)?;
m2.wrapping_add(&hq)
}
_ => {
// c^d (mod n)
pow_mod_params(&c, d, n_params)
}
};
match ir {
Some(ref ir) => {
// unblind
let res = unblind(&m, ir, n_params);
Ok(res)
}
None => Ok(m),
}
}
/// ⚠️ Performs raw RSA decryption with no padding.
///
/// Returns a plaintext `BoxedUint`. Performs RSA blinding if an `Rng` is passed. This will also
/// check for errors in the CRT computation.
///
/// `c` must have the same `bits_precision` as the RSA key modulus.
///
/// # ☢️ WARNING: HAZARDOUS API ☢️
///
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_decrypt_and_check<R: TryCryptoRng + ?Sized>(
priv_key: &impl PrivateKeyParts,
rng: Option<&mut R>,
c: &BoxedUint,
) -> Result<BoxedUint> {
let m = rsa_decrypt(rng, priv_key, c)?;
// In order to defend against errors in the CRT computation, m^e is
// calculated, which should match the original ciphertext.
let check = rsa_encrypt(priv_key, &m)?;
if c != &check {
return Err(Error::Internal);
}
Ok(m)
}
/// Returns the blinded c, along with the unblinding factor.
fn blind<R: TryCryptoRng + ?Sized, K: PublicKeyParts>(
rng: &mut R,
key: &K,
c: &BoxedUint,
n_params: &BoxedMontyParams,
) -> Result<(BoxedUint, BoxedUint)> {
// Blinding involves multiplying c by r^e.
// Then the decryption operation performs (m^e * r^e)^d mod n
// which equals mr mod n. The factor of r can then be removed
// by multiplying by the multiplicative inverse of r.
debug_assert_eq!(&key.n().clone().get(), n_params.modulus());
let bits = key.n_bits_precision();
let mut r: BoxedUint = BoxedUint::zero_with_precision(bits);
let mut ir: Option<BoxedUint> = None;
while ir.is_none() {
r = BoxedUint::try_random_mod_vartime(rng, key.n()).map_err(|_| Error::Rng)?;
// r^-1 (mod n)
ir = r.invert_mod(key.n()).into();
}
let blinded = {
// r^e (mod n)
let e = key.e();
let mut rpowe = pow_mod_params_vartime_exp_bits(&r, e, e.bits(), n_params);
// c * r^e (mod n)
let c = c.mul_mod(&rpowe, n_params.modulus().as_nz_ref());
rpowe.zeroize();
c
};
let ir = ir.expect("loop exited");
debug_assert_eq!(blinded.bits_precision(), bits);
debug_assert_eq!(ir.bits_precision(), bits);
Ok((blinded, ir))
}
/// Given an m and unblinding factor, unblind the m.
fn unblind(m: &BoxedUint, unblinder: &BoxedUint, n_params: &BoxedMontyParams) -> BoxedUint {
// m * r^-1 (mod n)
debug_assert_eq!(
m.bits_precision(),
unblinder.bits_precision(),
"invalid unblinder"
);
debug_assert_eq!(
m.bits_precision(),
n_params.bits_precision(),
"invalid n_params"
);
m.mul_mod(unblinder, n_params.modulus().as_nz_ref())
}
/// Computes `base.pow_mod(exp, n)` with precomputed `n_params`.
fn pow_mod_params(base: &BoxedUint, exp: &BoxedUint, n_params: &BoxedMontyParams) -> BoxedUint {
let base = reduce_vartime(base, n_params);
base.pow(exp).retrieve()
}
/// Computes `base.pow_mod(exp, n)` with a bounded exponent and precomputed `n_params`.
///
/// The exponent bit length `exp_bits` may be leaked in the time pattern.
fn pow_mod_params_vartime_exp_bits(
base: &BoxedUint,
exp: &BoxedUint,
exp_bits: u32,
n_params: &BoxedMontyParams,
) -> BoxedUint {
let base = reduce_vartime(base, n_params);
base.pow_bounded_exp(exp, exp_bits).retrieve()
}
fn reduce_vartime(n: &BoxedUint, p: &BoxedMontyParams) -> BoxedMontyForm {
let modulus = p.modulus().as_nz_ref().clone();
let n_reduced = n.rem_vartime(&modulus).resize_unchecked(p.bits_precision());
BoxedMontyForm::new(n_reduced, p)
}
/// The following (deterministic) algorithm also recovers the prime factors `p` and `q` of a modulus `n`, given the
/// public exponent `e` and private exponent `d` using the method described in
/// [NIST 800-56B Appendix C.2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf).
pub fn recover_primes(
n: &NonZero<BoxedUint>,
e: &BoxedUint,
d: &BoxedUint,
) -> Result<(BoxedUint, BoxedUint)> {
// Check precondition
// Note: because e is at most u64::MAX, it is already
// known to be < 2**256
if e <= &BoxedUint::from(2u64.pow(16)) {
return Err(Error::InvalidArguments);
}
// 1. Let a = (de 1) × GCD(n 1, de 1).
let bits = d.bits_precision() * 2;
let one = BoxedUint::one_with_precision(bits);
let e = e.resize_unchecked(d.bits_precision());
let d = d.resize_unchecked(d.bits_precision());
let n = n.resize_unchecked(bits);
let a1 = d.concatenating_mul(&e) - &one;
let a2 = (n.as_ref() - &one).gcd(&a1);
let a = a1.concatenating_mul(&a2);
let n = n.resize_unchecked(a.bits_precision());
// 2. Let m = floor(a /n) and r = a m n, so that a = m n + r and 0 ≤ r < n.
let m = &a / &n;
let r = a - m.concatenating_mul(&*n);
let n = n.get();
// 3. Let b = ( (n r)/(m + 1) ) + 1; if b is not an integer or b^2 ≤ 4n, then output an error indicator,
// and exit without further processing.
let modulus_check = (&n - &r) % NonZero::new(&m + &one).expect("adding 1");
if (!modulus_check.is_zero()).into() {
return Err(Error::InvalidArguments);
}
let b = ((&n - &r) / NonZero::new(&m + &one).expect("adding one")) + one;
let four = BoxedUint::from(4u32);
let four_n = n.concatenating_mul(&four);
let b_squared = b.concatenating_square();
if b_squared <= four_n {
return Err(Error::InvalidArguments);
}
let b_squared_minus_four_n = b_squared - four_n;
// 4. Let ϒ be the positive square root of b^2 4n; if ϒ is not an integer,
// then output an error indicator, and exit without further processing.
let y = b_squared_minus_four_n.floor_sqrt();
let y_squared = y.concatenating_square();
let sqrt_is_whole_number = y_squared == b_squared_minus_four_n;
if !sqrt_is_whole_number {
return Err(Error::InvalidArguments);
}
let bits = core::cmp::max(b.bits_precision(), y.bits_precision());
let two = NonZero::new(BoxedUint::from(2u64))
.expect("2 is non zero")
.resize_unchecked(bits);
let p = (&b + &y) / &two;
let q = (b - y) / two;
Ok((p, q))
}
/// Compute the modulus of a key from its primes.
pub(crate) fn compute_modulus(primes: &[BoxedUint]) -> Odd<BoxedUint> {
let mut primes = primes.iter();
let mut out = primes.next().expect("must at least be one prime").clone();
for p in primes {
out = out.concatenating_mul(&p);
}
Odd::new(out).expect("modulus must be odd")
}
/// Compute the private exponent from its primes (p and q) and public exponent
/// This uses Euler's totient function
#[inline]
pub(crate) fn compute_private_exponent_euler_totient(
primes: &[BoxedUint],
exp: &BoxedUint,
) -> Result<BoxedUint> {
if primes.len() < 2 {
return Err(Error::InvalidPrime);
}
let bits = primes[0].bits_precision();
let mut totient = BoxedUint::one_with_precision(bits);
for prime in primes {
totient = totient.concatenating_mul(&(prime - &BoxedUint::one()));
}
let exp = exp.resize_unchecked(totient.bits_precision());
// NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so.
// This ensures that `exp` is not a factor of any `(prime - 1)`.
let totient = NonZero::new(totient).expect("known");
match exp.invert_mod(&totient).into_option() {
Some(res) => Ok(res),
None => Err(Error::InvalidPrime),
}
}
/// Compute the private exponent from its primes (p and q) and public exponent
///
/// This is using the method defined by
/// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47).
/// (Carmichael function)
///
/// FIPS 186-4 **requires** the private exponent to be less than λ(n), which would
/// make Euler's totiem unreliable.
#[inline]
pub(crate) fn compute_private_exponent_carmicheal(
p: &BoxedUint,
q: &BoxedUint,
exp: &BoxedUint,
) -> Result<BoxedUint> {
let one = BoxedUint::one();
let p1 = p - &one;
let q1 = q - &one;
// LCM inlined
let gcd = p1.gcd(&q1);
let lcm = (p1 / NonZero::new(gcd).expect("gcd is non zero")).concatenating_mul(&q1);
let exp = exp.resize_unchecked(lcm.bits_precision());
if let Some(d) = exp.invert_mod(&NonZero::new(lcm).expect("non zero")).into() {
Ok(d)
} else {
// `exp` evenly divides `lcm`
Err(Error::InvalidPrime)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "private-key-operations-disabled")]
#[test]
fn private_operations_are_disabled_before_exponentiation() {
use crate::{dummy_rng::DummyRng, pkcs8::DecodePrivateKey, RsaPrivateKey};
let private_key = RsaPrivateKey::from_pkcs8_der(include_bytes!(
"../../tests/examples/pkcs8/rsa2048-priv.der"
))
.unwrap();
let message = BoxedUint::from(42u64).resize_unchecked(private_key.n_bits_precision());
let ciphertext = rsa_encrypt(&private_key, &message).unwrap();
assert_eq!(
rsa_decrypt::<DummyRng>(None, &private_key, &ciphertext),
Err(Error::Decryption)
);
}
#[test]
fn recover_primes_works() {
let bits = 2048;
let n = BoxedUint::from_be_hex(
concat!(
"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222",
"b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e",
"74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb",
"28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7",
"ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078c",
"c780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2",
"e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf67",
"76fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b"
),
bits,
)
.unwrap();
let e = BoxedUint::from(65_537u64);
let d = BoxedUint::from_be_hex(
concat!(
"c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d7395832",
"0dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92f",
"ae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552",
"e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f",
"54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057",
"fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0",
"c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd87",
"7d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341"
),
bits,
)
.unwrap();
let p = BoxedUint::from_be_hex(
concat!(
"f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd",
"624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07b",
"d405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159",
"067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61"
),
bits / 2,
)
.unwrap();
let q = BoxedUint::from_be_hex(
concat!(
"da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7",
"f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b",
"961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909",
"ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b"
),
bits / 2,
)
.unwrap();
let (mut p1, mut q1) = recover_primes(&NonZero::new(n).unwrap(), &e, &d).unwrap();
if p1 < q1 {
std::mem::swap(&mut p1, &mut q1);
}
assert_eq!(p, p1);
assert_eq!(q, q1);
}
}
+24
View File
@@ -0,0 +1,24 @@
use core::convert::Infallible;
use rand_core::{TryCryptoRng, TryRng};
/// This is a dummy RNG for cases when we need a concrete RNG type
/// which does not get used.
#[derive(Copy, Clone)]
pub(crate) struct DummyRng;
impl TryRng for DummyRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
unimplemented!();
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
unimplemented!();
}
fn try_fill_bytes(&mut self, _: &mut [u8]) -> Result<(), Self::Error> {
unimplemented!();
}
}
impl TryCryptoRng for DummyRng {}
+240
View File
@@ -0,0 +1,240 @@
//! PKCS#1 and PKCS#8 encoding support.
//!
//! Note: PKCS#1 support is achieved through a blanket impl of the
//! `pkcs1` crate's traits for types which impl the `pkcs8` crate's traits.
#![cfg(feature = "encoding")]
use crate::{
traits::{PrivateKeyParts, PublicKeyParts},
RsaPrivateKey, RsaPublicKey,
};
use core::convert::{TryFrom, TryInto};
use crypto_bigint::{BoxedUint, NonZero, Resize};
use pkcs8::{
der::{asn1::OctetStringRef, Decode},
Document, EncodePrivateKey, EncodePublicKey, ObjectIdentifier, SecretDocument,
};
use zeroize::Zeroizing;
/// ObjectID for the RSA PSS keys
pub const ID_RSASSA_PSS: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");
// PKCS#1
fn uint_from_slice(data: &[u8], bits: u32) -> pkcs1::Result<BoxedUint> {
BoxedUint::from_be_slice(data, bits).map_err(|_| pkcs1::Error::KeyMalformed)
}
impl pkcs1::DecodeRsaPrivateKey for RsaPrivateKey {
fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
pkcs1::RsaPrivateKey::from_der(bytes)?.try_into()
}
}
impl pkcs1::DecodeRsaPublicKey for RsaPublicKey {
fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
pkcs1::RsaPublicKey::from_der(bytes)?.try_into()
}
}
impl TryFrom<pkcs1::RsaPrivateKey<'_>> for RsaPrivateKey {
type Error = pkcs1::Error;
fn try_from(pkcs1_key: pkcs1::RsaPrivateKey<'_>) -> pkcs1::Result<RsaPrivateKey> {
use pkcs1::Error::KeyMalformed;
// Multi-prime RSA keys not currently supported
if pkcs1_key.version() != pkcs1::Version::TwoPrime {
return Err(pkcs1::Error::Version);
}
let bits = u32::try_from(pkcs1_key.modulus.as_bytes().len()).map_err(|_| KeyMalformed)? * 8;
let n = uint_from_slice(pkcs1_key.modulus.as_bytes(), bits)?;
let bits_e = u32::try_from(pkcs1_key.public_exponent.as_bytes().len())
.map_err(|_| pkcs1::Error::KeyMalformed)?
* 8;
let e = uint_from_slice(pkcs1_key.public_exponent.as_bytes(), bits_e)?;
let e = Option::from(e).ok_or(KeyMalformed)?;
let d = uint_from_slice(pkcs1_key.private_exponent.as_bytes(), bits)?;
let prime1 = uint_from_slice(pkcs1_key.prime1.as_bytes(), bits)?;
let prime2 = uint_from_slice(pkcs1_key.prime2.as_bytes(), bits)?;
let primes = vec![prime1, prime2];
RsaPrivateKey::from_components(n, e, d, primes).map_err(|_| KeyMalformed)
}
}
impl TryFrom<pkcs1::RsaPublicKey<'_>> for RsaPublicKey {
type Error = pkcs1::Error;
fn try_from(pkcs1_key: pkcs1::RsaPublicKey<'_>) -> pkcs1::Result<Self> {
use pkcs1::Error::KeyMalformed;
let bits = u32::try_from(pkcs1_key.modulus.as_bytes().len()).map_err(|_| KeyMalformed)? * 8;
let n = uint_from_slice(pkcs1_key.modulus.as_bytes(), bits)?;
let bits_e = u32::try_from(pkcs1_key.public_exponent.as_bytes().len())
.map_err(|_| KeyMalformed)?
* 8;
let e = uint_from_slice(pkcs1_key.public_exponent.as_bytes(), bits_e)?;
RsaPublicKey::new(n, e).map_err(|_| KeyMalformed)
}
}
impl pkcs1::EncodeRsaPrivateKey for RsaPrivateKey {
fn to_pkcs1_der(&self) -> pkcs1::Result<SecretDocument> {
// Check if the key is multi prime
if self.primes.len() > 2 {
return Err(pkcs1::Error::Crypto);
}
let modulus = self.n().to_be_bytes();
let public_exponent = self.e().to_be_bytes();
let private_exponent = Zeroizing::new(self.d().to_be_bytes());
let prime1 = Zeroizing::new(self.primes[0].to_be_bytes());
let prime2 = Zeroizing::new(self.primes[1].to_be_bytes());
let bits = self.d().bits_precision();
debug_assert!(bits >= self.primes[0].bits_vartime());
debug_assert!(bits >= self.primes[1].bits_vartime());
let exponent1 = Zeroizing::new(
(self.d()
% NonZero::new((&self.primes[0]).resize_unchecked(bits) - &BoxedUint::one())
.unwrap())
.to_be_bytes(),
);
let exponent2 = Zeroizing::new(
(self.d()
% NonZero::new((&self.primes[1]).resize_unchecked(bits) - &BoxedUint::one())
.unwrap())
.to_be_bytes(),
);
let coefficient = Zeroizing::new(
self.crt_coefficient()
.ok_or(pkcs1::Error::Crypto)?
.to_be_bytes(),
);
Ok(SecretDocument::encode_msg(&pkcs1::RsaPrivateKey {
modulus: pkcs1::UintRef::new(&modulus)?,
public_exponent: pkcs1::UintRef::new(&public_exponent)?,
private_exponent: pkcs1::UintRef::new(&private_exponent)?,
prime1: pkcs1::UintRef::new(&prime1)?,
prime2: pkcs1::UintRef::new(&prime2)?,
exponent1: pkcs1::UintRef::new(&exponent1)?,
exponent2: pkcs1::UintRef::new(&exponent2)?,
coefficient: pkcs1::UintRef::new(&coefficient)?,
other_prime_infos: None,
})?)
}
}
impl pkcs1::EncodeRsaPublicKey for RsaPublicKey {
fn to_pkcs1_der(&self) -> pkcs1::Result<Document> {
let modulus = self.n().to_be_bytes();
let public_exponent = self.e().to_be_bytes();
Ok(Document::encode_msg(&pkcs1::RsaPublicKey {
modulus: pkcs1::UintRef::new(&modulus)?,
public_exponent: pkcs1::UintRef::new(&public_exponent)?,
})?)
}
}
// PKCS#8
/// Verify that the `AlgorithmIdentifier` for a key is correct.
pub(crate) fn verify_algorithm_id(algorithm: &spki::AlgorithmIdentifierRef) -> spki::Result<()> {
match algorithm.oid {
pkcs1::ALGORITHM_OID => {
if algorithm.parameters_any()? != pkcs8::der::asn1::Null.into() {
return Err(spki::Error::KeyMalformed);
}
}
ID_RSASSA_PSS => {
if algorithm.parameters.is_some() {
return Err(spki::Error::KeyMalformed);
}
}
_ => return Err(spki::Error::OidUnknown { oid: algorithm.oid }),
};
Ok(())
}
impl TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for RsaPrivateKey {
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
verify_algorithm_id(&private_key_info.algorithm)?;
pkcs1::RsaPrivateKey::try_from(private_key_info.private_key)
.and_then(TryInto::try_into)
.map_err(pkcs1_error_to_pkcs8)
}
}
impl TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for RsaPublicKey {
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
use spki::Error::KeyMalformed;
verify_algorithm_id(&spki.algorithm)?;
pkcs1::RsaPublicKey::try_from(spki.subject_public_key.as_bytes().ok_or(KeyMalformed)?)
.and_then(TryInto::try_into)
.map_err(pkcs1_error_to_spki)
}
}
impl EncodePrivateKey for RsaPrivateKey {
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
let private_key =
pkcs1::EncodeRsaPrivateKey::to_pkcs1_der(self).map_err(pkcs1_error_to_pkcs8)?;
pkcs8::PrivateKeyInfoRef::new(
pkcs1::ALGORITHM_ID,
OctetStringRef::new(private_key.as_bytes())?,
)
.try_into()
}
}
impl EncodePublicKey for RsaPublicKey {
fn to_public_key_der(&self) -> spki::Result<Document> {
let subject_public_key =
pkcs1::EncodeRsaPublicKey::to_pkcs1_der(self).map_err(pkcs1_error_to_spki)?;
pkcs8::SubjectPublicKeyInfoRef {
algorithm: pkcs1::ALGORITHM_ID,
subject_public_key: pkcs8::der::asn1::BitStringRef::new(
0,
subject_public_key.as_ref(),
)?,
}
.try_into()
}
}
/// Convert `pkcs1::Result` to `pkcs8::Result`.
fn pkcs1_error_to_pkcs8(error: pkcs1::Error) -> pkcs8::Error {
match error {
pkcs1::Error::Asn1(e) => pkcs8::Error::Asn1(e),
_ => pkcs8::KeyError::Invalid.into(),
}
}
/// Convert `pkcs1::Result` to `spki::Result`.
fn pkcs1_error_to_spki(error: pkcs1::Error) -> spki::Error {
match error {
pkcs1::Error::Asn1(e) => spki::Error::Asn1(e),
_ => spki::Error::KeyMalformed,
}
}
+141
View File
@@ -0,0 +1,141 @@
//! Error types.
/// Alias for [`core::result::Result`] with the `rsa` crate's [`Error`] type.
pub type Result<T> = core::result::Result<T, Error>;
/// Error types
#[derive(Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Invalid padding scheme.
InvalidPaddingScheme,
/// Decryption error.
Decryption,
/// Verification error.
Verification,
/// Message too long.
MessageTooLong,
/// Input must be hashed.
InputNotHashed,
/// Number of primes must be 2 or greater.
NprimesTooSmall,
/// Too few primes of a given length to generate an RSA key.
TooFewPrimes,
/// Invalid prime value.
InvalidPrime,
/// Invalid modulus.
InvalidModulus,
/// Invalid exponent.
InvalidExponent,
/// Invalid coefficient.
InvalidCoefficient,
/// Modulus too small.
ModulusTooSmall,
/// Modulus too large.
ModulusTooLarge,
/// Public exponent too small.
PublicExponentTooSmall,
/// Public exponent too large.
PublicExponentTooLarge,
/// PKCS#1 error.
#[cfg(feature = "encoding")]
Pkcs1(pkcs1::Error),
/// PKCS#8 error.
#[cfg(feature = "encoding")]
Pkcs8(pkcs8::Error),
/// Internal error.
Internal,
/// Label too long.
LabelTooLong,
/// Invalid padding length.
InvalidPadLen,
/// Invalid arguments.
InvalidArguments,
/// Decoding error.
Decode(crypto_bigint::DecodeError),
/// Random number generator error.
Rng,
}
impl core::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Error::InvalidPaddingScheme => write!(f, "invalid padding scheme"),
Error::Decryption => write!(f, "decryption error"),
Error::Verification => write!(f, "verification error"),
Error::MessageTooLong => write!(f, "message too long"),
Error::InputNotHashed => write!(f, "input must be hashed"),
Error::NprimesTooSmall => write!(f, "nprimes must be >= 2"),
Error::TooFewPrimes => {
write!(f, "too few primes of given length to generate an RSA key")
}
Error::InvalidPrime => write!(f, "invalid prime value"),
Error::InvalidModulus => write!(f, "invalid modulus"),
Error::InvalidExponent => write!(f, "invalid exponent"),
Error::InvalidCoefficient => write!(f, "invalid coefficient"),
Error::ModulusTooSmall => write!(f, "modulus too small"),
Error::ModulusTooLarge => write!(f, "modulus too large"),
Error::PublicExponentTooSmall => write!(f, "public exponent too small"),
Error::PublicExponentTooLarge => write!(f, "public exponent too large"),
#[cfg(feature = "encoding")]
Error::Pkcs1(err) => write!(f, "{}", err),
#[cfg(feature = "encoding")]
Error::Pkcs8(err) => write!(f, "{}", err),
Error::Internal => write!(f, "internal error"),
Error::LabelTooLong => write!(f, "label too long"),
Error::InvalidPadLen => write!(f, "invalid padding length"),
Error::InvalidArguments => write!(f, "invalid arguments"),
Error::Decode(err) => write!(f, "{:?}", err),
Error::Rng => write!(f, "rng error"),
}
}
}
#[cfg(feature = "encoding")]
impl From<pkcs1::Error> for Error {
fn from(err: pkcs1::Error) -> Error {
Error::Pkcs1(err)
}
}
#[cfg(feature = "encoding")]
impl From<pkcs8::Error> for Error {
fn from(err: pkcs8::Error) -> Error {
Error::Pkcs8(err)
}
}
impl From<crypto_bigint::DecodeError> for Error {
fn from(err: crypto_bigint::DecodeError) -> Error {
Error::Decode(err)
}
}
impl From<Error> for signature::Error {
fn from(err: Error) -> Self {
Self::from_source(err)
}
}
+14
View File
@@ -0,0 +1,14 @@
//! ⚠️ Low-level "hazmat" RSA functions.
//!
//! # ☢️ WARNING: HAZARDOUS API ☢️
//!
//! This module holds functions that apply RSA's core encryption and decryption
//! primitives to raw data without adding or removing appropriate padding. A
//! well-reviewed padding scheme is crucial to the security of RSA, so there are
//! very few valid uses cases for this API. It's intended to be used for
//! implementing well-reviewed higher-level constructions.
//!
//! We do NOT recommend using it to implement any algorithm which has not
//! received extensive peer review by cryptographers.
pub use crate::algorithms::rsa::{rsa_decrypt, rsa_decrypt_and_check, rsa_encrypt};
+1305
View File
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
#![cfg_attr(not(test), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")]
#![warn(missing_docs)]
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
//! # Supported algorithms
//!
//! This crate supports several schemes described in [RFC8017]:
//!
//! - [OAEP encryption scheme](#oaep-encryption)
//! - [PKCS#1 v1.5 encryption scheme](#pkcs1-v15-encryption)
//! - [PKCS#1 v1.5 signature scheme](#pkcs1-v15-signatures)
//! - [PSS signature scheme](#pss-signatures)
//!
//! These schemes are described below.
//!
//! # Usage
//!
//! ## OAEP encryption
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::{RsaPrivateKey, RsaPublicKey, Oaep, sha2::Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let public_key = RsaPublicKey::from(&private_key);
//!
//! // Encrypt
//! let data = b"hello world";
//! let padding = Oaep::<Sha256>::new();
//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt");
//! assert_ne!(&data[..], &enc_data[..]);
//!
//! // Decrypt
//! let padding = Oaep::<Sha256>::new();
//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt");
//! assert_eq!(&data[..], &dec_data[..]);
//! ```
//!
//! ## PKCS#1 v1.5 encryption
//!
//! <div class="warning">
//! <b>Warning:</b>
//! See security notes in the <code><a href="./pkcs1v15/index.html">pkcs1v15</a></code> module.
//! </div>
//!
//! ```
//! use rsa::{RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let public_key = RsaPublicKey::from(&private_key);
//!
//! // Encrypt
//! let data = b"hello world";
//! let enc_data = public_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data[..]).expect("failed to encrypt");
//! assert_ne!(&data[..], &enc_data[..]);
//!
//! // Decrypt
//! let dec_data = private_key.decrypt(Pkcs1v15Encrypt, &enc_data).expect("failed to decrypt");
//! assert_eq!(&data[..], &dec_data[..]);
//! ```
//!
//! ## PKCS#1 v1.5 signatures
//!
//! <div class="warning">
//! <b>Warning:</b>
//! See security notes in the <code><a href="./pkcs1v15/index.html">pkcs1v15</a></code> module.
//! </div>
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::RsaPrivateKey;
//! use rsa::pkcs1v15::{SigningKey, VerifyingKey};
//! use rsa::signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let signing_key = SigningKey::<Sha256>::new(private_key);
//! let verifying_key = signing_key.verifying_key();
//!
//! // Sign
//! let data = b"hello world";
//! let signature = signing_key.sign_with_rng(&mut rng, data);
//! assert_ne!(signature.to_bytes().as_ref(), data.as_slice());
//!
//! // Verify
//! verifying_key.verify(data, &signature).expect("failed to verify");
//! ```
//!
//! ## PSS signatures
//!
//! Note: requires `sha2` feature of `rsa` crate is enabled.
//!
#![cfg_attr(feature = "sha2", doc = "```")]
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::RsaPrivateKey;
//! use rsa::pss::{BlindedSigningKey, VerifyingKey};
//! use rsa::signature::{Keypair,RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::rng();
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
//! let signing_key = BlindedSigningKey::<Sha256>::new(private_key);
//! let verifying_key = signing_key.verifying_key();
//!
//! // Sign
//! let data = b"hello world";
//! let signature = signing_key.sign_with_rng(&mut rng, data);
//! assert_ne!(signature.to_bytes().as_ref(), data);
//!
//! // Verify
//! verifying_key.verify(data, &signature).expect("failed to verify");
//! ```
//!
//! ## PKCS#1 RSA Key Encoding
//!
//! PKCS#1 supports a legacy format for encoding RSA keys as binary (DER) or
//! text (PEM) data.
//!
//! You can recognize PEM encoded PKCS#1 keys because they have "RSA * KEY" in
//! the type label, e.g.:
//!
//! ```text
//! -----BEGIN RSA PRIVATE KEY-----
//! ```
//!
//! Most modern applications use the newer PKCS#8 format instead (see below).
//!
//! The following traits can be used to decode/encode [`RsaPrivateKey`] and
//! [`RsaPublicKey`] as PKCS#1. Note that [`pkcs1`] is re-exported from the
//! toplevel of the `rsa` crate:
//!
//! - [`pkcs1::DecodeRsaPrivateKey`]: decode RSA private keys from PKCS#1
//! - [`pkcs1::EncodeRsaPrivateKey`]: encode RSA private keys to PKCS#1
//! - [`pkcs1::DecodeRsaPublicKey`]: decode RSA public keys from PKCS#1
//! - [`pkcs1::EncodeRsaPublicKey`]: encode RSA public keys to PKCS#1
//!
//! ### Example
//!
//! ```
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(all(feature = "encoding", feature = "std"))]
//! # {
//! use rsa::{RsaPublicKey, pkcs1::DecodeRsaPublicKey};
//!
//! let pem = "-----BEGIN RSA PUBLIC KEY-----
//! MIIBCgKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p78MZ
//! GsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC38Ff
//! wBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ67YGS
//! 0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J
//! 9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+CV+j
//! cFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQAB
//! -----END RSA PUBLIC KEY-----";
//!
//! let public_key = RsaPublicKey::from_pkcs1_pem(pem)?;
//! # }
//! # Ok(())
//! # }
//! ```
//!
//! ## PKCS#8 RSA Key Encoding
//!
//! PKCS#8 is a private key format with support for multiple algorithms.
//! Like PKCS#1, it can be encoded as binary (DER) or text (PEM).
//!
//! You can recognize PEM encoded PKCS#8 keys because they *don't* have
//! an algorithm name in the type label, e.g.:
//!
//! ```text
//! -----BEGIN PRIVATE KEY-----
//! ```
//!
//! The following traits can be used to decode/encode [`RsaPrivateKey`] and
//! [`RsaPublicKey`] as PKCS#8. Note that [`pkcs8`] is re-exported from the
//! toplevel of the `rsa` crate:
//!
//! - [`pkcs8::DecodePrivateKey`]: decode private keys from PKCS#8
//! - [`pkcs8::EncodePrivateKey`]: encode private keys to PKCS#8
//! - [`pkcs8::DecodePublicKey`]: decode public keys from PKCS#8
//! - [`pkcs8::EncodePublicKey`]: encode public keys to PKCS#8
//!
//! ### Example
//!
//! ```
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(all(feature = "encoding", feature = "std"))]
//! # {
//! use rsa::{RsaPublicKey, pkcs8::DecodePublicKey};
//!
//! let pem = "-----BEGIN PUBLIC KEY-----
//! MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsQsUV8QpqrygsY+2+JC
//! Q6Fw8/omM71IM2N/R8pPbzbgOl0p78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04
//! LHb2HJAYlz25+lN5cqfHAfa3fgmC38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrX
//! yrt8QxHJgvWO23ITrUVYszImbXQ67YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0N
//! fFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejI
//! n04APPKIjpMyQdnWlby7rNyQtE4+CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uH
//! LwIDAQAB
//! -----END PUBLIC KEY-----";
//!
//! let public_key = RsaPublicKey::from_public_key_pem(pem)?;
//! # }
//! # Ok(())
//! # }
//! ```
//!
//! [RFC8017]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
//!
// TODO(tarcieri): figure out why rustdoc isn't rendering these links correctly
//! [`pkcs8::DecodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.DecodePublicKey.html
//! [`pkcs8::EncodePublicKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.EncodePublicKey.html
#[cfg(doctest)]
pub struct ReadmeDoctests;
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub use crypto_bigint::BoxedUint;
pub use rand_core;
pub use signature;
mod algorithms;
pub mod errors;
pub mod oaep;
pub mod pkcs1v15;
pub mod pss;
pub mod traits;
mod dummy_rng;
mod encoding;
mod key;
#[cfg(feature = "encoding")]
pub use pkcs1;
#[cfg(feature = "encoding")]
pub use pkcs8;
#[cfg(feature = "sha2")]
pub use sha2;
pub use crate::{
errors::{Error, Result},
key::{RsaPrivateKey, RsaPublicKey},
oaep::Oaep,
pkcs1v15::{Pkcs1v15Encrypt, Pkcs1v15Sign},
pss::Pss,
traits::keys::CrtValue,
};
#[cfg(feature = "hazmat")]
pub mod hazmat;
+612
View File
@@ -0,0 +1,612 @@
//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#oaep-encryption).
mod decrypting_key;
mod encrypting_key;
pub use self::{decrypting_key::DecryptingKey, encrypting_key::EncryptingKey};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use crate::algorithms::oaep::*;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::key::{self, RsaPrivateKey, RsaPublicKey};
use crate::traits::{PaddingScheme, PublicKeyParts};
/// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
///
/// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`,
/// where `k` is the size of the RSA modulus.
/// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2).
/// - `label` is optional data that can be associated with the message.
///
/// The two hash functions can, but don't need to be the same.
///
/// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest).
/// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`.
pub struct Oaep<D, MGD = D> {
/// Digest type to use.
pub digest: D,
/// Digest to use for Mask Generation Function (MGF).
pub mgf_digest: MGD,
/// Optional label.
pub label: Option<Box<[u8]>>,
}
impl<D> Default for Oaep<D>
where
D: Digest + FixedOutputReset,
{
fn default() -> Self {
Self::new()
}
}
impl<D> Oaep<D>
where
D: Digest + FixedOutputReset,
{
/// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1.
///
/// # Example
/// ```
/// use sha1::Sha1;
/// use sha2::Sha256;
/// use rsa::{RsaPublicKey, Oaep};
/// use base64ct::{Base64, Encoding};
/// use crypto_bigint::BoxedUint;
///
/// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
/// let e_bytes = Base64::decode_vec("AQAB").unwrap();
/// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
/// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
///
/// let mut rng = rand::rng();
/// let key = RsaPublicKey::new(n, e).unwrap();
/// let padding = Oaep::<Sha256>::new();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new() -> Self {
Self {
digest: D::new(),
mgf_digest: D::new(),
label: None,
}
}
/// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for both the label and for MGF1.
pub fn new_with_label<S: Into<Box<[u8]>>>(label: S) -> Self {
Self {
digest: D::new(),
mgf_digest: D::new(),
label: Some(label.into()),
}
}
}
impl<D, MGD> Oaep<D, MGD>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
/// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1.
/// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`.
///
/// # Example
/// ```
/// use sha1::Sha1;
/// use sha2::Sha256;
/// use rsa::{RsaPublicKey, Oaep};
/// use base64ct::{Base64, Encoding};
/// use crypto_bigint::BoxedUint;
///
/// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
/// let e_bytes = Base64::decode_vec("AQAB").unwrap();
/// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
/// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
///
/// let mut rng = rand::rng();
/// let key = RsaPublicKey::new(n, e).unwrap();
/// let padding = Oaep::<Sha256, Sha1>::new_with_mgf_hash();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
/// ```
pub fn new_with_mgf_hash() -> Self {
Self {
digest: D::new(),
mgf_digest: MGD::new(),
label: None,
}
}
/// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for the label, and `U` as the hash function for MGF1.
pub fn new_with_mgf_hash_and_label<S: Into<Box<[u8]>>>(label: S) -> Self {
Self {
digest: D::new(),
mgf_digest: MGD::new(),
label: Some(label.into()),
}
}
}
impl<D, MGD> PaddingScheme for Oaep<D, MGD>
where
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
fn decrypt<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(
rng,
priv_key,
ciphertext,
&mut self.digest,
&mut self.mgf_digest,
self.label,
)
}
fn encrypt<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
encrypt(
rng,
pub_key,
msg,
&mut self.digest,
&mut self.mgf_digest,
self.label,
)
}
}
impl<D, MGD> fmt::Debug for Oaep<D, MGD> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OAEP")
.field("digest", &"...")
.field("mgf_digest", &"...")
.field("label", &self.label)
.finish()
}
}
/// Encrypts the given message with RSA and the padding scheme from
/// [PKCS#1 OAEP].
///
/// The message must be no longer than the length of the public modulus minus
/// `2 + (2 * hash.size())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn encrypt<R, D, MGD>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
key::check_public(pub_key)?;
let em = oaep_encrypt(rng, msg, digest, mgf_digest, label, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
}
/// Encrypts the given message with RSA and the padding scheme from
/// [PKCS#1 OAEP].
///
/// The message must be no longer than the length of the public modulus minus
/// `2 + (2 * hash.size())`.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
fn encrypt_digest<R, D, MGD>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
key::check_public(pub_key)?;
let em = oaep_encrypt_digest::<_, D, MGD>(rng, msg, label, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP].
///
/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key.
///
/// See `decrypt_session_key` for a way of solving this problem.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn decrypt<R, D, MGD>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
digest: &mut D,
mgf_digest: &mut MGD,
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
MGD: Digest + FixedOutputReset,
{
if ciphertext.len() != priv_key.size() {
return Err(Error::Decryption);
}
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
oaep_decrypt(&mut em, digest, mgf_digest, label, priv_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP].
///
/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key.
///
/// See `decrypt_session_key` for a way of solving this problem.
///
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[inline]
fn decrypt_digest<R, D, MGD>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
label: Option<Box<[u8]>>,
) -> Result<Vec<u8>>
where
R: TryCryptoRng + ?Sized,
D: Digest,
MGD: Digest + FixedOutputReset,
{
key::check_public(priv_key)?;
if ciphertext.len() != priv_key.size() {
return Err(Error::Decryption);
}
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
oaep_decrypt_digest::<D, MGD>(&mut em, label, priv_key.size())
}
#[cfg(test)]
mod tests {
use crate::key::{RsaPrivateKey, RsaPublicKey};
use crate::oaep::{DecryptingKey, EncryptingKey, Oaep};
use crate::traits::PublicKeyParts;
use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor};
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand::rngs::ChaCha8Rng;
use rand_core::{Rng, SeedableRng};
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512};
use sha3::{Sha3_256, Sha3_384, Sha3_512};
fn get_private_key() -> RsaPrivateKey {
// -----BEGIN RSA PRIVATE KEY-----
// MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW
// icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL
// TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11
// xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb
// WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn
// debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI
// KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+
// eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI
// hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY
// 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd
// a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF
// PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5
// g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0
// 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI
// F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76
// CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm
// 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv
// CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg
// WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh
// CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j
// AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K
// /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs
// SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8
// hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu
// BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A==
// -----END RSA PRIVATE KEY-----
RsaPrivateKey::from_components(
BoxedUint::from_be_hex("d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 2048).unwrap(),
BoxedUint::from(65_537u64),
BoxedUint::from_be_hex("c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 2048).unwrap(),
vec![
BoxedUint::from_be_hex("f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61", 1024).unwrap(),
BoxedUint::from_be_hex("da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 1024).unwrap()
],
).unwrap()
}
#[test]
fn test_encrypt_decrypt_oaep() {
let priv_key = get_private_key();
do_test_encrypt_decrypt_oaep::<Sha1>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha224>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha256>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha384>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha512>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_256>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_384>(&priv_key);
do_test_encrypt_decrypt_oaep::<Sha3_512>(&priv_key);
do_test_oaep_with_different_hashes::<Sha1, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha224, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha512, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes::<Sha3_512, Sha1>(&priv_key);
}
fn get_label(rng: &mut ChaCha8Rng) -> Option<Box<[u8]>> {
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
if rng.next_u32() % 2 == 0 {
Some(buf.into())
} else {
None
}
}
fn do_test_encrypt_decrypt_oaep<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let padding = Oaep::<D>::new_with_label(label.clone());
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = Oaep::<D>::new();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let padding = if let Some(label) = label {
Oaep::<D>::new_with_label::<Box<[u8]>>(label)
} else {
Oaep::<D>::new()
};
let plaintext = if blind {
prk.decrypt(padding, &ciphertext).unwrap()
} else {
prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
fn do_test_oaep_with_different_hashes<
D: Digest + FixedOutputReset,
U: Digest + FixedOutputReset,
>(
prk: &RsaPrivateKey,
) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let padding = Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label.clone());
pub_key.encrypt(&mut rng, padding, &input).unwrap()
} else {
let padding = Oaep::<D, U>::new_with_mgf_hash();
pub_key.encrypt(&mut rng, padding, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let padding = if let Some(label) = label {
Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label)
} else {
Oaep::<D, U>::new_with_mgf_hash()
};
let plaintext = if blind {
prk.decrypt(padding, &ciphertext).unwrap()
} else {
prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
#[test]
fn test_decrypt_oaep_invalid_hash() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = (&priv_key).into();
let ciphertext = pub_key
.encrypt(&mut rng, Oaep::<Sha1>::new(), "a_plain_text".as_bytes())
.unwrap();
assert!(
priv_key
.decrypt_blinded(
&mut rng,
Oaep::<Sha1>::new_with_label::<_>("label".as_bytes()),
&ciphertext,
)
.is_err(),
"decrypt should have failed on hash verification"
);
}
#[test]
fn test_encrypt_decrypt_oaep_traits() {
let priv_key = get_private_key();
do_test_encrypt_decrypt_oaep_traits::<Sha1>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha224>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha256>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha384>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha512>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_256>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_384>(&priv_key);
do_test_encrypt_decrypt_oaep_traits::<Sha3_512>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha1, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha224, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha512, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_256, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_384, Sha1>(&priv_key);
do_test_oaep_with_different_hashes_traits::<Sha3_512, Sha1>(&priv_key);
}
fn do_test_encrypt_decrypt_oaep_traits<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
do_test_oaep_with_different_hashes_traits::<D, D>(prk);
}
fn do_test_oaep_with_different_hashes_traits<D: Digest, MGD: Digest + FixedOutputReset>(
prk: &RsaPrivateKey,
) {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = prk.size();
for i in 1..8 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let label = get_label(&mut rng);
let pub_key: RsaPublicKey = prk.into();
let ciphertext = if let Some(ref label) = label {
let encrypting_key =
EncryptingKey::<D, MGD>::new_with_label(pub_key, label.clone());
encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
} else {
let encrypting_key = EncryptingKey::<D, MGD>::new(pub_key);
encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
};
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1 << 31);
let decrypting_key = if let Some(ref label) = label {
DecryptingKey::<D, MGD>::new_with_label(prk.clone(), label.clone())
} else {
DecryptingKey::<D, MGD>::new(prk.clone())
};
let plaintext = if blind {
decrypting_key.decrypt(&ciphertext).unwrap()
} else {
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.unwrap()
};
assert_eq!(input, plaintext);
}
}
#[test]
fn test_decrypt_oaep_invalid_hash_traits() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = (&priv_key).into();
let encrypting_key = EncryptingKey::<Sha1>::new(pub_key);
let decrypting_key = DecryptingKey::<Sha1>::new_with_label(priv_key, "label".as_bytes());
let ciphertext = encrypting_key
.encrypt_with_rng(&mut rng, "a_plain_text".as_bytes())
.unwrap();
assert!(
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.is_err(),
"decrypt should have failed on hash verification"
);
}
}
+139
View File
@@ -0,0 +1,139 @@
use super::decrypt_digest;
use crate::{
dummy_rng::DummyRng,
traits::{Decryptor, RandomizedDecryptor},
Result, RsaPrivateKey,
};
use alloc::{boxed::Box, vec::Vec};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset};
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.1].
///
/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DecryptingKey<D, MGD = D>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
inner: RsaPrivateKey,
label: Option<Box<[u8]>>,
phantom: PhantomData<D>,
mg_phantom: PhantomData<MGD>,
}
impl<D, MGD> DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPrivateKey) -> Self {
Self {
inner: key,
label: None,
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
/// Create a new verifying key from an RSA public key using provided label
pub fn new_with_label<S: Into<Box<[u8]>>>(key: RsaPrivateKey, label: S) -> Self {
Self {
inner: key,
label: Some(label.into()),
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
}
impl<D, MGD> Decryptor for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
decrypt_digest::<DummyRng, D, MGD>(None, &self.inner, ciphertext, self.label.clone())
}
}
impl<D, MGD> RandomizedDecryptor for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt_digest::<_, D, MGD>(Some(rng), &self.inner, ciphertext, self.label.clone())
}
}
impl<D, MGD> ZeroizeOnDrop for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
}
impl<D, MGD> PartialEq for DecryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.label == other.label
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let decrypting_key = DecryptingKey::<Sha256>::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [
Token::Struct {
name: "DecryptingKey",
len: 4,
},
Token::Str("inner"),
Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab",
"240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf30205",
"00d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
)),
Token::Str("label"),
Token::None,
Token::Str("phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::Str("mg_phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::StructEnd,
];
assert_tokens(&decrypting_key.readable(), &tokens);
}
}
+111
View File
@@ -0,0 +1,111 @@
use super::encrypt_digest;
use crate::{traits::RandomizedEncryptor, Result, RsaPublicKey};
use alloc::{boxed::Box, vec::Vec};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset};
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.1].
///
/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EncryptingKey<D, MGD = D>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
inner: RsaPublicKey,
label: Option<Box<[u8]>>,
phantom: PhantomData<D>,
mg_phantom: PhantomData<MGD>,
}
impl<D, MGD> EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPublicKey) -> Self {
Self {
inner: key,
label: None,
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
/// Create a new verifying key from an RSA public key using provided label
pub fn new_with_label<S: Into<Box<[u8]>>>(key: RsaPublicKey, label: S) -> Self {
Self {
inner: key,
label: Some(label.into()),
phantom: Default::default(),
mg_phantom: Default::default(),
}
}
}
impl<D, MGD> RandomizedEncryptor for EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>> {
encrypt_digest::<_, D, MGD>(rng, &self.inner, msg, self.label.clone())
}
}
impl<D, MGD> PartialEq for EncryptingKey<D, MGD>
where
D: Digest,
MGD: Digest + FixedOutputReset,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.label == other.label
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key =
crate::RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let encrypting_key = EncryptingKey::<sha2::Sha256>::new(priv_key.to_public_key());
let tokens = [
Token::Struct {
name: "EncryptingKey",
len: 4,
},
Token::Str("inner"),
Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
),
Token::Str("label"),
Token::None,
Token::Str("phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::Str("mg_phantom"),
Token::UnitStruct {
name: "PhantomData",
},
Token::StructEnd,
];
assert_tokens(&encrypting_key.readable(), &tokens);
}
}
+675
View File
@@ -0,0 +1,675 @@
//! PKCS#1 v1.5 support as described in [RFC8017 § 8.2].
//!
//! <div class="warning">
//! <b>Warning</b>
//!
//! PKCS#1 v1.5 padding has a longstanding history of issues generally classed as
//! [Bleichenbacher Attacks] which were originally discovered in 1998 but keep reappearing in
//! various forms again and again over the course of decades, including most recently in the 2023
//! [Marvin Attack], which the `rsa` crate is [still vulnerable] to.
//!
//! These attacks can result in complete plaintext recovery for encryption, or signature forgery,
//! leading to a total failure of either confidentiality or integrity.
//!
//! Unless explicitly needed for compatibility reasons, we recommend against using PKCS#1 v1.5,
//! and suggest using [PSS][`super::pss`] or [OAEP][`super::oaep`] instead (if there is a
//! requirement to use RSA).
//! </div>
//!
//! [Bleichenbacher Attacks]: https://en.wikipedia.org/wiki/Adaptive_chosen-ciphertext_attack#Practical_attacks
//! [Marvin Attack]: https://people.redhat.com/~hkario/marvin/
//! [still vulnerable]: https://github.com/RustCrypto/RSA/issues/626
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pkcs1-v15-signatures).
//!
//! [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
mod decrypting_key;
mod encrypting_key;
mod signature;
mod signing_key;
mod verifying_key;
pub use self::{
decrypting_key::DecryptingKey, encrypting_key::EncryptingKey, signature::Signature,
signing_key::SigningKey, verifying_key::VerifyingKey,
};
use alloc::{boxed::Box, vec::Vec};
use const_oid::AssociatedOid;
use core::fmt::Debug;
use crypto_bigint::BoxedUint;
use digest::Digest;
use rand_core::TryCryptoRng;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::pkcs1v15::*;
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::key::{self, RsaPrivateKey, RsaPublicKey};
use crate::traits::{PaddingScheme, PublicKeyParts, SignatureScheme};
/// Encryption using PKCS#1 v1.5 padding.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Pkcs1v15Encrypt;
impl PaddingScheme for Pkcs1v15Encrypt {
fn decrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(rng, priv_key, ciphertext)
}
fn encrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
encrypt(rng, pub_key, msg)
}
}
/// `RSASSA-PKCS1-v1_5`: digital signatures using PKCS#1 v1.5 padding.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Pkcs1v15Sign {
/// Length of hash to use.
pub hash_len: Option<usize>,
/// Prefix.
pub prefix: Box<[u8]>,
}
impl Pkcs1v15Sign {
/// Create new PKCS#1 v1.5 padding for the given digest.
///
/// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid`
/// feature of the relevant digest crate.
pub fn new<D>() -> Self
where
D: Digest + AssociatedOid,
{
Self {
hash_len: Some(<D as Digest>::output_size()),
prefix: pkcs1v15_generate_prefix::<D>().into_boxed_slice(),
}
}
/// Create new PKCS#1 v1.5 padding for computing an unprefixed signature.
///
/// This sets `hash_len` to `None` and uses an empty `prefix`.
pub fn new_unprefixed() -> Self {
Self {
hash_len: None,
prefix: Box::new([]),
}
}
}
impl SignatureScheme for Pkcs1v15Sign {
fn sign<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
) -> Result<Vec<u8>> {
if let Some(hash_len) = self.hash_len {
if hashed.len() != hash_len {
return Err(Error::InputNotHashed);
}
}
sign(rng, priv_key, &self.prefix, hashed)
}
fn verify(self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> {
if let Some(hash_len) = self.hash_len {
if hashed.len() != hash_len {
return Err(Error::InputNotHashed);
}
}
verify(
pub_key,
self.prefix.as_ref(),
hashed,
&BoxedUint::from_be_slice_vartime(sig),
)
}
}
/// Encrypts the given message with RSA and the padding
/// scheme from PKCS#1 v1.5. The message must be no longer than the
/// length of the public modulus minus 11 bytes.
#[inline]
fn encrypt<R: TryCryptoRng + ?Sized>(
rng: &mut R,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>> {
key::check_public(pub_key)?;
let em = pkcs1v15_encrypt_pad(rng, msg, pub_key.size())?;
let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
}
/// Decrypts a plaintext using RSA and the padding scheme from PKCS#1 v1.5.
///
/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
///
/// Note that whether this function returns an error or not discloses secret
/// information. If an attacker can cause this function to run repeatedly and
/// learn whether each instance returned an error then they can decrypt and
/// forge signatures as if they had the private key. See
/// `decrypt_session_key` for a way of solving this problem.
#[inline]
fn decrypt<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
key::check_public(priv_key)?;
let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
let em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
pkcs1v15_encrypt_unpad(em, priv_key.size())
}
/// Calculates the signature of hashed using
/// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5. Note that `hashed` must
/// be the result of hashing the input message using the given hash
/// function. If hash is `None`, hashed is signed directly. This isn't
/// advisable except for interoperability.
///
/// If `rng` is not `None` then RSA blinding will be used to avoid timing
/// side-channel attacks.
///
/// This function is deterministic. Thus, if the set of possible
/// messages is small, an attacker may be able to build a map from
/// messages to signatures and identify the signed messages. As ever,
/// signatures provide authenticity, not confidentiality.
#[inline]
fn sign<R: TryCryptoRng + ?Sized>(
rng: Option<&mut R>,
priv_key: &RsaPrivateKey,
prefix: &[u8],
hashed: &[u8],
) -> Result<Vec<u8>> {
let em = pkcs1v15_sign_pad(prefix, hashed, priv_key.size())?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
uint_to_zeroizing_be_pad(rsa_decrypt_and_check(priv_key, rng, &em)?, priv_key.size())
}
/// Verifies an RSA PKCS#1 v1.5 signature.
#[inline]
fn verify(pub_key: &RsaPublicKey, prefix: &[u8], hashed: &[u8], sig: &BoxedUint) -> Result<()> {
let n = pub_key.n();
if sig >= n.as_ref() || sig.bits_precision() != pub_key.n_bits_precision() {
return Err(Error::Verification);
}
let em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
pkcs1v15_sign_unpad(prefix, hashed, &em, pub_key.size())
}
mod oid {
use const_oid::ObjectIdentifier;
/// A trait which associates an RSA-specific OID with a type.
pub trait RsaSignatureAssociatedOid {
/// The OID associated with this type.
const OID: ObjectIdentifier;
}
#[cfg(feature = "sha1")]
impl RsaSignatureAssociatedOid for sha1::Sha1 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.5");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha224 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.14");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha256 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha384 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12");
}
#[cfg(feature = "sha2")]
impl RsaSignatureAssociatedOid for sha2::Sha512 {
const OID: ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13");
}
}
pub use oid::RsaSignatureAssociatedOid;
#[cfg(test)]
mod tests {
use super::*;
use ::signature::{
hazmat::{PrehashSigner, PrehashVerifier},
DigestSigner, DigestVerifier, Keypair, RandomizedDigestSigner, RandomizedSigner,
SignatureEncoding, Signer, Verifier,
};
use base64ct::{Base64, Encoding};
use hex_literal::hex;
use rand::rngs::ChaCha8Rng;
use rand_core::{Rng, SeedableRng};
use rstest::rstest;
use sha1::{Digest, Sha1};
use sha2::Sha256;
use sha3::Sha3_256;
use crate::traits::{
Decryptor, EncryptingKeypair, PublicKeyParts, RandomizedDecryptor, RandomizedEncryptor,
};
use crate::{RsaPrivateKey, RsaPublicKey};
fn get_private_key() -> RsaPrivateKey {
// In order to generate new test vectors you'll need the PEM form of this key:
// -----BEGIN RSA PRIVATE KEY-----
// MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
// fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
// /ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
// RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
// EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
// IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
// tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
// -----END RSA PRIVATE KEY-----
RsaPrivateKey::from_components(
BoxedUint::from_be_hex("B2990F49C47DFA8CD400AE6A4D1B8A3B6A13642B23F28B003BFB97790ADE9A4CC82B8B2A81747DDEC08B6296E53A08C331687EF25C4BF4936BA1C0E6041E9D15", 512).unwrap(),
BoxedUint::from(65_537u64),
BoxedUint::from_be_hex("8ABD6A69F4D1A4B487F0AB8D7AAEFD38609405C999984E30F567E1E8AEEFF44E8B18BDB1EC78DFA31A55E32A48D7FB131F5AF1F44D7D6B2CED2A9DF5E5AE4535", 512).unwrap(),
vec![
BoxedUint::from_be_hex("DAB2F18048BAA68DE7DF04D2D35D5D80E60E2DFA42D50A9B04219032715E46B3", 256).unwrap(),
BoxedUint::from_be_hex("D10F2E66B1D0C13F10EF9927BF5324A379CA218146CBF9CAFC795221F16A3117", 256).unwrap()
],
).unwrap()
}
#[rstest]
#[case(
"gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==",
"x"
)]
#[case(
"Y7TOCSqofGhkRb+jaVRLzK8xw2cSo1IVES19utzv6hwvx+M8kFsoWQm5DzBeJCZTCVDPkTpavUuEbgp8hnUGDw==",
"testing."
)]
#[case(
"arReP9DJtEVyV2Dg3dDp4c/PSk1O6lxkoJ8HcFupoRorBZG+7+1fDAwT1olNddFnQMjmkb8vxwmNMoTAT/BFjQ==",
"testing.\n"
)]
#[case(
"WtaBXIoGC54+vH0NH0CHHE+dRDOsMc/6BrfFu2lEqcKL9+uDuWaf+Xj9mrbQCjjZcpQuX733zyok/jsnqe/Ftw==",
"01234567890123456789012345678901234567890123456789012"
)]
fn test_decrypt_pkcs1v15(#[case] ciphertext: &str, #[case] plaintext: &str) {
let priv_key = get_private_key();
let out = priv_key
.decrypt(Pkcs1v15Encrypt, &Base64::decode_vec(ciphertext).unwrap())
.unwrap();
assert_eq!(out, plaintext.as_bytes());
}
#[test]
fn test_encrypt_decrypt_pkcs1v15() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let k = priv_key.size();
for i in 1..100 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let pub_key: RsaPublicKey = priv_key.clone().into();
let ciphertext = encrypt(&mut rng, &pub_key, &input).unwrap();
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1u32 << 31);
let blinder = if blind { Some(&mut rng) } else { None };
let plaintext = decrypt(blinder, &priv_key, &ciphertext).unwrap();
assert_eq!(input, plaintext);
}
}
#[rstest]
#[case(
"gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==",
"x"
)]
#[case(
"Y7TOCSqofGhkRb+jaVRLzK8xw2cSo1IVES19utzv6hwvx+M8kFsoWQm5DzBeJCZTCVDPkTpavUuEbgp8hnUGDw==",
"testing."
)]
#[case(
"arReP9DJtEVyV2Dg3dDp4c/PSk1O6lxkoJ8HcFupoRorBZG+7+1fDAwT1olNddFnQMjmkb8vxwmNMoTAT/BFjQ==",
"testing.\n"
)]
#[case(
"WtaBXIoGC54+vH0NH0CHHE+dRDOsMc/6BrfFu2lEqcKL9+uDuWaf+Xj9mrbQCjjZcpQuX733zyok/jsnqe/Ftw==",
"01234567890123456789012345678901234567890123456789012"
)]
fn test_decrypt_pkcs1v15_traits(#[case] ciphertext: &str, #[case] plaintext: &str) {
let priv_key = get_private_key();
let decrypting_key = DecryptingKey::new(priv_key);
let out = decrypting_key
.decrypt(&Base64::decode_vec(ciphertext).unwrap())
.unwrap();
assert_eq!(out, plaintext.as_bytes());
}
#[test]
fn test_encrypt_decrypt_pkcs1v15_traits() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = get_private_key();
let k = priv_key.size();
let decrypting_key = DecryptingKey::new(priv_key);
for i in 1..100 {
let mut input = vec![0u8; i * 8];
rng.fill_bytes(&mut input);
if input.len() > k - 11 {
input = input[0..k - 11].to_vec();
}
let encrypting_key = decrypting_key.encrypting_key();
let ciphertext = encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap();
assert_ne!(input, ciphertext);
let blind: bool = rng.next_u32() < (1u32 << 31);
let plaintext = if blind {
decrypting_key
.decrypt_with_rng(&mut rng, &ciphertext)
.unwrap()
} else {
decrypting_key.decrypt(&ciphertext).unwrap()
};
assert_eq!(input, plaintext);
}
}
#[rstest]
#[case("Test.\n", hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"))
]
fn test_sign_pkcs1v15(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let digest = Sha1::digest(text.as_bytes()).to_vec();
let out = priv_key.sign(Pkcs1v15Sign::new::<Sha1>(), &digest).unwrap();
assert_ne!(out, digest);
assert_eq!(out, expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = priv_key
.sign_with_rng(&mut rng, Pkcs1v15Sign::new::<Sha1>(), &digest)
.unwrap();
assert_eq!(out2, expected);
}
#[rstest]
#[case("Test.\n", hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"))
]
fn test_sign_pkcs1v15_signer(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha1>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case("Test.\n", hex!(
"2ffae3f3e130287b3a1dcb320e46f52e8f3f7969b646932273a7e3a6f2a182ea"
"02d42875a7ffa4a148aa311f9e4b562e4e13a2223fb15f4e5bf5f2b206d9451b"))
]
fn test_sign_pkcs1v15_signer_sha2_256(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha256>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case("Test.\n", hex!(
"55e9fba3354dfb51d2c8111794ea552c86afc2cab154652c03324df8c2c51ba7"
"2ff7c14de59a6f9ba50d90c13a7537cc3011948369f1f0ec4a49d21eb7e723f9"))
]
fn test_sign_pkcs1v15_signer_sha3_256(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha3_256>::new(priv_key);
let out = signing_key.sign(text.as_bytes()).to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_with_rng(&mut rng, text.as_bytes())
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
)
)]
fn test_sign_pkcs1v15_digest_signer(#[case] text: &str, #[case] expected: [u8; 64]) {
let priv_key = get_private_key();
let signing_key = SigningKey::new(priv_key);
let mut digest = Sha1::new();
digest.update(text.as_bytes());
let out = signing_key
.sign_digest(|digest: &mut Sha1| digest.update(text.as_bytes()))
.to_bytes();
assert_ne!(out.as_ref(), text.as_bytes());
assert_ne!(out.as_ref(), &Sha1::digest(text.as_bytes()).to_vec());
assert_eq!(out.as_ref(), expected);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(text.as_bytes()))
.to_bytes();
assert_eq!(out2.as_ref(), expected);
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let digest = Sha1::digest(text.as_bytes()).to_vec();
let result = pub_key.verify(Pkcs1v15Sign::new::<Sha1>(), &digest, &sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::<Sha1>::new(pub_key);
let result = verifying_key.verify(
text.as_bytes(),
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true
)]
#[case(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false
)]
fn test_verify_pkcs1v15_digest_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::new(pub_key);
let result = verifying_key.verify_digest(
|digest: &mut Sha1| {
digest.update(text.as_bytes());
Ok(())
},
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[test]
fn test_unpadded_signature() {
let msg = b"Thu Dec 19 18:06:16 EST 2013\n";
let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap();
let priv_key = get_private_key();
let sig = priv_key.sign(Pkcs1v15Sign::new_unprefixed(), msg).unwrap();
assert_eq!(expected_sig, sig);
let pub_key: RsaPublicKey = priv_key.into();
pub_key
.verify(Pkcs1v15Sign::new_unprefixed(), msg, &sig)
.expect("failed to verify");
}
#[test]
fn test_unpadded_signature_hazmat() {
let msg = b"Thu Dec 19 18:06:16 EST 2013\n";
let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap();
let priv_key = get_private_key();
let signing_key = SigningKey::<Sha1>::new_unprefixed(priv_key);
let sig = signing_key
.sign_prehash(msg)
.expect("Failure during sign")
.to_bytes();
assert_eq!(sig.as_ref(), expected_sig);
let verifying_key = signing_key.verifying_key();
verifying_key
.verify_prehash(msg, &Signature::try_from(expected_sig.as_slice()).unwrap())
.expect("failed to verify");
}
}
+86
View File
@@ -0,0 +1,86 @@
use super::{decrypt, EncryptingKey};
use crate::{
dummy_rng::DummyRng,
traits::{Decryptor, EncryptingKeypair, RandomizedDecryptor},
Result, RsaPrivateKey,
};
use alloc::vec::Vec;
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.2].
///
/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DecryptingKey {
inner: RsaPrivateKey,
}
impl DecryptingKey {
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPrivateKey) -> Self {
Self { inner: key }
}
}
impl Decryptor for DecryptingKey {
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
decrypt::<DummyRng>(None, &self.inner, ciphertext)
}
}
impl RandomizedDecryptor for DecryptingKey {
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>> {
decrypt(Some(rng), &self.inner, ciphertext)
}
}
impl EncryptingKeypair for DecryptingKey {
type EncryptingKey = EncryptingKey;
fn encrypting_key(&self) -> EncryptingKey {
EncryptingKey {
inner: self.inner.clone().into(),
}
}
}
impl ZeroizeOnDrop for DecryptingKey {}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let decrypting_key = DecryptingKey::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [
Token::Struct {
name: "DecryptingKey",
len: 1,
},
Token::Str("inner"),
Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab",
"240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf30205",
"00d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f"
)),
Token::StructEnd,
];
assert_tokens(&decrypting_key.readable(), &tokens);
}
}
+58
View File
@@ -0,0 +1,58 @@
use super::encrypt;
use crate::{traits::RandomizedEncryptor, Result, RsaPublicKey};
use alloc::vec::Vec;
use rand_core::CryptoRng;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.2].
///
/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EncryptingKey {
pub(super) inner: RsaPublicKey,
}
impl EncryptingKey {
/// Create a new verifying key from an RSA public key.
pub fn new(key: RsaPublicKey) -> Self {
Self { inner: key }
}
}
impl RandomizedEncryptor for EncryptingKey {
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>> {
encrypt(rng, &self.inner, msg)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let encrypting_key = EncryptingKey::new(priv_key.to_public_key());
let tokens = [
Token::Struct {
name: "EncryptingKey",
len: 1,
},
Token::Str("inner"),
Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
),
Token::StructEnd,
];
assert_tokens(&encrypting_key.clone().readable(), &tokens);
}
}
+112
View File
@@ -0,0 +1,112 @@
//! `RSASSA-PKCS1-v1_5` signatures.
use alloc::boxed::Box;
use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex};
use crypto_bigint::BoxedUint;
use signature::SignatureEncoding;
#[cfg(feature = "serde")]
use serdect::serde::{de, Deserialize, Serialize};
#[cfg(feature = "encoding")]
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
};
/// `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub(super) inner: BoxedUint,
}
impl SignatureEncoding for Signature {
type Repr = Box<[u8]>;
}
#[cfg(feature = "encoding")]
impl SignatureBitStringEncoding for Signature {
fn to_bitstring(&self) -> DerResult<BitString> {
BitString::new(0, self.to_vec())
}
}
impl TryFrom<&[u8]> for Signature {
type Error = signature::Error;
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
// TODO(tarcieri): max length restriction? (#350)
let inner = BoxedUint::from_be_slice_vartime(bytes);
Ok(Self { inner })
}
}
impl From<Signature> for Box<[u8]> {
fn from(signature: Signature) -> Box<[u8]> {
signature.inner.to_be_bytes()
}
}
impl LowerHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
for byte in self.to_bytes().iter() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl UpperHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
for byte in self.to_bytes().iter() {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", self)
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
serdect::slice::deserialize_hex_or_bin_vec(deserializer)?
.as_slice()
.try_into()
.map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
use super::*;
use serde_test::{assert_tokens, Configure, Token};
let signature = Signature {
inner: BoxedUint::from(42u32),
};
let tokens = [Token::Str("000000000000002a")];
assert_tokens(&signature.readable(), &tokens);
}
}
+358
View File
@@ -0,0 +1,358 @@
use super::{pkcs1v15_generate_prefix, sign, Signature, VerifyingKey};
use crate::{dummy_rng::DummyRng, Result, RsaPrivateKey};
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use core::marker::PhantomData;
use digest::{Digest, FixedOutput, HashMarker, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::PrehashSigner, DigestSigner, Keypair, MultipartSigner, RandomizedDigestSigner,
RandomizedMultipartSigner, RandomizedSigner, Signer,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "encoding")]
use {
super::oid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
SignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
/// Signing key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug, Clone)]
pub struct SigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
prefix: Vec<u8>,
phantom: PhantomData<D>,
}
impl<D> SigningKey<D>
where
D: Digest + AssociatedOid,
{
/// Create a new signing key with a prefix for the digest `D`.
pub fn new(key: RsaPrivateKey) -> Self {
Self {
inner: key,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
}
}
/// Generate a new signing key with a prefix for the digest `D`.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
})
}
}
impl<D> SigningKey<D>
where
D: Digest,
{
/// Create a new signing key from the give RSA private key with an empty prefix.
///
/// ## Note: unprefixed signatures are uncommon
///
/// In most cases you'll want to use [`SigningKey::new`].
pub fn new_unprefixed(key: RsaPrivateKey) -> Self {
Self {
inner: key,
prefix: Vec::new(),
phantom: Default::default(),
}
}
/// Generate a new signing key with an empty prefix.
pub fn random_unprefixed<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
prefix: Vec::new(),
phantom: Default::default(),
})
}
}
//
// `*Signer` trait impls
//
impl<D> DigestSigner<D, Signature> for SigningKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn try_sign_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize_fixed())?
.as_slice()
.try_into()
}
}
impl<D> PrehashSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
sign::<DummyRng>(None, &self.inner, &self.prefix, prehash)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedDigestSigner<D, Signature> for SigningKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign(
Some(rng),
&self.inner,
&self.prefix,
&digest.finalize_fixed(),
)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(rng, &[msg])
}
}
impl<D> RandomizedMultipartSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter().for_each(|slice| digest.update(slice));
sign(Some(rng), &self.inner, &self.prefix, &digest.finalize())?
.as_slice()
.try_into()
}
}
impl<D> Signer<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> {
self.try_multipart_sign(&[msg])
}
}
impl<D> MultipartSigner<Signature> for SigningKey<D>
where
D: Digest,
{
fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter().for_each(|slice| digest.update(slice));
sign::<DummyRng>(None, &self.inner, &self.prefix, &digest.finalize())?
.as_slice()
.try_into()
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for SigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for SigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<SigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: SigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for SigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
prefix: self.prefix.clone(),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> SignatureAlgorithmIdentifier for SigningKey<D>
where
D: Digest + oid::RsaSignatureAssociatedOid,
{
type Params = AnyRef<'static>;
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
AlgorithmIdentifierRef {
oid: D::OID,
parameters: Some(AnyRef::NULL),
};
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
private_key_info
.algorithm
.assert_algorithm_oid(pkcs1::ALGORITHM_OID)?;
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for SigningKey<D> where D: Digest {}
impl<D> PartialEq for SigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.prefix == other.prefix
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for SigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serdect::serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let signing_key = SigningKey::<Sha256>::new(priv_key);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f",
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+270
View File
@@ -0,0 +1,270 @@
use super::{pkcs1v15_generate_prefix, verify, Signature};
use crate::RsaPublicKey;
use alloc::vec::Vec;
use const_oid::AssociatedOid;
use core::marker::PhantomData;
use digest::{Digest, FixedOutput, HashMarker, Update};
use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
#[cfg(feature = "encoding")]
use {
super::oid,
spki::{
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier, Document,
EncodePublicKey, SignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
serdect::serde::{de, ser, Deserialize, Serialize},
spki::DecodePublicKey,
};
/// Verifying key for `RSASSA-PKCS1-v1_5` signatures as described in [RFC8017 § 8.2].
///
/// [RFC8017 § 8.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.2
#[derive(Debug)]
pub struct VerifyingKey<D>
where
D: Digest,
{
pub(super) inner: RsaPublicKey,
pub(super) prefix: Vec<u8>,
pub(super) phantom: PhantomData<D>,
}
impl<D> VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
/// Create a new verifying key with a prefix for the digest `D`.
pub fn new(key: RsaPublicKey) -> Self {
Self {
inner: key,
prefix: pkcs1v15_generate_prefix::<D>(),
phantom: Default::default(),
}
}
}
impl<D> VerifyingKey<D>
where
D: Digest,
{
/// Create a new verifying key from an RSA public key with an empty prefix.
///
/// ## Note: unprefixed signatures are uncommon
///
/// In most cases you'll want to use [`VerifyingKey::new`] instead.
pub fn new_unprefixed(key: RsaPublicKey) -> Self {
Self {
inner: key,
prefix: Vec::new(),
phantom: Default::default(),
}
}
}
//
// `*Verifier` trait impls
//
impl<D> DigestVerifier<D, Signature> for VerifyingKey<D>
where
D: Default + FixedOutput + HashMarker + Update,
{
fn verify_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
signature: &Signature,
) -> signature::Result<()> {
let mut digest = D::default();
f(&mut digest)?;
verify(
&self.inner,
&self.prefix,
&digest.finalize_fixed(),
&signature.inner,
)
.map_err(|e| e.into())
}
}
impl<D> PrehashVerifier<Signature> for VerifyingKey<D>
where
D: Digest,
{
fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> {
verify(&self.inner, &self.prefix, prehash, &signature.inner).map_err(|e| e.into())
}
}
impl<D> Verifier<Signature> for VerifyingKey<D>
where
D: Digest,
{
fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> {
verify(
&self.inner,
&self.prefix.clone(),
&D::digest(msg),
&signature.inner,
)
.map_err(|e| e.into())
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPublicKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
// Implemented manually so we don't have to bind D with Clone
impl<D> Clone for VerifyingKey<D>
where
D: Digest,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
prefix: self.prefix.clone(),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePublicKey for VerifyingKey<D>
where
D: Digest,
{
fn to_public_key_der(&self) -> spki::Result<Document> {
self.inner.to_public_key_der()
}
}
impl<D> From<RsaPublicKey> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn from(key: RsaPublicKey) -> Self {
Self::new(key)
}
}
impl<D> From<VerifyingKey<D>> for RsaPublicKey
where
D: Digest,
{
fn from(key: VerifyingKey<D>) -> Self {
key.inner
}
}
#[cfg(feature = "encoding")]
impl<D> SignatureAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest + oid::RsaSignatureAssociatedOid,
{
type Params = AnyRef<'static>;
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
AlgorithmIdentifierRef {
oid: D::OID,
parameters: Some(AnyRef::NULL),
};
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
spki.algorithm.assert_algorithm_oid(pkcs1::ALGORITHM_OID)?;
RsaPublicKey::try_from(spki).map(Self::new)
}
}
impl<D> PartialEq for VerifyingKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.prefix == other.prefix
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for VerifyingKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_public_key_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let pub_key = priv_key.to_public_key();
let verifying_key = VerifyingKey::<Sha256>::new(pub_key);
let tokens = [Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
)];
assert_tokens(&verifying_key.readable(), &tokens);
}
}
+675
View File
@@ -0,0 +1,675 @@
//! Support for the [Probabilistic Signature Scheme] (PSS) a.k.a. RSASSA-PSS.
//!
//! Designed by Mihir Bellare and Phillip Rogaway. Specified in [RFC8017 § 8.1].
//!
//! # Usage
//!
//! See [code example in the toplevel rustdoc](../index.html#pss-signatures).
//!
//! [Probabilistic Signature Scheme]: https://en.wikipedia.org/wiki/Probabilistic_signature_scheme
//! [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
mod blinded_signing_key;
mod signature;
mod signing_key;
mod verifying_key;
pub use self::{
blinded_signing_key::BlindedSigningKey, signature::Signature, signing_key::SigningKey,
verifying_key::VerifyingKey,
};
use alloc::vec::Vec;
use core::fmt::{self, Debug};
use crypto_bigint::BoxedUint;
use digest::{Digest, FixedOutputReset};
use rand_core::TryCryptoRng;
use crate::algorithms::pad::{uint_to_be_pad, uint_to_zeroizing_be_pad};
use crate::algorithms::pss::*;
use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt};
use crate::errors::{Error, Result};
use crate::traits::PublicKeyParts;
use crate::traits::SignatureScheme;
use crate::{RsaPrivateKey, RsaPublicKey};
#[cfg(feature = "encoding")]
use {
crate::encoding::ID_RSASSA_PSS,
const_oid::AssociatedOid,
pkcs1::RsaPssParams,
spki::{der::Any, AlgorithmIdentifierOwned},
};
/// Digital signatures using PSS padding.
pub struct Pss<D> {
/// Create blinded signatures.
pub blinded: bool,
/// Digest type to use.
pub digest: D,
/// Salt length.
/// Required for signing, optional for verifying.
pub salt_len: Option<usize>,
}
impl<D> Default for Pss<D>
where
D: Digest,
{
fn default() -> Self {
Self::new()
}
}
impl<D> Pss<D>
where
D: Digest,
{
/// New PSS padding for the given digest.
/// Digest output size is used as a salt length.
pub fn new() -> Self {
Self::new_with_salt(<D as Digest>::output_size())
}
/// New PSS padding for the given digest with a salt value of the given length.
pub fn new_with_salt(len: usize) -> Self {
Self {
blinded: false,
digest: D::new(),
salt_len: Some(len),
}
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest.
/// Digest output size is used as a salt length.
pub fn new_blinded() -> Self {
Self::new_blinded_with_salt(<D as Digest>::output_size())
}
/// New PSS padding for blinded signatures (RSA-BSSA) for the given digest
/// with a salt value of the given length.
pub fn new_blinded_with_salt(len: usize) -> Self {
Self {
blinded: true,
digest: D::new(),
salt_len: Some(len),
}
}
}
impl<D> SignatureScheme for Pss<D>
where
D: Digest + FixedOutputReset,
{
fn sign<Rng: TryCryptoRng + ?Sized>(
mut self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
) -> Result<Vec<u8>> {
sign(
rng.ok_or(Error::InvalidPaddingScheme)?,
self.blinded,
priv_key,
hashed,
self.salt_len.expect("salt_len to be Some"),
&mut self.digest,
)
}
fn verify(mut self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()> {
verify(
pub_key,
hashed,
&BoxedUint::from_be_slice_vartime(sig),
sig.len(),
&mut self.digest,
self.salt_len,
)
}
}
impl<D> Debug for Pss<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PSS")
.field("blinded", &self.blinded)
.field("digest", &"...")
.field("salt_len", &self.salt_len)
.finish()
}
}
pub(crate) fn verify<D>(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BoxedUint,
sig_len: usize,
digest: &mut D,
salt_len: Option<usize>,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
if sig_len != pub_key.size() {
return Err(Error::Verification);
}
let raw = rsa_encrypt(pub_key, sig)?;
let mut em = uint_to_be_pad(raw, pub_key.size())?;
emsa_pss_verify(hashed, &mut em, salt_len, digest, pub_key.n().bits() as _)
}
pub(crate) fn verify_digest<D>(
pub_key: &RsaPublicKey,
hashed: &[u8],
sig: &BoxedUint,
salt_len: Option<usize>,
) -> Result<()>
where
D: Digest + FixedOutputReset,
{
let n = pub_key.n();
if sig >= n.as_ref() || sig.bits_precision() != pub_key.n_bits_precision() {
return Err(Error::Verification);
}
let mut em = uint_to_be_pad(rsa_encrypt(pub_key, sig)?, pub_key.size())?;
emsa_pss_verify_digest::<D>(hashed, &mut em, salt_len, pub_key.n().bits() as _)
}
/// SignPSS calculates the signature of hashed using RSASSA-PSS.
///
/// Note that hashed must be the result of hashing the input message using the
/// given hash function. The opts argument may be nil, in which case sensible
/// defaults are used.
pub(crate) fn sign<T, D>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
digest: &mut D,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let mut salt = vec![0; salt_len];
rng.try_fill_bytes(&mut salt[..]).map_err(|_| Error::Rng)?;
sign_pss_with_salt(blind.then_some(rng), priv_key, hashed, &salt, digest)
}
pub(crate) fn sign_digest<T, D>(
rng: &mut T,
blind: bool,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt_len: usize,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let mut salt = vec![0; salt_len];
rng.try_fill_bytes(&mut salt[..]).map_err(|_| Error::Rng)?;
sign_pss_with_salt_digest::<_, D>(blind.then_some(rng), priv_key, hashed, &salt)
}
/// signPSSWithSalt calculates the signature of hashed using PSS with specified salt.
///
/// Note that hashed must be the result of hashing the input message using the
/// given hash function. salt is a random sequence of bytes whose length will be
/// later used to verify the signature.
fn sign_pss_with_salt<T, D>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
digest: &mut D,
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode(hashed, em_bits as _, salt, digest)?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
let raw = rsa_decrypt_and_check(priv_key, blind_rng, &em)?;
uint_to_zeroizing_be_pad(raw, priv_key.size())
}
fn sign_pss_with_salt_digest<T, D>(
blind_rng: Option<&mut T>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
salt: &[u8],
) -> Result<Vec<u8>>
where
T: TryCryptoRng + ?Sized,
D: Digest + FixedOutputReset,
{
let em_bits = priv_key.n().bits() - 1;
let em = emsa_pss_encode_digest::<D>(hashed, em_bits as _, salt)?;
let em = BoxedUint::from_be_slice(&em, priv_key.n_bits_precision())?;
uint_to_zeroizing_be_pad(
rsa_decrypt_and_check(priv_key, blind_rng, &em)?,
priv_key.size(),
)
}
/// Returns the [`AlgorithmIdentifierOwned`] associated with PSS signature using a given digest.
#[cfg(feature = "encoding")]
pub fn get_default_pss_signature_algo_id<D>() -> spki::Result<AlgorithmIdentifierOwned>
where
D: Digest + AssociatedOid,
{
let salt_len: u8 = <D as Digest>::output_size() as u8;
get_pss_signature_algo_id::<D>(salt_len)
}
#[cfg(feature = "encoding")]
fn get_pss_signature_algo_id<D>(salt_len: u8) -> spki::Result<AlgorithmIdentifierOwned>
where
D: Digest + AssociatedOid,
{
let pss_params = RsaPssParams::new::<D>(salt_len);
Ok(AlgorithmIdentifierOwned {
oid: ID_RSASSA_PSS,
parameters: Some(Any::encode_from(&pss_params)?),
})
}
#[cfg(all(test, feature = "encoding"))]
mod test {
use crate::pss::{BlindedSigningKey, Pss, Signature, SigningKey, VerifyingKey};
use crate::{RsaPrivateKey, RsaPublicKey};
use crate::traits::PublicKeyParts;
use hex_literal::hex;
use pkcs1::DecodeRsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use rstest::rstest;
use sha1::{Digest, Sha1};
use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
use signature::{DigestVerifier, Keypair, RandomizedDigestSigner, RandomizedSigner, Verifier};
fn get_private_key() -> RsaPrivateKey {
// In order to generate new test vectors you'll need the PEM form of this key:
// -----BEGIN RSA PRIVATE KEY-----
// MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
// fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
// /ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
// RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
// EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
// IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
// tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
// -----END RSA PRIVATE KEY-----
let pem = r#"
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
-----END RSA PRIVATE KEY-----"#;
RsaPrivateKey::from_pkcs1_pem(pem).unwrap()
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let digest = Sha1::digest(text.as_bytes()).to_vec();
let result = pub_key.verify(Pss::<Sha1>::new(), &digest, &sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss_signer(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key: VerifyingKey<Sha1> = VerifyingKey::new(pub_key);
let result = verifying_key.verify(
text.as_bytes(),
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true,
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false,
)]
fn test_verify_pss_digest_signer(
#[case] text: &str,
#[case] sig: [u8; 64],
#[case] expected: bool,
) {
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::new(pub_key);
let result = verifying_key.verify_digest(
|digest: &mut Sha1| {
digest.update(text.as_bytes());
Ok(())
},
&Signature::try_from(sig.as_slice()).unwrap(),
);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_blinded_and_verify_roundtrip(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let digest = Sha1::digest(test.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new_blinded(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_blinded_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_digest_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(test.as_bytes()));
verifying_key
.verify_digest(
|digest: &mut Sha1| {
digest.update(test.as_bytes());
Ok(())
},
&sig,
)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_roundtrip_blinded_digest_signer(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_digest_with_rng(&mut rng, |digest: &mut Sha1| digest.update(test.as_bytes()));
verifying_key
.verify_digest(
|digest: &mut Sha1| {
digest.update(test.as_bytes());
Ok(())
},
&sig,
)
.expect("failed to verify");
}
#[rstest]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962f"
),
true
)]
#[case(
"test\n",
hex!(
"6f86f26b14372b2279f79fb6807c49889835c204f71e38249b4c5601462da8ae"
"30f26ffdd9c13f1c75eee172bebe7b7c89f2f1526c722833b9737d6c172a962e"
),
false
)]
fn test_verify_pss_hazmat(#[case] text: &str, #[case] sig: [u8; 64], #[case] expected: bool) {
let text = Sha1::digest(text);
let priv_key = get_private_key();
let pub_key: RsaPublicKey = priv_key.into();
let verifying_key = VerifyingKey::<Sha1>::new(pub_key);
let result = verifying_key
.verify_prehash(text.as_ref(), &Signature::try_from(sig.as_slice()).unwrap());
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_hazmat(#[case] test: &str) {
let test = &Sha1::digest(test);
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = SigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_prehash_with_rng(&mut rng, test)
.expect("failed to sign");
verifying_key
.verify_prehash(test, &sig)
.expect("failed to verify");
}
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_blinded_hazmat(#[case] test: &str) {
let priv_key = get_private_key();
let test = &Sha1::digest(test);
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha1>::new(priv_key);
let verifying_key = signing_key.verifying_key();
let sig = signing_key
.sign_prehash_with_rng(&mut rng, test)
.expect("failed to sign");
verifying_key
.verify_prehash(test, &sig)
.expect("failed to verify");
}
#[test]
// Tests the corner case where the key is multiple of 8 + 1 bits long
fn test_sign_and_verify_2049bit_key() {
let plaintext = "Hello\n";
let mut rng = ChaCha8Rng::from_seed([42; 32]);
for i in 0..10 {
println!("round {i}");
let priv_key = RsaPrivateKey::new(&mut rng, 2049).unwrap();
let digest = Sha1::digest(plaintext.as_bytes()).to_vec();
let sig = priv_key
.sign_with_rng(&mut rng, Pss::<Sha1>::new(), &digest)
.expect("failed to sign");
priv_key
.to_public_key()
.verify(Pss::<Sha1>::new(), &digest, &sig)
.expect("failed to verify");
}
}
// Tests the case where the salt length used for signing differs from the default length
// while the verifier uses auto-detection.
#[rstest]
#[case("test\n")]
fn test_sign_and_verify_pss_differing_salt_len(#[case] test: &str) {
let priv_key = get_private_key();
let mut rng = ChaCha8Rng::from_seed([42; 32]);
// signing keys using different salt lengths
let signing_keys = [
// default salt length
SigningKey::<Sha1>::new(priv_key.clone()),
// maximum salt length
SigningKey::<Sha1>::new_with_salt_len(
priv_key.clone(),
priv_key.size() - Sha1::output_size() - 2,
),
// unsalted
SigningKey::<Sha1>::new_with_salt_len(priv_key.clone(), 0),
];
// verifying key uses default salt length strategy
let verifying_key = VerifyingKey::<Sha1>::new_with_auto_salt_len(priv_key.to_public_key());
for signing_key in &signing_keys {
let sig = signing_key.sign_with_rng(&mut rng, test.as_bytes());
verifying_key
.verify(test.as_bytes(), &sig)
.expect("verification to succeed");
}
}
}
+307
View File
@@ -0,0 +1,307 @@
use super::{sign_digest, Signature, VerifyingKey};
use crate::{Result, RsaPrivateKey};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, HashMarker, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::RandomizedPrehashSigner, Keypair, RandomizedDigestSigner, RandomizedMultipartSigner,
RandomizedSigner,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "encoding")]
use {
super::get_pss_signature_algo_id,
const_oid::AssociatedOid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierOwned, AlgorithmIdentifierRef,
AssociatedAlgorithmIdentifier, DynSignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
/// Signing key for producing "blinded" RSASSA-PSS signatures as described in
/// [draft-irtf-cfrg-rsa-blind-signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-rsa-blind-signatures/).
#[derive(Debug, Clone)]
pub struct BlindedSigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
salt_len: usize,
phantom: PhantomData<D>,
}
impl<D> BlindedSigningKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS signing key which produces "blinded"
/// signatures.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPrivateKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS signing key which produces "blinded"
/// signatures with a salt of the given length.
pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len,
phantom: Default::default(),
}
}
/// Create a new random RSASSA-PSS signing key which produces "blinded"
/// signatures.
/// Digest output size is used as a salt length.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Self::random_with_salt_len(rng, bit_size, <D as Digest>::output_size())
}
/// Create a new random RSASSA-PSS signing key which produces "blinded"
/// signatures with a salt of the given length.
pub fn random_with_salt_len<R: CryptoRng + ?Sized>(
rng: &mut R,
bit_size: usize,
salt_len: usize,
) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
salt_len,
phantom: Default::default(),
})
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> usize {
self.salt_len
}
}
//
// `*Signer` trait impls
//
impl<D> RandomizedSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(rng, &[msg])
}
}
impl<D> RandomizedMultipartSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
let mut digest = D::new();
msg.iter()
.for_each(|slice| <D as Digest>::update(&mut digest, slice));
sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedDigestSigner<D, Signature> for BlindedSigningKey<D>
where
D: Default + FixedOutputReset + HashMarker + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::default();
f(&mut digest)?;
sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedPrehashSigner<Signature> for BlindedSigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
prehash: &[u8],
) -> signature::Result<Signature> {
sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)?
.as_slice()
.try_into()
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for BlindedSigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for BlindedSigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> DynSignatureAlgorithmIdentifier for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
get_pss_signature_algo_id::<D>(self.salt_len as u8)
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for BlindedSigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for BlindedSigningKey<D>
where
D: Digest,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<BlindedSigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: BlindedSigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for BlindedSigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
salt_len: Some(self.salt_len),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for BlindedSigningKey<D> where D: Digest {}
impl<D> PartialEq for BlindedSigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for BlindedSigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for BlindedSigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let signing_key = BlindedSigningKey::<Sha256>::new(
RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key"),
);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f"
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+106
View File
@@ -0,0 +1,106 @@
//! `RSASSA-PSS` signatures.
use alloc::boxed::Box;
use core::fmt::{Debug, Display, Formatter, LowerHex, UpperHex};
use crypto_bigint::BoxedUint;
use signature::SignatureEncoding;
#[cfg(feature = "serde")]
use serdect::serde::{de, Deserialize, Serialize};
#[cfg(feature = "encoding")]
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
};
/// `RSASSA-PSS` signatures as described in [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub(super) inner: BoxedUint,
}
impl SignatureEncoding for Signature {
type Repr = Box<[u8]>;
}
#[cfg(feature = "encoding")]
impl SignatureBitStringEncoding for Signature {
fn to_bitstring(&self) -> DerResult<BitString> {
BitString::new(0, self.to_vec())
}
}
impl TryFrom<&[u8]> for Signature {
type Error = signature::Error;
fn try_from(bytes: &[u8]) -> signature::Result<Self> {
// TODO(tarcieri): max length restriction? (#350)
let inner = BoxedUint::from_be_slice_vartime(bytes);
Ok(Self { inner })
}
}
impl From<Signature> for Box<[u8]> {
fn from(signature: Signature) -> Box<[u8]> {
signature.inner.to_be_bytes()
}
}
impl LowerHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:x}", &self.inner)
}
}
impl UpperHex for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", &self.inner)
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:X}", self)
}
}
#[cfg(feature = "serde")]
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
serdect::slice::deserialize_hex_or_bin_vec(deserializer)?
.as_slice()
.try_into()
.map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
use super::*;
use serde_test::{assert_tokens, Configure, Token};
let signature = Signature {
inner: BoxedUint::from(42u32),
};
let tokens = [Token::Str("000000000000002a")];
assert_tokens(&signature.readable(), &tokens);
}
}
+346
View File
@@ -0,0 +1,346 @@
use super::{sign_digest, Signature, VerifyingKey};
use crate::{Result, RsaPrivateKey};
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, Update};
use rand_core::{CryptoRng, TryCryptoRng};
use signature::{
hazmat::RandomizedPrehashSigner, Keypair, RandomizedDigestSigner, RandomizedMultipartSigner,
RandomizedSigner,
};
use zeroize::ZeroizeOnDrop;
#[cfg(feature = "serde")]
use {
pkcs8::DecodePrivateKey,
serdect::serde::{de, ser, Deserialize, Serialize},
};
#[cfg(feature = "encoding")]
use {
super::get_pss_signature_algo_id,
crate::encoding::verify_algorithm_id,
const_oid::AssociatedOid,
pkcs8::{EncodePrivateKey, SecretDocument},
spki::{
der::AnyRef, AlgorithmIdentifierOwned, AlgorithmIdentifierRef,
AssociatedAlgorithmIdentifier, DynSignatureAlgorithmIdentifier,
},
};
#[cfg(feature = "getrandom")]
use {
crypto_common::getrandom::SysRng,
signature::{hazmat::PrehashSigner, MultipartSigner, Signer},
};
/// Signing key for producing RSASSA-PSS signatures as described in
/// [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug, Clone)]
pub struct SigningKey<D>
where
D: Digest,
{
inner: RsaPrivateKey,
salt_len: usize,
phantom: PhantomData<D>,
}
impl<D> SigningKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS signing key.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPrivateKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS signing key with a salt of the given length.
pub fn new_with_salt_len(key: RsaPrivateKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len,
phantom: Default::default(),
}
}
/// Generate a new random RSASSA-PSS signing key.
/// Digest output size is used as a salt length.
pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
Self::random_with_salt_len(rng, bit_size, <D as Digest>::output_size())
}
/// Generate a new random RSASSA-PSS signing key with a salt of the given length.
pub fn random_with_salt_len<R: CryptoRng + ?Sized>(
rng: &mut R,
bit_size: usize,
salt_len: usize,
) -> Result<Self> {
Ok(Self {
inner: RsaPrivateKey::new(rng, bit_size)?,
salt_len,
phantom: Default::default(),
})
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> usize {
self.salt_len
}
}
//
// `*Signer` trait impls
//
impl<D> RandomizedDigestSigner<D, Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_sign_digest_with_rng<
R: TryCryptoRng + ?Sized,
F: Fn(&mut D) -> signature::Result<()>,
>(
&self,
rng: &mut R,
f: F,
) -> signature::Result<Signature> {
let mut digest = D::new();
f(&mut digest)?;
sign_digest::<_, D>(rng, false, &self.inner, &digest.finalize(), self.salt_len)?
.as_slice()
.try_into()
}
}
impl<D> RandomizedSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[u8],
) -> signature::Result<Signature> {
self.try_sign_digest_with_rng(rng, |digest: &mut D| {
Update::update(digest, msg);
Ok(())
})
}
}
impl<D> RandomizedMultipartSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn try_multipart_sign_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
msg: &[&[u8]],
) -> signature::Result<Signature> {
self.try_sign_digest_with_rng(rng, |digest: &mut D| {
msg.iter().for_each(|slice| Update::update(digest, slice));
Ok(())
})
}
}
impl<D> RandomizedPrehashSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
prehash: &[u8],
) -> signature::Result<Signature> {
sign_digest::<_, D>(rng, false, &self.inner, prehash, self.salt_len)?
.as_slice()
.try_into()
}
}
#[cfg(feature = "getrandom")]
impl<D> PrehashSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn sign_prehash(&self, prehash: &[u8]) -> signature::Result<Signature> {
self.sign_prehash_with_rng(&mut SysRng, prehash)
}
}
#[cfg(feature = "getrandom")]
impl<D> Signer<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> {
self.try_sign_with_rng(&mut SysRng, msg)
}
}
#[cfg(feature = "getrandom")]
impl<D> MultipartSigner<Signature> for SigningKey<D>
where
D: Digest + FixedOutputReset,
{
fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result<Signature> {
self.try_multipart_sign_with_rng(&mut SysRng, msg)
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPrivateKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for SigningKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
#[cfg(feature = "encoding")]
impl<D> DynSignatureAlgorithmIdentifier for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn signature_algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
get_pss_signature_algo_id::<D>(self.salt_len as u8)
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePrivateKey for SigningKey<D>
where
D: Digest,
{
fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
self.inner.to_pkcs8_der()
}
}
impl<D> From<RsaPrivateKey> for SigningKey<D>
where
D: Digest,
{
fn from(key: RsaPrivateKey) -> Self {
Self::new(key)
}
}
impl<D> From<SigningKey<D>> for RsaPrivateKey
where
D: Digest,
{
fn from(key: SigningKey<D>) -> Self {
key.inner
}
}
impl<D> Keypair for SigningKey<D>
where
D: Digest,
{
type VerifyingKey = VerifyingKey<D>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey {
inner: self.inner.to_public_key(),
salt_len: Some(self.salt_len),
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::PrivateKeyInfoRef<'_>> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
type Error = pkcs8::Error;
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
verify_algorithm_id(&private_key_info.algorithm)?;
RsaPrivateKey::try_from(private_key_info).map(Self::new)
}
}
impl<D> ZeroizeOnDrop for SigningKey<D> where D: Digest {}
impl<D> PartialEq for SigningKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for SigningKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
let der = self.to_pkcs8_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der.as_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for SigningKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> core::result::Result<Self, De::Error>
where
De: serdect::serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_pkcs8_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let signing_key = SigningKey::<Sha256>::new(priv_key);
let tokens = [Token::Str(concat!(
"3056020100300d06092a864886f70d010101050004423040020100020900ab240c",
"3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa",
"ad020500ccaddf17020500cb529d3d020500bb526d6f"
))];
assert_tokens(&signing_key.readable(), &tokens);
}
}
+265
View File
@@ -0,0 +1,265 @@
use super::{verify_digest, Signature};
use crate::RsaPublicKey;
use core::marker::PhantomData;
use digest::{Digest, FixedOutputReset, Update};
use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
#[cfg(feature = "encoding")]
use {
crate::encoding::ID_RSASSA_PSS,
const_oid::AssociatedOid,
pkcs8::{Document, EncodePublicKey},
spki::{der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier},
};
#[cfg(feature = "serde")]
use {
serdect::serde::{de, ser, Deserialize, Serialize},
spki::DecodePublicKey,
};
/// Verifying key for checking the validity of RSASSA-PSS signatures as
/// described in [RFC8017 § 8.1].
///
/// [RFC8017 § 8.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1
#[derive(Debug)]
pub struct VerifyingKey<D>
where
D: Digest,
{
pub(super) inner: RsaPublicKey,
pub(super) salt_len: Option<usize>,
pub(super) phantom: PhantomData<D>,
}
impl<D> VerifyingKey<D>
where
D: Digest,
{
/// Create a new RSASSA-PSS verifying key.
/// Digest output size is used as a salt length.
pub fn new(key: RsaPublicKey) -> Self {
Self::new_with_salt_len(key, <D as Digest>::output_size())
}
/// Create a new RSASSA-PSS verifying key.
pub fn new_with_salt_len(key: RsaPublicKey, salt_len: usize) -> Self {
Self {
inner: key,
salt_len: Some(salt_len),
phantom: Default::default(),
}
}
/// Create a new RSASSA-PSS verifying key.
/// Attempts to automatically detect the salt length.
pub fn new_with_auto_salt_len(key: RsaPublicKey) -> Self {
Self {
inner: key,
salt_len: None,
phantom: Default::default(),
}
}
/// Return specified salt length for this key
pub fn salt_len(&self) -> Option<usize> {
self.salt_len
}
}
//
// `*Verifier` trait impls
//
impl<D> DigestVerifier<D, Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset + Update,
{
fn verify_digest<F: Fn(&mut D) -> signature::Result<()>>(
&self,
f: F,
signature: &Signature,
) -> signature::Result<()> {
let mut digest = D::new();
f(&mut digest)?;
verify_digest::<D>(
&self.inner,
&digest.finalize(),
&signature.inner,
self.salt_len,
)
.map_err(|e| e.into())
}
}
impl<D> PrehashVerifier<Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset,
{
fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> {
verify_digest::<D>(&self.inner, prehash, &signature.inner, self.salt_len)
.map_err(|e| e.into())
}
}
impl<D> Verifier<Signature> for VerifyingKey<D>
where
D: Digest + FixedOutputReset,
{
fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> {
verify_digest::<D>(
&self.inner,
&D::digest(msg),
&signature.inner,
self.salt_len,
)
.map_err(|e| e.into())
}
}
//
// Other trait impls
//
impl<D> AsRef<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn as_ref(&self) -> &RsaPublicKey {
&self.inner
}
}
#[cfg(feature = "encoding")]
impl<D> AssociatedAlgorithmIdentifier for VerifyingKey<D>
where
D: Digest,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
}
// Implemented manually so we don't have to bind D with Clone
impl<D> Clone for VerifyingKey<D>
where
D: Digest,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
salt_len: self.salt_len,
phantom: Default::default(),
}
}
}
#[cfg(feature = "encoding")]
impl<D> EncodePublicKey for VerifyingKey<D>
where
D: Digest,
{
fn to_public_key_der(&self) -> spki::Result<Document> {
self.inner.to_public_key_der()
}
}
impl<D> From<RsaPublicKey> for VerifyingKey<D>
where
D: Digest,
{
fn from(key: RsaPublicKey) -> Self {
Self::new(key)
}
}
impl<D> From<VerifyingKey<D>> for RsaPublicKey
where
D: Digest,
{
fn from(key: VerifyingKey<D>) -> Self {
key.inner
}
}
#[cfg(feature = "encoding")]
impl<D> TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
type Error = spki::Error;
fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
match spki.algorithm.oid {
ID_RSASSA_PSS | pkcs1::ALGORITHM_OID => (),
_ => {
return Err(spki::Error::OidUnknown {
oid: spki.algorithm.oid,
});
}
}
RsaPublicKey::try_from(spki).map(Self::new)
}
}
impl<D> PartialEq for VerifyingKey<D>
where
D: Digest,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.salt_len == other.salt_len
}
}
#[cfg(feature = "serde")]
impl<D> Serialize for VerifyingKey<D>
where
D: Digest,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let der = self.to_public_key_der().map_err(ser::Error::custom)?;
serdect::slice::serialize_hex_lower_or_bin(&der, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D> Deserialize<'de> for VerifyingKey<D>
where
D: Digest + AssociatedOid,
{
fn deserialize<De>(deserializer: De) -> Result<Self, De::Error>
where
De: serde::Deserializer<'de>,
{
let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::from_public_key_der(&der_bytes).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(all(feature = "hazmat", feature = "serde"))]
fn test_serde() {
use super::*;
use crate::RsaPrivateKey;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use serde_test::{assert_tokens, Configure, Token};
use sha2::Sha256;
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let priv_key = RsaPrivateKey::new_unchecked(&mut rng, 64).expect("failed to generate key");
let pub_key = priv_key.to_public_key();
let verifying_key = VerifyingKey::<Sha256>::new(pub_key);
let tokens = [Token::Str(
"3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001",
)];
assert_tokens(&verifying_key.readable(), &tokens);
}
}
+9
View File
@@ -0,0 +1,9 @@
//! RSA-related trait definitions.
mod encryption;
pub(crate) mod keys;
mod padding;
pub use encryption::{Decryptor, EncryptingKeypair, RandomizedDecryptor, RandomizedEncryptor};
pub use keys::{PrivateKeyParts, PublicKeyParts};
pub use padding::{PaddingScheme, SignatureScheme};
+38
View File
@@ -0,0 +1,38 @@
//! Encryption-related traits.
use alloc::vec::Vec;
use rand_core::CryptoRng;
use crate::errors::Result;
/// Encrypt the message using provided random source
pub trait RandomizedEncryptor {
/// Encrypt the given message.
fn encrypt_with_rng<R: CryptoRng + ?Sized>(&self, rng: &mut R, msg: &[u8]) -> Result<Vec<u8>>;
}
/// Decrypt the given message
pub trait Decryptor {
/// Decrypt the given message.
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>>;
}
/// Decrypt the given message using provided random source
pub trait RandomizedDecryptor {
/// Decrypt the given message.
fn decrypt_with_rng<R: CryptoRng + ?Sized>(
&self,
rng: &mut R,
ciphertext: &[u8],
) -> Result<Vec<u8>>;
}
/// Encryption keypair with an associated encryption key.
pub trait EncryptingKeypair {
/// Encrypting key type for this keypair.
type EncryptingKey: Clone;
/// Get the encrypting key which can encrypt messages to be decrypted by
/// the decryption key portion of this keypair.
fn encrypting_key(&self) -> Self::EncryptingKey;
}
+93
View File
@@ -0,0 +1,93 @@
//! Traits related to the key components
use alloc::boxed::Box;
use crypto_bigint::{
modular::{BoxedMontyForm, BoxedMontyParams},
BoxedUint, NonZero,
};
use zeroize::Zeroize;
/// Components of an RSA public key.
pub trait PublicKeyParts {
/// Returns the modulus of the key.
fn n(&self) -> &NonZero<BoxedUint>;
/// Returns the public exponent of the key.
fn e(&self) -> &BoxedUint;
/// Returns the modulus size in bytes. Raw signatures and ciphertexts for
/// or by this public key will have the same size.
fn size(&self) -> usize {
(self.n().bits() as usize).div_ceil(8)
}
/// Returns the parameters for montgomery operations.
fn n_params(&self) -> &BoxedMontyParams;
/// Returns precision (in bits) of `n`.
fn n_bits_precision(&self) -> u32 {
self.n().bits_precision()
}
/// Returns the big endian serialization of the modulus of the key
fn n_bytes(&self) -> Box<[u8]> {
self.n().to_be_bytes_trimmed_vartime()
}
/// Returns the big endian serialization of the public exponent of the key
fn e_bytes(&self) -> Box<[u8]> {
self.e().to_be_bytes_trimmed_vartime()
}
}
/// Components of an RSA private key.
pub trait PrivateKeyParts: PublicKeyParts {
/// Returns the private exponent of the key.
fn d(&self) -> &BoxedUint;
/// Returns the prime factors.
fn primes(&self) -> &[BoxedUint];
/// Returns the precomputed dp value, D mod (P-1)
fn dp(&self) -> Option<&BoxedUint>;
/// Returns the precomputed dq value, D mod (Q-1)
fn dq(&self) -> Option<&BoxedUint>;
/// Returns the precomputed qinv value, Q^-1 mod P
fn qinv(&self) -> Option<&BoxedMontyForm>;
/// Returns an iterator over the CRT Values
fn crt_values(&self) -> Option<&[CrtValue]>;
/// Returns the params for `p` if precomputed.
fn p_params(&self) -> Option<&BoxedMontyParams>;
/// Returns the params for `q` if precomputed.
fn q_params(&self) -> Option<&BoxedMontyParams>;
}
/// Contains the precomputed Chinese remainder theorem values.
#[derive(Debug, Clone)]
pub struct CrtValue {
/// D mod (prime - 1)
pub(crate) exp: BoxedUint,
/// R·Coeff ≡ 1 mod Prime.
pub(crate) coeff: BoxedUint,
/// product of primes prior to this (inc p and q)
pub(crate) r: BoxedUint,
}
impl Zeroize for CrtValue {
fn zeroize(&mut self) {
self.exp.zeroize();
self.coeff.zeroize();
self.r.zeroize();
}
}
impl Drop for CrtValue {
fn drop(&mut self) {
self.zeroize();
}
}
+49
View File
@@ -0,0 +1,49 @@
//! Supported padding schemes.
use alloc::vec::Vec;
use rand_core::TryCryptoRng;
use crate::errors::Result;
use crate::key::{RsaPrivateKey, RsaPublicKey};
/// Padding scheme used for encryption.
pub trait PaddingScheme {
/// Decrypt the given message using the given private key.
///
/// If an `rng` is passed, it uses RSA blinding to help mitigate timing
/// side-channel attacks.
fn decrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>>;
/// Encrypt the given message using the given public key.
fn encrypt<Rng: TryCryptoRng + ?Sized>(
self,
rng: &mut Rng,
pub_key: &RsaPublicKey,
msg: &[u8],
) -> Result<Vec<u8>>;
}
/// Digital signature scheme.
pub trait SignatureScheme {
/// Sign the given digest.
fn sign<Rng: TryCryptoRng + ?Sized>(
self,
rng: Option<&mut Rng>,
priv_key: &RsaPrivateKey,
hashed: &[u8],
) -> Result<Vec<u8>>;
/// Verify a signed message.
///
/// `hashed` must be the result of hashing the input using the hashing function
/// passed in through `hash`.
///
/// If the message is valid `Ok(())` is returned, otherwise an `Err` indicating failure.
fn verify(self, pub_key: &RsaPublicKey, hashed: &[u8], sig: &[u8]) -> Result<()>;
}
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p
78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC
38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ6
7YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/
vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+
CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQABAoIBAH7Mg2LA7bB0EWQh
XiL3SrnZG6BpAHAM9jaQ5RFNjua9z7suP5YUaSpnegg/FopeUuWWjmQHudl8bg5A
ZPgtoLdYoU8XubfUH19I4o1lUXBPVuaeeqn6Yw/HZCjAbSXkVdz8VbesK092ZD/e
0/4V/3irsn5lrMSq0L322yfvYKaRDFxKCF7UMnWrGcHZl6Msbv/OffLRk19uYB7t
4WGhK1zCfKIfgdLJnD0eoI6Q4wU6sJvvpyTe8NDDo8HpdAwNn3YSahSewKp9gHgg
VIQlTZUdsHxM+R+2RUwJZYj9WSTbq+s1nKICUmjQBPnWbrPW963BE5utQPFt3mOe
EWRzdsECgYEA3MBhJC1Okq+u5yrFE8plufdwNvm9fg5uYUYafvdlQiXsFTx+XDGm
FXpuWhP/bheOh1jByzPZ1rvjF57xiZjkIuzcvtePTs/b5fT82K7CydDchkc8qb0W
2dI40h+13e++sUPKYdC9aqjZHzOgl3kOlkDbyRCF3F8mNDujE49rLWcCgYEA0/MU
dX5A6VSDb5K+JCNq8vDaBKNGU8GAr2fpYAhtk/3mXLI+/Z0JN0di9ZgeNhhJr2jN
11OU/2pOButpsgnkIo2y36cOQPf5dQpSgXZke3iNDld3osuLIuPNJn/3C087AtOq
+w4YxZClZLAxiLCqX8SBVrB2IiFCQ70SJ++n8vkCgYEAzmi3rBsNEA1jblVIh1PF
wJhD/bOQ4nBd92iUV8m9jZdl4wl4YX4u/IBI9MMkIG24YIe2VOl7s9Rk5+4/jNg/
4QQ2998Y6aljxOZJEdZ+3jQELy4m49OhrTRq2ta5t/Z3CMsJTmLe6f9NXWZpr5iK
8iVdHOjtMXxqfYaR2jVNEtsCgYAl9uWUQiAoa037v0I1wO5YQ9IZgJGJUSDWynsg
C4JtPs5zji4ASY+sCipsqWnH8MPKGrC8QClxMr51ONe+30yw78a5jvfbpU9Wqpmq
vOU0xJwnlH1GeMUcY8eMfOFocjG0yOtYeubvBIDLr0/AFzz9WHp+Z69RX7m53nUR
GDlyKQKBgDGZVAbUBiB8rerqNbONBAxfipoa4IJ+ntBrFT2DtoIZNbSzaoK+nVbH
kbWMJycaV5PVOh1lfAiZeWCxQz5RcZh/RS8USnxyMG1j4dP/wLcbdasI8uRaSC6Y
hFHL5HjhLrIo0HRWySS2b2ztBI2FP1M+MaaGFPHDzm2OyZg85yr3
-----END RSA PRIVATE KEY-----
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p78MZ
GsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC38Ff
wBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ67YGS
0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J
9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+CV+j
cFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQAB
-----END RSA PUBLIC KEY-----
Binary file not shown.
+51
View File
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAC
hZ0LQIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORn
ZoosfuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2
chojUXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2
v4Fh6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHi
pYx1wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhX
Hm8O12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfL
NT9gITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk
+tQ9eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAK
kENiQMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66
qIRt+Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEA
AQKCAgEAn+MJeyMiuQ+rZgbAF6CV6+ZAw5wQC87gLyOPoU2v846eV1aPESftRDYS
a5BGMbEn7Dlbs+4SfrgsiNJWKn+1X+2NFFC35OLS839XQmNvzG8omWNSLVtXBggs
rfoBwO6ZtNDpJ006mS4Gl0y+AWlGhjVpYqwZWf2b1EfluZaMBUPfG/E0dCrzRc2y
+h+TcbDUz2HGjRbWU9jpmdT9OhbPl4o1qkDoYM3OCWVd2LTPGdQUGx6SrV5RqOSl
wn+nRWEdkOSdDpKCIiq28SZkPhx3V4gW/OO5jzIdJUnylKRw34RTRGvzb5hd8l7Y
/en98wc/sncn30jp4fxwVrx4llCQt4UBJkBkYsglMFHvhONO48POuPlsZYw4vkVV
jS9k4p0iM1BVX8Hvoo7B9K+1ukCA8JqGzcNTjBrXyXLm16NhLmhFupr73xnwkGDR
p3neljXi0vjgxRC6JMbESzDJvfr4W+kXrsXUOvqxqjrdM8yD2pPKxpIY9qNutH8Z
nVQkyV/Z7Xsei+KuqmQzsickExbCDueSZQzrSL/WNERrGdKGtOoXIkmNoaNpcyEO
w4JHUaWAjZqu9ZxEnhmlB3z+yhJr2ajdSZZWHU4ns2Cf+CxbGyHmJ4RdRJYbM7h1
1cT6n/NX72vjNklp4TN8kbKaB7mpE83kDOLVUwyQDnN1FoXmVDECggEBANAhOnlC
W2ZbcZEYRIiT7DJ1YA9j2/hbd/To6Z7zAvboJZYEj23Kdy3mu/ESTbhLCv5hsDqG
BKsAee1T8zBHl60Bs4xE/ielpF43hIOoBLVqSpZ/SPAahm5yHmfkyaEEivaJJ/qk
PWqF2T579wdNunl1Y/yr4SMJt2ZTxtthTcIxzFVtnyWsSEGgLTHN8wFbISMH+dDH
n+tdOVbOU8yPoWUb5gdh8Z90ZySJ6vnyFUCfOZVud6ghg/H3K7L+3fG5+/xK2J6k
RYCd29W9WVJ3mQwL6TZvuy7PewV8wcPcj7d7+EVtB7vJWzwYFfSOYrgUaMPU2dls
D0jasEmTvo2R7eUCggEBAM42xoEFIqvl1kZfNusTfaO56kpfHSfGYUcp645eLly4
jj7xpHOiGUS2ZVez3CzkYuS/NEbLSZADflZysXBcuugbZbr5Z6Jm3Bjv6A9Nu/4a
WQYyBc4pQ8rfQhzOdK9wY/0ag688Oa+EUl9ZvcH/VIFfUq/R6NSGKyw2VPbPqD3A
jiqdUrn4M8ZGr3aURn38X31617RBiV/Lf/vtUmMksBVKFYI/UQfIlUjt3LYdpTCM
bMg01KDBbfpsodZ7YaZWd+sXGc0SXQ7w24gC+3bPwXV3vLJRCuKU4b+KkXOiuFwW
prUIyY8tdwt/PeSNnnIMU+JjaAtX5xCUEAFXRVcGUv0CggEAdItGzQPlXmmyLEdk
iP4b8x1azwNh965we4m42DLH5C6WbWzcS+Rl3CQp9ZIER0BuRYe6QOsuzfqUS9sI
gG52doBPZCp2DwloAwIfiAGbsWJ1pdRcqWaRBGOOtyqb5ThAAFFJO8agRXfx8FVG
PKa/1qdvd9tfVFlqgzhCUDIqcqWj/+pEhbn1NBpXdF4YxxeadJ1QvCIsYIVxSDR9
JD0BaTa4FkY4IMvzvbglBhUS5X7DpfOXuWQbGHEJ3U9uRJ+ahOn8ZskhyiWbJhLD
Y7Ro1SAOVVc3f7za7HWxotVs/JfErEujWvojxoDOOoVIrj9vcslLu74QyQD8WhcL
Swb+KQKCAQB1yk4LBp7uZ8PEwMCC+MgsjJbq0ne575RDbQuTb/K1neoKxEa2kmIy
oKk0tpVOw0pF9X3r7lTfwU8aHDuEvkM5L+UlLy9mUbDpQahhjXqTxAMUCeDNCT8j
E/IUuE1opR9IRSvxHcqpmkDfHEjLFojzuTpnGdUQCG+Cuqo/rRAh7eqHJwRJHCCe
4mN5rWqyrkTxTQkHeuP4Zyp9AeusnBlEn+O3WWl0s7uqQ8xt7nMcTyoYFi1aggLL
J+Atvp5hwESRccmYHSQw053ijCmNjVCpQ7LyfF5mXLqyiXlZ/xml6H5jLFjNwx+b
3pvBAK//31DPIQ8eY6CmFJ0r1ujRs9gVAoIBAQCMVXxINei0BmYGpdwlXbw+tfFY
bHMomIyOJCQD+Vde+w0oASwGNLckF2itciBJOCkG1jWvyx0qBb3/yAX+ZwOJt/qQ
1l+Ur7wgCNm8DTzO5CXfxyQCBQYDyfV57oUQ7MaTrG3TKDa24V49xSA2ahukr8kd
Pkik1nJMpCRD9IA+OxJjSwQ4Vy2OE7xy8agoU7jZ/KYZnXqQq2TZ5595OsKH+aGQ
EQgqUmjyCTMtVNmNbhkDCQf9nmuC7xJGd7oIrWeXpEIhPQl6NRxQoIko3XMtaymm
z2cG2vXyD3j4cXD7J5QDxSIbXlxwwrqg5ppy4NHzJn29jJvd/yivqS83yY02
-----END RSA PRIVATE KEY-----
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAChZ0L
QIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORnZoos
fuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2choj
UXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2v4Fh
6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHipYx1
wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhXHm8O
12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfLNT9g
ITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk+tQ9
eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAKkENi
QMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66qIRt
+Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEAAQ==
-----END RSA PUBLIC KEY-----
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2xCxRXxCmqvKC
xj7b4kJDoXDz+iYzvUgzY39Hyk9vNuA6XSnvwxkayA85DYdLOeMPQU/Owfyg7YHl
R+3CzTgsdvYckBiXPbn6U3lyp8cB9rd+CYLfwV/AGSfuXnzZS09Zn/BwE6fIKBvf
Ity8mtfKu3xDEcmC9Y7bchOtRVizMiZtdDrtgZLRiEytuLFHOaja2mbclwgG2ces
RQyxPQ18V1+xmFNPxhvEG8DwV04OATDHu7+9/cn2puLj4q/xy+rIm6V4hFKNVc+w
gyeh6MifTgA88oiOkzJB2daVvLus3JC0Tj4JX6NwWOolsT9eKVy+rG3oOKuMUK9h
4piXW4cvAgMBAAECggEAfsyDYsDtsHQRZCFeIvdKudkboGkAcAz2NpDlEU2O5r3P
uy4/lhRpKmd6CD8Wil5S5ZaOZAe52XxuDkBk+C2gt1ihTxe5t9QfX0jijWVRcE9W
5p56qfpjD8dkKMBtJeRV3PxVt6wrT3ZkP97T/hX/eKuyfmWsxKrQvfbbJ+9gppEM
XEoIXtQydasZwdmXoyxu/8598tGTX25gHu3hYaErXMJ8oh+B0smcPR6gjpDjBTqw
m++nJN7w0MOjwel0DA2fdhJqFJ7Aqn2AeCBUhCVNlR2wfEz5H7ZFTAlliP1ZJNur
6zWcogJSaNAE+dZus9b3rcETm61A8W3eY54RZHN2wQKBgQDcwGEkLU6Sr67nKsUT
ymW593A2+b1+Dm5hRhp+92VCJewVPH5cMaYVem5aE/9uF46HWMHLM9nWu+MXnvGJ
mOQi7Ny+149Oz9vl9PzYrsLJ0NyGRzypvRbZ0jjSH7Xd776xQ8ph0L1qqNkfM6CX
eQ6WQNvJEIXcXyY0O6MTj2stZwKBgQDT8xR1fkDpVINvkr4kI2ry8NoEo0ZTwYCv
Z+lgCG2T/eZcsj79nQk3R2L1mB42GEmvaM3XU5T/ak4G62myCeQijbLfpw5A9/l1
ClKBdmR7eI0OV3eiy4si480mf/cLTzsC06r7DhjFkKVksDGIsKpfxIFWsHYiIUJD
vRIn76fy+QKBgQDOaLesGw0QDWNuVUiHU8XAmEP9s5DicF33aJRXyb2Nl2XjCXhh
fi78gEj0wyQgbbhgh7ZU6Xuz1GTn7j+M2D/hBDb33xjpqWPE5kkR1n7eNAQvLibj
06GtNGra1rm39ncIywlOYt7p/01dZmmvmIryJV0c6O0xfGp9hpHaNU0S2wKBgCX2
5ZRCIChrTfu/QjXA7lhD0hmAkYlRINbKeyALgm0+znOOLgBJj6wKKmypacfww8oa
sLxAKXEyvnU4177fTLDvxrmO99ulT1aqmaq85TTEnCeUfUZ4xRxjx4x84WhyMbTI
61h65u8EgMuvT8AXPP1Yen5nr1FfubnedREYOXIpAoGAMZlUBtQGIHyt6uo1s40E
DF+Kmhrggn6e0GsVPYO2ghk1tLNqgr6dVseRtYwnJxpXk9U6HWV8CJl5YLFDPlFx
mH9FLxRKfHIwbWPh0//Atxt1qwjy5FpILpiEUcvkeOEusijQdFbJJLZvbO0EjYU/
Uz4xpoYU8cPObY7JmDznKvc=
-----END PRIVATE KEY-----
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsQsUV8QpqrygsY+2+JC
Q6Fw8/omM71IM2N/R8pPbzbgOl0p78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04
LHb2HJAYlz25+lN5cqfHAfa3fgmC38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrX
yrt8QxHJgvWO23ITrUVYszImbXQ67YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0N
fFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejI
n04APPKIjpMyQdnWlby7rNyQtE4+CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uH
LwIDAQAB
-----END PUBLIC KEY-----
Binary file not shown.
Binary file not shown.
Binary file not shown.
+506
View File
@@ -0,0 +1,506 @@
//! PKCS#1 encoding tests
#![cfg(feature = "encoding")]
use crypto_bigint::{BoxedUint, CtEq};
use hex_literal::hex;
use rsa::{
pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey},
traits::{PrivateKeyParts, PublicKeyParts},
RsaPrivateKey, RsaPublicKey,
};
#[cfg(feature = "encoding")]
use rsa::pkcs1::LineEnding;
/// RSA-2048 PKCS#1 private key encoded as ASN.1 DER.
///
/// Note: this key is extracted from the corresponding `rsa2048-priv.der`
/// example key in the `pkcs8` crate.
const RSA_2048_PRIV_DER: &[u8] = include_bytes!("examples/pkcs1/rsa2048-priv.der");
/// RSA-4096 PKCS#1 private key encoded as ASN.1 DER
const RSA_4096_PRIV_DER: &[u8] = include_bytes!("examples/pkcs1/rsa4096-priv.der");
/// RSA-2048 PKCS#1 public key encoded as ASN.1 DER.
///
/// Note: this key is extracted from the corresponding `rsa2048-priv.der`
/// example key in the `pkcs8` crate.
const RSA_2048_PUB_DER: &[u8] = include_bytes!("examples/pkcs1/rsa2048-pub.der");
/// RSA-4096 PKCS#1 public key encoded as ASN.1 DER
const RSA_4096_PUB_DER: &[u8] = include_bytes!("examples/pkcs1/rsa4096-pub.der");
/// RSA-2048 PKCS#1 private key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_2048_PRIV_PEM: &str = include_str!("examples/pkcs1/rsa2048-priv.pem");
/// RSA-4096 PKCS#1 private key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_4096_PRIV_PEM: &str = include_str!("examples/pkcs1/rsa4096-priv.pem");
/// RSA-2048 PKCS#1 public key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_2048_PUB_PEM: &str = include_str!("examples/pkcs1/rsa2048-pub.pem");
/// RSA-4096 PKCS#1 public key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_4096_PUB_PEM: &str = include_str!("examples/pkcs1/rsa4096-pub.pem");
#[test]
fn decode_rsa2048_priv_der() {
let key = RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa2048-priv.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BD"
"CFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B7"
"58A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455"
"DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A691"
"0C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5C"
"C27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F"
"76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DB"
"ABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225"
"EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422"
"ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBE"
"B143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FD"
"E65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E422"
"8DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B"
"02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
}
#[test]
fn decode_rsa4096_priv_der() {
let key = RsaPrivateKey::from_pkcs1_der(RSA_4096_PRIV_DER).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa4096-priv.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0"
"BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375"
"852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1E"
"BDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E02"
"6B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E"
"365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F"
"6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C"
"52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919"
"D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368F"
"EE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E2554"
"0EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7E"
"D5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875"
"506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D97"
"5D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73"
"E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4"
"F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"9FE3097B2322B90FAB6606C017A095EBE640C39C100BCEE02F238FA14DAFF38E"
"9E57568F1127ED4436126B904631B127EC395BB3EE127EB82C88D2562A7FB55F"
"ED8D1450B7E4E2D2F37F5742636FCC6F289963522D5B5706082CADFA01C0EE99"
"B4D0E9274D3A992E06974CBE01694686356962AC1959FD9BD447E5B9968C0543"
"DF1BF134742AF345CDB2FA1F9371B0D4CF61C68D16D653D8E999D4FD3A16CF97"
"8A35AA40E860CDCE09655DD8B4CF19D4141B1E92AD5E51A8E4A5C27FA745611D"
"90E49D0E9282222AB6F126643E1C77578816FCE3B98F321D2549F294A470DF84"
"53446BF36F985DF25ED8FDE9FDF3073FB27727DF48E9E1FC7056BC78965090B7"
"850126406462C8253051EF84E34EE3C3CEB8F96C658C38BE45558D2F64E29D22"
"3350555FC1EFA28EC1F4AFB5BA4080F09A86CDC3538C1AD7C972E6D7A3612E68"
"45BA9AFBDF19F09060D1A779DE9635E2D2F8E0C510BA24C6C44B30C9BDFAF85B"
"E917AEC5D43AFAB1AA3ADD33CC83DA93CAC69218F6A36EB47F199D5424C95FD9"
"ED7B1E8BE2AEAA6433B227241316C20EE792650CEB48BFD634446B19D286B4EA"
"1722498DA1A36973210EC3824751A5808D9AAEF59C449E19A5077CFECA126BD9"
"A8DD4996561D4E27B3609FF82C5B1B21E627845D44961B33B875D5C4FA9FF357"
"EF6BE3364969E1337C91B29A07B9A913CDE40CE2D5530C900E73751685E65431"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D0213A79425B665B719118448893EC3275600F63DBF85B77F4E8E99EF302F6E8"
"2596048F6DCA772DE6BBF1124DB84B0AFE61B03A8604AB0079ED53F3304797AD"
"01B38C44FE27A5A45E378483A804B56A4A967F48F01A866E721E67E4C9A1048A"
"F68927FAA43D6A85D93E7BF7074DBA797563FCABE12309B76653C6DB614DC231"
"CC556D9F25AC4841A02D31CDF3015B212307F9D0C79FEB5D3956CE53CC8FA165"
"1BE60761F19F74672489EAF9F215409F39956E77A82183F1F72BB2FEDDF1B9FB"
"FC4AD89EA445809DDBD5BD595277990C0BE9366FBB2ECF7B057CC1C3DC8FB77B"
"F8456D07BBC95B3C1815F48E62B81468C3D4D9D96C0F48DAB04993BE8D91EDE5"
),
2048,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"CE36C6810522ABE5D6465F36EB137DA3B9EA4A5F1D27C6614729EB8E5E2E5CB8"
"8E3EF1A473A21944B66557B3DC2CE462E4BF3446CB4990037E5672B1705CBAE8"
"1B65BAF967A266DC18EFE80F4DBBFE1A59063205CE2943CADF421CCE74AF7063"
"FD1A83AF3C39AF84525F59BDC1FF54815F52AFD1E8D4862B2C3654F6CFA83DC0"
"8E2A9D52B9F833C646AF7694467DFC5F7D7AD7B441895FCB7FFBED526324B015"
"4A15823F5107C89548EDDCB61DA5308C6CC834D4A0C16DFA6CA1D67B61A65677"
"EB1719CD125D0EF0DB8802FB76CFC17577BCB2510AE294E1BF8A9173A2B85C16"
"A6B508C98F2D770B7F3DE48D9E720C53E263680B57E7109410015745570652FD"
),
2048,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
}
#[test]
fn decode_rsa2048_pub_der() {
let key = RsaPublicKey::from_pkcs1_der(RSA_2048_PUB_DER).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa2048-pub.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
}
#[test]
fn decode_rsa4096_pub_der() {
let key = RsaPublicKey::from_pkcs1_der(RSA_4096_PUB_DER).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa4096-pub.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0"
"BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375"
"852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1E"
"BDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E02"
"6B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E"
"365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F"
"6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C"
"52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919"
"D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368F"
"EE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E2554"
"0EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7E"
"D5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875"
"506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D97"
"5D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73"
"E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4"
"F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
}
#[test]
fn encode_rsa2048_priv_der() {
let key = RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap();
let der = key.to_pkcs1_der().unwrap();
assert_eq!(der.as_bytes(), RSA_2048_PRIV_DER)
}
#[test]
fn encode_rsa4096_priv_der() {
let key = RsaPrivateKey::from_pkcs1_der(RSA_4096_PRIV_DER).unwrap();
let der = key.to_pkcs1_der().unwrap();
assert_eq!(der.as_bytes(), RSA_4096_PRIV_DER)
}
#[test]
fn encode_rsa2048_pub_der() {
let key = RsaPublicKey::from_pkcs1_der(RSA_2048_PUB_DER).unwrap();
let der = key.to_pkcs1_der().unwrap();
assert_eq!(der.as_ref(), RSA_2048_PUB_DER)
}
#[test]
fn encode_rsa4096_pub_der() {
let key = RsaPublicKey::from_pkcs1_der(RSA_4096_PUB_DER).unwrap();
let der = key.to_pkcs1_der().unwrap();
assert_eq!(der.as_ref(), RSA_4096_PUB_DER)
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa2048_priv_pem() {
let key = RsaPrivateKey::from_pkcs1_pem(RSA_2048_PRIV_PEM).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa2048-priv.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BD"
"CFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B7"
"58A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455"
"DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A691"
"0C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5C"
"C27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F"
"76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DB"
"ABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225"
"EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422"
"ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBE"
"B143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FD"
"E65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E422"
"8DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B"
"02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa4096_priv_pem() {
let key = RsaPrivateKey::from_pkcs1_pem(RSA_4096_PRIV_PEM).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa4096-priv.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0"
"BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375"
"852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1E"
"BDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E02"
"6B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E"
"365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F"
"6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C"
"52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919"
"D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368F"
"EE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E2554"
"0EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7E"
"D5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875"
"506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D97"
"5D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73"
"E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4"
"F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"9FE3097B2322B90FAB6606C017A095EBE640C39C100BCEE02F238FA14DAFF38E"
"9E57568F1127ED4436126B904631B127EC395BB3EE127EB82C88D2562A7FB55F"
"ED8D1450B7E4E2D2F37F5742636FCC6F289963522D5B5706082CADFA01C0EE99"
"B4D0E9274D3A992E06974CBE01694686356962AC1959FD9BD447E5B9968C0543"
"DF1BF134742AF345CDB2FA1F9371B0D4CF61C68D16D653D8E999D4FD3A16CF97"
"8A35AA40E860CDCE09655DD8B4CF19D4141B1E92AD5E51A8E4A5C27FA745611D"
"90E49D0E9282222AB6F126643E1C77578816FCE3B98F321D2549F294A470DF84"
"53446BF36F985DF25ED8FDE9FDF3073FB27727DF48E9E1FC7056BC78965090B7"
"850126406462C8253051EF84E34EE3C3CEB8F96C658C38BE45558D2F64E29D22"
"3350555FC1EFA28EC1F4AFB5BA4080F09A86CDC3538C1AD7C972E6D7A3612E68"
"45BA9AFBDF19F09060D1A779DE9635E2D2F8E0C510BA24C6C44B30C9BDFAF85B"
"E917AEC5D43AFAB1AA3ADD33CC83DA93CAC69218F6A36EB47F199D5424C95FD9"
"ED7B1E8BE2AEAA6433B227241316C20EE792650CEB48BFD634446B19D286B4EA"
"1722498DA1A36973210EC3824751A5808D9AAEF59C449E19A5077CFECA126BD9"
"A8DD4996561D4E27B3609FF82C5B1B21E627845D44961B33B875D5C4FA9FF357"
"EF6BE3364969E1337C91B29A07B9A913CDE40CE2D5530C900E73751685E65431"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D0213A79425B665B719118448893EC3275600F63DBF85B77F4E8E99EF302F6E8"
"2596048F6DCA772DE6BBF1124DB84B0AFE61B03A8604AB0079ED53F3304797AD"
"01B38C44FE27A5A45E378483A804B56A4A967F48F01A866E721E67E4C9A1048A"
"F68927FAA43D6A85D93E7BF7074DBA797563FCABE12309B76653C6DB614DC231"
"CC556D9F25AC4841A02D31CDF3015B212307F9D0C79FEB5D3956CE53CC8FA165"
"1BE60761F19F74672489EAF9F215409F39956E77A82183F1F72BB2FEDDF1B9FB"
"FC4AD89EA445809DDBD5BD595277990C0BE9366FBB2ECF7B057CC1C3DC8FB77B"
"F8456D07BBC95B3C1815F48E62B81468C3D4D9D96C0F48DAB04993BE8D91EDE5"
),
2048,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"CE36C6810522ABE5D6465F36EB137DA3B9EA4A5F1D27C6614729EB8E5E2E5CB8"
"8E3EF1A473A21944B66557B3DC2CE462E4BF3446CB4990037E5672B1705CBAE8"
"1B65BAF967A266DC18EFE80F4DBBFE1A59063205CE2943CADF421CCE74AF7063"
"FD1A83AF3C39AF84525F59BDC1FF54815F52AFD1E8D4862B2C3654F6CFA83DC0"
"8E2A9D52B9F833C646AF7694467DFC5F7D7AD7B441895FCB7FFBED526324B015"
"4A15823F5107C89548EDDCB61DA5308C6CC834D4A0C16DFA6CA1D67B61A65677"
"EB1719CD125D0EF0DB8802FB76CFC17577BCB2510AE294E1BF8A9173A2B85C16"
"A6B508C98F2D770B7F3DE48D9E720C53E263680B57E7109410015745570652FD"
),
2048,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa2048_pub_pem() {
let key = RsaPublicKey::from_pkcs1_pem(RSA_2048_PUB_PEM).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa2048-pub.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa4096_pub_pem() {
let key = RsaPublicKey::from_pkcs1_pem(RSA_4096_PUB_PEM).unwrap();
// Extracted using:
// $ openssl asn1parse -in tests/examples/pkcs1/rsa4096-pub.pem
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0"
"BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375"
"852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1E"
"BDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E02"
"6B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E"
"365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F"
"6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C"
"52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919"
"D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368F"
"EE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E2554"
"0EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7E"
"D5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875"
"506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D97"
"5D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73"
"E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4"
"F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa2048_priv_pem() {
let key = RsaPrivateKey::from_pkcs1_pem(RSA_2048_PRIV_PEM).unwrap();
let pem = key.to_pkcs1_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_2048_PRIV_PEM)
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa4096_priv_pem() {
let key = RsaPrivateKey::from_pkcs1_pem(RSA_4096_PRIV_PEM).unwrap();
let pem = key.to_pkcs1_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_4096_PRIV_PEM)
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa2048_pub_pem() {
let key = RsaPublicKey::from_pkcs1_pem(RSA_2048_PUB_PEM).unwrap();
let pem = key.to_pkcs1_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_2048_PUB_PEM)
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa4096_pub_pem() {
let key = RsaPublicKey::from_pkcs1_pem(RSA_4096_PUB_PEM).unwrap();
let pem = key.to_pkcs1_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_4096_PUB_PEM)
}
+80
View File
@@ -0,0 +1,80 @@
// simple but prevent regression - see https://github.com/RustCrypto/RSA/issues/329
#[cfg(feature = "encoding")]
#[test]
fn signature_stringify() {
use pkcs8::DecodePrivateKey;
use signature::Signer;
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
let pem = include_str!("examples/pkcs8/rsa2048-priv.pem");
let private_key = RsaPrivateKey::from_pkcs8_pem(pem).unwrap();
let signing_key = SigningKey::<sha2::Sha256>::new(private_key);
let bytes: &[u8] = b"rsa4096"; // HACK - the criterion is that the signature has leading zeros.
let signature = signing_key.sign(bytes);
let expected = "029E365B60971D5A499FF5E1C288B954D3A5DCF52482CEE46DB90DC860B725A8D6CA031146FA156E9F17579BE6122FFB11DAC35E59B2193D75F7B31CE1442DDE7F4FF7885AD5D6080266E9A33BB4CEC93FCC2B6B885457A0ABF19E2DAA00876F694B37F535F119925CCCF9A17B90AE6CF39F07D7FEFBEECDF1B344C14B728196DDD154230BADDEDA5A7EFF373F6CD3EF6D41789572A7A068E3A252D3B7D5D706C6170D8CFDB48C8E738A4B3BFEA3E15716805E376EBD99EA09C6E82F3CFA13CEB23CD289E8F95C27F489ADC05AAACE8A9276EE7CED3B7A5C7264F0D34FF18CEDC3E91D667FCF9992A8CFDE8562F65FDDE1E06595C27E0F82063839A358C927B2";
assert_eq!(format!("{}", signature), expected);
assert_eq!(format!("{:x}", signature), expected.to_lowercase());
assert_eq!(format!("{:X}", signature), expected);
assert_eq!(signature.to_string(), expected);
}
#[cfg(feature = "encoding")]
#[test]
fn signing_key_new_same_as_from() {
use pkcs1::DecodeRsaPrivateKey;
use rsa::RsaPrivateKey;
use signature::{Keypair, Signer, Verifier};
// randomly generated key, hardcoded for test repeatability
const PRIV_KEY_PKCS1_PEM: &str = "-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwQe5brkkpxrwR/5TJ6JXsUyBzYtbEL/w8u8P6NnxQ8sL4KYp
MzzTB6aq1gq7bieYXChg0PIWeTukGaOzZe96KxhT0GbhhYRlukktM/quRrM7nYdm
UmXo7+KWU55kfcNOjWKADL/7qmxn6y/+kPmBg83nHdr1Mq6/pNkeHY/1CeGGECl0
rg7gfEkssHjZw/uKafA271fX9A/q3LcAeWi7iA01PgmP28BrWb7OQoYVY71kFY11
e919VlMh8oXsIV0nXCkYu9dR8Pzq6U4gFASK32fFkKX/djRMljEgss3kR0SWPH7t
m5uXX1wRTJ2mRaZh/BmGweIvYCZ5y0+9ESOD1wIDAQABAoIBAAj3NuGxr8YjNi3h
3jLlE3WkvBKz+lLY13QxLmf+V3pyn+abUSaUGKkuUJkIfpQrOqRtK7IIzIps/r5C
ID8H1IDT7HCtlqQA9kikxXi4mAeoo4g5lcMWAK/Dsn/Hx5sfyzI99PyininYRyth
W02YiS96DNYSKXllLHmXrBJrcVI4FqXAz5s7MezU0XYi+jeaVGEP2bd2cHfQJki/
pLOKBvA5DGT7HbmMV7Z1qg/zcr/4Py+7qAFC5XsbQIILSMTfC45QFgs1lApnUG8H
uIhf5lgZ8m0ouDBb/e1Q04ANtdLLI6EmrR11PwavUmvPvXuedXkv2OvnuAbiJr6g
j0I0VaECgYEAyWf2QZrEoZLzVrInZ+VYtov1+jjgFcxW9lHuCJXtTx8hFla13Bmp
bc8PoxWb+37jPdrOYPW0yv1sk5VeVkxOJbms0Gn8hpyI+0muQZ3jmwlS0A10T6FL
wWECYvrxO8DCaVCQ4V+egLSDb/GMkRgHJF3Dr7g3ep7krXf2eeWILQUCgYEA9VqJ
ijMDKw/KX6swyMe3A1nA0MlLBeseXxrwNIJenwRXCzjG3BH6oHW2MGwH0EV7sSoG
FR6j7LZbp9I9NvRcAYU/s1qiAX3iX3KIsbZYNtEC6tKn/HClaHLZOhyuE8tjshyD
jhK/0rhw7R5VQ1GfJhmuzvwoMFTA0fqZBQpWZCsCgYBA5WO+3dyv50bLT5pM6uR7
5Xs7xinGPFJlCh812wFdNj2WEhiFNCuYu1hhhyv8jHUyUBehvGol4iSjJUUBb5La
qwpZGV2KDlRBDAu/Dt3w7b8mVL9+jQ144QZA2HT0ePbrsk8Mn5/V/tQ/NMjDU8ex
WxkbvLL7qskqb/YWbvRC9QKBgQDUJYvFpmQ36LhozmIpSZ6yU/oHzfWD0Y/6VhWa
oZtlTeBhwJ8aDKWz9vQonFCJQns4bgjCXDMLa4aG7p+lk9a2LdwtndF1Dr8dHrCZ
UPynsUQffTRpb5FmZd/0gnX2gafbixIpV4brkjV6of7BbaL50702Fgw99hqftVp4
ZD7c7wKBgD7uIs6rgpaJzKbf7ejjZSjfLOgHlJhtH6Nejp8KoJRsEQI1ofWyIn7D
eMjIuecwLapPwjY2G0/sUW61bqrxgW10wDJHPNllGsZFanzpb7x5o/7eNhzc4qNf
Rmb665iB5fwpqmbE/hYKIn7asYQE+V0dkgt8M3qvlJJ5JJbCrJx3
-----END RSA PRIVATE KEY-----";
let priv_key = RsaPrivateKey::from_pkcs1_pem(PRIV_KEY_PKCS1_PEM).unwrap();
let msg = b"1234";
let key_via_new = rsa::pkcs1v15::SigningKey::<sha2::Sha256>::new(priv_key.clone());
let key_via_from = rsa::pkcs1v15::SigningKey::<sha2::Sha256>::from(priv_key.clone());
let sig_via_new = key_via_new.sign(msg);
let sig_via_from = key_via_from.sign(msg);
assert_eq!(sig_via_new, sig_via_from);
// each verifies the other
assert!(key_via_new
.verifying_key()
.verify(msg, &sig_via_from)
.is_ok());
assert!(key_via_from
.verifying_key()
.verify(msg, &sig_via_new)
.is_ok());
}
+329
View File
@@ -0,0 +1,329 @@
//! PKCS#8 encoding tests
#![cfg(feature = "encoding")]
use crypto_bigint::{BoxedUint, CtEq};
use hex_literal::hex;
use rsa::{
pkcs1v15,
pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey},
pss,
traits::{PrivateKeyParts, PublicKeyParts},
RsaPrivateKey, RsaPublicKey,
};
use sha2::Sha256;
#[cfg(feature = "encoding")]
use rsa::pkcs8::LineEnding;
/// RSA-2048 PKCS#8 private key encoded as ASN.1 DER
const RSA_2048_PRIV_DER: &[u8] = include_bytes!("examples/pkcs8/rsa2048-priv.der");
/// RSA-2048 `SubjectPublicKeyInfo` encoded as ASN.1 DER
const RSA_2048_PUB_DER: &[u8] = include_bytes!("examples/pkcs8/rsa2048-pub.der");
/// RSA-2048 PKCS#8 private key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_2048_PRIV_PEM: &str = include_str!("examples/pkcs8/rsa2048-priv.pem");
/// RSA-2048 PKCS#8 public key encoded as PEM
#[cfg(feature = "encoding")]
const RSA_2048_PUB_PEM: &str = include_str!("examples/pkcs8/rsa2048-pub.pem");
/// RSA-2048 PSS PKCS#8 private key encoded as DER
const RSA_2048_PSS_PRIV_DER: &[u8] = include_bytes!("examples/pkcs8/rsa2048-rfc9421-priv.der");
/// RSA-2048 PSS PKCS#8 public key encoded as DER
const RSA_2048_PSS_PUB_DER: &[u8] = include_bytes!("examples/pkcs8/rsa2048-rfc9421-pub.der");
#[test]
fn decode_rsa2048_priv_der() {
let key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap();
// Note: matches PKCS#1 test vectors
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 32).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BD"
"CFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B7"
"58A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455"
"DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A691"
"0C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5C"
"C27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F"
"76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DB"
"ABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225"
"EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422"
"ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBE"
"B143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FD"
"E65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E422"
"8DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B"
"02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
let _ = pkcs1v15::SigningKey::<Sha256>::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap();
}
#[test]
fn decode_rsa2048_pub_der() {
let key = RsaPublicKey::from_public_key_der(RSA_2048_PUB_DER).unwrap();
// Note: matches PKCS#1 test vectors
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
let _ = pkcs1v15::VerifyingKey::<Sha256>::from_public_key_der(RSA_2048_PUB_DER).unwrap();
}
#[test]
fn decode_rsa2048_pss_priv_der() {
let key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PSS_PRIV_DER).unwrap();
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"AF8B669B7AF6D1677F3DBAAF3F5B36F9012DBE9B91695F18AB8D208D447CCB64"
"63C5AE9DA46D865C76CF7EF32CF1CB7E2E1D461F8E71DBC470DD1CB9DE69BEA0"
"05E3C90F3A3A70E467937C9586E0803E0EDF0E8CEA902F2E4864F79027753AE2"
"7DB2053CD53C3CF30EECECAB1401EA803B339E33C59933AD08470DD99D45A568"
"1C870B982CF2FE5A892A96D775D67AAACE2F9B27D72F48A00361D50000DE5652"
"DCDDA62CBA2DB4E04B13FBA1C894E139F483923A683649EC0F0BCE8D0A4B2658"
"A00E3CE66A9C3B419501D570F65AB868E4FDBFA77E9DBE1B9CD91056494B4377"
"D502F266FB17433A9F4B08D08DE3C576A670CE90557AF94F67579A3273A5C8DB"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"9407C8A9FA426289954A17C02A7C1FDA50FD234C0A8E41EC0AD64289FE24025C"
"10AAA5BA37EB482F76DD391F9559FD10D590480EDA4EF7552B1BBA5A9ECCAB3C"
"445B36B44994F8981323D31E4093D670FE9768ACBA2C862CD04D9C5A0A7C1800"
"E0A01B3C96506AD14857D0A7DF82521E7A4DE7ED9E86B7860581ED9301C5B659"
"B3785DF2BB96EA45CA8E871F25918981CC3004505CB25E3927539F968C04FD0F"
"3B86D0CA4E4E4714D449E39C88F254164B501E4BC66F29BB2ABC847F01FC4E4B"
"342FB5A1CF23FAD0F2F7C52F4534E262F66FB3CEDC1821718342E28CD860EC21"
"3783DA6236A07A0F332003D30748EC1C12556D7CA7587E8E07DCE1D95EC4A611"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"E55FBA212239C846821579BE7E4D44336C700167A478F542032BEBF506D39453"
"82670B7D5B08D48E1B4A46EB22E54ABE21867FB6AD96444E00B386FF14710CB6"
"9D80111E3721CBE65CFA8A141A1492D5434BB7538481EBB27462D54EDD1EA55D"
"C2230431EE63C4A3609EC28BA67ABEE0DCA1A12E8E796BB5485A331BD27DC509"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"C3EC0875ED7B5B96340A9869DD9674B8CF0E52AD4092B57620A6AEA981DA0F10"
"13DF610CE1C8B630C111DA7214128E20FF8DA55B4CD8A2E145A8E370BF4F87C8"
"EB203E9752A8A442E562E09F455769B8DA35CCBA2A134F5DE274020B6A7620F0"
"3DE276FCBFDE2B0356438DD17DD40152AB80C1277B4849A643CB158AA07ADBC3"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
let _ = pss::SigningKey::<Sha256>::from_pkcs8_der(RSA_2048_PSS_PRIV_DER).unwrap();
}
#[test]
fn decode_rsa2048_pss_pub_der() {
let key = RsaPublicKey::from_public_key_der(RSA_2048_PSS_PUB_DER).unwrap();
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"AF8B669B7AF6D1677F3DBAAF3F5B36F9012DBE9B91695F18AB8D208D447CCB64"
"63C5AE9DA46D865C76CF7EF32CF1CB7E2E1D461F8E71DBC470DD1CB9DE69BEA0"
"05E3C90F3A3A70E467937C9586E0803E0EDF0E8CEA902F2E4864F79027753AE2"
"7DB2053CD53C3CF30EECECAB1401EA803B339E33C59933AD08470DD99D45A568"
"1C870B982CF2FE5A892A96D775D67AAACE2F9B27D72F48A00361D50000DE5652"
"DCDDA62CBA2DB4E04B13FBA1C894E139F483923A683649EC0F0BCE8D0A4B2658"
"A00E3CE66A9C3B419501D570F65AB868E4FDBFA77E9DBE1B9CD91056494B4377"
"D502F266FB17433A9F4B08D08DE3C576A670CE90557AF94F67579A3273A5C8DB"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
let _ = pss::VerifyingKey::<Sha256>::from_public_key_der(RSA_2048_PSS_PUB_DER).unwrap();
}
#[test]
fn encode_rsa2048_priv_der() {
let key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap();
let der = key.to_pkcs8_der().unwrap();
assert_eq!(der.as_bytes(), RSA_2048_PRIV_DER);
let pkcs1v15_key = pkcs1v15::SigningKey::<Sha256>::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap();
let pkcs1v15_der = pkcs1v15_key.to_pkcs8_der().unwrap();
assert_eq!(pkcs1v15_der.as_bytes(), RSA_2048_PRIV_DER);
}
#[test]
fn encode_rsa2048_pub_der() {
let key = RsaPublicKey::from_public_key_der(RSA_2048_PUB_DER).unwrap();
let der = key.to_public_key_der().unwrap();
assert_eq!(der.as_ref(), RSA_2048_PUB_DER);
let pkcs1v15_key =
pkcs1v15::VerifyingKey::<Sha256>::from_public_key_der(RSA_2048_PUB_DER).unwrap();
let pkcs1v15_der = pkcs1v15_key.to_public_key_der().unwrap();
assert_eq!(pkcs1v15_der.as_ref(), RSA_2048_PUB_DER);
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa2048_priv_pem() {
let key = RsaPrivateKey::from_pkcs8_pem(RSA_2048_PRIV_PEM).unwrap();
// Note: matches PKCS#1 test vectors
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
assert_eq!(
&key.d().to_be_bytes()[..],
&hex!(
"7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BD"
"CFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B7"
"58A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455"
"DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A691"
"0C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5C"
"C27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F"
"76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DB"
"ABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1"
)
);
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225"
"EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422"
"ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBE"
"B143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[0].ct_eq(&expected_prime)));
let expected_prime = BoxedUint::from_be_slice(
&hex!(
"D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FD"
"E65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E422"
"8DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B"
"02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9"
),
1024,
)
.unwrap();
assert!(bool::from(key.primes()[1].ct_eq(&expected_prime)));
let _ = pkcs1v15::SigningKey::<Sha256>::from_pkcs8_pem(RSA_2048_PRIV_PEM).unwrap();
}
#[test]
#[cfg(feature = "encoding")]
fn decode_rsa2048_pub_pem() {
let key = RsaPublicKey::from_public_key_pem(RSA_2048_PUB_PEM).unwrap();
// Note: matches PKCS#1 test vectors
assert_eq!(
&key.n().to_be_bytes()[..],
&hex!(
"B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36"
"E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C"
"76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F"
"599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332"
"266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C"
"575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEA"
"C89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC"
"90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F"
)
);
let expected_e = BoxedUint::from_be_slice(&hex!("010001"), 128).unwrap();
assert!(bool::from(key.e().ct_eq(&expected_e)));
let _ = pkcs1v15::VerifyingKey::<Sha256>::from_public_key_pem(RSA_2048_PUB_PEM).unwrap();
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa2048_priv_pem() {
let key = RsaPrivateKey::from_pkcs8_pem(RSA_2048_PRIV_PEM).unwrap();
let pem = key.to_pkcs8_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_2048_PRIV_PEM)
}
#[test]
#[cfg(feature = "encoding")]
fn encode_rsa2048_pub_pem() {
let key = RsaPublicKey::from_public_key_pem(RSA_2048_PUB_PEM).unwrap();
let pem = key.to_public_key_pem(LineEnding::LF).unwrap();
assert_eq!(&*pem, RSA_2048_PUB_PEM)
}
+7
View File
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 6eb8993a76d99005d1cb0f3d848d5390c3e0f4f2de4a7517eccfb477f74e13a0 # shrinks to private_key = RsaPrivateKey { pubkey_components: RsaPublicKey { n: NonZero(BoxedUint(0x8347C96BF9CDBB267650CB931400D5091139DB988E11C5AAF9EAC86BA5D4EA3EEBA0569077555B3FA4CE0D41300461BF8926A34B7993A48B1F3F69CAB3158DFB)), e: 65537, n_params: BoxedMontyParams { modulus: Odd(BoxedUint(0x8347C96BF9CDBB267650CB931400D5091139DB988E11C5AAF9EAC86BA5D4EA3EEBA0569077555B3FA4CE0D41300461BF8926A34B7993A48B1F3F69CAB3158DFB)), one: BoxedUint(0x7CB83694063244D989AF346CEBFF2AF6EEC6246771EE3A55061537945A2B15C1145FA96F88AAA4C05B31F2BECFFB9E4076D95CB4866C5B74E0C096354CEA7205), r2: BoxedUint(0x70E018F6DD63DB9D8182776C303A6B688E9D44CEE054FF801E11E9DEA040862E9E8EC3E4CC0FF3B0D573D09C381621AB35B7C6CDC49098E583F643AAC2238D65), r3: BoxedUint(0x1ADF6E5E9A880615C0CC586BB70BA0D657CF3F1624A68671A192471E75F4CD56A401C11B483909871F0FA8554275EA17ABA04BE17F88AF9B749F44D591277079), mod_neg_inv: Limb(0xD0EBDD5E695C8ACD) } }, d: BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000002F08B129763E3726F88CC9E2CFEFDC637B40776498C1D5480472118C3FC5A08694CCAE7DCBFD25B7850C79332F5F100111BEED9DC0A7B8D8C37EB657E4985081), primes: [BoxedUint(0x981BE188EF711A1E2C840EC3CE9A7F3B7F5BB8E81F09A5A13E00EF2F895F4213), BoxedUint(0xDCF20F8FD566A26BC0FD581259F9A2AABF0ADB6C01F2A5ADD2AEFA0DEAA5C179)], precomputed: Some(PrecomputedValues { dp: BoxedUint(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053084D3A51F2BC9E2210C87A8CCA7B8FBFFB12D9EB2F79F1A6061E8B2583116F), dq: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007056BB46DCB044A1190D373C8D76FA186AEE7046686F218251FF19B0FDBFADB1), qinv: BoxedMontyForm { montgomery_form: BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000682511332F037EAFDE67A19620CFDC9961A7FF261F4F185D49E5EF21E2686753), params: BoxedMontyParams { modulus: Odd(BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000981BE188EF711A1E2C840EC3CE9A7F3B7F5BB8E81F09A5A13E00EF2F895F4213)), one: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006BB5F6A9EDD4349E0EF3418B86FD1D88DAF170653B6F050CAD2062140743E1EE), r2: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003C67CD14A702D9D3E7D7790AE0DB96B7E2DA351552A50382262CF0D0BB51E17D), r3: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001C83D6B4CE99FAFBB23D7586BA520C62B5206D43755A767BEBB5764A015BF27D), mod_neg_inv: Limb(0xC61D8CFC698327E5) } }, p_params: BoxedMontyParams { modulus: Odd(BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000981BE188EF711A1E2C840EC3CE9A7F3B7F5BB8E81F09A5A13E00EF2F895F4213)), one: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006BB5F6A9EDD4349E0EF3418B86FD1D88DAF170653B6F050CAD2062140743E1EE), r2: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003C67CD14A702D9D3E7D7790AE0DB96B7E2DA351552A50382262CF0D0BB51E17D), r3: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001C83D6B4CE99FAFBB23D7586BA520C62B5206D43755A767BEBB5764A015BF27D), mod_neg_inv: Limb(0xC61D8CFC698327E5) }, q_params: BoxedMontyParams { modulus: Odd(BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000DCF20F8FD566A26BC0FD581259F9A2AABF0ADB6C01F2A5ADD2AEFA0DEAA5C179)), one: BoxedUint(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007FFB34771D54239EC9DEDC1D2108D7D70D73B05E764B8E38EEE1014C49C29BF5), r2: BoxedUint(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A29B3272D65244B833205BCB2F0670190F04A5C945B416487C3F470C9A6126F6), r3: BoxedUint(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033E334261962415DBDE417A3E2844C84E5252176396B638A3873E3B5A75352DE), mod_neg_inv: Limb(0x6092436DE4BA2737) } }) }, msg = []
+44
View File
@@ -0,0 +1,44 @@
//! Property-based tests.
#![cfg(feature = "hazmat")]
use proptest::prelude::*;
use rand::rngs::ChaCha8Rng;
use rand_core::SeedableRng;
use rsa::{
pkcs1v15,
signature::{Keypair, SignatureEncoding, Signer, Verifier},
RsaPrivateKey,
};
use sha2::Sha256;
prop_compose! {
// WARNING: do *NOT* copy and paste this code. It's insecure and optimized for test speed.
fn private_key()(seed in any::<[u8; 32]>()) -> RsaPrivateKey {
let mut rng = ChaCha8Rng::from_seed(seed);
RsaPrivateKey::new_unchecked(&mut rng, 512).unwrap()
}
}
proptest! {
#[test]
fn pkcs1v15_sign_roundtrip(private_key in private_key(), msg in any::<Vec<u8>>()) {
let signing_key = pkcs1v15::SigningKey::<Sha256>::new(private_key);
let signature_bytes = signing_key.sign(&msg).to_bytes();
let verifying_key = signing_key.verifying_key();
let signature = pkcs1v15::Signature::try_from(&*signature_bytes).unwrap();
prop_assert!(verifying_key.verify(&msg, &signature).is_ok());
}
// TODO(tarcieri): debug why these are failing
// #[test]
// fn pss_sign_roundtrip(private_key in private_key(), msg in any::<Vec<u8>>()) {
// let signing_key = pss::SigningKey::<Sha256>::new(private_key);
// let signature_bytes = signing_key.sign(&msg).to_bytes();
//
// let verifying_key = signing_key.verifying_key();
// let signature = pss::Signature::try_from(&*signature_bytes).unwrap();
// prop_assert!(verifying_key.verify(&msg, &signature).is_ok());
// }
}
+272
View File
@@ -0,0 +1,272 @@
//! Executes tests based on the wycheproof testsuite.
#![cfg(feature = "encoding")]
// This implementation here is based on
// <https://github.com/ctz/graviola/blob/main/graviola/tests/wycheproof.rs>
use std::fs::File;
use pkcs1::DecodeRsaPublicKey;
use rsa::{
pkcs1v15, pss,
signature::{Error as SignatureError, Verifier},
RsaPublicKey,
};
use rstest::rstest;
use serde::Deserialize;
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512};
#[derive(Deserialize, Debug)]
struct TestFile {
#[serde(rename(deserialize = "testGroups"))]
groups: Vec<TestGroup>,
header: Vec<String>,
algorithm: String,
}
#[derive(Deserialize, Debug)]
struct TestGroup {
#[serde(rename(deserialize = "type"))]
typ: String,
#[serde(default, rename(deserialize = "publicKeyAsn"), with = "hex::serde")]
public_key_asn: Vec<u8>,
#[serde(default)]
sha: String,
#[serde(default, rename(deserialize = "mgfSha"))]
mgf_sha: String,
#[serde(default, rename(deserialize = "sLen"))]
salt_len: usize,
tests: Vec<Test>,
}
#[derive(Deserialize, Debug)]
struct Test {
#[serde(rename(deserialize = "tcId"))]
#[allow(unused)] // for Debug
id: usize,
#[allow(unused)] // for Debug
comment: String,
#[serde(default, with = "hex::serde")]
msg: Vec<u8>,
#[serde(default, with = "hex::serde")]
sig: Vec<u8>,
result: ExpectedResult,
}
#[derive(Copy, Clone, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
enum ExpectedResult {
Valid,
Invalid,
Acceptable,
}
#[derive(Debug)]
struct Summary {
started: usize,
skipped: usize,
failed: usize,
in_test: bool,
}
impl Summary {
fn new() -> Self {
Self {
started: 0,
skipped: 0,
failed: 0,
in_test: false,
}
}
fn fail(&mut self, test: Test, res: Option<SignatureError>) {
if self.in_test {
eprintln!(
" failed: {}: expected {:?}, got {:?}",
test.id, test.result, res
);
self.failed += 1;
self.in_test = false;
}
}
fn group(&mut self, group: &TestGroup) {
println!(" group: {:?}", group.typ);
self.in_test = false;
}
fn start(&mut self, test: &Test) {
println!(" test {}:", test.id);
self.started += 1;
self.in_test = true;
}
fn skipped(&mut self, why: &str) {
if self.in_test {
println!(" skipped: {why}");
self.skipped += 1;
self.in_test = false;
} else {
println!(" skipped group: {why}");
}
}
}
impl Drop for Summary {
fn drop(&mut self) {
let passed = self.started - self.skipped - self.failed;
println!(
"DONE: started {} passed {} skipped {} failed {}",
self.started, passed, self.skipped, self.failed
);
assert!(passed > 0, "no tests have passed");
if self.failed > 0 {
panic!("{} tests failed", self.failed);
}
}
}
#[rstest]
#[case("rsa_signature_2048_sha256_test.json")]
#[case("rsa_signature_2048_sha384_test.json")]
#[case("rsa_signature_2048_sha512_test.json")]
#[case("rsa_signature_3072_sha256_test.json")]
#[case("rsa_signature_3072_sha384_test.json")]
#[case("rsa_signature_3072_sha512_test.json")]
#[case("rsa_signature_4096_sha256_test.json")]
#[case("rsa_signature_4096_sha384_test.json")]
#[case("rsa_signature_4096_sha512_test.json")]
// #[case("rsa_signature_8192_sha256_test.json")] TODO: needs disabling of maxsize
// #[case("rsa_signature_8192_sha384_test.json")] TODO: needs disabling of maxsize
// #[case("rsa_signature_8192_sha512_test.json")] TODO: needs disabling of maxsize
fn test_rsa_pkcs1_verify(#[case] file: &str) {
let path = format!("thirdparty/wycheproof/testvectors_v1/{file}");
let data_file = File::open(&path)
.expect("failed to open data file (try running `git submodule update --init`)");
println!("Loading file: {path}");
let tests: TestFile = serde_json::from_reader(data_file).expect("invalid test JSON");
println!("{}:\n{}\n", tests.algorithm, tests.header.join(""));
let mut summary = Summary::new();
for group in tests.groups {
summary.group(&group);
let key = RsaPublicKey::from_pkcs1_der(&group.public_key_asn).unwrap();
println!("key is {:?}", key);
for test in group.tests {
summary.start(&test);
let sig = pkcs1v15::Signature::try_from(&test.sig[..]).expect("invalid signature");
let result = match group.sha.as_ref() {
"SHA-256" => {
let vk = pkcs1v15::VerifyingKey::<Sha256>::new(key.clone());
vk.verify(&test.msg, &sig)
}
"SHA-384" => {
let vk = pkcs1v15::VerifyingKey::<Sha384>::new(key.clone());
vk.verify(&test.msg, &sig)
}
"SHA-512" => {
let vk = pkcs1v15::VerifyingKey::<Sha512>::new(key.clone());
vk.verify(&test.msg, &sig)
}
other => panic!("unhandled sha {other:?}"),
};
match (test.result, &result) {
(ExpectedResult::Valid, Ok(())) => {}
(ExpectedResult::Invalid | ExpectedResult::Acceptable, Err(_err)) => {}
_ => summary.fail(test, result.err()),
}
}
}
}
#[rstest]
#[case("rsa_pss_2048_sha256_mgf1_0_test.json")]
#[case("rsa_pss_2048_sha256_mgf1_32_test.json")]
#[case("rsa_pss_2048_sha384_mgf1_48_test.json")]
#[case("rsa_pss_3072_sha256_mgf1_32_test.json")]
#[case("rsa_pss_4096_sha256_mgf1_32_test.json")]
#[case("rsa_pss_4096_sha384_mgf1_48_test.json")]
#[case("rsa_pss_4096_sha512_mgf1_64_test.json")]
#[case("rsa_pss_misc_test.json")]
fn test_rsa_pss_verify(#[case] file: &str) {
let path = format!("thirdparty/wycheproof/testvectors_v1/{file}");
let data_file = File::open(&path)
.expect("failed to open data file (try running `git submodule update --init`)");
println!("Loading file: {path}");
let tests: TestFile = serde_json::from_reader(data_file).expect("invalid test JSON");
println!("{}:\n{}\n", tests.algorithm, tests.header.join(""));
let mut summary = Summary::new();
for group in tests.groups {
summary.group(&group);
let key = rsa::RsaPublicKey::from_pkcs1_der(&group.public_key_asn).unwrap();
println!("key is {:?}", key);
for test in group.tests {
summary.start(&test);
if group.sha != group.mgf_sha {
summary.skipped(&format!(
"pss with sha={} mgf={} salt_len={} not supported",
group.sha, group.mgf_sha, group.salt_len,
));
}
let sig = pss::Signature::try_from(&test.sig[..]).expect("invalid signature");
let result = match group.sha.as_ref() {
"SHA-1" => {
let vk =
pss::VerifyingKey::<Sha1>::new_with_salt_len(key.clone(), group.salt_len);
vk.verify(&test.msg, &sig)
}
"SHA-256" => {
let vk =
pss::VerifyingKey::<Sha256>::new_with_salt_len(key.clone(), group.salt_len);
vk.verify(&test.msg, &sig)
}
"SHA-224" => {
let vk =
pss::VerifyingKey::<Sha224>::new_with_salt_len(key.clone(), group.salt_len);
vk.verify(&test.msg, &sig)
}
"SHA-384" => {
let vk =
pss::VerifyingKey::<Sha384>::new_with_salt_len(key.clone(), group.salt_len);
vk.verify(&test.msg, &sig)
}
"SHA-512" => {
let vk =
pss::VerifyingKey::<Sha512>::new_with_salt_len(key.clone(), group.salt_len);
vk.verify(&test.msg, &sig)
}
other => panic!("unhandled sha {other:?}"),
};
match (test.result, &result) {
(ExpectedResult::Valid, Ok(())) => {}
(ExpectedResult::Invalid | ExpectedResult::Acceptable, Err(_err)) => {}
_ => {
summary.fail(test, result.err());
}
};
}
}
}