git_crypt/
gpg.rs

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    /// Encrypt a key for a GPG recipient using rPGP.
18    #[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    /// Encrypt a key for a GPG recipient (no GPG support compiled in)
49    #[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    /// Decrypt a GPG-encrypted key
60    #[cfg(feature = "gpg")]
61    #[allow(dead_code)]
62    pub fn decrypt_key(_encrypted_data: &[u8]) -> Result<CryptoKey> {
63        // This is a placeholder - actual implementation would need:
64        // 1. Access to private key material
65        // 2. Proper decryption with rPGP
66        // 3. Password handling for encrypted private keys
67
68        Err(GitCryptError::Gpg(
69            "GPG decryption via rPGP requires private key access - to be implemented".into(),
70        ))
71    }
72
73    /// Decrypt a GPG-encrypted key (no GPG support compiled in)
74    #[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    /// Get a public key from the keyring
82    #[cfg(feature = "gpg")]
83    fn get_public_key_from_keyring(fingerprint: &str) -> Result<SignedPublicKey> {
84        // This is a placeholder - actual implementation would:
85        // 1. Query the GPG keyring (via gpg, gpgme, or another interface)
86        // 2. Look up by fingerprint or key ID
87        // 3. Return the signed public key for encryption
88
89        Err(GitCryptError::Gpg(format!(
90            "Certificate lookup not yet implemented for: {fingerprint}"
91        )))
92    }
93
94    /// List available GPG keys
95    #[cfg(feature = "gpg")]
96    #[allow(dead_code)]
97    pub fn list_keys() -> Result<Vec<String>> {
98        // Placeholder for listing available GPG keys
99        Err(GitCryptError::Gpg("Key listing not yet implemented".into()))
100    }
101
102    /// List available GPG keys (no GPG support compiled in)
103    #[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}