Add code to support keystore certificate post processing.
Adds the AIDL needed for the post processing service.
Also adds the client which would communicate via the post processing
service. The call to post processing depends on the presence of a
boolean system property which is set during build time.
Bug: 361877215
Test: manual testing
Change-Id: Icfc11d0d83187e036902ed1060038fc627512879
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 88b674e..4da0b6a 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_hardware_backed_security",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_security_license"
@@ -59,6 +60,7 @@
"liblibc",
"liblog_rust",
"libmessage_macro",
+ "libpostprocessor_client",
"librand",
"librkpd_client",
"librustutils",
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index ae3fb18..afc2743 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -123,6 +123,26 @@
}
aidl_interface {
+ name: "android.security.postprocessor",
+ srcs: ["android/security/postprocessor/*.aidl"],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: false,
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+aidl_interface {
name: "android.security.metrics",
srcs: ["android/security/metrics/*.aidl"],
imports: [
diff --git a/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
new file mode 100644
index 0000000..8d9daad
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
@@ -0,0 +1,34 @@
+// 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.
+
+package android.security.postprocessor;
+
+/**
+ * General parcelable for holding the encoded certificates to be used in Keystore. This parcelable
+ * is returned by `IKeystoreCertificatePostProcessor::processKeystoreCertificates`.
+ * @hide
+ */
+@RustDerive(Clone=true)
+parcelable CertificateChain {
+ /**
+ * Holds the DER-encoded representation of the leaf certificate.
+ */
+ byte[] leafCertificate;
+ /**
+ * Holds a byte array containing the concatenation of all the remaining elements of the
+ * certificate chain with root certificate as the last with each certificate represented in
+ * DER-encoded format.
+ */
+ byte[] remainingChain;
+}
diff --git a/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
new file mode 100644
index 0000000..0ceaacb
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.security.postprocessor;
+
+import android.security.postprocessor.CertificateChain;
+
+interface IKeystoreCertificatePostProcessor {
+ /**
+ * Allows implementing services to process the keystore certificates after the certificate
+ * chain has been generated.
+ *
+ * certificateChain holds the chain associated with a newly generated Keystore asymmetric
+ * keypair, where the leafCertificate is the certificate for the public key of generated key.
+ * The remaining attestation certificates are stored as a concatenated byte array of the
+ * encoded certificates with root certificate as the last element.
+ *
+ * Successful calls would get the processed certificate chain which then replaces the original
+ * certificate chain. In case of any failures/exceptions, keystore would fallback to the
+ * original certificate chain.
+ *
+ * @hide
+ */
+ CertificateChain processKeystoreCertificates(in CertificateChain certificateChain);
+}
diff --git a/keystore2/postprocessor_client/Android.bp b/keystore2/postprocessor_client/Android.bp
new file mode 100644
index 0000000..7f0194a
--- /dev/null
+++ b/keystore2/postprocessor_client/Android.bp
@@ -0,0 +1,47 @@
+//
+// 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+ name: "libpostprocessor_client_defaults",
+ crate_name: "postprocessor_client",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "android.security.postprocessor-rust",
+ "libanyhow",
+ "libbinder_rs",
+ "liblog_rust",
+ "libmessage_macro",
+ "libthiserror",
+ ],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
+}
+
+rust_library {
+ name: "libpostprocessor_client",
+ defaults: [
+ "libpostprocessor_client_defaults",
+ ],
+}
diff --git a/keystore2/postprocessor_client/src/lib.rs b/keystore2/postprocessor_client/src/lib.rs
new file mode 100644
index 0000000..8b347f9
--- /dev/null
+++ b/keystore2/postprocessor_client/src/lib.rs
@@ -0,0 +1,109 @@
+// 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.
+
+//! Helper wrapper around PostProcessor interface.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Certificate::Certificate;
+use android_security_postprocessor::aidl::android::security::postprocessor::{
+ CertificateChain::CertificateChain,
+ IKeystoreCertificatePostProcessor::IKeystoreCertificatePostProcessor,
+};
+use anyhow::{Context, Result};
+use binder::{StatusCode, Strong};
+use log::{error, info, warn};
+use message_macro::source_location_msg;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
+
+/// Errors occurred during the interaction with Certificate Processor
+#[derive(Debug, Clone, Copy, thiserror::Error, PartialEq, Eq)]
+#[error("Binder transaction error {0:?}")]
+pub struct Error(pub StatusCode);
+
+static CERT_PROCESSOR_FAILURE: AtomicBool = AtomicBool::new(false);
+
+fn send_certificate_chain_to_processor(
+ attestation_chain: CertificateChain,
+) -> Result<CertificateChain> {
+ let cert_processing_server: Strong<dyn IKeystoreCertificatePostProcessor> = wait_for_interface(
+ "rkp_cert_processor.service".to_string(),
+ )
+ .context(source_location_msg!("While trying to connect to the post processor service."))?;
+ cert_processing_server
+ .processKeystoreCertificates(&attestation_chain)
+ .context(source_location_msg!("While trying to post process certificates."))
+}
+
+/// Processes the keystore certificates after the certificate chain has been generated by Keystore.
+/// More details about this function provided in IKeystoreCertificatePostProcessor.aidl
+pub fn process_certificate_chain(
+ mut certificates: Vec<Certificate>,
+ attestation_certs: Vec<u8>,
+) -> Vec<Certificate> {
+ // If no certificates are provided from keymint, return the original chain.
+ if certificates.is_empty() {
+ error!("No leaf certificate provided.");
+ return vec![Certificate { encodedCertificate: attestation_certs }];
+ }
+
+ if certificates.len() > 1 {
+ warn!("dropping {} unexpected extra certificates after the leaf", certificates.len() - 1);
+ }
+
+ let attestation_chain = CertificateChain {
+ leafCertificate: certificates[0].encodedCertificate.clone(),
+ remainingChain: attestation_certs.clone(),
+ };
+ let result = send_certificate_chain_to_processor(attestation_chain);
+ match result {
+ Ok(certificate_chain) => {
+ info!("Post processing successful. Replacing certificates.");
+ vec![
+ Certificate { encodedCertificate: certificate_chain.leafCertificate },
+ Certificate { encodedCertificate: certificate_chain.remainingChain },
+ ]
+ }
+ Err(err) => {
+ error!("Failed to replace certificates ({err:#?}), falling back to original chain.");
+ certificates.push(Certificate { encodedCertificate: attestation_certs });
+ certificates
+ }
+ }
+}
+
+fn wait_for_interface(
+ service_name: String,
+) -> Result<Strong<dyn IKeystoreCertificatePostProcessor>> {
+ if CERT_PROCESSOR_FAILURE.load(Ordering::Relaxed) {
+ return Err(Error(StatusCode::INVALID_OPERATION).into());
+ }
+ let (sender, receiver) = mpsc::channel();
+ let _t = thread::spawn(move || {
+ if let Err(e) = sender.send(binder::wait_for_interface(&service_name)) {
+ error!("failed to send result of wait_for_interface({service_name}), likely due to timeout: {e:?}");
+ }
+ });
+
+ match receiver.recv_timeout(Duration::from_secs(5)) {
+ Ok(service_binder) => Ok(service_binder?),
+ Err(e) => {
+ error!("Timed out while connecting to post processor service: {e:#?}");
+ // Cert processor has failed. Retry only after reboot.
+ CERT_PROCESSOR_FAILURE.store(true, Ordering::Relaxed);
+ Err(e.into())
+ }
+ }
+}
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index 5e80266..d57ba0c 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -34,6 +34,7 @@
ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
};
use keystore2_selinux as selinux;
+use postprocessor_client::Error as PostProcessorError;
use rkpd_client::Error as RkpdError;
use std::cmp::PartialEq;
use std::ffi::CString;
@@ -103,6 +104,14 @@
}
}
+impl From<PostProcessorError> for Error {
+ fn from(e: PostProcessorError) -> Self {
+ match e {
+ PostProcessorError(s) => Error::BinderTransaction(s),
+ }
+ }
+}
+
/// Maps an `rkpd_client::Error` that is wrapped with an `anyhow::Error` to a keystore2 `Error`.
pub fn wrapped_rkpd_error_to_ks_error(e: &anyhow::Error) -> Error {
match e.downcast_ref::<RkpdError>() {
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 89c0e97..233f2ae 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -64,7 +64,9 @@
KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode,
};
use anyhow::{anyhow, Context, Result};
+use postprocessor_client::process_certificate_chain;
use rkpd_client::store_rkpd_attestation_key;
+use rustutils::system_properties::read_bool;
use std::convert::TryInto;
use std::time::SystemTime;
@@ -632,14 +634,30 @@
log_security_safe_params(¶ms)
))
.map(|(mut result, _)| {
- // The `certificateChain` in a `KeyCreationResult` should normally have one
- // `Certificate` for each certificate in the chain. To avoid having to
- // unnecessarily parse the RKP chain (which is concatenated DER-encoded certs),
- // stuff the whole concatenated chain into a single `Certificate`.
- // This is untangled by `store_new_key()`.
- result
- .certificateChain
- .push(Certificate { encodedCertificate: attestation_certs });
+ if read_bool("remote_provisioning.use_cert_processor", false).unwrap_or(false) {
+ let _wp = self.watch_millis(
+ concat!(
+ "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
+ "calling KeystorePostProcessor::process_certificate_chain",
+ ),
+ 1000, // Post processing may take a little while due to network call.
+ );
+ // process_certificate_chain would either replace the certificate chain if
+ // post-processing is successful or it would fallback to the original chain
+ // on failure. In either case, we should get back the certificate chain
+ // that is fit for storing with the newly generated key.
+ result.certificateChain =
+ process_certificate_chain(result.certificateChain, attestation_certs);
+ } else {
+ // The `certificateChain` in a `KeyCreationResult` should normally have one
+ // `Certificate` for each certificate in the chain. To avoid having to
+ // unnecessarily parse the RKP chain (which is concatenated DER-encoded
+ // certs), stuff the whole concatenated chain into a single `Certificate`.
+ // This is untangled by `store_new_key()`.
+ result
+ .certificateChain
+ .push(Certificate { encodedCertificate: attestation_certs });
+ }
result
})
}