| /* |
| * Copyright 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| use std::error::Error; |
| use std::ffi::{c_void, CStr}; |
| use std::fmt::{self, Display}; |
| use std::iter::FusedIterator; |
| use std::ptr::{self, NonNull}; |
| |
| use vm_payload_bindgen::{ |
| AVmAttestationResult, AVmAttestationResult_free, AVmAttestationResult_getCertificateAt, |
| AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey, |
| AVmAttestationResult_sign, AVmAttestationStatus, AVmAttestationStatus_toString, |
| AVmPayload_requestAttestation, AVmPayload_requestAttestationForTesting, |
| }; |
| |
| /// Holds the result of a successful Virtual Machine attestation request. |
| /// See [`request_attestation`]. |
| #[derive(Debug)] |
| pub struct AttestationResult { |
| result: NonNull<AVmAttestationResult>, |
| } |
| |
| /// Error type that can be returned from an unsuccessful Virtual Machine attestation request. |
| /// See [`request_attestation`]. |
| #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] |
| pub enum AttestationError { |
| /// The challenge size was not between 0 and 64 bytes (inclusive). |
| InvalidChallenge, |
| /// The attempt to attest the VM failed. A subsequent request may succeed. |
| AttestationFailed, |
| /// VM attestation is not supported in the current environment. |
| AttestationUnsupported, |
| } |
| |
| impl Error for AttestationError {} |
| |
| impl Display for AttestationError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
| let status = match self { |
| Self::InvalidChallenge => AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE, |
| Self::AttestationFailed => AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED, |
| Self::AttestationUnsupported => AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED, |
| }; |
| // SAFETY: AVmAttestationStatus_toString always returns a non-null pointer to a |
| // nul-terminated C string with static lifetime (which is valid UTF-8). |
| let c_str = unsafe { CStr::from_ptr(AVmAttestationStatus_toString(status)) }; |
| let str = c_str.to_str().expect("Invalid UTF-8 for AVmAttestationStatus"); |
| f.write_str(str) |
| } |
| } |
| |
| impl Drop for AttestationResult { |
| fn drop(&mut self) { |
| let ptr = self.result.as_ptr(); |
| |
| // SAFETY: The `result` field is private, and only populated with a successful call to |
| // `AVmPayload_requestAttestation`, and not freed elsewhere. |
| unsafe { AVmAttestationResult_free(ptr) }; |
| } |
| } |
| |
| // SAFETY: The API functions that accept the `AVmAttestationResult` pointer are all safe to call |
| // from any thread, including `AVmAttestationResult_free` which is called only on drop. |
| unsafe impl Send for AttestationResult {} |
| |
| // SAFETY: There is no interior mutation here; any future functions that might mutate the data would |
| // require a non-const pointer and hence need `&mut self` here. The only existing such function is |
| // `AVmAttestationResult_free` where we take a mutable reference guaranteeing no other references |
| // exist. The raw API functions are safe to call from any thread. |
| unsafe impl Sync for AttestationResult {} |
| |
| /// Requests the remote attestation of this VM. |
| /// |
| /// On success the supplied [`challenge`] will be included in the certificate chain accessible from |
| /// the [`AttestationResult`]; this can be used as proof of the freshness of the attestation. |
| /// |
| /// The challenge should be no more than 64 bytes long or the request will fail. |
| pub fn request_attestation(challenge: &[u8]) -> Result<AttestationResult, AttestationError> { |
| let mut result: *mut AVmAttestationResult = ptr::null_mut(); |
| // SAFETY: We only read the challenge within its bounds and the function does not retain any |
| // reference to it. |
| let status = unsafe { |
| AVmPayload_requestAttestation( |
| challenge.as_ptr() as *const c_void, |
| challenge.len(), |
| &mut result, |
| ) |
| }; |
| AttestationResult::new(status, result) |
| } |
| |
| /// A variant of [`request_attestation`] used for testing purposes. This should not be used by |
| /// normal VMs, and is not available to app owned VMs. |
| pub fn request_attestation_for_testing( |
| challenge: &[u8], |
| ) -> Result<AttestationResult, AttestationError> { |
| let mut result: *mut AVmAttestationResult = ptr::null_mut(); |
| // SAFETY: We only read the challenge within its bounds and the function does not retain any |
| // reference to it. |
| let status = unsafe { |
| AVmPayload_requestAttestationForTesting( |
| challenge.as_ptr() as *const c_void, |
| challenge.len(), |
| &mut result, |
| ) |
| }; |
| AttestationResult::new(status, result) |
| } |
| |
| impl AttestationResult { |
| fn new( |
| status: AVmAttestationStatus, |
| result: *mut AVmAttestationResult, |
| ) -> Result<AttestationResult, AttestationError> { |
| match status { |
| AVmAttestationStatus::ATTESTATION_ERROR_INVALID_CHALLENGE => { |
| Err(AttestationError::InvalidChallenge) |
| } |
| AVmAttestationStatus::ATTESTATION_ERROR_ATTESTATION_FAILED => { |
| Err(AttestationError::AttestationFailed) |
| } |
| AVmAttestationStatus::ATTESTATION_ERROR_UNSUPPORTED => { |
| Err(AttestationError::AttestationUnsupported) |
| } |
| AVmAttestationStatus::ATTESTATION_OK => { |
| let result = NonNull::new(result) |
| .expect("Attestation succeeded but the attestation result is null"); |
| Ok(AttestationResult { result }) |
| } |
| } |
| } |
| |
| fn as_const_ptr(&self) -> *const AVmAttestationResult { |
| self.result.as_ptr().cast_const() |
| } |
| |
| /// Returns the attested private key. This is the ECDSA P-256 private key corresponding to the |
| /// public key described by the leaf certificate in the attested |
| /// [certificate chain](AttestationResult::certificate_chain). It is a DER-encoded |
| /// `ECPrivateKey` structure as specified in |
| /// [RFC 5915 s3](https://datatracker.ietf.org/doc/html/rfc5915#section-3). |
| /// |
| /// Note: The [`sign_message`](AttestationResult::sign_message) method allows signing with the |
| /// key without retrieving it. |
| pub fn private_key(&self) -> Vec<u8> { |
| let ptr = self.as_const_ptr(); |
| |
| let size = |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| // writes no data since we pass a zero size, and null is explicitly allowed for the |
| // destination in that case. |
| unsafe { AVmAttestationResult_getPrivateKey(ptr, ptr::null_mut(), 0) }; |
| |
| let mut private_key = vec![0u8; size]; |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| // writes within the bounds of `private_key`, which we just allocated so cannot be aliased. |
| let size = unsafe { |
| AVmAttestationResult_getPrivateKey( |
| ptr, |
| private_key.as_mut_ptr() as *mut c_void, |
| private_key.len(), |
| ) |
| }; |
| assert_eq!(size, private_key.len()); |
| private_key |
| } |
| |
| /// Signs the given message using the attested private key. The signature uses ECDSA P-256; the |
| /// message is first hashed with SHA-256 and then it is signed with the attested EC P-256 |
| /// [private key](AttestationResult::private_key). |
| /// |
| /// The signature is a DER-encoded `ECDSASignature`` structure as described in |
| /// [RFC 6979](https://datatracker.ietf.org/doc/html/rfc6979). |
| pub fn sign_message(&self, message: &[u8]) -> Vec<u8> { |
| let ptr = self.as_const_ptr(); |
| |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| // writes no data since we pass a zero size, and null is explicitly allowed for the |
| // destination in that case. |
| let size = unsafe { |
| AVmAttestationResult_sign( |
| ptr, |
| message.as_ptr() as *const c_void, |
| message.len(), |
| ptr::null_mut(), |
| 0, |
| ) |
| }; |
| |
| let mut signature = vec![0u8; size]; |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| // writes within the bounds of `signature`, which we just allocated so cannot be aliased. |
| let size = unsafe { |
| AVmAttestationResult_sign( |
| ptr, |
| message.as_ptr() as *const c_void, |
| message.len(), |
| signature.as_mut_ptr() as *mut c_void, |
| signature.len(), |
| ) |
| }; |
| assert!(size <= signature.len()); |
| signature.truncate(size); |
| signature |
| } |
| |
| /// Returns an iterator over the certificates forming the certificate chain for the VM, and its |
| /// public key, obtained by the attestation process. |
| /// |
| /// The certificate chain consists of a sequence of DER-encoded X.509 certificates that form |
| /// the attestation key's certificate chain. It starts with the leaf certificate covering the |
| /// attested public key and ends with the root certificate. |
| pub fn certificate_chain(&self) -> CertIterator { |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. |
| let count = unsafe { AVmAttestationResult_getCertificateCount(self.as_const_ptr()) }; |
| |
| CertIterator { result: self, count, current: 0 } |
| } |
| |
| fn certificate(&self, index: usize) -> Vec<u8> { |
| let ptr = self.as_const_ptr(); |
| |
| let size = |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function |
| // writes no data since we pass a zero size, and null is explicitly allowed for the |
| // destination in that case. The function will panic if `index` is out of range (which |
| // is safe). |
| unsafe { AVmAttestationResult_getCertificateAt(ptr, index, ptr::null_mut(), 0) }; |
| |
| let mut cert = vec![0u8; size]; |
| // SAFETY: We own the `AVmAttestationResult` pointer, so it is valid. The function only |
| // writes within the bounds of `cert`, which we just allocated so cannot be aliased. |
| let size = unsafe { |
| AVmAttestationResult_getCertificateAt( |
| ptr, |
| index, |
| cert.as_mut_ptr() as *mut c_void, |
| cert.len(), |
| ) |
| }; |
| assert_eq!(size, cert.len()); |
| cert |
| } |
| } |
| |
| /// An iterator over the DER-encoded X.509 certificates containin in an [`AttestationResult`]. |
| /// See [`certificate_chain`](AttestationResult::certificate_chain) for more details. |
| pub struct CertIterator<'a> { |
| result: &'a AttestationResult, |
| count: usize, |
| current: usize, // Invariant: current <= count |
| } |
| |
| impl<'a> Iterator for CertIterator<'a> { |
| type Item = Vec<u8>; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| if self.current < self.count { |
| let cert = self.result.certificate(self.current); |
| self.current += 1; |
| Some(cert) |
| } else { |
| None |
| } |
| } |
| |
| fn size_hint(&self) -> (usize, Option<usize>) { |
| let size = self.count - self.current; |
| (size, Some(size)) |
| } |
| } |
| |
| impl<'a> ExactSizeIterator for CertIterator<'a> {} |
| impl<'a> FusedIterator for CertIterator<'a> {} |