1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use kmr_common::{crypto, keyblob};
16 use kmr_wire::keymint;
17 use std::collections::HashMap;
18 use std::fmt::Write;
19
20 /// Combined schema, with CBOR-encoded examples of specific types.
21 #[derive(Default)]
22 struct AccumulatedSchema {
23 schema: String,
24 samples: HashMap<String, Vec<u8>>,
25 }
26
27 impl AccumulatedSchema {
28 /// Add a new type to the accumulated schema, along with a sample instance of the type.
add<T: kmr_wire::AsCborValue>(&mut self, sample: T)29 fn add<T: kmr_wire::AsCborValue>(&mut self, sample: T) {
30 if let (Some(name), Some(schema)) = (<T>::cddl_typename(), <T>::cddl_schema()) {
31 self.add_name_schema(&name, &schema);
32 self.samples.insert(name, sample.into_vec().unwrap());
33 } else {
34 eprintln!("No CDDL typename+schema for {}", std::any::type_name::<T>());
35 }
36 }
37
38 /// Add the given name = schema to the accumulated schema.
add_name_schema(&mut self, name: &str, schema: &str)39 fn add_name_schema(&mut self, name: &str, schema: &str) {
40 let _ = writeln!(self.schema, "{} = {}", name, schema);
41 }
42
43 /// Check that all of the sample type instances match their CDDL schema.
44 ///
45 /// This method is a no-op if the `cddl-cat` feature is not enabled.
check(&self)46 fn check(&self) {
47 // TODO: enable this if/when cddl-cat supports tagged CBOR items (which are used in the
48 // EncryptedKeyBlob encoding)
49 #[cfg(feature = "cddl-cat")]
50 for (name, data) in &self.samples {
51 if let Err(e) = cddl_cat::validate_cbor_bytes(&name, &self.schema, &data) {
52 eprintln!("Failed to validate sample data for {} against CDDL: {:?}", name, e);
53 }
54 }
55 }
56 }
57
58 impl std::fmt::Display for AccumulatedSchema {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(f, "{}", self.schema)
61 }
62 }
63
main()64 fn main() {
65 // CDDL for encrypted keyblobs, top-down.
66 let mut schema = AccumulatedSchema::default();
67
68 schema.add(keyblob::EncryptedKeyBlob::V1(keyblob::EncryptedKeyBlobV1 {
69 characteristics: vec![],
70 key_derivation_input: [0u8; 32],
71 kek_context: vec![],
72 encrypted_key_material: coset::CoseEncrypt0Builder::new()
73 .protected(
74 coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(),
75 )
76 .ciphertext(vec![1, 2, 3])
77 .build(),
78 secure_deletion_slot: Some(keyblob::SecureDeletionSlot(1)),
79 }));
80 schema.add(keyblob::Version::V1);
81 schema.add(keyblob::EncryptedKeyBlobV1 {
82 characteristics: vec![],
83 key_derivation_input: [0u8; 32],
84 kek_context: vec![],
85 encrypted_key_material: coset::CoseEncrypt0Builder::new()
86 .protected(
87 coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build(),
88 )
89 .ciphertext(vec![1, 2, 3])
90 .build(),
91 secure_deletion_slot: Some(keyblob::SecureDeletionSlot(1)),
92 });
93 schema.add(keymint::KeyCharacteristics {
94 security_level: keymint::SecurityLevel::TrustedEnvironment,
95 authorizations: vec![],
96 });
97 // From RFC 8152.
98 schema.add_name_schema(
99 "Cose_Encrypt0",
100 "[ protected: bstr, unprotected: { * (int / tstr) => any }, ciphertext: bstr / nil ]",
101 );
102
103 schema.add(crypto::KeyMaterial::Aes(crypto::aes::Key::Aes128([0u8; 16]).into()));
104 schema.add(keyblob::SecureDeletionSlot(1));
105 schema.add(keyblob::SecureDeletionData {
106 factory_reset_secret: [0; 32],
107 secure_deletion_secret: [0; 16],
108 });
109 schema.add(keyblob::RootOfTrustInfo {
110 verified_boot_key: vec![0; 32],
111 device_boot_locked: false,
112 verified_boot_state: keymint::VerifiedBootState::Unverified,
113 });
114 schema.add(keymint::VerifiedBootState::Unverified);
115
116 schema.add(keymint::SecurityLevel::TrustedEnvironment);
117 schema.add(keymint::KeyParam::CreationDatetime(keymint::DateTime {
118 ms_since_epoch: 22_593_600_000,
119 }));
120 schema.add(keymint::Tag::NoAuthRequired);
121
122 schema.add(keymint::Algorithm::Ec);
123 schema.add(keymint::BlockMode::Ecb);
124 schema.add(keymint::Digest::None);
125 schema.add(keymint::EcCurve::Curve25519);
126 schema.add(crypto::CurveType::Nist);
127 schema.add(keymint::KeyOrigin::Generated);
128 schema.add(keymint::KeyPurpose::Sign);
129 schema.add(keymint::HardwareAuthenticatorType::Fingerprint);
130 schema.add(keymint::PaddingMode::None);
131
132 schema.add(keymint::DateTime { ms_since_epoch: 22_593_600_000 });
133 schema.add(kmr_wire::KeySizeInBits(256));
134 schema.add(kmr_wire::RsaExponent(65537));
135
136 println!(
137 "; encrypted_key_material is AES-GCM encrypted with:\n\
138 ; - key derived as described below\n\
139 ; - plaintext is the CBOR-serialization of `KeyMaterial`\n\
140 ; - nonce value is fixed, all zeroes\n\
141 ; - no additional data\n\
142 ;\n\
143 ; Key derivation uses HKDF (RFC 5869) with HMAC-SHA256 to generate an AES-256 key:\n\
144 ; - input keying material = a root key held in hardware\n\
145 ; - salt = absent\n\
146 ; - info = the following three or four chunks of context data concatenated:\n\
147 ; - content of `EncryptedKeyBlob.key_derivation_input` (a random nonce)\n\
148 ; - CBOR-serialization of `EncryptedKeyBlob.characteristics`\n\
149 ; - CBOR-serialized array of additional hidden `KeyParam` items associated with the key, specifically:\n\
150 ; - [Tag_ApplicationId, bstr] if required\n\
151 ; - [Tag_ApplicationData, bstr] if required\n\
152 ; - [Tag_RootOfTrust, bstr .cbor RootOfTrustInfo]\n\
153 ; - (if secure storage is available) CBOR serialization of the `SecureDeletionData` structure, with:\n\
154 ; - `factory_reset_secret` always populated\n\
155 ; - `secure_deletion_secret` populated with:\n\
156 ; - all zeroes (if `EncryptedKeyBlob.secure_deletion_slot` is empty)\n\
157 ; - the contents of the slot (if `EncryptedKeyBlob.secure_deletion_slot` is non-empty)",
158 );
159 println!("{}", schema);
160 schema.check();
161 }
162