1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// SPDX-FileCopyrightText: Wiktor Kwapisiewicz <wiktor@metacode.biz>
// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::convert::TryFrom;
use std::path::PathBuf;

use clap::Parser;

/// A model of the subset of the gpg CLI interface that we're supporting, to be called by git.
#[derive(Parser, Debug, Default)]
pub struct Args {
    #[clap(long)]
    pub verify: Option<PathBuf>,

    #[clap(long, short = 'b')]
    pub detach_sign: bool,

    #[clap(long, short = 's')]
    pub sign: bool,

    #[clap(long, short = 'a')]
    pub armor: bool,

    #[clap(long, short = 'u')]
    pub user_id: Option<String>,

    #[clap(long)]
    pub keyid_format: Option<String>,

    #[clap(long)]
    pub status_fd: Option<String>,

    pub file_to_verify: Option<String>,

    /// Path to cert-d storage (used for signature verification).
    /// If None, use the user's default (shared) PGP certificate directory.
    ///
    /// Also see <https://datatracker.ietf.org/doc/draft-nwjw-openpgp-cert-d/>
    #[clap(short, long, env = "PGP_CERT_D")]
    pub cert_store: Option<PathBuf>,

    /// Store User PIN for an OpenPGP card in openpgp-card-state
    #[clap(long)]
    pub store_card_pin: bool,
}

#[derive(Copy, Clone)]
pub enum Armor {
    NoArmor,
    Armor,
}

pub enum Mode {
    Verify {
        signature: PathBuf,
        cert_store: Option<PathBuf>,
    },
    Sign {
        id: String,
        armor: Armor,
        cert_store: Option<PathBuf>,
    },
    StoreCardPin,
}

impl TryFrom<Args> for Mode {
    type Error = String;

    fn try_from(value: Args) -> Result<Self, Self::Error> {
        if value.store_card_pin {
            Ok(Mode::StoreCardPin)
        } else if let Some(signature) = value.verify {
            if Some("-".into()) == value.file_to_verify {
                Ok(Mode::Verify {
                    signature,
                    cert_store: value.cert_store,
                })
            } else {
                Err("Verification of files other than stdin is unsupported. Use -".into())
            }
        } else if value.detach_sign {
            if let Some(user_id) = value.user_id {
                Ok(Mode::Sign {
                    id: user_id,
                    armor: if value.armor {
                        Armor::Armor
                    } else {
                        Armor::NoArmor
                    },
                    cert_store: value.cert_store,
                })
            } else {
                Err("The -u parameter is required. Please provide a hex-encoded signing subkey fingerprint with no spaces".into())
            }
        } else if value.sign {
            Err("Inline signing is not supported. Use --detach-sign".into())
        } else {
            Err("Unknown operation: only verify and detach-sign are supported.".into())
        }
    }
}