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