use std::io::BufRead;
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use digest::generic_array::GenericArray;
use md5::Md5;
use rand::{CryptoRng, Rng};
use rsa::traits::PublicKeyParts;
use sha1_checked::Sha1;
use sha2::Sha256;
use crate::pgp::{
crypto::{
self,
hash::{HashAlgorithm, KnownDigest},
public_key::PublicKeyAlgorithm,
},
errors::{bail, ensure, ensure_eq, unimplemented_err, unsupported_err, Result},
packet::{
KeyFlags, PacketHeader, Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData,
},
ser::Serialize,
types::{
EcdhPublicParams, EddsaLegacyPublicParams, EncryptionKey, EskType, Fingerprint, Imprint,
KeyDetails, KeyId, KeyVersion, Mpi, Password, PkeskBytes, PublicParams, SignatureBytes,
SigningKey, Tag, Timestamp, VerifyingKey,
},
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PublicKey {
packet_header: PacketHeader,
inner: PubKeyInner,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PublicSubkey {
packet_header: PacketHeader,
inner: PubKeyInner,
}
impl PublicKey {
pub fn from_inner(inner: PubKeyInner) -> Result<Self> {
let len = inner.write_len();
let packet_header = PacketHeader::new_fixed(Tag::PublicKey, len.try_into()?);
Ok(Self {
packet_header,
inner,
})
}
pub(super) fn from_inner_with_header(packet_header: PacketHeader, inner: PubKeyInner) -> Self {
Self {
packet_header,
inner,
}
}
pub fn new_with_header(
packet_header: PacketHeader,
version: KeyVersion,
algorithm: PublicKeyAlgorithm,
created_at: Timestamp,
expiration: Option<u16>,
public_params: PublicParams,
) -> Result<Self> {
let inner = PubKeyInner::new(version, algorithm, created_at, expiration, public_params)?;
if let Some(len) = packet_header.packet_length().maybe_len() {
ensure_eq!(
inner.write_len(),
len as usize,
"PublicKey: inconsisteng packet length"
);
}
ensure_eq!(packet_header.tag(), Tag::PublicKey, "invalid tag");
Ok(Self {
packet_header,
inner,
})
}
pub fn try_from_reader<B: BufRead>(packet_header: PacketHeader, input: B) -> Result<Self> {
ensure_eq!(packet_header.tag(), Tag::PublicKey, "invalid tag");
let inner = PubKeyInner::try_from_reader(input)?;
Ok(Self {
packet_header,
inner,
})
}
}
impl EncryptionKey for PublicKey {
fn encrypt<R: rand::CryptoRng + rand::Rng>(
&self,
rng: R,
plain: &[u8],
typ: EskType,
) -> Result<PkeskBytes> {
encrypt(&self.inner, rng, plain, typ)
}
}
impl PublicSubkey {
pub fn from_inner(inner: PubKeyInner) -> Result<Self> {
let len = inner.write_len();
let packet_header = PacketHeader::new_fixed(Tag::PublicSubkey, len.try_into()?);
Ok(Self {
packet_header,
inner,
})
}
pub fn from_inner_with_header(packet_header: PacketHeader, inner: PubKeyInner) -> Result<Self> {
Ok(Self {
packet_header,
inner,
})
}
pub fn new_with_header(
packet_header: PacketHeader,
version: KeyVersion,
algorithm: PublicKeyAlgorithm,
created_at: Timestamp,
expiration: Option<u16>,
public_params: PublicParams,
) -> Result<Self> {
let inner = PubKeyInner::new(version, algorithm, created_at, expiration, public_params)?;
if let Some(len) = packet_header.packet_length().maybe_len() {
ensure_eq!(
inner.write_len(),
len as usize,
"PublicSubkey: inconsistent packet length"
);
}
ensure_eq!(packet_header.tag(), Tag::PublicSubkey, "invalid tag");
Ok(Self {
packet_header,
inner,
})
}
pub fn try_from_reader<B: BufRead>(packet_header: PacketHeader, input: B) -> Result<Self> {
ensure_eq!(packet_header.tag(), Tag::PublicSubkey, "invalid tag");
let inner = PubKeyInner::try_from_reader(input)?;
Ok(Self {
packet_header,
inner,
})
}
pub fn sign<R: CryptoRng + Rng, S, K>(
&self,
rng: R,
primary_sec_key: &S,
primary_pub_key: &K,
key_pw: &Password,
keyflags: KeyFlags,
embedded: Option<Signature>,
) -> Result<Signature>
where
S: SigningKey,
K: KeyDetails + Serialize,
{
let mut config =
SignatureConfig::from_key(rng, primary_sec_key, SignatureType::SubkeyBinding)?;
config.hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))?,
Subpacket::regular(SubpacketData::KeyFlags(keyflags))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(
primary_sec_key.fingerprint(),
))?,
];
if let Some(embedded) = embedded {
config
.hashed_subpackets
.push(Subpacket::regular(SubpacketData::EmbeddedSignature(
Box::new(embedded),
))?)
}
if primary_sec_key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
primary_sec_key.legacy_key_id(),
))?];
}
config.sign_subkey_binding(primary_sec_key, primary_pub_key, key_pw, &self)
}
}
impl EncryptionKey for PublicSubkey {
fn encrypt<R: rand::CryptoRng + rand::Rng>(
&self,
rng: R,
plain: &[u8],
typ: EskType,
) -> Result<PkeskBytes> {
encrypt(&self.inner, rng, plain, typ)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[doc(hidden)] pub struct PubKeyInner {
version: KeyVersion,
algorithm: PublicKeyAlgorithm,
created_at: Timestamp,
expiration: Option<u16>,
public_params: PublicParams,
}
impl PubKeyInner {
fn try_from_reader<B: BufRead>(input: B) -> Result<Self> {
let details = crate::pgp::packet::public_key_parser::parse(input)?;
let (version, algorithm, created_at, expiration, public_params) = details;
Self::new(version, algorithm, created_at, expiration, public_params)
}
pub fn new(
version: KeyVersion,
algorithm: PublicKeyAlgorithm,
created_at: Timestamp,
expiration: Option<u16>,
public_params: PublicParams,
) -> Result<Self> {
if (version == KeyVersion::V2 || version == KeyVersion::V3)
&& !(algorithm == PublicKeyAlgorithm::RSA
|| algorithm == PublicKeyAlgorithm::RSAEncrypt
|| algorithm == PublicKeyAlgorithm::RSASign)
{
unsupported_err!(
"Invalid algorithm {:?} for key version: {:?}",
algorithm,
version,
);
}
if version != KeyVersion::V4 {
if matches!(
public_params,
PublicParams::ECDH(EcdhPublicParams::Curve25519 { .. })
) {
bail!(
"ECDH over Curve25519 is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::EdDSALegacy { .. }) {
bail!(
"EdDSALegacy is illegal for key version {}",
u8::from(version)
);
}
}
#[cfg(feature = "draft-pqc")]
if version != KeyVersion::V4
&& version != KeyVersion::V6
&& matches!(public_params, PublicParams::MlKem768X25519(_))
{
bail!(
"ML-KEM-768+X25519 is illegal for key version {}",
u8::from(version)
);
}
#[cfg(feature = "draft-pqc")]
if version != KeyVersion::V6 {
if matches!(public_params, PublicParams::MlKem1024X448(_)) {
bail!(
"ML-KEM-1024+X448 is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::MlDsa65Ed25519(_)) {
bail!(
"ML-DSA-65+Ed25519 is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::MlDsa87Ed448(_)) {
bail!(
"ML-DSA-87+Ed448 is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::SlhDsaShake128s(_)) {
bail!(
"SLH-DSA-SHAKE-128s is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::SlhDsaShake128f(_)) {
bail!(
"SLH-DSA-SHAKE-128f is illegal for key version {}",
u8::from(version)
);
}
if matches!(public_params, PublicParams::SlhDsaShake256s(_)) {
bail!(
"SLH-DSA-SHAKE-256s is illegal for key version {}",
u8::from(version)
);
}
}
Ok(Self {
version,
algorithm,
created_at,
expiration,
public_params,
})
}
fn to_writer_v2_v3<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
use crate::pgp::ser::Serialize;
self.created_at.to_writer(writer)?;
writer.write_u16::<BigEndian>(
self.expiration
.expect("old key versions have an expiration"),
)?;
writer.write_u8(self.algorithm.into())?;
self.public_params.to_writer(writer)?;
Ok(())
}
fn writer_len_v2_v3(&self) -> usize {
let mut sum = 4 + 2 + 1;
sum += self.public_params.write_len();
sum
}
fn to_writer_v4_v6<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
use crate::pgp::ser::Serialize;
self.created_at.to_writer(writer)?;
writer.write_u8(self.algorithm.into())?;
if self.version == KeyVersion::V6 {
writer.write_u32::<BigEndian>(self.public_params.write_len().try_into()?)?;
}
self.public_params.to_writer(writer)?;
Ok(())
}
fn writer_len_v4_v6(&self) -> usize {
let mut sum = 4 + 1;
if self.version == KeyVersion::V6 {
sum += 4;
}
sum += self.public_params.write_len();
sum
}
#[allow(dead_code)]
fn sign<R: CryptoRng + Rng, S>(
&self,
mut rng: R,
key: &S,
key_pw: &Password,
sig_type: SignatureType,
) -> Result<Signature>
where
S: SigningKey + Serialize,
{
let mut config = SignatureConfig::from_key(&mut rng, key, sig_type)?;
config.hashed_subpackets = vec![
Subpacket::regular(SubpacketData::SignatureCreationTime(Timestamp::now()))?,
Subpacket::regular(SubpacketData::IssuerFingerprint(key.fingerprint()))?,
];
if key.version() <= KeyVersion::V4 {
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::IssuerKeyId(
key.legacy_key_id(),
))?];
}
config.sign_key(key, key_pw, &self)
}
}
pub(crate) fn encrypt<R: rand::CryptoRng + rand::Rng, K: KeyDetails>(
key: &K,
mut rng: R,
plain: &[u8],
typ: EskType,
) -> Result<PkeskBytes> {
match key.public_params() {
PublicParams::RSA(ref params) => crypto::rsa::encrypt(rng, ¶ms.key, plain),
PublicParams::EdDSALegacy { .. } => bail!("EdDSALegacy is only used for signing"),
PublicParams::Ed25519 { .. } => bail!("Ed25519 is only used for signing"),
PublicParams::Ed448 { .. } => bail!("Ed448 is only used for signing"),
PublicParams::ECDSA { .. } => bail!("ECDSA is only used for signing"),
PublicParams::ECDH(ref params) => match params {
EcdhPublicParams::Unsupported { ref curve, .. } => {
unsupported_err!("ECDH over curve {:?} is unsupported", curve)
}
_ => {
if key.version() == KeyVersion::V6 {
let curve = params.curve();
match params {
EcdhPublicParams::Curve25519 { hash, alg_sym, .. }
| EcdhPublicParams::P256 { hash, alg_sym, .. }
| EcdhPublicParams::P521 { hash, alg_sym, .. }
| EcdhPublicParams::P384 { hash, alg_sym, .. } => {
if curve.hash_algo()? != *hash || curve.sym_algo()? != *alg_sym {
bail!(
"Unsupported KDF/KEK parameters for {:?} and KeyVersion::V6: {:?}, {:?}",
curve,
hash,
alg_sym
);
}
}
_ => unsupported_err!("{:?} for ECDH", params),
}
}
crypto::ecdh::encrypt(rng, params, key.fingerprint().as_bytes(), plain)
}
},
PublicParams::X25519(ref params) => {
let (sym_alg, plain) = match typ {
EskType::V6 => (None, plain),
EskType::V3_4 => {
ensure!(!plain.is_empty(), "plain may not be empty");
(
Some(plain[0].into()), &plain[1..], )
}
};
let (ephemeral, session_key) = crypto::x25519::encrypt(&mut rng, ¶ms.key, plain)?;
Ok(PkeskBytes::X25519 {
ephemeral,
session_key: session_key.into(),
sym_alg,
})
}
PublicParams::X448(ref params) => {
let (sym_alg, plain) = match typ {
EskType::V6 => (None, plain),
EskType::V3_4 => {
ensure!(!plain.is_empty(), "plain may not be empty");
(
Some(plain[0].into()), &plain[1..], )
}
};
let (ephemeral, session_key) = crypto::x448::encrypt(&mut rng, params, plain)?;
Ok(PkeskBytes::X448 {
ephemeral,
session_key: session_key.into(),
sym_alg,
})
}
PublicParams::Elgamal { .. } => unimplemented_err!("encryption with Elgamal"),
PublicParams::DSA { .. } => bail!("DSA is only used for signing"),
#[cfg(feature = "draft-pqc")]
PublicParams::MlKem768X25519(ref params) => {
let (sym_alg, plain) = match typ {
EskType::V6 => (None, plain),
EskType::V3_4 => {
ensure!(!plain.is_empty(), "plain may not be empty");
(
Some(plain[0].into()), &plain[1..], )
}
};
let (ecdh_ciphertext, ml_kem_ciphertext, session_key) =
crypto::ml_kem768_x25519::encrypt(
&mut rng,
¶ms.x25519_key,
¶ms.ml_kem_key,
plain,
)?;
Ok(PkeskBytes::MlKem768X25519 {
ecdh_ciphertext,
ml_kem_ciphertext,
session_key: session_key.into(),
sym_alg,
})
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlKem1024X448(ref params) => {
let (sym_alg, plain) = match typ {
EskType::V6 => (None, plain),
EskType::V3_4 => {
ensure!(!plain.is_empty(), "plain may not be empty");
(
Some(plain[0].into()), &plain[1..], )
}
};
let (ecdh_ciphertext, ml_kem_ciphertext, session_key) =
crypto::ml_kem1024_x448::encrypt(
&mut rng,
¶ms.x448_key,
¶ms.ml_kem_key,
plain,
)?;
Ok(PkeskBytes::MlKem1024X448 {
ecdh_ciphertext,
ml_kem_ciphertext,
session_key: session_key.into(),
sym_alg,
})
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlDsa65Ed25519(_) => {
bail!("ML DSA 65 ED2519 is only used for signing")
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlDsa87Ed448(_) => {
bail!("ML DSA 87 ED448 is only used for signing")
}
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake128s(_) => {
bail!("SLH DSA Shake 128s is only used for signing")
}
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake128f(_) => {
bail!("SLH DSA Shake 128f is only used for signing")
}
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake256s(_) => {
bail!("SLH DSA Shake 256s is only used for signing")
}
PublicParams::Unknown { .. } => bail!("Unknown algorithm"),
}
}
impl crate::pgp::ser::Serialize for PublicKey {
fn to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
self.inner.to_writer(writer)
}
fn write_len(&self) -> usize {
self.inner.write_len()
}
}
impl crate::pgp::ser::Serialize for PublicSubkey {
fn to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
self.inner.to_writer(writer)
}
fn write_len(&self) -> usize {
self.inner.write_len()
}
}
impl crate::pgp::ser::Serialize for PubKeyInner {
fn to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u8(self.version.into())?;
match self.version {
KeyVersion::V2 | KeyVersion::V3 => self.to_writer_v2_v3(writer),
KeyVersion::V4 | KeyVersion::V6 => self.to_writer_v4_v6(writer),
KeyVersion::V5 => unimplemented_err!("V5 keys"),
KeyVersion::Other(v) => {
unimplemented_err!("Unsupported key version {}", v)
}
}
}
fn write_len(&self) -> usize {
let mut sum = 1;
sum += match self.version {
KeyVersion::V2 | KeyVersion::V3 => self.writer_len_v2_v3(),
KeyVersion::V4 | KeyVersion::V6 => self.writer_len_v4_v6(),
KeyVersion::V5 => panic!("V5 keys"),
KeyVersion::Other(v) => {
panic!("Unsupported key version {v}")
}
};
sum
}
}
impl crate::pgp::packet::PacketTrait for PublicKey {
fn packet_header(&self) -> &crate::pgp::packet::PacketHeader {
&self.packet_header
}
}
impl crate::pgp::packet::PacketTrait for PublicSubkey {
fn packet_header(&self) -> &crate::pgp::packet::PacketHeader {
&self.packet_header
}
}
impl PubKeyInner {
fn imprint<D: KnownDigest>(&self) -> Result<GenericArray<u8, D::OutputSize>> {
let mut hasher = D::new();
use crate::pgp::ser::Serialize;
match self.version {
KeyVersion::V2 | KeyVersion::V3 => {
let mut v: Vec<u8> = Vec::new();
self.public_params.to_writer(&mut v)?;
hasher.update(&v);
Ok(hasher.finalize())
}
KeyVersion::V4 => {
hasher.update([0x99]);
let mut packet = vec![4, 0, 0, 0, 0];
BigEndian::write_u32(&mut packet[1..5], self.created_at.as_secs());
packet.push(self.algorithm.into());
self.public_params.to_writer(&mut packet)?;
hasher.update((packet.len() as u16).to_be_bytes());
hasher.update(&packet);
Ok(hasher.finalize())
}
KeyVersion::V5 => unsupported_err!("Imprint for V5 keys"),
KeyVersion::V6 => {
hasher.update([0x9B]);
let len = self.public_params.write_len() as u32;
let total_len: u32 = 1 + 4 + 1 + 4 + len;
hasher.update(total_len.to_be_bytes());
hasher.update([0x06]);
hasher.update(self.created_at.as_secs().to_be_bytes());
hasher.update([self.algorithm.into()]);
hasher.update(len.to_be_bytes());
let mut pp: Vec<u8> = vec![];
self.public_params.to_writer(&mut pp)?;
hasher.update(&pp);
Ok(hasher.finalize())
}
KeyVersion::Other(v) => unsupported_err!("Imprint for {} keys", v),
}
}
}
impl KeyDetails for PubKeyInner {
fn version(&self) -> KeyVersion {
self.version
}
fn fingerprint(&self) -> Fingerprint {
match self.version {
KeyVersion::V2 => Fingerprint::V2(
self.imprint::<Md5>()
.expect("failure is not an option")
.into(),
),
KeyVersion::V3 => Fingerprint::V3(
self.imprint::<Md5>()
.expect("failure is not an option")
.into(),
),
KeyVersion::V4 => Fingerprint::V4(
self.imprint::<Sha1>()
.expect("failure is not an option")
.into(),
),
KeyVersion::V5 => unimplemented!("V5 keys"),
KeyVersion::V6 => Fingerprint::V6(
self.imprint::<Sha256>()
.expect("failure is not an option")
.into(),
),
KeyVersion::Other(v) => unimplemented!("Unsupported key version {}", v),
}
}
fn legacy_key_id(&self) -> KeyId {
match self.version {
KeyVersion::V2 | KeyVersion::V3 => match &self.public_params {
PublicParams::RSA(params) => {
let n: Mpi = params.key.n().into();
let offset = n.len() - 8;
let raw: [u8; 8] = n.as_ref()[offset..].try_into().expect("fixed size");
raw.into()
}
_ => panic!("invalid key constructed: {:?}", &self.public_params),
},
KeyVersion::V4 => {
let f = self.fingerprint();
let offset = f.len() - 8;
let raw: [u8; 8] = f.as_bytes()[offset..].try_into().expect("fixed size");
raw.into()
}
KeyVersion::V5 => unimplemented!("V5 keys"),
KeyVersion::V6 => {
let f = self.fingerprint();
let raw: [u8; 8] = f.as_bytes()[0..8].try_into().expect("fixed size");
raw.into()
}
KeyVersion::Other(v) => unimplemented!("Unsupported key version {}", v),
}
}
fn algorithm(&self) -> PublicKeyAlgorithm {
self.algorithm
}
fn expiration(&self) -> Option<u16> {
self.expiration
}
fn created_at(&self) -> Timestamp {
self.created_at
}
fn public_params(&self) -> &PublicParams {
&self.public_params
}
}
impl VerifyingKey for PubKeyInner {
fn verify(&self, hash: HashAlgorithm, hashed: &[u8], sig: &SignatureBytes) -> Result<()> {
match self.public_params {
PublicParams::RSA(ref params) => {
let sig: &[Mpi] = sig.try_into()?;
ensure_eq!(sig.len(), 1, "invalid signature");
crypto::rsa::verify(¶ms.key, hash, hashed, sig[0].as_ref())
}
PublicParams::EdDSALegacy(ref params) => {
match params {
EddsaLegacyPublicParams::Ed25519 { ref key } => {
let sig: &[Mpi] = sig.try_into()?;
ensure_eq!(sig.len(), 2);
let r = sig[0].as_ref();
let s = sig[1].as_ref();
ensure!(r.len() < 33, "invalid R (len)");
ensure!(s.len() < 33, "invalid S (len)");
let mut sig_bytes = vec![0u8; 64];
sig_bytes[(32 - r.len())..32].copy_from_slice(r);
sig_bytes[32 + (32 - s.len())..].copy_from_slice(s);
crypto::ed25519::verify(key, hash, hashed, &sig_bytes)
}
EddsaLegacyPublicParams::Unsupported { curve, .. } => {
unsupported_err!("curve {:?} for EdDSA", curve.to_string());
}
}
}
PublicParams::Ed25519(ref params) => {
crypto::ed25519::verify(¶ms.key, hash, hashed, sig.try_into()?)
}
PublicParams::Ed448(ref params) => {
crypto::ed448::verify(¶ms.key, hash, hashed, sig.try_into()?)
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlDsa65Ed25519(ref params) => crypto::ml_dsa65_ed25519::verify(
¶ms.ed25519,
¶ms.ml_dsa,
hash,
hashed,
sig.try_into()?,
),
#[cfg(feature = "draft-pqc")]
PublicParams::MlDsa87Ed448(ref params) => crypto::ml_dsa87_ed448::verify(
¶ms.ed448,
¶ms.ml_dsa,
hash,
hashed,
sig.try_into()?,
),
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake128s(ref params) => {
crypto::slh_dsa_shake128s::verify(¶ms.key, hash, hashed, sig.try_into()?)
}
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake128f(ref params) => {
crypto::slh_dsa_shake128f::verify(¶ms.key, hash, hashed, sig.try_into()?)
}
#[cfg(feature = "draft-pqc")]
PublicParams::SlhDsaShake256s(ref params) => {
crypto::slh_dsa_shake256s::verify(¶ms.key, hash, hashed, sig.try_into()?)
}
PublicParams::X25519 { .. } => {
bail!("X25519 can not be used for verify operations");
}
PublicParams::X448 { .. } => {
bail!("X448 can not be used for verify operations");
}
PublicParams::ECDSA(ref params) => {
let sig: &[Mpi] = sig.try_into()?;
crypto::ecdsa::verify(params, hash, hashed, sig)
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlKem768X25519(_) => {
bail!("ML KEM 768 X25519 can not be used for verify operations");
}
#[cfg(feature = "draft-pqc")]
PublicParams::MlKem1024X448(_) => {
bail!("ML KEM 1024 X448 can not be used for verify operations");
}
PublicParams::ECDH(
ref params @ EcdhPublicParams::Curve25519 { .. }
| ref params @ EcdhPublicParams::P256 { .. }
| ref params @ EcdhPublicParams::P384 { .. }
| ref params @ EcdhPublicParams::P521 { .. },
) => {
bail!("ECDH ({:?}) can not be used for verify operations", params,);
}
PublicParams::ECDH(
EcdhPublicParams::Brainpool256 { .. }
| EcdhPublicParams::Brainpool384 { .. }
| EcdhPublicParams::Brainpool512 { .. },
) => {
bail!("ECDH (unsupported: brainpool) can not be used for verify operations");
}
PublicParams::ECDH(EcdhPublicParams::Unsupported { ref curve, .. }) => {
bail!(
"ECDH (unsupported: {:?}) can not be used for verify operations",
curve,
);
}
PublicParams::Elgamal { .. } => {
unimplemented_err!("verify Elgamal");
}
PublicParams::DSA(ref params) => {
let sig: &[Mpi] = sig.try_into()?;
ensure_eq!(sig.len(), 2, "invalid signature");
crypto::dsa::verify(params, hashed, sig[0].clone().into(), sig[1].clone().into())
}
PublicParams::Unknown { .. } => {
unimplemented_err!("PublicParams::Unknown can not be used for verify operations");
}
}
}
}
impl KeyDetails for PublicKey {
fn version(&self) -> KeyVersion {
self.inner.version()
}
fn fingerprint(&self) -> Fingerprint {
self.inner.fingerprint()
}
fn legacy_key_id(&self) -> KeyId {
self.inner.legacy_key_id()
}
fn algorithm(&self) -> PublicKeyAlgorithm {
self.inner.algorithm()
}
fn created_at(&self) -> Timestamp {
self.inner.created_at
}
fn expiration(&self) -> Option<u16> {
self.inner.expiration
}
fn public_params(&self) -> &PublicParams {
self.inner.public_params()
}
}
impl Imprint for PublicKey {
fn imprint<D: KnownDigest>(&self) -> Result<GenericArray<u8, D::OutputSize>> {
self.inner.imprint::<D>()
}
}
impl VerifyingKey for PublicKey {
fn verify(&self, hash: HashAlgorithm, hashed: &[u8], sig: &SignatureBytes) -> Result<()> {
self.inner.verify(hash, hashed, sig)
}
}
impl KeyDetails for PublicSubkey {
fn version(&self) -> KeyVersion {
self.inner.version()
}
fn fingerprint(&self) -> Fingerprint {
self.inner.fingerprint()
}
fn legacy_key_id(&self) -> KeyId {
self.inner.legacy_key_id()
}
fn algorithm(&self) -> PublicKeyAlgorithm {
self.inner.algorithm()
}
fn created_at(&self) -> Timestamp {
self.inner.created_at()
}
fn expiration(&self) -> Option<u16> {
self.inner.expiration()
}
fn public_params(&self) -> &PublicParams {
self.inner.public_params()
}
}
impl Imprint for PublicSubkey {
fn imprint<D: KnownDigest>(&self) -> Result<GenericArray<u8, D::OutputSize>> {
self.inner.imprint::<D>()
}
}
impl VerifyingKey for PublicSubkey {
fn verify(&self, hash: HashAlgorithm, hashed: &[u8], sig: &SignatureBytes) -> Result<()> {
VerifyingKey::verify(&self.inner, hash, hashed, sig)
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
use crate::pgp::packet::PacketTrait;
fn v3_alg() -> BoxedStrategy<PublicKeyAlgorithm> {
prop_oneof![Just(PublicKeyAlgorithm::RSA),].boxed()
}
fn v4_alg() -> BoxedStrategy<PublicKeyAlgorithm> {
prop_oneof![
Just(PublicKeyAlgorithm::RSA),
Just(PublicKeyAlgorithm::DSA),
Just(PublicKeyAlgorithm::ECDSA),
Just(PublicKeyAlgorithm::ECDH),
Just(PublicKeyAlgorithm::Elgamal),
Just(PublicKeyAlgorithm::EdDSALegacy),
Just(PublicKeyAlgorithm::Ed25519),
Just(PublicKeyAlgorithm::X25519),
]
.boxed()
}
fn v6_alg() -> BoxedStrategy<PublicKeyAlgorithm> {
prop_oneof![
Just(PublicKeyAlgorithm::RSA),
Just(PublicKeyAlgorithm::DSA),
Just(PublicKeyAlgorithm::ECDSA),
Just(PublicKeyAlgorithm::Elgamal),
Just(PublicKeyAlgorithm::Ed25519),
Just(PublicKeyAlgorithm::X25519),
]
.boxed()
}
impl Arbitrary for PubKeyInner {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<(KeyVersion, Timestamp, u16)>()
.prop_flat_map(|(version, created_at, expiration)| match version {
KeyVersion::V2 | KeyVersion::V3 => (
Just(version),
Just(created_at),
Just(Some(expiration)),
v3_alg(),
),
KeyVersion::V4 => (Just(version), Just(created_at), Just(None), v4_alg()),
KeyVersion::V5 | KeyVersion::V6 => {
(Just(version), Just(created_at), Just(None), v6_alg())
}
KeyVersion::Other(_) => unimplemented!(),
})
.prop_flat_map(|(version, created_at, expiration, algorithm)| {
(
Just(version),
Just(algorithm),
Just(created_at),
Just(expiration),
any_with::<PublicParams>(algorithm),
)
})
.prop_map(|(version, algorithm, created_at, expiration, pub_params)| {
PubKeyInner::new(version, algorithm, created_at, expiration, pub_params)
.unwrap()
})
.boxed()
}
}
impl Arbitrary for PublicKey {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<PubKeyInner>()
.prop_map(|k| PublicKey::from_inner(k).unwrap())
.boxed()
}
}
impl Arbitrary for PublicSubkey {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<PubKeyInner>()
.prop_map(|k| PublicSubkey::from_inner(k).unwrap())
.boxed()
}
}
proptest! {
#[test]
#[ignore]
fn public_key_write_len(packet: PublicKey) {
let mut buf = Vec::new();
packet.to_writer(&mut buf)?;
prop_assert_eq!(buf.len(), packet.write_len());
}
#[test]
#[ignore]
fn public_key_packet_roundtrip(packet: PublicKey) {
let _: Box<&dyn KeyDetails> = Box::new(&packet);
let _: Box<&dyn VerifyingKey> = Box::new(&packet);
let mut buf = Vec::new();
packet.to_writer(&mut buf)?;
let new_packet = PublicKey::try_from_reader(*packet.packet_header(), &mut &buf[..])?;
prop_assert_eq!(packet, new_packet);
}
#[test]
#[ignore]
fn public_sub_key_write_len(packet: PublicSubkey) {
let mut buf = Vec::new();
packet.to_writer(&mut buf)?;
prop_assert_eq!(buf.len(), packet.write_len());
}
#[test]
#[ignore]
fn public_sub_key_packet_roundtrip(packet: PublicSubkey) {
let mut buf = Vec::new();
packet.to_writer(&mut buf)?;
let new_packet = PublicSubkey::try_from_reader(*packet.packet_header(), &mut &buf[..])?;
prop_assert_eq!(packet, new_packet);
}
}
}