From e085174b89f3fbf9bc39b60dc7ed2967530e157f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 10 Jul 2026 01:06:48 -0400 Subject: [PATCH] fix(servo): gate vulnerable RSA private operations --- Cargo.lock | 2 - Cargo.toml | 2 + crates/ely_servo_host/tests/sidecar.rs | 18 + crates/ely_servo_host/tests/sidecar/pages.rs | 81 + .../ely_servo_host/tests/sidecar/support.rs | 40 +- third_party/rsa/CHANGELOG.md | 238 +++ third_party/rsa/Cargo.toml | 255 ++++ third_party/rsa/Cargo.toml.orig | 71 + third_party/rsa/ELY_PATCHES.md | 13 + third_party/rsa/LICENSE-APACHE | 201 +++ third_party/rsa/LICENSE-MIT | 23 + third_party/rsa/README.md | 123 ++ third_party/rsa/SECURITY.md | 17 + third_party/rsa/benches/key.rs | 122 ++ third_party/rsa/src/algorithms.rs | 10 + third_party/rsa/src/algorithms/generate.rs | 183 +++ third_party/rsa/src/algorithms/mgf.rs | 78 + third_party/rsa/src/algorithms/oaep.rs | 260 ++++ third_party/rsa/src/algorithms/pad.rs | 63 + third_party/rsa/src/algorithms/pkcs1v15.rs | 213 +++ third_party/rsa/src/algorithms/pss.rs | 383 +++++ third_party/rsa/src/algorithms/rsa.rs | 484 ++++++ third_party/rsa/src/dummy_rng.rs | 24 + third_party/rsa/src/encoding.rs | 240 +++ third_party/rsa/src/errors.rs | 141 ++ third_party/rsa/src/hazmat.rs | 14 + third_party/rsa/src/key.rs | 1305 +++++++++++++++++ third_party/rsa/src/lib.rs | 269 ++++ third_party/rsa/src/oaep.rs | 612 ++++++++ third_party/rsa/src/oaep/decrypting_key.rs | 139 ++ third_party/rsa/src/oaep/encrypting_key.rs | 111 ++ third_party/rsa/src/pkcs1v15.rs | 675 +++++++++ .../rsa/src/pkcs1v15/decrypting_key.rs | 86 ++ .../rsa/src/pkcs1v15/encrypting_key.rs | 58 + third_party/rsa/src/pkcs1v15/signature.rs | 112 ++ third_party/rsa/src/pkcs1v15/signing_key.rs | 358 +++++ third_party/rsa/src/pkcs1v15/verifying_key.rs | 270 ++++ third_party/rsa/src/pss.rs | 675 +++++++++ .../rsa/src/pss/blinded_signing_key.rs | 307 ++++ third_party/rsa/src/pss/signature.rs | 106 ++ third_party/rsa/src/pss/signing_key.rs | 346 +++++ third_party/rsa/src/pss/verifying_key.rs | 265 ++++ third_party/rsa/src/traits.rs | 9 + third_party/rsa/src/traits/encryption.rs | 38 + third_party/rsa/src/traits/keys.rs | 93 ++ third_party/rsa/src/traits/padding.rs | 49 + .../rsa/tests/examples/pkcs1/rsa2048-priv.der | Bin 0 -> 1191 bytes .../rsa/tests/examples/pkcs1/rsa2048-priv.pem | 27 + .../rsa/tests/examples/pkcs1/rsa2048-pub.der | Bin 0 -> 270 bytes .../rsa/tests/examples/pkcs1/rsa2048-pub.pem | 8 + .../rsa/tests/examples/pkcs1/rsa4096-priv.der | Bin 0 -> 2349 bytes .../rsa/tests/examples/pkcs1/rsa4096-priv.pem | 51 + .../rsa/tests/examples/pkcs1/rsa4096-pub.der | Bin 0 -> 526 bytes .../rsa/tests/examples/pkcs1/rsa4096-pub.pem | 13 + .../rsa/tests/examples/pkcs8/rsa2048-priv.der | Bin 0 -> 1217 bytes .../rsa/tests/examples/pkcs8/rsa2048-priv.pem | 28 + .../rsa/tests/examples/pkcs8/rsa2048-pub.der | Bin 0 -> 294 bytes .../rsa/tests/examples/pkcs8/rsa2048-pub.pem | 9 + .../examples/pkcs8/rsa2048-rfc9421-priv.der | Bin 0 -> 1218 bytes .../examples/pkcs8/rsa2048-rfc9421-pub.der | Bin 0 -> 294 bytes .../examples/pkcs8/rsa2048-sp800-56b-priv.der | Bin 0 -> 1217 bytes third_party/rsa/tests/pkcs1.rs | 506 +++++++ third_party/rsa/tests/pkcs1v15.rs | 80 + third_party/rsa/tests/pkcs8.rs | 329 +++++ .../rsa/tests/proptests.proptest-regressions | 7 + third_party/rsa/tests/proptests.rs | 44 + third_party/rsa/tests/wycheproof.rs | 272 ++++ 67 files changed, 10535 insertions(+), 21 deletions(-) create mode 100644 crates/ely_servo_host/tests/sidecar/pages.rs create mode 100644 third_party/rsa/CHANGELOG.md create mode 100644 third_party/rsa/Cargo.toml create mode 100644 third_party/rsa/Cargo.toml.orig create mode 100644 third_party/rsa/ELY_PATCHES.md create mode 100644 third_party/rsa/LICENSE-APACHE create mode 100644 third_party/rsa/LICENSE-MIT create mode 100644 third_party/rsa/README.md create mode 100644 third_party/rsa/SECURITY.md create mode 100644 third_party/rsa/benches/key.rs create mode 100644 third_party/rsa/src/algorithms.rs create mode 100644 third_party/rsa/src/algorithms/generate.rs create mode 100644 third_party/rsa/src/algorithms/mgf.rs create mode 100644 third_party/rsa/src/algorithms/oaep.rs create mode 100644 third_party/rsa/src/algorithms/pad.rs create mode 100644 third_party/rsa/src/algorithms/pkcs1v15.rs create mode 100644 third_party/rsa/src/algorithms/pss.rs create mode 100644 third_party/rsa/src/algorithms/rsa.rs create mode 100644 third_party/rsa/src/dummy_rng.rs create mode 100644 third_party/rsa/src/encoding.rs create mode 100644 third_party/rsa/src/errors.rs create mode 100644 third_party/rsa/src/hazmat.rs create mode 100644 third_party/rsa/src/key.rs create mode 100644 third_party/rsa/src/lib.rs create mode 100644 third_party/rsa/src/oaep.rs create mode 100644 third_party/rsa/src/oaep/decrypting_key.rs create mode 100644 third_party/rsa/src/oaep/encrypting_key.rs create mode 100644 third_party/rsa/src/pkcs1v15.rs create mode 100644 third_party/rsa/src/pkcs1v15/decrypting_key.rs create mode 100644 third_party/rsa/src/pkcs1v15/encrypting_key.rs create mode 100644 third_party/rsa/src/pkcs1v15/signature.rs create mode 100644 third_party/rsa/src/pkcs1v15/signing_key.rs create mode 100644 third_party/rsa/src/pkcs1v15/verifying_key.rs create mode 100644 third_party/rsa/src/pss.rs create mode 100644 third_party/rsa/src/pss/blinded_signing_key.rs create mode 100644 third_party/rsa/src/pss/signature.rs create mode 100644 third_party/rsa/src/pss/signing_key.rs create mode 100644 third_party/rsa/src/pss/verifying_key.rs create mode 100644 third_party/rsa/src/traits.rs create mode 100644 third_party/rsa/src/traits/encryption.rs create mode 100644 third_party/rsa/src/traits/keys.rs create mode 100644 third_party/rsa/src/traits/padding.rs create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa2048-priv.der create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa2048-priv.pem create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa2048-pub.der create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa2048-pub.pem create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa4096-priv.der create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa4096-priv.pem create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa4096-pub.der create mode 100644 third_party/rsa/tests/examples/pkcs1/rsa4096-pub.pem create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-priv.der create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-priv.pem create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-pub.der create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-pub.pem create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-rfc9421-priv.der create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-rfc9421-pub.der create mode 100644 third_party/rsa/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der create mode 100644 third_party/rsa/tests/pkcs1.rs create mode 100644 third_party/rsa/tests/pkcs1v15.rs create mode 100644 third_party/rsa/tests/pkcs8.rs create mode 100644 third_party/rsa/tests/proptests.proptest-regressions create mode 100644 third_party/rsa/tests/proptests.rs create mode 100644 third_party/rsa/tests/wycheproof.rs diff --git a/Cargo.lock b/Cargo.lock index 0e4b0cb..26f8dca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index fbdf38c..83590d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/crates/ely_servo_host/tests/sidecar.rs b/crates/ely_servo_host/tests/sidecar.rs index 2038503..58a1efd 100644 --- a/crates/ely_servo_host/tests/sidecar.rs +++ b/crates/ely_servo_host/tests/sidecar.rs @@ -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> { Ok(()) } +#[test] +fn live_sidecar_gates_rsa_private_operations() -> Result<(), Box> { + 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> { let root = TestDirectory::new()?; diff --git a/crates/ely_servo_host/tests/sidecar/pages.rs b/crates/ely_servo_host/tests/sidecar/pages.rs new file mode 100644 index 0000000..9fbd592 --- /dev/null +++ b/crates/ely_servo_host/tests/sidecar/pages.rs @@ -0,0 +1,81 @@ +pub(super) const SET_PAGE: &str = r#"loadingProfile persistence"#; + +pub(super) const READ_PAGE: &str = r#"loadingProfile persistence"#; + +pub(super) const HISTORY_PAGE: &str = r#"loadingHistory mutation"#; + +pub(super) const OVERSIZED_HISTORY_PAGE: &str = r#"oversized-history"#; + +pub(super) const WHITE_PAGE: &str = r#"white-ready"#; + +pub(super) const RSA_PRIVATE_OPERATION_PAGE: &str = r#"rsa-private-operations-loadingRSA private-operation gate"#; + +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" +)); diff --git a/crates/ely_servo_host/tests/sidecar/support.rs b/crates/ely_servo_host/tests/sidecar/support.rs index c227262..c65e4df 100644 --- a/crates/ely_servo_host/tests/sidecar/support.rs +++ b/crates/ely_servo_host/tests/sidecar/support.rs @@ -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#"loadingProfile persistence"#; - -const READ_PAGE: &str = r#"loadingProfile persistence"#; - -const HISTORY_PAGE: &str = r#"loadingHistory mutation"#; - -const OVERSIZED_HISTORY_PAGE: &str = r#"oversized-history"#; - -const WHITE_PAGE: &str = r#"white-ready"#; diff --git a/third_party/rsa/CHANGELOG.md b/third_party/rsa/CHANGELOG.md new file mode 100644 index 0000000..5d091bd --- /dev/null +++ b/third_party/rsa/CHANGELOG.md @@ -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` 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` 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) diff --git a/third_party/rsa/Cargo.toml b/third_party/rsa/Cargo.toml new file mode 100644 index 0000000..58985bd --- /dev/null +++ b/third_party/rsa/Cargo.toml @@ -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 ", +] +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 diff --git a/third_party/rsa/Cargo.toml.orig b/third_party/rsa/Cargo.toml.orig new file mode 100644 index 0000000..407efa2 --- /dev/null +++ b/third_party/rsa/Cargo.toml.orig @@ -0,0 +1,71 @@ +[package] +name = "rsa" +version = "0.10.0-rc.18" +authors = ["RustCrypto Developers", "dignifiedquire "] +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 diff --git a/third_party/rsa/ELY_PATCHES.md b/third_party/rsa/ELY_PATCHES.md new file mode 100644 index 0000000..aedcd98 --- /dev/null +++ b/third_party/rsa/ELY_PATCHES.md @@ -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. diff --git a/third_party/rsa/LICENSE-APACHE b/third_party/rsa/LICENSE-APACHE new file mode 100644 index 0000000..f8e5e5e --- /dev/null +++ b/third_party/rsa/LICENSE-APACHE @@ -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. \ No newline at end of file diff --git a/third_party/rsa/LICENSE-MIT b/third_party/rsa/LICENSE-MIT new file mode 100644 index 0000000..468cd79 --- /dev/null +++ b/third_party/rsa/LICENSE-MIT @@ -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. \ No newline at end of file diff --git a/third_party/rsa/README.md b/third_party/rsa/README.md new file mode 100644 index 0000000..cdd416d --- /dev/null +++ b/third_party/rsa/README.md @@ -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 diff --git a/third_party/rsa/SECURITY.md b/third_party/rsa/SECURITY.md new file mode 100644 index 0000000..e8c5a82 --- /dev/null +++ b/third_party/rsa/SECURITY.md @@ -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. diff --git a/third_party/rsa/benches/key.rs b/third_party/rsa/benches/key.rs new file mode 100644 index 0000000..eb7f123 --- /dev/null +++ b/third_party/rsa/benches/key.rs @@ -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::(), &digest) + .unwrap(); + test::black_box(res); + }); +} diff --git a/third_party/rsa/src/algorithms.rs b/third_party/rsa/src/algorithms.rs new file mode 100644 index 0000000..c74112f --- /dev/null +++ b/third_party/rsa/src/algorithms.rs @@ -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; diff --git a/third_party/rsa/src/algorithms/generate.rs b/third_party/rsa/src/algorithms/generate.rs new file mode 100644 index 0000000..0f24e22 --- /dev/null +++ b/third_party/rsa/src/algorithms/generate.rs @@ -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, + pub e: BoxedUint, + pub d: BoxedUint, + pub primes: Vec, +} + +/// 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( + rng: &mut R, + nprimes: usize, + bit_size: usize, + exp: BoxedUint, +) -> Result { + 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; + 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(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); +} diff --git a/third_party/rsa/src/algorithms/mgf.rs b/third_party/rsa/src/algorithms/mgf.rs new file mode 100644 index 0000000..b0ca553 --- /dev/null +++ b/third_party/rsa/src/algorithms/mgf.rs @@ -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(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(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; + } + } +} diff --git a/third_party/rsa/src/algorithms/oaep.rs b/third_party/rsa/src/algorithms/oaep.rs new file mode 100644 index 0000000..0b9693c --- /dev/null +++ b/third_party/rsa/src/algorithms/oaep.rs @@ -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( + rng: &mut R, + msg: &[u8], + p_hash: &[u8], + h_size: usize, + k: usize, + mut mgf: MGF, +) -> Result>> { + 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( + rng: &mut R, + msg: &[u8], + digest: &mut D, + mgf_digest: &mut MGD, + label: Option>, + k: usize, +) -> Result>> +where + R: TryCryptoRng + ?Sized, + D: Digest + FixedOutputReset, + MGD: Digest + FixedOutputReset, +{ + let h_size = ::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( + rng: &mut R, + msg: &[u8], + label: Option>, + k: usize, +) -> Result>> +where + R: TryCryptoRng + ?Sized, + D: Digest, + MGD: Digest + FixedOutputReset, +{ + let h_size = ::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( + em: &mut [u8], + digest: &mut D, + mgf_digest: &mut MGD, + label: Option>, + k: usize, +) -> Result> +where + D: Digest + FixedOutputReset, + MGD: Digest + FixedOutputReset, +{ + let h_size = ::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( + em: &mut [u8], + label: Option>, + k: usize, +) -> Result> +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + let h_size = ::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( + em: &mut [u8], + h_size: usize, + expected_p_hash: &[u8], + k: usize, + mut mgf: MGF, +) -> Result> { + 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)) +} diff --git a/third_party/rsa/src/algorithms/pad.rs b/third_party/rsa/src/algorithms/pad.rs new file mode 100644 index 0000000..c84ab12 --- /dev/null +++ b/third_party/rsa/src/algorithms/pad.rs @@ -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> { + 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> { + 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> { + 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()); + } +} diff --git a/third_party/rsa/src/algorithms/pkcs1v15.rs b/third_party/rsa/src/algorithms/pkcs1v15.rs new file mode 100644 index 0000000..346d794 --- /dev/null +++ b/third_party/rsa/src/algorithms/pkcs1v15.rs @@ -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( + 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( + rng: &mut R, + msg: &[u8], + k: usize, +) -> Result>> +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, k: usize) -> Result> { + 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, k: usize) -> Result<(u8, Vec, 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> { + 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 0x30 0x06 oid 0x05 0x00 0x04 +#[inline] +pub(crate) fn pkcs1v15_generate_prefix() -> Vec +where + D: Digest + AssociatedOid, +{ + let oid = D::OID.as_bytes(); + let oid_len = oid.len() as u8; + let digest_len = ::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)); + } +} diff --git a/third_party/rsa/src/algorithms/pss.rs b/third_party/rsa/src/algorithms/pss.rs new file mode 100644 index 0000000..42dbf33 --- /dev/null +++ b/third_party/rsa/src/algorithms/pss.rs @@ -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( + m_hash: &[u8], + em_bits: usize, + salt: &[u8], + hash: &mut D, +) -> Result> +where + D: Digest + FixedOutputReset, +{ + // See [1], section 9.1.1 + let h_len = ::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( + m_hash: &[u8], + em_bits: usize, + salt: &[u8], +) -> Result> +where + D: Digest + FixedOutputReset, +{ + // See [1], section 9.1.1 + let h_len = ::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, + 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( + m_hash: &[u8], + em: &mut [u8], + s_len: Option, + 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 = ::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( + m_hash: &[u8], + em: &mut [u8], + s_len: Option, + 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 = ::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::(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) + } +} diff --git a/third_party/rsa/src/algorithms/rsa.rs b/third_party/rsa/src/algorithms/rsa.rs new file mode 100644 index 0000000..3f8b1ab --- /dev/null +++ b/third_party/rsa/src/algorithms/rsa.rs @@ -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(key: &K, m: &BoxedUint) -> Result { + 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( + rng: Option<&mut R>, + priv_key: &impl PrivateKeyParts, + c: &BoxedUint, +) -> Result { + // 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( + priv_key: &impl PrivateKeyParts, + rng: Option<&mut R>, + c: &BoxedUint, +) -> Result { + 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( + 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 = 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, + 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 { + 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 { + 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 { + 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::(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); + } +} diff --git a/third_party/rsa/src/dummy_rng.rs b/third_party/rsa/src/dummy_rng.rs new file mode 100644 index 0000000..539f3c4 --- /dev/null +++ b/third_party/rsa/src/dummy_rng.rs @@ -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 { + unimplemented!(); + } + + fn try_next_u64(&mut self) -> Result { + unimplemented!(); + } + + fn try_fill_bytes(&mut self, _: &mut [u8]) -> Result<(), Self::Error> { + unimplemented!(); + } +} + +impl TryCryptoRng for DummyRng {} diff --git a/third_party/rsa/src/encoding.rs b/third_party/rsa/src/encoding.rs new file mode 100644 index 0000000..086c8c9 --- /dev/null +++ b/third_party/rsa/src/encoding.rs @@ -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::from_be_slice(data, bits).map_err(|_| pkcs1::Error::KeyMalformed) +} + +impl pkcs1::DecodeRsaPrivateKey for RsaPrivateKey { + fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result { + pkcs1::RsaPrivateKey::from_der(bytes)?.try_into() + } +} + +impl pkcs1::DecodeRsaPublicKey for RsaPublicKey { + fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result { + pkcs1::RsaPublicKey::from_der(bytes)?.try_into() + } +} + +impl TryFrom> for RsaPrivateKey { + type Error = pkcs1::Error; + + fn try_from(pkcs1_key: pkcs1::RsaPrivateKey<'_>) -> pkcs1::Result { + 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> for RsaPublicKey { + type Error = pkcs1::Error; + + fn try_from(pkcs1_key: pkcs1::RsaPublicKey<'_>) -> pkcs1::Result { + 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 { + // 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 { + 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> for RsaPrivateKey { + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + 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> for RsaPublicKey { + type Error = spki::Error; + + fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result { + 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 { + 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 { + 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, + } +} diff --git a/third_party/rsa/src/errors.rs b/third_party/rsa/src/errors.rs new file mode 100644 index 0000000..2a69b38 --- /dev/null +++ b/third_party/rsa/src/errors.rs @@ -0,0 +1,141 @@ +//! Error types. + +/// Alias for [`core::result::Result`] with the `rsa` crate's [`Error`] type. +pub type Result = core::result::Result; + +/// 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 for Error { + fn from(err: pkcs1::Error) -> Error { + Error::Pkcs1(err) + } +} + +#[cfg(feature = "encoding")] +impl From for Error { + fn from(err: pkcs8::Error) -> Error { + Error::Pkcs8(err) + } +} +impl From for Error { + fn from(err: crypto_bigint::DecodeError) -> Error { + Error::Decode(err) + } +} + +impl From for signature::Error { + fn from(err: Error) -> Self { + Self::from_source(err) + } +} diff --git a/third_party/rsa/src/hazmat.rs b/third_party/rsa/src/hazmat.rs new file mode 100644 index 0000000..0a5f13c --- /dev/null +++ b/third_party/rsa/src/hazmat.rs @@ -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}; diff --git a/third_party/rsa/src/key.rs b/third_party/rsa/src/key.rs new file mode 100644 index 0000000..d7b1812 --- /dev/null +++ b/third_party/rsa/src/key.rs @@ -0,0 +1,1305 @@ +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; + +use crypto_bigint::{ + modular::{BoxedMontyForm, BoxedMontyParams}, + BoxedUint, ConcatenatingMul, Integer, NonZero, Odd, Resize, +}; +use rand_core::CryptoRng; +use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "serde")] +use { + pkcs8::{DecodePrivateKey, EncodePrivateKey}, + serdect::serde::{de, ser, Deserialize, Serialize}, + spki::{DecodePublicKey, EncodePublicKey}, +}; + +use crate::algorithms::generate::generate_multi_prime_key_with_exp; +use crate::algorithms::rsa::{ + compute_modulus, compute_private_exponent_carmicheal, compute_private_exponent_euler_totient, + recover_primes, +}; + +use crate::dummy_rng::DummyRng; +use crate::errors::{Error, Result}; +use crate::traits::keys::{CrtValue, PrivateKeyParts, PublicKeyParts}; +use crate::traits::{PaddingScheme, SignatureScheme}; + +/// Represents the public part of an RSA key. +#[derive(Debug, Clone)] +pub struct RsaPublicKey { + /// Modulus: product of prime numbers `p` and `q` + n: NonZero, + /// Public exponent: power to which a plaintext message is raised in + /// order to encrypt it. + /// + /// Typically `0x10001` (`65537`) + e: BoxedUint, + + n_params: BoxedMontyParams, +} + +impl Eq for RsaPublicKey {} + +impl PartialEq for RsaPublicKey { + #[inline] + fn eq(&self, other: &RsaPublicKey) -> bool { + self.n == other.n && self.e == other.e + } +} + +impl Hash for RsaPublicKey { + fn hash(&self, state: &mut H) { + // Domain separator for RSA private keys + state.write(b"RsaPublicKey"); + // TODO(tarcieri): to match the `PartialEq` impl we should strip leading zeros + Hash::hash(&self.n.as_limbs(), state); + Hash::hash(&self.e.as_limbs(), state); + } +} + +/// Represents a whole RSA key, public and private parts. +#[derive(Clone)] +pub struct RsaPrivateKey { + /// Public components of the private key. + pubkey_components: RsaPublicKey, + /// Private exponent + pub(crate) d: BoxedUint, + /// Prime factors of N, contains >= 2 elements. + pub(crate) primes: Vec, + /// Precomputed values to speed up private operations + pub(crate) precomputed: Option, +} + +impl fmt::Debug for RsaPrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let precomputed = if self.precomputed.is_some() { + "Some(...)" + } else { + "None" + }; + f.debug_struct("RsaPrivateKey") + .field("pubkey_components", &self.pubkey_components) + .field("d", &"...") + .field("primes", &"&[...]") + .field("precomputed", &precomputed) + .finish() + } +} + +impl Eq for RsaPrivateKey {} +impl PartialEq for RsaPrivateKey { + #[inline] + fn eq(&self, other: &RsaPrivateKey) -> bool { + self.pubkey_components == other.pubkey_components + && self.d == other.d + && self.primes == other.primes + } +} + +impl AsRef for RsaPrivateKey { + fn as_ref(&self) -> &RsaPublicKey { + &self.pubkey_components + } +} + +impl Hash for RsaPrivateKey { + fn hash(&self, state: &mut H) { + // Domain separator for RSA private keys + state.write(b"RsaPrivateKey"); + Hash::hash(&self.pubkey_components, state); + } +} + +impl Drop for RsaPrivateKey { + fn drop(&mut self) { + self.d.zeroize(); + self.primes.zeroize(); + self.precomputed.zeroize(); + } +} + +impl ZeroizeOnDrop for RsaPrivateKey {} + +#[derive(Clone)] +pub(crate) struct PrecomputedValues { + /// D mod (P-1) + pub(crate) dp: BoxedUint, + /// D mod (Q-1) + pub(crate) dq: BoxedUint, + /// Q^-1 mod P + pub(crate) qinv: BoxedMontyForm, + + /// Montgomery params for `p` + pub(crate) p_params: BoxedMontyParams, + /// Montgomery params for `q` + pub(crate) q_params: BoxedMontyParams, +} + +impl ZeroizeOnDrop for PrecomputedValues {} + +impl Zeroize for PrecomputedValues { + fn zeroize(&mut self) { + self.dp.zeroize(); + self.dq.zeroize(); + // TODO: once these have landed in crypto-bigint + // self.p_params.zeroize(); + // self.q_params.zeroize(); + } +} + +impl Drop for PrecomputedValues { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl From for RsaPublicKey { + fn from(private_key: RsaPrivateKey) -> Self { + (&private_key).into() + } +} + +impl From<&RsaPrivateKey> for RsaPublicKey { + fn from(private_key: &RsaPrivateKey) -> Self { + let n = PublicKeyParts::n(private_key); + let e = PublicKeyParts::e(private_key); + let n_params = PublicKeyParts::n_params(private_key); + RsaPublicKey { + n: n.clone(), + e: e.clone(), + n_params: n_params.clone(), + } + } +} + +impl PublicKeyParts for RsaPublicKey { + fn n(&self) -> &NonZero { + &self.n + } + + fn e(&self) -> &BoxedUint { + &self.e + } + + fn n_params(&self) -> &BoxedMontyParams { + &self.n_params + } +} + +impl RsaPublicKey { + /// Encrypt the given message. + pub fn encrypt( + &self, + rng: &mut R, + padding: P, + msg: &[u8], + ) -> Result> { + padding.encrypt(rng, self, msg) + } + + /// 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. + pub fn verify(&self, scheme: S, hashed: &[u8], sig: &[u8]) -> Result<()> { + scheme.verify(self, hashed, sig) + } +} + +impl RsaPublicKey { + /// Minimum value of the public exponent `e`. + pub const MIN_PUB_EXPONENT: u64 = 2; + + /// Maximum value of the public exponent `e`. + pub const MAX_PUB_EXPONENT: u64 = (1 << 33) - 1; + + /// Maximum size of the modulus `n` in bits. + pub const MAX_SIZE: usize = 8192; + + /// Create a new public key from its components. + /// + /// This function accepts public keys with a modulus size up to 8192-bits, + /// i.e. [`RsaPublicKey::MAX_SIZE`]. + pub fn new(n: BoxedUint, e: BoxedUint) -> Result { + Self::new_with_max_size(n, e, Self::MAX_SIZE) + } + + /// Create a new public key from its components. + pub fn new_with_max_size(n: BoxedUint, e: BoxedUint, max_size: usize) -> Result { + check_public_with_max_size(&n, &e, Some(max_size))?; + + let n_odd = Odd::new(n.clone()) + .into_option() + .ok_or(Error::InvalidModulus)?; + let n_params = BoxedMontyParams::new(n_odd); + let n = NonZero::new(n).expect("checked above"); + + Ok(Self { n, e, n_params }) + } + + /// Create a new public key, bypassing checks around the modulus and public + /// exponent size. + /// + /// This method is not recommended, and only intended for unusual use cases. + /// Most applications should use [`RsaPublicKey::new`] or + /// [`RsaPublicKey::new_with_max_size`] instead. + pub fn new_unchecked(n: BoxedUint, e: BoxedUint) -> Self { + let n_odd = Odd::new(n.clone()).expect("n must be odd"); + let n_params = BoxedMontyParams::new(n_odd); + let n = NonZero::new(n).expect("odd numbers are non zero"); + + Self { n, e, n_params } + } +} + +impl PublicKeyParts for RsaPrivateKey { + fn n(&self) -> &NonZero { + &self.pubkey_components.n + } + + fn e(&self) -> &BoxedUint { + &self.pubkey_components.e + } + + fn n_params(&self) -> &BoxedMontyParams { + &self.pubkey_components.n_params + } +} + +impl RsaPrivateKey { + /// Default exponent for RSA keys. + const EXP: u64 = 65537; + + /// Minimum size of the modulus `n` in bits. Currently only applies to keygen. + const MIN_SIZE: u32 = 1024; + + /// Generate a new RSA key pair with a modulus of the given bit size using the passed in `rng`. + /// + /// # Errors + /// - If `bit_size` is lower than the minimum 1024-bits. + pub fn new(rng: &mut R, bit_size: usize) -> Result { + Self::new_with_exp(rng, bit_size, Self::EXP.into()) + } + + /// Generate a new RSA key pair of the given bit size. + /// + /// #⚠️Warning: Hazmat! + /// This version does not apply minimum key size checks, and as such may generate keys + /// which are insecure! + #[cfg(feature = "hazmat")] + pub fn new_unchecked(rng: &mut R, bit_size: usize) -> Result { + Self::new_with_exp_unchecked(rng, bit_size, Self::EXP.into()) + } + + /// Generate a new RSA key pair of the given bit size and the public exponent + /// using the passed in `rng`. + /// + /// Unless you have specific needs, you should use [`RsaPrivateKey::new`] instead. + pub fn new_with_exp( + rng: &mut R, + bit_size: usize, + exp: BoxedUint, + ) -> Result { + if bit_size < Self::MIN_SIZE as usize { + return Err(Error::ModulusTooSmall); + } + + let components = generate_multi_prime_key_with_exp(rng, 2, bit_size, exp)?; + RsaPrivateKey::from_components( + components.n.get(), + components.e, + components.d, + components.primes, + ) + } + + /// Generate a new RSA key pair of the given bit size and the public exponent + /// using the passed in `rng`. + /// + /// Unless you have specific needs, you should use [`RsaPrivateKey::new`] instead. + /// + /// #⚠️Warning: Hazmat! + /// This version does not apply minimum key size checks, and as such may generate keys + /// which are insecure! + #[cfg(feature = "hazmat")] + pub fn new_with_exp_unchecked( + rng: &mut R, + bit_size: usize, + exp: BoxedUint, + ) -> Result { + let components = generate_multi_prime_key_with_exp(rng, 2, bit_size, exp)?; + RsaPrivateKey::from_components( + components.n.get(), + components.e, + components.d, + components.primes, + ) + } + + /// Private helper function that constructs an RSA key pair from components + /// WITHOUT performing any validation or precomputation. + /// + /// This is the shared implementation used by `from_components` and + /// `from_components_with_large_exponent`. + /// + /// Callers are responsible for: + /// 1. Validating the key (to ensure precomputation won't fail) + /// 2. Calling precompute() after validation + fn from_components_inner( + n: BoxedUint, + e: BoxedUint, + d: BoxedUint, + mut primes: Vec, + ) -> Result { + let n = Odd::new(n).into_option().ok_or(Error::InvalidModulus)?; + + // The modulus may come in padded with zeros, shorten it + // to ensure optimal performance of arithmetic operations. + let n_bits = n.bits_vartime(); + let n = n.resize_unchecked(n_bits); + + let n_params = BoxedMontyParams::new(n.clone()); + let n_c = NonZero::new(n.get()) + .into_option() + .ok_or(Error::InvalidModulus)?; + + match primes.len() { + 0 => { + // Recover `p` and `q` from `d`. + // See method in Appendix C.2: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf + let (p, q) = recover_primes(&n_c, &e, &d)?; + primes.push(p); + primes.push(q); + } + 1 => return Err(Error::NprimesTooSmall), + _ => { + // Check that the product of primes matches the modulus. + // This also ensures that `bit_precision` of each prime is <= that of the modulus, + // and `bit_precision` of their product is >= that of the modulus. + if primes + .iter() + .fold(BoxedUint::one(), |acc, p| acc.concatenating_mul(&p)) + != n_c.as_ref() + { + return Err(Error::InvalidModulus); + } + } + } + + // The primes may come in padded with zeros too, so we need to shorten them as well. + let primes = primes + .into_iter() + .map(|p| { + let p_bits = p.bits(); + p.resize_unchecked(p_bits) + }) + .collect(); + + let k = RsaPrivateKey { + pubkey_components: RsaPublicKey { + n: n_c, + e, + n_params, + }, + d, + primes, + precomputed: None, + }; + + Ok(k) + } + + /// Constructs an RSA key pair from individual components, accepting exponents outside + /// the normal size bounds. + /// + /// See [`RsaPrivateKey::from_components`] for an explanation on the parameters. + /// + /// # ⚠️ Warning: Hazmat! + /// + /// This method accepts public exponents outside the standard bounds (2 ≀ e ≀ 2^33-1), + /// but still performs full cryptographic validation to ensure the key is mathematically + /// correct (i.e., verifies that de ≑ 1 mod Ξ»(n)). + /// + /// **Note:** This method is dangerous as it can be used as a DOS vector if used with + /// untrusted input https://www.imperialviolet.org/2012/03/17/rsados.html + /// + /// This is intended for interoperating with systems that use non-standard exponents + /// or loading legacy keys. Use [`RsaPrivateKey::from_components`] for standard key + /// construction. + #[cfg(feature = "hazmat")] + pub fn from_components_with_large_exponent( + n: BoxedUint, + e: BoxedUint, + d: BoxedUint, + primes: Vec, + ) -> Result { + let mut k = Self::from_components_inner(n, e, d, primes)?; + + // Validate everything except exponent size bounds (to ensure precompute can't fail) + validate_skip_exponent_size(&k)?; + + // Precompute when possible, ignore error otherwise. + k.precompute().ok(); + + Ok(k) + } + + /// Constructs an RSA key pair from individual components: + /// + /// - `n`: RSA modulus + /// - `e`: public exponent (i.e. encrypting exponent) + /// - `d`: private exponent (i.e. decrypting exponent) + /// - `primes`: prime factors of `n`: typically two primes `p` and `q`. More than two primes can + /// be provided for multiprime RSA, however this is generally not recommended. If no `primes` + /// are provided, a prime factor recovery algorithm will be employed to attempt to recover the + /// factors (as described in [NIST SP 800-56B Revision 2] Appendix C.2). This algorithm only + /// works if there are just two prime factors `p` and `q` (as opposed to multiprime), and `e` + /// is between 2^16 and 2^256. + /// + /// [NIST SP 800-56B Revision 2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf + pub fn from_components( + n: BoxedUint, + e: BoxedUint, + d: BoxedUint, + primes: Vec, + ) -> Result { + let mut k = Self::from_components_inner(n, e, d, primes)?; + + // Always validate the key, to ensure precompute can't fail + k.validate()?; + + // Precompute when possible, ignore error otherwise. + k.precompute().ok(); + + Ok(k) + } + + /// Constructs an RSA key pair from its two primes p and q. + /// + /// This will rebuild the private exponent and the modulus. + /// + /// Private exponent will be rebuilt using the method defined in + /// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47). + pub fn from_p_q( + p: BoxedUint, + q: BoxedUint, + public_exponent: BoxedUint, + ) -> Result { + if p == q { + return Err(Error::InvalidPrime); + } + + let d = compute_private_exponent_carmicheal(&p, &q, &public_exponent)?; + let primes = vec![p, q]; + let n = compute_modulus(&primes); + + Self::from_components(n.get(), public_exponent, d, primes) + } + + /// Constructs an RSA key pair from its primes. + /// + /// This will rebuild the private exponent and the modulus. + pub fn from_primes( + primes: Vec, + public_exponent: BoxedUint, + ) -> Result { + if primes.len() < 2 { + return Err(Error::NprimesTooSmall); + } + + // Makes sure that the primes are pairwise unequal. + for (i, prime1) in primes.iter().enumerate() { + for prime2 in primes.iter().take(i) { + if prime1 == prime2 { + return Err(Error::InvalidPrime); + } + } + } + + let n = compute_modulus(&primes); + let d = compute_private_exponent_euler_totient(&primes, &public_exponent)?; + + Self::from_components(n.get(), public_exponent, d, primes) + } + + /// Get the public key from the private key. + /// + /// Specific alternative to [`AsRef::as_ref`]. + pub fn as_public_key(&self) -> &RsaPublicKey { + &self.pubkey_components + } + + /// Get the public key from the private key, cloning `n` and `e`. + /// + /// Generally this is not needed since `RsaPrivateKey` implements the `PublicKey` trait, + /// but it can occasionally be useful to discard the private information entirely. + pub fn to_public_key(&self) -> RsaPublicKey { + self.pubkey_components.clone() + } + + /// Performs some calculations to speed up private key operations. + pub fn precompute(&mut self) -> Result<()> { + if self.precomputed.is_some() { + return Ok(()); + } + + let d = &self.d; + let p = self.primes[0].clone(); + let q = self.primes[1].clone(); + + let p_odd = Odd::new(p.clone()) + .into_option() + .ok_or(Error::InvalidPrime)?; + let p_params = BoxedMontyParams::new(p_odd); + let q_odd = Odd::new(q.clone()) + .into_option() + .ok_or(Error::InvalidPrime)?; + let q_params = BoxedMontyParams::new(q_odd); + + let x = NonZero::new(p.wrapping_sub(BoxedUint::one())) + .into_option() + .ok_or(Error::InvalidPrime)?; + let dp = d.rem_vartime(&x); + + let x = NonZero::new(q.wrapping_sub(BoxedUint::one())) + .into_option() + .ok_or(Error::InvalidPrime)?; + let dq = d.rem_vartime(&x); + + // Note that since `p` and `q` may have different `bits_precision`, + // so we have to equalize them to calculate the remainder. + let q_mod_p = match p.bits_precision().cmp(&q.bits_precision()) { + Ordering::Less => (&q + % NonZero::new(p.clone()) + .expect("`p` is non-zero") + .resize_unchecked(q.bits_precision())) + .resize_unchecked(p.bits_precision()), + Ordering::Greater => { + (&q).resize_unchecked(p.bits_precision()) + % &NonZero::new(p.clone()).expect("`p` is non-zero") + } + Ordering::Equal => &q % NonZero::new(p.clone()).expect("`p` is non-zero"), + }; + + let q_mod_p = BoxedMontyForm::new(q_mod_p, &p_params); + let qinv = q_mod_p.invert().into_option().ok_or(Error::InvalidPrime)?; + + debug_assert_eq!(dp.bits_precision(), p.bits_precision()); + debug_assert_eq!(dq.bits_precision(), q.bits_precision()); + debug_assert_eq!(qinv.bits_precision(), p.bits_precision()); + debug_assert_eq!(p_params.bits_precision(), p.bits_precision()); + debug_assert_eq!(q_params.bits_precision(), q.bits_precision()); + + self.precomputed = Some(PrecomputedValues { + dp, + dq, + qinv, + p_params, + q_params, + }); + + Ok(()) + } + + /// Clears precomputed values by setting to None + pub fn clear_precomputed(&mut self) { + self.precomputed = None; + } + + /// Compute CRT coefficient: `(1/q) mod p`. + pub fn crt_coefficient(&self) -> Option { + let p = &self.primes[0]; + let q = &self.primes[1]; + // TODO: maybe store primes as `NonZero`? + Option::from(q.invert_mod(&NonZero::new(p.clone()).expect("prime"))) + } + + /// Performs basic sanity checks on the key. + /// Returns `Ok(())` if everything is good, otherwise an appropriate error. + pub fn validate(&self) -> Result<()> { + check_public(self)?; + validate_private_key_parts(self)?; + Ok(()) + } + + /// Decrypt the given message. + pub fn decrypt(&self, padding: P, ciphertext: &[u8]) -> Result> { + padding.decrypt(Option::<&mut DummyRng>::None, self, ciphertext) + } + + /// Decrypt the given message. + /// + /// Uses `rng` to blind the decryption process. + pub fn decrypt_blinded( + &self, + rng: &mut R, + padding: P, + ciphertext: &[u8], + ) -> Result> { + padding.decrypt(Some(rng), self, ciphertext) + } + + /// Sign the given digest. + pub fn sign(&self, padding: S, digest_in: &[u8]) -> Result> { + padding.sign(Option::<&mut DummyRng>::None, self, digest_in) + } + + /// Sign the given digest using the provided `rng`, which is used in the + /// following ways depending on the [`SignatureScheme`]: + /// + /// - [`Pkcs1v15Sign`][`crate::Pkcs1v15Sign`] padding: uses the RNG + /// to mask the private key operation with random blinding, which helps + /// mitigate sidechannel attacks. + /// - [`Pss`][`crate::Pss`] always requires randomness. Use + /// [`Pss::new`][`crate::Pss::new`] for a standard RSASSA-PSS signature, or + /// [`Pss::new_blinded`][`crate::Pss::new_blinded`] for RSA-BSSA blind + /// signatures. + pub fn sign_with_rng( + &self, + rng: &mut R, + padding: S, + digest_in: &[u8], + ) -> Result> { + padding.sign(Some(rng), self, digest_in) + } +} + +impl PrivateKeyParts for RsaPrivateKey { + fn d(&self) -> &BoxedUint { + &self.d + } + + fn primes(&self) -> &[BoxedUint] { + &self.primes + } + + fn dp(&self) -> Option<&BoxedUint> { + self.precomputed.as_ref().map(|p| &p.dp) + } + + fn dq(&self) -> Option<&BoxedUint> { + self.precomputed.as_ref().map(|p| &p.dq) + } + + fn qinv(&self) -> Option<&BoxedMontyForm> { + self.precomputed.as_ref().map(|p| &p.qinv) + } + + fn crt_values(&self) -> Option<&[CrtValue]> { + None + } + + fn p_params(&self) -> Option<&BoxedMontyParams> { + self.precomputed.as_ref().map(|p| &p.p_params) + } + + fn q_params(&self) -> Option<&BoxedMontyParams> { + self.precomputed.as_ref().map(|p| &p.q_params) + } +} + +/// Check that the public key is well formed and has an exponent within acceptable bounds. +#[inline] +pub fn check_public(public_key: &impl PublicKeyParts) -> Result<()> { + check_public_with_max_size(public_key.n(), public_key.e(), None) +} + +/// Check that the public key is well formed and has an exponent within acceptable bounds. +#[inline] +fn check_public_with_max_size(n: &BoxedUint, e: &BoxedUint, max_size: Option) -> Result<()> { + if let Some(max_size) = max_size { + if n.bits_vartime() as usize > max_size { + return Err(Error::ModulusTooLarge); + } + } + + check_public_skip_exponent_size(n, e)?; + + if e < &BoxedUint::from(RsaPublicKey::MIN_PUB_EXPONENT) { + return Err(Error::PublicExponentTooSmall); + } + + if e > &BoxedUint::from(RsaPublicKey::MAX_PUB_EXPONENT) { + return Err(Error::PublicExponentTooLarge); + } + + Ok(()) +} + +/// Check that the public key is well formed, skipping exponent size bounds checks. +/// +/// This is used internally by both public validation functions and hazmat APIs. +#[inline] +fn check_public_skip_exponent_size(n: &BoxedUint, e: &BoxedUint) -> Result<()> { + if e >= n || n.is_even().into() || n.is_zero().into() { + return Err(Error::InvalidModulus); + } + + if e.is_even().into() { + return Err(Error::InvalidExponent); + } + + // Skip exponent size bounds checks + Ok(()) +} + +/// Helper function that validates the private key structure and cryptographic correctness. +/// +/// This performs the structural and mathematical validation checks that are common to both +/// `validate()` and `validate_skip_exponent_size()`. +fn validate_private_key_parts(key: &RsaPrivateKey) -> Result<()> { + // Check that Ξ primes == n. + let mut m = BoxedUint::one_with_precision(key.pubkey_components.n.bits_precision()); + let one = BoxedUint::one(); + for prime in &key.primes { + // Any primes ≀ 1 will cause divide-by-zero panics later. + if prime <= &one { + return Err(Error::InvalidPrime); + } + m = m.wrapping_mul(prime); + } + if m != *key.pubkey_components.n { + return Err(Error::InvalidModulus); + } + + // Check that de ≑ 1 mod p-1, for each prime. + // This implies that e is coprime to each p-1 as e has a multiplicative + // inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) = + // exponent(β„€/nβ„€). It also implies that a^de ≑ a mod p as a^(p-1) ≑ 1 + // mod p. Thus a^de ≑ a mod n for all a coprime to n, as required. + let de = key.d.concatenating_mul(&key.pubkey_components.e); + + for prime in &key.primes { + let x = NonZero::new(prime.wrapping_sub(BoxedUint::one())).unwrap(); + let congruence = de.rem_vartime(&x); + if !bool::from(congruence.is_one()) { + return Err(Error::InvalidExponent); + } + } + + Ok(()) +} + +/// Validate the private key structure and cryptographic correctness, +/// skipping only the exponent size bounds checks. +/// +/// This performs all the same checks as `RsaPrivateKey::validate()` except +/// it doesn't verify that the exponent is within the standard bounds. +#[cfg(feature = "hazmat")] +fn validate_skip_exponent_size(key: &RsaPrivateKey) -> Result<()> { + // Check public key properties (without exponent size checks) + check_public_skip_exponent_size(key.pubkey_components.n.as_ref(), &key.pubkey_components.e)?; + + // Perform common private key validation + validate_private_key_parts(key)?; + + Ok(()) +} + +#[cfg(feature = "serde")] +impl Serialize for RsaPublicKey { + fn serialize(&self, serializer: S) -> core::prelude::v1::Result + where + S: serdect::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> Deserialize<'de> for RsaPublicKey { + fn deserialize(deserializer: D) -> core::prelude::v1::Result + where + D: serdect::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(feature = "serde")] +impl Serialize for RsaPrivateKey { + fn serialize(&self, serializer: S) -> core::prelude::v1::Result + where + S: ser::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> Deserialize<'de> for RsaPrivateKey { + fn deserialize(deserializer: D) -> core::prelude::v1::Result + where + D: de::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 { + use super::*; + use crate::algorithms::rsa::{rsa_decrypt_and_check, rsa_encrypt}; + use crate::traits::{PrivateKeyParts, PublicKeyParts}; + + use hex_literal::hex; + use rand::rngs::ChaCha8Rng; + use rand_core::SeedableRng; + + #[cfg(feature = "encoding")] + use pkcs8::DecodePrivateKey; + + #[test] + fn test_from_into() { + let raw_n = BoxedUint::from(101u64); + let n_odd = Odd::new(raw_n.clone()).unwrap(); + let private_key = RsaPrivateKey { + pubkey_components: RsaPublicKey { + n: NonZero::new(raw_n.clone()).unwrap(), + e: BoxedUint::from(200u64), + n_params: BoxedMontyParams::new(n_odd), + }, + d: BoxedUint::from(123u64), + primes: vec![], + precomputed: None, + }; + let public_key: RsaPublicKey = private_key.into(); + + let n_limbs: &[u64] = PublicKeyParts::n(&public_key).as_ref().as_ref(); + assert_eq!(n_limbs, &[101u64]); + assert_eq!(PublicKeyParts::e(&public_key), &BoxedUint::from(200u64)); + assert_eq!(PublicKeyParts::e_bytes(&public_key), [200].into()); + assert_eq!(PublicKeyParts::n_bytes(&public_key), [101].into()); + } + + fn test_key_basics(private_key: &RsaPrivateKey) { + private_key.validate().expect("invalid private key"); + + assert!( + PrivateKeyParts::d(private_key) < PublicKeyParts::n(private_key).as_ref(), + "private exponent too large" + ); + + let pub_key: RsaPublicKey = private_key.clone().into(); + let m = BoxedUint::from(42u64); + let c = rsa_encrypt(&pub_key, &m).expect("encryption successful"); + + let m2 = rsa_decrypt_and_check::(private_key, None, &c) + .expect("unable to decrypt without blinding"); + assert_eq!(m, m2); + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let m3 = rsa_decrypt_and_check(private_key, Some(&mut rng), &c) + .expect("unable to decrypt with blinding"); + assert_eq!(m, m3); + } + + 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(RsaPrivateKey::EXP); + + for _ in 0..10 { + let components = + generate_multi_prime_key_with_exp(&mut rng, $multi, $size, exp.clone()) + .unwrap(); + let private_key = RsaPrivateKey::from_components( + components.n.get(), + components.e, + components.d, + components.primes, + ) + .unwrap(); + assert_eq!(PublicKeyParts::n(&private_key).bits(), $size); + + test_key_basics(&private_key); + } + } + }; + } + + 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); + key_generation!(key_generation_multi_16_1024, 16, 1024); + + #[test] + fn test_negative_decryption_value() { + let bits = 128; + let private_key = RsaPrivateKey::from_components( + BoxedUint::from_le_slice( + &[ + 99, 192, 208, 179, 0, 220, 7, 29, 49, 151, 75, 107, 75, 73, 200, 180, + ], + bits, + ) + .unwrap(), + BoxedUint::from_le_slice(&[1, 0, 1, 0, 0, 0, 0, 0], 64).unwrap(), + BoxedUint::from_le_slice( + &[ + 81, 163, 254, 144, 171, 159, 144, 42, 244, 133, 51, 249, 28, 12, 63, 65, + ], + bits, + ) + .unwrap(), + vec![ + BoxedUint::from_le_slice(&[105, 101, 60, 173, 19, 153, 3, 192], bits / 2).unwrap(), + BoxedUint::from_le_slice(&[235, 65, 160, 134, 32, 136, 6, 241], bits / 2).unwrap(), + ], + ) + .unwrap(); + + for _ in 0..1000 { + test_key_basics(&private_key); + } + } + + #[test] + #[cfg(all(feature = "hazmat", feature = "serde"))] + fn test_serde() { + 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 priv_tokens = [Token::Str(concat!( + "3056020100300d06092a864886f70d010101050004423040020100020900a", + "b240c3361d02e370203010001020811e54a15259d22f9020500ceff5cf302", + "0500d3a7aaad020500ccaddf17020500cb529d3d020500bb526d6f" + ))]; + assert_tokens(&priv_key.clone().readable(), &priv_tokens); + + let priv_tokens = [Token::Str( + "3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001", + )]; + assert_tokens( + &RsaPublicKey::from(priv_key.clone()).readable(), + &priv_tokens, + ); + } + + #[test] + fn invalid_coeff_private_key_regression() { + use base64ct::{Base64, Encoding}; + + let n = Base64::decode_vec( + "wC8GyQvTCZOK+iiBR5fGQCmzRCTWX9TQ3aRG5gGFk0wB6EFoLMAyEEqeG3gS8xhA\ + m2rSWYx9kKufvNat3iWlbSRVqkcbpVAYlj2vTrpqDpJl+6u+zxFYoUEBevlJJkAh\ + l8EuCccOA30fVpcfRvXPTtvRd3yFT9E9EwZljtgSI02w7gZwg7VIxaGeajh5Euz6\ + ZVQZ+qNRKgXrRC7gPRqVyI6Dt0Jc+Su5KBGNn0QcPDzOahWha1ieaeMkFisZ9mdp\ + sJoZ4tw5eicLaUomKzALHXQVt+/rcZSrCd6/7uUo11B/CYBM4UfSpwXaL88J9AE6\ + A5++no9hmJzaF2LLp+Qwx4yY3j9TDutxSAjsraxxJOGZ3XyA9nG++Ybt3cxZ5fP7\ + ROjxCfROBmVv5dYn0O9OBIqYeCH6QraNpZMadlLNIhyMv8Y+P3r5l/PaK4VJaEi5\ + pPosnEPawp0W0yZDzmjk2z1LthaRx0aZVrAjlH0Rb/6goLUQ9qu1xsDtQVVpN4A8\ + 9ZUmtTWORnnJr0+595eHHxssd2gpzqf4bPjNITdAEuOCCtpvyi4ls23zwuzryUYj\ + cUOEnsXNQ+DrZpLKxdtsD/qNV/j1hfeyBoPllC3cV+6bcGOFcVGbjYqb+Kw1b0+j\ + L69RSKQqgmS+qYqr8c48nDRxyq3QXhR8qtzUwBFSLVk=", + ) + .unwrap(); + let e = Base64::decode_vec("AQAB").unwrap(); + let d = Base64::decode_vec( + "qQazSQ+FRN7nVK1bRsROMRB8AmsDwLVEHivlz1V3Td2Dr+oW3YUMgxedhztML1Id\ + QJPq/ad6qErJ6yRFNySVIjDaxzBTOEoB1eHa1btOnBJWb8rVvvjaorixvJ6Tn3i4\ + EuhsvVy9DoR1k4rGj3qSIiFjUVvLRDAbLyhpGgEfsr0Z577yJmTC5E8JLRMOKX8T\ + mxsk3jPVpsgd65Hu1s8S/ZmabwuHCf9SkdMeY/1bd/9i7BqqJeeDLE4B5x1xcC3z\ + 3scqDUTzqGO+vZPhjgprPDRlBamVwgenhr7KwCn8iaLamFinRVwOAag8BeBqOJj7\ + lURiOsKQa9FIX1kdFUS1QMQxgtPycLjkbvCJjriqT7zWKsmJ7l8YLs6Wmm9/+QJR\ + wNCEVdMTXKfCP1cJjudaiskEQThfUldtgu8gUDNYbQ/Filb2eKfiX4h1TiMxZqUZ\ + HVZyb9nShbQoXJ3vj/MGVF0QM8TxhXM8r2Lv9gDYU5t9nQlUMLhs0jVjai48jHAB\ + bFNyH3sEcOmJOIwJrCXw1dzG7AotwyaEVUHOmL04TffmwCFfnyrLjbFgnyOeoyII\ + BYjcY7QFRm/9nupXMTH5hZ2qrHfCJIp0KK4tNBdQqmnHapFl5l6Le1s4qBS5bEIz\ + jitobLvAFm9abPlDGfxmY6mlrMK4+nytwF9Ct7wc1AE=", + ) + .unwrap(); + let primes = [ + Base64::decode_vec( + "9kQWEAzsbzOcdPa+s5wFfw4XDd7bB1q9foZ31b1+TNjGNxbSBCFlDF1q98vwpV6n\ + M8bWDh/wtbNoETSQDgpEnYOQ26LWEw6YY1+q1Q2GGEFceYUf+Myk8/vTc8TN6Zw0\ + bKZBWy10Qo8h7xk4JpzuI7NcxvjJYTkS9aErFxi3vVH0aiZC0tmfaCqr8a2rJxyV\ + wqreRpOjwAWrotMsf2wGsF4ofx5ScoFy5GB5fJkkdOrW1LyTvZAUCX3cstPr19+T\ + NC5zZOk7WzZatnCkN5H5WzalWtZuu0oVL205KPOa3R8V2yv5e6fm0v5fTmqSuvjm\ + aMJLXCN4QJkmIzojO99ckQ==", + ) + .unwrap(), + Base64::decode_vec( + "x8exdMjVA2CiI+Thx7loHtVcevoeE2sZ7btRVAvmBqo+lkHwxb7FHRnWvuj6eJSl\ + D2f0T50EewIhhiW3R9BmktCk7hXjbSCnC1u9Oxc1IAUm/7azRqyfCMx43XhLxpD+\ + xkBCpWkKDLxGczsRwTuaP3lKS3bSdBrNlGmdblubvVBIq4YZ2vXVlnYtza0cS+dg\ + CK7BGTqUsrCUd/ZbIvwcwZkZtpkhj1KQfto9X/0OMurBzAqbkeq1cyRHXHkOfN/q\ + bUIIRqr9Ii7Eswf9Vk8xp2O1Nt8nzcYS9PFD12M5eyaeFEkEYfpNMNGuTzp/31oq\ + VjbpoCxS6vuWAZyADxhISQ==", + ) + .unwrap(), + Base64::decode_vec( + "is7d0LY4HoXszlC2NO7gejkq7XqL4p1W6hZJPYTNx+r37t1CC2n3Vvzg6kNdpRix\ + DhIpXVTLjN9O7UO/XuqSumYKJIKoP52eb4Tg+a3hw5Iz2Zsb5lUTNSLgkQSBPAf7\ + 1LHxbL82JL4g1nBUog8ae60BwnVArThKY4EwlJguGNw09BAU4lwf6csDl/nX2vfV\ + wiAloYpeZkHL+L8m+bueGZM5KE2jEz+7ztZCI+T+E5i69rZEYDjx0lfLKlEhQlCW\ + 3HbCPELqXgNJJkRfi6MP9kXa9lSfnZmoT081RMvqonB/FUa4HOcKyCrw9XZEtnbN\ + CIdbitfDVEX+pSSD7596wQ==", + ) + .unwrap(), + Base64::decode_vec( + "GPs0injugfycacaeIP5jMa/WX55VEnKLDHom4k6WlfDF4L4gIGoJdekcPEUfxOI5\ + faKvHyFwRP1wObkPoRBDM0qZxRfBl4zEtpvjHrd5MibSyJkM8+J0BIKk/nSjbRIG\ + eb3hV5O56PvGB3S0dKhCUnuVObiC+ne7izplsD4OTG70l1Yud33UFntyoMxrxGYL\ + USqhBMmZfHquJg4NOWOzKNY/K+EcHDLj1Kjvkcgv9Vf7ocsVxvpFdD9uGPceQ6kw\ + RDdEl6mb+6FDgWuXVyqR9+904oanEIkbJ7vfkthagLbEf57dyG6nJlqh5FBZWxGI\ + R72YGypPuAh7qnnqXXjY2Q==", + ) + .unwrap(), + Base64::decode_vec( + "CUWC+hRWOT421kwRllgVjy6FYv6jQUcgDNHeAiYZnf5HjS9iK2ki7v8G5dL/0f+Y\ + f+NhE/4q8w4m8go51hACrVpP1p8GJDjiT09+RsOzITsHwl+ceEKoe56ZW6iDHBLl\ + rNw5/MtcYhKpjNU9KJ2udm5J/c9iislcjgckrZG2IB8ADgXHMEByZ5DgaMl4AKZ1\ + Gx8/q6KftTvmOT5rNTMLi76VN5KWQcDWK/DqXiOiZHM7Nr4dX4me3XeRgABJyNR8\ + Fqxj3N1+HrYLe/zs7LOaK0++F9Ul3tLelhrhsvLxei3oCZkF9A/foD3on3luYA+1\ + cRcxWpSY3h2J4/22+yo4+Q==", + ) + .unwrap(), + ]; + + let e = BoxedUint::from_be_slice(&e, 64).unwrap(); + + let bits = 4096; + let n = BoxedUint::from_be_slice(&n, bits).unwrap(); + let d = BoxedUint::from_be_slice(&d, bits).unwrap(); + let primes = primes + .iter() + .map(|p| BoxedUint::from_be_slice(p, bits / 2).unwrap()) + .collect(); + let res = RsaPrivateKey::from_components(n, e, d, primes); + assert_eq!(res, Err(Error::InvalidModulus)); + } + + #[test] + fn reject_oversized_private_key() { + // -----BEGIN PUBLIC KEY----- + // MIIEKjANBgkqhkiG9w0BAQEFAAOCBBcAMIIEEgKCBAkAqQn6O7pd9ioQJEOwS2sh + // nD2bM3+PaLovro+OKOE9t7jxrp+b9Xq81oeT6zN5u5yPewa+V08ZsAJQEbF9D5AM + // UZkHZc/sW/XAItC8CojQhHoCQfjOXZpONmGsQxnSJNgwLV5TDVKUApbQIPzIm9yD + // wOvl1yXIypaRINHzthz36ysHmaHlNVZZQ40BHVkOiUd+ws7W9U9vHN0QcaSHC8lH + // UEqb/Iyb0FSmZs+qbm4NXyaI90oloAFftOnt8VFbHfT/TXS0VwMyescxFsuvcuTr + // Xx8EYc9TuJThW22wBAFOK6SpftgtZ6i4WJqk0F8JrTwZ3TyhzKsPRwe8KeNmtmqY + // oaGiPj9lUOc928QzOyTETVd8pV7UpnaOe9Q4WHL0QmnXn61pCu4qpoLuK8jB+IO7 + // xifRZHj3PMfsjJurZ4MF57LgpSrI60ekYNh1o6ViXODHQuzGxzTaF3n/7GITDBQX + // DRTlGuQH77hykxFqPclRGI0wxECPKasxpzjhiaTua9eipKedXB+o5XFyosnDt/X4 + // Ygqxj/q2/18LPuQgFLqWRzsHd4TdVQyiq9xCmzIoGUjAPz1Q8cjIXRpUnp2rZQjE + // SCLeTjewrGNbjSMDUhdF5M2OBRmn7Q8XHHCUxT9fY/BZeydeE54KvEdEkomxkbXo + // hHKEmbWeEdhp78h04/xW364p1Nu9Y49w7gtO+9nmwKcpNJq32M6Qb0d2dQ3wJ0oI + // I9ml+n/DTna+IIwwbI8UOFEI4KZQzZaqmNv3TzGmpnocHK7eMyEtAUeQZUIGrPmr + // FQEmKZn9rkgr/2Hw8T20q7e0lE65Is69vTP2wXm17B5zKFYska41jJoZ6jIpbMOt + // uVPZV3SoGYM39Z4Ax3JaGZE0L/dQ6lJJhdFUACFIQXwNWqzd7srnvbym4hLqoPuM + // hjkUtTcv6YODEk7LB2FLDcymmH/zCL3w4VSi4+HyZZ13gM7Cz8WmkX4H+jeL0+Ja + // QyG1CzqV/HA78vUpJv/bb/J1+X1i/1HltLeTjteY4rBhVT1cxBoVBGQaCwindAs+ + // Fjcp+8cAK+/3pMQghnkrGHzrx9YIYoOGXs4vQIMGngYaTa7aXAaft4fWjg4EeSjd + // rZwqqrPNuUcEuneFP9RPffj8f3vkhqCFgoVBfVM7ontu2d2nRu/hhAkgT13Uc68J + // dM2imBvHAo6DDUt6msWCAMOAEXYuO7aA+n3eettnBqtECoQAoCJdCHCebjIploMB + // XMLXyseGtLK9arI48hDvcxSlf7/1lkBB6LgNQmQJ7925TDipiYQIZ63f4d5Z2JCp + // W0vUkwzrH4iPb2hy+TBQSOw1kvjLyG/lHWjzDQa60xxVW9u59DxQueHsNEMHUORD + // 1oFXvFLe/AllAgMBAAE= + // -----END PUBLIC KEY----- + + let n = BoxedUint::from_be_slice( + &hex!( + "a909fa3bba5df62a102443b04b6b219c3d9b337f8f68ba2fae8f8e28e13db7b8 + f1ae9f9bf57abcd68793eb3379bb9c8f7b06be574f19b0025011b17d0f900c51 + 990765cfec5bf5c022d0bc0a88d0847a0241f8ce5d9a4e3661ac4319d224d830 + 2d5e530d52940296d020fcc89bdc83c0ebe5d725c8ca969120d1f3b61cf7eb2b + 0799a1e5355659438d011d590e89477ec2ced6f54f6f1cdd1071a4870bc94750 + 4a9bfc8c9bd054a666cfaa6e6e0d5f2688f74a25a0015fb4e9edf1515b1df4ff + 4d74b45703327ac73116cbaf72e4eb5f1f0461cf53b894e15b6db004014e2ba4 + a97ed82d67a8b8589aa4d05f09ad3c19dd3ca1ccab0f4707bc29e366b66a98a1 + a1a23e3f6550e73ddbc4333b24c44d577ca55ed4a6768e7bd4385872f44269d7 + 9fad690aee2aa682ee2bc8c1f883bbc627d16478f73cc7ec8c9bab678305e7b2 + e0a52ac8eb47a460d875a3a5625ce0c742ecc6c734da1779ffec62130c14170d + 14e51ae407efb87293116a3dc951188d30c4408f29ab31a738e189a4ee6bd7a2 + a4a79d5c1fa8e57172a2c9c3b7f5f8620ab18ffab6ff5f0b3ee42014ba96473b + 077784dd550ca2abdc429b32281948c03f3d50f1c8c85d1a549e9dab6508c448 + 22de4e37b0ac635b8d2303521745e4cd8e0519a7ed0f171c7094c53f5f63f059 + 7b275e139e0abc47449289b191b5e884728499b59e11d869efc874e3fc56dfae + 29d4dbbd638f70ee0b4efbd9e6c0a729349ab7d8ce906f4776750df0274a0823 + d9a5fa7fc34e76be208c306c8f14385108e0a650cd96aa98dbf74f31a6a67a1c + 1caede33212d014790654206acf9ab1501262999fdae482bff61f0f13db4abb7 + b4944eb922cebdbd33f6c179b5ec1e7328562c91ae358c9a19ea32296cc3adb9 + 53d95774a8198337f59e00c7725a1991342ff750ea524985d154002148417c0d + 5aacddeecae7bdbca6e212eaa0fb8c863914b5372fe98383124ecb07614b0dcc + a6987ff308bdf0e154a2e3e1f2659d7780cec2cfc5a6917e07fa378bd3e25a43 + 21b50b3a95fc703bf2f52926ffdb6ff275f97d62ff51e5b4b7938ed798e2b061 + 553d5cc41a1504641a0b08a7740b3e163729fbc7002beff7a4c42086792b187c + ebc7d6086283865ece2f4083069e061a4daeda5c069fb787d68e0e047928ddad + 9c2aaab3cdb94704ba77853fd44f7df8fc7f7be486a0858285417d533ba27b6e + d9dda746efe18409204f5dd473af0974cda2981bc7028e830d4b7a9ac58200c3 + 8011762e3bb680fa7dde7adb6706ab440a8400a0225d08709e6e32299683015c + c2d7cac786b4b2bd6ab238f210ef7314a57fbff5964041e8b80d426409efddb9 + 4c38a989840867addfe1de59d890a95b4bd4930ceb1f888f6f6872f9305048ec + 3592f8cbc86fe51d68f30d06bad31c555bdbb9f43c50b9e1ec34430750e443d6 + 8157bc52defc0965" + ), + 8256, + ) + .unwrap(); + + let e = BoxedUint::from(65_537u64); + + assert_eq!( + RsaPublicKey::new(n, e).err().unwrap(), + Error::ModulusTooLarge + ); + } + + #[test] + #[cfg(feature = "encoding")] + fn build_key_from_primes() { + const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/pkcs8/rsa2048-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = PrivateKeyParts::primes(&ref_key).to_vec(); + + let exp = PublicKeyParts::e(&ref_key); + let key = RsaPrivateKey::from_primes(primes, exp.clone()) + .expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(PublicKeyParts::n(&key), PublicKeyParts::n(&ref_key)); + + assert_eq!(PrivateKeyParts::dp(&key), PrivateKeyParts::dp(&ref_key)); + assert_eq!(PrivateKeyParts::dq(&key), PrivateKeyParts::dq(&ref_key)); + + assert_eq!(PrivateKeyParts::d(&key), PrivateKeyParts::d(&ref_key)); + } + + #[test] + #[cfg(feature = "encoding")] + fn build_key_from_p_q() { + const RSA_2048_SP800_PRIV_DER: &[u8] = + include_bytes!("../tests/examples/pkcs8/rsa2048-sp800-56b-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_SP800_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = PrivateKeyParts::primes(&ref_key).to_vec(); + let exp = PublicKeyParts::e(&ref_key); + + let key = RsaPrivateKey::from_p_q(primes[0].clone(), primes[1].clone(), exp.clone()) + .expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(PublicKeyParts::n(&key), PublicKeyParts::n(&ref_key)); + + assert_eq!(PrivateKeyParts::dp(&key), PrivateKeyParts::dp(&ref_key)); + assert_eq!(PrivateKeyParts::dq(&key), PrivateKeyParts::dq(&ref_key)); + + assert_eq!(PrivateKeyParts::d(&key), PrivateKeyParts::d(&ref_key)); + } + + #[test] + #[cfg(feature = "hazmat")] + fn test_from_components_with_large_exponent() { + // Test that from_components_with_large_exponent accepts exponents outside normal bounds + // while from_components would reject them + + use rand::rngs::ChaCha8Rng; + use rand_core::SeedableRng; + + let mut rng = ChaCha8Rng::from_seed([42; 32]); + + // Use an exponent larger than the normal maximum (2^33 - 1) + let large_e = BoxedUint::from((1u64 << 34) + 1); // 2^34 + 1 (odd number) + + // Generate a key with this large exponent + let components = + generate_multi_prime_key_with_exp(&mut rng, 2, 1024, large_e.clone()).unwrap(); + + // Extract components + let n = components.n.get().clone(); + let d = components.d; + let primes = components.primes; + + // from_components should fail with PublicExponentTooLarge + let result = + RsaPrivateKey::from_components(n.clone(), large_e.clone(), d.clone(), primes.clone()); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), Error::PublicExponentTooLarge); + + // from_components_with_large_exponent should succeed + let key_with_large_exp = RsaPrivateKey::from_components_with_large_exponent( + n.clone(), + large_e.clone(), + d.clone(), + primes.clone(), + ); + assert!(key_with_large_exp.is_ok()); + + let key_with_large_exp = key_with_large_exp.unwrap(); + assert_eq!(PublicKeyParts::e(&key_with_large_exp), &large_e); + assert_eq!(PublicKeyParts::n(&key_with_large_exp).as_ref(), &n); + assert_eq!(PrivateKeyParts::d(&key_with_large_exp), &d); + + // Verify that the key is still cryptographically valid (de ≑ 1 mod Ξ»(n)) + // by checking that validation with skip_exponent_size passes + assert!(validate_skip_exponent_size(&key_with_large_exp).is_ok()); + } + + #[test] + #[cfg(feature = "hazmat")] + fn test_from_components_with_small_exponent() { + // Test that from_components_with_large_exponent accepts exponents below normal minimum + // (despite the name, it works for any non-standard exponent size) + + use rand::rngs::ChaCha8Rng; + use rand_core::SeedableRng; + + let mut rng = ChaCha8Rng::from_seed([43; 32]); + + // Use an exponent smaller than the normal minimum (2) + let small_e = BoxedUint::from(1u64); // This is odd, which is required + + // Generate a key with this small exponent + let components = + generate_multi_prime_key_with_exp(&mut rng, 2, 1024, small_e.clone()).unwrap(); + + // Extract components + let n = components.n.get().clone(); + let d = components.d; + let primes = components.primes; + + // from_components should fail + let result = + RsaPrivateKey::from_components(n.clone(), small_e.clone(), d.clone(), primes.clone()); + assert!(result.is_err()); + + // from_components_with_large_exponent should succeed + let key_with_small_exp = RsaPrivateKey::from_components_with_large_exponent( + n.clone(), + small_e.clone(), + d.clone(), + primes, + ); + assert!(key_with_small_exp.is_ok()); + + let key_with_small_exp = key_with_small_exp.unwrap(); + assert_eq!(PublicKeyParts::e(&key_with_small_exp), &small_e); + + // Verify that the key is cryptographically valid + assert!(validate_skip_exponent_size(&key_with_small_exp).is_ok()); + } +} diff --git a/third_party/rsa/src/lib.rs b/third_party/rsa/src/lib.rs new file mode 100644 index 0000000..7a99366 --- /dev/null +++ b/third_party/rsa/src/lib.rs @@ -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::::new(); +//! let enc_data = public_key.encrypt(&mut rng, padding, &data[..]).expect("failed to encrypt"); +//! assert_ne!(&data[..], &enc_data[..]); +//! +//! // Decrypt +//! let padding = Oaep::::new(); +//! let dec_data = private_key.decrypt(padding, &enc_data).expect("failed to decrypt"); +//! assert_eq!(&data[..], &dec_data[..]); +//! ``` +//! +//! ## PKCS#1 v1.5 encryption +//! +//!
+//! Warning: +//! See security notes in the pkcs1v15 module. +//!
+//! +//! ``` +//! 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 +//! +//!
+//! Warning: +//! See security notes in the pkcs1v15 module. +//!
+//! +//! 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::::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::::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> { +//! # #[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> { +//! # #[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; diff --git a/third_party/rsa/src/oaep.rs b/third_party/rsa/src/oaep.rs new file mode 100644 index 0000000..96cd2ef --- /dev/null +++ b/third_party/rsa/src/oaep.rs @@ -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 { + /// Digest type to use. + pub digest: D, + + /// Digest to use for Mask Generation Function (MGF). + pub mgf_digest: MGD, + + /// Optional label. + pub label: Option>, +} + +impl Default for Oaep +where + D: Digest + FixedOutputReset, +{ + fn default() -> Self { + Self::new() + } +} + +impl Oaep +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::::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>>(label: S) -> Self { + Self { + digest: D::new(), + mgf_digest: D::new(), + label: Some(label.into()), + } + } +} + +impl Oaep +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::::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>>(label: S) -> Self { + Self { + digest: D::new(), + mgf_digest: MGD::new(), + label: Some(label.into()), + } + } +} + +impl PaddingScheme for Oaep +where + D: Digest + FixedOutputReset, + MGD: Digest + FixedOutputReset, +{ + fn decrypt( + mut self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], + ) -> Result> { + decrypt( + rng, + priv_key, + ciphertext, + &mut self.digest, + &mut self.mgf_digest, + self.label, + ) + } + + fn encrypt( + mut self, + rng: &mut Rng, + pub_key: &RsaPublicKey, + msg: &[u8], + ) -> Result> { + encrypt( + rng, + pub_key, + msg, + &mut self.digest, + &mut self.mgf_digest, + self.label, + ) + } +} + +impl fmt::Debug for Oaep { + 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( + rng: &mut R, + pub_key: &RsaPublicKey, + msg: &[u8], + digest: &mut D, + mgf_digest: &mut MGD, + label: Option>, +) -> Result> +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( + rng: &mut R, + pub_key: &RsaPublicKey, + msg: &[u8], + label: Option>, +) -> Result> +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( + rng: Option<&mut R>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], + digest: &mut D, + mgf_digest: &mut MGD, + label: Option>, +) -> Result> +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( + rng: Option<&mut R>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], + label: Option>, +) -> Result> +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::(&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::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + } + + fn get_label(rng: &mut ChaCha8Rng) -> Option> { + 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(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::::new_with_label(label.clone()); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + } else { + let padding = Oaep::::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::::new_with_label::>(label) + } else { + Oaep::::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::::new_with_mgf_hash_and_label::<_>(label.clone()); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + } else { + let padding = Oaep::::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::::new_with_mgf_hash_and_label::<_>(label) + } else { + Oaep::::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::::new(), "a_plain_text".as_bytes()) + .unwrap(); + assert!( + priv_key + .decrypt_blinded( + &mut rng, + Oaep::::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::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + } + + fn do_test_encrypt_decrypt_oaep_traits(prk: &RsaPrivateKey) { + do_test_oaep_with_different_hashes_traits::(prk); + } + + fn do_test_oaep_with_different_hashes_traits( + 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::::new_with_label(pub_key, label.clone()); + encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap() + } else { + let encrypting_key = EncryptingKey::::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::::new_with_label(prk.clone(), label.clone()) + } else { + DecryptingKey::::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::::new(pub_key); + let decrypting_key = DecryptingKey::::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" + ); + } +} diff --git a/third_party/rsa/src/oaep/decrypting_key.rs b/third_party/rsa/src/oaep/decrypting_key.rs new file mode 100644 index 0000000..95064a1 --- /dev/null +++ b/third_party/rsa/src/oaep/decrypting_key.rs @@ -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 +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPrivateKey, + label: Option>, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl DecryptingKey +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>>(key: RsaPrivateKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.into()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl Decryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt(&self, ciphertext: &[u8]) -> Result> { + decrypt_digest::(None, &self.inner, ciphertext, self.label.clone()) + } +} + +impl RandomizedDecryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + decrypt_digest::<_, D, MGD>(Some(rng), &self.inner, ciphertext, self.label.clone()) + } +} + +impl ZeroizeOnDrop for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ +} + +impl PartialEq for DecryptingKey +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::::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); + } +} diff --git a/third_party/rsa/src/oaep/encrypting_key.rs b/third_party/rsa/src/oaep/encrypting_key.rs new file mode 100644 index 0000000..a577901 --- /dev/null +++ b/third_party/rsa/src/oaep/encrypting_key.rs @@ -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 +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPublicKey, + label: Option>, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl EncryptingKey +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>>(key: RsaPublicKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.into()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl RandomizedEncryptor for EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn encrypt_with_rng(&self, rng: &mut R, msg: &[u8]) -> Result> { + encrypt_digest::<_, D, MGD>(rng, &self.inner, msg, self.label.clone()) + } +} + +impl PartialEq for EncryptingKey +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::::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); + } +} diff --git a/third_party/rsa/src/pkcs1v15.rs b/third_party/rsa/src/pkcs1v15.rs new file mode 100644 index 0000000..75834cb --- /dev/null +++ b/third_party/rsa/src/pkcs1v15.rs @@ -0,0 +1,675 @@ +//! PKCS#1 v1.5 support as described in [RFC8017 Β§ 8.2]. +//! +//!
+//! Warning +//! +//! 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). +//!
+//! +//! [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( + self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], + ) -> Result> { + decrypt(rng, priv_key, ciphertext) + } + + fn encrypt( + self, + rng: &mut Rng, + pub_key: &RsaPublicKey, + msg: &[u8], + ) -> Result> { + 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, + + /// 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() -> Self + where + D: Digest + AssociatedOid, + { + Self { + hash_len: Some(::output_size()), + prefix: pkcs1v15_generate_prefix::().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( + self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + hashed: &[u8], + ) -> Result> { + 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( + rng: &mut R, + pub_key: &RsaPublicKey, + msg: &[u8], +) -> Result> { + 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( + rng: Option<&mut R>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], +) -> Result> { + 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( + rng: Option<&mut R>, + priv_key: &RsaPrivateKey, + prefix: &[u8], + hashed: &[u8], +) -> Result> { + 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::(), &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::(), &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::::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::::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::::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::(), &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::::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::::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"); + } +} diff --git a/third_party/rsa/src/pkcs1v15/decrypting_key.rs b/third_party/rsa/src/pkcs1v15/decrypting_key.rs new file mode 100644 index 0000000..d3ae9ee --- /dev/null +++ b/third_party/rsa/src/pkcs1v15/decrypting_key.rs @@ -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> { + decrypt::(None, &self.inner, ciphertext) + } +} + +impl RandomizedDecryptor for DecryptingKey { + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + 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); + } +} diff --git a/third_party/rsa/src/pkcs1v15/encrypting_key.rs b/third_party/rsa/src/pkcs1v15/encrypting_key.rs new file mode 100644 index 0000000..bf851d5 --- /dev/null +++ b/third_party/rsa/src/pkcs1v15/encrypting_key.rs @@ -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(&self, rng: &mut R, msg: &[u8]) -> Result> { + 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); + } +} diff --git a/third_party/rsa/src/pkcs1v15/signature.rs b/third_party/rsa/src/pkcs1v15/signature.rs new file mode 100644 index 0000000..4dee9ce --- /dev/null +++ b/third_party/rsa/src/pkcs1v15/signature.rs @@ -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::new(0, self.to_vec()) + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = signature::Error; + + fn try_from(bytes: &[u8]) -> signature::Result { + // TODO(tarcieri): max length restriction? (#350) + let inner = BoxedUint::from_be_slice_vartime(bytes); + Ok(Self { inner }) + } +} + +impl From 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(&self, serializer: S) -> core::result::Result + 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(deserializer: D) -> core::result::Result + 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); + } +} diff --git a/third_party/rsa/src/pkcs1v15/signing_key.rs b/third_party/rsa/src/pkcs1v15/signing_key.rs new file mode 100644 index 0000000..eff44c9 --- /dev/null +++ b/third_party/rsa/src/pkcs1v15/signing_key.rs @@ -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 +where + D: Digest, +{ + inner: RsaPrivateKey, + prefix: Vec, + phantom: PhantomData, +} + +impl SigningKey +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::(), + phantom: Default::default(), + } + } + + /// Generate a new signing key with a prefix for the digest `D`. + pub fn random(rng: &mut R, bit_size: usize) -> Result { + Ok(Self { + inner: RsaPrivateKey::new(rng, bit_size)?, + prefix: pkcs1v15_generate_prefix::(), + phantom: Default::default(), + }) + } +} + +impl SigningKey +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(rng: &mut R, bit_size: usize) -> Result { + Ok(Self { + inner: RsaPrivateKey::new(rng, bit_size)?, + prefix: Vec::new(), + phantom: Default::default(), + }) + } +} + +// +// `*Signer` trait impls +// + +impl DigestSigner for SigningKey +where + D: Default + FixedOutput + HashMarker + Update, +{ + fn try_sign_digest signature::Result<()>>( + &self, + f: F, + ) -> signature::Result { + let mut digest = D::default(); + f(&mut digest)?; + sign::(None, &self.inner, &self.prefix, &digest.finalize_fixed())? + .as_slice() + .try_into() + } +} + +impl PrehashSigner for SigningKey +where + D: Digest, +{ + fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { + sign::(None, &self.inner, &self.prefix, prehash)? + .as_slice() + .try_into() + } +} + +impl RandomizedDigestSigner for SigningKey +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 { + let mut digest = D::default(); + f(&mut digest)?; + sign( + Some(rng), + &self.inner, + &self.prefix, + &digest.finalize_fixed(), + )? + .as_slice() + .try_into() + } +} + +impl RandomizedSigner for SigningKey +where + D: Digest, +{ + fn try_sign_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> signature::Result { + self.try_multipart_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultipartSigner for SigningKey +where + D: Digest, +{ + fn try_multipart_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> signature::Result { + 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 Signer for SigningKey +where + D: Digest, +{ + fn try_sign(&self, msg: &[u8]) -> signature::Result { + self.try_multipart_sign(&[msg]) + } +} + +impl MultipartSigner for SigningKey +where + D: Digest, +{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result { + let mut digest = D::new(); + msg.iter().for_each(|slice| digest.update(slice)); + sign::(None, &self.inner, &self.prefix, &digest.finalize())? + .as_slice() + .try_into() + } +} + +// +// Other trait impls +// + +impl AsRef for SigningKey +where + D: Digest, +{ + fn as_ref(&self) -> &RsaPrivateKey { + &self.inner + } +} + +#[cfg(feature = "encoding")] +impl AssociatedAlgorithmIdentifier for SigningKey +where + D: Digest, +{ + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID; +} + +#[cfg(feature = "encoding")] +impl EncodePrivateKey for SigningKey +where + D: Digest, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + self.inner.to_pkcs8_der() + } +} + +impl From for SigningKey +where + D: Digest + AssociatedOid, +{ + fn from(key: RsaPrivateKey) -> Self { + Self::new(key) + } +} + +impl From> for RsaPrivateKey +where + D: Digest, +{ + fn from(key: SigningKey) -> Self { + key.inner + } +} + +impl Keypair for SigningKey +where + D: Digest, +{ + type VerifyingKey = VerifyingKey; + + fn verifying_key(&self) -> Self::VerifyingKey { + VerifyingKey { + inner: self.inner.to_public_key(), + prefix: self.prefix.clone(), + phantom: Default::default(), + } + } +} + +#[cfg(feature = "encoding")] +impl SignatureAlgorithmIdentifier for SigningKey +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 TryFrom> for SigningKey +where + D: Digest + AssociatedOid, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + private_key_info + .algorithm + .assert_algorithm_oid(pkcs1::ALGORITHM_OID)?; + RsaPrivateKey::try_from(private_key_info).map(Self::new) + } +} + +impl ZeroizeOnDrop for SigningKey where D: Digest {} + +impl PartialEq for SigningKey +where + D: Digest, +{ + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner && self.prefix == other.prefix + } +} + +#[cfg(feature = "serde")] +impl Serialize for SigningKey +where + D: Digest, +{ + fn serialize(&self, serializer: S) -> core::result::Result + 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 +where + D: Digest + AssociatedOid, +{ + fn deserialize(deserializer: De) -> core::result::Result + 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::::new(priv_key); + + let tokens = [Token::Str(concat!( + "3056020100300d06092a864886f70d010101050004423040020100020900ab240c", + "3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa", + "ad020500ccaddf17020500cb529d3d020500bb526d6f", + ))]; + + assert_tokens(&signing_key.readable(), &tokens); + } +} diff --git a/third_party/rsa/src/pkcs1v15/verifying_key.rs b/third_party/rsa/src/pkcs1v15/verifying_key.rs new file mode 100644 index 0000000..5eb9d63 --- /dev/null +++ b/third_party/rsa/src/pkcs1v15/verifying_key.rs @@ -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 +where + D: Digest, +{ + pub(super) inner: RsaPublicKey, + pub(super) prefix: Vec, + pub(super) phantom: PhantomData, +} + +impl VerifyingKey +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::(), + phantom: Default::default(), + } + } +} + +impl VerifyingKey +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 DigestVerifier for VerifyingKey +where + D: Default + FixedOutput + HashMarker + Update, +{ + fn verify_digest 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 PrehashVerifier for VerifyingKey +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 Verifier for VerifyingKey +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 AsRef for VerifyingKey +where + D: Digest, +{ + fn as_ref(&self) -> &RsaPublicKey { + &self.inner + } +} + +#[cfg(feature = "encoding")] +impl AssociatedAlgorithmIdentifier for VerifyingKey +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 Clone for VerifyingKey +where + D: Digest, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + prefix: self.prefix.clone(), + phantom: Default::default(), + } + } +} + +#[cfg(feature = "encoding")] +impl EncodePublicKey for VerifyingKey +where + D: Digest, +{ + fn to_public_key_der(&self) -> spki::Result { + self.inner.to_public_key_der() + } +} + +impl From for VerifyingKey +where + D: Digest + AssociatedOid, +{ + fn from(key: RsaPublicKey) -> Self { + Self::new(key) + } +} + +impl From> for RsaPublicKey +where + D: Digest, +{ + fn from(key: VerifyingKey) -> Self { + key.inner + } +} + +#[cfg(feature = "encoding")] +impl SignatureAlgorithmIdentifier for VerifyingKey +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 TryFrom> for VerifyingKey +where + D: Digest + AssociatedOid, +{ + type Error = spki::Error; + + fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result { + spki.algorithm.assert_algorithm_oid(pkcs1::ALGORITHM_OID)?; + + RsaPublicKey::try_from(spki).map(Self::new) + } +} + +impl PartialEq for VerifyingKey +where + D: Digest, +{ + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner && self.prefix == other.prefix + } +} + +#[cfg(feature = "serde")] +impl Serialize for VerifyingKey +where + D: Digest, +{ + fn serialize(&self, serializer: S) -> Result + 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 +where + D: Digest + AssociatedOid, +{ + fn deserialize(deserializer: De) -> Result + 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::::new(pub_key); + + let tokens = [Token::Str( + "3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001", + )]; + + assert_tokens(&verifying_key.readable(), &tokens); + } +} diff --git a/third_party/rsa/src/pss.rs b/third_party/rsa/src/pss.rs new file mode 100644 index 0000000..363989a --- /dev/null +++ b/third_party/rsa/src/pss.rs @@ -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 { + /// 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, +} + +impl Default for Pss +where + D: Digest, +{ + fn default() -> Self { + Self::new() + } +} + +impl Pss +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(::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(::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 SignatureScheme for Pss +where + D: Digest + FixedOutputReset, +{ + fn sign( + mut self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + hashed: &[u8], + ) -> Result> { + 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 Debug for Pss { + 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( + pub_key: &RsaPublicKey, + hashed: &[u8], + sig: &BoxedUint, + sig_len: usize, + digest: &mut D, + salt_len: Option, +) -> 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( + pub_key: &RsaPublicKey, + hashed: &[u8], + sig: &BoxedUint, + salt_len: Option, +) -> 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::(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( + rng: &mut T, + blind: bool, + priv_key: &RsaPrivateKey, + hashed: &[u8], + salt_len: usize, + digest: &mut D, +) -> Result> +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( + rng: &mut T, + blind: bool, + priv_key: &RsaPrivateKey, + hashed: &[u8], + salt_len: usize, +) -> Result> +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( + blind_rng: Option<&mut T>, + priv_key: &RsaPrivateKey, + hashed: &[u8], + salt: &[u8], + digest: &mut D, +) -> Result> +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( + blind_rng: Option<&mut T>, + priv_key: &RsaPrivateKey, + hashed: &[u8], + salt: &[u8], +) -> Result> +where + T: TryCryptoRng + ?Sized, + D: Digest + FixedOutputReset, +{ + let em_bits = priv_key.n().bits() - 1; + let em = emsa_pss_encode_digest::(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() -> spki::Result +where + D: Digest + AssociatedOid, +{ + let salt_len: u8 = ::output_size() as u8; + get_pss_signature_algo_id::(salt_len) +} + +#[cfg(feature = "encoding")] +fn get_pss_signature_algo_id(salt_len: u8) -> spki::Result +where + D: Digest + AssociatedOid, +{ + let pss_params = RsaPssParams::new::(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::::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 = 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::::new(), &digest) + .expect("failed to sign"); + + priv_key + .to_public_key() + .verify(Pss::::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::::new_blinded(), &digest) + .expect("failed to sign"); + + priv_key + .to_public_key() + .verify(Pss::::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::::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::::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::::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::::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::::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::::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::::new(), &digest) + .expect("failed to sign"); + + priv_key + .to_public_key() + .verify(Pss::::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::::new(priv_key.clone()), + // maximum salt length + SigningKey::::new_with_salt_len( + priv_key.clone(), + priv_key.size() - Sha1::output_size() - 2, + ), + // unsalted + SigningKey::::new_with_salt_len(priv_key.clone(), 0), + ]; + + // verifying key uses default salt length strategy + let verifying_key = VerifyingKey::::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"); + } + } +} diff --git a/third_party/rsa/src/pss/blinded_signing_key.rs b/third_party/rsa/src/pss/blinded_signing_key.rs new file mode 100644 index 0000000..3063c8a --- /dev/null +++ b/third_party/rsa/src/pss/blinded_signing_key.rs @@ -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 +where + D: Digest, +{ + inner: RsaPrivateKey, + salt_len: usize, + phantom: PhantomData, +} + +impl BlindedSigningKey +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, ::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(rng: &mut R, bit_size: usize) -> Result { + Self::random_with_salt_len(rng, bit_size, ::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( + rng: &mut R, + bit_size: usize, + salt_len: usize, + ) -> Result { + 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 RandomizedSigner for BlindedSigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_sign_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> signature::Result { + self.try_multipart_sign_with_rng(rng, &[msg]) + } +} + +impl RandomizedMultipartSigner for BlindedSigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_multipart_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> signature::Result { + let mut digest = D::new(); + msg.iter() + .for_each(|slice| ::update(&mut digest, slice)); + sign_digest::<_, D>(rng, true, &self.inner, &digest.finalize(), self.salt_len)? + .as_slice() + .try_into() + } +} + +impl RandomizedDigestSigner for BlindedSigningKey +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 { + 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 RandomizedPrehashSigner for BlindedSigningKey +where + D: Digest + FixedOutputReset, +{ + fn sign_prehash_with_rng( + &self, + rng: &mut R, + prehash: &[u8], + ) -> signature::Result { + sign_digest::<_, D>(rng, true, &self.inner, prehash, self.salt_len)? + .as_slice() + .try_into() + } +} + +// +// Other trait impls +// + +impl AsRef for BlindedSigningKey +where + D: Digest, +{ + fn as_ref(&self) -> &RsaPrivateKey { + &self.inner + } +} + +#[cfg(feature = "encoding")] +impl AssociatedAlgorithmIdentifier for BlindedSigningKey +where + D: Digest, +{ + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID; +} + +#[cfg(feature = "encoding")] +impl DynSignatureAlgorithmIdentifier for BlindedSigningKey +where + D: Digest + AssociatedOid, +{ + fn signature_algorithm_identifier(&self) -> spki::Result { + get_pss_signature_algo_id::(self.salt_len as u8) + } +} + +#[cfg(feature = "encoding")] +impl EncodePrivateKey for BlindedSigningKey +where + D: Digest, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + self.inner.to_pkcs8_der() + } +} + +impl From for BlindedSigningKey +where + D: Digest, +{ + fn from(key: RsaPrivateKey) -> Self { + Self::new(key) + } +} + +impl From> for RsaPrivateKey +where + D: Digest, +{ + fn from(key: BlindedSigningKey) -> Self { + key.inner + } +} + +impl Keypair for BlindedSigningKey +where + D: Digest, +{ + type VerifyingKey = VerifyingKey; + 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 TryFrom> for BlindedSigningKey +where + D: Digest + AssociatedOid, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + RsaPrivateKey::try_from(private_key_info).map(Self::new) + } +} + +impl ZeroizeOnDrop for BlindedSigningKey where D: Digest {} + +impl PartialEq for BlindedSigningKey +where + D: Digest, +{ + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner && self.salt_len == other.salt_len + } +} + +#[cfg(feature = "serde")] +impl Serialize for BlindedSigningKey +where + D: Digest, +{ + fn serialize(&self, serializer: S) -> core::result::Result + 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 +where + D: Digest + AssociatedOid, +{ + fn deserialize(deserializer: De) -> core::result::Result + 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::::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); + } +} diff --git a/third_party/rsa/src/pss/signature.rs b/third_party/rsa/src/pss/signature.rs new file mode 100644 index 0000000..a5e24bc --- /dev/null +++ b/third_party/rsa/src/pss/signature.rs @@ -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::new(0, self.to_vec()) + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = signature::Error; + + fn try_from(bytes: &[u8]) -> signature::Result { + // TODO(tarcieri): max length restriction? (#350) + let inner = BoxedUint::from_be_slice_vartime(bytes); + Ok(Self { inner }) + } +} + +impl From 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(&self, serializer: S) -> core::result::Result + 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(deserializer: D) -> core::result::Result + 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); + } +} diff --git a/third_party/rsa/src/pss/signing_key.rs b/third_party/rsa/src/pss/signing_key.rs new file mode 100644 index 0000000..c159f0b --- /dev/null +++ b/third_party/rsa/src/pss/signing_key.rs @@ -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 +where + D: Digest, +{ + inner: RsaPrivateKey, + salt_len: usize, + phantom: PhantomData, +} + +impl SigningKey +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, ::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(rng: &mut R, bit_size: usize) -> Result { + Self::random_with_salt_len(rng, bit_size, ::output_size()) + } + + /// Generate a new random RSASSA-PSS signing key with a salt of the given length. + pub fn random_with_salt_len( + rng: &mut R, + bit_size: usize, + salt_len: usize, + ) -> Result { + 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 RandomizedDigestSigner for SigningKey +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 { + 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 RandomizedSigner for SigningKey +where + D: Digest + FixedOutputReset + Update, +{ + fn try_sign_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> signature::Result { + self.try_sign_digest_with_rng(rng, |digest: &mut D| { + Update::update(digest, msg); + Ok(()) + }) + } +} + +impl RandomizedMultipartSigner for SigningKey +where + D: Digest + FixedOutputReset + Update, +{ + fn try_multipart_sign_with_rng( + &self, + rng: &mut R, + msg: &[&[u8]], + ) -> signature::Result { + self.try_sign_digest_with_rng(rng, |digest: &mut D| { + msg.iter().for_each(|slice| Update::update(digest, slice)); + Ok(()) + }) + } +} + +impl RandomizedPrehashSigner for SigningKey +where + D: Digest + FixedOutputReset + Update, +{ + fn sign_prehash_with_rng( + &self, + rng: &mut R, + prehash: &[u8], + ) -> signature::Result { + sign_digest::<_, D>(rng, false, &self.inner, prehash, self.salt_len)? + .as_slice() + .try_into() + } +} + +#[cfg(feature = "getrandom")] +impl PrehashSigner for SigningKey +where + D: Digest + FixedOutputReset, +{ + fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { + self.sign_prehash_with_rng(&mut SysRng, prehash) + } +} + +#[cfg(feature = "getrandom")] +impl Signer for SigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_sign(&self, msg: &[u8]) -> signature::Result { + self.try_sign_with_rng(&mut SysRng, msg) + } +} + +#[cfg(feature = "getrandom")] +impl MultipartSigner for SigningKey +where + D: Digest + FixedOutputReset, +{ + fn try_multipart_sign(&self, msg: &[&[u8]]) -> signature::Result { + self.try_multipart_sign_with_rng(&mut SysRng, msg) + } +} + +// +// Other trait impls +// + +impl AsRef for SigningKey +where + D: Digest, +{ + fn as_ref(&self) -> &RsaPrivateKey { + &self.inner + } +} + +#[cfg(feature = "encoding")] +impl AssociatedAlgorithmIdentifier for SigningKey +where + D: Digest, +{ + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID; +} + +#[cfg(feature = "encoding")] +impl DynSignatureAlgorithmIdentifier for SigningKey +where + D: Digest + AssociatedOid, +{ + fn signature_algorithm_identifier(&self) -> spki::Result { + get_pss_signature_algo_id::(self.salt_len as u8) + } +} + +#[cfg(feature = "encoding")] +impl EncodePrivateKey for SigningKey +where + D: Digest, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + self.inner.to_pkcs8_der() + } +} + +impl From for SigningKey +where + D: Digest, +{ + fn from(key: RsaPrivateKey) -> Self { + Self::new(key) + } +} + +impl From> for RsaPrivateKey +where + D: Digest, +{ + fn from(key: SigningKey) -> Self { + key.inner + } +} + +impl Keypair for SigningKey +where + D: Digest, +{ + type VerifyingKey = VerifyingKey; + 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 TryFrom> for SigningKey +where + D: Digest + AssociatedOid, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + verify_algorithm_id(&private_key_info.algorithm)?; + RsaPrivateKey::try_from(private_key_info).map(Self::new) + } +} + +impl ZeroizeOnDrop for SigningKey where D: Digest {} + +impl PartialEq for SigningKey +where + D: Digest, +{ + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner && self.salt_len == other.salt_len + } +} + +#[cfg(feature = "serde")] +impl Serialize for SigningKey +where + D: Digest, +{ + fn serialize(&self, serializer: S) -> core::result::Result + 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 +where + D: Digest + AssociatedOid, +{ + fn deserialize(deserializer: De) -> core::result::Result + 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::::new(priv_key); + + let tokens = [Token::Str(concat!( + "3056020100300d06092a864886f70d010101050004423040020100020900ab240c", + "3361d02e370203010001020811e54a15259d22f9020500ceff5cf3020500d3a7aa", + "ad020500ccaddf17020500cb529d3d020500bb526d6f" + ))]; + + assert_tokens(&signing_key.readable(), &tokens); + } +} diff --git a/third_party/rsa/src/pss/verifying_key.rs b/third_party/rsa/src/pss/verifying_key.rs new file mode 100644 index 0000000..840c38b --- /dev/null +++ b/third_party/rsa/src/pss/verifying_key.rs @@ -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 +where + D: Digest, +{ + pub(super) inner: RsaPublicKey, + pub(super) salt_len: Option, + pub(super) phantom: PhantomData, +} + +impl VerifyingKey +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, ::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 { + self.salt_len + } +} + +// +// `*Verifier` trait impls +// + +impl DigestVerifier for VerifyingKey +where + D: Digest + FixedOutputReset + Update, +{ + fn verify_digest signature::Result<()>>( + &self, + f: F, + signature: &Signature, + ) -> signature::Result<()> { + let mut digest = D::new(); + f(&mut digest)?; + verify_digest::( + &self.inner, + &digest.finalize(), + &signature.inner, + self.salt_len, + ) + .map_err(|e| e.into()) + } +} + +impl PrehashVerifier for VerifyingKey +where + D: Digest + FixedOutputReset, +{ + fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> { + verify_digest::(&self.inner, prehash, &signature.inner, self.salt_len) + .map_err(|e| e.into()) + } +} + +impl Verifier for VerifyingKey +where + D: Digest + FixedOutputReset, +{ + fn verify(&self, msg: &[u8], signature: &Signature) -> signature::Result<()> { + verify_digest::( + &self.inner, + &D::digest(msg), + &signature.inner, + self.salt_len, + ) + .map_err(|e| e.into()) + } +} + +// +// Other trait impls +// + +impl AsRef for VerifyingKey +where + D: Digest, +{ + fn as_ref(&self) -> &RsaPublicKey { + &self.inner + } +} + +#[cfg(feature = "encoding")] +impl AssociatedAlgorithmIdentifier for VerifyingKey +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 Clone for VerifyingKey +where + D: Digest, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + salt_len: self.salt_len, + phantom: Default::default(), + } + } +} + +#[cfg(feature = "encoding")] +impl EncodePublicKey for VerifyingKey +where + D: Digest, +{ + fn to_public_key_der(&self) -> spki::Result { + self.inner.to_public_key_der() + } +} + +impl From for VerifyingKey +where + D: Digest, +{ + fn from(key: RsaPublicKey) -> Self { + Self::new(key) + } +} + +impl From> for RsaPublicKey +where + D: Digest, +{ + fn from(key: VerifyingKey) -> Self { + key.inner + } +} + +#[cfg(feature = "encoding")] +impl TryFrom> for VerifyingKey +where + D: Digest + AssociatedOid, +{ + type Error = spki::Error; + + fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> spki::Result { + 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 PartialEq for VerifyingKey +where + D: Digest, +{ + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner && self.salt_len == other.salt_len + } +} + +#[cfg(feature = "serde")] +impl Serialize for VerifyingKey +where + D: Digest, +{ + fn serialize(&self, serializer: S) -> Result + 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 +where + D: Digest + AssociatedOid, +{ + fn deserialize(deserializer: De) -> Result + 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::::new(pub_key); + + let tokens = [Token::Str( + "3024300d06092a864886f70d01010105000313003010020900ab240c3361d02e370203010001", + )]; + + assert_tokens(&verifying_key.readable(), &tokens); + } +} diff --git a/third_party/rsa/src/traits.rs b/third_party/rsa/src/traits.rs new file mode 100644 index 0000000..d3563d4 --- /dev/null +++ b/third_party/rsa/src/traits.rs @@ -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}; diff --git a/third_party/rsa/src/traits/encryption.rs b/third_party/rsa/src/traits/encryption.rs new file mode 100644 index 0000000..cb60541 --- /dev/null +++ b/third_party/rsa/src/traits/encryption.rs @@ -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(&self, rng: &mut R, msg: &[u8]) -> Result>; +} + +/// Decrypt the given message +pub trait Decryptor { + /// Decrypt the given message. + fn decrypt(&self, ciphertext: &[u8]) -> Result>; +} + +/// Decrypt the given message using provided random source +pub trait RandomizedDecryptor { + /// Decrypt the given message. + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result>; +} + +/// 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; +} diff --git a/third_party/rsa/src/traits/keys.rs b/third_party/rsa/src/traits/keys.rs new file mode 100644 index 0000000..ee43bc6 --- /dev/null +++ b/third_party/rsa/src/traits/keys.rs @@ -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; + + /// 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(); + } +} diff --git a/third_party/rsa/src/traits/padding.rs b/third_party/rsa/src/traits/padding.rs new file mode 100644 index 0000000..568c76d --- /dev/null +++ b/third_party/rsa/src/traits/padding.rs @@ -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( + self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + ciphertext: &[u8], + ) -> Result>; + + /// Encrypt the given message using the given public key. + fn encrypt( + self, + rng: &mut Rng, + pub_key: &RsaPublicKey, + msg: &[u8], + ) -> Result>; +} + +/// Digital signature scheme. +pub trait SignatureScheme { + /// Sign the given digest. + fn sign( + self, + rng: Option<&mut Rng>, + priv_key: &RsaPrivateKey, + hashed: &[u8], + ) -> Result>; + + /// 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<()>; +} diff --git a/third_party/rsa/tests/examples/pkcs1/rsa2048-priv.der b/third_party/rsa/tests/examples/pkcs1/rsa2048-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..bbf18768c21458f7a0ee3122ae18a9d8fc19863d GIT binary patch literal 1191 zcmV;Y1X%kpf&`-i0RRGm0RaHE#4J%?5T>f~g2q1E;zC2AaP#^mGrdSNV}D1=Pj5Eh zI$bI6!x=8oOF82YK~K)X{GjcD(k4+GgCB2nN~5tVIm5Jq>(UU$K}|PsSU>8^G{aP7VPu$GgA1{mJ&G;^X44 z@yqJSo27V!QjJy5u!AR|=*XW=06g-Dj*~J$+18c3yR6)hv`#(=U!!nX>Lsy1UMXC@ ztZnExtBg>uVd9vVTZb@z^VWBHr!hE71fzrvGJszNrkmCh9u$%9vB;N4Q z!=u6JbPNrjb`ok7p1`VofOsHOge6Ut9k6^%`5(4LObKO({aGa2tLrtKq5@KA&;=itITrs8h1_2zG&`dck83Sw0)c@5 z)AJN{en9C|gKv_)BqM6_@Y)2UMpMCnuV?9C2yK)7=3KHq{hbLnM`HDu9yS3g%(WasWbjMzWn1UC2I80o2F#O6s6 z)_&eJ1TQWo(iwn}iBTZd%6lLSf^9y|bB-{o z=I;c6%dbzs7d-t~dVXiGQD3>a-gOZeIdUlifq*fYR0h-rAbhRr>NT^C1Pot_ni}AO zexA^46+MHtf*Ccmvuc9AomR(@wTvex8dsCmIvr(v2$^|cu|qylahQKaFBD3AaxiUU z;nV-Xw;Oe<2=e4wNG_O!QOo3b;V!Z$&~#SGB(`sC?F5a5KT|$2riK*p!_IAv$(TIn FD)$5fRH*;} literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs1/rsa2048-priv.pem b/third_party/rsa/tests/examples/pkcs1/rsa2048-priv.pem new file mode 100644 index 0000000..3b924f5 --- /dev/null +++ b/third_party/rsa/tests/examples/pkcs1/rsa2048-priv.pem @@ -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----- diff --git a/third_party/rsa/tests/examples/pkcs1/rsa2048-pub.der b/third_party/rsa/tests/examples/pkcs1/rsa2048-pub.der new file mode 100644 index 0000000000000000000000000000000000000000..e75261c3e789cc322a2bf576d37c9134752d9e46 GIT binary patch literal 270 zcmV+p0rCDYf&mHwf&l>lw!|z^Ul69M@`A=b+u}k)p>Xs1CNsTAGh=^8%1>`L;5uC? z@531y$PYOUhf6u*4?$1P!Tg}@f#pZ-!p%4=cJ>^Q7?(Y{`crvwr^f;Iw|)tN-@#wN z87J;ueA!D+S)cH56Q{^18{Z<_yqed_yL>|t$%6Hc+j0}FMOd>kCT(;&?SYcfh)k`x zu}3+m+S+E^mk0*g$E-yRu{{lZS6{K1Q%}Yl#2diyS56KAFvq*Uz5U7drsCt`ukp+3 z$eX2jgi?)F&#;3hq3Fn;P5?aeh>nvoLD|-oyt}O2khD%d316ddSn4IQKVB(ZzN~HN UIIE0MuVLbtms^J~0s{d60mc}Hw*UYD literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs1/rsa2048-pub.pem b/third_party/rsa/tests/examples/pkcs1/rsa2048-pub.pem new file mode 100644 index 0000000..f2f72d7 --- /dev/null +++ b/third_party/rsa/tests/examples/pkcs1/rsa2048-pub.pem @@ -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----- diff --git a/third_party/rsa/tests/examples/pkcs1/rsa4096-priv.der b/third_party/rsa/tests/examples/pkcs1/rsa4096-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..b154e597d111d71e241b3d0fe3c7dd9826572f56 GIT binary patch literal 2349 zcmV+|3DWj3f(a=C0RRGm0s#Q0r$uss9-?7>N$6YGFx}%15I=p0?mu>3R1H1Zle@6G z6aWH+oeMyTbT(suShy(abl-y^V?cNLol2tBOrv#$F7=mY9XAus9`m&QYK|fTIOJz$ ziY$KPh8-dju4*2=->8dX-w^h^_n!Ys!kGIdW8WKxWH=i6vHJ|o2wnnf3fcB@8Y5A6 z&^c>JbxPEyQbHvPhdc82=k7dp-C;g9Tcq)!fnnpi$zbjCI1<*Z`5cmQo^XX7t2kF4Zw}XEK`Hr^>Nbz=b93L` zz(hss%18Pj?G-?%V97|jyqMZq9HHJWE+teB`M1k8KVTs{fl5WwS?{U15$^v8##xiU ziiN_z>Z*R#=CnQ-u3$uD&R>=l0XFB*pNk~=)IE4`5sdnK!bo*cZo9)Fxp&}(8tPCI zh9D9j3pq-h-3mfM8WT{!cMX?aaj*)ILt;S5XQ0ES!U_*<=YKyeuiBLD0Yky|<4<$o z%EEc>>wIH(BciY-$S4Bgq^`QCgl+kbc^OHEbKunT8I^BM*heioDd7n;4dSOp-r#z zj-FRmj}a&BL^cv@kVY}FC+s;}v+fdpxGad$Rw{qBU+s+)P`Bjb((`{;LSt{tZz!2# zQY~9o1_&&z`T@Z1nY7U9Crvt;E(VuOz5!`QhBawotQlGTo76|;xt5FtL*E zf2T!Z9gyUm4w8Z*Dz@<^WIh~sSBMt;HYHu zKeBfx-$?1<{BTyhc$QF*w}k;FKxAUbB`{I%gyT-*!_K(*Y-Nl%zC~4yFJ$7KA~R4` zU%~I9j=}V=wYorn@S29r!&8hJ*U56`*P~%BXhpi3``;PxkYLfLdES;a;?nrw#SpqA z#>7i7$-VmcTj>|B#nd|bv8p=VGt7h9lgh@D81|!Xw0{|$R3yn?+3kBCi{h@TWHYiS zBoh|G4(F0(4C_e0)-*(G8PbNd>K7tOjiIAyb0H4Hf=5xMfQ_22^_)bW8Knn&{>l<- z*{Iz~mR223C$nIm_$*r+A?7E9T||}}Gq`ot#QLA}SMO`%Hc4sWGklS`C0RYe;I(b4{W?ONQ7(|GZ>@sy=4`bW-TX*#6>7Mff_UI*+ z1dnaXcP-|-@e)nAOA7vBusVhWs{ncJQ}ZxKm#qP_j70t?rKDargoCIAwQ5S1e@O5e zhHi2mXXMGD1d8^FC;Fs4YK7T8d-n%Tx_NbD{Hx(33Abib#@k^{!ZFNMZJ#BqNI{@2 zG0pPs>ik&Qr{fp=BH92VwD_bY~=q>iO~&K%Y64Zg;35gYow(vi{xi zx%>P|*q)?CfSudby;)LsnG6f*HgCHw&wB-Y!Nc5-w|n?SZ3nx_TRa#Q^p0Y<6llZL z+1YFlNZPPTlfI3S?d1Z30RaHcHpYPkBCF-rMqf7T6Mdt(>PlZ7C&pn%DeI12E?l^d zKJlb;q8UWCWmmJ@EaYP3zcfb6Nst46R&ud$T)OBRWxDxiqGsF}@8}OryZ#zk1~LWC zDMQNNLLAO?uW)1i8iTJqIj@9LUs=7u|5SlrQm@hI)P^f8HdOY{s6D`rDxFff_%p^v zuXdD1ef(d2de^i;iC@cq`|VO=B(N1q6@oue2gsF3?cBB-p^atu@A zV`vLk=Ma<-0ary=22%Y3f&l<@i$=`@DfN;BM?h{xhq^%PF3tLsOWO#5ZgzHnPh=`~4+&@k0w0J0o3UbbrPN%h zW|0JAj<+hCHKENA<8A2CKAJAv}n~J4pmn-f4ti4b+Mw=Z2XtRtV^R>`Xk1G&N_uiu0L;b z$xFMw5Xk`iS{DmT2L34mf&l<^%1#Rgp6+ME#K6FU_{c1bmg>@Xx$l%iZ3~lc^0l4n z3dBaXl43HTsWi5gPQyw?^?mE^RNui*8XP->zC$@LK)|L8xJkdXvNh6bazX z2|ptf@)WpDXr&)WMJw?g%Bh+_-yBHG7Krn?I%gTx5D0IAx~e~|5FzdAhbII{93Ylj8%L{HR!YkW(K9)C0)EeweeVNb10aMjwB=l`Bz^1 z4JZLD1~j)M7ig_=AW1kW2G%vN%N;5Oz5mDs{$~S;xB8IQUzD%BAPCvK4Lr`|CEv#+ z0tE&G$@O{eg%Iq z6TEWqs3=po+5Dy%oqCX~WZCDRc{;*}`Js>z2r5!&@(D97RN0Mg83PFi{hn)r?-E9L zx(Kajm!v`=Jqda>98jQ%DBW`{YbmDBX9n8!@(+0Uad7)5lmo>g8(v&+!n&a5nsVUL T^Co@0jGNv6D6gq6H_44QgwAlb literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs1/rsa4096-priv.pem b/third_party/rsa/tests/examples/pkcs1/rsa4096-priv.pem new file mode 100644 index 0000000..9156bf0 --- /dev/null +++ b/third_party/rsa/tests/examples/pkcs1/rsa4096-priv.pem @@ -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----- diff --git a/third_party/rsa/tests/examples/pkcs1/rsa4096-pub.der b/third_party/rsa/tests/examples/pkcs1/rsa4096-pub.der new file mode 100644 index 0000000000000000000000000000000000000000..d8b7c7d7536766a24758bce9e127193871059c32 GIT binary patch literal 526 zcmV+p0`dJYf&vNxf&u{mr>8}7fgYk^eo5$C*D&4V4-h|ni0(gjUQ`V|*^|4lx)cBc zg`EpPiF7t&e^|IE>U7_OA!9&y`JGCl)l8#xg)a4%W*s*Z&K~o${c4UP0yyMnW{NC+ zXqb zY>p>BaMW5hTZ&i#^Ang@Es9?!Ry1ba$SsbC>E-och4*9psH9ue>JAq0?(^>DSxDoFE2*z2Hzlw#z zzv`-f)#kK57_MMMWX@lf6#+Kq(4UJW`qVvma1o69d%{R{P;R@!A-Q+ph8pTn5{4iW z9}783oZSjSK^hZKz;_LoU2(7qkV9fX$!DO$rNRmiZRdYKEU(&>?Eyo<_Tx`;;L5^z z?(2MGcO#;(CdeoP;iRs*sDy3#j(HhLhjZZ6^BI+IP2;N24*QxEeP*+)8UfQ=Ec-~* Q0I=t%4as#;0s{d60T^8R8vp&LNQUrr!ay9qXGc{0)hbn0Jg*|QC|?I zs`7%yKHK6#L!of<`X)2INHb%9N6JrcHsCs4DeuD>8psbh4TnoP;}1bk&cXbk?SbV- z?ZVACEOz!BkQkRex%yLia;L`u_P2fsg5SYkz!@j*UVPb0Pg$Sva1*D!4*+`O9C z%Da3+5y^t}j@xn*twmU~GA3d2d=c!W}oRnM@4C!y%bpH2Wg@`#R; zGC|qamAt#G+>o?RJ_%o=a9HXku|HlZT)wPr=s2s4P_JR)n3r3JF9HJr009Dm0RVo? zgJQt#uyhe*AzmW)O1aq^plJYb4E8pVN(f%mGIgsN!P%FiEN=hKee%(hUv6L??crge zD_p{Sq91|M$(%hNppKB^1v;>s@24c*@X*7f!Rd4i4WD)rY80Nps(pZXAXJ1UO_d$6 zd`$Ttwna<{Wr+P*B-^X&HJqXXQfSZw`POc;*7vQ!6Pv9-@onB?o)KhocEJLHfdJgV zVI(b1lCQ4kD#a7ZWx4loHu=4N4sKya8h-a>LM7}KJbqj;rWJZ_S`+_n7mkNm!OJt* z*1O{up7Dv8f1fxb%!GN!4 z>0k(Lll|sgvOfKt2{%V#^_U(u7)h^a&DT?u|7uPK>uItHHke#W@)dOit;609O&&ad}@7$k=iv)659fSfF<_jltLgVYfbyVLN&ndSVPhofRTw& zAlAxzAPa(RKF)KFE&xf7tO_b@scFaX!^#@4yg(^&GQM><*S_COu18N_=uKZDZln|G>8!b*l*S>r`Z*1)ZjfFo`J~5_-6!F8(ZH~#9Jm)I+*b!PL literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs8/rsa2048-priv.pem b/third_party/rsa/tests/examples/pkcs8/rsa2048-priv.pem new file mode 100644 index 0000000..e2a218c --- /dev/null +++ b/third_party/rsa/tests/examples/pkcs8/rsa2048-priv.pem @@ -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----- diff --git a/third_party/rsa/tests/examples/pkcs8/rsa2048-pub.der b/third_party/rsa/tests/examples/pkcs8/rsa2048-pub.der new file mode 100644 index 0000000000000000000000000000000000000000..4148aaaaaffcd235fca03d18d0605a7d28fdd4e4 GIT binary patch literal 294 zcmV+>0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>lw!|z^Ul69M@`A=b z+u}k)p>Xs1CNsTAGh=^8%1>`L;5uC?@531y$PYOUhf6u*4?$1P!Tg}@f#pZ-!p%4= zcJ>^Q7?(Y{`crvwr^f;Iw|)tN-@#wN87J;ueA!D+S)cH56Q{^18{Z<_yqed_yL>|t z$%6Hc+j0}FMOd>kCT(;&?SYcfh)k`xu}3+m+S+E^mk0*g$E-yRu{{lZS6{K1Q%}Yl z#2diyS56KAFvq*Uz5U7drsCt`ukp+3$eX2jgi?)F&#;3hq3Fn;P5?aeh>nvoLD|-o syt}O2khD%d316ddSn4IQKVB(ZzN~HNIIE0MuVLbtms^J~0s{d60e+T>_W%F@ literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs8/rsa2048-pub.pem b/third_party/rsa/tests/examples/pkcs8/rsa2048-pub.pem new file mode 100644 index 0000000..5ecd892 --- /dev/null +++ b/third_party/rsa/tests/examples/pkcs8/rsa2048-pub.pem @@ -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----- diff --git a/third_party/rsa/tests/examples/pkcs8/rsa2048-rfc9421-priv.der b/third_party/rsa/tests/examples/pkcs8/rsa2048-rfc9421-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..4585234df3b88c33591dc19ec04a394f650f0e23 GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)y1_>&LNQUOtExwzPX+oI6340o8E!TDWNB{lBMvoxU5K z*$`GqOG9_n0`g}27ehLqO9;@7=K4%$xl zRVy31TAs|SJVaYIv`Lism=hz@9zc`UaQ>HQthy|QEYMAyS_*s^0N|h-JeE*u(MVU& zr{9879(qmZ?Vg6Wh6RD`lL5uHS+jUu^1GJmMaqtcA0?5Afy^)jP+YQJIVV$}mW%}b z4?Bj?%1%y46x2!MoQU#N7E4ebOU7?0yDGede*yeXOEfRFq0b}w(DL`iFGV!sV)k#d z&fFLwaf3qQjM!l8Avc5CVm6?94>KSG(+5cG91>M+e5Y7`jtAV~*mub!z9EKxwyl;# zP5`rp{}gcyww-_x9ycM&=3M%U6dDwg)k90SQ-p!*vUFnAPTd}*UBV*-G45l;qhOxG zi>7+M;M}31E{=I?wMbes8`6En2?BwE0K@DEb?tjwmNW{OY2B7|xX%t!tw55sb|9v% zse#%L5EI{F4B^POFu@VpaugDdApeb}TTIxZ;YFz9aKBH7$m<|Jmr|&tLgixMpG8+` zxY{+$x+)V-UE*{C3u<;C@IB&o{J-8S16D(g(S6hbQmcT$CwoXqrbEjWilBPi!vcYU z0HV<7mP~CYw50M0ktU`>8ye?moDl%ZAo>wTTTmEw-thB9!2*GS0Ev+`OQ5N$U}!)*PO)*2nWuqv8Um_7 z(k2lDxi8%=5Nh+mP-{gba7|FW;0s!tG$7B7~52g<=)Kxcz-${hGX}3#9MthFQh@4`5Ur*lyK{mEVNP%~nvylZ; zLjr+;0F4kTeTcubpreHUUNb{>U8H3->KF+;bm(bPEI%F7H#lXRgn0Hf(6a_uj+p$A zv0ondAf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>luZw1zdiK$0e?7Xd zKU+5W0WH3pk!fETtBoLyM10F+W5uqWq-};=cF%tEEb+^JE*(Z6j&a+>aNQia-f6y| z1>?yNIy!LVXOn!DhTwoc4&M%p>X0ukNM!erCv`gFeX<2S)jT}&4(#l!6anghJ2Rd$ z#hEj$2uBUsokgW+9ES^-Eb{(Zi7J-Yb=G>S&M%uM*Dpw*17Xzw0Nz$o+})-ux-GQe zOB4H{$dutZ^n;Q*Xf{dg4-3wX3QHzfpbk9dYMeVkl>yao_FA}ToY@do sNlQa_)dKQn`xiqxpGyePjpN03rf|-XReJePXIGjsbEU}J0s{d60l^xBrvLx| literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der b/third_party/rsa/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..7c03cbaa9a77113575e3cd5bcca91b449d9f096e GIT binary patch literal 1217 zcmV;y1U~yPf&{$+0RS)!1_>&LNQUrr!ay9qXGc{0)hbn0HzZvroZ5z zS*uI)t(!6UY58u~=2H=q5%r21V8E;qs=P3%W&B#>G+`{^J0DpC`smc8noT+nhS?En zpr$f82g{O`&uincpw<3x0ev!@2#QK{7?ZEXSfqd6dypr36qtLh&AKFO{Fbgr5oy)t zPRBavCG4Y%)6BbWQ#wpW#X5pu5-Ls(4llMhaY3tQh?)Qe6XpW9(lVuzNe(r3M-%iO zlKC4qcL@&;m4(8gOBlR**&U??2hEx$fWFw&;4~@&3K}wPa!{Q;FS!*7AMSao$)mEO z-(%C4V@#z&HRZW47iE{Gn8Ii>sF@dNKO>%V?p?~HGcWe{@4+R9(E8Phssj}rcmB1&Ce@TvIS~-TuJI3JJxm>K zk(D$>q9|&1#o_{YxT_FlCh%jB>Z;V^-}21(rD1@JP5F9}M2xeePS+QUhEqnOCRF#l z6aJ#87I5RLzee}lQnw^2hN1kDn%0M(yyYP^GVrwm8_mtD(qHL8`{(*;SbEe7U+e71 zmZ}|6WwtK5GKYzyqk3JI5P(VGW9n!YT-wiz4W)PhZ^W$p-Z;2;*#V-6?0nDnH9XY- zi#Z*v)FEAD0UhGXpUNZ1iFwP0?ov^VxMf3O1^?02_JV7})Qz6N&9CDD0)c@5)AuR2 zGHGhefl2^-C%kxU5yR{5RkxE}OWsYz05Y_epn~ws!*p`O_{KC$y;leb^MS|>3jq*t zX*P<}DlQ8Qq!&vUv0otxKap`*%SNee5kI6M%5(F^rRC{7XsvQp^_+PtygU1)B zA)&uD_f(EDbn#Pd3d||BOD|wZ#1hV3zh;Lus}AiL2=-@+5RHEeWJR;4oVb^ujxmrp zjBHh!a`7#6<|-&PGSccG(Ce7tJX+n5XNQ(JhKts^-l9*F&?~_^j@pG0)c@5 zpsv7I*#=_huBH2-V5t@y&W6}VZ-QT29aUpp5Z+%|#7Z9H3e+y&Fqw!l7-DXF;-sRJ zh@k-HMobHbGIf6V(Kow1HNdEui!bF}>ysdY1if0`Cd?_LkVUS(m6y(u?L+nRI-OV= zWYi<HDB(`;!g$} z8h8qX6um6JKq5Q53}4Jw1*vDO)I_K>59t)(t*SLpK}`BZlh#|qC+@iSpRx=iFMPyO6M9&||F@!>T||5V z0)c@5n1I@6x0)x@cnF@X*G#_cUxf#8li1JaeDfCl!d>bKO!MkgL9rlEnypHwOGAut zi*0qW`nS2gGc-wvKCHkP5R)>}in@gQR?PsgVl}Pa)*&3As)#`W=Mz%(^C-gpR{SaJ f$B@(zQLS{pMc2VGdm+**Fuk&->T>Ys56DCP9Y;vY literal 0 HcmV?d00001 diff --git a/third_party/rsa/tests/pkcs1.rs b/third_party/rsa/tests/pkcs1.rs new file mode 100644 index 0000000..b638472 --- /dev/null +++ b/third_party/rsa/tests/pkcs1.rs @@ -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) +} diff --git a/third_party/rsa/tests/pkcs1v15.rs b/third_party/rsa/tests/pkcs1v15.rs new file mode 100644 index 0000000..49f7f7d --- /dev/null +++ b/third_party/rsa/tests/pkcs1v15.rs @@ -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::::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::::new(priv_key.clone()); + let key_via_from = rsa::pkcs1v15::SigningKey::::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()); +} diff --git a/third_party/rsa/tests/pkcs8.rs b/third_party/rsa/tests/pkcs8.rs new file mode 100644 index 0000000..e63f0b0 --- /dev/null +++ b/third_party/rsa/tests/pkcs8.rs @@ -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::::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::::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::::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::::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::::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::::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::::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::::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) +} diff --git a/third_party/rsa/tests/proptests.proptest-regressions b/third_party/rsa/tests/proptests.proptest-regressions new file mode 100644 index 0000000..145a36a --- /dev/null +++ b/third_party/rsa/tests/proptests.proptest-regressions @@ -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 = [] diff --git a/third_party/rsa/tests/proptests.rs b/third_party/rsa/tests/proptests.rs new file mode 100644 index 0000000..42cbe1e --- /dev/null +++ b/third_party/rsa/tests/proptests.rs @@ -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::>()) { + let signing_key = pkcs1v15::SigningKey::::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::>()) { + // let signing_key = pss::SigningKey::::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()); + // } +} diff --git a/third_party/rsa/tests/wycheproof.rs b/third_party/rsa/tests/wycheproof.rs new file mode 100644 index 0000000..3d30e86 --- /dev/null +++ b/third_party/rsa/tests/wycheproof.rs @@ -0,0 +1,272 @@ +//! Executes tests based on the wycheproof testsuite. + +#![cfg(feature = "encoding")] + +// This implementation here is based on +// + +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, + header: Vec, + 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, + + #[serde(default)] + sha: String, + + #[serde(default, rename(deserialize = "mgfSha"))] + mgf_sha: String, + + #[serde(default, rename(deserialize = "sLen"))] + salt_len: usize, + + tests: Vec, +} + +#[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, + #[serde(default, with = "hex::serde")] + sig: Vec, + 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) { + 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::::new(key.clone()); + vk.verify(&test.msg, &sig) + } + "SHA-384" => { + let vk = pkcs1v15::VerifyingKey::::new(key.clone()); + vk.verify(&test.msg, &sig) + } + "SHA-512" => { + let vk = pkcs1v15::VerifyingKey::::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::::new_with_salt_len(key.clone(), group.salt_len); + vk.verify(&test.msg, &sig) + } + "SHA-256" => { + let vk = + pss::VerifyingKey::::new_with_salt_len(key.clone(), group.salt_len); + vk.verify(&test.msg, &sig) + } + "SHA-224" => { + let vk = + pss::VerifyingKey::::new_with_salt_len(key.clone(), group.salt_len); + vk.verify(&test.msg, &sig) + } + "SHA-384" => { + let vk = + pss::VerifyingKey::::new_with_salt_len(key.clone(), group.salt_len); + vk.verify(&test.msg, &sig) + } + "SHA-512" => { + let vk = + pss::VerifyingKey::::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()); + } + }; + } + } +}