1 use crate::rkp::Csr;
2 use crate::session::{RkpInstance, Session};
3 use anyhow::{bail, Result};
4 use serde_json::{Map, Value};
5 use std::str::FromStr;
6
7 /// Represents a "Factory CSR", which is a JSON value captured for each device on the factory
8 /// line. This JSON is uploaded to the RKP backend to register the device. We reuse the CSR
9 /// (Certificate Signing Request) format for this as an implementation convenience. The CSR
10 /// actually contains an empty set of keys for which certificates are needed.
11 #[non_exhaustive]
12 #[derive(Debug, PartialEq)]
13 pub struct FactoryCsr {
14 /// The CSR, as created by an IRemotelyProvisionedComponent HAL.
15 pub csr: Csr,
16 /// The name of the HAL that generated the CSR.
17 pub name: String,
18 }
19
get_string_from_map(fields: &Map<String, Value>, key: &str) -> Result<String>20 fn get_string_from_map(fields: &Map<String, Value>, key: &str) -> Result<String> {
21 match fields.get(key) {
22 Some(Value::String(s)) => Ok(s.to_string()),
23 Some(v) => bail!("Unexpected type for '{key}'. Expected String, found '{v:?}'"),
24 None => bail!("Unable to locate '{key}' in input"),
25 }
26 }
27
28 impl FactoryCsr {
29 /// Parse the input JSON string into a CSR that was captured on the factory line. The
30 /// format of the JSON data is defined by rkp_factory_extraction_tool.
from_json(session: &Session, json: &str) -> Result<Self>31 pub fn from_json(session: &Session, json: &str) -> Result<Self> {
32 match serde_json::from_str(json) {
33 Ok(Value::Object(map)) => Self::from_map(session, map),
34 Ok(unexpected) => bail!("Expected a map, got some other type: {unexpected}"),
35 Err(e) => bail!("Error parsing input json: {e}"),
36 }
37 }
38
from_map(session: &Session, fields: Map<String, Value>) -> Result<Self>39 fn from_map(session: &Session, fields: Map<String, Value>) -> Result<Self> {
40 let base64 = get_string_from_map(&fields, "csr")?;
41 let name = get_string_from_map(&fields, "name")?;
42 let mut new_session = session.clone();
43 new_session.set_rkp_instance(RkpInstance::from_str(&name)?);
44 let csr = Csr::from_base64_cbor(&new_session, &base64)?;
45 Ok(Self { csr, name })
46 }
47 }
48
49 #[cfg(test)]
50 mod tests {
51 use super::*;
52 use crate::cbor::rkp::csr::testutil::{parse_pem_public_key_or_panic, test_device_info};
53 use crate::dice::{ChainForm, DegenerateChain};
54 use crate::rkp::device_info::DeviceInfoVersion;
55 use crate::rkp::factory_csr::FactoryCsr;
56 use crate::rkp::{ProtectedData, UdsCerts, UdsCertsEntry};
57 use anyhow::anyhow;
58 use itertools::Itertools;
59 use openssl::{pkey::PKey, x509::X509};
60 use std::fs;
61 use std::fs::File;
62
json_map_from_file(path: &str) -> Result<Map<String, Value>>63 fn json_map_from_file(path: &str) -> Result<Map<String, Value>> {
64 let input = File::open(path)?;
65 match serde_json::from_reader(input)? {
66 Value::Object(map) => Ok(map),
67 other => Err(anyhow!("Unexpected JSON. Wanted a map, found {other:?}")),
68 }
69 }
70
71 #[test]
from_json_valid_v2_ed25519()72 fn from_json_valid_v2_ed25519() {
73 let json = fs::read_to_string("testdata/factory_csr/v2_ed25519_valid.json").unwrap();
74 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
75 let subject_public_key = parse_pem_public_key_or_panic(
76 "-----BEGIN PUBLIC KEY-----\n\
77 MCowBQYDK2VwAyEAOhWsfxcBLgUfLLdqpb8cLUWutkkPtfIqDRfJC3LUihI=\n\
78 -----END PUBLIC KEY-----\n",
79 );
80 let degenerate = ChainForm::Degenerate(
81 DegenerateChain::new("self-signed", "self-signed", subject_public_key).unwrap(),
82 );
83 let chain = [
84 "-----BEGIN CERTIFICATE-----\n\
85 MIIBaDCCARqgAwIBAgIBezAFBgMrZXAwKzEVMBMGA1UEChMMRmFrZSBDb21wYW55\n\
86 MRIwEAYDVQQDEwlGYWtlIFJvb3QwHhcNMjMwODAxMjI1MTM0WhcNMjMwODMxMjI1\n\
87 MTM0WjArMRUwEwYDVQQKEwxGYWtlIENvbXBhbnkxEjAQBgNVBAMTCUZha2UgUm9v\n\
88 dDAqMAUGAytlcAMhAJYhPNIeQXe6+GPFiQAg4WtK+D8HuWaF6Es4X3HgDzq7o2Mw\n\
89 YTAdBgNVHQ4EFgQUDR0DF3abDeR3WXSlIhpN07R049owHwYDVR0jBBgwFoAUDR0D\n\
90 F3abDeR3WXSlIhpN07R049owDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n\
91 AgQwBQYDK2VwA0EAWNEhXrATWj4MXT/n38OwbngUm/n/5+vGFqV0aXuJPX/8d6Yx\n\
92 BbX/LFv6m8/VuPuItSqK4AudgwZJoupR/lknDg==\n\
93 -----END CERTIFICATE-----\n",
94 "-----BEGIN CERTIFICATE-----\n\
95 MIIBbDCCAR6gAwIBAgICAcgwBQYDK2VwMCsxFTATBgNVBAoTDEZha2UgQ29tcGFu\n\
96 eTESMBAGA1UEAxMJRmFrZSBSb290MB4XDTIzMDgwMTIyNTEzNFoXDTIzMDgzMTIy\n\
97 NTEzNFowLjEVMBMGA1UEChMMRmFrZSBDb21wYW55MRUwEwYDVQQDEwxGYWtlIENo\n\
98 aXBzZXQwKjAFBgMrZXADIQA6Fax/FwEuBR8st2qlvxwtRa62SQ+18ioNF8kLctSK\n\
99 EqNjMGEwHQYDVR0OBBYEFEbOrkgBL2SUCLJayyvpc+oR7m6/MB8GA1UdIwQYMBaA\n\
100 FA0dAxd2mw3kd1l0pSIaTdO0dOPaMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n\
101 BAQDAgIEMAUGAytlcANBANJmKGgiZP3tqtjnHpbmR3ypkXtvuqmo6KTBIHKsGKAO\n\
102 mH8qlMQPLmNSdxTs3OnVrlxQwZqXxHjVHS/6OpkkFgo=\n\
103 -----END CERTIFICATE-----\n",
104 ];
105 let chain = chain
106 .iter()
107 .map(|pem| X509::from_pem(pem.as_bytes()).unwrap().to_der().unwrap())
108 .collect_vec();
109
110 let mut uds_certs = UdsCerts::new();
111 uds_certs
112 .0
113 .insert("google-test".to_string(), UdsCertsEntry::new_x509_chain(chain).unwrap());
114 assert_eq!(
115 csr,
116 FactoryCsr {
117 csr: Csr::V2 {
118 device_info: test_device_info(DeviceInfoVersion::V2),
119 challenge: b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
120 .to_vec(),
121 protected_data: ProtectedData::new(vec![0; 32], degenerate, Some(uds_certs)),
122 },
123 name: "default".to_string(),
124 }
125 );
126 }
127
128 #[test]
from_json_valid_v3_ed25519()129 fn from_json_valid_v3_ed25519() {
130 let json = fs::read_to_string("testdata/factory_csr/v3_ed25519_valid.json").unwrap();
131 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
132 if let Csr::V3 { dice_chain, uds_certs, csr_payload, .. } = csr.csr {
133 assert_eq!(csr_payload.device_info, test_device_info(DeviceInfoVersion::V3));
134 let root_public_key = parse_pem_public_key_or_panic(
135 "-----BEGIN PUBLIC KEY-----\n\
136 MCowBQYDK2VwAyEArqr7jIIQ8TB1+l/Sh69eiSJL6t6txO1oLhpkdVSUuBk=\n\
137 -----END PUBLIC KEY-----\n",
138 );
139 match dice_chain {
140 ChainForm::Proper(p) => {
141 assert_eq!(p.root_public_key(), &root_public_key);
142 assert_eq!(p.payloads().len(), 1);
143 }
144 ChainForm::Degenerate(d) => panic!("Parsed chain is not proper: {:?}", d),
145 }
146 assert_eq!(uds_certs.len(), 0);
147 } else {
148 panic!("Parsed CSR was not V3: {:?}", csr);
149 }
150 }
151
152 #[test]
from_json_valid_v2_p256()153 fn from_json_valid_v2_p256() {
154 let json = fs::read_to_string("testdata/factory_csr/v2_p256_valid.json").unwrap();
155 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
156 let pem = "-----BEGIN PUBLIC KEY-----\n\
157 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERd9pHZbUJ/b4IleUGDN8fs8+LDxE\n\
158 vG6VX1dkw0sClFs4imbzfXGbocEq74S7TQiyZkd1LhY6HRZnTC51KoGDIA==\n\
159 -----END PUBLIC KEY-----\n";
160 let subject_public_key =
161 PKey::public_key_from_pem(pem.as_bytes()).unwrap().try_into().unwrap();
162 let degenerate = ChainForm::Degenerate(
163 DegenerateChain::new("self-signed", "self-signed", subject_public_key).unwrap(),
164 );
165 assert_eq!(
166 csr,
167 FactoryCsr {
168 csr: Csr::V2 {
169 device_info: test_device_info(DeviceInfoVersion::V2),
170 challenge: b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
171 .to_vec(),
172 protected_data: ProtectedData::new(vec![0; 32], degenerate, None),
173 },
174 name: "default".to_string(),
175 }
176 );
177 }
178
179 #[test]
from_json_valid_v3_p256()180 fn from_json_valid_v3_p256() {
181 let json = fs::read_to_string("testdata/factory_csr/v3_p256_valid.json").unwrap();
182 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
183 if let Csr::V3 { dice_chain, uds_certs, csr_payload, .. } = csr.csr {
184 assert_eq!(csr_payload.device_info, test_device_info(DeviceInfoVersion::V3));
185 let root_public_key = parse_pem_public_key_or_panic(
186 "-----BEGIN PUBLIC KEY-----\n\
187 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh5NUV4872vKEL3XPSp8lfkV4AN3J\n\
188 KJti1Y5kbbR9ucTpSyoOjX9UmBCM/uuPU/MGXMWrgbBf3++02ALzC+V3eQ==\n\
189 -----END PUBLIC KEY-----\n",
190 );
191 match dice_chain {
192 ChainForm::Proper(p) => {
193 assert_eq!(p.root_public_key(), &root_public_key);
194 assert_eq!(p.payloads().len(), 1);
195 }
196 ChainForm::Degenerate(d) => panic!("Parsed chain is not proper: {:?}", d),
197 }
198 assert_eq!(uds_certs.len(), 0);
199 } else {
200 panic!("Parsed CSR was not V3: {:?}", csr);
201 }
202 }
203
get_pem_or_die(cert: Option<&X509>) -> String204 fn get_pem_or_die(cert: Option<&X509>) -> String {
205 let cert = cert.unwrap_or_else(|| panic!("Missing x.509 cert"));
206 let pem =
207 cert.to_pem().unwrap_or_else(|e| panic!("Failed to encode X.509 cert to PEM: {e}"));
208 String::from_utf8_lossy(&pem).to_string()
209 }
210
211 #[test]
from_json_valid_v3_p256_with_uds_certs()212 fn from_json_valid_v3_p256_with_uds_certs() {
213 let json =
214 fs::read_to_string("testdata/factory_csr/v3_p256_valid_with_uds_certs.json").unwrap();
215 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
216 if let Csr::V3 { uds_certs, .. } = csr.csr {
217 assert_eq!(uds_certs.len(), 1);
218 let chain = uds_certs.get("test-signer-name").unwrap_or_else(|| {
219 panic!("Unable to find 'test-signer-name' in UdsCerts: {uds_certs:?}")
220 });
221 assert_eq!(chain.len(), 2);
222 assert_eq!(
223 get_pem_or_die(chain.first()),
224 "-----BEGIN CERTIFICATE-----\n\
225 MIIBaDCCARqgAwIBAgIBezAFBgMrZXAwKzEVMBMGA1UEChMMRmFrZSBDb21wYW55\n\
226 MRIwEAYDVQQDEwlGYWtlIFJvb3QwHhcNMjQxMTA3MTMwOTMxWhcNNDkxMTAxMTMw\n\
227 OTMxWjArMRUwEwYDVQQKEwxGYWtlIENvbXBhbnkxEjAQBgNVBAMTCUZha2UgUm9v\n\
228 dDAqMAUGAytlcAMhAOgFrCrwxUYuOBSIk31/ykUsDP1vSRCzs8x2e8u8vumIo2Mw\n\
229 YTAdBgNVHQ4EFgQUtLO8kYH4qiyhGNKhkzZvxk7td94wHwYDVR0jBBgwFoAUtLO8\n\
230 kYH4qiyhGNKhkzZvxk7td94wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n\
231 AgQwBQYDK2VwA0EA1o8kJ3NTsY7B5/rRkJi8i/RZE1/0pQC2OUTOi8S7ZCkVdBJK\n\
232 7RyHo5/rVPXwVcsd3ZU1jZQalooek4mbDAWxAw==\n\
233 -----END CERTIFICATE-----\n"
234 );
235 assert_eq!(
236 get_pem_or_die(chain.get(1)),
237 "-----BEGIN CERTIFICATE-----\n\
238 MIIBmzCCAU2gAwIBAgICAcgwBQYDK2VwMCsxFTATBgNVBAoTDEZha2UgQ29tcGFu\n\
239 eTESMBAGA1UEAxMJRmFrZSBSb290MB4XDTI0MTEwNzEzMDkzMVoXDTQ5MTEwMTEz\n\
240 MDkzMVowLjEVMBMGA1UEChMMRmFrZSBDb21wYW55MRUwEwYDVQQDEwxGYWtlIENo\n\
241 aXBzZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmCOHpHOZzSZvp1frFACgm\n\
242 Itnj33YAKYseZfT68AlrN4UtC5boNVU5wjKWQFRcOlup5kxX2UVlb+jFCO7eskFU\n\
243 o2MwYTAdBgNVHQ4EFgQU7KrNWsfWHijorD/+b5TBIZCzj3MwHwYDVR0jBBgwFoAU\n\
244 tLO8kYH4qiyhGNKhkzZvxk7td94wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E\n\
245 BAMCAgQwBQYDK2VwA0EAuDdXCHTYt92UxftrDJnKXxjtDBCYMqXSlIuYw8p1W/UP\n\
246 Ccerp/jUng8ELnfPj2ZTkTP2+NhvwsYKvbaxaz9pDA==\n\
247 -----END CERTIFICATE-----\n"
248 );
249 } else {
250 panic!("Parsed CSR was not V3: {:?}", csr);
251 }
252 }
253
254 #[test]
from_json_v3_p256_with_mismatch_uds_certs()255 fn from_json_v3_p256_with_mismatch_uds_certs() {
256 let json =
257 fs::read_to_string("testdata/factory_csr/v3_p256_mismatched_uds_certs.json").unwrap();
258 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
259 assert!(
260 err.to_string().contains("does not match the DICE chain root"),
261 "Expected mismatch between UDS_pub and UdsCerts leaf"
262 );
263 }
264
265 #[test]
from_json_v3_p256_with_extra_uds_cert_in_chain()266 fn from_json_v3_p256_with_extra_uds_cert_in_chain() {
267 let json = fs::read_to_string("testdata/factory_csr/v3_p256_extra_uds_cert_in_chain.json")
268 .unwrap();
269 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
270 assert!(
271 err.to_string().contains("Verified chain doesn't match input"),
272 "Expected cert validation to fail due to extra cert in UDS chain"
273 );
274 }
275
276 #[test]
from_json_name_is_missing()277 fn from_json_name_is_missing() {
278 let mut value = json_map_from_file("testdata/factory_csr/v2_ed25519_valid.json").unwrap();
279 value.remove_entry("name");
280 let json = serde_json::to_string(&value).unwrap();
281 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
282 assert!(err.to_string().contains("Unable to locate 'name'"));
283 }
284
285 #[test]
from_json_name_is_wrong_type()286 fn from_json_name_is_wrong_type() {
287 let mut value = json_map_from_file("testdata/factory_csr/v2_ed25519_valid.json").unwrap();
288 value.insert("name".to_string(), Value::Object(Map::default()));
289 let json = serde_json::to_string(&value).unwrap();
290 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
291 assert!(err.to_string().contains("Unexpected type for 'name'"));
292 }
293
294 #[test]
from_json_csr_is_missing()295 fn from_json_csr_is_missing() {
296 let json = r#"{ "name": "default" }"#;
297 let err = FactoryCsr::from_json(&Session::default(), json).unwrap_err();
298 assert!(err.to_string().contains("Unable to locate 'csr'"));
299 }
300
301 #[test]
from_json_csr_is_wrong_type()302 fn from_json_csr_is_wrong_type() {
303 let json = r#"{ "csr": 3.1415, "name": "default" }"#;
304 let err = FactoryCsr::from_json(&Session::default(), json).unwrap_err();
305 assert!(err.to_string().contains("Unexpected type for 'csr'"));
306 }
307
308 #[test]
from_json_extra_tag_is_ignored()309 fn from_json_extra_tag_is_ignored() {
310 let mut value = json_map_from_file("testdata/factory_csr/v2_ed25519_valid.json").unwrap();
311 value.insert("extra".to_string(), Value::Bool(true));
312 let json = serde_json::to_string(&value).unwrap();
313 let csr = FactoryCsr::from_json(&Session::default(), &json).unwrap();
314 assert_eq!(csr.name, "default");
315 }
316
317 #[test]
from_json_valid_v3_avf_with_rkpvm_markers()318 fn from_json_valid_v3_avf_with_rkpvm_markers() {
319 let json = fs::read_to_string("testdata/factory_csr/v3_avf_valid_with_rkpvm_markers.json")
320 .unwrap();
321 let mut session = Session::default();
322 session.set_allow_any_mode(true);
323 let csr = FactoryCsr::from_json(&session, &json).unwrap();
324 assert_eq!(csr.name, "avf");
325 }
326
327 #[test]
from_json_v3_p256_with_private_key()328 fn from_json_v3_p256_with_private_key() {
329 let json =
330 fs::read_to_string("testdata/factory_csr/v3_p256_with_private_key.json").unwrap();
331 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
332 let source = err.source().unwrap().to_string();
333 assert!(
334 source.contains("disallowed labels should be empty")
335 && source
336 .contains("12953f77f0726491a09c5b2d134a26a8a657dbc170c4036ffde81e881e0acd03")
337 );
338 }
339
340 #[test]
from_json_v3_p256_with_corrupted_payload()341 fn from_json_v3_p256_with_corrupted_payload() {
342 let json =
343 fs::read_to_string("testdata/factory_csr/v3_p256_with_corrupted_payload.json").unwrap();
344 let err = FactoryCsr::from_json(&Session::default(), &json).unwrap_err();
345 let source = err.source().unwrap().to_string();
346 assert!(source.contains("Signature verification failed"));
347 }
348 }
349