• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, 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 }
51 
52 #[derive(Parser)]
53 /// Verify a CSR generated by the rkp_factory_extraction_tool
54 ///
55 /// "v1" CSRs are also decrypted using the factory EEK.
56 struct FactoryCsrArgs {
57     /// Path to a file containing one or more CSRs, in the "csr+json" format as defined by
58     /// rkp_factory_extraction_tool. Each line is interpreted as a separate JSON blob containing
59     /// a base64-encoded CSR.
60     csr_file: String,
61 }
62 
63 #[derive(Parser)]
64 /// Parse and verify a request payload that is suitable for the RKP server's SignCertificates API.
65 /// In HALv3, this is the output of generateCertificateRequestV2. For previous HAL versions,
66 /// the CSR is constructed by the remote provisioning service client, but is constructed from the
67 /// outputs of generateCertificateRequest.
68 struct CsrArgs {
69     /// Path to a file containing a single CSR, encoded as CBOR.
70     csr_file: String,
71 }
72 
73 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
74 enum VsrVersion {
75     /// VSR 13 / Android T / 2022
76     Vsr13,
77     /// VSR 14 / Android U / 2023
78     Vsr14,
79     /// VSR 15 / Android V / 2024
80     Vsr15,
81     /// VSR 16 / Android W / 2025
82     Vsr16,
83 }
84 
session_from_vsr(vsr: Option<VsrVersion>) -> Session85 fn session_from_vsr(vsr: Option<VsrVersion>) -> Session {
86     Session {
87         options: match vsr {
88             Some(VsrVersion::Vsr13) => Options::vsr13(),
89             Some(VsrVersion::Vsr14) => Options::vsr14(),
90             Some(VsrVersion::Vsr15) => Options::vsr15(),
91             Some(VsrVersion::Vsr16) => {
92                 println!();
93                 println!();
94                 println!("  ********************************************************************");
95                 println!("  ! The selected VSR is not finalized and is subject to change.      !");
96                 println!("  ! Please contact your TAM if you intend to depend on the           !");
97                 println!("  ! validation rules use for the selected VSR.                       !");
98                 println!("  ********************************************************************");
99                 println!();
100                 println!();
101                 Options::vsr16()
102             }
103             None => Options::default(),
104         },
105     }
106 }
107 
main() -> Result<()>108 fn main() -> Result<()> {
109     let args = Args::parse();
110     let message = match &args.action {
111         Action::VerifyDiceChain(sub_args) => {
112             println!();
113             println!("  ********************************************************************");
114             println!("  ! 'verify-dice-chain' has been deprecated in favor of 'dice-chain'.!");
115             println!("  ********************************************************************");
116             println!();
117             verify_dice_chain(&args, sub_args)?
118         }
119         Action::DiceChain(sub_args) => verify_dice_chain(&args, sub_args)?,
120         Action::FactoryCsr(sub_args) => parse_factory_csr(&args, sub_args)?,
121         Action::Csr(sub_args) => parse_csr(&args, sub_args)?,
122     };
123     println!("{}", message.unwrap_or(String::from("Success")));
124     Ok(())
125 }
126 
verify_dice_chain(args: &Args, sub_args: &DiceChainArgs) -> Result<Option<String>>127 fn verify_dice_chain(args: &Args, sub_args: &DiceChainArgs) -> Result<Option<String>> {
128     let session = session_from_vsr(args.vsr);
129     let chain = dice::ChainForm::from_cbor(&session, &fs::read(&sub_args.chain)?)?;
130     if args.verbose {
131         println!("{chain:#?}");
132     }
133     if let ChainForm::Degenerate(_) = chain {
134         return Ok(Some(String::from(
135             "WARNING!
136 The given 'degenerate' DICE chain is valid. However, the degenerate chain form is deprecated in
137 favor of full DICE chains, rooted in ROM, that measure the system's boot components.",
138         )));
139     }
140     Ok(None)
141 }
142 
parse_factory_csr(args: &Args, sub_args: &FactoryCsrArgs) -> Result<Option<String>>143 fn parse_factory_csr(args: &Args, sub_args: &FactoryCsrArgs) -> Result<Option<String>> {
144     let session = session_from_vsr(args.vsr);
145     let input = &fs::File::open(&sub_args.csr_file)?;
146     let mut csr_count = 0;
147     for line in io::BufReader::new(input).lines() {
148         let line = line?;
149         if line.is_empty() {
150             continue;
151         }
152         let csr = rkp::FactoryCsr::from_json(&session, &line)?;
153         csr_count += 1;
154         if args.verbose {
155             println!("{csr_count}: {csr:#?}");
156         }
157     }
158     if csr_count == 0 {
159         bail!("No CSRs found in the input file '{}'", sub_args.csr_file);
160     }
161     Ok(None)
162 }
163 
parse_csr(args: &Args, sub_args: &CsrArgs) -> Result<Option<String>>164 fn parse_csr(args: &Args, sub_args: &CsrArgs) -> Result<Option<String>> {
165     let session = session_from_vsr(args.vsr);
166     let input = &fs::File::open(&sub_args.csr_file)?;
167     let csr = rkp::Csr::from_cbor(&session, input)?;
168     if args.verbose {
169         print!("{csr:#?}");
170     }
171     Ok(None)
172 }
173 
174 #[cfg(test)]
175 mod tests {
176     use super::*;
177     use clap::CommandFactory;
178 
179     #[test]
verify_command()180     fn verify_command() {
181         Args::command().debug_assert();
182     }
183 }
184