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