use std::fmt;
use std::io::Read;
use bstr::{BStr, BString};
use byteorder::{BigEndian, ByteOrder};
use chrono::{DateTime, Duration, Utc};
use iter_read::IterRead;
use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
use crate::crypto::aead::AeadAlgorithm;
use crate::crypto::hash::HashAlgorithm;
use crate::crypto::public_key::PublicKeyAlgorithm;
use crate::crypto::sym::SymmetricKeyAlgorithm;
use crate::errors::Result;
use crate::line_writer::LineBreak;
use crate::normalize_lines::Normalized;
use crate::packet::signature::SignatureConfig;
use crate::packet::PacketTrait;
use crate::ser::Serialize;
use crate::types::{
self, CompressionAlgorithm, KeyId, KeyVersion, Mpi, PublicKeyTrait, Tag, Version,
};
use smallvec::SmallVec;
#[derive(Clone, PartialEq, Eq)]
pub struct Signature {
packet_version: Version,
pub config: SignatureConfig,
pub signed_hash_value: [u8; 2],
pub signature: Vec<Mpi>,
}
impl Signature {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::complexity))]
pub fn new(
packet_version: Version,
version: SignatureVersion,
typ: SignatureType,
pub_alg: PublicKeyAlgorithm,
hash_alg: HashAlgorithm,
signed_hash_value: [u8; 2],
signature: Vec<Mpi>,
hashed_subpackets: Vec<Subpacket>,
unhashed_subpackets: Vec<Subpacket>,
) -> Self {
Signature {
packet_version,
config: SignatureConfig::new_v4(
version,
typ,
pub_alg,
hash_alg,
hashed_subpackets,
unhashed_subpackets,
),
signed_hash_value,
signature,
}
}
pub fn from_config(
config: SignatureConfig,
signed_hash_value: [u8; 2],
signature: Vec<Mpi>,
) -> Self {
Signature {
packet_version: Default::default(),
config,
signed_hash_value,
signature,
}
}
pub fn typ(&self) -> SignatureType {
self.config.typ()
}
fn match_identity(sig: &Signature, key: &impl PublicKeyTrait) -> bool {
let issuers = sig.issuer();
let issuer_fps = sig.issuer_fingerprint();
if issuers.is_empty() && issuer_fps.is_empty() {
return true;
}
issuers.iter().any(|&key_id| key_id == &key.key_id())
|| issuer_fps.iter().any(|&fp| fp == key.fingerprint())
}
pub fn verify<R>(&self, key: &impl PublicKeyTrait, data: R) -> Result<()>
where
R: Read,
{
ensure!(
Self::match_identity(self, key),
"verify: No matching issuer or issuer_fingerprint for Key ID: {:?}",
&key.key_id(),
);
let mut hasher = self.config.hash_alg.new_hasher()?;
if matches!(self.typ(), SignatureType::Text) {
let normalized = Normalized::new(data.bytes().flat_map(|b| b.ok()), LineBreak::Crlf);
self.config
.hash_data_to_sign(&mut *hasher, IterRead::new(normalized))?;
} else {
self.config.hash_data_to_sign(&mut *hasher, data)?;
}
let len = self.config.hash_signature_data(&mut *hasher)?;
hasher.update(&self.config.trailer(len)?);
let hash = &hasher.finish()[..];
ensure_eq!(
&self.signed_hash_value,
&hash[0..2],
"signature: invalid signed hash value"
);
key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn verify_certification(
&self,
key: &impl PublicKeyTrait,
tag: Tag,
id: &impl Serialize,
) -> Result<()> {
let key_id = key.key_id();
debug!("verifying certification {:?} {:#?}", key_id, self);
ensure!(
Self::match_identity(self, key),
"verify_certification: No matching issuer or issuer_fingerprint for Key ID: {:?}",
key_id,
);
let mut hasher = self.config.hash_alg.new_hasher()?;
{
let mut key_buf = Vec::new();
key.to_writer_old(&mut key_buf)?;
hasher.update(&key_buf);
}
{
let mut packet_buf = Vec::new();
id.to_writer(&mut packet_buf)?;
match self.config.version {
SignatureVersion::V2 | SignatureVersion::V3 => {
}
SignatureVersion::V4 | SignatureVersion::V5 => {
let prefix = match tag {
Tag::UserId => 0xB4,
Tag::UserAttribute => 0xD1,
_ => bail!("invalid tag for certification validation: {:?}", tag),
};
let mut prefix_buf = [prefix, 0u8, 0u8, 0u8, 0u8];
BigEndian::write_u32(&mut prefix_buf[1..], packet_buf.len() as u32);
hasher.update(&prefix_buf);
}
SignatureVersion::Other(version) => {
bail!("unsupported signature version: {:?}", version)
}
}
hasher.update(&packet_buf);
}
let len = self.config.hash_signature_data(&mut *hasher)?;
hasher.update(&self.config.trailer(len)?);
let hash = &hasher.finish()[..];
ensure_eq!(
&self.signed_hash_value,
&hash[0..2],
"certification: invalid signed hash value"
);
key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn verify_key_binding(
&self,
signing_key: &impl PublicKeyTrait,
key: &impl PublicKeyTrait,
) -> Result<()> {
self.verify_key_binding_internal(signing_key, key, false)
}
pub fn verify_backwards_key_binding(
&self,
signing_key: &impl PublicKeyTrait,
key: &impl PublicKeyTrait,
) -> Result<()> {
self.verify_key_binding_internal(signing_key, key, true)
}
fn verify_key_binding_internal(
&self,
signer: &impl PublicKeyTrait,
signee: &impl PublicKeyTrait,
backsig: bool,
) -> Result<()> {
debug!(
"verifying key binding: {:#?} - {:#?} - {:#?} (backsig: {})",
self, signer, signee, backsig
);
let mut hasher = self.config.hash_alg.new_hasher()?;
{
let mut key_buf = Vec::new();
if !backsig {
signer.to_writer_old(&mut key_buf)?; } else {
signee.to_writer_old(&mut key_buf)?; }
hasher.update(&key_buf);
}
{
let mut key_buf = Vec::new();
if !backsig {
signee.to_writer_old(&mut key_buf)?; } else {
signer.to_writer_old(&mut key_buf)?; }
hasher.update(&key_buf);
}
let len = self.config.hash_signature_data(&mut *hasher)?;
hasher.update(&self.config.trailer(len)?);
let hash = &hasher.finish()[..];
ensure_eq!(
&self.signed_hash_value,
&hash[0..2],
"key binding: invalid signed hash value"
);
signer.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn verify_key(&self, key: &impl PublicKeyTrait) -> Result<()> {
debug!("verifying key (revocation): {:#?} - {:#?}", self, key);
ensure!(
Self::match_identity(self, key),
"verify_key: No matching issuer or issuer_fingerprint for Key ID: {:?}",
&key.key_id(),
);
let mut hasher = self.config.hash_alg.new_hasher()?;
{
let mut key_buf = Vec::new();
key.to_writer_old(&mut key_buf)?;
hasher.update(&key_buf);
}
let len = self.config.hash_signature_data(&mut *hasher)?;
hasher.update(&self.config.trailer(len)?);
let hash = &hasher.finish()[..];
ensure_eq!(
&self.signed_hash_value,
&hash[0..2],
"key: invalid signed hash value"
);
key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn is_certification(&self) -> bool {
self.config.is_certification()
}
pub fn key_expiration_time(&self) -> Option<&Duration> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::KeyExpirationTime(d) => Some(d),
_ => None,
})
}
pub fn signature_expiration_time(&self) -> Option<&Duration> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::SignatureExpirationTime(d) => Some(d),
_ => None,
})
}
pub fn created(&self) -> Option<&DateTime<Utc>> {
self.config.created()
}
pub fn issuer(&self) -> Vec<&KeyId> {
self.config.issuer()
}
pub fn issuer_fingerprint(&self) -> Vec<&[u8]> {
self.config.issuer_fingerprint()
}
pub fn preferred_symmetric_algs(&self) -> &[SymmetricKeyAlgorithm] {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::PreferredSymmetricAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn preferred_hash_algs(&self) -> &[HashAlgorithm] {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::PreferredHashAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn preferred_compression_algs(&self) -> &[CompressionAlgorithm] {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::PreferredCompressionAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn key_server_prefs(&self) -> &[u8] {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::KeyServerPreferences(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn key_flags(&self) -> KeyFlags {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::KeyFlags(d) => Some(d[..].into()),
_ => None,
})
.unwrap_or_default()
}
pub fn features(&self) -> &[u8] {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::Features(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn revocation_reason_code(&self) -> Option<&RevocationCode> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::RevocationReason(code, _) => Some(code),
_ => None,
})
}
pub fn revocation_reason_string(&self) -> Option<&BStr> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::RevocationReason(_, reason) => Some(reason.as_ref()),
_ => None,
})
}
pub fn is_primary(&self) -> bool {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::IsPrimary(d) => Some(*d),
_ => None,
})
.unwrap_or(false)
}
pub fn is_revocable(&self) -> bool {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::Revocable(d) => Some(*d),
_ => None,
})
.unwrap_or(true)
}
pub fn embedded_signature(&self) -> Option<&Signature> {
self.config
.hashed_subpackets()
.chain(self.config.unhashed_subpackets())
.find_map(|p| match &p.data {
SubpacketData::EmbeddedSignature(d) => Some(&**d),
_ => None,
})
}
pub fn preferred_key_server(&self) -> Option<&str> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::PreferredKeyServer(d) => Some(d.as_str()),
_ => None,
})
}
pub fn notations(&self) -> Vec<&Notation> {
self.config
.hashed_subpackets()
.filter_map(|p| match &p.data {
SubpacketData::Notation(d) => Some(d),
_ => None,
})
.collect()
}
pub fn revocation_key(&self) -> Option<&types::RevocationKey> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::RevocationKey(d) => Some(d),
_ => None,
})
}
pub fn signers_userid(&self) -> Option<&BStr> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::SignersUserID(d) => Some(d.as_ref()),
_ => None,
})
}
pub fn policy_uri(&self) -> Option<&str> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::PolicyURI(d) => Some(d.as_ref()),
_ => None,
})
}
pub fn trust_signature(&self) -> Option<(u8, u8)> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::TrustSignature(depth, value) => Some((*depth, *value)),
_ => None,
})
}
pub fn regular_expression(&self) -> Option<&BStr> {
self.config.hashed_subpackets().find_map(|p| match &p.data {
SubpacketData::RegularExpression(d) => Some(d.as_ref()),
_ => None,
})
}
pub fn exportable_certification(&self) -> bool {
self.config
.hashed_subpackets()
.find_map(|p| match &p.data {
SubpacketData::ExportableCertification(d) => Some(*d),
_ => None,
})
.unwrap_or(true)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum SignatureVersion {
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
#[num_enum(catch_all)]
Other(u8),
}
impl Default for SignatureVersion {
fn default() -> Self {
Self::V4
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum SignatureType {
Binary = 0x00,
Text = 0x01,
Standalone = 0x02,
CertGeneric = 0x10,
CertPersona = 0x11,
CertCasual = 0x12,
CertPositive = 0x13,
SubkeyBinding = 0x18,
KeyBinding = 0x19,
Key = 0x1F,
KeyRevocation = 0x20,
SubkeyRevocation = 0x28,
CertRevocation = 0x30,
Timestamp = 0x40,
ThirdParty = 0x50,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum SubpacketType {
SignatureCreationTime,
SignatureExpirationTime,
ExportableCertification,
TrustSignature,
RegularExpression,
Revocable,
KeyExpirationTime,
PreferredSymmetricAlgorithms,
RevocationKey,
Issuer,
Notation,
PreferredHashAlgorithms,
PreferredCompressionAlgorithms,
KeyServerPreferences,
PreferredKeyServer,
PrimaryUserId,
PolicyURI,
KeyFlags,
SignersUserID,
RevocationReason,
Features,
SignatureTarget,
EmbeddedSignature,
IssuerFingerprint,
PreferredAead,
Experimental(u8),
Other(u8),
}
impl SubpacketType {
pub fn as_u8(&self, is_critical: bool) -> u8 {
let raw: u8 = match self {
SubpacketType::SignatureCreationTime => 2,
SubpacketType::SignatureExpirationTime => 3,
SubpacketType::ExportableCertification => 4,
SubpacketType::TrustSignature => 5,
SubpacketType::RegularExpression => 6,
SubpacketType::Revocable => 7,
SubpacketType::KeyExpirationTime => 9,
SubpacketType::PreferredSymmetricAlgorithms => 11,
SubpacketType::RevocationKey => 12,
SubpacketType::Issuer => 16,
SubpacketType::Notation => 20,
SubpacketType::PreferredHashAlgorithms => 21,
SubpacketType::PreferredCompressionAlgorithms => 22,
SubpacketType::KeyServerPreferences => 23,
SubpacketType::PreferredKeyServer => 24,
SubpacketType::PrimaryUserId => 25,
SubpacketType::PolicyURI => 26,
SubpacketType::KeyFlags => 27,
SubpacketType::SignersUserID => 28,
SubpacketType::RevocationReason => 29,
SubpacketType::Features => 30,
SubpacketType::SignatureTarget => 31,
SubpacketType::EmbeddedSignature => 32,
SubpacketType::IssuerFingerprint => 33,
SubpacketType::PreferredAead => 34,
SubpacketType::Experimental(n) => *n,
SubpacketType::Other(n) => *n,
};
if is_critical {
raw | 0b1000_0000
} else {
raw
}
}
#[inline]
pub fn from_u8(n: u8) -> (Self, bool) {
let is_critical = (n >> 7) == 1;
let n = n & 0b0111_1111;
let m = match n {
2 => SubpacketType::SignatureCreationTime,
3 => SubpacketType::SignatureExpirationTime,
4 => SubpacketType::ExportableCertification,
5 => SubpacketType::TrustSignature,
6 => SubpacketType::RegularExpression,
7 => SubpacketType::Revocable,
9 => SubpacketType::KeyExpirationTime,
11 => SubpacketType::PreferredSymmetricAlgorithms,
12 => SubpacketType::RevocationKey,
16 => SubpacketType::Issuer,
20 => SubpacketType::Notation,
21 => SubpacketType::PreferredHashAlgorithms,
22 => SubpacketType::PreferredCompressionAlgorithms,
23 => SubpacketType::KeyServerPreferences,
24 => SubpacketType::PreferredKeyServer,
25 => SubpacketType::PrimaryUserId,
26 => SubpacketType::PolicyURI,
27 => SubpacketType::KeyFlags,
28 => SubpacketType::SignersUserID,
29 => SubpacketType::RevocationReason,
30 => SubpacketType::Features,
31 => SubpacketType::SignatureTarget,
32 => SubpacketType::EmbeddedSignature,
33 => SubpacketType::IssuerFingerprint,
34 => SubpacketType::PreferredAead,
100..=110 => SubpacketType::Experimental(n),
_ => SubpacketType::Other(n),
};
(m, is_critical)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Subpacket {
pub is_critical: bool,
pub data: SubpacketData,
}
impl Subpacket {
pub const fn regular(data: SubpacketData) -> Self {
Subpacket {
is_critical: false,
data,
}
}
pub const fn critical(data: SubpacketData) -> Self {
Subpacket {
is_critical: true,
data,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SubpacketData {
SignatureCreationTime(DateTime<Utc>),
SignatureExpirationTime(Duration),
KeyExpirationTime(Duration),
Issuer(KeyId),
PreferredSymmetricAlgorithms(SmallVec<[SymmetricKeyAlgorithm; 8]>),
PreferredHashAlgorithms(SmallVec<[HashAlgorithm; 8]>),
PreferredCompressionAlgorithms(SmallVec<[CompressionAlgorithm; 8]>),
KeyServerPreferences(SmallVec<[u8; 4]>),
KeyFlags(SmallVec<[u8; 1]>),
Features(SmallVec<[u8; 1]>),
RevocationReason(RevocationCode, BString),
IsPrimary(bool),
Revocable(bool),
EmbeddedSignature(Box<Signature>),
PreferredKeyServer(String),
Notation(Notation),
RevocationKey(types::RevocationKey),
SignersUserID(BString),
PolicyURI(String),
TrustSignature(u8, u8),
RegularExpression(BString),
ExportableCertification(bool),
IssuerFingerprint(KeyVersion, SmallVec<[u8; 20]>),
PreferredAeadAlgorithms(SmallVec<[AeadAlgorithm; 2]>),
Experimental(u8, SmallVec<[u8; 2]>),
Other(u8, Vec<u8>),
SignatureTarget(PublicKeyAlgorithm, HashAlgorithm, Vec<u8>),
}
bitfield! {
#[derive(Default, PartialEq, Eq, Copy, Clone)]
pub struct KeyFlags(u8);
impl Debug;
pub certify, set_certify: 0;
pub sign, set_sign: 1;
pub encrypt_comms, set_encrypt_comms: 2;
pub encrypt_storage, set_encrypt_storage: 3;
pub shared, set_shared: 4;
pub authentication, set_authentication: 5;
pub group, set_group: 7;
}
impl<'a> From<&'a [u8]> for KeyFlags {
fn from(other: &'a [u8]) -> Self {
if other.is_empty() {
Default::default()
} else {
KeyFlags(other[0])
}
}
}
impl From<KeyFlags> for SmallVec<[u8; 1]> {
fn from(flags: KeyFlags) -> Self {
smallvec![flags.0]
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Notation {
pub readable: bool,
pub name: BString,
pub value: BString,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, FromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum RevocationCode {
NoReason = 0,
KeySuperseded = 1,
KeyCompromised = 2,
KeyRetired = 3,
CertUserIdInvalid = 32,
Private100 = 100,
Private101 = 101,
Private102 = 102,
Private103 = 103,
Private104 = 104,
Private105 = 105,
Private106 = 106,
Private107 = 107,
Private108 = 108,
Private109 = 109,
Private110 = 110,
#[num_enum(catch_all)]
Other(u8),
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Signature")
.field("packet_version", &self.packet_version)
.field("config", &self.config)
.field("signed_hash_value", &hex::encode(self.signed_hash_value))
.field(
"signature",
&format_args!(
"{:?}",
self.signature.iter().map(hex::encode).collect::<Vec<_>>()
),
)
.finish()
}
}
impl PacketTrait for Signature {
fn packet_version(&self) -> Version {
self.packet_version
}
fn tag(&self) -> Tag {
Tag::Signature
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keyflags() {
let flags: KeyFlags = Default::default();
assert_eq!(flags.0, 0x00);
let mut flags = KeyFlags::default();
flags.set_certify(true);
assert!(flags.certify());
assert_eq!(flags.0, 0x01);
let mut flags = KeyFlags::default();
flags.set_sign(true);
assert_eq!(flags.0, 0x02);
let mut flags = KeyFlags::default();
flags.set_encrypt_comms(true);
assert_eq!(flags.0, 0x04);
let mut flags = KeyFlags::default();
flags.set_encrypt_storage(true);
assert_eq!(flags.0, 0x08);
let mut flags = KeyFlags::default();
flags.set_shared(true);
assert_eq!(flags.0, 0x10);
let mut flags = KeyFlags::default();
flags.set_authentication(true);
assert_eq!(flags.0, 0x20);
let mut flags = KeyFlags::default();
flags.set_group(true);
assert_eq!(flags.0, 0x80);
}
#[test]
fn test_critical() {
use SubpacketType::*;
let cases = [
SignatureCreationTime,
SignatureExpirationTime,
ExportableCertification,
TrustSignature,
RegularExpression,
Revocable,
KeyExpirationTime,
PreferredSymmetricAlgorithms,
RevocationKey,
Issuer,
Notation,
PreferredHashAlgorithms,
PreferredCompressionAlgorithms,
KeyServerPreferences,
PreferredKeyServer,
PrimaryUserId,
PolicyURI,
KeyFlags,
SignersUserID,
RevocationReason,
Features,
SignatureTarget,
EmbeddedSignature,
IssuerFingerprint,
PreferredAead,
Experimental(101),
Other(95),
];
for case in cases {
assert_eq!(SubpacketType::from_u8(case.as_u8(false)), (case, false));
assert_eq!(SubpacketType::from_u8(case.as_u8(true)), (case, true));
}
}
}