• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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