1use crate::signing::{KeyPair, PublicKey};
25use base64::{Engine, engine::general_purpose::STANDARD};
26use serde::{Deserialize, Serialize};
27use sha2::{Digest, Sha256};
28use std::fmt;
29use std::time::{SystemTime, UNIX_EPOCH};
30use thiserror::Error;
31
32#[derive(Debug, Error, Clone, PartialEq, Eq)]
34pub enum OpenPgpError {
35 #[error("Invalid packet format: {0}")]
37 InvalidPacket(String),
38
39 #[error("Invalid key length: expected {expected}, got {actual}")]
41 InvalidLength { expected: usize, actual: usize },
42
43 #[error("Unsupported algorithm: {0}")]
45 UnsupportedAlgorithm(u8),
46
47 #[error("Base64 decode error: {0}")]
49 Base64Error(String),
50
51 #[error("Invalid armor format: {0}")]
53 InvalidArmor(String),
54
55 #[error("Unsupported packet type: {0}")]
57 UnsupportedPacketType(u8),
58
59 #[error("Invalid secret key")]
61 InvalidSecretKey,
62}
63
64pub type OpenPgpResult<T> = Result<T, OpenPgpError>;
66
67const PGP_PUBLIC_KEY_HEADER: &str = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
69const PGP_PUBLIC_KEY_FOOTER: &str = "-----END PGP PUBLIC KEY BLOCK-----";
70const PGP_PRIVATE_KEY_HEADER: &str = "-----BEGIN PGP PRIVATE KEY BLOCK-----";
71const PGP_PRIVATE_KEY_FOOTER: &str = "-----END PGP PRIVATE KEY BLOCK-----";
72
73const 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];
83
84#[derive(Clone, Debug, Serialize, Deserialize)]
86pub struct OpenPgpPublicKey {
87 pub created: u32,
89 pub key_material: Vec<u8>,
91 pub user_id: String,
93}
94
95impl OpenPgpPublicKey {
96 pub fn from_ed25519(public_key: &PublicKey, user_id: impl Into<String>) -> Self {
98 let created = SystemTime::now()
99 .duration_since(UNIX_EPOCH)
100 .unwrap()
101 .as_secs() as u32;
102
103 Self {
104 created,
105 key_material: public_key.to_vec(),
106 user_id: user_id.into(),
107 }
108 }
109
110 pub fn to_packet(&self) -> Vec<u8> {
112 let mut packet = Vec::new();
113
114 let mut key_packet = Vec::new();
116
117 key_packet.push(4);
119
120 key_packet.extend_from_slice(&self.created.to_be_bytes());
122
123 key_packet.push(ALGO_EDDSA);
125
126 key_packet.push(ED25519_OID.len() as u8);
128
129 key_packet.extend_from_slice(ED25519_OID);
131
132 write_mpi(&mut key_packet, &self.key_material);
134
135 add_packet_header(&mut packet, TAG_PUBLIC_KEY, &key_packet);
137
138 let user_id_bytes = self.user_id.as_bytes();
140 add_packet_header(&mut packet, TAG_USER_ID, user_id_bytes);
141
142 packet
143 }
144
145 pub fn to_armored(&self) -> String {
147 let packet = self.to_packet();
148 let encoded = STANDARD.encode(&packet);
149
150 let crc = crc24(&packet);
152 let crc_bytes = [(crc >> 16) as u8, (crc >> 8) as u8, crc as u8];
153 let crc_encoded = STANDARD.encode(crc_bytes);
154
155 let mut result = String::new();
156 result.push_str(PGP_PUBLIC_KEY_HEADER);
157 result.push_str("\n\n");
158
159 for chunk in encoded.as_bytes().chunks(64) {
161 result.push_str(std::str::from_utf8(chunk).unwrap());
162 result.push('\n');
163 }
164
165 result.push('=');
166 result.push_str(&crc_encoded);
167 result.push('\n');
168 result.push_str(PGP_PUBLIC_KEY_FOOTER);
169 result.push('\n');
170
171 result
172 }
173
174 pub fn fingerprint(&self) -> [u8; 32] {
176 let packet = self.to_packet();
177 let mut hasher = Sha256::new();
178 hasher.update(&packet);
179 hasher.finalize().into()
180 }
181}
182
183impl fmt::Display for OpenPgpPublicKey {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "{}", self.to_armored())
186 }
187}
188
189#[derive(Clone, Serialize, Deserialize)]
191pub struct OpenPgpSecretKey {
192 pub created: u32,
194 pub public_key: Vec<u8>,
196 pub secret_key: Vec<u8>,
198 pub user_id: String,
200}
201
202impl OpenPgpSecretKey {
203 pub fn from_ed25519(keypair: &KeyPair, user_id: impl Into<String>) -> Self {
205 let created = SystemTime::now()
206 .duration_since(UNIX_EPOCH)
207 .unwrap()
208 .as_secs() as u32;
209
210 Self {
211 created,
212 public_key: keypair.public_key().to_vec(),
213 secret_key: keypair.secret_key().to_vec(),
214 user_id: user_id.into(),
215 }
216 }
217
218 pub fn to_packet(&self) -> Vec<u8> {
220 let mut packet = Vec::new();
221
222 let mut key_packet = Vec::new();
224
225 key_packet.push(4);
227
228 key_packet.extend_from_slice(&self.created.to_be_bytes());
230
231 key_packet.push(ALGO_EDDSA);
233
234 key_packet.push(ED25519_OID.len() as u8);
236
237 key_packet.extend_from_slice(ED25519_OID);
239
240 write_mpi(&mut key_packet, &self.public_key);
242
243 key_packet.push(0);
245
246 write_mpi(&mut key_packet, &self.secret_key);
248
249 let checksum: u16 = self.secret_key.iter().map(|&b| b as u16).sum();
251 key_packet.extend_from_slice(&checksum.to_be_bytes());
252
253 add_packet_header(&mut packet, TAG_SECRET_KEY, &key_packet);
255
256 let user_id_bytes = self.user_id.as_bytes();
258 add_packet_header(&mut packet, TAG_USER_ID, user_id_bytes);
259
260 packet
261 }
262
263 pub fn to_armored(&self) -> String {
265 let packet = self.to_packet();
266 let encoded = STANDARD.encode(&packet);
267
268 let crc = crc24(&packet);
270 let crc_bytes = [(crc >> 16) as u8, (crc >> 8) as u8, crc as u8];
271 let crc_encoded = STANDARD.encode(crc_bytes);
272
273 let mut result = String::new();
274 result.push_str(PGP_PRIVATE_KEY_HEADER);
275 result.push_str("\n\n");
276
277 for chunk in encoded.as_bytes().chunks(64) {
279 result.push_str(std::str::from_utf8(chunk).unwrap());
280 result.push('\n');
281 }
282
283 result.push('=');
284 result.push_str(&crc_encoded);
285 result.push('\n');
286 result.push_str(PGP_PRIVATE_KEY_FOOTER);
287 result.push('\n');
288
289 result
290 }
291
292 pub fn to_ed25519(&self) -> OpenPgpResult<KeyPair> {
294 if self.secret_key.len() != 32 {
295 return Err(OpenPgpError::InvalidLength {
296 expected: 32,
297 actual: self.secret_key.len(),
298 });
299 }
300
301 let mut secret = [0u8; 32];
302 secret.copy_from_slice(&self.secret_key);
303
304 KeyPair::from_secret_key(&secret).map_err(|_| OpenPgpError::InvalidSecretKey)
305 }
306
307 pub fn public_key(&self) -> OpenPgpPublicKey {
309 OpenPgpPublicKey {
310 created: self.created,
311 key_material: self.public_key.clone(),
312 user_id: self.user_id.clone(),
313 }
314 }
315}
316
317fn add_packet_header(buf: &mut Vec<u8>, tag: u8, body: &[u8]) {
321 let len = body.len();
322
323 if len < 256 {
324 buf.push(0x80 | (tag << 2));
326 buf.push(len as u8);
327 } else if len < 65536 {
328 buf.push(0x80 | (tag << 2) | 0x01);
330 buf.extend_from_slice(&(len as u16).to_be_bytes());
331 } else {
332 buf.push(0x80 | (tag << 2) | 0x02);
334 buf.extend_from_slice(&(len as u32).to_be_bytes());
335 }
336
337 buf.extend_from_slice(body);
338}
339
340fn write_mpi(buf: &mut Vec<u8>, data: &[u8]) {
342 let bit_count = (data.len() * 8 + 7) as u16;
344 buf.extend_from_slice(&bit_count.to_be_bytes());
345
346 buf.push(0x40);
348 buf.extend_from_slice(data);
349}
350
351fn crc24(data: &[u8]) -> u32 {
353 const CRC24_INIT: u32 = 0xB704CE;
354 const CRC24_POLY: u32 = 0x1864CFB;
355
356 let mut crc = CRC24_INIT;
357
358 for &byte in data {
359 crc ^= (byte as u32) << 16;
360 for _ in 0..8 {
361 crc <<= 1;
362 if crc & 0x1000000 != 0 {
363 crc ^= CRC24_POLY;
364 }
365 }
366 }
367
368 crc & 0xFFFFFF
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_pgp_public_key_creation() {
377 let keypair = KeyPair::generate();
378 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
379
380 assert_eq!(pgp_pub.key_material.len(), 32);
381 assert_eq!(pgp_pub.user_id, "test@example.com");
382 }
383
384 #[test]
385 fn test_pgp_public_key_packet() {
386 let keypair = KeyPair::generate();
387 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
388
389 let packet = pgp_pub.to_packet();
390 assert!(!packet.is_empty());
391
392 assert_eq!(packet[0] & 0x80, 0x80); }
395
396 #[test]
397 fn test_pgp_public_key_armor() {
398 let keypair = KeyPair::generate();
399 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
400
401 let armored = pgp_pub.to_armored();
402 assert!(armored.contains(PGP_PUBLIC_KEY_HEADER));
403 assert!(armored.contains(PGP_PUBLIC_KEY_FOOTER));
404 assert!(armored.contains("=")); }
406
407 #[test]
408 fn test_pgp_secret_key_creation() {
409 let keypair = KeyPair::generate();
410 let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
411
412 assert_eq!(pgp_sec.public_key.len(), 32);
413 assert_eq!(pgp_sec.secret_key.len(), 32);
414 assert_eq!(pgp_sec.user_id, "test@example.com");
415 }
416
417 #[test]
418 fn test_pgp_secret_key_armor() {
419 let keypair = KeyPair::generate();
420 let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
421
422 let armored = pgp_sec.to_armored();
423 assert!(armored.contains(PGP_PRIVATE_KEY_HEADER));
424 assert!(armored.contains(PGP_PRIVATE_KEY_FOOTER));
425 }
426
427 #[test]
428 fn test_pgp_secret_to_keypair() {
429 let keypair = KeyPair::generate();
430 let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
431
432 let recovered = pgp_sec.to_ed25519().unwrap();
433 assert_eq!(recovered.public_key(), keypair.public_key());
434 assert_eq!(recovered.secret_key(), keypair.secret_key());
435 }
436
437 #[test]
438 fn test_pgp_public_from_secret() {
439 let keypair = KeyPair::generate();
440 let pgp_sec = OpenPgpSecretKey::from_ed25519(&keypair, "test@example.com");
441 let pgp_pub = pgp_sec.public_key();
442
443 assert_eq!(pgp_pub.key_material, pgp_sec.public_key);
444 assert_eq!(pgp_pub.user_id, pgp_sec.user_id);
445 }
446
447 #[test]
448 fn test_fingerprint() {
449 let keypair = KeyPair::generate();
450 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
451
452 let fp1 = pgp_pub.fingerprint();
453 let fp2 = pgp_pub.fingerprint();
454
455 assert_eq!(fp1, fp2);
457 assert_eq!(fp1.len(), 32);
458 }
459
460 #[test]
461 fn test_crc24() {
462 let data = b"hello";
463 let crc = crc24(data);
464
465 assert!(crc < 0x1000000);
467
468 assert_eq!(crc, crc24(data));
470 }
471
472 #[test]
473 fn test_armor_line_length() {
474 let keypair = KeyPair::generate();
475 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
476
477 let armored = pgp_pub.to_armored();
478
479 for line in armored.lines() {
481 if !line.contains("BEGIN") && !line.contains("END") && !line.starts_with('=') {
482 assert!(line.len() <= 64, "Line too long: {}", line.len());
483 }
484 }
485 }
486
487 #[test]
488 fn test_mpi_encoding() {
489 let mut buf = Vec::new();
490 let data = &[0x12, 0x34, 0x56, 0x78];
491
492 write_mpi(&mut buf, data);
493
494 assert_eq!(buf.len(), 2 + 1 + data.len());
496 assert_eq!(buf[2], 0x40); }
498
499 #[test]
500 fn test_packet_header() {
501 let mut buf = Vec::new();
502 let body = b"test";
503
504 add_packet_header(&mut buf, TAG_USER_ID, body);
505
506 assert_eq!(buf[0] & 0x80, 0x80);
508
509 assert_eq!((buf[0] >> 2) & 0x0F, TAG_USER_ID);
511 }
512
513 #[test]
514 fn test_serialization() {
515 let keypair = KeyPair::generate();
516 let pgp_pub = OpenPgpPublicKey::from_ed25519(&keypair.public_key(), "test@example.com");
517
518 let serialized = crate::codec::encode(&pgp_pub).unwrap();
519 let deserialized: OpenPgpPublicKey = crate::codec::decode(&serialized).unwrap();
520
521 assert_eq!(deserialized.key_material, pgp_pub.key_material);
522 assert_eq!(deserialized.user_id, pgp_pub.user_id);
523 }
524}