1#[cfg(feature = "gpg")]
2use pgp::{
3 composed::{MessageBuilder, SignedPublicKey, SignedPublicSubKey},
4 crypto::sym::SymmetricKeyAlgorithm,
5 errors::Error as PgpError,
6 packet::{KeyFlags, PublicKey, PublicSubkey},
7};
8#[cfg(feature = "gpg")]
9use rand::rngs::OsRng;
10
11use crate::crypto::CryptoKey;
12use crate::error::{GitCryptError, Result};
13
14pub struct GpgManager;
15
16impl GpgManager {
17 #[cfg(feature = "gpg")]
19 pub fn encrypt_key_for_recipient(
20 key: &CryptoKey,
21 recipient_fingerprint: &str,
22 ) -> Result<Vec<u8>> {
23 let signed_key = Self::get_public_key_from_keyring(recipient_fingerprint)?;
24 let recipient = select_recipient_key(&signed_key).ok_or_else(|| {
25 GitCryptError::Gpg(format!(
26 "No encryption-capable keys found for recipient: {recipient_fingerprint}"
27 ))
28 })?;
29
30 let mut rng = OsRng;
31 let mut builder = MessageBuilder::from_bytes("", key.as_bytes().to_vec())
32 .seipd_v1(&mut rng, SymmetricKeyAlgorithm::AES256);
33
34 match recipient {
35 RecipientKey::Primary(pk) => builder.encrypt_to_key(&mut rng, pk),
36 RecipientKey::Subkey(subkey) => builder.encrypt_to_key(&mut rng, subkey),
37 }
38 .map_err(map_pgp_err)?;
39
40 let mut encrypted = Vec::new();
41 builder
42 .to_writer(&mut rng, &mut encrypted)
43 .map_err(map_pgp_err)?;
44
45 Ok(encrypted)
46 }
47
48 #[cfg(not(feature = "gpg"))]
50 pub fn encrypt_key_for_recipient(
51 _key: &CryptoKey,
52 _recipient_fingerprint: &str,
53 ) -> Result<Vec<u8>> {
54 Err(GitCryptError::Gpg(
55 "GPG support not enabled. Rebuild with --features gpg".into(),
56 ))
57 }
58
59 #[cfg(feature = "gpg")]
61 #[allow(dead_code)]
62 pub fn decrypt_key(_encrypted_data: &[u8]) -> Result<CryptoKey> {
63 Err(GitCryptError::Gpg(
69 "GPG decryption via rPGP requires private key access - to be implemented".into(),
70 ))
71 }
72
73 #[cfg(not(feature = "gpg"))]
75 pub fn decrypt_key(_encrypted_data: &[u8]) -> Result<CryptoKey> {
76 Err(GitCryptError::Gpg(
77 "GPG support not enabled. Rebuild with --features gpg".into(),
78 ))
79 }
80
81 #[cfg(feature = "gpg")]
83 fn get_public_key_from_keyring(fingerprint: &str) -> Result<SignedPublicKey> {
84 Err(GitCryptError::Gpg(
90 format!("Certificate lookup not yet implemented for: {fingerprint}"),
91 ))
92 }
93
94 #[cfg(feature = "gpg")]
96 #[allow(dead_code)]
97 pub fn list_keys() -> Result<Vec<String>> {
98 Err(GitCryptError::Gpg("Key listing not yet implemented".into()))
100 }
101
102 #[cfg(not(feature = "gpg"))]
104 pub fn list_keys() -> Result<Vec<String>> {
105 Err(GitCryptError::Gpg(
106 "GPG support not enabled. Rebuild with --features gpg".into(),
107 ))
108 }
109}
110
111#[cfg(feature = "gpg")]
112enum RecipientKey<'a> {
113 Primary(&'a PublicKey),
114 Subkey(&'a PublicSubkey),
115}
116
117#[cfg(feature = "gpg")]
118fn select_recipient_key(signed_key: &SignedPublicKey) -> Option<RecipientKey<'_>> {
119 signed_key
120 .public_subkeys
121 .iter()
122 .find(|subkey| subkey_supports_encryption(subkey))
123 .map(|subkey| RecipientKey::Subkey(&subkey.key))
124 .or_else(|| Some(RecipientKey::Primary(&signed_key.primary_key)))
125}
126
127#[cfg(feature = "gpg")]
128fn subkey_supports_encryption(subkey: &SignedPublicSubKey) -> bool {
129 subkey
130 .signatures
131 .iter()
132 .any(|sig| key_flags_allow_encryption(&sig.key_flags()))
133}
134
135#[cfg(feature = "gpg")]
136fn key_flags_allow_encryption(flags: &KeyFlags) -> bool {
137 flags.encrypt_comms() || flags.encrypt_storage()
138}
139
140#[cfg(feature = "gpg")]
141fn map_pgp_err(err: PgpError) -> GitCryptError {
142 GitCryptError::Gpg(err.to_string())
143}