1 // Copyright 2023, 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 //! Generate the attestation key and CSR for client VM in the remote
16 //! attestation.
17 
18 use anyhow::{anyhow, Context, Result};
19 use coset::{
20     iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
21     CoseSignatureBuilder, HeaderBuilder,
22 };
23 use diced_open_dice::{
24     derive_cdi_leaf_priv, sign, DiceArtifacts, PrivateKey, DICE_COSE_KEY_ALG_VALUE,
25 };
26 use openssl::{
27     bn::{BigNum, BigNumContext},
28     ec::{EcGroup, EcKey, EcKeyRef},
29     ecdsa::EcdsaSig,
30     nid::Nid,
31     pkey::Private,
32     sha::sha256,
33 };
34 use service_vm_comm::{Csr, CsrPayload};
35 use zeroize::Zeroizing;
36 
37 /// Key parameters for the attestation key.
38 ///
39 /// See service_vm/comm/client_vm_csr.cddl for more information about the attestation key.
40 const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
41 const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
42 const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
43 const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
44 
45 /// Represents the output of generating the attestation key and CSR for the client VM.
46 pub struct ClientVmAttestationData {
47     /// DER-encoded ECPrivateKey to be attested.
48     pub private_key: Zeroizing<Vec<u8>>,
49 
50     /// CSR containing client VM information and the public key corresponding to the
51     /// private key to be attested.
52     pub csr: Csr,
53 }
54 
55 /// Generates the attestation key and CSR including the public key to be attested for the
56 /// client VM in remote attestation.
generate_attestation_key_and_csr( challenge: &[u8], dice_artifacts: &dyn DiceArtifacts, ) -> Result<ClientVmAttestationData>57 pub fn generate_attestation_key_and_csr(
58     challenge: &[u8],
59     dice_artifacts: &dyn DiceArtifacts,
60 ) -> Result<ClientVmAttestationData> {
61     let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
62     let attestation_key = EcKey::generate(&group)?;
63 
64     let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
65     let private_key = attestation_key.private_key_to_der()?;
66     Ok(ClientVmAttestationData { private_key: Zeroizing::new(private_key), csr })
67 }
68 
build_csr( challenge: &[u8], attestation_key: &EcKeyRef<Private>, dice_artifacts: &dyn DiceArtifacts, ) -> Result<Csr>69 fn build_csr(
70     challenge: &[u8],
71     attestation_key: &EcKeyRef<Private>,
72     dice_artifacts: &dyn DiceArtifacts,
73 ) -> Result<Csr> {
74     // Builds CSR Payload to be signed.
75     let public_key =
76         to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
77     let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
78     let csr_payload = csr_payload.into_cbor_vec()?;
79 
80     // Builds signed CSR Payload.
81     let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
82     let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
83         .to_vec()
84         .context("Failed to serialize signed CSR payload")?;
85 
86     // Builds CSR.
87     let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
88     Ok(Csr { dice_cert_chain, signed_csr_payload })
89 }
90 
build_signed_data( payload: Vec<u8>, cdi_leaf_priv: &PrivateKey, attestation_key: &EcKeyRef<Private>, ) -> Result<CoseSign>91 fn build_signed_data(
92     payload: Vec<u8>,
93     cdi_leaf_priv: &PrivateKey,
94     attestation_key: &EcKeyRef<Private>,
95 ) -> Result<CoseSign> {
96     let dice_key_alg = cbor_util::dice_cose_key_alg(DICE_COSE_KEY_ALG_VALUE)?;
97     let cdi_leaf_sig_headers = build_signature_headers(dice_key_alg);
98     let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
99     let aad = &[];
100     let signed_data = CoseSignBuilder::new()
101         .payload(payload)
102         .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
103             sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
104         })?
105         .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
106             ecdsa_sign_cose(message, attestation_key)
107         })?
108         .build();
109     Ok(signed_data)
110 }
111 
112 /// Builds a signature with headers filled with the provided algorithm.
113 /// The signature data will be filled later when building the signed data.
build_signature_headers(alg: iana::Algorithm) -> CoseSignature114 fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
115     let protected = HeaderBuilder::new().algorithm(alg).build();
116     CoseSignatureBuilder::new().protected(protected).build()
117 }
118 
ecdsa_sign_cose(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>>119 fn ecdsa_sign_cose(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
120     let digest = sha256(message);
121     // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
122     // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
123     let sig = EcdsaSig::sign::<Private>(&digest, key)?;
124     ecdsa_sig_to_cose(&sig)
125 }
126 
ecdsa_sig_to_cose(signature: &EcdsaSig) -> Result<Vec<u8>>127 fn ecdsa_sig_to_cose(signature: &EcdsaSig) -> Result<Vec<u8>> {
128     let mut result = signature.r().to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
129     result.extend_from_slice(&signature.s().to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?);
130     Ok(result)
131 }
132 
get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)>133 fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
134     let mut ctx = BigNumContext::new()?;
135     let mut x = BigNum::new()?;
136     let mut y = BigNum::new()?;
137     key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
138     let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
139     let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
140     Ok((x, y))
141 }
142 
to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey>143 fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
144     let (x, y) = get_affine_coordinates(key)?;
145     Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
146         .algorithm(ATTESTATION_KEY_ALGO)
147         .build())
148 }
149 
150 #[cfg(test)]
151 mod tests {
152     use super::*;
153     use anyhow::bail;
154     use ciborium::Value;
155     use coset::{iana::EnumI64, Label};
156     use hwtrust::{dice, session::Session};
157     use openssl::pkey::Public;
158 
159     /// The following data was generated randomly with urandom.
160     const CHALLENGE: [u8; 16] = [
161         0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
162         0x41,
163     ];
164 
165     #[test]
csr_and_private_key_have_correct_format() -> Result<()>166     fn csr_and_private_key_have_correct_format() -> Result<()> {
167         let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
168 
169         let ClientVmAttestationData { private_key, csr } =
170             generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
171         let ec_private_key = EcKey::private_key_from_der(&private_key)?;
172         let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
173         let aad = &[];
174 
175         // Checks CSR payload.
176         let csr_payload =
177             cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
178         let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
179         let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
180         assert_eq!(expected_csr_payload, csr_payload);
181 
182         // Checks the first signature is signed with CDI_Leaf_Priv.
183         let session = Session::default();
184         let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
185         let public_key = chain.leaf().subject_public_key();
186         cose_sign
187             .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))
188             .context("Verifying CDI_Leaf_Priv signature")?;
189 
190         // Checks the second signature is signed with attestation key.
191         let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
192         let ec_public_key = to_ec_public_key(&attestation_public_key)?;
193         cose_sign
194             .verify_signature(1, aad, |signature, message| {
195                 ecdsa_verify_cose(signature, message, &ec_public_key)
196             })
197             .context("Verifying attestation key signature")?;
198 
199         // Verifies that private key and the public key form a valid key pair.
200         let message = b"test message";
201         let signature = ecdsa_sign_cose(message, &ec_private_key)?;
202         ecdsa_verify_cose(&signature, message, &ec_public_key)
203             .context("Verifying signature with attested key")?;
204 
205         Ok(())
206     }
207 
ecdsa_verify_cose( signature: &[u8], message: &[u8], ec_public_key: &EcKeyRef<Public>, ) -> Result<()>208     fn ecdsa_verify_cose(
209         signature: &[u8],
210         message: &[u8],
211         ec_public_key: &EcKeyRef<Public>,
212     ) -> Result<()> {
213         let coord_bytes = signature.len() / 2;
214         assert_eq!(signature.len(), coord_bytes * 2);
215 
216         let r = BigNum::from_slice(&signature[..coord_bytes])?;
217         let s = BigNum::from_slice(&signature[coord_bytes..])?;
218         let sig = EcdsaSig::from_private_components(r, s)?;
219         let digest = sha256(message);
220         if sig.verify(&digest, ec_public_key)? {
221             Ok(())
222         } else {
223             bail!("Signature does not match")
224         }
225     }
226 
to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>>227     fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
228         check_ec_key_params(cose_key)?;
229         let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
230         let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
231         let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
232         let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
233         key.check_key()?;
234         Ok(key)
235     }
236 
check_ec_key_params(cose_key: &CoseKey) -> Result<()>237     fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
238         assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
239         assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
240         let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
241         assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
242         Ok(())
243     }
244 
get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum>245     fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
246         get_label_value(key, label)?
247             .as_bytes()
248             .map(|v| BigNum::from_slice(&v[..]).unwrap())
249             .ok_or_else(|| anyhow!("Value not a bstr."))
250     }
251 
get_label_value(key: &CoseKey, label: Label) -> Result<&Value>252     fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
253         Ok(&key
254             .params
255             .iter()
256             .find(|(k, _)| k == &label)
257             .ok_or_else(|| anyhow!("Label {:?} not found", label))?
258             .1)
259     }
260 }
261