blob: d02bfe62facdc3ede7d2461559c3d3e2aca3bea1 [file] [log] [blame]
David Drysdaleb95093d2024-01-11 19:26:57 +00001/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Command line test tool for interacting with Secretkeeper.
18
19use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::{
20 ISecretkeeper::ISecretkeeper, SecretId::SecretId,
21};
22use anyhow::{anyhow, bail, Context, Result};
23use authgraph_boringssl::BoringSha256;
24use authgraph_core::traits::Sha256;
25use clap::{Args, Parser, Subcommand};
26use coset::CborSerializable;
Shikha Panwar9e15e5e2024-01-25 11:16:28 +000027use dice_policy_builder::{
28 policy_for_dice_chain, CertIndex, ConstraintSpec, ConstraintType, MissingAction,
29 WILDCARD_FULL_ARRAY,
30};
Shikha Panwar9eab79b2024-01-19 11:05:11 +000031
David Drysdaleb95093d2024-01-11 19:26:57 +000032use secretkeeper_client::{dice::OwnedDiceArtifactsWithExplicitKey, SkSession};
33use secretkeeper_comm::data_types::{
34 error::SecretkeeperError,
35 packet::{ResponsePacket, ResponseType},
36 request::Request,
37 request_response_impl::{GetSecretRequest, GetSecretResponse, StoreSecretRequest},
38 response::Response,
39 {Id, Secret},
40};
41use secretkeeper_test::{
42 dice_sample::make_explicit_owned_dice, AUTHORITY_HASH, CONFIG_DESC, MODE, SECURITY_VERSION,
Shikha Panwar9e15e5e2024-01-25 11:16:28 +000043 SUBCOMPONENT_AUTHORITY_HASH, SUBCOMPONENT_DESCRIPTORS, SUBCOMPONENT_SECURITY_VERSION,
David Drysdaleb95093d2024-01-11 19:26:57 +000044};
45use std::io::Write;
46
47#[derive(Parser, Debug)]
48#[command(about = "Interact with Secretkeeper HAL")]
49#[command(version = "0.1")]
50#[command(propagate_version = true)]
51struct Cli {
52 #[command(subcommand)]
53 command: Command,
54
55 /// Secretkeeper instance to connect to.
56 #[arg(long, short)]
57 instance: Option<String>,
58
59 /// Security version in leaf DICE node.
60 #[clap(default_value_t = 100)]
61 #[arg(long, short = 'v')]
62 dice_version: u64,
63
64 /// Show hex versions of secrets and their IDs.
65 #[clap(default_value_t = false)]
66 #[arg(long, short = 'v')]
67 hex: bool,
68}
69
70#[derive(Subcommand, Debug)]
71enum Command {
72 /// Store a secret value.
73 Store(StoreArgs),
74 /// Get a secret value.
75 Get(GetArgs),
76 /// Delete a secret value.
77 Delete(DeleteArgs),
78 /// Delete all secret values.
79 DeleteAll(DeleteAllArgs),
80}
81
82#[derive(Args, Debug)]
83struct StoreArgs {
84 /// Identifier for the secret, as either a short (< 32 byte) string, or as 32 bytes of hex.
85 id: String,
86 /// Value to use as the secret value. If specified as 32 bytes of hex, the decoded value
87 /// will be used as-is; otherwise, a string (less than 31 bytes in length) will be encoded
88 /// as the secret.
89 value: String,
90}
91
92#[derive(Args, Debug)]
93struct GetArgs {
94 /// Identifier for the secret, as either a short (< 32 byte) string, or as 32 bytes of hex.
95 id: String,
96}
97
98#[derive(Args, Debug)]
99struct DeleteArgs {
100 /// Identifier for the secret, as either a short (< 32 byte) string, or as 32 bytes of hex.
101 id: String,
102}
103
104#[derive(Args, Debug)]
105struct DeleteAllArgs {
106 /// Confirm deletion of all secrets.
107 yes: bool,
108}
109
110const SECRETKEEPER_SERVICE: &str = "android.hardware.security.secretkeeper.ISecretkeeper";
111
112/// Secretkeeper client information.
113struct SkClient {
114 sk: binder::Strong<dyn ISecretkeeper>,
115 session: SkSession,
116 dice_artifacts: OwnedDiceArtifactsWithExplicitKey,
117}
118
119impl SkClient {
120 fn new(instance: &str, dice_artifacts: OwnedDiceArtifactsWithExplicitKey) -> Self {
121 let sk: binder::Strong<dyn ISecretkeeper> =
122 binder::get_interface(&format!("{SECRETKEEPER_SERVICE}/{instance}")).unwrap();
123 let session = SkSession::new(sk.clone(), &dice_artifacts).unwrap();
124 Self { sk, session, dice_artifacts }
125 }
126
127 fn secret_management_request(&mut self, req_data: &[u8]) -> Result<Vec<u8>> {
128 self.session
129 .secret_management_request(req_data)
130 .map_err(|e| anyhow!("secret management: {e:?}"))
131 }
132
133 /// Construct a sealing policy on the DICE chain with constraints:
134 /// 1. `ExactMatch` on `AUTHORITY_HASH` (non-optional).
135 /// 2. `ExactMatch` on `MODE` (non-optional).
136 /// 3. `GreaterOrEqual` on `SECURITY_VERSION` (optional).
137 fn sealing_policy(&self) -> Result<Vec<u8>> {
138 let dice =
139 self.dice_artifacts.explicit_key_dice_chain().context("extract explicit DICE chain")?;
140
141 let constraint_spec = [
142 ConstraintSpec::new(
143 ConstraintType::ExactMatch,
144 vec![AUTHORITY_HASH],
145 MissingAction::Fail,
Shikha Panwar9e15e5e2024-01-25 11:16:28 +0000146 CertIndex::All,
David Drysdaleb95093d2024-01-11 19:26:57 +0000147 ),
Shikha Panwar9e15e5e2024-01-25 11:16:28 +0000148 ConstraintSpec::new(
149 ConstraintType::ExactMatch,
150 vec![MODE],
151 MissingAction::Fail,
152 CertIndex::All,
153 ),
David Drysdaleb95093d2024-01-11 19:26:57 +0000154 ConstraintSpec::new(
155 ConstraintType::GreaterOrEqual,
156 vec![CONFIG_DESC, SECURITY_VERSION],
157 MissingAction::Ignore,
Shikha Panwar9e15e5e2024-01-25 11:16:28 +0000158 CertIndex::All,
159 ),
160 // Constraints on sub components in the second last DiceChainEntry
161 ConstraintSpec::new(
162 ConstraintType::GreaterOrEqual,
163 vec![
164 CONFIG_DESC,
165 SUBCOMPONENT_DESCRIPTORS,
166 WILDCARD_FULL_ARRAY,
167 SUBCOMPONENT_SECURITY_VERSION,
168 ],
169 MissingAction::Fail,
170 CertIndex::FromEnd(1),
171 ),
172 ConstraintSpec::new(
173 ConstraintType::ExactMatch,
174 vec![
175 CONFIG_DESC,
176 SUBCOMPONENT_DESCRIPTORS,
177 WILDCARD_FULL_ARRAY,
178 SUBCOMPONENT_AUTHORITY_HASH,
179 ],
180 MissingAction::Fail,
181 CertIndex::FromEnd(1),
David Drysdaleb95093d2024-01-11 19:26:57 +0000182 ),
183 ];
Shikha Panwar9eab79b2024-01-19 11:05:11 +0000184 policy_for_dice_chain(dice, &constraint_spec)
David Drysdaleb95093d2024-01-11 19:26:57 +0000185 .unwrap()
186 .to_vec()
187 .context("serialize DICE policy")
188 }
189
190 fn store(&mut self, id: &Id, secret: &Secret) -> Result<()> {
191 let store_request = StoreSecretRequest {
192 id: id.clone(),
193 secret: secret.clone(),
194 sealing_policy: self.sealing_policy().context("build sealing policy")?,
195 };
196 let store_request =
197 store_request.serialize_to_packet().to_vec().context("serialize StoreSecretRequest")?;
198
199 let store_response = self.secret_management_request(&store_request)?;
200 let store_response =
201 ResponsePacket::from_slice(&store_response).context("deserialize ResponsePacket")?;
202 let response_type = store_response.response_type().unwrap();
203 if response_type == ResponseType::Success {
204 Ok(())
205 } else {
206 let err = *SecretkeeperError::deserialize_from_packet(store_response).unwrap();
207 Err(anyhow!("STORE failed: {err:?}"))
208 }
209 }
210
211 fn get(&mut self, id: &Id) -> Result<Option<Secret>> {
212 let get_request = GetSecretRequest { id: id.clone(), updated_sealing_policy: None }
213 .serialize_to_packet()
214 .to_vec()
215 .context("serialize GetSecretRequest")?;
216
217 let get_response = self.secret_management_request(&get_request).context("secret mgmt")?;
218 let get_response =
219 ResponsePacket::from_slice(&get_response).context("deserialize ResponsePacket")?;
220
221 if get_response.response_type().unwrap() == ResponseType::Success {
222 let get_response = *GetSecretResponse::deserialize_from_packet(get_response).unwrap();
223 Ok(Some(Secret(get_response.secret.0)))
224 } else {
225 // Only expect a not-found failure.
226 let err = *SecretkeeperError::deserialize_from_packet(get_response).unwrap();
227 if err == SecretkeeperError::EntryNotFound {
228 Ok(None)
229 } else {
230 Err(anyhow!("GET failed: {err:?}"))
231 }
232 }
233 }
234
235 /// Helper method to delete secrets.
236 fn delete(&self, ids: &[&Id]) -> Result<()> {
237 let ids: Vec<SecretId> = ids.iter().map(|id| SecretId { id: id.0 }).collect();
238 self.sk.deleteIds(&ids).context("deleteIds")
239 }
240
241 /// Helper method to delete everything.
242 fn delete_all(&self) -> Result<()> {
243 self.sk.deleteAll().context("deleteAll")
244 }
245}
246
247/// Convert a string input into an `Id`. Input can be 64 bytes of hex, or a string
248/// that will be hashed to give the `Id` value. Returns the `Id` and a display string.
249fn string_to_id(s: &str, show_hex: bool) -> (Id, String) {
250 if let Ok(data) = hex::decode(s) {
251 if data.len() == 64 {
252 // Assume something that parses as 64 bytes of hex is it.
253 return (Id(data.try_into().unwrap()), s.to_string().to_lowercase());
254 }
255 }
256 // Create a secret ID by repeating the SHA-256 hash of the string twice.
257 let hash = BoringSha256.compute_sha256(s.as_bytes()).unwrap();
258 let mut id = Id([0; 64]);
259 id.0[..32].copy_from_slice(&hash);
260 id.0[32..].copy_from_slice(&hash);
261 if show_hex {
262 let hex_id = hex::encode(&id.0);
263 (id, format!("'{s}' (as {hex_id})"))
264 } else {
265 (id, format!("'{s}'"))
266 }
267}
268
269/// Convert a string input into a `Secret`. Input can be 32 bytes of hex, or a short string
270/// that will be encoded as the `Secret` value. Returns the `Secret` and a display string.
271fn value_to_secret(s: &str, show_hex: bool) -> Result<(Secret, String)> {
272 if let Ok(data) = hex::decode(s) {
273 if data.len() == 32 {
274 // Assume something that parses as 32 bytes of hex is it.
275 return Ok((Secret(data.try_into().unwrap()), s.to_string().to_lowercase()));
276 }
277 }
278 let data = s.as_bytes();
279 if data.len() > 31 {
280 return Err(anyhow!("secret too long"));
281 }
282 let mut secret = Secret([0; 32]);
283 secret.0[0] = data.len() as u8;
284 secret.0[1..1 + data.len()].copy_from_slice(data);
285 Ok(if show_hex {
286 let hex_secret = hex::encode(&secret.0);
287 (secret, format!("'{s}' (as {hex_secret})"))
288 } else {
289 (secret, format!("'{s}'"))
290 })
291}
292
293/// Convert a `Secret` into a displayable string. If the secret looks like an encoded
294/// string, show that, otherwise show the value in hex.
295fn secret_to_value_display(secret: &Secret, show_hex: bool) -> String {
296 let hex = hex::encode(&secret.0);
297 secret_to_value(secret)
298 .map(|s| if show_hex { format!("'{s}' (from {hex})") } else { format!("'{s}'") })
299 .unwrap_or_else(|_e| format!("{hex}"))
300}
301
302/// Attempt to convert a `Secret` back to a string.
303fn secret_to_value(secret: &Secret) -> Result<String> {
304 let len = secret.0[0] as usize;
305 if len > 31 {
306 return Err(anyhow!("too long"));
307 }
308 std::str::from_utf8(&secret.0[1..1 + len]).map(|s| s.to_string()).context("not UTF-8 string")
309}
310
311fn main() -> Result<()> {
312 let cli = Cli::parse();
313
314 // Figure out which Secretkeeper instance is desired, and connect to it.
315 let instance = if let Some(instance) = &cli.instance {
316 // Explicitly specified.
317 instance.clone()
318 } else {
319 // If there's only one instance, use that.
320 let instances: Vec<String> = binder::get_declared_instances(SECRETKEEPER_SERVICE)
321 .unwrap_or_default()
322 .into_iter()
323 .collect();
324 match instances.len() {
325 0 => bail!("No Secretkeeper instances available on device!"),
326 1 => instances[0].clone(),
327 _ => {
328 bail!(
329 concat!(
330 "Multiple Secretkeeper instances available on device: {}\n",
331 "Use --instance <instance> to specify one."
332 ),
333 instances.join(", ")
334 );
335 }
336 }
337 };
338 let dice = make_explicit_owned_dice(cli.dice_version);
339 let mut sk_client = SkClient::new(&instance, dice);
340
341 match cli.command {
342 Command::Get(args) => {
343 let (id, display_id) = string_to_id(&args.id, cli.hex);
344 print!("GET key {display_id}: ");
345 match sk_client.get(&id).context("GET") {
346 Ok(None) => println!("not found"),
347 Ok(Some(s)) => println!("{}", secret_to_value_display(&s, cli.hex)),
348 Err(e) => {
349 println!("failed!");
350 return Err(e);
351 }
352 }
353 }
354 Command::Store(args) => {
355 let (id, display_id) = string_to_id(&args.id, cli.hex);
356 let (secret, display_secret) = value_to_secret(&args.value, cli.hex)?;
357 println!("STORE key {display_id}: {display_secret}");
358 sk_client.store(&id, &secret).context("STORE")?;
359 }
360 Command::Delete(args) => {
361 let (id, display_id) = string_to_id(&args.id, cli.hex);
362 println!("DELETE key {display_id}");
363 sk_client.delete(&[&id]).context("DELETE")?;
364 }
365 Command::DeleteAll(args) => {
366 if !args.yes {
367 // Request confirmation.
368 println!("Confirm delete all secrets: [y/N]");
369 let _ = std::io::stdout().flush();
370 let mut input = String::new();
371 std::io::stdin().read_line(&mut input)?;
372 let c = input.chars().next();
373 if c != Some('y') && c != Some('Y') {
374 bail!("DELETE_ALL not confirmed");
375 }
376 }
377 println!("DELETE_ALL");
378 sk_client.delete_all().context("DELETE_ALL")?;
379 }
380 }
381 Ok(())
382}