summaryrefslogtreecommitdiffstats
path: root/sq/src/commands/net.rs
blob: 8865af1e44c63c042bb5060192a6eb9e9f217c4b (plain)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Network services.

use anyhow::Context;

use sequoia_openpgp as openpgp;
use openpgp::{
    Result,
    KeyHandle,
    cert::{
        Cert,
        CertParser,
    },
    packet::{
        UserID,
    },
    parse::Parse,
    serialize::Serialize,
};
use sequoia_net as net;
use net::{
    KeyServer,
    wkd,
};

use crate::{
    Config,
    open_or_stdin,
    serialize_keyring,
};

use crate::sq_cli::KeyserverCommand;
use crate::sq_cli::KeyserverSubcommands;
use crate::sq_cli::WkdCommand;
use crate::sq_cli::WkdSubcommands;

pub fn dispatch_keyserver(config: Config, c: KeyserverCommand) -> Result<()> {
    let network_policy = c.policy.into();
    let mut ks = if let Some(uri) = c.server {
        KeyServer::new(network_policy, &uri)
    } else {
        KeyServer::keys_openpgp_org(network_policy)
    }.context("Malformed keyserver URI")?;

    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_io()
        .enable_time()
        .build()?;

    match c.subcommand {
         KeyserverSubcommands::Get(c) => {
            let query = c.query;

            let handle = query.parse::<KeyHandle>();

            if let Ok(handle) = handle {
                let cert = rt.block_on(ks.get(handle))
                    .context("Failed to retrieve cert")?;

                let mut output =
                    config.create_or_stdout_safe(c.output.as_deref())?;
                if !c.binary {
                    cert.armored().serialize(&mut output)
                } else {
                    cert.serialize(&mut output)
                }.context("Failed to serialize cert")?;
            } else if let Ok(Some(addr)) = UserID::from(query.as_str()).email() {
                let certs = rt.block_on(ks.search(addr))
                    .context("Failed to retrieve certs")?;

                let mut output =
                    config.create_or_stdout_safe(c.output.as_deref())?;
                serialize_keyring(&mut output, &certs, c.binary)?;
            } else {
                return Err(anyhow::anyhow!(
                    "Query must be a fingerprint, a keyid, \
                     or an email address: {:?}", query));
            }
        },
        KeyserverSubcommands::Send(c) => {
            let mut input = open_or_stdin(c.input.as_deref())?;
            let cert = Cert::from_reader(&mut input).
                context("Malformed key")?;

            rt.block_on(ks.send(&cert))
                .context("Failed to send key to server")?;
        },
    }

    Ok(())
}

pub fn dispatch_wkd(config: Config, c: WkdCommand) -> Result<()> {
    let network_policy: net::Policy = c.policy.into();

    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_io()
        .enable_time()
        .build()?;

    match c.subcommand {
        WkdSubcommands::Url(c) => {
            let email_address = c.email_address;
            let wkd_url = wkd::Url::from(email_address)?;
            let url = wkd_url.to_url(None)?;
            println!("{}", url);
        },
        WkdSubcommands::DirectUrl(c) => {
            let email_address = c.email_address;
            let wkd_url = wkd::Url::from(email_address)?;
            let url = wkd_url.to_url(wkd::Variant::Direct)?;
            println!("{}", url);
        },
        WkdSubcommands::Get(c) => {
            // Check that the policy allows https.
            network_policy.assert(net::Policy::Encrypted)?;

            let email_address = c.email_address;
            // XXX: EmailAddress could be created here to
            // check it's a valid email address, print the error to
            // stderr and exit.
            // Because it might be created a WkdServer struct, not
            // doing it for now.
            let certs = rt.block_on(wkd::get(&email_address))?;
            // ```text
            //     The HTTP GET method MUST return the binary representation of the
            //     OpenPGP key for the given mail address.
            // [draft-koch]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-07
            // ```
            // But to keep the parallelism with `store export` and `keyserver get`,
            // The output is armored if not `--binary` option is given.
            let mut output =
                config.create_or_stdout_safe(c.output.as_deref())?;
            serialize_keyring(&mut output, &certs, c.binary)?;
        },
        WkdSubcommands::Generate(c) => {
            let domain = c.domain;
            let skip = c.skip;
            let f = open_or_stdin(c.input.as_deref())?;
            let base_path = c.base_directory;
            let variant = if c.direct_method {
                wkd::Variant::Direct
            } else {
                wkd::Variant::Advanced
            };
            let parser = CertParser::from_reader(f)?;
            let policy = &config.policy;
            let certs: Vec<Cert> = parser.filter_map(|cert| cert.ok())
                .collect();
            for cert in certs {
                let vc = match cert.with_policy(policy, None) {
                    Ok(vc) => vc,
                    e @ Err(_) if !skip => e?,
                    _ => continue,
                };
                if wkd::cert_contains_domain_userid(&domain, &vc) {
                    wkd::insert(&base_path, &domain, variant, &vc)
                        .context(format!("Failed to generate the WKD in \
                        {}.", base_path))?;
                } else if !skip {
                    return Err(openpgp::Error::InvalidArgument(
                        format!("Certificate {} does not contain User IDs in domain {}.",
                        vc.fingerprint(), domain)
                    ).into());
                }
            }
        },
    }

    Ok(())
}