The AGPL asks a modified version to carry prominent notices saying it was modified, and giving a date. Publishing the source is the conveyance that asks for it, so it wants doing before the repository is public rather than at the release. Every upstream file the fork changed now says so in its header, beneath the notice it came with: 164 files, found by diffing against the upstream snapshot branch rather than by guessing, so the list is what actually differs. Files the fork wrote itself already carry their own copyright and need nothing. Upstream's notices are untouched, which its licence requires and which was already true. The README says the same thing in prose, since the obligation is on the work as a whole and not only its Rust files. Builds unchanged: the server and the test binary both compile.
345 lines
16 KiB
Rust
345 lines
16 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*
|
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
|
*/
|
|
|
|
use std::io::Cursor;
|
|
|
|
use self::{mailstore::jmap::JmapConfig, smtp::SmtpConfig, storage::Storage};
|
|
use crate::{
|
|
Core, Network,
|
|
auth::oauth::config::OAuthConfig,
|
|
config::mailstore::{
|
|
email::EmailConfig, imap::ImapConfig, scripts::Scripting, spamfilter::SpamFilterConfig,
|
|
},
|
|
};
|
|
use arc_swap::ArcSwap;
|
|
use groupware::GroupwareConfig;
|
|
use hyper::HeaderMap;
|
|
use p256::elliptic_curve::sec1::ToEncodedPoint;
|
|
use pkcs8::EncodePrivateKey;
|
|
use rsa::{
|
|
RsaPrivateKey,
|
|
pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey},
|
|
pkcs8::DecodePrivateKey as _,
|
|
traits::PublicKeyParts,
|
|
};
|
|
use store::registry::bootstrap::Bootstrap;
|
|
use telemetry::Metrics;
|
|
|
|
pub mod groupware;
|
|
pub mod inner;
|
|
pub mod mailstore;
|
|
pub mod network;
|
|
pub mod server;
|
|
pub mod smtp;
|
|
pub mod storage;
|
|
pub mod telemetry;
|
|
|
|
impl Core {
|
|
pub async fn parse(bp: &mut Bootstrap, storage: Storage) -> Self {
|
|
|
|
Self {
|
|
sieve: Scripting::parse(bp).await,
|
|
network: Network::parse(bp).await,
|
|
smtp: Box::pin(SmtpConfig::parse(bp)).await,
|
|
jmap: JmapConfig::parse(bp).await,
|
|
imap: ImapConfig::parse(bp).await,
|
|
oauth: OAuthConfig::parse(bp).await,
|
|
metrics: Metrics::parse(bp).await,
|
|
spam: SpamFilterConfig::parse(bp).await,
|
|
email: EmailConfig::parse(bp).await,
|
|
groupware: GroupwareConfig::parse(bp).await,
|
|
storage,
|
|
}
|
|
}
|
|
|
|
pub fn into_shared(self) -> ArcSwap<Self> {
|
|
ArcSwap::from_pointee(self)
|
|
}
|
|
}
|
|
|
|
const RSA_MIN_MODULUS_BITS: usize = 2048;
|
|
const RSA_MAX_MODULUS_BITS: usize = 8192;
|
|
|
|
fn no_key_found(pem: &str, expected: &str) -> String {
|
|
if pem.contains("ENCRYPTED PRIVATE KEY") || pem.contains("Proc-Type: 4,ENCRYPTED") {
|
|
format!(
|
|
"No usable {expected} private key found in PEM: the key is password-protected, \
|
|
which is not supported. Decrypt it first with 'openssl pkcs8 -topk8 -nocrypt'."
|
|
)
|
|
} else {
|
|
format!("No usable {expected} private key found in PEM")
|
|
}
|
|
}
|
|
|
|
pub struct RsaSigningKey {
|
|
pub pkcs1_der: Vec<u8>,
|
|
pub modulus: Vec<u8>,
|
|
pub exponent: Vec<u8>,
|
|
}
|
|
|
|
pub fn build_rsa_keypair(pem: &str) -> Result<RsaSigningKey, String> {
|
|
for item in rustls_pemfile::read_all(&mut Cursor::new(pem)) {
|
|
let key = match item.map_err(|err| format!("Failed to read private key PEM: {err}"))? {
|
|
rustls_pemfile::Item::Pkcs1Key(key) => {
|
|
RsaPrivateKey::from_pkcs1_der(key.secret_pkcs1_der())
|
|
.map_err(|err| format!("Failed to parse PKCS1 RSA key: {err}"))?
|
|
}
|
|
rustls_pemfile::Item::Pkcs8Key(key) => {
|
|
RsaPrivateKey::from_pkcs8_der(key.secret_pkcs8_der())
|
|
.map_err(|err| format!("Failed to parse PKCS8 RSA key: {err}"))?
|
|
}
|
|
_ => continue,
|
|
};
|
|
|
|
let bits = key.n().bits();
|
|
if !(RSA_MIN_MODULUS_BITS..=RSA_MAX_MODULUS_BITS).contains(&bits) {
|
|
return Err(format!(
|
|
"RSA key modulus is {bits} bits, expected between {RSA_MIN_MODULUS_BITS} and {RSA_MAX_MODULUS_BITS}"
|
|
));
|
|
}
|
|
|
|
let pkcs1_der = key
|
|
.to_pkcs1_der()
|
|
.map_err(|err| format!("Failed to encode RSA key as PKCS1: {err}"))?;
|
|
|
|
return Ok(RsaSigningKey {
|
|
pkcs1_der: pkcs1_der.as_bytes().to_vec(),
|
|
modulus: key.n().to_bytes_be(),
|
|
exponent: key.e().to_bytes_be(),
|
|
});
|
|
}
|
|
|
|
Err(no_key_found(pem, "RSA"))
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum EcKeyCurve {
|
|
P256,
|
|
P384,
|
|
}
|
|
|
|
pub struct EcdsaSigningKey {
|
|
pub pkcs8_der: Vec<u8>,
|
|
pub x: Vec<u8>,
|
|
pub y: Vec<u8>,
|
|
}
|
|
|
|
pub fn build_ecdsa_pem(curve: EcKeyCurve, pem: &str) -> Result<EcdsaSigningKey, String> {
|
|
for item in rustls_pemfile::read_all(&mut Cursor::new(pem)) {
|
|
let pkcs8 = match item.map_err(|err| format!("Failed to read private key PEM: {err}"))? {
|
|
rustls_pemfile::Item::Pkcs8Key(key) => key.secret_pkcs8_der().to_vec(),
|
|
rustls_pemfile::Item::Sec1Key(key) => curve
|
|
.sec1_to_pkcs8(key.secret_sec1_der())?
|
|
.as_bytes()
|
|
.to_vec(),
|
|
_ => continue,
|
|
};
|
|
|
|
let (x, y) = curve.public_coordinates(&pkcs8)?;
|
|
|
|
return Ok(EcdsaSigningKey {
|
|
pkcs8_der: pkcs8,
|
|
x,
|
|
y,
|
|
});
|
|
}
|
|
|
|
Err(no_key_found(pem, "ECDSA"))
|
|
}
|
|
|
|
impl EcKeyCurve {
|
|
fn sec1_to_pkcs8(self, der: &[u8]) -> Result<pkcs8::SecretDocument, String> {
|
|
match self {
|
|
EcKeyCurve::P256 => p256::SecretKey::from_sec1_der(der)
|
|
.map_err(|err| format!("Failed to parse SEC1 ECDSA key: {err}"))?
|
|
.to_pkcs8_der()
|
|
.map_err(|err| format!("Failed to convert SEC1 ECDSA key to PKCS8: {err}")),
|
|
EcKeyCurve::P384 => p384::SecretKey::from_sec1_der(der)
|
|
.map_err(|err| format!("Failed to parse SEC1 ECDSA key: {err}"))?
|
|
.to_pkcs8_der()
|
|
.map_err(|err| format!("Failed to convert SEC1 ECDSA key to PKCS8: {err}")),
|
|
}
|
|
}
|
|
|
|
fn public_coordinates(self, pkcs8: &[u8]) -> Result<(Vec<u8>, Vec<u8>), String> {
|
|
use pkcs8::DecodePrivateKey;
|
|
|
|
match self {
|
|
EcKeyCurve::P256 => {
|
|
let point = p256::SecretKey::from_pkcs8_der(pkcs8)
|
|
.map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}"))?
|
|
.public_key()
|
|
.to_encoded_point(false);
|
|
Ok((
|
|
point.x().map(|x| x.to_vec()).unwrap_or_default(),
|
|
point.y().map(|y| y.to_vec()).unwrap_or_default(),
|
|
))
|
|
}
|
|
EcKeyCurve::P384 => {
|
|
let point = p384::SecretKey::from_pkcs8_der(pkcs8)
|
|
.map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}"))?
|
|
.public_key()
|
|
.to_encoded_point(false);
|
|
Ok((
|
|
point.x().map(|x| x.to_vec()).unwrap_or_default(),
|
|
point.y().map(|y| y.to_vec()).unwrap_or_default(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{EcKeyCurve, build_ecdsa_pem, build_rsa_keypair};
|
|
|
|
const P256_SEC1: &str = "-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIJ9a6n/cu7XaQez5ZX8z8jDFkkfsMB1P9Vbqzbaes2zOoAoGCCqGSM49
|
|
AwEHoUQDQgAEPCbID7bo+8Nk1vIsTFhVKwRWvb9GWTzzwS75Dd8iZuFl23Twn6Sp
|
|
V2ZO1FC0WyXxcVOMZN2sJFlCjtaQS+p5Zg==
|
|
-----END EC PRIVATE KEY-----";
|
|
|
|
const P256_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
|
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgn1rqf9y7tdpB7Pll
|
|
fzPyMMWSR+wwHU/1VurNtp6zbM6hRANCAAQ8JsgPtuj7w2TW8ixMWFUrBFa9v0ZZ
|
|
PPPBLvkN3yJm4WXbdPCfpKlXZk7UULRbJfFxU4xk3awkWUKO1pBL6nlm
|
|
-----END PRIVATE KEY-----";
|
|
|
|
const P384_SEC1: &str = "-----BEGIN EC PRIVATE KEY-----
|
|
MIGkAgEBBDAeecJf8ju/70Nf5nbI4DeRo/+Z3VWXUvB+GwuUczew7fyMbyc6B3EE
|
|
BskOIqvqu6egBwYFK4EEACKhZANiAAQQjDW03Xn2h9ZmmCMRx+uRaLLfg4o2XITE
|
|
pwACH9EY4IjTe9LNNp5CTjERd+RlpWxkYopmDS5Trzycz9sDxxSzzXmq90vomJqt
|
|
fTnNHPFHuR2SAiwuzUf26rcPwa7DCWk=
|
|
-----END EC PRIVATE KEY-----";
|
|
|
|
const P384_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
|
|
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAeecJf8ju/70Nf5nbI
|
|
4DeRo/+Z3VWXUvB+GwuUczew7fyMbyc6B3EEBskOIqvqu6ehZANiAAQQjDW03Xn2
|
|
h9ZmmCMRx+uRaLLfg4o2XITEpwACH9EY4IjTe9LNNp5CTjERd+RlpWxkYopmDS5T
|
|
rzycz9sDxxSzzXmq90vomJqtfTnNHPFHuR2SAiwuzUf26rcPwa7DCWk=
|
|
-----END PRIVATE KEY-----";
|
|
|
|
#[test]
|
|
fn ecdsa_pem_accepts_sec1_and_pkcs8() {
|
|
let sec1 =
|
|
build_ecdsa_pem(EcKeyCurve::P256, P256_SEC1).expect("P-256 SEC1 key should parse");
|
|
let pkcs8 =
|
|
build_ecdsa_pem(EcKeyCurve::P256, P256_PKCS8).expect("P-256 PKCS8 key should parse");
|
|
assert_eq!((&sec1.x, &sec1.y), (&pkcs8.x, &pkcs8.y));
|
|
assert_eq!(sec1.x.len(), 32);
|
|
|
|
let sec1 =
|
|
build_ecdsa_pem(EcKeyCurve::P384, P384_SEC1).expect("P-384 SEC1 key should parse");
|
|
let pkcs8 =
|
|
build_ecdsa_pem(EcKeyCurve::P384, P384_PKCS8).expect("P-384 PKCS8 key should parse");
|
|
assert_eq!((&sec1.x, &sec1.y), (&pkcs8.x, &pkcs8.y));
|
|
assert_eq!(sec1.x.len(), 48);
|
|
}
|
|
|
|
const RSA_PKCS1: &str = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAt5Kp7o945bxbnvviI9Kzbjcwi6B5cStu4dBbNhe/ld0Js4tQ\n8Uq9qYaFBlocYzWkEd3e2IG0+uIVB37ewUe0xjq25u6G4ZWeK+SZgzXB4jHinXvh\nuhHW+KzbmO+aYO115451Cu6ymJ8DLVaR6zxT9CJsiS4lMsYZ5JHcLY3az1A5z0df\nF+chjR+sLxdc0ggKqnX6fT/sVXHIlVk6riyeFV929k/v1f0pmRQ2nNu0NMSOK7Mk\nqsvHiAb1e/41LIwlbmbzd5ASHitYYXKP+2YR29SRr2D+52S1M29h4/XbUcP6Zo2U\np5mKgQ0kFZ8pHFhbruamzRp87+yhu98IbZ9ksQIDAQABAoIBAAu2+BGxhbNReR5U\n8Co9krZEntw2NjHG5glSkNOLoe4IIEudJyHy1VYpb7lHTFr3bBw4xrUV1+0PuuxS\nyBfZAdwJmKz1iVWBhQnDiZliN5h9+vp2UqIba9bMPypMFhO766OGh4kWUP7k3ODK\njr7Oh4QDo14AvB54nmPj/ANLM2y50/Upy5s7FK0tm0ntzxSscwQFSZAJ9B0ne6Qe\nu1/PXgiXW4JKNOgrCTrRB2BcOi/Ke6OA/kg54sD+Z9PZivO/qHTx9xXzqivmbg9a\nGmoivaWH/pKwAywFogJnWH/iTe+r//fKdlEDeK+s/iCr0ht//c0w+GxPvPF//wz0\n+1u9+n0CgYEA5M7jzpT8rCYWdORvvRP1BC4+A5jb83zXW4FS+rZRSmq775zDPAif\npm653vAlNHIphEvqSdVw64+36nJFtjuBI17BHCQi0j3iNVjrLC7lbfqIobnNDdmR\n9VeqZ6qwPYt2oi4iBY2dAnPdYVTDMomHSC4vW/SER0l9A9bxt3Co1a0CgYEAzWOQ\n490s6K186CyUMFrNrUmIWEJNd7b6JGI+oCioZLtPZzxO4ebc+bHEPbpbSqx7lJRJ\nt5u6zw/RwUc+6YXXImekvMfZpZMH9v1wjp3djnxGQO4ucmvmu6H25qcYup8tRtlo\n2AVLd1jg3yka1yr7O26M3bhVfm5LOUQfoLuCA5UCgYEA3Iw7882SfFE+RjBHMIcD\nHqOALTFzmhDU+SQAGyAP3V5ihwWg/sYFNYT3btgl1JbSQ+51B/RQIw9mJPs/DPfw\nc2qLU5fVZLg3ylpKXU1a4xaiCtmwuM/mLAnzfHd/5+L9WDiFnLqzBEEwu/fbK2R7\nXOz/w3A+7QP+F+xhFAPpCgUCgYEAsnZOIkA/UlnUi6SYir+LsYOQLihGSbw687xN\n8DoDv6sl3mz/mbhQz8GP45b21hazNrH2r8xn8J0tRATU/HIoMaPe942rZvwv0oP6\n9mDjb3g6TxbmUtPA485iy53rldTTsZkdSX6oSSZ4FlAQG2AkdkqjqdAOsVHCmRrB\nZJco7FUCgYBwk7tQt3YS5b0wi8fH3BIfAH31vJ2VGlAin860H8FXjAj8EZ7Ff9Iq\n5dQIyPbp89TOSxIVxPGniI2ruLy4DZQM7xa42oyxyRir4UeHN2P5D2yEAHaVSwSd\nO6yiiOBj62OATapI8BqeFJZGRFltDsj6XbwC/Z9S2tRKCuE/zp+FLg==\n-----END RSA PRIVATE KEY-----";
|
|
|
|
const RSA_PKCS8: &str = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3kqnuj3jlvFue\n++Ij0rNuNzCLoHlxK27h0Fs2F7+V3Qmzi1DxSr2phoUGWhxjNaQR3d7YgbT64hUH\nft7BR7TGOrbm7obhlZ4r5JmDNcHiMeKde+G6Edb4rNuY75pg7XXnjnUK7rKYnwMt\nVpHrPFP0ImyJLiUyxhnkkdwtjdrPUDnPR18X5yGNH6wvF1zSCAqqdfp9P+xVcciV\nWTquLJ4VX3b2T+/V/SmZFDac27Q0xI4rsySqy8eIBvV7/jUsjCVuZvN3kBIeK1hh\nco/7ZhHb1JGvYP7nZLUzb2Hj9dtRw/pmjZSnmYqBDSQVnykcWFuu5qbNGnzv7KG7\n3whtn2SxAgMBAAECggEAC7b4EbGFs1F5HlTwKj2StkSe3DY2McbmCVKQ04uh7ggg\nS50nIfLVVilvuUdMWvdsHDjGtRXX7Q+67FLIF9kB3AmYrPWJVYGFCcOJmWI3mH36\n+nZSohtr1sw/KkwWE7vro4aHiRZQ/uTc4MqOvs6HhAOjXgC8HnieY+P8A0szbLnT\n9SnLmzsUrS2bSe3PFKxzBAVJkAn0HSd7pB67X89eCJdbgko06CsJOtEHYFw6L8p7\no4D+SDniwP5n09mK87+odPH3FfOqK+ZuD1oaaiK9pYf+krADLAWiAmdYf+JN76v/\n98p2UQN4r6z+IKvSG3/9zTD4bE+88X//DPT7W736fQKBgQDkzuPOlPysJhZ05G+9\nE/UELj4DmNvzfNdbgVL6tlFKarvvnMM8CJ+mbrne8CU0cimES+pJ1XDrj7fqckW2\nO4EjXsEcJCLSPeI1WOssLuVt+oihuc0N2ZH1V6pnqrA9i3aiLiIFjZ0Cc91hVMMy\niYdILi9b9IRHSX0D1vG3cKjVrQKBgQDNY5Dj3SzorXzoLJQwWs2tSYhYQk13tvok\nYj6gKKhku09nPE7h5tz5scQ9ultKrHuUlEm3m7rPD9HBRz7phdciZ6S8x9mlkwf2\n/XCOnd2OfEZA7i5ya+a7ofbmpxi6ny1G2WjYBUt3WODfKRrXKvs7bozduFV+bks5\nRB+gu4IDlQKBgQDcjDvzzZJ8UT5GMEcwhwMeo4AtMXOaENT5JAAbIA/dXmKHBaD+\nxgU1hPdu2CXUltJD7nUH9FAjD2Yk+z8M9/BzaotTl9VkuDfKWkpdTVrjFqIK2bC4\nz+YsCfN8d3/n4v1YOIWcurMEQTC799srZHtc7P/DcD7tA/4X7GEUA+kKBQKBgQCy\ndk4iQD9SWdSLpJiKv4uxg5AuKEZJvDrzvE3wOgO/qyXebP+ZuFDPwY/jlvbWFrM2\nsfavzGfwnS1EBNT8cigxo973jatm/C/Sg/r2YONveDpPFuZS08DjzmLLneuV1NOx\nmR1JfqhJJngWUBAbYCR2SqOp0A6xUcKZGsFklyjsVQKBgHCTu1C3dhLlvTCLx8fc\nEh8AffW8nZUaUCKfzrQfwVeMCPwRnsV/0irl1AjI9unz1M5LEhXE8aeIjau4vLgN\nlAzvFrjajLHJGKvhR4c3Y/kPbIQAdpVLBJ07rKKI4GPrY4BNqkjwGp4UlkZEWW0O\nyPpdvAL9n1La1EoK4T/On4Uu\n-----END PRIVATE KEY-----";
|
|
|
|
#[test]
|
|
fn rsa_pem_accepts_pkcs1_and_pkcs8() {
|
|
let a = build_rsa_keypair(RSA_PKCS1).expect("PKCS1 RSA key should parse");
|
|
let b = build_rsa_keypair(RSA_PKCS8).expect("PKCS8 RSA key should parse");
|
|
assert_eq!(a.modulus, b.modulus);
|
|
assert_eq!(a.exponent, b.exponent);
|
|
assert_eq!(a.pkcs1_der, b.pkcs1_der);
|
|
assert_eq!(a.modulus.len(), 256);
|
|
}
|
|
|
|
#[test]
|
|
fn signing_keys_are_accepted_by_jsonwebtoken() {
|
|
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct Claims {
|
|
sub: &'static str,
|
|
}
|
|
|
|
let claims = Claims { sub: "test" };
|
|
|
|
for (curve, pem, alg) in [
|
|
(EcKeyCurve::P256, P256_SEC1, Algorithm::ES256),
|
|
(EcKeyCurve::P256, P256_PKCS8, Algorithm::ES256),
|
|
(EcKeyCurve::P384, P384_SEC1, Algorithm::ES384),
|
|
(EcKeyCurve::P384, P384_PKCS8, Algorithm::ES384),
|
|
] {
|
|
let key = build_ecdsa_pem(curve, pem).expect("key should parse");
|
|
jsonwebtoken::encode(
|
|
&Header::new(alg),
|
|
&claims,
|
|
&EncodingKey::from_ec_der(&key.pkcs8_der),
|
|
)
|
|
.unwrap_or_else(|err| panic!("{alg:?} signing failed: {err}"));
|
|
}
|
|
|
|
for pem in [RSA_PKCS1, RSA_PKCS8] {
|
|
let key = build_rsa_keypair(pem).expect("key should parse");
|
|
for alg in [Algorithm::RS256, Algorithm::PS512] {
|
|
jsonwebtoken::encode(
|
|
&Header::new(alg),
|
|
&claims,
|
|
&EncodingKey::from_rsa_der(&key.pkcs1_der),
|
|
)
|
|
.unwrap_or_else(|err| panic!("{alg:?} signing failed: {err}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ecdsa_pem_rejects_keyless_pem() {
|
|
let err = match build_ecdsa_pem(
|
|
EcKeyCurve::P256,
|
|
"-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----",
|
|
) {
|
|
Ok(_) => panic!("expected a certificate-only PEM to be rejected"),
|
|
Err(err) => err,
|
|
};
|
|
assert!(err.contains("No usable ECDSA private key"), "{err}");
|
|
}
|
|
|
|
const P256_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBCoW4qsep9YbFLRW2u4\nk8ljAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQZYoEYHqh+y9uqT70\n6521jwSBkA9dcdq6hT/7Fzqcu0wX3QVr+8g1Kxc6tCV9dLShi8VU2ax8jG3zZt3h\nBp1CLyX8UfT98SujtoH36PEXPDDTralcP6vWViqGx5AagT4DRFjcI8yucTUXkLoD\n9ZIRBVPviTeznEHt3OvCCMuO76rsyu/gxNC7D46TBtq8JX1OFcaXPctpN8l5GKqH\nh4gA6av3og==\n-----END ENCRYPTED PRIVATE KEY-----";
|
|
|
|
#[test]
|
|
fn ecdsa_pem_reports_password_protected_key() {
|
|
let err = match build_ecdsa_pem(EcKeyCurve::P256, P256_ENCRYPTED) {
|
|
Ok(_) => panic!("expected an encrypted key to be rejected"),
|
|
Err(err) => err,
|
|
};
|
|
assert!(err.contains("password-protected"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn rsa_pem_rejects_undersized_modulus() {
|
|
const RSA_1024: &str = "-----BEGIN RSA PRIVATE KEY-----
|
|
MIICXAIBAAKBgQCz6gcAg0f+2/HFrudtMfRSylyzI8W/lNmPQZhUpz+R6D/7/+4/
|
|
HFKsUcZIi+nzdOnrzW/kw19nVOKk2ylAUNV9d2TR75HqrPBYsu0LCDRidb9XOyhY
|
|
bQJII1KuFlaWNjfxG28Tlg//FdVPPkn/oTQwnhvMcWCK3Hatho6cx9uzWwIDAQAB
|
|
AoGBAIPvjwr1OwrOyFIrnVMaWw2LkMdd6FpCEflYJRmPPLMHGkT2vgRSBN6RaVMy
|
|
J3J9vj1J/lBIZeIlAb/baDjeDnAj5GBzCB319oxnBuZSmpyYntW1DEsdhbK0Yeu+
|
|
7v05oXBXfzdZvGBWYrwlj5ipoHQo0R+WN4NVXqJFwiagaGBBAkEA6qwv5Pww54za
|
|
fHNUD1M6MKRBk5m0Y/GJ58sWmnmFJI6I3sHBIfcy5lylm5KecduzSKoVtAUUbNWf
|
|
KOKoZcKHMwJBAMRD2WDEd5+8q5ZxzYG0x5sEdz1lhJkt+YSbudNgfE1kPDDrCE0V
|
|
8+hgNdp6Mj1hfihwB0hTCcnaPsXLl9AyAzkCQDVU+HWD0uFso2LRGvN4qKrRSY3v
|
|
yo1EIWEqSHLG1zldo0FsqyW69jhgKcrXYWbi1TXYYaJN3Tx2t/skt7yYnv0CQDtD
|
|
NYdHq8tbAADcei5ZNRB058BtP/206SwGjbTq5H3F73rh7U7BezXGn1xKG5N3Nc3m
|
|
DfzjvgfqU5wMHtopz9kCQBw3AAiRCY8Y0UgejUtu8tXIK76qebaNcMCabBnFrAqV
|
|
A4Oj1c5BcOHVtww9W6NeiiRMJpUNN71gmyjsnOyT3cY=
|
|
-----END RSA PRIVATE KEY-----";
|
|
|
|
let err = match build_rsa_keypair(RSA_1024) {
|
|
Ok(_) => panic!("expected a 1024-bit RSA key to be rejected"),
|
|
Err(err) => err,
|
|
};
|
|
assert!(err.contains("1024 bits"), "{err}");
|
|
}
|
|
}
|