use std::fmt;
use base64ct::{Base64, Encoding};
use pkcs8::der::Decode;
use qso_graph_trust_awslc::RsaKey;
use zeroize::Zeroizing;
use crate::protected_key::{Envelope, Parameters};
use crate::records::{self, Record, RecordError};
#[derive(Clone)]
pub struct KeyEntry {
callsign: String,
request_dxcc: Option<u32>,
public_key_pem: String,
private_key_pem: Zeroizing<String>,
}
impl fmt::Debug for KeyEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyEntry")
.field("callsign", &self.callsign)
.field("encrypted", &self.is_encrypted())
.finish_non_exhaustive()
}
}
pub struct TqslKey {
key: RsaKey,
}
impl fmt::Debug for TqslKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TqslKey(..)")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum KeyError {
File(RecordError),
MissingField(&'static str),
RepeatedField(&'static str),
NotAKey,
PasswordNeeded,
WrongPassword,
NotRsa,
FileTooLarge,
NotLotwKey,
PublicKeyMismatch,
UnsupportedProtection,
Signing,
}
impl fmt::Display for KeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(e) => write!(f, "the key file could not be read: {e}"),
Self::MissingField(name) => write!(f, "the key file has no {name}"),
Self::RepeatedField(name) => write!(f, "the key file gives {name} more than once"),
Self::NotAKey => f.write_str("the key file does not hold a private key"),
Self::PasswordNeeded => f.write_str("this key is protected: its password is needed"),
Self::WrongPassword => f.write_str("the password does not unlock this key"),
Self::NotRsa => f.write_str("the key is not an RSA key"),
Self::FileTooLarge => f.write_str("the key file is too large to be one"),
Self::NotLotwKey => f.write_str("the key is not the kind LoTW uses"),
Self::PublicKeyMismatch => {
f.write_str("the key file's public key does not belong to its private key")
}
Self::UnsupportedProtection => {
f.write_str("the key is protected in a way QG Trust does not accept")
}
Self::Signing => f.write_str("the record could not be signed"),
}
}
}
impl std::error::Error for KeyError {}
const ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
pub const MAX_KEY_FILE_LEN: usize = 1024 * 1024;
pub(crate) fn decrypt(
params: &Parameters<'_>,
password: &[u8],
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, KeyError> {
if !params.accepted() {
return Err(KeyError::UnsupportedProtection);
}
let key = qso_graph_trust_awslc::import_pbkdf2(
params.digest,
password,
params.salt,
std::num::NonZeroU32::new(params.iteration_count).ok_or(KeyError::UnsupportedProtection)?,
params.key_len,
);
qso_graph_trust_awslc::import_cbc_decrypt(params.cipher, &key, params.iv, ciphertext)
.map_err(|_| KeyError::WrongPassword)
}
pub const MAX_PBKDF2_ITERATIONS: u32 = 1_000_000;
impl KeyEntry {
pub fn read_key_file(bytes: &[u8]) -> Result<Vec<KeyEntry>, KeyError> {
if bytes.len() > MAX_KEY_FILE_LEN {
return Err(KeyError::FileTooLarge);
}
records::parse(bytes)
.map_err(KeyError::File)?
.iter()
.map(Self::from_record)
.collect()
}
fn from_record(record: &Record) -> Result<KeyEntry, KeyError> {
for name in [
"CALLSIGN",
"PUBLIC_KEY",
"PRIVATE_KEY",
"TQSL_CRQ_DXCC_ENTITY",
] {
if record
.fields
.iter()
.filter(|f| f.name.eq_ignore_ascii_case(name))
.count()
> 1
{
return Err(KeyError::RepeatedField(name));
}
}
let text = |name: &'static str| record.text(name).ok_or(KeyError::MissingField(name));
let request_dxcc = match record.get("TQSL_CRQ_DXCC_ENTITY") {
None => None,
Some(v) => Some(
std::str::from_utf8(v)
.ok()
.and_then(|d| d.trim().parse().ok())
.ok_or(KeyError::NotAKey)?,
),
};
Ok(KeyEntry {
callsign: text("CALLSIGN")?.to_string(),
request_dxcc,
public_key_pem: text("PUBLIC_KEY")?.to_string(),
private_key_pem: Zeroizing::new(text("PRIVATE_KEY")?.to_string()),
})
}
pub fn callsign(&self) -> &str {
&self.callsign
}
pub fn request_dxcc(&self) -> Option<u32> {
self.request_dxcc
}
pub fn public_key_pem(&self) -> &str {
&self.public_key_pem
}
pub fn public_key_der(&self) -> Option<Vec<u8>> {
let (label, der) = pkcs8::der::pem::decode_vec(self.public_key_pem.as_bytes()).ok()?;
(label == "PUBLIC KEY").then_some(der)
}
pub fn check(&self) -> Result<(), KeyError> {
const RSA: pkcs8::ObjectIdentifier =
pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
let spki = self.public_key_der().ok_or(KeyError::PublicKeyMismatch)?;
let info = pkcs8::spki::SubjectPublicKeyInfoRef::from_der(&spki)
.map_err(|_| KeyError::PublicKeyMismatch)?;
if info.algorithm.oid != RSA {
return Err(KeyError::NotRsa);
}
if !self.is_encrypted() {
return self.unlock(None).map(drop);
}
let mut decoded = Zeroizing::new(vec![0u8; self.private_key_pem.len()]);
let (label, der) = pkcs8::der::pem::decode(self.private_key_pem.as_bytes(), &mut decoded)
.map_err(|_| KeyError::NotAKey)?;
if label != "ENCRYPTED PRIVATE KEY" {
return Err(KeyError::NotAKey);
}
let envelope = Envelope::from_der(der).map_err(|_| KeyError::NotAKey)?;
let params = envelope
.parameters()
.map_err(|_| KeyError::UnsupportedProtection)?;
if !params.accepted() {
return Err(KeyError::UnsupportedProtection);
}
Ok(())
}
pub fn is_encrypted(&self) -> bool {
self.private_key_pem.trim_start().starts_with(ENCRYPTED)
}
pub fn unlock(&self, password: Option<&[u8]>) -> Result<TqslKey, KeyError> {
crate::memory::scrub_after(|| {
let der = self.pkcs8_der_inner(password)?;
TqslKey::from_pkcs8_der_inner(
&der,
&self.public_key_der().ok_or(KeyError::PublicKeyMismatch)?,
)
})
}
pub(crate) fn pkcs8_der(
&self,
password: Option<&[u8]>,
) -> Result<Zeroizing<Vec<u8>>, KeyError> {
crate::memory::scrub_after(|| self.pkcs8_der_inner(password))
}
#[inline(never)]
fn pkcs8_der_inner(&self, password: Option<&[u8]>) -> Result<Zeroizing<Vec<u8>>, KeyError> {
if self.is_encrypted() && password.is_none() {
return Err(KeyError::PasswordNeeded);
}
let mut decoded = Zeroizing::new(vec![0u8; self.private_key_pem.len()]);
let (label, der) = pkcs8::der::pem::decode(self.private_key_pem.as_bytes(), &mut decoded)
.map_err(|_| KeyError::NotAKey)?;
match label {
"ENCRYPTED PRIVATE KEY" => {
let password = password.ok_or(KeyError::PasswordNeeded)?;
let info = Envelope::from_der(der).map_err(|_| KeyError::NotAKey)?;
let params = info
.parameters()
.map_err(|_| KeyError::UnsupportedProtection)?;
decrypt(¶ms, password, info.ciphertext)
}
"PRIVATE KEY" => Ok(Zeroizing::new(der.to_vec())),
_ => Err(KeyError::NotAKey),
}
}
}
impl TqslKey {
pub(crate) fn from_pkcs8_der(der: &[u8], spki: &[u8]) -> Result<Self, KeyError> {
crate::memory::scrub_after(|| Self::from_pkcs8_der_inner(der, spki))
}
#[inline(never)]
pub(crate) fn from_pkcs8_der_inner(der: &[u8], spki: &[u8]) -> Result<Self, KeyError> {
let key = RsaKey::from_pkcs8_der(der).map_err(|_| KeyError::NotAKey)?;
if !key.is_lotw_shaped() {
return Err(KeyError::NotLotwKey);
}
if !key.matches_public_key(spki) {
return Err(KeyError::PublicKeyMismatch);
}
Ok(TqslKey { key })
}
}
impl TqslKey {
pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>, KeyError> {
crate::memory::scrub_after(|| self.key.sign_sha1(data).map_err(|_| KeyError::Signing))
}
pub fn matches_public_key(&self, spki_der: &[u8]) -> bool {
self.key.matches_public_key(spki_der)
}
}
pub fn tqsl_base64(signature: &[u8]) -> String {
let encoded = Base64::encode_string(signature);
let mut out = String::with_capacity(encoded.len() + encoded.len() / 64 + 1);
for line in encoded.as_bytes().chunks(64) {
out.push_str(std::str::from_utf8(line).unwrap_or_default());
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use pkcs8::{
EncryptedPrivateKeyInfo,
pkcs5::{EncryptionScheme, pbes2},
};
use rsa::PrivateKey;
use rsa::pkcs8::DecodePrivateKey;
#[test]
fn base64_is_wrapped_as_tqsllib_wraps_it() {
let wrapped = tqsl_base64(&[0u8; 128]);
let lines: Vec<usize> = wrapped.split('\n').map(str::len).collect();
assert_eq!(lines, vec![64, 64, 44, 0]);
assert_eq!(wrapped.len(), 175);
assert_eq!(tqsl_base64(&[]), "");
}
#[test]
fn a_key_entry_never_prints_its_key() {
let entry = KeyEntry {
request_dxcc: None,
callsign: "QG0TEST".into(),
public_key_pem: "public".into(),
private_key_pem: Zeroizing::new("-----BEGIN PRIVATE KEY-----secret".into()),
};
let shown = format!("{entry:?}");
assert!(
!shown.contains("secret") && !shown.contains("public"),
"{shown}"
);
}
#[test]
fn a_record_without_its_key_fields_is_refused() {
let file = b"<CALLSIGN:7>QG0TEST\n<PUBLIC_KEY:1>x\n<eor>\n";
assert_eq!(
KeyEntry::read_key_file(file).map(|v| v.len()),
Err(KeyError::MissingField("PRIVATE_KEY"))
);
}
#[test]
fn the_private_key_text_is_wiped_when_dropped() {
fn wiped<T: zeroize::ZeroizeOnDrop>(_: &T) {}
let entry = KeyEntry {
request_dxcc: None,
callsign: "QG0TEST".into(),
public_key_pem: String::new(),
private_key_pem: Zeroizing::new(String::new()),
};
wiped(&entry.private_key_pem);
}
fn protected(scheme: EncryptionScheme<'_>) -> KeyEntry {
use pkcs8::der::{Encode, pem::LineEnding};
let info = EncryptedPrivateKeyInfo {
encryption_algorithm: scheme,
encrypted_data: &[0u8; 32],
};
let der = info.to_der().expect("DER");
let pem = pkcs8::der::pem::encode_string("ENCRYPTED PRIVATE KEY", LineEnding::LF, &der)
.expect("PEM");
KeyEntry {
request_dxcc: None,
callsign: "QG0TEST".into(),
public_key_pem: String::new(),
private_key_pem: Zeroizing::new(pem),
}
}
#[test]
fn a_crafted_encryption_is_refused_before_any_work() {
let (salt, iv) = ([7u8; 16], [9u8; 16]);
let huge = pkcs8::pkcs5::scrypt::Params::new(30, 8, 1, 32).expect("params");
let scrypt = pbes2::Parameters::scrypt_aes256cbc(huge, &salt, &iv).expect("scrypt");
assert_eq!(
protected(EncryptionScheme::Pbes2(scrypt))
.unlock(Some(b"x"))
.err(),
Some(KeyError::UnsupportedProtection)
);
let slow = pbes2::Parameters::pbkdf2_sha256_aes256cbc(
pbes2::Pbkdf2Params::MAX_ITERATION_COUNT,
&salt,
&iv,
)
.expect("pbkdf2");
assert_eq!(
protected(EncryptionScheme::Pbes2(slow))
.unlock(Some(b"x"))
.err(),
Some(KeyError::UnsupportedProtection)
);
let ok = pbes2::Parameters::pbkdf2_sha256_aes256cbc(MAX_PBKDF2_ITERATIONS, &salt, &iv)
.expect("pbkdf2");
use pkcs8::der::Encode;
let encoded = ok.to_der().expect("parameters");
assert!(
Parameters::from_der(&encoded)
.expect("parameters")
.accepted()
);
}
fn test_public_key() -> String {
use rsa::pkcs8::EncodePublicKey;
PrivateKey::from_pkcs8_pem(include_str!("../../../tests/corpus/pki/user-1024.key.pem"))
.expect("key")
.to_public_key()
.to_public_key_pem(pkcs8::LineEnding::LF)
.expect("PEM")
}
fn entry(pem: &str) -> KeyEntry {
KeyEntry {
request_dxcc: None,
callsign: "QG0TEST".into(),
public_key_pem: test_public_key(),
private_key_pem: Zeroizing::new(pem.into()),
}
}
fn protected_salt_matches_previous_path(salt: &[u8]) {
use pkcs8::der::{Encode, pem::LineEnding};
use qso_graph_trust_awslc::{
ImportCipher, ImportDigest, import_cbc_decrypt, import_pbkdf2,
};
let password = b"test-password";
let plain = entry(include_str!("../../../tests/corpus/pki/user-1024.key.pem"));
let plaintext = plain.pkcs8_der(None).expect("plain key");
let params = pbes2::Parameters {
kdf: pbes2::Kdf::Pbkdf2(pbes2::Pbkdf2Params {
salt,
iteration_count: 2048,
key_length: None,
prf: pbes2::Pbkdf2Prf::HmacWithSha256,
}),
encryption: pbes2::EncryptionScheme::Aes256Cbc { iv: &[9; 16] },
};
let ciphertext = params
.encrypt(password, &plaintext)
.expect("encrypt fixture");
let encoded = EncryptedPrivateKeyInfo {
encryption_algorithm: EncryptionScheme::Pbes2(params),
encrypted_data: &ciphertext,
}
.to_der()
.expect("envelope");
let previous = EncryptedPrivateKeyInfo::from_der(&encoded).expect("previous parser");
let EncryptionScheme::Pbes2(params) = previous.encryption_algorithm else {
panic!("PBES2 fixture");
};
let pbes2::Kdf::Pbkdf2(kdf) = params.kdf else {
panic!("PBKDF2 fixture");
};
assert_eq!(kdf.salt, salt);
assert_eq!(kdf.key_length, None);
assert_eq!(kdf.prf, pbes2::Pbkdf2Prf::HmacWithSha256);
let pbes2::EncryptionScheme::Aes256Cbc { iv } = params.encryption else {
panic!("AES256 fixture");
};
let derived = import_pbkdf2(
ImportDigest::Sha256,
password,
kdf.salt,
std::num::NonZeroU32::new(kdf.iteration_count).expect("iterations"),
32,
);
let previous_der =
import_cbc_decrypt(ImportCipher::Aes256, &derived, iv, previous.encrypted_data)
.expect("previous decryption");
assert_eq!(&*previous_der, &*plaintext);
let pem = pkcs8::der::pem::encode_string("ENCRYPTED PRIVATE KEY", LineEnding::LF, &encoded)
.expect("PEM");
let file = Zeroizing::new(format!(
"<CALLSIGN:7>QG0TEST\n<PUBLIC_KEY:{}>{}\n<PRIVATE_KEY:{}>{pem}\n<eor>\n",
plain.public_key_pem.len(),
plain.public_key_pem,
pem.len(),
));
let protected = KeyEntry::read_key_file(file.as_bytes())
.expect("bounded key file")
.remove(0);
protected.check().expect("accepted protection");
assert_eq!(
&*protected.pkcs8_der(Some(password)).expect("decrypted key"),
&*previous_der
);
let previous_key =
TqslKey::from_pkcs8_der(&previous_der, &plain.public_key_der().expect("SPKI"))
.expect("previous key");
let data = b"4EM177SEDGWICKKS20MKC4QPQ14.074000FT82026-11-0118:30:00Z";
assert_eq!(
protected
.unlock(Some(password))
.expect("unlocks")
.sign(data)
.expect("signs"),
previous_key.sign(data).expect("previous signature")
);
}
#[test]
fn a_protected_key_with_a_65_byte_salt_matches_previous_path() {
protected_salt_matches_previous_path(&[7; 65]);
}
#[test]
fn a_protected_key_with_an_empty_salt_matches_previous_path() {
protected_salt_matches_previous_path(&[]);
}
#[test]
fn a_key_protected_with_pbkdf2_hmac_sha1_unlocks_and_signs_the_same() {
let plain = entry(include_str!("../../../tests/corpus/pki/user-1024.key.pem"));
let sha1 = entry(include_str!(
"../../../tests/corpus/pki/user-1024-pbkdf2-sha1.key.pem"
));
assert!(sha1.is_encrypted());
let data = b"4EM177SEDGWICKKS20MKC4QPQ14.074000FT82026-11-0118:30:00Z";
let ours = sha1
.unlock(Some(b"qgt-key"))
.expect("unlocks")
.sign(data)
.expect("signs");
assert_eq!(
ours,
plain
.unlock(None)
.expect("unlocks")
.sign(data)
.expect("signs")
);
}
#[test]
fn a_damaged_protected_key_is_refused() {
let file = include_bytes!("../../../tests/corpus/tqsl/1024-password/tqsl/keys/QG0TEST");
let pem = KeyEntry::read_key_file(file)
.expect("key file")
.remove(0)
.private_key_pem;
let (_, der) = pkcs8::der::pem::decode_vec(pem.as_bytes()).expect("PEM");
let mut der = der;
let last = der.len() - 9;
der[last] ^= 0x80;
let damaged = pkcs8::der::pem::encode_string(
"ENCRYPTED PRIVATE KEY",
pkcs8::der::pem::LineEnding::LF,
&der,
)
.expect("PEM");
assert_eq!(
entry(&damaged).unlock(Some(b"qgt-key")).err(),
Some(KeyError::WrongPassword)
);
}
#[test]
fn a_key_that_is_not_lotw_shaped_is_refused_before_it_can_sign() {
use rsa::pkcs8::EncodePrivateKey;
let q = [
"39241060f9dd871a47f383b6d93528e91ef2f26a23a18ac4c84be4a7de864fd4",
"38956267fbd8af381f09420f73705ac1e2132bb74997b110e63688ec7fe57a23",
"4ad07b484127addfa820c10871a478b17cc45b778ff3dd2570e7a69fa3fbb215",
"e45b9f2cce85625caafe930ff7e6992e060b76e032f908075ba56c5c86e35fb7",
]
.concat();
let q = num_bigint_dig::BigUint::parse_bytes(q.as_bytes(), 16).expect("q");
let p = num_bigint_dig::BigUint::from(3u32);
let d = inverse_of_e(&(&q - num_bigint_dig::BigUint::from(1u32)));
let refused = crafted(&p, &q, &d).unlock(None).err();
assert!(matches!(
refused,
Some(KeyError::NotAKey | KeyError::NotLotwKey)
));
let other = PrivateKey::new(&mut getrandom::SysRng, 1536).expect("key");
let pem = other.to_pkcs8_pem(pkcs8::LineEnding::LF).expect("PEM");
assert_eq!(entry(&pem).unlock(None).err(), Some(KeyError::NotLotwKey));
}
#[test]
fn a_key_file_too_large_to_be_one_is_refused_unread() {
assert_eq!(
KeyEntry::read_key_file(&vec![b' '; MAX_KEY_FILE_LEN + 1]).err(),
Some(KeyError::FileTooLarge)
);
}
#[test]
fn a_public_key_that_is_not_the_private_keys_is_refused() {
let file = |case: &str| -> Vec<u8> {
std::fs::read(format!(
"{}/../../tests/corpus/tqsl/{case}/tqsl/keys/QG0TEST",
env!("CARGO_MANIFEST_DIR")
))
.expect("key file")
};
let mine = KeyEntry::read_key_file(&file("1024"))
.expect("key file")
.remove(0);
let other = KeyEntry::read_key_file(&file("1024-password"))
.expect("key file")
.remove(0);
let crossed = KeyEntry {
public_key_pem: other.public_key_pem.clone(),
..mine.clone()
};
assert!(mine.unlock(None).is_ok());
assert_eq!(
crossed.unlock(None).err(),
Some(KeyError::PublicKeyMismatch)
);
}
#[test]
fn every_buffer_in_decryption_is_wiped() {
fn wiped<T: zeroize::ZeroizeOnDrop>() {}
wiped::<Zeroizing<Vec<u8>>>();
}
fn crafted(
p: &num_bigint_dig::BigUint,
q: &num_bigint_dig::BigUint,
d: &num_bigint_dig::BigUint,
) -> KeyEntry {
use num_bigint_dig::ModInverse;
use pkcs8::der::Encode;
use rsa::pkcs8::EncodePublicKey;
fn uint(b: &[u8]) -> pkcs8::der::asn1::UintRef<'_> {
pkcs8::der::asn1::UintRef::new(b).expect("uint")
}
let e = num_bigint_dig::BigUint::from(65_537u32);
let n = p * q;
let (nb, eb, db, pb, qb) = (
n.to_bytes_be(),
e.to_bytes_be(),
d.to_bytes_be(),
p.to_bytes_be(),
q.to_bytes_be(),
);
let one = num_bigint_dig::BigUint::from(1u32);
let dp = (d % (p - &one)).to_bytes_be();
let dq = (d % (q - &one)).to_bytes_be();
let coefficient = q
.clone()
.mod_inverse(p)
.and_then(|n| n.to_biguint())
.unwrap_or(one)
.to_bytes_be();
let pkcs1_key = pkcs1_test::RsaPrivateKey {
modulus: uint(&nb),
public_exponent: uint(&eb),
private_exponent: uint(&db),
prime1: uint(&pb),
prime2: uint(&qb),
exponent1: uint(&dp),
exponent2: uint(&dq),
coefficient: uint(&coefficient),
other_prime_infos: None,
}
.to_der()
.expect("PKCS#1");
let info = pkcs8::PrivateKeyInfo::new(pkcs1_test::ALGORITHM_ID, &pkcs1_key);
let pem = pkcs8::der::pem::encode_string(
"PRIVATE KEY",
pkcs8::LineEnding::LF,
&info.to_der().expect("PKCS#8"),
)
.expect("PEM");
let public = rsa::PublicKey::new(
rsa::BoxedUint::from_be_slice_vartime(&n.to_bytes_be()),
rsa::BoxedUint::from_be_slice_vartime(&e.to_bytes_be()),
)
.expect("public")
.to_public_key_pem(pkcs8::LineEnding::LF)
.expect("PEM");
KeyEntry {
request_dxcc: None,
callsign: "QG0TEST".into(),
public_key_pem: public,
private_key_pem: Zeroizing::new(pem),
}
}
fn inverse_of_e(m: &num_bigint_dig::BigUint) -> num_bigint_dig::BigUint {
use num_bigint_dig::ModInverse;
num_bigint_dig::BigUint::from(65_537u32)
.mod_inverse(m)
.and_then(|d| d.to_biguint())
.expect("d")
}
#[test]
fn a_key_with_composite_factors_does_not_unlock() {
let three_to =
|k: u32| (0..k).fold(num_bigint_dig::BigUint::from(1u32), |acc, _| acc * 3u32);
let one = num_bigint_dig::BigUint::from(1u32);
let (p, q) = (
three_to(323),
num_bigint_dig::BigUint::from(7u32) * three_to(321),
);
assert_eq!((p.bits(), q.bits(), (&p * &q).bits()), (512, 512, 1024));
let gcd = |mut a: num_bigint_dig::BigUint, mut b: num_bigint_dig::BigUint| {
while b != num_bigint_dig::BigUint::from(0u32) {
let r = &a % &b;
a = b;
b = r;
}
a
};
let lcm = |a: num_bigint_dig::BigUint, b: num_bigint_dig::BigUint| {
&a * &b / gcd(a.clone(), b.clone())
};
let lambda = lcm(
lcm(&p - &one, &q - &one),
num_bigint_dig::BigUint::from(2u32) * three_to(643),
);
let d = inverse_of_e(&lambda);
let entry = crafted(&p, &q, &d);
assert!(pkcs8::SecretDocument::from_pem(&entry.private_key_pem).is_ok());
let refused = entry.unlock(None).err();
assert!(
matches!(refused, Some(KeyError::NotAKey | KeyError::NotLotwKey)),
"{refused:?}"
);
}
#[test]
fn a_key_with_equal_primes_does_not_unlock() {
use rsa::traits::PrivateKeyParts;
let real =
PrivateKey::from_pkcs8_pem(include_str!("../../../tests/corpus/pki/user-1024.key.pem"))
.expect("key");
let p = num_bigint_dig::BigUint::from_bytes_be(&real.primes()[0].to_be_bytes());
let d = inverse_of_e(&(&p * (&p - num_bigint_dig::BigUint::from(1u32))));
let refused = crafted(&p, &p, &d).unlock(None).err();
assert!(
matches!(refused, Some(KeyError::NotAKey | KeyError::NotLotwKey)),
"{refused:?}"
);
}
#[test]
fn a_key_field_given_twice_is_refused() {
let file =
b"<CALLSIGN:7>QG0TEST\n<PUBLIC_KEY:1>a\n<PRIVATE_KEY:1>b\n<private_key:1>c\n<eor>\n";
assert_eq!(
KeyEntry::read_key_file(file).err(),
Some(KeyError::RepeatedField("PRIVATE_KEY"))
);
}
}