dcap_artifact_retrieval/
cli.rs1use 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
22const 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 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}