Merge "Keystore 2.0: Rename keystore2 enable property."
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index f9295ca..0a5fb29 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -23,6 +23,7 @@
"android.security.apc-rust",
"android.security.authorization-rust",
"android.security.compat-rust",
+ "android.security.remoteprovisioning-rust",
"android.system.keystore2-V1-rust",
"libanyhow",
"libbinder_rs",
@@ -57,12 +58,14 @@
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
+ compile_multilib: "first",
rustlibs: [
"android.hardware.security.keymint-V1-rust",
"android.hardware.security.secureclock-V1-rust",
"android.security.apc-rust",
"android.security.authorization-rust",
"android.security.compat-rust",
+ "android.security.remoteprovisioning-rust",
"android.system.keystore2-V1-rust",
"libandroid_logger",
"libanyhow",
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index fac36e5..36cff16 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -83,3 +83,25 @@
}
},
}
+
+aidl_interface {
+ name: "android.security.remoteprovisioning",
+ srcs: [ "android/security/remoteprovisioning/*.aidl" ],
+ imports: [
+ "android.hardware.security.keymint",
+ ],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: true,
+ sdk_version: "module_current",
+ platform_apis: true,
+ },
+ ndk: {
+ enabled: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl b/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl
new file mode 100644
index 0000000..3528b42
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/AttestationPoolStatus.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020, 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.remoteprovisioning;
+
+/**
+ * This parcelable provides information about the state of the attestation key pool.
+ * @hide
+ */
+parcelable AttestationPoolStatus {
+ /**
+ * The number of signed attestation certificate chains which will expire when the date provided
+ * to keystore to check against is reached.
+ */
+ int expiring;
+ /**
+ * The number of signed attestation certificate chains which have not yet been assigned to an
+ * app. This should be less than or equal to signed keys. The remainder of `signed` -
+ * `unassigned` gives the number of signed keys that have been assigned to an app.
+ */
+ int unassigned;
+ /**
+ * The number of signed attestation keys. This should be less than or equal to `total`. The
+ * remainder of `total` - `attested` gives the number of keypairs available to be sent off to
+ * the server for signing.
+ */
+ int attested;
+ /**
+ * The total number of attestation keys.
+ */
+ int total;
+}
diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
new file mode 100644
index 0000000..d045345
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 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.remoteprovisioning;
+
+import android.hardware.security.keymint.SecurityLevel;
+import android.security.remoteprovisioning.AttestationPoolStatus;
+
+/**
+ * `IRemoteProvisioning` is the interface provided to use the remote provisioning functionality
+ * provided through KeyStore. The intent is for a higher level system component to use these
+ * functions in order to drive the process through which the device can receive functioning
+ * attestation certificates.
+ *
+ * ## Error conditions
+ * Error conditions are reported as service specific errors.
+ * Positive codes correspond to `android.security.remoteprovisioning.ResponseCode`
+ * and indicate error conditions diagnosed by the Keystore 2.0 service.
+ * TODO: Remote Provisioning HAL error code info
+ *
+ * `ResponseCode::PERMISSION_DENIED` if the caller does not have the permissions
+ * to use the RemoteProvisioning API. This permission is defined under access_vectors in SEPolicy
+ * in the keystore2 class: remotely_provision
+ *
+ * `ResponseCode::SYSTEM_ERROR` for any unexpected errors like IO or IPC failures.
+ *
+ * @hide
+ */
+interface IRemoteProvisioning {
+
+ /**
+ * Returns the status of the attestation key pool in the database.
+ *
+ * @param expiredBy The date as seconds since epoch by which to judge expiration status of
+ * certificates.
+ *
+ * @param secLevel The security level to specify which KM instance to get the pool for.
+ *
+ * @return The `AttestationPoolStatus` parcelable contains fields communicating information
+ * relevant to making decisions about when to generate and provision
+ * more attestation keys.
+ */
+ AttestationPoolStatus getPoolStatus(in long expiredBy, in SecurityLevel secLevel);
+
+ /**
+ * This is the primary entry point for beginning a remote provisioning flow. The caller
+ * specifies how many CSRs should be generated and provides an X25519 ECDH public key along
+ * with a challenge to encrypt privacy sensitive portions of the returned CBOR blob and
+ * guarantee freshness of the request to the certifying third party.
+ *
+ * ## Error conditions
+ * `ResponseCode::NO_UNSIGNED_KEYS` if there are no unsigned keypairs in the database that can
+ * be used for the CSRs.
+ *
+ * A RemoteProvisioning HAL response code may indicate backend errors such as failed EEK
+ * verification.
+ *
+ * @param testMode Whether or not the TA implementing the Remote Provisioning HAL should accept
+ * any EEK (Endpoint Encryption Key), or only one signed by a chain
+ * that verifies back to the Root of Trust baked into the TA. True
+ * means that any key is accepted.
+ *
+ * @param numCsr How many certificate signing requests should be generated.
+ *
+ * @param eek A chain of certificates terminating in an X25519 public key, the Endpoint
+ * Encryption Key.
+ *
+ * @param challenge A challenge to be included and MACed in the returned CBOR blob.
+ *
+ * @param secLevel The security level to specify which KM instance from which to generate a
+ * CSR.
+ *
+ * @return A CBOR blob composed of various encrypted/signed elements from the TA in a byte[]
+ */
+ byte[] generateCsr(in boolean testMode, in int numCsr, in byte[] eek, in byte[] challenge,
+ in SecurityLevel secLevel);
+
+ /**
+ * This method provides a way for the returned attestation certificate chains to be provisioned
+ * to the attestation key database. When an app requests an attesation key, it will be assigned
+ * one of these certificate chains along with the corresponding private key.
+ *
+ * @param publicKey The raw public key encoded in the leaf certificate.
+ *
+ * @param cert An X.509, DER encoded certificate chain.
+ *
+ * @param expirationDate The expiration date on the certificate chain, provided by the caller
+ * for convenience.
+ *
+ * @param secLevel The security level representing the KM instance containing the key that this
+ * chain corresponds to.
+ */
+ void provisionCertChain(in byte[] publicKey, in byte[] certs, in long expirationDate,
+ in SecurityLevel secLevel);
+
+ /**
+ * This method allows the caller to instruct KeyStore to generate and store a key pair to be
+ * used for attestation in the `generateCsr` method. The caller should handle spacing out these
+ * requests so as not to jam up the KeyStore work queue.
+ *
+ * @param is_test_mode Instructs the underlying HAL interface to mark the generated key with a
+ * tag to indicate that it's for testing.
+ *
+ * @param secLevel The security level to specify which KM instance should generate a key pair.
+ */
+ void generateKeyPair(in boolean is_test_mode, in SecurityLevel secLevel);
+}
diff --git a/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl b/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl
new file mode 100644
index 0000000..c9877db
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/ResponseCode.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020, 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.remoteprovisioning;
+
+@Backing(type="int")
+/** @hide */
+enum ResponseCode {
+ /**
+ * Returned if there are no keys available in the database to be used in a CSR
+ */
+ NO_UNSIGNED_KEYS = 1,
+ /**
+ * The caller has imrproper SELinux permissions to access the Remote Provisioning API.
+ */
+ PERMISSION_DENIED = 2,
+ /**
+ * An unexpected error occurred, likely with IO or IPC.
+ */
+ SYSTEM_ERROR = 3,
+}
diff --git a/keystore2/src/crypto/certificate_utils.cpp b/keystore2/src/crypto/certificate_utils.cpp
index 4b0dca4..4aed224 100644
--- a/keystore2/src/crypto/certificate_utils.cpp
+++ b/keystore2/src/crypto/certificate_utils.cpp
@@ -42,17 +42,30 @@
DEFINE_OPENSSL_OBJECT_POINTER(AUTHORITY_KEYID);
DEFINE_OPENSSL_OBJECT_POINTER(BASIC_CONSTRAINTS);
DEFINE_OPENSSL_OBJECT_POINTER(X509_ALGOR);
+DEFINE_OPENSSL_OBJECT_POINTER(BIGNUM);
} // namespace
-std::variant<CertUtilsError, X509_NAME_Ptr> makeCommonName(const std::string& name) {
+constexpr const char kDefaultCommonName[] = "Default Common Name";
+
+std::variant<CertUtilsError, X509_NAME_Ptr>
+makeCommonName(std::optional<std::reference_wrapper<const std::vector<uint8_t>>> name) {
+ if (name) {
+ const uint8_t* p = name->get().data();
+ X509_NAME_Ptr x509_name(d2i_X509_NAME(nullptr, &p, name->get().size()));
+ if (!x509_name) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ return x509_name;
+ }
+
X509_NAME_Ptr x509_name(X509_NAME_new());
if (!x509_name) {
- return CertUtilsError::BoringSsl;
+ return CertUtilsError::MemoryAllocation;
}
if (!X509_NAME_add_entry_by_txt(x509_name.get(), "CN", MBSTRING_ASC,
- reinterpret_cast<const uint8_t*>(name.c_str()), name.length(),
- -1 /* loc */, 0 /* set */)) {
+ reinterpret_cast<const uint8_t*>(kDefaultCommonName),
+ sizeof(kDefaultCommonName) - 1, -1 /* loc */, 0 /* set */)) {
return CertUtilsError::BoringSsl;
}
return x509_name;
@@ -159,14 +172,11 @@
// Callers should pass an empty X509_Ptr and check the return value for CertUtilsError::Ok (0)
// before accessing the result.
std::variant<CertUtilsError, X509_Ptr>
-makeCertRump(const uint32_t serial, const char subject[], const uint64_t activeDateTimeMilliSeconds,
+makeCertRump(std::optional<std::reference_wrapper<const std::vector<uint8_t>>> serial,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> subject,
+ const uint64_t activeDateTimeMilliSeconds,
const uint64_t usageExpireDateTimeMilliSeconds) {
- // Sanitize pointer arguments.
- if (!subject || strlen(subject) == 0) {
- return CertUtilsError::InvalidArgument;
- }
-
// Create certificate structure.
X509_Ptr certificate(X509_new());
if (!certificate) {
@@ -178,9 +188,23 @@
return CertUtilsError::BoringSsl;
}
+ BIGNUM_Ptr bn_serial;
+ if (serial) {
+ bn_serial = BIGNUM_Ptr(BN_bin2bn(serial->get().data(), serial->get().size(), nullptr));
+ if (!bn_serial) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ } else {
+ bn_serial = BIGNUM_Ptr(BN_new());
+ if (!bn_serial) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ BN_zero(bn_serial.get());
+ }
+
// Set the certificate serialNumber
ASN1_INTEGER_Ptr serialNumber(ASN1_INTEGER_new());
- if (!serialNumber || !ASN1_INTEGER_set(serialNumber.get(), serial) ||
+ if (!serialNumber || !BN_to_ASN1_INTEGER(bn_serial.get(), serialNumber.get()) ||
!X509_set_serialNumber(certificate.get(), serialNumber.get() /* Don't release; copied */))
return CertUtilsError::BoringSsl;
@@ -215,7 +239,9 @@
}
std::variant<CertUtilsError, X509_Ptr>
-makeCert(const EVP_PKEY* evp_pkey, const uint32_t serial, const char subject[],
+makeCert(const EVP_PKEY* evp_pkey,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> serial,
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> subject,
const uint64_t activeDateTimeMilliSeconds, const uint64_t usageExpireDateTimeMilliSeconds,
bool addSubjectKeyIdEx, std::optional<KeyUsageExtension> keyUsageEx,
std::optional<BasicConstraintsExtension> basicConstraints) {
diff --git a/keystore2/src/crypto/include/certificate_utils.h b/keystore2/src/crypto/include/certificate_utils.h
index 1e80d80..e31ebc4 100644
--- a/keystore2/src/crypto/include/certificate_utils.h
+++ b/keystore2/src/crypto/include/certificate_utils.h
@@ -80,7 +80,7 @@
* `signCert` or `signCertWith`.
* @param evp_pkey The public key that the certificate is issued for.
* @param serial The certificate serial number.
- * @param subject The subject common name.
+ * @param subject The X509 name encoded subject common name.
* @param activeDateTimeMilliSeconds The not before date in epoch milliseconds.
* @param usageExpireDateTimeMilliSeconds The not after date in epoch milliseconds.
* @param addSubjectKeyIdEx If true, adds the subject key id extension.
@@ -89,14 +89,14 @@
* @return CertUtilsError::Ok on success.
*/
std::variant<CertUtilsError, X509_Ptr>
-makeCert(const EVP_PKEY* evp_pkey, //
- const uint32_t serial, //
- const char subject[], //
- const uint64_t activeDateTimeMilliSeconds, //
- const uint64_t usageExpireDateTimeMilliSeconds, //
- bool addSubjectKeyIdEx, //
- std::optional<KeyUsageExtension> keyUsageEx, //
- std::optional<BasicConstraintsExtension> basicConstraints); //
+makeCert(const EVP_PKEY* evp_pkey, //
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> serial, //
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> subject, //
+ const uint64_t activeDateTimeMilliSeconds, //
+ const uint64_t usageExpireDateTimeMilliSeconds, //
+ bool addSubjectKeyIdEx, //
+ std::optional<KeyUsageExtension> keyUsageEx, //
+ std::optional<BasicConstraintsExtension> basicConstraints); //
/**
* Takes the subject name from `signingCert` and sets it as issuer name in `cert`.
diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp
index 2df9ce5..119c3fa 100644
--- a/keystore2/src/crypto/tests/certificate_utils_test.cpp
+++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp
@@ -173,8 +173,8 @@
.isCertificationKey = true,
};
- auto certV = makeCert(pkey.get(), 1, "Me", now_ms - kValidity, now_ms + kValidity,
- true /* subject key id extension */, keyUsage, bcons);
+ auto certV = makeCert(pkey.get(), std::nullopt, std::nullopt, now_ms - kValidity,
+ now_ms + kValidity, true /* subject key id extension */, keyUsage, bcons);
ASSERT_TRUE(std::holds_alternative<X509_Ptr>(certV));
auto& cert = std::get<X509_Ptr>(certV);
ASSERT_TRUE(!setIssuer(cert.get(), cert.get(), true));
@@ -272,8 +272,8 @@
.isCertificationKey = true,
};
- auto certV = makeCert(pkey.get(), 1, "Me", now_ms - kValidity, now_ms + kValidity,
- true /* subject key id extension */, keyUsage, bcons);
+ auto certV = makeCert(pkey.get(), std::nullopt, std::nullopt, now_ms - kValidity,
+ now_ms + kValidity, true /* subject key id extension */, keyUsage, bcons);
ASSERT_TRUE(std::holds_alternative<X509_Ptr>(certV));
auto& cert = std::get<X509_Ptr>(certV);
ASSERT_TRUE(!setIssuer(cert.get(), cert.get(), true));
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 0e7a2d2..3789d28 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -60,6 +60,11 @@
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor,
};
+use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
+ AttestationPoolStatus::AttestationPoolStatus,
+};
+
+use keystore2_crypto::ZVec;
use lazy_static::lazy_static;
use log::error;
#[cfg(not(test))]
@@ -72,12 +77,14 @@
types::{FromSqlError, Value, ValueRef},
Connection, OptionalExtension, ToSql, Transaction, TransactionBehavior, NO_PARAMS,
};
+
use std::{
collections::{HashMap, HashSet},
path::Path,
sync::{Condvar, Mutex},
time::{Duration, SystemTime},
};
+
#[cfg(test)]
use tests::random;
@@ -102,6 +109,12 @@
CreationDate(DateTime) with accessor creation_date,
/// Expiration date for attestation keys.
AttestationExpirationDate(DateTime) with accessor attestation_expiration_date,
+ /// CBOR Blob that represents a COSE_Key and associated metadata needed for remote
+ /// provisioning
+ AttestationMacedPublicKey(Vec<u8>) with accessor attestation_maced_public_key,
+ /// Vector representing the raw public key so results from the server can be matched
+ /// to the right entry
+ AttestationRawPubKey(Vec<u8>) with accessor attestation_raw_pub_key,
// --- ADD NEW META DATA FIELDS HERE ---
// For backwards compatibility add new entries only to
// end of this list and above this comment.
@@ -393,7 +406,7 @@
/// certificate chain components.
/// KeyEntryLoadBits is a bitmap that indicates to `KeystoreDB::load_key_entry`
/// which components shall be loaded from the database if present.
-#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct KeyEntryLoadBits(u32);
impl KeyEntryLoadBits {
@@ -480,7 +493,7 @@
}
/// This type represents a certificate and certificate chain entry for a key.
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct CertificateInfo {
cert: Option<Vec<u8>>,
cert_chain: Option<Vec<u8>>,
@@ -503,6 +516,14 @@
}
}
+/// This type represents a certificate chain with a private key corresponding to the leaf
+/// certificate. TODO(jbires): This will be used in a follow-on CL, for now it's used in the tests.
+#[allow(dead_code)]
+pub struct CertificateChain {
+ private_key: ZVec,
+ cert_chain: ZVec,
+}
+
/// This type represents a Keystore 2.0 key entry.
/// An entry has a unique `id` by which it can be found in the database.
/// It has a security level field, key parameters, and three optional fields
@@ -704,18 +725,19 @@
persistent_path_str.push_str(&persistent_path.to_string_lossy());
let conn = Self::make_connection(&persistent_path_str, &Self::PERBOOT_DB_FILE_NAME)?;
- conn.busy_handler(Some(|_| {
- std::thread::sleep(std::time::Duration::from_micros(50));
- true
- }))
- .context("In KeystoreDB::new: Failed to set busy handler.")?;
- Self::init_tables(&conn)?;
- Ok(Self { conn })
+ // On busy fail Immediately. It is unlikely to succeed given a bug in sqlite.
+ conn.busy_handler(None).context("In KeystoreDB::new: Failed to set busy handler.")?;
+
+ let mut db = Self { conn };
+ db.with_transaction(TransactionBehavior::Immediate, |tx| {
+ Self::init_tables(tx).context("Trying to initialize tables.")
+ })?;
+ Ok(db)
}
- fn init_tables(conn: &Connection) -> Result<()> {
- conn.execute(
+ fn init_tables(tx: &Transaction) -> Result<()> {
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.keyentry (
id INTEGER UNIQUE,
key_type INTEGER,
@@ -728,7 +750,21 @@
)
.context("Failed to initialize \"keyentry\" table.")?;
- conn.execute(
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keyentry_id_index
+ ON keyentry(id);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index keyentry_id_index.")?;
+
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keyentry_domain_namespace_index
+ ON keyentry(domain, namespace, alias);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index keyentry_domain_namespace_index.")?;
+
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.blobentry (
id INTEGER PRIMARY KEY,
subcomponent_type INTEGER,
@@ -738,7 +774,14 @@
)
.context("Failed to initialize \"blobentry\" table.")?;
- conn.execute(
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.blobentry_keyentryid_index
+ ON blobentry(keyentryid);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index blobentry_keyentryid_index.")?;
+
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.keyparameter (
keyentryid INTEGER,
tag INTEGER,
@@ -748,7 +791,14 @@
)
.context("Failed to initialize \"keyparameter\" table.")?;
- conn.execute(
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keyparameter_keyentryid_index
+ ON keyparameter(keyentryid);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index keyparameter_keyentryid_index.")?;
+
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.keymetadata (
keyentryid INTEGER,
tag INTEGER,
@@ -757,7 +807,14 @@
)
.context("Failed to initialize \"keymetadata\" table.")?;
- conn.execute(
+ tx.execute(
+ "CREATE INDEX IF NOT EXISTS persistent.keymetadata_keyentryid_index
+ ON keymetadata(keyentryid);",
+ NO_PARAMS,
+ )
+ .context("Failed to create index keymetadata_keyentryid_index.")?;
+
+ tx.execute(
"CREATE TABLE IF NOT EXISTS persistent.grant (
id INTEGER UNIQUE,
grantee INTEGER,
@@ -769,9 +826,9 @@
//TODO: only drop the following two perboot tables if this is the first start up
//during the boot (b/175716626).
- // conn.execute("DROP TABLE IF EXISTS perboot.authtoken;", NO_PARAMS)
+ // tx.execute("DROP TABLE IF EXISTS perboot.authtoken;", NO_PARAMS)
// .context("Failed to drop perboot.authtoken table")?;
- conn.execute(
+ tx.execute(
"CREATE TABLE IF NOT EXISTS perboot.authtoken (
id INTEGER PRIMARY KEY,
challenge INTEGER,
@@ -786,11 +843,11 @@
)
.context("Failed to initialize \"authtoken\" table.")?;
- // conn.execute("DROP TABLE IF EXISTS perboot.metadata;", NO_PARAMS)
+ // tx.execute("DROP TABLE IF EXISTS perboot.metadata;", NO_PARAMS)
// .context("Failed to drop perboot.metadata table")?;
// metadata table stores certain miscellaneous information required for keystore functioning
// during a boot cycle, as key-value pairs.
- conn.execute(
+ tx.execute(
"CREATE TABLE IF NOT EXISTS perboot.metadata (
key TEXT,
value BLOB,
@@ -805,10 +862,34 @@
let conn =
Connection::open_in_memory().context("Failed to initialize SQLite connection.")?;
- conn.execute("ATTACH DATABASE ? as persistent;", params![persistent_file])
- .context("Failed to attach database persistent.")?;
- conn.execute("ATTACH DATABASE ? as perboot;", params![perboot_file])
- .context("Failed to attach database perboot.")?;
+ loop {
+ if let Err(e) = conn
+ .execute("ATTACH DATABASE ? as persistent;", params![persistent_file])
+ .context("Failed to attach database persistent.")
+ {
+ if Self::is_locked_error(&e) {
+ std::thread::sleep(std::time::Duration::from_micros(500));
+ continue;
+ } else {
+ return Err(e);
+ }
+ }
+ break;
+ }
+ loop {
+ if let Err(e) = conn
+ .execute("ATTACH DATABASE ? as perboot;", params![perboot_file])
+ .context("Failed to attach database perboot.")
+ {
+ if Self::is_locked_error(&e) {
+ std::thread::sleep(std::time::Duration::from_micros(500));
+ continue;
+ } else {
+ return Err(e);
+ }
+ }
+ break;
+ }
Ok(conn)
}
@@ -895,12 +976,14 @@
/// Unlike with `mark_unreferenced`, we don't need to purge grants, because only keys that made
/// it to `KeyLifeCycle::Live` may have grants.
pub fn cleanup_leftovers(&mut self) -> Result<usize> {
- self.conn
- .execute(
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
"UPDATE persistent.keyentry SET state = ? WHERE state = ?;",
params![KeyLifeCycle::Unreferenced, KeyLifeCycle::Existing],
)
- .context("In cleanup_leftovers.")
+ .context("Failed to execute query.")
+ })
+ .context("In cleanup_leftovers.")
}
/// Atomically loads a key entry and associated metadata or creates it using the
@@ -916,98 +999,134 @@
create_new_key: F,
) -> Result<(KeyIdGuard, KeyEntry)>
where
- F: FnOnce() -> Result<(Vec<u8>, KeyMetaData)>,
+ F: Fn() -> Result<(Vec<u8>, KeyMetaData)>,
{
- let tx = self
- .conn
- .transaction_with_behavior(TransactionBehavior::Immediate)
- .context("In get_or_create_key_with: Failed to initialize transaction.")?;
-
- let id = {
- let mut stmt = tx
- .prepare(
- "SELECT id FROM persistent.keyentry
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let id = {
+ let mut stmt = tx
+ .prepare(
+ "SELECT id FROM persistent.keyentry
WHERE
key_type = ?
AND domain = ?
AND namespace = ?
AND alias = ?
AND state = ?;",
- )
- .context("In get_or_create_key_with: Failed to select from keyentry table.")?;
- let mut rows = stmt
- .query(params![KeyType::Super, domain.0, namespace, alias, KeyLifeCycle::Live])
- .context("In get_or_create_key_with: Failed to query from keyentry table.")?;
+ )
+ .context("In get_or_create_key_with: Failed to select from keyentry table.")?;
+ let mut rows = stmt
+ .query(params![KeyType::Super, domain.0, namespace, alias, KeyLifeCycle::Live])
+ .context("In get_or_create_key_with: Failed to query from keyentry table.")?;
- db_utils::with_rows_extract_one(&mut rows, |row| {
- Ok(match row {
- Some(r) => r.get(0).context("Failed to unpack id.")?,
- None => None,
+ db_utils::with_rows_extract_one(&mut rows, |row| {
+ Ok(match row {
+ Some(r) => r.get(0).context("Failed to unpack id.")?,
+ None => None,
+ })
})
- })
- .context("In get_or_create_key_with.")?
- };
+ .context("In get_or_create_key_with.")?
+ };
- let (id, entry) = match id {
- Some(id) => (
- id,
- Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
- .context("In get_or_create_key_with.")?,
- ),
+ let (id, entry) = match id {
+ Some(id) => (
+ id,
+ Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
+ .context("In get_or_create_key_with.")?,
+ ),
- None => {
- let id = Self::insert_with_retry(|id| {
- tx.execute(
- "INSERT into persistent.keyentry
+ None => {
+ let id = Self::insert_with_retry(|id| {
+ tx.execute(
+ "INSERT into persistent.keyentry
(id, key_type, domain, namespace, alias, state, km_uuid)
VALUES(?, ?, ?, ?, ?, ?, ?);",
- params![
- id,
- KeyType::Super,
- domain.0,
- namespace,
- alias,
- KeyLifeCycle::Live,
- km_uuid,
- ],
- )
- })
- .context("In get_or_create_key_with.")?;
+ params![
+ id,
+ KeyType::Super,
+ domain.0,
+ namespace,
+ alias,
+ KeyLifeCycle::Live,
+ km_uuid,
+ ],
+ )
+ })
+ .context("In get_or_create_key_with.")?;
- let (blob, metadata) = create_new_key().context("In get_or_create_key_with.")?;
- Self::set_blob_internal(&tx, id, SubComponentType::KEY_BLOB, Some(&blob))
- .context("In get_of_create_key_with.")?;
- metadata.store_in_db(id, &tx).context("In get_or_create_key_with.")?;
- (
- id,
- KeyEntry {
+ let (blob, metadata) =
+ create_new_key().context("In get_or_create_key_with.")?;
+ Self::set_blob_internal(&tx, id, SubComponentType::KEY_BLOB, Some(&blob))
+ .context("In get_of_create_key_with.")?;
+ metadata.store_in_db(id, &tx).context("In get_or_create_key_with.")?;
+ (
id,
- km_blob: Some(blob),
- metadata,
- pure_cert: false,
- ..Default::default()
- },
- )
- }
- };
- tx.commit().context("In get_or_create_key_with: Failed to commit transaction.")?;
- Ok((KEY_ID_LOCK.get(id), entry))
+ KeyEntry {
+ id,
+ km_blob: Some(blob),
+ metadata,
+ pure_cert: false,
+ ..Default::default()
+ },
+ )
+ }
+ };
+ Ok((KEY_ID_LOCK.get(id), entry))
+ })
+ .context("In get_or_create_key_with.")
}
+ /// SQLite3 seems to hold a shared mutex while running the busy handler when
+ /// waiting for the database file to become available. This makes it
+ /// impossible to successfully recover from a locked database when the
+ /// transaction holding the device busy is in the same process on a
+ /// different connection. As a result the busy handler has to time out and
+ /// fail in order to make progress.
+ ///
+ /// Instead, we set the busy handler to None (return immediately). And catch
+ /// Busy and Locked errors (the latter occur on in memory databases with
+ /// shared cache, e.g., the per-boot database.) and restart the transaction
+ /// after a grace period of half a millisecond.
+ ///
/// Creates a transaction with the given behavior and executes f with the new transaction.
- /// The transaction is committed only if f returns Ok.
+ /// The transaction is committed only if f returns Ok and retried if DatabaseBusy
+ /// or DatabaseLocked is encountered.
fn with_transaction<T, F>(&mut self, behavior: TransactionBehavior, f: F) -> Result<T>
where
- F: FnOnce(&Transaction) -> Result<T>,
+ F: Fn(&Transaction) -> Result<T>,
{
- let tx = self
- .conn
- .transaction_with_behavior(behavior)
- .context("In with_transaction: Failed to initialize transaction.")?;
- f(&tx).and_then(|result| {
- tx.commit().context("In with_transaction: Failed to commit transaction.")?;
- Ok(result)
+ loop {
+ match self
+ .conn
+ .transaction_with_behavior(behavior)
+ .context("In with_transaction.")
+ .and_then(|tx| f(&tx).map(|result| (result, tx)))
+ .and_then(|(result, tx)| {
+ tx.commit().context("In with_transaction: Failed to commit transaction.")?;
+ Ok(result)
+ }) {
+ Ok(result) => break Ok(result),
+ Err(e) => {
+ if Self::is_locked_error(&e) {
+ std::thread::sleep(std::time::Duration::from_micros(500));
+ continue;
+ } else {
+ return Err(e).context("In with_transaction.");
+ }
+ }
+ }
+ }
+ }
+
+ fn is_locked_error(e: &anyhow::Error) -> bool {
+ matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
+ Some(rusqlite::ffi::Error {
+ code: rusqlite::ErrorCode::DatabaseBusy,
+ ..
})
+ | Some(rusqlite::ffi::Error {
+ code: rusqlite::ErrorCode::DatabaseLocked,
+ ..
+ }))
}
/// Creates a new key entry and allocates a new randomized id for the new key.
@@ -1018,8 +1137,8 @@
/// atomic even if key generation is not.
pub fn create_key_entry(
&mut self,
- domain: Domain,
- namespace: i64,
+ domain: &Domain,
+ namespace: &i64,
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
@@ -1030,11 +1149,11 @@
fn create_key_entry_internal(
tx: &Transaction,
- domain: Domain,
- namespace: i64,
+ domain: &Domain,
+ namespace: &i64,
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
- match domain {
+ match *domain {
Domain::APP | Domain::SELINUX => {}
_ => {
return Err(KsError::sys())
@@ -1051,7 +1170,7 @@
id,
KeyType::Client,
domain.0 as u32,
- namespace,
+ *namespace,
KeyLifeCycle::Existing,
km_uuid,
],
@@ -1061,6 +1180,40 @@
))
}
+ /// Creates a new attestation key entry and allocates a new randomized id for the new key.
+ /// The key id gets associated with a domain and namespace later but not with an alias. The
+ /// alias will be used to denote if a key has been signed as each key can only be bound to one
+ /// domain and namespace pairing so there is no need to use them as a value for indexing into
+ /// a key.
+ pub fn create_attestation_key_entry(
+ &mut self,
+ maced_public_key: &[u8],
+ raw_public_key: &[u8],
+ private_key: &[u8],
+ km_uuid: &Uuid,
+ ) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_id = KEY_ID_LOCK.get(
+ Self::insert_with_retry(|id| {
+ tx.execute(
+ "INSERT into persistent.keyentry
+ (id, key_type, domain, namespace, alias, state, km_uuid)
+ VALUES(?, ?, NULL, NULL, NULL, ?, ?);",
+ params![id, KeyType::Attestation, KeyLifeCycle::Live, km_uuid],
+ )
+ })
+ .context("In create_key_entry")?,
+ );
+ Self::set_blob_internal(&tx, key_id.0, SubComponentType::KEY_BLOB, Some(private_key))?;
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::AttestationMacedPublicKey(maced_public_key.to_vec()));
+ metadata.add(KeyMetaEntry::AttestationRawPubKey(raw_public_key.to_vec()));
+ metadata.store_in_db(key_id.0, &tx)?;
+ Ok(())
+ })
+ .context("In create_attestation_key_entry")
+ }
+
/// Set a new blob and associates it with the given key id. Each blob
/// has a sub component type.
/// Each key can have one of each sub component type associated. If more
@@ -1113,10 +1266,10 @@
/// Inserts a collection of key parameters into the `persistent.keyparameter` table
/// and associates them with the given `key_id`.
- pub fn insert_keyparameter<'a>(
+ pub fn insert_keyparameter(
&mut self,
key_id: &KeyIdGuard,
- params: impl IntoIterator<Item = &'a KeyParameter>,
+ params: &[KeyParameter],
) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::insert_keyparameter_internal(tx, key_id, params)
@@ -1124,10 +1277,10 @@
.context("In insert_keyparameter.")
}
- fn insert_keyparameter_internal<'a>(
+ fn insert_keyparameter_internal(
tx: &Transaction,
key_id: &KeyIdGuard,
- params: impl IntoIterator<Item = &'a KeyParameter>,
+ params: &[KeyParameter],
) -> Result<()> {
let mut stmt = tx
.prepare(
@@ -1136,8 +1289,7 @@
)
.context("In insert_keyparameter_internal: Failed to prepare statement.")?;
- let iter = params.into_iter();
- for p in iter {
+ for p in params.iter() {
stmt.insert(params![
key_id.0,
p.get_tag().0,
@@ -1163,6 +1315,343 @@
.context("In insert_key_metadata.")
}
+ /// Stores a signed certificate chain signed by a remote provisioning server, keyed
+ /// on the public key.
+ pub fn store_signed_attestation_certificate_chain(
+ &mut self,
+ raw_public_key: &[u8],
+ cert_chain: &[u8],
+ expiration_date: i64,
+ km_uuid: &Uuid,
+ ) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx
+ .prepare(
+ "SELECT keyentryid
+ FROM persistent.keymetadata
+ WHERE tag = ? AND data = ? AND keyentryid IN
+ (SELECT id
+ FROM persistent.keyentry
+ WHERE
+ alias IS NULL AND
+ domain IS NULL AND
+ namespace IS NULL AND
+ key_type = ? AND
+ km_uuid = ?);",
+ )
+ .context("Failed to store attestation certificate chain.")?;
+ let mut rows = stmt
+ .query(params![
+ KeyMetaData::AttestationRawPubKey,
+ raw_public_key,
+ KeyType::Attestation,
+ km_uuid
+ ])
+ .context("Failed to fetch keyid")?;
+ let key_id = db_utils::with_rows_extract_one(&mut rows, |row| {
+ row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?
+ .get(0)
+ .context("Failed to unpack id.")
+ })
+ .context("Failed to get key_id.")?;
+ let num_updated = tx
+ .execute(
+ "UPDATE persistent.keyentry
+ SET alias = ?
+ WHERE id = ?;",
+ params!["signed", key_id],
+ )
+ .context("Failed to update alias.")?;
+ if num_updated != 1 {
+ return Err(KsError::sys()).context("Alias not updated for the key.");
+ }
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::AttestationExpirationDate(DateTime::from_millis_epoch(
+ expiration_date,
+ )));
+ metadata.store_in_db(key_id, &tx).context("Failed to insert key metadata.")?;
+ Self::set_blob_internal(&tx, key_id, SubComponentType::CERT_CHAIN, Some(cert_chain))
+ .context("Failed to insert cert chain")?;
+ Ok(())
+ })
+ .context("In store_signed_attestation_certificate_chain: ")
+ }
+
+ /// Assigns the next unassigned attestation key to a domain/namespace combo that does not
+ /// currently have a key assigned to it.
+ pub fn assign_attestation_key(
+ &mut self,
+ domain: Domain,
+ namespace: i64,
+ km_uuid: &Uuid,
+ ) -> Result<()> {
+ match domain {
+ Domain::APP | Domain::SELINUX => {}
+ _ => {
+ return Err(KsError::sys()).context(format!(
+ concat!(
+ "In assign_attestation_key: Domain {:?} ",
+ "must be either App or SELinux.",
+ ),
+ domain
+ ));
+ }
+ }
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let result = tx
+ .execute(
+ "UPDATE persistent.keyentry
+ SET domain=?1, namespace=?2
+ WHERE
+ id =
+ (SELECT MIN(id)
+ FROM persistent.keyentry
+ WHERE ALIAS IS NOT NULL
+ AND domain IS NULL
+ AND key_type IS ?3
+ AND state IS ?4
+ AND km_uuid IS ?5)
+ AND
+ (SELECT COUNT(*)
+ FROM persistent.keyentry
+ WHERE domain=?1
+ AND namespace=?2
+ AND key_type IS ?3
+ AND state IS ?4
+ AND km_uuid IS ?5) = 0;",
+ params![
+ domain.0 as u32,
+ namespace,
+ KeyType::Attestation,
+ KeyLifeCycle::Live,
+ km_uuid,
+ ],
+ )
+ .context("Failed to assign attestation key")?;
+ if result != 1 {
+ return Err(KsError::sys()).context(format!(
+ "Expected to update a single entry but instead updated {}.",
+ result
+ ));
+ }
+ Ok(())
+ })
+ .context("In assign_attestation_key: ")
+ }
+
+ /// Retrieves num_keys number of attestation keys that have not yet been signed by a remote
+ /// provisioning server, or the maximum number available if there are not num_keys number of
+ /// entries in the table.
+ pub fn fetch_unsigned_attestation_keys(
+ &mut self,
+ num_keys: i32,
+ km_uuid: &Uuid,
+ ) -> Result<Vec<Vec<u8>>> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx
+ .prepare(
+ "SELECT data
+ FROM persistent.keymetadata
+ WHERE tag = ? AND keyentryid IN
+ (SELECT id
+ FROM persistent.keyentry
+ WHERE
+ alias IS NULL AND
+ domain IS NULL AND
+ namespace IS NULL AND
+ key_type = ? AND
+ km_uuid = ?
+ LIMIT ?);",
+ )
+ .context("Failed to prepare statement")?;
+ let rows = stmt
+ .query_map(
+ params![
+ KeyMetaData::AttestationMacedPublicKey,
+ KeyType::Attestation,
+ km_uuid,
+ num_keys
+ ],
+ |row| Ok(row.get(0)?),
+ )?
+ .collect::<rusqlite::Result<Vec<Vec<u8>>>>()
+ .context("Failed to execute statement")?;
+ Ok(rows)
+ })
+ .context("In fetch_unsigned_attestation_keys")
+ }
+
+ /// Removes any keys that have expired as of the current time. Returns the number of keys
+ /// marked unreferenced that are bound to be garbage collected.
+ pub fn delete_expired_attestation_keys(&mut self) -> Result<i32> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx
+ .prepare(
+ "SELECT keyentryid, data
+ FROM persistent.keymetadata
+ WHERE tag = ? AND keyentryid IN
+ (SELECT id
+ FROM persistent.keyentry
+ WHERE key_type = ?);",
+ )
+ .context("Failed to prepare query")?;
+ let key_ids_to_check = stmt
+ .query_map(
+ params![KeyMetaData::AttestationExpirationDate, KeyType::Attestation],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ )?
+ .collect::<rusqlite::Result<Vec<(i64, DateTime)>>>()
+ .context("Failed to get date metadata")?;
+ let curr_time = DateTime::from_millis_epoch(
+ SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64,
+ );
+ let mut num_deleted = 0;
+ for id in key_ids_to_check.iter().filter(|kt| kt.1 < curr_time).map(|kt| kt.0) {
+ if Self::mark_unreferenced(&tx, id)? {
+ num_deleted += 1;
+ }
+ }
+ Ok(num_deleted)
+ })
+ .context("In delete_expired_attestation_keys: ")
+ }
+
+ /// Counts the number of keys that will expire by the provided epoch date and the number of
+ /// keys not currently assigned to a domain.
+ pub fn get_attestation_pool_status(
+ &mut self,
+ date: i64,
+ km_uuid: &Uuid,
+ ) -> Result<AttestationPoolStatus> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx.prepare(
+ "SELECT data
+ FROM persistent.keymetadata
+ WHERE tag = ? AND keyentryid IN
+ (SELECT id
+ FROM persistent.keyentry
+ WHERE alias IS NOT NULL
+ AND key_type = ?
+ AND km_uuid = ?
+ AND state = ?);",
+ )?;
+ let times = stmt
+ .query_map(
+ params![
+ KeyMetaData::AttestationExpirationDate,
+ KeyType::Attestation,
+ km_uuid,
+ KeyLifeCycle::Live
+ ],
+ |row| Ok(row.get(0)?),
+ )?
+ .collect::<rusqlite::Result<Vec<DateTime>>>()
+ .context("Failed to execute metadata statement")?;
+ let expiring =
+ times.iter().filter(|time| time < &&DateTime::from_millis_epoch(date)).count()
+ as i32;
+ stmt = tx.prepare(
+ "SELECT alias, domain
+ FROM persistent.keyentry
+ WHERE key_type = ? AND km_uuid = ? AND state = ?;",
+ )?;
+ let rows = stmt
+ .query_map(params![KeyType::Attestation, km_uuid, KeyLifeCycle::Live], |row| {
+ Ok((row.get(0)?, row.get(1)?))
+ })?
+ .collect::<rusqlite::Result<Vec<(Option<String>, Option<u32>)>>>()
+ .context("Failed to execute keyentry statement")?;
+ let mut unassigned = 0i32;
+ let mut attested = 0i32;
+ let total = rows.len() as i32;
+ for (alias, domain) in rows {
+ match (alias, domain) {
+ (Some(_alias), None) => {
+ attested += 1;
+ unassigned += 1;
+ }
+ (Some(_alias), Some(_domain)) => {
+ attested += 1;
+ }
+ _ => {}
+ }
+ }
+ Ok(AttestationPoolStatus { expiring, unassigned, attested, total })
+ })
+ .context("In get_attestation_pool_status: ")
+ }
+
+ /// Fetches the private key and corresponding certificate chain assigned to a
+ /// domain/namespace pair. Will either return nothing if the domain/namespace is
+ /// not assigned, or one CertificateChain.
+ pub fn retrieve_attestation_key_and_cert_chain(
+ &mut self,
+ domain: Domain,
+ namespace: i64,
+ km_uuid: &Uuid,
+ ) -> Result<Option<CertificateChain>> {
+ match domain {
+ Domain::APP | Domain::SELINUX => {}
+ _ => {
+ return Err(KsError::sys())
+ .context(format!("Domain {:?} must be either App or SELinux.", domain));
+ }
+ }
+ let mut stmt = self.conn.prepare(
+ "SELECT subcomponent_type, blob
+ FROM persistent.blobentry
+ WHERE keyentryid IN
+ (SELECT id
+ FROM persistent.keyentry
+ WHERE key_type = ?
+ AND domain = ?
+ AND namespace = ?
+ AND state = ?
+ AND km_uuid = ?);",
+ )?;
+ let rows = stmt
+ .query_map(
+ params![
+ KeyType::Attestation,
+ domain.0 as u32,
+ namespace,
+ KeyLifeCycle::Live,
+ km_uuid
+ ],
+ |row| Ok((row.get(0)?, row.get(1)?)),
+ )?
+ .collect::<rusqlite::Result<Vec<(SubComponentType, Vec<u8>)>>>()
+ .context("In retrieve_attestation_key_and_cert_chain: query failed.")?;
+ if rows.is_empty() {
+ return Ok(None);
+ } else if rows.len() != 2 {
+ return Err(KsError::sys()).context(format!(
+ concat!(
+ "In retrieve_attestation_key_and_cert_chain: Expected to get a single attestation",
+ "key chain but instead got {}."),
+ rows.len()
+ ));
+ }
+ let mut km_blob: Vec<u8> = Vec::new();
+ let mut cert_chain_blob: Vec<u8> = Vec::new();
+ for row in rows {
+ let sub_type: SubComponentType = row.0;
+ match sub_type {
+ SubComponentType::KEY_BLOB => {
+ km_blob = row.1;
+ }
+ SubComponentType::CERT_CHAIN => {
+ cert_chain_blob = row.1;
+ }
+ _ => Err(KsError::sys()).context("Unknown or incorrect subcomponent type.")?,
+ }
+ }
+ Ok(Some(CertificateChain {
+ private_key: ZVec::try_from(km_blob)?,
+ cert_chain: ZVec::try_from(cert_chain_blob)?,
+ }))
+ }
+
/// Updates the alias column of the given key id `newid` with the given alias,
/// and atomically, removes the alias, domain, and namespace from another row
/// with the same alias-domain-namespace tuple if such row exits.
@@ -1172,10 +1661,10 @@
tx: &Transaction,
newid: &KeyIdGuard,
alias: &str,
- domain: Domain,
- namespace: i64,
+ domain: &Domain,
+ namespace: &i64,
) -> Result<bool> {
- match domain {
+ match *domain {
Domain::APP | Domain::SELINUX => {}
_ => {
return Err(KsError::sys()).context(format!(
@@ -1202,7 +1691,7 @@
KeyLifeCycle::Live,
newid.0,
domain.0 as u32,
- namespace,
+ *namespace,
KeyLifeCycle::Existing,
],
)
@@ -1221,10 +1710,10 @@
/// fields, and rebinds the given alias to the new key.
/// The boolean returned is a hint for the garbage collector. If true, a key was replaced,
/// is now unreferenced and needs to be collected.
- pub fn store_new_key<'a>(
+ pub fn store_new_key(
&mut self,
- key: KeyDescriptor,
- params: impl IntoIterator<Item = &'a KeyParameter>,
+ key: &KeyDescriptor,
+ params: &[KeyParameter],
blob: &[u8],
cert_info: &CertificateInfo,
metadata: &KeyMetaData,
@@ -1241,7 +1730,7 @@
}
};
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- let key_id = Self::create_key_entry_internal(tx, domain, namespace, km_uuid)
+ let key_id = Self::create_key_entry_internal(tx, &domain, namespace, km_uuid)
.context("Trying to create new key entry.")?;
Self::set_blob_internal(tx, key_id.id(), SubComponentType::KEY_BLOB, Some(blob))
.context("Trying to insert the key blob.")?;
@@ -1261,7 +1750,7 @@
Self::insert_keyparameter_internal(tx, &key_id, params)
.context("Trying to insert key parameters.")?;
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
- let need_gc = Self::rebind_alias(tx, &key_id, &alias, domain, namespace)
+ let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace)
.context("Trying to rebind alias.")?;
Ok((need_gc, key_id))
})
@@ -1273,7 +1762,7 @@
/// the given alias to the new cert.
pub fn store_new_certificate(
&mut self,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
cert: &[u8],
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
@@ -1289,7 +1778,7 @@
}
};
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- let key_id = Self::create_key_entry_internal(tx, domain, namespace, km_uuid)
+ let key_id = Self::create_key_entry_internal(tx, &domain, namespace, km_uuid)
.context("Trying to create new key entry.")?;
Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT_CHAIN, Some(cert))
@@ -1302,7 +1791,7 @@
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
- Self::rebind_alias(tx, &key_id, &alias, domain, namespace)
+ Self::rebind_alias(tx, &key_id, &alias, &domain, namespace)
.context("Trying to rebind alias.")?;
Ok(key_id)
})
@@ -1355,7 +1844,7 @@
/// check and the key id can be used to load further key artifacts.
fn load_access_tuple(
tx: &Transaction,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
key_type: KeyType,
caller_uid: u32,
) -> Result<(i64, KeyDescriptor, Option<KeyPermSet>)> {
@@ -1367,7 +1856,7 @@
// of the caller supplied namespace if the domain field is
// Domain::APP.
Domain::APP | Domain::SELINUX => {
- let mut access_key = key;
+ let mut access_key = key.clone();
if access_key.domain == Domain::APP {
access_key.nspace = caller_uid as i64;
}
@@ -1399,7 +1888,7 @@
))
})
.context("Domain::GRANT.")?;
- Ok((key_id, key, Some(access_vector.into())))
+ Ok((key_id, key.clone(), Some(access_vector.into())))
}
// Domain::KEY_ID. In this case we load the domain and namespace from the
@@ -1451,7 +1940,7 @@
};
let key_id = key.nspace;
- let mut access_key = key;
+ let mut access_key: KeyDescriptor = key.clone();
access_key.domain = domain;
access_key.nspace = namespace;
@@ -1578,11 +2067,40 @@
/// the blob database.
pub fn load_key_entry(
&mut self,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
key_type: KeyType,
load_bits: KeyEntryLoadBits,
caller_uid: u32,
- check_permission: impl FnOnce(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
+ check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
+ ) -> Result<(KeyIdGuard, KeyEntry)> {
+ loop {
+ match self.load_key_entry_internal(
+ key,
+ key_type,
+ load_bits,
+ caller_uid,
+ &check_permission,
+ ) {
+ Ok(result) => break Ok(result),
+ Err(e) => {
+ if Self::is_locked_error(&e) {
+ std::thread::sleep(std::time::Duration::from_micros(500));
+ continue;
+ } else {
+ return Err(e).context("In load_key_entry.");
+ }
+ }
+ }
+ }
+ }
+
+ fn load_key_entry_internal(
+ &mut self,
+ key: &KeyDescriptor,
+ key_type: KeyType,
+ load_bits: KeyEntryLoadBits,
+ caller_uid: u32,
+ check_permission: &impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
) -> Result<(KeyIdGuard, KeyEntry)> {
// KEY ID LOCK 1/2
// If we got a key descriptor with a key id we can get the lock right away.
@@ -1627,15 +2145,16 @@
let key_id_guard = KEY_ID_LOCK.get(key_id);
// Create a new transaction.
- let tx = self.conn.unchecked_transaction().context(
- "In load_key_entry: Failed to initialize transaction. (deferred key lock)",
- )?;
+ let tx = self
+ .conn
+ .unchecked_transaction()
+ .context("In load_key_entry: Failed to initialize transaction.")?;
Self::load_access_tuple(
&tx,
// This time we have to load the key by the retrieved key id, because the
// alias may have been rebound after we rolled back the transaction.
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::KEY_ID,
nspace: key_id,
..Default::default()
@@ -1675,10 +2194,10 @@
/// Returns Ok(true) if a key was marked unreferenced as a hint for the garbage collector.
pub fn unbind_key(
&mut self,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
key_type: KeyType,
caller_uid: u32,
- check_permission: impl FnOnce(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
+ check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
) -> Result<bool> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let (key_id, access_key_descriptor, access_vector) =
@@ -1737,30 +2256,31 @@
/// The key descriptors will have the domain, nspace, and alias field set.
/// Domain must be APP or SELINUX, the caller must make sure of that.
pub fn list(&mut self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
- let mut stmt = self
- .conn
- .prepare(
- "SELECT alias FROM persistent.keyentry
+ self.with_transaction(TransactionBehavior::Deferred, |tx| {
+ let mut stmt = tx
+ .prepare(
+ "SELECT alias FROM persistent.keyentry
WHERE domain = ? AND namespace = ? AND alias IS NOT NULL AND state = ?;",
- )
- .context("In list: Failed to prepare.")?;
+ )
+ .context("In list: Failed to prepare.")?;
- let mut rows = stmt
- .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live])
- .context("In list: Failed to query.")?;
+ let mut rows = stmt
+ .query(params![domain.0 as u32, namespace, KeyLifeCycle::Live])
+ .context("In list: Failed to query.")?;
- let mut descriptors: Vec<KeyDescriptor> = Vec::new();
- db_utils::with_rows_extract_all(&mut rows, |row| {
- descriptors.push(KeyDescriptor {
- domain,
- nspace: namespace,
- alias: Some(row.get(0).context("Trying to extract alias.")?),
- blob: None,
- });
- Ok(())
+ let mut descriptors: Vec<KeyDescriptor> = Vec::new();
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ descriptors.push(KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(row.get(0).context("Trying to extract alias.")?),
+ blob: None,
+ });
+ Ok(())
+ })
+ .context("In list: Failed to extract rows.")?;
+ Ok(descriptors)
})
- .context("In list.")?;
- Ok(descriptors)
}
/// Adds a grant to the grant table.
@@ -1771,104 +2291,97 @@
/// grant id in the namespace field of the resulting KeyDescriptor.
pub fn grant(
&mut self,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
caller_uid: u32,
grantee_uid: u32,
access_vector: KeyPermSet,
- check_permission: impl FnOnce(&KeyDescriptor, &KeyPermSet) -> Result<()>,
+ check_permission: impl Fn(&KeyDescriptor, &KeyPermSet) -> Result<()>,
) -> Result<KeyDescriptor> {
- let tx = self
- .conn
- .transaction_with_behavior(TransactionBehavior::Immediate)
- .context("In grant: Failed to initialize transaction.")?;
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ // Load the key_id and complete the access control tuple.
+ // We ignore the access vector here because grants cannot be granted.
+ // The access vector returned here expresses the permissions the
+ // grantee has if key.domain == Domain::GRANT. But this vector
+ // cannot include the grant permission by design, so there is no way the
+ // subsequent permission check can pass.
+ // We could check key.domain == Domain::GRANT and fail early.
+ // But even if we load the access tuple by grant here, the permission
+ // check denies the attempt to create a grant by grant descriptor.
+ let (key_id, access_key_descriptor, _) =
+ Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid)
+ .context("In grant")?;
- // Load the key_id and complete the access control tuple.
- // We ignore the access vector here because grants cannot be granted.
- // The access vector returned here expresses the permissions the
- // grantee has if key.domain == Domain::GRANT. But this vector
- // cannot include the grant permission by design, so there is no way the
- // subsequent permission check can pass.
- // We could check key.domain == Domain::GRANT and fail early.
- // But even if we load the access tuple by grant here, the permission
- // check denies the attempt to create a grant by grant descriptor.
- let (key_id, access_key_descriptor, _) =
- Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid).context("In grant")?;
+ // Perform access control. It is vital that we return here if the permission
+ // was denied. So do not touch that '?' at the end of the line.
+ // This permission check checks if the caller has the grant permission
+ // for the given key and in addition to all of the permissions
+ // expressed in `access_vector`.
+ check_permission(&access_key_descriptor, &access_vector)
+ .context("In grant: check_permission failed.")?;
- // Perform access control. It is vital that we return here if the permission
- // was denied. So do not touch that '?' at the end of the line.
- // This permission check checks if the caller has the grant permission
- // for the given key and in addition to all of the permissions
- // expressed in `access_vector`.
- check_permission(&access_key_descriptor, &access_vector)
- .context("In grant: check_permission failed.")?;
-
- let grant_id = if let Some(grant_id) = tx
- .query_row(
- "SELECT id FROM persistent.grant
+ let grant_id = if let Some(grant_id) = tx
+ .query_row(
+ "SELECT id FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
- params![key_id, grantee_uid],
- |row| row.get(0),
- )
- .optional()
- .context("In grant: Failed get optional existing grant id.")?
- {
- tx.execute(
- "UPDATE persistent.grant
+ params![key_id, grantee_uid],
+ |row| row.get(0),
+ )
+ .optional()
+ .context("In grant: Failed get optional existing grant id.")?
+ {
+ tx.execute(
+ "UPDATE persistent.grant
SET access_vector = ?
WHERE id = ?;",
- params![i32::from(access_vector), grant_id],
- )
- .context("In grant: Failed to update existing grant.")?;
- grant_id
- } else {
- Self::insert_with_retry(|id| {
- tx.execute(
- "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
- VALUES (?, ?, ?, ?);",
- params![id, grantee_uid, key_id, i32::from(access_vector)],
+ params![i32::from(access_vector), grant_id],
)
- })
- .context("In grant")?
- };
- tx.commit().context("In grant: failed to commit transaction.")?;
+ .context("In grant: Failed to update existing grant.")?;
+ grant_id
+ } else {
+ Self::insert_with_retry(|id| {
+ tx.execute(
+ "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
+ VALUES (?, ?, ?, ?);",
+ params![id, grantee_uid, key_id, i32::from(access_vector)],
+ )
+ })
+ .context("In grant")?
+ };
- Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None })
+ Ok(KeyDescriptor { domain: Domain::GRANT, nspace: grant_id, alias: None, blob: None })
+ })
}
/// This function checks permissions like `grant` and `load_key_entry`
/// before removing a grant from the grant table.
pub fn ungrant(
&mut self,
- key: KeyDescriptor,
+ key: &KeyDescriptor,
caller_uid: u32,
grantee_uid: u32,
- check_permission: impl FnOnce(&KeyDescriptor) -> Result<()>,
+ check_permission: impl Fn(&KeyDescriptor) -> Result<()>,
) -> Result<()> {
- let tx = self
- .conn
- .transaction_with_behavior(TransactionBehavior::Immediate)
- .context("In ungrant: Failed to initialize transaction.")?;
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ // Load the key_id and complete the access control tuple.
+ // We ignore the access vector here because grants cannot be granted.
+ let (key_id, access_key_descriptor, _) =
+ Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid)
+ .context("In ungrant.")?;
- // Load the key_id and complete the access control tuple.
- // We ignore the access vector here because grants cannot be granted.
- let (key_id, access_key_descriptor, _) =
- Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid)
- .context("In ungrant.")?;
+ // Perform access control. We must return here if the permission
+ // was denied. So do not touch the '?' at the end of this line.
+ check_permission(&access_key_descriptor)
+ .context("In grant: check_permission failed.")?;
- // Perform access control. We must return here if the permission
- // was denied. So do not touch the '?' at the end of this line.
- check_permission(&access_key_descriptor).context("In grant: check_permission failed.")?;
-
- tx.execute(
- "DELETE FROM persistent.grant
+ tx.execute(
+ "DELETE FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
- params![key_id, grantee_uid],
- )
- .context("Failed to delete grant.")?;
+ params![key_id, grantee_uid],
+ )
+ .context("Failed to delete grant.")?;
- tx.commit().context("In ungrant: failed to commit transaction.")?;
-
- Ok(())
+ Ok(())
+ })
}
// Generates a random id and passes it to the given function, which will
@@ -1896,8 +2409,8 @@
/// Insert or replace the auth token based on the UNIQUE constraint of the auth token table
pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) -> Result<()> {
- self.conn
- .execute(
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
"INSERT OR REPLACE INTO perboot.authtoken (challenge, user_id, auth_id,
authenticator_type, timestamp, mac, time_received) VALUES(?, ?, ?, ?, ?, ?, ?);",
params![
@@ -1911,7 +2424,8 @@
],
)
.context("In insert_auth_token: failed to insert auth token into the database")?;
- Ok(())
+ Ok(())
+ })
}
/// Find the newest auth token matching the given predicate.
@@ -1955,25 +2469,27 @@
}
/// Insert last_off_body into the metadata table at the initialization of auth token table
- pub fn insert_last_off_body(&self, last_off_body: MonotonicRawTime) -> Result<()> {
- self.conn
- .execute(
+ pub fn insert_last_off_body(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
"INSERT OR REPLACE INTO perboot.metadata (key, value) VALUES (?, ?);",
params!["last_off_body", last_off_body],
)
.context("In insert_last_off_body: failed to insert.")?;
- Ok(())
+ Ok(())
+ })
}
/// Update last_off_body when on_device_off_body is called
- pub fn update_last_off_body(&self, last_off_body: MonotonicRawTime) -> Result<()> {
- self.conn
- .execute(
+ pub fn update_last_off_body(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ tx.execute(
"UPDATE perboot.metadata SET value = ? WHERE key = ?;",
params![last_off_body, "last_off_body"],
)
.context("In update_last_off_body: failed to update.")?;
- Ok(())
+ Ok(())
+ })
}
/// Get last_off_body time when finding auth tokens
@@ -2012,12 +2528,17 @@
use std::sync::Arc;
use std::thread;
use std::time::{Duration, SystemTime};
+ #[cfg(disabled)]
+ use std::time::Instant;
fn new_test_db() -> Result<KeystoreDB> {
let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
- KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
- Ok(KeystoreDB { conn })
+ let mut db = KeystoreDB { conn };
+ db.with_transaction(TransactionBehavior::Immediate, |tx| {
+ KeystoreDB::init_tables(tx).context("Failed to initialize tables.")
+ })?;
+ Ok(db)
}
fn rebind_alias(
@@ -2028,7 +2549,7 @@
namespace: i64,
) -> Result<bool> {
db.with_transaction(TransactionBehavior::Immediate, |tx| {
- KeystoreDB::rebind_alias(tx, newid, alias, domain, namespace)
+ KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace)
})
.context("In rebind_alias.")
}
@@ -2177,7 +2698,7 @@
let temp_dir = TempDir::new("persistent_db_test")?;
let mut db = KeystoreDB::new(temp_dir.path())?;
- db.create_key_entry(Domain::APP, 100, &KEYSTORE_UUID)?;
+ db.create_key_entry(&Domain::APP, &100, &KEYSTORE_UUID)?;
let entries = get_keyentry(&db)?;
assert_eq!(entries.len(), 1);
@@ -2196,8 +2717,8 @@
let mut db = new_test_db()?;
- db.create_key_entry(Domain::APP, 100, &KEYSTORE_UUID)?;
- db.create_key_entry(Domain::SELINUX, 101, &KEYSTORE_UUID)?;
+ db.create_key_entry(&Domain::APP, &100, &KEYSTORE_UUID)?;
+ db.create_key_entry(&Domain::SELINUX, &101, &KEYSTORE_UUID)?;
let entries = get_keyentry(&db)?;
assert_eq!(entries.len(), 2);
@@ -2206,15 +2727,15 @@
// Test that we must pass in a valid Domain.
check_result_is_error_containing_string(
- db.create_key_entry(Domain::GRANT, 102, &KEYSTORE_UUID),
+ db.create_key_entry(&Domain::GRANT, &102, &KEYSTORE_UUID),
"Domain Domain(1) must be either App or SELinux.",
);
check_result_is_error_containing_string(
- db.create_key_entry(Domain::BLOB, 103, &KEYSTORE_UUID),
+ db.create_key_entry(&Domain::BLOB, &103, &KEYSTORE_UUID),
"Domain Domain(3) must be either App or SELinux.",
);
check_result_is_error_containing_string(
- db.create_key_entry(Domain::KEY_ID, 104, &KEYSTORE_UUID),
+ db.create_key_entry(&Domain::KEY_ID, &104, &KEYSTORE_UUID),
"Domain Domain(4) must be either App or SELinux.",
);
@@ -2222,6 +2743,148 @@
}
#[test]
+ fn test_add_unsigned_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let public_key: Vec<u8> = vec![0x01, 0x02, 0x03];
+ let private_key: Vec<u8> = vec![0x04, 0x05, 0x06];
+ let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09];
+ db.create_attestation_key_entry(
+ &public_key,
+ &raw_public_key,
+ &private_key,
+ &KEYSTORE_UUID,
+ )?;
+ let keys = db.fetch_unsigned_attestation_keys(5, &KEYSTORE_UUID)?;
+ assert_eq!(keys.len(), 1);
+ assert_eq!(keys[0], public_key);
+ Ok(())
+ }
+
+ #[test]
+ fn test_store_signed_attestation_certificate_chain() -> Result<()> {
+ let mut db = new_test_db()?;
+ let expiration_date: i64 = 20;
+ let namespace: i64 = 30;
+ let base_byte: u8 = 1;
+ let loaded_values =
+ load_attestation_key_pool(&mut db, expiration_date, namespace, base_byte)?;
+ let chain =
+ db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
+ assert_eq!(true, chain.is_some());
+ let cert_chain = chain.unwrap();
+ assert_eq!(cert_chain.private_key.to_vec(), loaded_values[2]);
+ assert_eq!(cert_chain.cert_chain.to_vec(), loaded_values[1]);
+ Ok(())
+ }
+
+ #[test]
+ fn test_get_attestation_pool_status() -> Result<()> {
+ let mut db = new_test_db()?;
+ let namespace: i64 = 30;
+ load_attestation_key_pool(
+ &mut db, 10, /* expiration */
+ namespace, 0x01, /* base_byte */
+ )?;
+ load_attestation_key_pool(&mut db, 20 /* expiration */, namespace + 1, 0x02)?;
+ load_attestation_key_pool(&mut db, 40 /* expiration */, namespace + 2, 0x03)?;
+ let mut status = db.get_attestation_pool_status(9 /* expiration */, &KEYSTORE_UUID)?;
+ assert_eq!(status.expiring, 0);
+ assert_eq!(status.attested, 3);
+ assert_eq!(status.unassigned, 0);
+ assert_eq!(status.total, 3);
+ assert_eq!(
+ db.get_attestation_pool_status(15 /* expiration */, &KEYSTORE_UUID)?.expiring,
+ 1
+ );
+ assert_eq!(
+ db.get_attestation_pool_status(25 /* expiration */, &KEYSTORE_UUID)?.expiring,
+ 2
+ );
+ assert_eq!(
+ db.get_attestation_pool_status(60 /* expiration */, &KEYSTORE_UUID)?.expiring,
+ 3
+ );
+ let public_key: Vec<u8> = vec![0x01, 0x02, 0x03];
+ let private_key: Vec<u8> = vec![0x04, 0x05, 0x06];
+ let raw_public_key: Vec<u8> = vec![0x07, 0x08, 0x09];
+ let cert_chain: Vec<u8> = vec![0x0a, 0x0b, 0x0c];
+ db.create_attestation_key_entry(
+ &public_key,
+ &raw_public_key,
+ &private_key,
+ &KEYSTORE_UUID,
+ )?;
+ status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?;
+ assert_eq!(status.attested, 3);
+ assert_eq!(status.unassigned, 0);
+ assert_eq!(status.total, 4);
+ db.store_signed_attestation_certificate_chain(
+ &raw_public_key,
+ &cert_chain,
+ 20,
+ &KEYSTORE_UUID,
+ )?;
+ status = db.get_attestation_pool_status(0 /* expiration */, &KEYSTORE_UUID)?;
+ assert_eq!(status.attested, 4);
+ assert_eq!(status.unassigned, 1);
+ assert_eq!(status.total, 4);
+ Ok(())
+ }
+
+ #[test]
+ fn test_remove_expired_certs() -> Result<()> {
+ let mut db = new_test_db()?;
+ let expiration_date: i64 =
+ SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as i64 + 10000;
+ let namespace: i64 = 30;
+ let namespace_del1: i64 = 45;
+ let namespace_del2: i64 = 60;
+ let entry_values = load_attestation_key_pool(
+ &mut db,
+ expiration_date,
+ namespace,
+ 0x01, /* base_byte */
+ )?;
+ load_attestation_key_pool(&mut db, 45, namespace_del1, 0x02)?;
+ load_attestation_key_pool(&mut db, 60, namespace_del2, 0x03)?;
+ assert_eq!(db.delete_expired_attestation_keys()?, 2);
+
+ let mut cert_chain =
+ db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
+ assert_eq!(true, cert_chain.is_some());
+ let value = cert_chain.unwrap();
+ assert_eq!(entry_values[1], value.cert_chain.to_vec());
+ assert_eq!(entry_values[2], value.private_key.to_vec());
+
+ cert_chain = db.retrieve_attestation_key_and_cert_chain(
+ Domain::APP,
+ namespace_del1,
+ &KEYSTORE_UUID,
+ )?;
+ assert_eq!(false, cert_chain.is_some());
+ cert_chain = db.retrieve_attestation_key_and_cert_chain(
+ Domain::APP,
+ namespace_del2,
+ &KEYSTORE_UUID,
+ )?;
+ assert_eq!(false, cert_chain.is_some());
+
+ let mut option_entry = db.get_unreferenced_key()?;
+ assert_eq!(true, option_entry.is_some());
+ let (key_guard, _) = option_entry.unwrap();
+ db.purge_key_entry(key_guard)?;
+
+ option_entry = db.get_unreferenced_key()?;
+ assert_eq!(true, option_entry.is_some());
+ let (key_guard, _) = option_entry.unwrap();
+ db.purge_key_entry(key_guard)?;
+
+ option_entry = db.get_unreferenced_key()?;
+ assert_eq!(false, option_entry.is_some());
+ Ok(())
+ }
+
+ #[test]
fn test_rebind_alias() -> Result<()> {
fn extractor(
ke: &KeyEntryRow,
@@ -2230,8 +2893,8 @@
}
let mut db = new_test_db()?;
- db.create_key_entry(Domain::APP, 42, &KEYSTORE_UUID)?;
- db.create_key_entry(Domain::APP, 42, &KEYSTORE_UUID)?;
+ db.create_key_entry(&Domain::APP, &42, &KEYSTORE_UUID)?;
+ db.create_key_entry(&Domain::APP, &42, &KEYSTORE_UUID)?;
let entries = get_keyentry(&db)?;
assert_eq!(entries.len(), 2);
assert_eq!(
@@ -2324,7 +2987,7 @@
let next_random = 0i64;
let app_granted_key = db
- .grant(app_key.clone(), CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
+ .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
assert_eq!(*a, PVEC1);
assert_eq!(
*k,
@@ -2359,7 +3022,7 @@
};
let selinux_granted_key = db
- .grant(selinux_key.clone(), CALLER_UID, 12, PVEC1, |k, a| {
+ .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| {
assert_eq!(*a, PVEC1);
assert_eq!(
*k,
@@ -2389,7 +3052,7 @@
// This should update the existing grant with PVEC2.
let selinux_granted_key = db
- .grant(selinux_key.clone(), CALLER_UID, 12, PVEC2, |k, a| {
+ .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| {
assert_eq!(*a, PVEC2);
assert_eq!(
*k,
@@ -2443,8 +3106,8 @@
println!("app_key {:?}", app_key);
println!("selinux_key {:?}", selinux_key);
- db.ungrant(app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
- db.ungrant(selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+ db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+ db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
Ok(())
}
@@ -2490,7 +3153,7 @@
.0;
let (_key_guard, key_entry) = db
.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(TEST_ALIAS.to_string()),
@@ -2505,7 +3168,7 @@
assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
db.unbind_key(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(TEST_ALIAS.to_string()),
@@ -2520,7 +3183,7 @@
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(TEST_ALIAS.to_string()),
@@ -2544,7 +3207,7 @@
let mut db = new_test_db()?;
db.store_new_certificate(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2557,7 +3220,7 @@
let (_key_guard, mut key_entry) = db
.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2575,7 +3238,7 @@
assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec()));
db.unbind_key(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2590,7 +3253,7 @@
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2617,7 +3280,7 @@
.0;
let (_key_guard, key_entry) = db
.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::SELINUX,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2632,7 +3295,7 @@
assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
db.unbind_key(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::SELINUX,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2647,7 +3310,7 @@
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::SELINUX,
nspace: 1,
alias: Some(TEST_ALIAS.to_string()),
@@ -2674,7 +3337,7 @@
.0;
let (_, key_entry) = db
.load_key_entry(
- KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
KeyType::Client,
KeyEntryLoadBits::BOTH,
1,
@@ -2685,7 +3348,7 @@
assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
db.unbind_key(
- KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
KeyType::Client,
1,
|_, _| Ok(()),
@@ -2695,7 +3358,7 @@
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
KeyType::Client,
KeyEntryLoadBits::NONE,
1,
@@ -2719,7 +3382,7 @@
db.check_and_update_key_usage_count(key_id)?;
let (_key_guard, key_entry) = db.load_key_entry(
- KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+ &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
KeyType::Client,
KeyEntryLoadBits::BOTH,
1,
@@ -2766,7 +3429,7 @@
let granted_key = db
.grant(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(TEST_ALIAS.to_string()),
@@ -2782,27 +3445,21 @@
debug_dump_grant_table(&mut db)?;
let (_key_guard, key_entry) = db
- .load_key_entry(
- granted_key.clone(),
- KeyType::Client,
- KeyEntryLoadBits::BOTH,
- 2,
- |k, av| {
- assert_eq!(Domain::GRANT, k.domain);
- assert!(av.unwrap().includes(KeyPerm::use_()));
- Ok(())
- },
- )
+ .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
+ assert_eq!(Domain::GRANT, k.domain);
+ assert!(av.unwrap().includes(KeyPerm::use_()));
+ Ok(())
+ })
.unwrap();
assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
- db.unbind_key(granted_key.clone(), KeyType::Client, 2, |_, _| Ok(())).unwrap();
+ db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap();
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- granted_key,
+ &granted_key,
KeyType::Client,
KeyEntryLoadBits::NONE,
2,
@@ -2829,7 +3486,7 @@
.0;
db.grant(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(TEST_ALIAS.to_string()),
@@ -2849,7 +3506,7 @@
let (_, key_entry) = db
.load_key_entry(
- id_descriptor.clone(),
+ &id_descriptor,
KeyType::Client,
KeyEntryLoadBits::BOTH,
GRANTEE_UID,
@@ -2866,7 +3523,7 @@
let (_, key_entry) = db
.load_key_entry(
- id_descriptor.clone(),
+ &id_descriptor,
KeyType::Client,
KeyEntryLoadBits::BOTH,
SOMEONE_ELSE_UID,
@@ -2881,12 +3538,12 @@
assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
- db.unbind_key(id_descriptor.clone(), KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
+ db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
assert_eq!(
Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
db.load_key_entry(
- id_descriptor,
+ &id_descriptor,
KeyType::Client,
KeyEntryLoadBits::NONE,
GRANTEE_UID,
@@ -2913,7 +3570,7 @@
.0;
let (_key_guard, key_entry) = db
.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
@@ -2941,7 +3598,7 @@
let mut db = KeystoreDB::new(temp_dir.path()).unwrap();
assert!(db
.load_key_entry(
- KeyDescriptor {
+ &KeyDescriptor {
domain: Domain::APP,
nspace: 0,
alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
@@ -2977,6 +3634,169 @@
}
#[test]
+ fn teset_database_busy_error_code() {
+ let temp_dir =
+ TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
+
+ let mut db1 = KeystoreDB::new(temp_dir.path()).expect("Failed to open database1.");
+ let mut db2 = KeystoreDB::new(temp_dir.path()).expect("Failed to open database2.");
+
+ let _tx1 = db1
+ .conn
+ .transaction_with_behavior(TransactionBehavior::Immediate)
+ .expect("Failed to create first transaction.");
+
+ let error = db2
+ .conn
+ .transaction_with_behavior(TransactionBehavior::Immediate)
+ .context("Transaction begin failed.")
+ .expect_err("This should fail.");
+ let root_cause = error.root_cause();
+ if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) =
+ root_cause.downcast_ref::<rusqlite::ffi::Error>()
+ {
+ return;
+ }
+ panic!(
+ "Unexpected error {:?} \n{:?} \n{:?}",
+ error,
+ root_cause,
+ root_cause.downcast_ref::<rusqlite::ffi::Error>()
+ )
+ }
+
+ #[cfg(disabled)]
+ #[test]
+ fn test_large_number_of_concurrent_db_manipulations() -> Result<()> {
+ let temp_dir = Arc::new(
+ TempDir::new("test_large_number_of_concurrent_db_manipulations_")
+ .expect("Failed to create temp dir."),
+ );
+
+ let test_begin = Instant::now();
+
+ let mut db = KeystoreDB::new(temp_dir.path()).expect("Failed to open database.");
+ const KEY_COUNT: u32 = 500u32;
+ const OPEN_DB_COUNT: u32 = 50u32;
+
+ let mut actual_key_count = KEY_COUNT;
+ // First insert KEY_COUNT keys.
+ for count in 0..KEY_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
+ actual_key_count = count;
+ break;
+ }
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 1, &alias, None)
+ .expect("Failed to make key entry.");
+ }
+
+ // Insert more keys from a different thread and into a different namespace.
+ let temp_dir1 = temp_dir.clone();
+ let handle1 = thread::spawn(move || {
+ let mut db = KeystoreDB::new(temp_dir1.path()).expect("Failed to open database.");
+
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 2, &alias, None)
+ .expect("Failed to make key entry.");
+ }
+
+ // then unbind them again.
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let key = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(format!("test_alias_{}", count)),
+ blob: None,
+ };
+ db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ // And start unbinding the first set of keys.
+ let temp_dir2 = temp_dir.clone();
+ let handle2 = thread::spawn(move || {
+ let mut db = KeystoreDB::new(temp_dir2.path()).expect("Failed to open database.");
+
+ for count in 0..actual_key_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let key = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(format!("test_alias_{}", count)),
+ blob: None,
+ };
+ db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ let stop_deleting = Arc::new(AtomicU8::new(0));
+ let stop_deleting2 = stop_deleting.clone();
+
+ // And delete anything that is unreferenced keys.
+ let temp_dir3 = temp_dir.clone();
+ let handle3 = thread::spawn(move || {
+ let mut db = KeystoreDB::new(temp_dir3.path()).expect("Failed to open database.");
+
+ while stop_deleting2.load(Ordering::Relaxed) != 1 {
+ while let Some((key_guard, _key)) =
+ db.get_unreferenced_key().expect("Failed to get unreferenced Key.")
+ {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ db.purge_key_entry(key_guard).expect("Failed to purge key.");
+ }
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ }
+ });
+
+ // While a lot of inserting and deleting is going on we have to open database connections
+ // successfully and use them.
+ // This clone is not redundant, because temp_dir needs to be kept alive until db goes
+ // out of scope.
+ #[allow(clippy::redundant_clone)]
+ let temp_dir4 = temp_dir.clone();
+ let handle4 = thread::spawn(move || {
+ for count in 0..OPEN_DB_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let mut db = KeystoreDB::new(temp_dir4.path()).expect("Failed to open database.");
+
+ let alias = format!("test_alias_{}", count);
+ make_test_key_entry(&mut db, Domain::APP, 3, &alias, None)
+ .expect("Failed to make key entry.");
+ let key = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(alias),
+ blob: None,
+ };
+ db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed.");
+ }
+ });
+
+ handle1.join().expect("Thread 1 panicked.");
+ handle2.join().expect("Thread 2 panicked.");
+ handle4.join().expect("Thread 4 panicked.");
+
+ stop_deleting.store(1, Ordering::Relaxed);
+ handle3.join().expect("Thread 3 panicked.");
+
+ Ok(())
+ }
+
+ #[test]
fn list() -> Result<()> {
let temp_dir = TempDir::new("list_test")?;
let mut db = KeystoreDB::new(temp_dir.path())?;
@@ -3040,7 +3860,7 @@
.map(|d| {
let (_, entry) = db
.load_key_entry(
- d,
+ &d,
KeyType::Client,
KeyEntryLoadBits::NONE,
*namespace as u32,
@@ -3115,6 +3935,32 @@
.collect::<Result<Vec<_>>>()
}
+ fn load_attestation_key_pool(
+ db: &mut KeystoreDB,
+ expiration_date: i64,
+ namespace: i64,
+ base_byte: u8,
+ ) -> Result<Vec<Vec<u8>>> {
+ let mut chain: Vec<Vec<u8>> = Vec::new();
+ let public_key: Vec<u8> = vec![base_byte, 0x02 * base_byte];
+ let cert_chain: Vec<u8> = vec![0x03 * base_byte, 0x04 * base_byte];
+ let priv_key: Vec<u8> = vec![0x05 * base_byte, 0x06 * base_byte];
+ let raw_public_key: Vec<u8> = vec![0x0b * base_byte, 0x0c * base_byte];
+ db.create_attestation_key_entry(&public_key, &raw_public_key, &priv_key, &KEYSTORE_UUID)?;
+ db.store_signed_attestation_certificate_chain(
+ &raw_public_key,
+ &cert_chain,
+ expiration_date,
+ &KEYSTORE_UUID,
+ )?;
+ db.assign_attestation_key(Domain::APP, namespace, &KEYSTORE_UUID)?;
+ chain.push(public_key);
+ chain.push(cert_chain);
+ chain.push(priv_key);
+ chain.push(raw_public_key);
+ Ok(chain)
+ }
+
// Note: The parameters and SecurityLevel associations are nonsensical. This
// collection is only used to check if the parameters are preserved as expected by the
// database.
@@ -3351,7 +4197,7 @@
alias: &str,
max_usage_count: Option<i32>,
) -> Result<KeyIdGuard> {
- let key_id = db.create_key_entry(domain, namespace, &KEYSTORE_UUID)?;
+ let key_id = db.create_key_entry(&domain, &namespace, &KEYSTORE_UUID)?;
db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB))?;
db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB))?;
db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB))?;
diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs
index 93de6f2..117dea8 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -948,9 +948,23 @@
#[key_param(tag = RESET_SINCE_ID_ROTATION, field = BoolValue)]
ResetSinceIdRotation,
/// Used to deliver a cryptographic token proving that the user
- /// confirmed a signing request
+ /// confirmed a signing request
#[key_param(tag = CONFIRMATION_TOKEN, field = Blob)]
ConfirmationToken(Vec<u8>),
+ /// Used to deliver the certificate serial number to the KeyMint instance
+ /// certificate generation.
+ #[key_param(tag = CERTIFICATE_SERIAL, field = Blob)]
+ CertificateSerial(Vec<u8>),
+ /// Used to deliver the certificate subject to the KeyMint instance
+ /// certificate generation. This must be DER encoded X509 name.
+ #[key_param(tag = CERTIFICATE_SUBJECT, field = Blob)]
+ CertificateSubject(Vec<u8>),
+ /// Used to deliver the not before date in milliseconds to KeyMint during key generation/import.
+ #[key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)]
+ CertificateNotBefore(i64),
+ /// Used to deliver the not after date in milliseconds to KeyMint during key generation/import.
+ #[key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
+ CertificateNotAfter(i64),
}
}
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 601baf1..93a8b70 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -30,6 +30,8 @@
#include <keymasterV4_1/Keymaster3.h>
#include <keymasterV4_1/Keymaster4.h>
+#include <chrono>
+
#include "certificate_utils.h"
using ::aidl::android::hardware::security::keymint::Algorithm;
@@ -50,6 +52,9 @@
namespace V4_1 = ::android::hardware::keymaster::V4_1;
namespace KMV1 = ::aidl::android::hardware::security::keymint;
+using namespace std::chrono_literals;
+using std::chrono::duration_cast;
+
// Utility functions
ScopedAStatus convertErrorCode(KMV1::ErrorCode result) {
@@ -579,21 +584,34 @@
CBS cbs;
CBS_init(&cbs, key.data(), key.size());
auto pkey = EVP_parse_public_key(&cbs);
+
// makeCert
- // TODO: Get the serial and subject from key params once the tags are added. Also use new tags
- // for the two datetime parameters once we get those.
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> subject;
+ if (auto blob = getParam(keyParams, KMV1::TAG_CERTIFICATE_SUBJECT)) {
+ subject = *blob;
+ }
+
+ std::optional<std::reference_wrapper<const std::vector<uint8_t>>> serial;
+ if (auto blob = getParam(keyParams, KMV1::TAG_CERTIFICATE_SERIAL)) {
+ serial = *blob;
+ }
uint64_t activation = 0;
- if (auto date = getParam(keyParams, KMV1::TAG_ACTIVE_DATETIME)) {
+ if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_BEFORE)) {
activation = *date;
+ } else {
+ return KMV1::ErrorCode::MISSING_NOT_BEFORE;
}
- uint64_t expiration = std::numeric_limits<uint64_t>::max();
- if (auto date = getParam(keyParams, KMV1::TAG_USAGE_EXPIRE_DATETIME)) {
+
+ uint64_t expiration;
+ if (auto date = getParam(keyParams, KMV1::TAG_CERTIFICATE_NOT_AFTER)) {
expiration = *date;
+ } else {
+ return KMV1::ErrorCode::MISSING_NOT_AFTER;
}
auto certOrError = keystore::makeCert(
- pkey, 42, "TODO", activation, expiration, false /* intentionally left blank */,
+ pkey, serial, subject, activation, expiration, false /* intentionally left blank */,
std::nullopt /* intentionally left blank */, std::nullopt /* intentionally left blank */);
if (std::holds_alternative<keystore::CertUtilsError>(certOrError)) {
LOG(ERROR) << __func__ << ": Failed to make certificate";
diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h
index 5fdca91..b36b78a 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -734,7 +734,11 @@
}
break;
case KMV1::Tag::RSA_OAEP_MGF_DIGEST:
- // Does not exist in KM < KeyMint 1.0.
+ case KMV1::Tag::CERTIFICATE_SERIAL:
+ case KMV1::Tag::CERTIFICATE_SUBJECT:
+ case KMV1::Tag::CERTIFICATE_NOT_BEFORE:
+ case KMV1::Tag::CERTIFICATE_NOT_AFTER:
+ // These tags do not exist in KM < KeyMint 1.0.
break;
}
return V4_0::KeyParameter{.tag = V4_0::Tag::INVALID};
diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs
index d264e7a..097e6d4 100644
--- a/keystore2/src/km_compat/lib.rs
+++ b/keystore2/src/km_compat/lib.rs
@@ -76,6 +76,10 @@
creation_result
}
+ // Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime
+ // 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
+ const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64;
+
fn generate_rsa_key(legacy: &dyn IKeyMintDevice, encrypt: bool, attest: bool) -> Vec<u8> {
let mut kps = vec![
KeyParameter {
@@ -97,6 +101,14 @@
tag: Tag::PURPOSE,
value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
},
+ KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_BEFORE,
+ value: KeyParameterValue::DateTime(0),
+ },
+ KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_AFTER,
+ value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+ },
];
if encrypt {
kps.push(KeyParameter {
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 811db91..f9554ea 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -26,6 +26,7 @@
pub mod legacy_blob;
pub mod operation;
pub mod permission;
+pub mod remote_provisioning;
pub mod security_level;
pub mod service;
pub mod utils;
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
new file mode 100644
index 0000000..eb21671
--- /dev/null
+++ b/keystore2/src/remote_provisioning.rs
@@ -0,0 +1,155 @@
+// Copyright 2020, 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.
+
+//! This is the implementation for the remote provisioning AIDL interface between
+//! the network providers for remote provisioning and the system. This interface
+//! allows the caller to prompt the Remote Provisioning HAL to generate keys and
+//! CBOR blobs that can be ferried to a provisioning server that will return
+//! certificate chains signed by some root authority and stored in a keystore SQLite
+//! DB.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+
+use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
+ AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning,
+ IRemoteProvisioning::IRemoteProvisioning,
+};
+use anyhow::Result;
+
+use crate::error::map_or_log_err;
+use crate::globals::{get_keymint_device, DB};
+
+/// Implementation of the IRemoteProvisioning service.
+pub struct RemoteProvisioningService {
+ // TODO(b/179222809): Add the remote provisioner hal aidl interface when available
+}
+
+impl RemoteProvisioningService {
+ /// Creates a new instance of the remote provisioning service
+ pub fn new_native_binder() -> Result<impl IRemoteProvisioning> {
+ let result = BnRemoteProvisioning::new_binder(Self {});
+ Ok(result)
+ }
+
+ /// Populates the AttestationPoolStatus parcelable with information about how many
+ /// certs will be expiring by the date provided in `expired_by` along with how many
+ /// keys have not yet been assigned.
+ pub fn get_pool_status(
+ &self,
+ expired_by: i64,
+ sec_level: SecurityLevel,
+ ) -> Result<AttestationPoolStatus> {
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ DB.with::<_, Result<AttestationPoolStatus>>(|db| {
+ let mut db = db.borrow_mut();
+ Ok(db.get_attestation_pool_status(expired_by, &uuid)?)
+ })
+ }
+
+ /// Generates a CBOR blob which will be assembled by the calling code into a larger
+ /// CBOR blob intended for delivery to a provisioning serever. This blob will contain
+ /// `num_csr` certificate signing requests for attestation keys generated in the TEE,
+ /// along with a server provided `eek` and `challenge`. The endpoint encryption key will
+ /// be used to encrypt the sensitive contents being transmitted to the server, and the
+ /// challenge will ensure freshness. A `test_mode` flag will instruct the remote provisioning
+ /// HAL if it is okay to accept EEKs that aren't signed by something that chains back to the
+ /// baked in root of trust in the underlying IRemotelyProvisionedComponent instance.
+ pub fn generate_csr(
+ &self,
+ _test_mode: bool,
+ _num_csr: i32,
+ _eek: &[u8],
+ _challenge: &[u8],
+ _sec_level: SecurityLevel,
+ ) -> Result<Vec<u8>> {
+ // TODO(b/179222809): implement with actual remote provisioner AIDL when available. For now
+ // it isnice to have some junk values
+ Ok(vec![0, 1, 3, 3])
+ }
+
+ /// Provisions a certificate chain for a key whose CSR was included in generate_csr. The
+ /// `public_key` is used to index into the SQL database in order to insert the `certs` blob
+ /// which represents a PEM encoded X.509 certificate chain. The `expiration_date` is provided
+ /// as a convenience from the caller to avoid having to parse the certificates semantically
+ /// here.
+ pub fn provision_cert_chain(
+ &self,
+ public_key: &[u8],
+ certs: &[u8],
+ expiration_date: i64,
+ sec_level: SecurityLevel,
+ ) -> Result<()> {
+ DB.with::<_, Result<()>>(|db| {
+ let mut db = db.borrow_mut();
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ Ok(db.store_signed_attestation_certificate_chain(
+ public_key,
+ certs, /* DER encoded certificate chain */
+ expiration_date,
+ &uuid,
+ )?)
+ })
+ }
+
+ /// Submits a request to the Remote Provisioner HAL to generate a signing key pair.
+ /// `is_test_mode` indicates whether or not the returned public key should be marked as being
+ /// for testing in order to differentiate them from private keys. If the call is successful,
+ /// the key pair is then added to the database.
+ pub fn generate_key_pair(&self, _is_test_mode: bool, _sec_level: SecurityLevel) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl binder::Interface for RemoteProvisioningService {}
+
+// Implementation of IRemoteProvisioning. See AIDL spec at
+// :aidl/android/security/remoteprovisioning/IRemoteProvisioning.aidl
+impl IRemoteProvisioning for RemoteProvisioningService {
+ fn getPoolStatus(
+ &self,
+ expired_by: i64,
+ sec_level: SecurityLevel,
+ ) -> binder::public_api::Result<AttestationPoolStatus> {
+ map_or_log_err(self.get_pool_status(expired_by, sec_level), Ok)
+ }
+
+ fn generateCsr(
+ &self,
+ test_mode: bool,
+ num_csr: i32,
+ eek: &[u8],
+ challenge: &[u8],
+ sec_level: SecurityLevel,
+ ) -> binder::public_api::Result<Vec<u8>> {
+ map_or_log_err(self.generate_csr(test_mode, num_csr, eek, challenge, sec_level), Ok)
+ }
+
+ fn provisionCertChain(
+ &self,
+ public_key: &[u8],
+ certs: &[u8],
+ expiration_date: i64,
+ sec_level: SecurityLevel,
+ ) -> binder::public_api::Result<()> {
+ map_or_log_err(self.provision_cert_chain(public_key, certs, expiration_date, sec_level), Ok)
+ }
+
+ fn generateKeyPair(
+ &self,
+ is_test_mode: bool,
+ sec_level: SecurityLevel,
+ ) -> binder::public_api::Result<()> {
+ map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok)
+ }
+}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index f6d8108..cc1da98 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -68,6 +68,10 @@
// Blob of 32 zeroes used as empty masking key.
static ZERO_BLOB_32: &[u8] = &[0; 32];
+// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime
+// 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
+const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64;
+
impl KeystoreSecurityLevel {
/// Creates a new security level instance wrapped in a
/// BnKeystoreSecurityLevel proxy object. It also
@@ -140,7 +144,7 @@
let mut db = db.borrow_mut();
let (need_gc, key_id) = db
.store_new_key(
- key,
+ &key,
&key_parameters,
&key_blob,
&cert_info,
@@ -203,7 +207,7 @@
let (key_id_guard, mut key_entry) = DB
.with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
db.borrow_mut().load_key_entry(
- key.clone(),
+ &key,
KeyType::Client,
KeyEntryLoadBits::KM,
caller_uid,
@@ -305,17 +309,39 @@
})
}
- fn add_attestation_parameters(uid: u32, params: &[KeyParameter]) -> Result<Vec<KeyParameter>> {
+ fn add_certificate_parameters(uid: u32, params: &[KeyParameter]) -> Result<Vec<KeyParameter>> {
let mut result = params.to_vec();
+ // If there is an attestation challenge we need to get an application id.
if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) {
let aaid = keystore2_aaid::get_aaid(uid).map_err(|e| {
- anyhow!(format!("In add_attestation_parameters: get_aaid returned status {}.", e))
+ anyhow!(format!("In add_certificate_parameters: get_aaid returned status {}.", e))
})?;
result.push(KeyParameter {
tag: Tag::ATTESTATION_APPLICATION_ID,
value: KeyParameterValue::Blob(aaid),
});
}
+
+ // If we are generating/importing an asymmetric key, we need to make sure
+ // that NOT_BEFORE and NOT_AFTER are present.
+ match params.iter().find(|kp| kp.tag == Tag::ALGORITHM) {
+ Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::RSA) })
+ | Some(KeyParameter { tag: _, value: KeyParameterValue::Algorithm(Algorithm::EC) }) => {
+ if !params.iter().any(|kp| kp.tag == Tag::CERTIFICATE_NOT_BEFORE) {
+ result.push(KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_BEFORE,
+ value: KeyParameterValue::DateTime(0),
+ })
+ }
+ if !params.iter().any(|kp| kp.tag == Tag::CERTIFICATE_NOT_AFTER) {
+ result.push(KeyParameter {
+ tag: Tag::CERTIFICATE_NOT_AFTER,
+ value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+ })
+ }
+ }
+ _ => {}
+ }
Ok(result)
}
@@ -346,7 +372,7 @@
// generate_key requires the rebind permission.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In generate_key.")?;
- let params = Self::add_attestation_parameters(caller_uid, params)
+ let params = Self::add_certificate_parameters(caller_uid, params)
.context("In generate_key: Trying to get aaid.")?;
let km_dev: Box<dyn IKeyMintDevice> = self.keymint.get_interface()?;
@@ -386,7 +412,7 @@
// import_key requires the rebind permission.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_key.")?;
- let params = Self::add_attestation_parameters(caller_uid, params)
+ let params = Self::add_certificate_parameters(caller_uid, params)
.context("In import_key: Trying to get aaid.")?;
let format = params
@@ -459,7 +485,7 @@
let (wrapping_key_id_guard, wrapping_key_entry) = DB
.with(|db| {
db.borrow_mut().load_key_entry(
- wrapping_key.clone(),
+ &wrapping_key,
KeyType::Client,
KeyEntryLoadBits::KM,
caller_uid,
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 72671c6..ab6d621 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -118,7 +118,7 @@
let (key_id_guard, mut key_entry) = DB
.with(|db| {
db.borrow_mut().load_key_entry(
- key.clone(),
+ &key,
KeyType::Client,
KeyEntryLoadBits::PUBLIC,
ThreadState::get_calling_uid(),
@@ -167,7 +167,7 @@
DB.with::<_, Result<()>>(|db| {
let mut db = db.borrow_mut();
let entry = match db.load_key_entry(
- key.clone(),
+ &key,
KeyType::Client,
KeyEntryLoadBits::NONE,
ThreadState::get_calling_uid(),
@@ -219,7 +219,7 @@
check_key_permission(KeyPerm::rebind(), &key, &None)
.context("Caller does not have permission to insert this certificate.")?;
- db.store_new_certificate(key, certificate_chain.unwrap(), &KEYSTORE_UUID)
+ db.store_new_certificate(&key, certificate_chain.unwrap(), &KEYSTORE_UUID)
.context("Failed to insert new certificate.")?;
Ok(())
})
@@ -271,7 +271,7 @@
let caller_uid = ThreadState::get_calling_uid();
let need_gc = DB
.with(|db| {
- db.borrow_mut().unbind_key(key.clone(), KeyType::Client, caller_uid, |k, av| {
+ db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| {
check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.")
})
})
@@ -290,7 +290,7 @@
) -> Result<KeyDescriptor> {
DB.with(|db| {
db.borrow_mut().grant(
- key.clone(),
+ &key,
ThreadState::get_calling_uid(),
grantee_uid as u32,
access_vector,
@@ -302,12 +302,9 @@
fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> {
DB.with(|db| {
- db.borrow_mut().ungrant(
- key.clone(),
- ThreadState::get_calling_uid(),
- grantee_uid as u32,
- |k| check_key_permission(KeyPerm::grant(), k, &None),
- )
+ db.borrow_mut().ungrant(&key, ThreadState::get_calling_uid(), grantee_uid as u32, |k| {
+ check_key_permission(KeyPerm::grant(), k, &None)
+ })
})
.context("In KeystoreService::ungrant.")
}