1 /*
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
19 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::{
20 ISecretkeeper::ISecretkeeper, SecretId::SecretId,
21 };
22 use anyhow::{anyhow, bail, Context, Result};
23 use authgraph_boringssl::BoringSha256;
24 use authgraph_core::traits::Sha256;
25 use clap::{Args, Parser, Subcommand};
26 use coset::CborSerializable;
27 use dice_policy_builder::{
28 policy_for_dice_chain, ConstraintSpec, ConstraintType, MissingAction, TargetEntry,
29 WILDCARD_FULL_ARRAY,
30 };
31
32 use secretkeeper_client::{dice::OwnedDiceArtifactsWithExplicitKey, SkSession};
33 use 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 };
41 use secretkeeper_test::{
42 dice_sample::make_explicit_owned_dice, AUTHORITY_HASH, CONFIG_DESC, MODE, SECURITY_VERSION,
43 SUBCOMPONENT_AUTHORITY_HASH, SUBCOMPONENT_DESCRIPTORS, SUBCOMPONENT_SECURITY_VERSION,
44 };
45 use 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)]
51 struct 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)]
71 enum 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)]
83 struct 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)]
93 struct 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)]
99 struct 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)]
105 struct DeleteAllArgs {
106 /// Confirm deletion of all secrets.
107 yes: bool,
108 }
109
110 const SECRETKEEPER_SERVICE: &str = "android.hardware.security.secretkeeper.ISecretkeeper";
111
112 /// Secretkeeper client information.
113 struct SkClient {
114 sk: binder::Strong<dyn ISecretkeeper>,
115 session: SkSession,
116 dice_artifacts: OwnedDiceArtifactsWithExplicitKey,
117 }
118
119 impl SkClient {
new(instance: &str, dice_artifacts: OwnedDiceArtifactsWithExplicitKey) -> Self120 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, None).unwrap();
124 Self { sk, session, dice_artifacts }
125 }
126
secret_management_request(&mut self, req_data: &[u8]) -> Result<Vec<u8>>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) on all nodes.
135 /// 2. `ExactMatch` on `MODE` (non-optional) on all nodes.
136 /// 3. `GreaterOrEqual` on `SECURITY_VERSION` (optional) on all nodes.
137 /// 4. The DiceChainEntry corresponding to "AVB" contains SubcomponentDescriptor, for each of those:
138 /// a) GreaterOrEqual on SECURITY_VERSION (Required)
139 // b) ExactMatch on AUTHORITY_HASH (Required).
sealing_policy(&self) -> Result<Vec<u8>>140 fn sealing_policy(&self) -> Result<Vec<u8>> {
141 let dice =
142 self.dice_artifacts.explicit_key_dice_chain().context("extract explicit DICE chain")?;
143
144 let constraint_spec = vec![
145 ConstraintSpec::new(
146 ConstraintType::ExactMatch,
147 vec![AUTHORITY_HASH],
148 MissingAction::Fail,
149 TargetEntry::All,
150 ),
151 ConstraintSpec::new(
152 ConstraintType::ExactMatch,
153 vec![MODE],
154 MissingAction::Fail,
155 TargetEntry::All,
156 ),
157 ConstraintSpec::new(
158 ConstraintType::GreaterOrEqual,
159 vec![CONFIG_DESC, SECURITY_VERSION],
160 MissingAction::Ignore,
161 TargetEntry::All,
162 ),
163 ConstraintSpec::new(
164 ConstraintType::GreaterOrEqual,
165 vec![
166 CONFIG_DESC,
167 SUBCOMPONENT_DESCRIPTORS,
168 WILDCARD_FULL_ARRAY,
169 SUBCOMPONENT_SECURITY_VERSION,
170 ],
171 MissingAction::Fail,
172 TargetEntry::ByName("AVB".to_string()),
173 ),
174 ConstraintSpec::new(
175 ConstraintType::ExactMatch,
176 vec![
177 CONFIG_DESC,
178 SUBCOMPONENT_DESCRIPTORS,
179 WILDCARD_FULL_ARRAY,
180 SUBCOMPONENT_AUTHORITY_HASH,
181 ],
182 MissingAction::Fail,
183 TargetEntry::ByName("AVB".to_string()),
184 ),
185 ];
186 policy_for_dice_chain(dice, constraint_spec)
187 .unwrap()
188 .to_vec()
189 .context("serialize DICE policy")
190 }
191
store(&mut self, id: &Id, secret: &Secret) -> Result<()>192 fn store(&mut self, id: &Id, secret: &Secret) -> Result<()> {
193 let store_request = StoreSecretRequest {
194 id: id.clone(),
195 secret: secret.clone(),
196 sealing_policy: self.sealing_policy().context("build sealing policy")?,
197 };
198 let store_request =
199 store_request.serialize_to_packet().to_vec().context("serialize StoreSecretRequest")?;
200
201 let store_response = self.secret_management_request(&store_request)?;
202 let store_response =
203 ResponsePacket::from_slice(&store_response).context("deserialize ResponsePacket")?;
204 let response_type = store_response.response_type().unwrap();
205 if response_type == ResponseType::Success {
206 Ok(())
207 } else {
208 let err = *SecretkeeperError::deserialize_from_packet(store_response).unwrap();
209 Err(anyhow!("STORE failed: {err:?}"))
210 }
211 }
212
get(&mut self, id: &Id) -> Result<Option<Secret>>213 fn get(&mut self, id: &Id) -> Result<Option<Secret>> {
214 let get_request = GetSecretRequest { id: id.clone(), updated_sealing_policy: None }
215 .serialize_to_packet()
216 .to_vec()
217 .context("serialize GetSecretRequest")?;
218
219 let get_response = self.secret_management_request(&get_request).context("secret mgmt")?;
220 let get_response =
221 ResponsePacket::from_slice(&get_response).context("deserialize ResponsePacket")?;
222
223 if get_response.response_type().unwrap() == ResponseType::Success {
224 let get_response = *GetSecretResponse::deserialize_from_packet(get_response).unwrap();
225 Ok(Some(Secret(get_response.secret.0)))
226 } else {
227 // Only expect a not-found failure.
228 let err = *SecretkeeperError::deserialize_from_packet(get_response).unwrap();
229 if err == SecretkeeperError::EntryNotFound {
230 Ok(None)
231 } else {
232 Err(anyhow!("GET failed: {err:?}"))
233 }
234 }
235 }
236
237 /// Helper method to delete secrets.
delete(&self, ids: &[&Id]) -> Result<()>238 fn delete(&self, ids: &[&Id]) -> Result<()> {
239 let ids: Vec<SecretId> = ids.iter().map(|id| SecretId { id: id.0 }).collect();
240 self.sk.deleteIds(&ids).context("deleteIds")
241 }
242
243 /// Helper method to delete everything.
delete_all(&self) -> Result<()>244 fn delete_all(&self) -> Result<()> {
245 self.sk.deleteAll().context("deleteAll")
246 }
247 }
248
249 /// Convert a string input into an `Id`. Input can be 64 bytes of hex, or a string
250 /// that will be hashed to give the `Id` value. Returns the `Id` and a display string.
string_to_id(s: &str, show_hex: bool) -> (Id, String)251 fn string_to_id(s: &str, show_hex: bool) -> (Id, String) {
252 if let Ok(data) = hex::decode(s) {
253 if data.len() == 64 {
254 // Assume something that parses as 64 bytes of hex is it.
255 return (Id(data.try_into().unwrap()), s.to_string().to_lowercase());
256 }
257 }
258 // Create a secret ID by repeating the SHA-256 hash of the string twice.
259 let hash = BoringSha256.compute_sha256(s.as_bytes()).unwrap();
260 let mut id = Id([0; 64]);
261 id.0[..32].copy_from_slice(&hash);
262 id.0[32..].copy_from_slice(&hash);
263 if show_hex {
264 let hex_id = hex::encode(&id.0);
265 (id, format!("'{s}' (as {hex_id})"))
266 } else {
267 (id, format!("'{s}'"))
268 }
269 }
270
271 /// Convert a string input into a `Secret`. Input can be 32 bytes of hex, or a short string
272 /// that will be encoded as the `Secret` value. Returns the `Secret` and a display string.
value_to_secret(s: &str, show_hex: bool) -> Result<(Secret, String)>273 fn value_to_secret(s: &str, show_hex: bool) -> Result<(Secret, String)> {
274 if let Ok(data) = hex::decode(s) {
275 if data.len() == 32 {
276 // Assume something that parses as 32 bytes of hex is it.
277 return Ok((Secret(data.try_into().unwrap()), s.to_string().to_lowercase()));
278 }
279 }
280 let data = s.as_bytes();
281 if data.len() > 31 {
282 return Err(anyhow!("secret too long"));
283 }
284 let mut secret = Secret([0; 32]);
285 secret.0[0] = data.len() as u8;
286 secret.0[1..1 + data.len()].copy_from_slice(data);
287 Ok(if show_hex {
288 let hex_secret = hex::encode(&secret.0);
289 (secret, format!("'{s}' (as {hex_secret})"))
290 } else {
291 (secret, format!("'{s}'"))
292 })
293 }
294
295 /// Convert a `Secret` into a displayable string. If the secret looks like an encoded
296 /// string, show that, otherwise show the value in hex.
secret_to_value_display(secret: &Secret, show_hex: bool) -> String297 fn secret_to_value_display(secret: &Secret, show_hex: bool) -> String {
298 let hex = hex::encode(&secret.0);
299 secret_to_value(secret)
300 .map(|s| if show_hex { format!("'{s}' (from {hex})") } else { format!("'{s}'") })
301 .unwrap_or_else(|_e| format!("{hex}"))
302 }
303
304 /// Attempt to convert a `Secret` back to a string.
secret_to_value(secret: &Secret) -> Result<String>305 fn secret_to_value(secret: &Secret) -> Result<String> {
306 let len = secret.0[0] as usize;
307 if len > 31 {
308 return Err(anyhow!("too long"));
309 }
310 std::str::from_utf8(&secret.0[1..1 + len]).map(|s| s.to_string()).context("not UTF-8 string")
311 }
312
main() -> Result<()>313 fn main() -> Result<()> {
314 let cli = Cli::parse();
315
316 // Figure out which Secretkeeper instance is desired, and connect to it.
317 let instance = if let Some(instance) = &cli.instance {
318 // Explicitly specified.
319 instance.clone()
320 } else {
321 // If there's only one instance, use that.
322 let instances: Vec<String> = binder::get_declared_instances(SECRETKEEPER_SERVICE)
323 .unwrap_or_default()
324 .into_iter()
325 .collect();
326 match instances.len() {
327 0 => bail!("No Secretkeeper instances available on device!"),
328 1 => instances[0].clone(),
329 _ => {
330 bail!(
331 concat!(
332 "Multiple Secretkeeper instances available on device: {}\n",
333 "Use --instance <instance> to specify one."
334 ),
335 instances.join(", ")
336 );
337 }
338 }
339 };
340 let dice = make_explicit_owned_dice(cli.dice_version);
341 let mut sk_client = SkClient::new(&instance, dice);
342
343 match cli.command {
344 Command::Get(args) => {
345 let (id, display_id) = string_to_id(&args.id, cli.hex);
346 print!("GET key {display_id}: ");
347 match sk_client.get(&id).context("GET") {
348 Ok(None) => println!("not found"),
349 Ok(Some(s)) => println!("{}", secret_to_value_display(&s, cli.hex)),
350 Err(e) => {
351 println!("failed!");
352 return Err(e);
353 }
354 }
355 }
356 Command::Store(args) => {
357 let (id, display_id) = string_to_id(&args.id, cli.hex);
358 let (secret, display_secret) = value_to_secret(&args.value, cli.hex)?;
359 println!("STORE key {display_id}: {display_secret}");
360 sk_client.store(&id, &secret).context("STORE")?;
361 }
362 Command::Delete(args) => {
363 let (id, display_id) = string_to_id(&args.id, cli.hex);
364 println!("DELETE key {display_id}");
365 sk_client.delete(&[&id]).context("DELETE")?;
366 }
367 Command::DeleteAll(args) => {
368 if !args.yes {
369 // Request confirmation.
370 println!("Confirm delete all secrets: [y/N]");
371 let _ = std::io::stdout().flush();
372 let mut input = String::new();
373 std::io::stdin().read_line(&mut input)?;
374 let c = input.chars().next();
375 if c != Some('y') && c != Some('Y') {
376 bail!("DELETE_ALL not confirmed");
377 }
378 }
379 println!("DELETE_ALL");
380 sk_client.delete_all().context("DELETE_ALL")?;
381 }
382 }
383 Ok(())
384 }
385