• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! This library provides bindings for C++ code to comfortably and reasonably safely interface with
2 //! the libhwtrust Rust library.
3 
4 use coset::CborSerializable;
5 use hwtrust::dice::ChainForm;
6 use hwtrust::dice::DiceMode;
7 use hwtrust::dice::ProfileVersion;
8 use hwtrust::rkp::Csr as InnerCsr;
9 use hwtrust::session::{DiceProfileRange, Options, RkpInstance, Session};
10 use std::str::FromStr;
11 
12 /// Since the AVF DICE chain combines both vendor and AOSP DICE chains, the chain doesn't rely
13 /// solely on the VSR for vendors, unlike pure vendor DICE chains. This constant defines the
14 /// minimum profile version required for the AOSP/AVF portion of the DICE chain.
15 ///
16 /// Since the AVF portion follows the vendor portion, its version is always higher.
17 const AVF_DICE_PROFILE_VERSION: ProfileVersion = ProfileVersion::Android16;
18 
19 #[allow(clippy::needless_maybe_sized)]
20 #[allow(unsafe_op_in_unsafe_fn)]
21 #[cxx::bridge(namespace = "hwtrust::rust")]
22 mod ffi {
23     /// The set of validation rules to apply.
24     enum DiceChainKind {
25         /// The DICE chain specified by VSR 13.
26         Vsr13,
27         /// The DICE chain specified by VSR 14.
28         Vsr14,
29         /// The DICE chain specified by VSR 15.
30         Vsr15,
31         /// The DICE chain specified by VSR 16.
32         Vsr16,
33     }
34 
35     /// The result type used by [`verify_dice_chain()`]. The standard [`Result`] is currently only
36     /// converted to exceptions by `cxxbridge` but we can't use exceptions so need to do something
37     /// custom.
38     struct VerifyDiceChainResult {
39         /// If non-empty, the description of the verification error that occurred.
40         error: String,
41         /// If [`error`] is empty, a handle to the verified chain.
42         chain: Box<DiceChain>,
43         /// If [`error`] is empty, the length of the chain.
44         len: usize,
45     }
46 
47     /// The result type used by [`validate_csr()`]. The standard [`Result`] is currently only
48     /// converted to exceptions by `cxxbridge` but we can't use exceptions so need to do something
49     /// custom.
50     struct ValidateCsrResult {
51         /// If non-empty, the description of the verification error that occurred.
52         error: String,
53         /// If [`error`] is empty, a handle to the validated Csr.
54         csr: Box<Csr>,
55     }
56 
57     struct BoolResult {
58         error: String,
59         value: bool,
60     }
61 
62     extern "Rust" {
63         type DiceChain;
64 
65         #[cxx_name = VerifyDiceChain]
verify_dice_chain( chain: &[u8], kind: DiceChainKind, allow_any_mode: bool, instance: &str, ) -> VerifyDiceChainResult66         fn verify_dice_chain(
67             chain: &[u8],
68             kind: DiceChainKind,
69             allow_any_mode: bool,
70             instance: &str,
71         ) -> VerifyDiceChainResult;
72 
73         #[cxx_name = GetDiceChainPublicKey]
get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8>74         fn get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8>;
75 
76         #[cxx_name = compareRootPublicKeyInDiceChain]
compare_root_public_key_in_dice_chain( chain1: &DiceChain, chain2: &DiceChain, ) -> BoolResult77         fn compare_root_public_key_in_dice_chain(
78             chain1: &DiceChain,
79             chain2: &DiceChain,
80         ) -> BoolResult;
81 
82         #[cxx_name = componentNameInDiceChainContains]
component_name_in_dice_chain_contains(chain: &DiceChain, substring: &str) -> BoolResult83         fn component_name_in_dice_chain_contains(chain: &DiceChain, substring: &str) -> BoolResult;
84 
85         #[cxx_name = hasNonNormalModeInDiceChain]
has_non_normal_mode_in_dice_chain(chain: &DiceChain) -> BoolResult86         fn has_non_normal_mode_in_dice_chain(chain: &DiceChain) -> BoolResult;
87 
88         #[cxx_name = IsDiceChainProper]
is_dice_chain_proper(chain: &DiceChain) -> bool89         fn is_dice_chain_proper(chain: &DiceChain) -> bool;
90 
91         type Csr;
92 
93         #[cxx_name = validateCsr]
validate_csr( csr: &[u8], kind: DiceChainKind, is_factory: bool, allow_any_mode: bool, instance: &str, ) -> ValidateCsrResult94         fn validate_csr(
95             csr: &[u8],
96             kind: DiceChainKind,
97             is_factory: bool,
98             allow_any_mode: bool,
99             instance: &str,
100         ) -> ValidateCsrResult;
101 
102         #[cxx_name = getDiceChainFromCsr]
get_dice_chain_from_csr(csr: &Csr) -> VerifyDiceChainResult103         fn get_dice_chain_from_csr(csr: &Csr) -> VerifyDiceChainResult;
104 
105         #[cxx_name = csrHasUdsCerts]
csr_has_uds_certs(csr: &Csr) -> bool106         fn csr_has_uds_certs(csr: &Csr) -> bool;
107 
108         #[cxx_name = getCsrPayloadFromCsr]
get_csr_payload_from_csr(csr: &Csr) -> Vec<u8>109         fn get_csr_payload_from_csr(csr: &Csr) -> Vec<u8>;
110 
111         #[cxx_name = compareKeysToSignInCsr]
compare_keys_to_sign_in_csr(csr: &Csr, keys_to_sign: &[u8]) -> BoolResult112         fn compare_keys_to_sign_in_csr(csr: &Csr, keys_to_sign: &[u8]) -> BoolResult;
113 
114         #[cxx_name = compareChallengeInCsr]
compare_challenge_in_csr(csr: &Csr, challenge: &[u8]) -> BoolResult115         fn compare_challenge_in_csr(csr: &Csr, challenge: &[u8]) -> BoolResult;
116     }
117 }
118 
119 impl TryInto<Options> for ffi::DiceChainKind {
120     type Error = String;
121 
try_into(self) -> Result<Options, Self::Error>122     fn try_into(self) -> Result<Options, Self::Error> {
123         match self {
124             ffi::DiceChainKind::Vsr13 => Ok(Options::vsr13()),
125             ffi::DiceChainKind::Vsr14 => Ok(Options::vsr14()),
126             ffi::DiceChainKind::Vsr15 => Ok(Options::vsr15()),
127             ffi::DiceChainKind::Vsr16 => Ok(Options::vsr16()),
128             _ => Err("invalid chain kind".to_string()),
129         }
130     }
131 }
132 
133 /// A DICE chain as exposed over the cxx bridge.
134 pub struct DiceChain(Option<ChainForm>);
135 
new_session( kind: ffi::DiceChainKind, allow_any_mode: bool, instance: &str, ) -> Result<Session, String>136 fn new_session(
137     kind: ffi::DiceChainKind,
138     allow_any_mode: bool,
139     instance: &str,
140 ) -> Result<Session, String> {
141     let mut options: Options = kind.try_into()?;
142     let Ok(rkp_instance) = RkpInstance::from_str(instance) else {
143         return Err(format!("invalid RKP instance: {}", instance));
144     };
145     if rkp_instance == RkpInstance::Avf {
146         options.dice_profile_range =
147             DiceProfileRange::new(options.dice_profile_range.start(), AVF_DICE_PROFILE_VERSION)
148     }
149     let mut session = Session { options };
150     session.set_rkp_instance(rkp_instance);
151     session.set_allow_any_mode(allow_any_mode);
152     Ok(session)
153 }
154 
verify_dice_chain( chain: &[u8], kind: ffi::DiceChainKind, allow_any_mode: bool, instance: &str, ) -> ffi::VerifyDiceChainResult155 fn verify_dice_chain(
156     chain: &[u8],
157     kind: ffi::DiceChainKind,
158     allow_any_mode: bool,
159     instance: &str,
160 ) -> ffi::VerifyDiceChainResult {
161     let session = match new_session(kind, allow_any_mode, instance) {
162         Ok(session) => session,
163         Err(e) => {
164             return ffi::VerifyDiceChainResult {
165                 error: e,
166                 chain: Box::new(DiceChain(None)),
167                 len: 0,
168             }
169         }
170     };
171 
172     match ChainForm::from_cbor(&session, chain) {
173         Ok(chain) => {
174             let len = chain.length();
175             let chain = Box::new(DiceChain(Some(chain)));
176             ffi::VerifyDiceChainResult { error: "".to_string(), chain, len }
177         }
178         Err(e) => {
179             let error = format!("{:#}", e);
180             ffi::VerifyDiceChainResult { error, chain: Box::new(DiceChain(None)), len: 0 }
181         }
182     }
183 }
184 
get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8>185 fn get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8> {
186     if let DiceChain(Some(chain)) = chain {
187         let key = match chain {
188             ChainForm::Proper(chain) => chain.payloads()[n].subject_public_key(),
189             ChainForm::Degenerate(chain) => chain.public_key(),
190         };
191         if let Ok(cose_key) = key.to_cose_key() {
192             if let Ok(bytes) = cose_key.to_vec() {
193                 return bytes;
194             }
195         }
196     }
197     Vec::new()
198 }
199 
compare_root_public_key_in_dice_chain( chain1: &DiceChain, chain2: &DiceChain, ) -> ffi::BoolResult200 fn compare_root_public_key_in_dice_chain(
201     chain1: &DiceChain,
202     chain2: &DiceChain,
203 ) -> ffi::BoolResult {
204     match (chain1, chain2) {
205         (
206             DiceChain(Some(ChainForm::Proper(chain1))),
207             DiceChain(Some(ChainForm::Proper(chain2))),
208         ) => {
209             let equal = chain1.root_public_key() == chain2.root_public_key();
210             ffi::BoolResult { error: "".to_string(), value: equal }
211         }
212         _ => ffi::BoolResult {
213             error: "Two proper DICE chains were not provided".to_string(),
214             value: false,
215         },
216     }
217 }
218 
component_name_in_dice_chain_contains(chain: &DiceChain, substring: &str) -> ffi::BoolResult219 fn component_name_in_dice_chain_contains(chain: &DiceChain, substring: &str) -> ffi::BoolResult {
220     match chain {
221         DiceChain(Some(chain)) => match chain {
222             ChainForm::Proper(chain) => {
223                 match chain
224                     .payloads()
225                     .last()
226                     .expect("leaf cert was empty")
227                     .config_desc()
228                     .component_name()
229                 {
230                     Some(name) => {
231                         ffi::BoolResult { error: "".to_string(), value: name.contains(substring) }
232                     }
233                     None => ffi::BoolResult {
234                         error: "component name could not be retrieved".to_string(),
235                         value: false,
236                     },
237                 }
238             }
239             ChainForm::Degenerate(_) => {
240                 ffi::BoolResult { error: "DICE chain is degenerate".to_string(), value: false }
241             }
242         },
243         _ => ffi::BoolResult { error: "A DICE chain must be provided".to_string(), value: false },
244     }
245 }
246 
has_non_normal_mode_in_dice_chain(chain: &DiceChain) -> ffi::BoolResult247 fn has_non_normal_mode_in_dice_chain(chain: &DiceChain) -> ffi::BoolResult {
248     match chain {
249         DiceChain(Some(ChainForm::Proper(chain))) => {
250             let has_non_normal_mode =
251                 chain.payloads().iter().any(|payload| payload.mode() != DiceMode::Normal);
252             ffi::BoolResult { error: "".to_string(), value: has_non_normal_mode }
253         }
254         _ => ffi::BoolResult {
255             error: "A proper DICE chain must be provided".to_string(),
256             value: false,
257         },
258     }
259 }
260 
is_dice_chain_proper(chain: &DiceChain) -> bool261 fn is_dice_chain_proper(chain: &DiceChain) -> bool {
262     if let DiceChain(Some(chain)) = chain {
263         match chain {
264             ChainForm::Proper(_) => true,
265             ChainForm::Degenerate(_) => false,
266         }
267     } else {
268         false
269     }
270 }
271 
272 /// A Csr as exposed over the cxx bridge.
273 pub struct Csr(Option<InnerCsr>);
274 
validate_csr( csr: &[u8], kind: ffi::DiceChainKind, is_factory: bool, allow_any_mode: bool, instance: &str, ) -> ffi::ValidateCsrResult275 fn validate_csr(
276     csr: &[u8],
277     kind: ffi::DiceChainKind,
278     is_factory: bool,
279     allow_any_mode: bool,
280     instance: &str,
281 ) -> ffi::ValidateCsrResult {
282     let mut session = match new_session(kind, allow_any_mode, instance) {
283         Ok(session) => session,
284         Err(e) => return ffi::ValidateCsrResult { error: e, csr: Box::new(Csr(None)) },
285     };
286     session.set_is_factory(is_factory);
287     match InnerCsr::from_cbor(&session, csr) {
288         Ok(csr) => {
289             let csr = Box::new(Csr(Some(csr)));
290             ffi::ValidateCsrResult { error: "".to_string(), csr }
291         }
292         Err(e) => {
293             let error = format!("{:#}", e);
294             ffi::ValidateCsrResult { error, csr: Box::new(Csr(None)) }
295         }
296     }
297 }
298 
get_dice_chain_from_csr(csr: &Csr) -> ffi::VerifyDiceChainResult299 fn get_dice_chain_from_csr(csr: &Csr) -> ffi::VerifyDiceChainResult {
300     match csr {
301         Csr(Some(csr)) => {
302             let chain = csr.dice_chain();
303             let len = chain.length();
304             let chain = Box::new(DiceChain(Some(chain)));
305             ffi::VerifyDiceChainResult { error: "".to_string(), chain, len }
306         }
307         _ => ffi::VerifyDiceChainResult {
308             error: "A CSR needs to be provided".to_string(),
309             chain: Box::new(DiceChain(None)),
310             len: 0,
311         },
312     }
313 }
314 
csr_has_uds_certs(csr: &Csr) -> bool315 fn csr_has_uds_certs(csr: &Csr) -> bool {
316     match csr {
317         Csr(Some(csr)) => csr.has_uds_certs(),
318         _ => false,
319     }
320 }
321 
get_csr_payload_from_csr(csr: &Csr) -> Vec<u8>322 fn get_csr_payload_from_csr(csr: &Csr) -> Vec<u8> {
323     match csr {
324         Csr(Some(csr)) => csr.csr_payload(),
325         _ => Vec::new(),
326     }
327 }
328 
compare_keys_to_sign_in_csr(csr: &Csr, keys_to_sign: &[u8]) -> ffi::BoolResult329 fn compare_keys_to_sign_in_csr(csr: &Csr, keys_to_sign: &[u8]) -> ffi::BoolResult {
330     match csr {
331         Csr(Some(csr)) => {
332             ffi::BoolResult { error: "".to_string(), value: csr.compare_keys_to_sign(keys_to_sign) }
333         }
334         _ => {
335             ffi::BoolResult { error: "KeysToSign could not be compared".to_string(), value: false }
336         }
337     }
338 }
339 
compare_challenge_in_csr(csr: &Csr, challenge: &[u8]) -> ffi::BoolResult340 fn compare_challenge_in_csr(csr: &Csr, challenge: &[u8]) -> ffi::BoolResult {
341     match csr {
342         Csr(Some(csr)) => {
343             ffi::BoolResult { error: "".to_string(), value: challenge == csr.challenge() }
344         }
345         _ => ffi::BoolResult { error: "challenge could not be compared".to_string(), value: false },
346     }
347 }
348