use crate::signing::{KeyPair, PublicKey};
use base64::{Engine, engine::general_purpose::STANDARD};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum OpenPgpError {
#[error("Invalid packet format: {0}")]
InvalidPacket(String),
#[error("Invalid key length: expected {expected}, got {actual}")]
InvalidLength { expected: usize, actual: usize },
#[error("Unsupported algorithm: {0}")]
UnsupportedAlgorithm(u8),
#[error("Base64 decode error: {0}")]
Base64Error(String),
#[error("Invalid armor format: {0}")]
InvalidArmor(String),
#[error("Unsupported packet type: {0}")]
UnsupportedPacketType(u8),
#[error("Invalid secret key")]
InvalidSecretKey,
}
pub type OpenPgpResult<T> = Result<T, OpenPgpError>;
const PGP_PUBLIC_KEY_HEADER: &str = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
const PGP_PUBLIC_KEY_FOOTER: &str = "-----END PGP PUBLIC KEY BLOCK-----";
const PGP_PRIVATE_KEY_HEADER: &str = "-----BEGIN PGP PRIVATE KEY BLOCK-----";
const PGP_PRIVATE_KEY_FOOTER: &str = "-----END PGP PRIVATE KEY BLOCK-----";
const TAG_PUBLIC_KEY: u8 = 6; const TAG_USER_ID: u8 = 13; const TAG_SECRET_KEY: u8 = 5;
const ALGO_EDDSA: u8 = 22;
const ED25519_OID: &[u8] = &[0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01];
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OpenPgpPublicKey {
pub created: u32,
pub key_material: Vec<u8>,
pub user_id: String,
}
impl OpenPgpPublicKey {
pub fn from_ed25519(public_key: &PublicKey, user_id: impl Into<String>) -> Self {
let created = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
Self {
created,
key_material: public_key.to_vec(),
user_id: user_id.into(),
}
}
pub fn to_packet(&self) -> Vec<u8> {
let mut packet = Vec::new();
let mut key_packet = Vec::new();
key_packet.push(4);
key_packet.extend_from_slice(&self.created.to_be_bytes());
key_packet.push(ALGO_EDDSA);
key_packet.push(ED25519_OID.len() as u8);
key_packet.extend_from_slice(ED25519_OID);
write_mpi(&mut key_packet, &self.key_material);
add_packet_header(&mut packet, TAG_PUBLIC_KEY, &key_packet);
let user_id_bytes = self.user_id.as_bytes();
add_packet_header(&mut packet, TAG_USER_ID, user_id_bytes);
packet
}
pub fn to_armored(&self) -> String {
let packet = self.to_packet();
let encoded = STANDARD.encode(&packet);
let crc = crc24(&packet);
let crc_bytes = [(crc >> 16) as u8, (crc >> 8) as u8, crc as u8];
let crc_encoded = STANDARD.encode(crc_bytes);
let mut result = String::new();
result.push_str(PGP_PUBLIC_KEY_HEADER);
result.push_str("\n\n");
for chunk in encoded.as_bytes().chunks(64) {
result.push_str(std::str::from_utf8(chunk).unwrap());
result.push('\n');
}
result.push('=');
result.push_str(&crc_encoded);
result.push('\n');
result.push_str(PGP_PUBLIC_KEY_FOOTER);
result.push('\n');
result
}
pub fn fingerprint(&self) -> [u8; 32] {
let packet = self.to_packet();
let mut hasher = Sha256::new();
hasher.update(&packet);
hasher.finalize().into()
}
}
impl fmt::Display for OpenPgpPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_armored())
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct OpenPgpSecretKey {
pub created: u32,
pub public_key: Vec<u8>,
pub secret_key: Vec<u8>,
pub user_id: String,
}
impl OpenPgpSecretKey {
pub fn from_ed25519(keypair: &KeyPair, user_id: impl Into<String>) -> Self {
let created = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as u32;
Self {
created,
public_key: keypair.public_key().to_vec(),
secret_key: keypair.secret_key().to_vec(),
user_id: user_id.into(),
}
}
pub fn to_packet(&self) -> Vec<u8> {
let mut packet = Vec::new();
let mut key_packet = Vec::new();
key_packet.push(4);
key_packet.extend_from_slice(&self.created.to_be_bytes());
key_packet.push(ALGO_EDDSA);
key_packet.push(ED25519_OID.len() as u8);
key_packet.extend_from_slice(ED25519_OID);
write_mpi(&mut key_packet, &self.public_key);
key_packet.push(0);
write_mpi(&mut key_packet, &self.secret_key);
let checksum: u16 = self.secret_key.iter().map(|&b| b as u16).sum();
key_packet.extend_from_slice(&checksum.to_be_bytes());
add_packet_header(&mut packet, TAG_SECRET_KEY, &key_packet);
let user_id_bytes = self.user_id.as_bytes();
add_packet_header(&mut packet, TAG_USER_ID, user_id_bytes);
packet
}
pub fn to_armored(&self) -> String {
let packet = self.to_packet();
let encoded = STANDARD.encode(&packet);
let crc = crc24(&packet);
let crc_bytes = [(crc >> 16) as u8, (crc >> 8) as u8, crc as u8];
let crc_encoded = STANDARD.encode(crc_bytes);
let mut result = String::new();
result.push_str(PGP_PRIVATE_KEY_HEADER);
result.push_str("\n\n");
for chunk in encoded.as_bytes().chunks(64) {
result.push_str(std::str::from_utf8(chunk).unwrap());
result.push('\n');
}
result.push('=');
result.push_str(&crc_encoded);
result.push('\n');
result.push_str(PGP_PRIVATE_KEY_FOOTER);
result.push('\n');
result
}
pub fn to_ed25519(&self) -> OpenPgpResult<KeyPair> {
if self.secret_key.len() != 32 {
return Err(OpenPgpError::InvalidLength {
expected: 32,
actual: self.secret_key.len(),
});
}
let mut secret = [0u8; 32];
secret.copy_from_slice(&self.secret_key);
KeyPair::from_secret_key(&secret).map_err(|_| OpenPgpError::InvalidSecretKey)
}
pub fn public_key(&self) -> OpenPgpPublicKey {
OpenPgpPublicKey {
created: self.created,
key_material: self.public_key.clone(),
user_id: self.user_id.clone(),
}
}
}
fn add_packet_header(buf: &mut Vec<u8>, tag: u8, body: &[u8]) {
let len = body.len();
if len < 256 {
buf.push(0x80 | (tag << 2));
buf.push(len as u8);
} else if len < 65536 {
buf.push(0x80 | (tag << 2) | 0x01);
buf.extend_from_slice(&(len as u16).to_be_bytes());
} else {
buf.push(0x80 | (tag << 2) | 0x02);
buf.extend_from_slice(&(len as u32).to_be_bytes());
}
buf.extend_from_slice(body);
}
fn write_mpi(buf: &mut Vec<u8>, data: &[u8]) {
let bit_count = (data.len() * 8 + 7) as u16;
buf.extend_from_slice(&bit_count.to_be_bytes());
buf.push(0x40);
buf.extend_from_slice(data);
}
fn crc24(data: &[u8]) -> u32 {
const CRC24_INIT: u32 = 0xB704CE;
const CRC24_POLY: u32 = 0x1864CFB;
let mut crc = CRC24_INIT;
for &byte in data {
crc ^= (byte as u32) << 16;
for _ in 0..8 {
crc <<= 1;
if crc & 0x1000000 != 0 {
crc ^= CRC24_POLY;
}
}
}
crc & 0xFFFFFF
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pgp_public_key_creation() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
assert_eq!(pgp_pub.key_material.len(), 32);
assert_eq!(pgp_pub.user_id, "test@example.com");
}
#[test]
fn test_pgp_public_key_packet() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
let packet = pgp_pub.to_packet();
assert!(!packet.is_empty());
assert_eq!(packet[0] & 0x80, 0x80); }
#[test]
fn test_pgp_public_key_armor() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
let armored = pgp_pub.to_armored();
assert!(armored.contains(PGP_PUBLIC_KEY_HEADER));
assert!(armored.contains(PGP_PUBLIC_KEY_FOOTER));
assert!(armored.contains("=")); }
#[test]
fn test_pgp_secret_key_creation() {
let keypair = KeyPair::generate();
let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
assert_eq!(pgp_sec.public_key.len(), 32);
assert_eq!(pgp_sec.secret_key.len(), 32);
assert_eq!(pgp_sec.user_id, "test@example.com");
}
#[test]
fn test_pgp_secret_key_armor() {
let keypair = KeyPair::generate();
let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
let armored = pgp_sec.to_armored();
assert!(armored.contains(PGP_PRIVATE_KEY_HEADER));
assert!(armored.contains(PGP_PRIVATE_KEY_FOOTER));
}
#[test]
fn test_pgp_secret_to_keypair() {
let keypair = KeyPair::generate();
let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
let recovered = pgp_sec.to_ed25519().unwrap();
assert_eq!(recovered.public_key(), keypair.public_key());
assert_eq!(recovered.secret_key(), keypair.secret_key());
}
#[test]
fn test_pgp_public_from_secret() {
let keypair = KeyPair::generate();
let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
let pgp_pub = pgp_sec.public_key();
assert_eq!(pgp_pub.key_material, pgp_sec.public_key);
assert_eq!(pgp_pub.user_id, pgp_sec.user_id);
}
#[test]
fn test_fingerprint() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
let fp1 = pgp_pub.fingerprint();
let fp2 = pgp_pub.fingerprint();
assert_eq!(fp1, fp2);
assert_eq!(fp1.len(), 32);
}
#[test]
fn test_crc24() {
let data = b"hello";
let crc = crc24(data);
assert!(crc < 0x1000000);
assert_eq!(crc, crc24(data));
}
#[test]
fn test_armor_line_length() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
let armored = pgp_pub.to_armored();
for line in armored.lines() {
if !line.contains("BEGIN") && !line.contains("END") && !line.starts_with('=') {
assert!(line.len() <= 64, "Line too long: {}", line.len());
}
}
}
#[test]
fn test_mpi_encoding() {
let mut buf = Vec::new();
let data = &[0x12, 0x34, 0x56, 0x78];
write_mpi(&mut buf, data);
assert_eq!(buf.len(), 2 + 1 + data.len());
assert_eq!(buf[2], 0x40); }
#[test]
fn test_packet_header() {
let mut buf = Vec::new();
let body = b"test";
add_packet_header(&mut buf, TAG_USER_ID, body);
assert_eq!(buf[0] & 0x80, 0x80);
assert_eq!((buf[0] >> 2) & 0x0F, TAG_USER_ID);
}
#[test]
fn test_serialization() {
let keypair = KeyPair::generate();
let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
let serialized = crate::codec::encode(&pgp_pub).unwrap();
let deserialized: OpenPgpPublicKey = crate::codec::decode(&serialized).unwrap();
assert_eq!(deserialized.key_material, pgp_pub.key_material);
assert_eq!(deserialized.user_id, pgp_pub.user_id);
}
}