dcap_artifact_retrieval/
cli.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
7
8use std::path::{Path, PathBuf};
9
10use clap::clap_app;
11use pcs::{PckID, DcapArtifactIssuer};
12use reqwest::Url;
13use rustc_serialize::hex::ToHex;
14use serde::de::{value, IntoDeserializer};
15use serde::Deserialize;
16
17use crate::{
18    AzureProvisioningClientBuilder, Error, IntelProvisioningClientBuilder,
19    PccsProvisioningClientBuilder, PcsVersion, ProvisioningClient, StatusCode,
20};
21
22// NOTE: unfortunately these default values need to be repeated in arg
23// descriptions in `main`. Please keep them in sync.
24const DEFAULT_ORIGIN: &'static str = "intel";
25const DEFAULT_API_VERSION: &'static str = "4";
26
27#[derive(Debug, Deserialize, Copy, Clone, Eq, PartialEq, Hash)]
28#[serde(rename_all = "kebab-case")]
29enum Origin {
30    Intel,
31    Azure,
32    Pccs,
33}
34
35fn str_deserialize(s: &str) -> value::StrDeserializer<value::Error> {
36    s.into_deserializer()
37}
38
39fn parse_origin(p: &str) -> Result<Origin, String> {
40    Origin::deserialize(str_deserialize(p)).map_err(|e| e.to_string())
41}
42
43fn download_dcap_artifacts(
44    prov_client: &dyn ProvisioningClient,
45    pckid_file: &str,
46    output_dir: &str,
47    verbose: bool,
48) -> Result<(), Error> {
49    for (idx, pckid) in PckID::parse_file(&PathBuf::from(&pckid_file).as_path())?
50        .iter()
51        .enumerate()
52    {
53        let enc_ppid = &pckid.enc_ppid.as_slice();
54        if verbose {
55            println!("==[ entry {} ]==", idx);
56            println!(" Info:");
57            println!(
58                "   Encr. PPID:  {}",
59                enc_ppid.to_hex(),
60            );
61            println!("   pce_id:      {}", &&pckid.pce_id.to_le_bytes().to_hex());
62            println!("   cpu svn:     {}", pckid.cpu_svn.as_slice().to_hex());
63            println!(
64                "   pce isvsvn:  {}",
65                pckid.pce_isvsvn.to_le_bytes().to_hex()
66            );
67            println!("   qe_id:       {}", pckid.qe_id.as_slice().to_hex());
68            println!(" Storing artifacts:");
69        }
70
71        // Fetch pckcerts, note that Azure and PCCS do not support this API,
72        // instead we mimic it using pckcert API.
73        let pckcerts = prov_client.pckcerts_with_fallback(&pckid)?;
74
75        let pckcerts_file = pckcerts.store(output_dir, pckid.qe_id.as_slice())?;
76
77        if verbose {
78            println!("   pckcerts:    {}", pckcerts_file);
79        }
80
81        let fmspc = pckcerts.fmspc()?;
82        let evaluation_data_numbers = prov_client
83            .tcb_evaluation_data_numbers()?;
84
85        let file = evaluation_data_numbers.write_to_file(output_dir)?;
86        if verbose {
87            println!("   tcb evaluation data numbers:    {}\n", file);
88        }
89
90        for number in evaluation_data_numbers.evaluation_data_numbers()?.numbers() {
91            let tcb_info = prov_client
92                .tcbinfo(&fmspc, Some(number.number()));
93
94            match tcb_info {
95                Ok(tcb_info) => {
96                    let file = tcb_info.store(output_dir)?;
97                    if verbose {
98                        println!("   tcb info:    {}", file);
99                    }
100                },
101                Err(Error::PCSError(StatusCode::Gone, _)) => {
102                    if verbose {
103                        println!("   tcb info:    Gone (silently ignoring)");
104                    }
105                }
106                Err(e) => {
107                    return Err(e)?;
108                },
109            }
110
111
112            let qe_identity = prov_client
113                .qe_identity(Some(number.number()));
114
115            match qe_identity {
116                Ok(qe_identity) => {
117                    let file = qe_identity.write_to_file(output_dir)?;
118                    if verbose {
119                        println!("   qe identity: {}\n", file);
120                    }
121                }
122                Err(Error::PCSError(StatusCode::Gone, _)) => {
123                    if verbose {
124                        println!("   qe identity: Gone (silently ignoring)\n");
125                    }
126                }
127                Err(e) => {
128                    return Err(e)?;
129                },
130            }
131        }
132    }
133    let pckcrl = prov_client
134        .pckcrl(DcapArtifactIssuer::PCKProcessorCA)
135        .and_then(|crl| crl.write_to_file_as(output_dir, DcapArtifactIssuer::PCKProcessorCA).map_err(|e| e.into()))?;
136    if verbose {
137        println!("==[ generic ]==");
138        println!("   PCKProcessorCA Crl:      {}", pckcrl);
139    }
140
141    let pckcrl = prov_client
142        .pckcrl(DcapArtifactIssuer::PCKPlatformCA)
143        .and_then(|crl| crl.write_to_file_as(output_dir, DcapArtifactIssuer::PCKPlatformCA).map_err(|e| e.into()))?;
144    if verbose {
145        println!("   PCKPlatformCA Crl:      {}", pckcrl);
146    }
147    Ok(())
148}
149
150pub fn main() {
151    fn is_directory(directory_path: String) -> Result<(), String> {
152        let path = Path::new(&directory_path);
153
154        match (path.exists(), path.is_dir()) {
155            (true, true) => return Ok(()),
156            (true, false) => {
157                return Err(format!(
158                    "Path {} exists, but is not a directory",
159                    directory_path
160                ))
161            }
162            (false, _) => return Err(format!("Directory {} does not exists", directory_path)),
163        };
164    }
165
166    fn is_file(filename: String) -> Result<(), String> {
167        if Path::new(&filename).exists() {
168            Ok(())
169        } else {
170            Err(format!("Cannot open {}", filename))
171        }
172    }
173
174    fn parse_pcs_version(value: &str) -> Result<PcsVersion, String> {
175        match value {
176            "3" => Ok(PcsVersion::V3),
177            "4" => Ok(PcsVersion::V4),
178            _ => Err(format!("Expected 3 or 4, found `{}`", value)),
179        }
180    }
181
182    fn is_url(value: String) -> Result<(), String> {
183        let url = Url::parse(&value)
184            .map_err(|e| format!("cannot parse `{}` as a valid URL: {}", value, e))?;
185
186        if url.scheme() != "http" && url.scheme() != "https" {
187            return Err(format!(
188                "Expected an http or https URL found: `{}`",
189                url.scheme()
190            ));
191        }
192        Ok(())
193    }
194
195    let matches = clap::clap_app!(("DCAP Artifact Retrieval Tool") =>
196        (author: "Fortanix")
197        (about: "Fortanix ecdsa artifact retrieval tool for DCAP attestation")
198            (
199                @arg ORIGIN: --("origin") +takes_value
200                validator(|s| parse_origin(s.as_str()).map(|_| ()))
201                "Origin for downloading artifacts. Options are: \"intel\", \"azure\" and \"pccs\". \
202                 Note that Azure does not provide access to all artifacts. Intel will be contacted as a fallback. \
203                 Default: \"intel\"."
204            )
205            (
206                @arg PCKID_FILE: --("pckid-file") +takes_value +required requires("PCKID_FILE")
207                validator(is_file)
208                "File describing the PCK identity (outputted by PCKIDRetrievalTool)."
209            )
210            (
211                @arg OUTPUT_DIR: --("output-dir") +takes_value +required requires("OUTPUT_DIR")
212                validator(is_directory)
213                "Destination folder for storing downloaded artifacts."
214            )
215            (
216                @arg API_VERSION: --("api-version") +takes_value
217                validator(|s| parse_pcs_version(s.as_str()).map(|_| ()))
218                "API version for provisioning service, supported values are 3 and 4. Default: \"4\"."
219            )
220            (
221                @arg API_KEY: --("api-key") +takes_value
222                "API key for authenticating with Intel provisioning service."
223            )
224            (
225                @arg PCCS_URL: --("pccs-url") +takes_value required_if("ORIGIN", "pccs")
226                validator(is_url)
227                "PCCS base URL. This is relevant only when using `--origin pccs`."
228            )
229            (
230                @arg INSECURE: -k --insecure
231                "Do not verify that server's hostname matches their TLS certificate and accept self-signed certificates. This is insecure."
232            )
233            (
234                @arg VERBOSE: -v --verbose
235                "Print additional information abut files that are fetched."
236            )
237        )
238        .get_matches();
239
240    let result = match (
241        matches.value_of("PCKID_FILE"),
242        matches.value_of("OUTPUT_DIR"),
243    ) {
244        (Some(pckid_file), Some(output_dir)) => {
245            let verboseness = matches.occurrences_of("VERBOSE");
246            let api_version = parse_pcs_version(matches.value_of("API_VERSION").unwrap_or(DEFAULT_API_VERSION))
247                .expect("validated");
248
249            let origin =
250                parse_origin(matches.value_of("ORIGIN").unwrap_or(DEFAULT_ORIGIN)).expect("validated");
251
252            let fetcher = match matches.is_present("INSECURE") {
253                false => crate::reqwest_client(),
254                true => crate::reqwest_client_insecure_tls(),
255            };
256
257            let client: Box<dyn ProvisioningClient> = match origin {
258                Origin::Intel => {
259                    let mut client_builder = IntelProvisioningClientBuilder::new(api_version);
260                    if let Some(api_key) = matches.value_of("API_KEY") {
261                        client_builder.set_api_key(api_key.into());
262                    }
263                    Box::new(client_builder.build(fetcher))
264                }
265                Origin::Azure => {
266                    let client_builder = AzureProvisioningClientBuilder::new(api_version);
267                    Box::new(client_builder.build(fetcher))
268                }
269                Origin::Pccs => {
270                    let pccs_url = matches.value_of("PCCS_URL").expect("validated").to_owned();
271                    let client_builder = PccsProvisioningClientBuilder::new(api_version, pccs_url);
272                    Box::new(client_builder.build(fetcher))
273                }
274            };
275            download_dcap_artifacts(&*client, pckid_file, output_dir, 0 < verboseness)
276        }
277        _ => unreachable!("validated"),
278    };
279
280    match result {
281        Ok(()) => {}
282        Err(Error::PCSError(StatusCode::NotFound, _)) => {
283            eprintln!("Error: Artifact not found. Perhaps specify a different origin?");
284            std::process::exit(1);
285        }
286        Err(err) => {
287            eprintln!("Error downloading artifact: {}", err);
288            std::process::exit(1);
289        }
290    }
291}