Alan Stokes | 9fd57b0 | 2024-05-28 09:50:22 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2024 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | use std::error::Error; |
| 18 | use std::ffi::{c_void, CStr}; |
| 19 | use std::fmt::{self, Display}; |
| 20 | use std::iter::FusedIterator; |
| 21 | use std::ptr::{self, NonNull}; |
| 22 | |
| 23 | use vm_payload_bindgen::{ |
| 24 | AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt, |
| 25 | AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey, |
| 26 | AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString, |
| 27 | AVmPayload_requestAttestation, AVmPayload_requestAttestationForTesting, |
| 28 | }; |
| 29 | |
| 30 | /// Holds the result of a successful Virtual Machine attestation request. |
| 31 | /// See [`request_attestation`]. |
| 32 | #[derive(Debug)] |
| 33 | pub struct AttestationResult { |
| 34 | result: NonNull<AVmAttestationResult>, |
| 35 | } |
| 36 | |
| 37 | /// Error type that can be returned from an unsuccessful Virtual Machine attestation request. |
| 38 | /// See [`request_attestation`]. |
| 39 | #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] |
| 40 | pub enum AttestationError { |
| 41 | /// The challenge size was not between 0 and 64 bytes (inclusive). |
| 42 | InvalidChallenge, |
| 43 | /// The attempt to attest the VM failed. A subsequent request may succeed. |
| 44 | AttestationFailed, |
| 45 | /// VM attestation is not supported in the current environment. |
| 46 | AttestationUnsupported, |
| 47 | } |
| 48 | |
| 49 | impl Error for AttestationError {} |
| 50 | |
| 51 | impl Display for AttestationError { |
| 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
| 53 | let status = match self { |
| 54 | Self::InvalidChallenge => AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE, |
| 55 | Self::AttestationFailed => AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED, |
| 56 | Self::AttestationUnsupported => AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED, |
| 57 | }; |
| 58 | // SAFETY: AVmAttestationStatus_toString always returns a non-null pointer to a |
| 59 | // nul-terminated C string with static lifetime (which is valid UTF-8). |
| 60 | let c_str = unsafe { CStr::from_ptr(AVmAttestationStatus_toString(status)) }; |
| 61 | let str = c_str.to_str().expect("Invalid UTF-8 for AVmAttestationStatus"); |
| 62 | f.write_str(str) |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | impl Drop for AttestationResult { |
| 67 | fn drop(&mut self) { |
| 68 | let ptr = self.result.as_ptr(); |
| 69 | |
| 70 | // SAFETY: The `result` field is private, and only populated with a successful call to |
| 71 | // `AVmPayload_requestAttestation`, and not freed elsewhere. |
| 72 | unsafe { AVmAttestationResult_free(ptr) }; |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | // SAFETY: The API functions that accept the `AVmAttestationResult` pointer are all safe to call |
| 77 | // from any thread, including `AVmAttestationResult_free` which is called only on drop. |
| 78 | unsafe impl Send for AttestationResult {} |
| 79 | |
| 80 | // SAFETY: There is no interior mutation here; any future functions that might mutate the data would |
| 81 | // require a non-const pointer and hence need `&mut self` here. The only existing such function is |
| 82 | // `AVmAttestationResult_free` where we take a mutable reference guaranteeing no other references |
| 83 | // exist. The raw API functions are safe to call from any thread. |
| 84 | unsafe impl Sync for AttestationResult {} |
| 85 | |
| 86 | /// Requests the remote attestation of this VM. |
| 87 | /// |
| 88 | /// On success the supplied [`challenge`] will be included in the certificate chain accessible from |
| 89 | /// the [`AttestationResult`]; this can be used as proof of the freshness of the attestation. |
| 90 | /// |
| 91 | /// The challenge should be no more than 64 bytes long or the request will fail. |
| 92 | pub fn request_attestation(challenge: &[u8]) -> Result<AttestationResult, AttestationError> { |
| 93 | let mut result: *mut AVmAttestationResult = ptr::null_mut(); |
| 94 | // SAFETY: We only read the challenge within its bounds and the function does not retain any |
| 95 | // reference to it. |
| 96 | let status = unsafe { |
| 97 | AVmPayload_requestAttestation( |
| 98 | challenge.as_ptr() as *const c_void, |
| 99 | challenge.len(), |
| 100 | &mut result, |
| 101 | ) |
| 102 | }; |
| 103 | AttestationResult::new(status, result) |
| 104 | } |
| 105 | |
| 106 | /// A variant of [`request_attestation`] used for testing purposes. This should not be used by |
| 107 | /// normal VMs, and is not available to app owned VMs. |
| 108 | pub fn request_attestation_for_testing( |
| 109 | challenge: &[u8], |
| 110 | ) -> Result<AttestationResult, AttestationError> { |
| 111 | let mut result: *mut AVmAttestationResult = ptr::null_mut(); |
| 112 | // SAFETY: We only read the challenge within its bounds and the function does not retain any |
| 113 | // reference to it. |
| 114 | let status = unsafe { |
| 115 | AVmPayload_requestAttestationForTesting( |
| 116 | challenge.as_ptr() as *const c_void, |
| 117 | challenge.len(), |
| 118 | &mut result, |
| 119 | ) |
| 120 | }; |
| 121 | AttestationResult::new(status, result) |
| 122 | } |
| 123 | |
| 124 | impl AttestationResult { |
| 125 | fn new( |
| 126 | status: AVmAttestationStatus, |
| 127 | result: *mut AVmAttestationResult, |
| 128 | ) -> Result<AttestationResult, AttestationError> { |
| 129 | match status { |
| 130 | AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => { |
| 131 | Err(AttestationError::InvalidChallenge) |
| 132 | } |
| 133 | AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => { |
| 134 | Err(AttestationError::AttestationFailed) |
| 135 | } |
| 136 | AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => { |
| 137 | Err(AttestationError::AttestationUnsupported) |
| 138 | } |
| 139 | AVmAttestationStatus::ATTESTATION_OK => { |
| 140 | let result = NonNull::new(result) |
| 141 | .expect("Attestation succeeded but the attestation result is null"); |
| 142 | Ok(AttestationResult { result }) |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | fn as_const_ptr(&self) -> *const AVmAttestationResult { |
| 148 | self.result.as_ptr().cast_const() |
| 149 | } |
| 150 | |
| 151 | /// Returns the attested private key. This is the ECDSA P-256 private key corresponding to the |
| 152 | /// public key described by the leaf certificate in the attested |
| 153 | /// [certificate chain](AttestationResult::certificate_chain). It is a DER-encoded |
| 154 | /// `ECPrivateKey` structure as specified in |
| 155 | /// [RFC 5915 s3](https://datatracker.ietf.org/doc/html/rfc5915#section-3). |
| 156 | /// |
| 157 | /// Note: The [`sign_message`](AttestationResult::sign_message) method allows signing with the |
| 158 | /// key without retrieving it. |
| 159 | pub fn private_key(&self) -> Vec<u8> { |
| 160 | let ptr = self.as_const_ptr(); |
| 161 | |
| 162 | let size = |
| 163 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| 164 | // writes no data since we pass a zero size, and null is explicitly allowed for the |
| 165 | // destination in that case. |
| 166 | unsafe { AVmAttestationResult_getPrivateKey(ptr, ptr::null_mut(), 0) }; |
| 167 | |
| 168 | let mut private_key = vec![0u8; size]; |
| 169 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| 170 | // writes within the bounds of `private_key`, which we just allocated so cannot be aliased. |
| 171 | let size = unsafe { |
| 172 | AVmAttestationResult_getPrivateKey( |
| 173 | ptr, |
| 174 | private_key.as_mut_ptr() as *mut c_void, |
| 175 | private_key.len(), |
| 176 | ) |
| 177 | }; |
| 178 | assert_eq!(size, private_key.len()); |
| 179 | private_key |
| 180 | } |
| 181 | |
| 182 | /// Signs the given message using the attested private key. The signature uses ECDSA P-256; the |
| 183 | /// message is first hashed with SHA-256 and then it is signed with the attested EC P-256 |
| 184 | /// [private key](AttestationResult::private_key). |
| 185 | /// |
| 186 | /// The signature is a DER-encoded `ECDSASignature`` structure as described in |
| 187 | /// [RFC 6979](https://datatracker.ietf.org/doc/html/rfc6979). |
| 188 | pub fn sign_message(&self, message: &[u8]) -> Vec<u8> { |
| 189 | let ptr = self.as_const_ptr(); |
| 190 | |
| 191 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| 192 | // writes no data since we pass a zero size, and null is explicitly allowed for the |
| 193 | // destination in that case. |
| 194 | let size = unsafe { |
| 195 | AVmAttestationResult_sign( |
| 196 | ptr, |
| 197 | message.as_ptr() as *const c_void, |
| 198 | message.len(), |
| 199 | ptr::null_mut(), |
| 200 | 0, |
| 201 | ) |
| 202 | }; |
| 203 | |
| 204 | let mut signature = vec![0u8; size]; |
| 205 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| 206 | // writes within the bounds of `signature`, which we just allocated so cannot be aliased. |
| 207 | let size = unsafe { |
| 208 | AVmAttestationResult_sign( |
| 209 | ptr, |
| 210 | message.as_ptr() as *const c_void, |
| 211 | message.len(), |
| 212 | signature.as_mut_ptr() as *mut c_void, |
| 213 | signature.len(), |
| 214 | ) |
| 215 | }; |
| 216 | assert!(size <= signature.len()); |
| 217 | signature.truncate(size); |
| 218 | signature |
| 219 | } |
| 220 | |
| 221 | /// Returns an iterator over the certificates forming the certificate chain for the VM, and its |
| 222 | /// public key, obtained by the attestation process. |
| 223 | /// |
| 224 | /// The certificate chain consists of a sequence of DER-encoded X.509 certificates that form |
| 225 | /// the attestation key's certificate chain. It starts with the leaf certificate covering the |
| 226 | /// attested public key and ends with the root certificate. |
| 227 | pub fn certificate_chain(&self) -> CertIterator { |
| 228 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. |
| 229 | let count = unsafe { AVmAttestationResult_getCertificateCount(self.as_const_ptr()) }; |
| 230 | |
| 231 | CertIterator { result: self, count, current: 0 } |
| 232 | } |
| 233 | |
| 234 | fn certificate(&self, index: usize) -> Vec<u8> { |
| 235 | let ptr = self.as_const_ptr(); |
| 236 | |
| 237 | let size = |
| 238 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| 239 | // writes no data since we pass a zero size, and null is explicitly allowed for the |
| 240 | // destination in that case. The function will panic if `index` is out of range (which |
| 241 | // is safe). |
| 242 | unsafe { AVmAttestationResult_getCertificateAt(ptr, index, ptr::null_mut(), 0) }; |
| 243 | |
| 244 | let mut cert = vec![0u8; size]; |
| 245 | // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| 246 | // writes within the bounds of `cert`, which we just allocated so cannot be aliased. |
| 247 | let size = unsafe { |
| 248 | AVmAttestationResult_getCertificateAt( |
| 249 | ptr, |
| 250 | index, |
| 251 | cert.as_mut_ptr() as *mut c_void, |
| 252 | cert.len(), |
| 253 | ) |
| 254 | }; |
| 255 | assert_eq!(size, cert.len()); |
| 256 | cert |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /// An iterator over the DER-encoded X.509 certificates containin in an [`AttestationResult`]. |
| 261 | /// See [`certificate_chain`](AttestationResult::certificate_chain) for more details. |
| 262 | pub struct CertIterator<'a> { |
| 263 | result: &'a AttestationResult, |
| 264 | count: usize, |
| 265 | current: usize, // Invariant: current <= count |
| 266 | } |
| 267 | |
| 268 | impl<'a> Iterator for CertIterator<'a> { |
| 269 | type Item = Vec<u8>; |
| 270 | |
| 271 | fn next(&mut self) -> Option<Self::Item> { |
| 272 | if self.current < self.count { |
| 273 | let cert = self.result.certificate(self.current); |
| 274 | self.current += 1; |
| 275 | Some(cert) |
| 276 | } else { |
| 277 | None |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 282 | let size = self.count - self.current; |
| 283 | (size, Some(size)) |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | impl<'a> ExactSizeIterator for CertIterator<'a> {} |
| 288 | impl<'a> FusedIterator for CertIterator<'a> {} |