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