use std::fmt;
use byteorder::{BigEndian, ByteOrder};
use chrono::{DateTime, Utc};
use num_traits::FromPrimitive;
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::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()
}
pub fn verify(&self, key: &impl PublicKeyTrait, data: &[u8]) -> Result<()> {
if let Some(issuer) = self.issuer() {
if &key.key_id() != issuer {
warn!(
"validating signature with a non matching Key ID {:?} != {:?}",
&key.key_id(),
issuer
);
}
}
let mut hasher = self.config.hash_alg.new_hasher()?;
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],
"invalid signed hash value"
);
key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn verify_certificate(
&self,
key: &impl PublicKeyTrait,
tag: Tag,
id: &impl Serialize,
) -> Result<()> {
debug!("verifying certificate {:#?}", self);
if let Some(issuer) = self.issuer() {
if &key.key_id() != issuer {
warn!(
"validating certificate with a non matching Key ID {:?} != {:?}",
&key.key_id(),
issuer
);
}
}
let mut hasher = self.config.hash_alg.new_hasher()?;
let mut key_buf = Vec::new();
key.to_writer_old(&mut key_buf)?;
let mut packet_buf = Vec::new();
id.to_writer(&mut packet_buf)?;
debug!("packet: {}", hex::encode(&packet_buf));
hasher.update(&key_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 certificate validation: {:?}", tag),
};
let mut prefix_buf = [prefix, 0u8, 0u8, 0u8, 0u8];
BigEndian::write_u32(&mut prefix_buf[1..], packet_buf.len() as u32);
debug!("prefix: {}", hex::encode(&prefix_buf));
hasher.update(&prefix_buf);
}
}
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],
"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<()> {
debug!(
"verifying key binding: {:#?} - {:#?} - {:#?}",
self, signing_key, key
);
let key_id = signing_key.key_id();
if let Some(issuer) = self.issuer() {
if &key_id != issuer {
warn!(
"validating key binding with a non matching Key ID {:?} != {:?}",
&key_id, issuer
);
}
}
let mut hasher = self.config.hash_alg.new_hasher()?;
{
let mut key_buf = Vec::new();
signing_key.to_writer_old(&mut key_buf)?;
hasher.update(&key_buf);
}
{
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],
"invalid signed hash value"
);
signing_key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn verify_key(&self, key: &impl PublicKeyTrait) -> Result<()> {
debug!("verifying key (revocation): {:#?} - {:#?}", self, key);
let key_id = key.key_id();
if let Some(issuer) = self.issuer() {
if &key_id != issuer {
warn!(
"validating key (revocation) with a non matching Key ID {:?} != {:?}",
&key_id, issuer
);
}
}
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],
"invalid signed hash value"
);
key.verify_signature(self.config.hash_alg, hash, &self.signature)
}
pub fn is_certificate(&self) -> bool {
self.config.is_certificate()
}
fn subpackets(&self) -> impl Iterator<Item = &Subpacket> {
self.config.subpackets()
}
pub fn key_expiration_time(&self) -> Option<&DateTime<Utc>> {
self.subpackets().find_map(|p| match p {
Subpacket::KeyExpirationTime(d) => Some(d),
_ => None,
})
}
pub fn signature_expiration_time(&self) -> Option<&DateTime<Utc>> {
self.subpackets().find_map(|p| match p {
Subpacket::SignatureExpirationTime(d) => Some(d),
_ => None,
})
}
pub fn created(&self) -> Option<&DateTime<Utc>> {
self.config.created()
}
pub fn issuer(&self) -> Option<&KeyId> {
self.config.issuer()
}
pub fn preferred_symmetric_algs(&self) -> &[SymmetricKeyAlgorithm] {
self.subpackets()
.find_map(|p| match p {
Subpacket::PreferredSymmetricAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn preferred_hash_algs(&self) -> &[HashAlgorithm] {
self.subpackets()
.find_map(|p| match p {
Subpacket::PreferredHashAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn preferred_compression_algs(&self) -> &[CompressionAlgorithm] {
self.subpackets()
.find_map(|p| match p {
Subpacket::PreferredCompressionAlgorithms(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn key_server_prefs(&self) -> &[u8] {
self.subpackets()
.find_map(|p| match p {
Subpacket::KeyServerPreferences(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn key_flags(&self) -> KeyFlags {
self.subpackets()
.find_map(|p| match p {
Subpacket::KeyFlags(d) => Some(d[..].into()),
_ => None,
})
.unwrap_or_default()
}
pub fn features(&self) -> &[u8] {
self.subpackets()
.find_map(|p| match p {
Subpacket::Features(d) => Some(&d[..]),
_ => None,
})
.unwrap_or_else(|| &[][..])
}
pub fn revocation_reason_code(&self) -> Option<&RevocationCode> {
self.subpackets().find_map(|p| match p {
Subpacket::RevocationReason(code, _) => Some(code),
_ => None,
})
}
pub fn revocation_reason_string(&self) -> Option<&str> {
self.subpackets().find_map(|p| match p {
Subpacket::RevocationReason(_, reason) => Some(reason.as_str()),
_ => None,
})
}
pub fn is_primary(&self) -> bool {
self.subpackets()
.find_map(|p| match p {
Subpacket::IsPrimary(d) => Some(*d),
_ => None,
})
.unwrap_or_else(|| false)
}
pub fn is_revocable(&self) -> bool {
self.subpackets()
.find_map(|p| match p {
Subpacket::Revocable(d) => Some(*d),
_ => None,
})
.unwrap_or_else(|| true)
}
pub fn embedded_signature(&self) -> Option<&Signature> {
self.subpackets().find_map(|p| match p {
Subpacket::EmbeddedSignature(d) => Some(&**d),
_ => None,
})
}
pub fn preferred_key_server(&self) -> Option<&str> {
self.subpackets().find_map(|p| match p {
Subpacket::PreferredKeyServer(d) => Some(d.as_str()),
_ => None,
})
}
pub fn notations(&self) -> Vec<&Notation> {
self.subpackets()
.filter_map(|p| match p {
Subpacket::Notation(d) => Some(d),
_ => None,
})
.collect()
}
pub fn revocation_key(&self) -> Option<&types::RevocationKey> {
self.subpackets().find_map(|p| match p {
Subpacket::RevocationKey(d) => Some(d),
_ => None,
})
}
pub fn signers_userid(&self) -> Option<&str> {
self.subpackets().find_map(|p| match p {
Subpacket::SignersUserID(d) => Some(d.as_str()),
_ => None,
})
}
pub fn policy_uri(&self) -> Option<&str> {
self.subpackets().find_map(|p| match p {
Subpacket::PolicyURI(d) => Some(d.as_str()),
_ => None,
})
}
pub fn trust_signature(&self) -> Option<(u8, u8)> {
self.subpackets().find_map(|p| match p {
Subpacket::TrustSignature(depth, value) => Some((*depth, *value)),
_ => None,
})
}
pub fn regular_expression(&self) -> Option<&str> {
self.subpackets().find_map(|p| match p {
Subpacket::RegularExpression(d) => Some(d.as_str()),
_ => None,
})
}
pub fn exportable_certification(&self) -> bool {
self.subpackets()
.find_map(|p| match p {
Subpacket::ExportableCertification(d) => Some(*d),
_ => None,
})
.unwrap_or_else(|| true)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum SignatureVersion {
V2 = 2,
V3 = 3,
V4 = 4,
V5 = 5,
}
impl Default for SignatureVersion {
fn default() -> Self {
SignatureVersion::V4
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, FromPrimitive)]
#[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 Into<u8> for SubpacketType {
#[inline]
fn into(self) -> 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,
}
}
}
impl FromPrimitive for SubpacketType {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
if n > 0 && n < 256 {
Self::from_u64(n as u64)
} else {
None
}
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
if n > 255 {
None
} else {
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 as u8),
_ => SubpacketType::Other(n as u8),
};
Some(m)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Subpacket {
SignatureCreationTime(DateTime<Utc>),
SignatureExpirationTime(DateTime<Utc>),
KeyExpirationTime(DateTime<Utc>),
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, String),
IsPrimary(bool),
Revocable(bool),
EmbeddedSignature(Box<Signature>),
PreferredKeyServer(String),
Notation(Notation),
RevocationKey(types::RevocationKey),
SignersUserID(String),
PolicyURI(String),
TrustSignature(u8, u8),
RegularExpression(String),
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: String,
pub value: String,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, FromPrimitive)]
#[repr(u8)]
pub enum RevocationCode {
NoReason = 0,
KeySuperseded = 1,
KeyCompromised = 2,
KeyRetired = 3,
CertUserIdInvalid = 32,
}
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);
}
}