1 use crate::cbor::field_value::FieldValue;
2 use crate::cbor::value_from_bytes;
3 use crate::dice::Chain;
4 use crate::rkp::{Csr, DeviceInfo, ProtectedData};
5 use crate::session::Session;
6 use anyhow::{anyhow, bail, ensure, Context, Result};
7 use base64::{prelude::BASE64_STANDARD, Engine};
8 use ciborium::value::Value;
9 
10 const VERSION_OR_DEVICE_INFO_INDEX: usize = 0;
11 
12 impl Csr {
13     /// Parse base64-encoded CBOR data as a Certificate Signing Request.
from_base64_cbor<S: AsRef<[u8]>>(session: &Session, base64: &S) -> Result<Self>14     pub fn from_base64_cbor<S: AsRef<[u8]>>(session: &Session, base64: &S) -> Result<Self> {
15         let cbor: Vec<u8> = BASE64_STANDARD.decode(base64).context("invalid base64 CSR")?;
16         Self::from_cbor(session, cbor.as_slice())
17     }
18 
19     /// Read and parse CBOR data as a Certificate Signing Request.
from_cbor<S: std::io::Read>(session: &Session, cbor: S) -> Result<Self>20     pub fn from_cbor<S: std::io::Read>(session: &Session, cbor: S) -> Result<Self> {
21         let value: Value = ciborium::de::from_reader(cbor).context("invalid CBOR")?;
22         let mut array = match value {
23             Value::Array(a) if a.is_empty() => bail!("CSR CBOR is an empty array"),
24             Value::Array(a) => a,
25             other => bail!("expected array, found {other:?}"),
26         };
27         let version_or_device_info =
28             std::mem::replace(&mut array[VERSION_OR_DEVICE_INFO_INDEX], Value::Null);
29         match version_or_device_info {
30             Value::Array(device_info) => Self::v2_from_cbor_values(session, array, device_info),
31             Value::Integer(i) => Self::v3_from_authenticated_request(session, array, i.into()),
32             other => Err(anyhow!(
33                 "Expected integer or array at index {VERSION_OR_DEVICE_INFO_INDEX}, \
34                 found {other:?}"
35             )),
36         }
37     }
38 
v2_from_cbor_values( session: &Session, mut csr: Vec<Value>, mut device_info: Vec<Value>, ) -> Result<Self>39     fn v2_from_cbor_values(
40         session: &Session,
41         mut csr: Vec<Value>,
42         mut device_info: Vec<Value>,
43     ) -> Result<Self> {
44         let maced_keys_to_sign =
45             FieldValue::from_optional_value("MacedKeysToSign", csr.pop()).into_cose_mac0()?;
46         let encrypted_protected_data =
47             FieldValue::from_optional_value("ProtectedData", csr.pop()).into_cose_encrypt()?;
48         let challenge = FieldValue::from_optional_value("Challenge", csr.pop()).into_bytes()?;
49 
50         ensure!(device_info.len() == 2, "Device info should contain exactly 2 entries");
51         device_info.pop(); // ignore unverified info
52         let verified_device_info = match device_info.pop() {
53             Some(Value::Map(d)) => Value::Map(d),
54             other => bail!("Expected a map for verified device info, found '{:?}'", other),
55         };
56 
57         let protected_data = ProtectedData::from_cose_encrypt(
58             session,
59             encrypted_protected_data,
60             &challenge,
61             &verified_device_info,
62             &maced_keys_to_sign.tag,
63         )?;
64 
65         let verified_device_info = match verified_device_info {
66             Value::Map(m) => m,
67             _ => unreachable!("verified device info is always a map"),
68         };
69 
70         Ok(Self::V2 {
71             device_info: DeviceInfo::from_cbor_values(verified_device_info, None)?,
72             challenge,
73             protected_data,
74         })
75     }
76 
v3_from_authenticated_request( session: &Session, mut csr: Vec<Value>, version: i128, ) -> Result<Self>77     fn v3_from_authenticated_request(
78         session: &Session,
79         mut csr: Vec<Value>,
80         version: i128,
81     ) -> Result<Self> {
82         if version != 1 {
83             bail!("Invalid CSR version. Only '1' is supported, found '{}", version);
84         }
85 
86         // CSRs that are uploaded to the backend have an additional unverified info field tacked
87         // onto them. We just ignore that, so if it's there pop it and move on.
88         if csr.len() == 5 {
89             FieldValue::from_optional_value("UnverifiedDeviceInfo", csr.pop());
90         }
91 
92         let signed_data =
93             FieldValue::from_optional_value("SignedData", csr.pop()).into_cose_sign1()?;
94         let dice_chain =
95             Chain::from_value(session, csr.pop().ok_or(anyhow!("Missing DiceCertChain"))?)?;
96 
97         let signed_data_payload = signed_data.payload.context("missing payload in SignedData")?;
98         let csr_payload_value = value_from_bytes(&signed_data_payload)
99             .context("SignedData payload is not valid CBOR")?
100             .as_array_mut()
101             .context("SignedData payload is not a CBOR array")?
102             .pop()
103             .context("Missing CsrPayload in SignedData")?;
104         let csr_payload_bytes = csr_payload_value
105             .as_bytes()
106             .context("CsrPayload (in SignedData) is expected to be encoded CBOR")?
107             .as_slice();
108         let mut csr_payload = match value_from_bytes(csr_payload_bytes)? {
109             Value::Array(a) => a,
110             other => bail!("CsrPayload is expected to be an array, found {other:?}"),
111         };
112 
113         let _keys_to_sign = FieldValue::from_optional_value("KeysToSign", csr_payload.pop());
114         let device_info = FieldValue::from_optional_value("DeviceInfo", csr_payload.pop());
115         let _certificate_type =
116             FieldValue::from_optional_value("CertificateType", csr_payload.pop());
117 
118         let device_info = DeviceInfo::from_cbor_values(device_info.into_map()?, Some(3))?;
119         Ok(Self::V3 { device_info, dice_chain })
120     }
121 }
122 
123 #[cfg(test)]
124 mod tests {
125     // More complete testing happens in the factorycsr module, as the test data
126     // generation spits out full JSON files, not just a CSR. Therefore, only a
127     // minimal number of smoke tests are here.
128     use super::*;
129     use crate::cbor::rkp::csr::testutil::{parse_pem_public_key_or_panic, test_device_info};
130     use crate::dice::{ChainForm, DegenerateChain, DiceMode};
131     use crate::rkp::DeviceInfoVersion;
132     use std::fs;
133 
134     #[test]
from_base64_valid_v2()135     fn from_base64_valid_v2() {
136         let input = fs::read_to_string("testdata/csr/v2_csr.base64").unwrap().trim().to_owned();
137         let csr = Csr::from_base64_cbor(&Session::default(), &input).unwrap();
138 
139         let device_info = testutil::test_device_info(DeviceInfoVersion::V2);
140         let challenge =
141             b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10".to_vec();
142         let pem = "-----BEGIN PUBLIC KEY-----\n\
143                    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERd9pHZbUJ/b4IleUGDN8fs8+LDxE\n\
144                    vG6VX1dkw0sClFs4imbzfXGbocEq74S7TQiyZkd1LhY6HRZnTC51KoGDIA==\n\
145                    -----END PUBLIC KEY-----\n";
146         let subject_public_key = testutil::parse_pem_public_key_or_panic(pem);
147         let degenerate = ChainForm::Degenerate(
148             DegenerateChain::new("self-signed", "self-signed", subject_public_key).unwrap(),
149         );
150         let protected_data = ProtectedData::new(vec![0; 32], degenerate, None);
151         assert_eq!(csr, Csr::V2 { device_info, challenge, protected_data });
152     }
153 
154     #[test]
from_base64_valid_v3()155     fn from_base64_valid_v3() {
156         let input = fs::read_to_string("testdata/csr/v3_csr.base64").unwrap().trim().to_owned();
157         let csr = Csr::from_base64_cbor(&Session::default(), &input).unwrap();
158         if let Csr::V3 { device_info, dice_chain } = csr {
159             assert_eq!(device_info, test_device_info(DeviceInfoVersion::V3));
160             let root_public_key = parse_pem_public_key_or_panic(
161                 "-----BEGIN PUBLIC KEY-----\n\
162                 MCowBQYDK2VwAyEA3FEn/nhqoGOKNok1AJaLfTKI+aFXHf4TfC42vUyPU6s=\n\
163                 -----END PUBLIC KEY-----\n",
164             );
165             assert_eq!(dice_chain.root_public_key(), &root_public_key);
166             let payloads = dice_chain.payloads();
167             assert_eq!(payloads.len(), 1);
168             assert_eq!(payloads[0].issuer(), "issuer");
169             assert_eq!(payloads[0].subject(), "subject");
170             assert_eq!(payloads[0].mode(), DiceMode::Normal);
171             assert_eq!(payloads[0].code_hash(), &[0x55; 32]);
172             let expected_config_hash: &[u8] =
173                 b"\xb8\x96\x54\xe2\x2c\xa4\xd2\x4a\x9c\x0e\x45\x11\xc8\xf2\x63\xf0\
174                   \x66\x0d\x2e\x20\x48\x96\x90\x14\xf4\x54\x63\xc4\xf4\x39\x30\x38";
175             assert_eq!(payloads[0].config_hash(), Some(expected_config_hash));
176             assert_eq!(payloads[0].authority_hash(), &[0x55; 32]);
177             assert_eq!(payloads[0].config_desc().component_name(), Some("component_name"));
178             assert_eq!(payloads[0].config_desc().component_version(), None);
179             assert!(!payloads[0].config_desc().resettable());
180             assert_eq!(payloads[0].config_desc().security_version(), None);
181             assert_eq!(payloads[0].config_desc().extensions(), []);
182         } else {
183             panic!("Parsed CSR was not V3: {:?}", csr);
184         }
185     }
186 
187     #[test]
from_empty_string()188     fn from_empty_string() {
189         let err = Csr::from_base64_cbor(&Session::default(), &"").unwrap_err();
190         assert!(err.to_string().contains("invalid CBOR"));
191     }
192 
193     #[test]
from_garbage()194     fn from_garbage() {
195         let err = Csr::from_base64_cbor(&Session::default(), &"cnViYmlzaAo=").unwrap_err();
196         assert!(err.to_string().contains("invalid CBOR"));
197     }
198 
199     #[test]
from_invalid_base64()200     fn from_invalid_base64() {
201         let err = Csr::from_base64_cbor(&Session::default(), &"not base64").unwrap_err();
202         assert!(err.to_string().contains("invalid base64"));
203     }
204 }
205 
206 #[cfg(test)]
207 pub(crate) mod testutil {
208     use crate::publickey::PublicKey;
209     use crate::rkp::{
210         DeviceInfo, DeviceInfoBootloaderState, DeviceInfoSecurityLevel, DeviceInfoVbState,
211         DeviceInfoVersion,
212     };
213     use openssl::pkey::PKey;
214 
215     // Parse the given PEM-encoded public key into a PublicKey object, panicking on failure.
parse_pem_public_key_or_panic(pem: &str) -> PublicKey216     pub fn parse_pem_public_key_or_panic(pem: &str) -> PublicKey {
217         PKey::public_key_from_pem(pem.as_bytes()).unwrap().try_into().unwrap()
218     }
219 
220     // The test data uses mostly common DeviceInfo fields
test_device_info(version: DeviceInfoVersion) -> DeviceInfo221     pub fn test_device_info(version: DeviceInfoVersion) -> DeviceInfo {
222         DeviceInfo {
223             version,
224             brand: "Google".to_string(),
225             manufacturer: "Google".to_string(),
226             product: "pixel".to_string(),
227             model: "model".to_string(),
228             device: "device".to_string(),
229             vb_state: DeviceInfoVbState::Green,
230             bootloader_state: DeviceInfoBootloaderState::Locked,
231             vbmeta_digest: b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff".to_vec(),
232             os_version: Some("12".to_string()),
233             system_patch_level: 20221025,
234             boot_patch_level: 20221026,
235             vendor_patch_level: 20221027,
236             security_level: Some(DeviceInfoSecurityLevel::Tee),
237             fused: true,
238         }
239     }
240 }
241