fix(servo): gate vulnerable RSA private operations
This commit is contained in:
Generated
-2
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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"
|
||||
));
|
||||
@@ -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>"#;
|
||||
|
||||
Vendored
+238
@@ -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)
|
||||
Vendored
+255
@@ -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
@@ -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
|
||||
Vendored
+13
@@ -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.
|
||||
Vendored
+201
@@ -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.
|
||||
Vendored
+23
@@ -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.
|
||||
Vendored
+123
@@ -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
|
||||
Vendored
+17
@@ -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.
|
||||
Vendored
+122
@@ -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);
|
||||
});
|
||||
}
|
||||
Vendored
+10
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+24
@@ -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 {}
|
||||
Vendored
+240
@@ -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,
|
||||
}
|
||||
}
|
||||
Vendored
+141
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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};
|
||||
Vendored
+1305
File diff suppressed because it is too large
Load Diff
Vendored
+269
@@ -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;
|
||||
Vendored
+612
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+675
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+675
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+106
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -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
@@ -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;
|
||||
}
|
||||
Vendored
+93
@@ -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
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
Vendored
+506
@@ -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)
|
||||
}
|
||||
Vendored
+80
@@ -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());
|
||||
}
|
||||
Vendored
+329
@@ -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)
|
||||
}
|
||||
@@ -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 = []
|
||||
Vendored
+44
@@ -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());
|
||||
// }
|
||||
}
|
||||
Vendored
+272
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user