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