1 //! A tool for handling data related to the hardware root-of-trust.
2
3 use anyhow::{bail, Result};
4 use clap::{Parser, Subcommand, ValueEnum};
5 use hwtrust::dice;
6 use hwtrust::dice::ChainForm;
7 use hwtrust::rkp;
8 use hwtrust::session::{Options, RkpInstance, Session};
9 use std::io::BufRead;
10 use std::{fs, io};
11
12 #[derive(Parser)]
13 /// A tool for handling data related to the hardware root-of-trust
14 #[clap(name = "hwtrust")]
15 struct Args {
16 #[clap(subcommand)]
17 action: Action,
18
19 /// Verbose output, including parsed data structures.
20 #[clap(long)]
21 verbose: bool,
22
23 /// The VSR version to validate against. If omitted, the set of rules that are used have no
24 /// compromises or workarounds and new implementations should validate against them as it will
25 /// be the basis for future VSR versions.
26 #[clap(long, value_enum)]
27 vsr: Option<VsrVersion>,
28 }
29
30 #[derive(Subcommand)]
31 enum Action {
32 /// Deprecated alias of dice-chain
33 VerifyDiceChain(DiceChainArgs),
34 DiceChain(DiceChainArgs),
35 FactoryCsr(FactoryCsrArgs),
36 Csr(CsrArgs),
37 }
38
39 #[derive(Parser)]
40 /// Verify that a DICE chain is well-formed
41 ///
42 /// DICE chains are expected to follow the specification of the RKP HAL [1] which is based on the
43 /// Open Profile for DICE [2].
44 ///
45 /// [1] -- https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
46 /// [2] -- https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
47 struct DiceChainArgs {
48 /// Path to a file containing a DICE chain.
49 chain: String,
50 /// Allow non-normal DICE chain modes.
51 #[clap(long)]
52 allow_any_mode: bool,
53 /// Validate the chain against the requirements of a specific RKP instance.
54 /// If not specified, the default RKP instance is used.
55 #[clap(value_enum, long, default_value = "default")]
56 rkp_instance: RkpInstance,
57 }
58
59 #[derive(Parser)]
60 /// Verify a CSR generated by the rkp_factory_extraction_tool
61 ///
62 /// "v1" CSRs are also decrypted using the factory EEK.
63 struct FactoryCsrArgs {
64 /// Path to a file containing one or more CSRs, in the "csr+json" format as defined by
65 /// rkp_factory_extraction_tool. Each line is interpreted as a separate JSON blob containing
66 /// a base64-encoded CSR.
67 csr_file: String,
68 /// Allow non-normal DICE chain modes.
69 #[clap(long)]
70 allow_any_mode: bool,
71 }
72
73 #[derive(Parser)]
74 /// Parse and verify a request payload that is suitable for the RKP server's SignCertificates API.
75 /// In HALv3, this is the output of generateCertificateRequestV2. For previous HAL versions,
76 /// the CSR is constructed by the remote provisioning service client, but is constructed from the
77 /// outputs of generateCertificateRequest.
78 struct CsrArgs {
79 /// Path to a file containing a single CSR, encoded as CBOR.
80 csr_file: String,
81 /// Allow non-normal DICE chain modes.
82 #[clap(long)]
83 allow_any_mode: bool,
84 /// Validate the chain against the requirements of a specific RKP instance.
85 /// If not specified, the default RKP instance is used.
86 #[clap(value_enum, long, default_value = "default")]
87 rkp_instance: RkpInstance,
88 }
89
90 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
91 enum VsrVersion {
92 /// VSR 13 / Android T / 2022
93 Vsr13,
94 /// VSR 14 / Android U / 2023
95 Vsr14,
96 /// VSR 15 / Android V / 2024
97 Vsr15,
98 /// VSR 16 / Android W / 2025
99 Vsr16,
100 }
101
session_from_vsr(vsr: Option<VsrVersion>) -> Session102 fn session_from_vsr(vsr: Option<VsrVersion>) -> Session {
103 Session {
104 options: match vsr {
105 Some(VsrVersion::Vsr13) => Options::vsr13(),
106 Some(VsrVersion::Vsr14) => Options::vsr14(),
107 Some(VsrVersion::Vsr15) => Options::vsr15(),
108 Some(VsrVersion::Vsr16) => {
109 println!();
110 println!();
111 println!(" ********************************************************************");
112 println!(" ! The selected VSR is not finalized and is subject to change. !");
113 println!(" ! Please contact your TAM if you intend to depend on the !");
114 println!(" ! validation rules use for the selected VSR. !");
115 println!(" ********************************************************************");
116 println!();
117 println!();
118 Options::vsr16()
119 }
120 None => Options::default(),
121 },
122 }
123 }
124
main() -> Result<()>125 fn main() -> Result<()> {
126 let args = Args::parse();
127 let message = match &args.action {
128 Action::VerifyDiceChain(sub_args) => {
129 println!();
130 println!(" ********************************************************************");
131 println!(" ! 'verify-dice-chain' has been deprecated in favor of 'dice-chain'.!");
132 println!(" ********************************************************************");
133 println!();
134 verify_dice_chain(&args, sub_args)?
135 }
136 Action::DiceChain(sub_args) => verify_dice_chain(&args, sub_args)?,
137 Action::FactoryCsr(sub_args) => parse_factory_csr(&args, sub_args)?,
138 Action::Csr(sub_args) => parse_csr(&args, sub_args)?,
139 };
140 println!("{}", message.unwrap_or(String::from("Success")));
141 Ok(())
142 }
143
verify_dice_chain(args: &Args, sub_args: &DiceChainArgs) -> Result<Option<String>>144 fn verify_dice_chain(args: &Args, sub_args: &DiceChainArgs) -> Result<Option<String>> {
145 let mut session = session_from_vsr(args.vsr);
146 session.set_allow_any_mode(sub_args.allow_any_mode);
147 session.set_rkp_instance(sub_args.rkp_instance);
148 let chain = dice::ChainForm::from_cbor(&session, &fs::read(&sub_args.chain)?)?;
149 if args.verbose {
150 println!("{chain:#?}");
151 }
152 if let ChainForm::Degenerate(_) = chain {
153 return Ok(Some(String::from(
154 "WARNING!
155 The given 'degenerate' DICE chain is valid. However, the degenerate chain form is deprecated in
156 favor of full DICE chains, rooted in ROM, that measure the system's boot components.",
157 )));
158 }
159 Ok(None)
160 }
161
parse_factory_csr(args: &Args, sub_args: &FactoryCsrArgs) -> Result<Option<String>>162 fn parse_factory_csr(args: &Args, sub_args: &FactoryCsrArgs) -> Result<Option<String>> {
163 let mut session = session_from_vsr(args.vsr);
164 session.set_allow_any_mode(sub_args.allow_any_mode);
165 let input = &fs::File::open(&sub_args.csr_file)?;
166 let mut csr_count = 0;
167 for line in io::BufReader::new(input).lines() {
168 let line = line?;
169 if line.is_empty() {
170 continue;
171 }
172 let csr = rkp::FactoryCsr::from_json(&session, &line)?;
173 csr_count += 1;
174 if args.verbose {
175 println!("{csr_count}: {csr:#?}");
176 }
177 }
178 if csr_count == 0 {
179 bail!("No CSRs found in the input file '{}'", sub_args.csr_file);
180 }
181 Ok(None)
182 }
183
parse_csr(args: &Args, sub_args: &CsrArgs) -> Result<Option<String>>184 fn parse_csr(args: &Args, sub_args: &CsrArgs) -> Result<Option<String>> {
185 let mut session = session_from_vsr(args.vsr);
186 session.set_allow_any_mode(sub_args.allow_any_mode);
187 session.set_rkp_instance(sub_args.rkp_instance);
188 let input = &fs::File::open(&sub_args.csr_file)?;
189 let csr = rkp::Csr::from_cbor(&session, input)?;
190 if args.verbose {
191 print!("{csr:#?}");
192 }
193 Ok(None)
194 }
195
196 #[cfg(test)]
197 mod tests {
198 use super::*;
199 use clap::CommandFactory;
200
201 #[test]
verify_command()202 fn verify_command() {
203 Args::command().debug_assert();
204 }
205 }
206