summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bo Zhu <bozhu@google.com> 2018-02-08 21:47:50 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-02-08 21:47:50 +0000
commit5ec5a6929b2944f052d5348e13dc210c093759fb (patch)
treee8dcbccb45125f66fa0411351877745dc0a4a9e5
parent793a823467f48ea8b92f0b53578b603f50a560ba (diff)
parent14d993dc2c0bbdee6a6ae0c270a92107c9f57a84 (diff)
Merge "Accept an XML file containing a list of THM certificates instead of the temporary solution using the raw public key"
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java12
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java76
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java4
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java74
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java134
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java13
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java45
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java202
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java179
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java64
12 files changed, 816 insertions, 99 deletions
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 662ffc814390..dee24c7c0450 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -44,6 +44,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
+import java.security.cert.CertPath;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -176,8 +177,17 @@ public class KeySyncTask implements Runnable {
return;
}
- PublicKey publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
+ PublicKey publicKey;
+ CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
recoveryAgentUid);
+ if (certPath != null) {
+ Log.d(TAG, "Using the public key in stored CertPath for syncing");
+ publicKey = certPath.getCertificates().get(0).getPublicKey();
+ } else {
+ Log.d(TAG, "Using the stored raw public key for syncing");
+ publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
+ recoveryAgentUid);
+ }
if (publicKey == null) {
Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
return;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 33e767fe1a8f..9f822688a8c0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -23,16 +23,15 @@ import static android.security.keystore.RecoveryController.ERROR_NO_SNAPSHOT_PEN
import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
import static android.security.keystore.RecoveryController.ERROR_SESSION_EXPIRED;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
-import android.Manifest;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
-
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryController;
@@ -43,6 +42,10 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
+import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
+import com.android.server.locksettings.recoverablekeystore.certificate.TrustedRootCert;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -52,8 +55,10 @@ import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
import java.security.UnrecoverableKeyException;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateEncodingException;
+import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
@@ -152,18 +157,67 @@ public class RecoverableKeyStoreManager {
}
public void initRecoveryService(
- @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
+ @NonNull String rootCertificateAlias, @NonNull byte[] recoveryServiceCertFile)
throws RemoteException {
checkRecoverKeyStorePermission();
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
- // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
- PublicKey publicKey;
+
+ // TODO: Check the public-key signature on the whole file before parsing it
+
+ CertXml certXml;
+ try {
+ certXml = CertXml.parse(recoveryServiceCertFile);
+ } catch (CertParsingException e) {
+ // TODO: Do not use raw key bytes anymore once the other components are updated
+ Log.d(TAG, "Failed to parse the cert file", e);
+ PublicKey publicKey = parseEcPublicKey(recoveryServiceCertFile);
+ if (mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey) > 0) {
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
+ return;
+ }
+
+ // Check serial number
+ long newSerial = certXml.getSerial();
+ Long oldSerial = mDatabase.getRecoveryServiceCertSerial(userId, uid);
+ if (oldSerial != null && oldSerial >= newSerial) {
+ if (oldSerial == newSerial) {
+ Log.i(TAG, "The cert file serial number is the same, so skip updating.");
+ } else {
+ Log.e(TAG, "The cert file serial number is older than the one in database.");
+ }
+ return;
+ }
+ Log.i(TAG, "Updating the certificate with the new serial number " + newSerial);
+
+ CertPath certPath;
+ try {
+ Log.d(TAG, "Getting and validating a random endpoint certificate");
+ certPath = certXml.getRandomEndpointCert(TrustedRootCert.TRUSTED_ROOT_CERT);
+ } catch (CertValidationException e) {
+ Log.e(TAG, "Invalid endpoint cert", e);
+ throw new ServiceSpecificException(
+ ERROR_BAD_CERTIFICATE_FORMAT, "Failed to validate certificate.");
+ }
+ try {
+ Log.d(TAG, "Saving the randomly chosen endpoint certificate to database");
+ if (mDatabase.setRecoveryServiceCertPath(userId, uid, certPath) > 0) {
+ mDatabase.setRecoveryServiceCertSerial(userId, uid, newSerial);
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
+ } catch (CertificateEncodingException e) {
+ Log.e(TAG, "Failed to encode CertPath", e);
+ throw new ServiceSpecificException(
+ ERROR_BAD_CERTIFICATE_FORMAT, "Failed to encode CertPath.");
+ }
+ }
+
+ private PublicKey parseEcPublicKey(@NonNull byte[] bytes) throws ServiceSpecificException {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
- // TODO: Randomly choose a key from the list -- right now we just use the whole input
- X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
- publicKey = kf.generatePublic(pkSpec);
+ X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
+ return kf.generatePublic(pkSpec);
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "EC algorithm not available. AOSP must support this.", e);
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
@@ -171,10 +225,6 @@ public class RecoverableKeyStoreManager {
throw new ServiceSpecificException(
ERROR_BAD_CERTIFICATE_FORMAT, "Not a valid X509 certificate.");
}
- long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
- if (updatedRows > 0) {
- mDatabase.setShouldCreateSnapshot(userId, uid, true);
- }
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
index fea6733dd1f5..09ec5ad1d5dd 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -68,7 +68,7 @@ import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/** Utility functions related to parsing and validating public-key certificates. */
-final class CertUtils {
+public final class CertUtils {
private static final String CERT_FORMAT = "X.509";
private static final String CERT_PATH_ALG = "PKIX";
@@ -217,7 +217,7 @@ final class CertUtils {
* @return the decoding decoding result
* @throws CertParsingException if the input string is not a properly base64-encoded string
*/
- static byte[] decodeBase64(String str) throws CertParsingException {
+ public static byte[] decodeBase64(String str) throws CertParsingException {
try {
return Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
new file mode 100644
index 000000000000..7195d628e4bd
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/TrustedRootCert.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.locksettings.recoverablekeystore.certificate;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Holds the X509 certificate of the trusted root CA cert for the recoverable key store service.
+ *
+ * TODO: Read the certificate from a PEM file directly and remove this class.
+ */
+public final class TrustedRootCert {
+
+ private static final String TRUSTED_ROOT_CERT_BASE64 = ""
+ + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV"
+ + "BAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDIxOTM5MTRaFw0zODAx"
+ + "MjgxOTM5MTRaMCAxHjAcBgNVBAMMFUdvb2dsZSBDcnlwdEF1dGhWYXVsdDCCAiIw"
+ + "DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2OT5i40/H7LINg/lq/0G0hR65P"
+ + "Q4Mud3OnuVt6UIYV2T18+v6qW1yJd5FcnND/ZKPau4aUAYklqJuSVjOXQD0BjgS2"
+ + "98Xa4dSn8Ci1rUR+5tdmrxqbYUdT2ZvJIUMMR6fRoqi+LlAbKECrV+zYQTyLU68w"
+ + "V66hQpAButjJKiZzkXjmKLfJ5IWrNEn17XM988rk6qAQn/BYCCQGf3rQuJeksGmA"
+ + "N1lJOwNYxmWUyouVwqwZthNEWqTuEyBFMkAT+99PXW7oVDc7oU5cevuihxQWNTYq"
+ + "viGB8cck6RW3cmqrDSaJF/E+N0cXFKyYC7FDcggt6k3UrxNKTuySdDEa8+2RTQqU"
+ + "Y9npxBlQE+x9Ig56OI1BG3bSBsGdPgjpyHadZeh2tgk+oqlGsSsum24YxaxuSysT"
+ + "Qfcu/XhyfUXavfmGrBOXerTzIl5oBh/F5aHTV85M2tYEG0qsPPvSpZAWtdJ/2rca"
+ + "OxvhwOL+leZKr8McjXVR00lBsRuKXX4nTUMwya09CO3QHFPFZtZvqjy2HaMOnVLQ"
+ + "I6b6dHEfmsHybzVOe3yPEoFQSU9UhUdmi71kwwoanPD3j9fJHmXTx4PzYYBRf1ZE"
+ + "o+uPgMPk7CDKQFZLjnR40z1uzu3O8aZ3AKZzP+j7T4XQKJLQLmllKtPgLgNdJyib"
+ + "2Glg7QhXH/jBTL6hAgMBAAGjYzBhMB0GA1UdDgQWBBSbZfrqOYH54EJpkdKMZjMc"
+ + "z/Hp+DAfBgNVHSMEGDAWgBSbZfrqOYH54EJpkdKMZjMcz/Hp+DAPBgNVHRMBAf8E"
+ + "BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQ0FAAOCAgEAKh9nm/vW"
+ + "glMWp3vcCwWwJW286ecREDlI+CjGh5h+f2N4QRrXd/tKE3qQJWCqGx8sFfIUjmI7"
+ + "KYdsC2gyQ2cA2zl0w7pB2QkuqE6zVbnh1D17Hwl19IMyAakFaM9ad4/EoH7oQmqX"
+ + "nF/f5QXGZw4kf1HcgKgoCHWXjqR8MqHOcXR8n6WFqxjzJf1jxzi6Yo2dZ7PJbnE6"
+ + "+kHIJuiCpiHL75v5g1HM41gT3ddFFSrn88ThNPWItT5Z8WpFjryVzank2Yt02LLl"
+ + "WqZg9IC375QULc5B58NMnaiVJIDJQ8zoNgj1yaxqtUMnJX570lotO2OXe4ec9aCQ"
+ + "DIJ84YLM/qStFdeZ9416E80dchskbDG04GuVJKlzWjxAQNMRFhyaPUSBTLLg+kwP"
+ + "t9+AMmc+A7xjtFQLZ9fBYHOBsndJOmeSQeYeckl+z/1WQf7DdwXn/yijon7mxz4z"
+ + "cCczfKwTJTwBh3wR5SQr2vQm7qaXM87qxF8PCAZrdZaw5I80QwkgTj0WTZ2/GdSw"
+ + "d3o5SyzzBAjpwtG+4bO/BD9h9wlTsHpT6yWOZs4OYAKU5ykQrncI8OyavMggArh3"
+ + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8"
+ + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA=";
+
+ /**
+ * The X509 certificate of the trusted root CA cert for the recoverable key store service.
+ *
+ * TODO: Change it to the production certificate root CA before the final launch.
+ */
+ public static final X509Certificate TRUSTED_ROOT_CERT;
+
+ static {
+ try {
+ TRUSTED_ROOT_CERT = CertUtils.decodeCert(
+ CertUtils.decodeBase64(TRUSTED_ROOT_CERT_BASE64));
+ } catch (CertParsingException e) {
+ // Should not happen
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index b96208d66b01..89ddb6c9eb30 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -31,9 +31,14 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
@@ -53,6 +58,7 @@ public class RecoverableKeyStoreDb {
private static final String TAG = "RecoverableKeyStoreDb";
private static final int IDLE_TIMEOUT_SECONDS = 30;
private static final int LAST_SYNCED_AT_UNSYNCED = -1;
+ private static final String CERT_PATH_ENCODING = "PkiPath";
private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
@@ -361,6 +367,79 @@ public class RecoverableKeyStoreDb {
}
/**
+ * Returns the serial number of the XML file containing certificates of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @return The value that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public Long getRecoveryServiceCertSerial(int userId, int uid) {
+ return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
+ }
+
+ /**
+ * Records the serial number of the XML file containing certificates of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @param serial The serial number contained in the XML file for recovery service certificates.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+ public long setRecoveryServiceCertSerial(int userId, int uid, long serial) {
+ return setLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, serial);
+ }
+
+ /**
+ * Returns the {@code CertPath} of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @return The value that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public CertPath getRecoveryServiceCertPath(int userId, int uid) {
+ byte[] bytes = getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
+ if (bytes == null) {
+ return null;
+ }
+ try {
+ return decodeCertPath(bytes);
+ } catch (CertificateException e) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "Recovery service CertPath entry cannot be decoded for "
+ + "userId=%d uid=%d.",
+ userId, uid), e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the {@code CertPath} of the recovery service.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initializes the local recovery components.
+ * @param certPath The certificate path of the recovery service.
+ * @return The primary key of the inserted row, or -1 if failed.
+ * @hide
+ */
+ public long setRecoveryServiceCertPath(int userId, int uid, CertPath certPath) throws
+ CertificateEncodingException {
+ if (certPath.getCertificates().size() == 0) {
+ throw new CertificateEncodingException("No certificate contained in the cert path.");
+ }
+ return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
+ certPath.getEncoded(CERT_PATH_ENCODING));
+ }
+
+ /**
* Returns the list of recovery agents initialized for given {@code userId}
* @param userId The userId of the profile the application is running under.
* @return The list of recovery agents
@@ -515,48 +594,6 @@ public class RecoverableKeyStoreDb {
}
/**
- * Returns the first (and only?) public key for {@code userId}.
- *
- * @param userId The userId of the profile whose keys are to be synced.
- * @return The public key, or null if none exists.
- */
- @Nullable
- public PublicKey getRecoveryServicePublicKey(int userId) {
- SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
-
- String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY };
- String selection =
- RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
- String[] selectionArguments = { Integer.toString(userId) };
-
- try (
- Cursor cursor = db.query(
- RecoveryServiceMetadataEntry.TABLE_NAME,
- projection,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)
- ) {
- if (cursor.getCount() < 1) {
- return null;
- }
-
- cursor.moveToFirst();
- byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
- RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY));
-
- try {
- return decodeX509Key(keyBytes);
- } catch (InvalidKeySpecException e) {
- Log.wtf(TAG, "Could not decode public key for " + userId);
- return null;
- }
- }
- }
-
- /**
* Updates the counterId
*
* @param userId The userId of the profile the application is running under.
@@ -585,7 +622,6 @@ public class RecoverableKeyStoreDb {
return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
}
-
/**
* Updates the server parameters given by the application initializing the local recovery
* components.
@@ -869,4 +905,16 @@ public class RecoverableKeyStoreDb {
throw new RuntimeException(e);
}
}
+
+ @Nullable
+ private static CertPath decodeCertPath(byte[] bytes) throws CertificateException {
+ CertificateFactory certFactory;
+ try {
+ certFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ // Should not happen, as X.509 is mandatory for all providers.
+ throw new RuntimeException(e);
+ }
+ return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 4ee282b6115e..1cb5d91be3ba 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -104,9 +104,10 @@ class RecoverableKeyStoreDbContract {
static final String COLUMN_NAME_UID = "uid";
/**
- * Version of the latest recovery snapshot
+ * Version of the latest recovery snapshot.
*/
static final String COLUMN_NAME_SNAPSHOT_VERSION = "snapshot_version";
+
/**
* Flag to generate new snapshot.
*/
@@ -118,6 +119,16 @@ class RecoverableKeyStoreDbContract {
static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
/**
+ * The certificate path of the recovery service.
+ */
+ static final String COLUMN_NAME_CERT_PATH = "cert_path";
+
+ /**
+ * The serial number contained in the certificate XML file of the recovery service.
+ */
+ static final String COLUMN_NAME_CERT_SERIAL = "cert_serial";
+
+ /**
* Secret types used for end-to-end encryption.
*/
static final String COLUMN_NAME_SECRET_TYPES = "secret_types";
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 79fd496ad11c..8a89f2d4faa9 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -19,6 +19,7 @@ package com.android.server.locksettings.recoverablekeystore.storage;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
@@ -28,7 +29,9 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe
* Helper for creating the recoverable key database.
*/
class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
- private static final int DATABASE_VERSION = 2;
+ private static final String TAG = "RecoverableKeyStoreDbHp";
+
+ static final int DATABASE_VERSION = 3;
private static final String DATABASE_NAME = "recoverablekeystore.db";
private static final String SQL_CREATE_KEYS_ENTRY =
@@ -59,6 +62,8 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
@@ -88,9 +93,39 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DELETE_KEYS_ENTRY);
- db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
- db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
- onCreate(db);
+ if (oldVersion < 2) {
+ db.execSQL(SQL_DELETE_KEYS_ENTRY);
+ db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
+ db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
+ onCreate(db);
+ return;
+ }
+
+ if (oldVersion < 3) {
+ upgradeDbForVersion3(db);
+ }
+ }
+
+ private void upgradeDbForVersion3(SQLiteDatabase db) {
+ // Add the two columns for cert path and cert serial number
+ addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, "BLOB", /*defaultStr=*/ null);
+ addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, "INTEGER", /*defaultStr=*/
+ null);
+ }
+
+ private static void addColumnToTable(
+ SQLiteDatabase db, String tableName, String column, String columnType,
+ String defaultStr) {
+ Log.d(TAG, "Adding column " + column + " to " + tableName + ".");
+
+ String alterStr = "ALTER TABLE " + tableName + " ADD COLUMN " + column + " " + columnType;
+ if (defaultStr != null && !defaultStr.isEmpty()) {
+ alterStr += " DEFAULT " + defaultStr;
+ }
+
+ db.execSQL(alterStr + ";");
}
}
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index ce5ee138cc3d..e40e3a42ee53 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -275,7 +275,7 @@ public class KeySyncTaskTest {
}
@Test
- public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
+ public void run_sendsEncryptedKeysIfAvailableToSync_withRawPublicKey() throws Exception {
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
@@ -323,6 +323,26 @@ public class KeySyncTaskTest {
}
@Test
+ public void run_sendsEncryptedKeysIfAvailableToSync_withCertPath() throws Exception {
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TestData.CERT_PATH_1);
+ mRecoverableKeyStoreDb.setServerParams(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
+ when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
+ addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+
+ mKeySyncTask.run();
+
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
+ List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
+ assertThat(applicationKeys).hasSize(1);
+ assertThat(keyChainSnapshot.getTrustedHardwarePublicKey())
+ .isEqualTo(SecureBox.encodePublicKey(
+ TestData.CERT_PATH_1.getCertificates().get(0).getPublicKey()));
+ }
+
+ @Test
public void run_setsCorrectSnapshotVersion() throws Exception {
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 2343deec020b..a523b86f3b0b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -238,15 +238,81 @@ public class RecoverableKeyStoreManagerTest {
}
@Test
- public void initRecoveryService_updatesShouldCreateSnapshot() throws Exception {
+ public void initRecoveryService_succeeds() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
+ certSerial);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
+ }
+
+ @Test
+ public void initRecoveryService_updatesWithLargerSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial + 1));
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
+ .isEqualTo(certSerial + 1);
+ }
+
+ @Test
+ public void initRecoveryService_ignoresSmallerSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial - 1));
+
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid))
+ .isEqualTo(certSerial);
+ }
+
+ @Test
+ public void initRecoveryService_ignoresTheSameSerial() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ long certSerial = 1000L;
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
+ TestData.getCertXmlWithSerial(certSerial));
+
+ // If the second update succeeds, getShouldCreateSnapshot() will return true.
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
+ }
+
+ @Test
+ public void initRecoveryService_succeedsWithRawPublicKey() throws Exception {
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
- // Sync is not needed.
mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNotNull();
}
@Test
@@ -344,26 +410,6 @@ public class RecoverableKeyStoreManagerTest {
}
@Test
- public void startRecoverySession_throwsIfBadKey() throws Exception {
- try {
- mRecoverableKeyStoreManager.startRecoverySession(
- TEST_SESSION_ID,
- getUtf8Bytes("0"),
- TEST_VAULT_PARAMS,
- TEST_VAULT_CHALLENGE,
- ImmutableList.of(
- new KeyChainProtectionParams(
- TYPE_LOCKSCREEN,
- UI_FORMAT_PASSWORD,
- KeyDerivationParams.createSha256Params(TEST_SALT),
- TEST_SECRET)));
- fail("should have thrown");
- } catch (ServiceSpecificException e) {
- assertEquals("Not a valid X509 key", e.getMessage());
- }
- }
-
- @Test
public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception {
byte[] vaultParams = TEST_VAULT_PARAMS.clone();
vaultParams[1] ^= (byte) 1; // Flip 1 bit
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
new file mode 100644
index 000000000000..0e4f91b20ae2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
@@ -0,0 +1,202 @@
+package com.android.server.locksettings.recoverablekeystore;
+
+import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateFactory;
+
+public final class TestData {
+
+ private static final String CERT_PATH_ENCODING = "PkiPath";
+
+ private static final String CERT_PATH_1_BASE64 = ""
+ + "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"
+ + "Zus8";
+ private static String CERT_PATH_2_BASE64 = ""
+ + "MIIFMzCCBS8wggMXoAMCAQICAhAAMA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMM"
+ + "FUdvb2dsZSBDcnlwdEF1dGhWYXVsdDAeFw0xODAyMDMwMDQyMDNaFw0yODAyMDEw"
+ + "MDQyMDNaMC0xKzApBgNVBAMMIkdvb2dsZSBDcnlwdEF1dGhWYXVsdCBJbnRlcm1l"
+ + "ZGlhdGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDckHib0X6rQyDq"
+ + "k4519b5du0OrCPk30XXKwz+Hz5y4cGZaWKGcHOHWS2X9YApRzO00/EbvFkWVUTVG"
+ + "27wJ54V+C3HHSOAUWHhEgfFWvvHwfn9HTDx1BEk79aQqJ7DuJ06Sn/WOiMtKVAT5"
+ + "6Mi8mekBxpMOrdZqwlcLrUVsZxEHsw5/ceZu4cSWzc7SzlnbNK1cCgyRDGqWf6Gp"
+ + "3hGE86kUOtM1i95RgUIpw+w/z0wxpF6kIyQTjK+KjiYH/RBOJIEcm6sSWZlMotKL"
+ + "Sn2lhf+XL8yUxExIHTosfeb077QWW4w2BB2NZM4wPAO3w4aw33FNigDQc2SQYmnU"
+ + "EYmIcD8kx77+JWCgCxBJc2zTHXtBxWuXAQ+iegt8RO+QD97pd6XKM9xPsAOkcWLp"
+ + "79o+AJol4P5fwvgYM69mM4lwH12v86RI4aptPQOag0KDIHXyKbjaQyAgv30l4KkD"
+ + "pf2uWODhOOTwNbVPYUm3sYUlhBcbyhTk8YqN9sPU4QAao5sKTAYZgB/mlheQypTU"
+ + "wyvqz6bRzGehVB3ltP9gCyKdI04VXEUuUBWk3STyV2REQen5/LKAns6v11Cz22Zr"
+ + "EdCvNLgetnyV7CJsOa/wD/GiUWL2Ta7pzshi9ahJqrrcNPRbAzOLcNKZkFexhzPp"
+ + "onuo/pNrcaRda1frepXxVkmbsgOULwIDAQABo2YwZDAdBgNVHQ4EFgQUd6md2hCP"
+ + "lmf3VkEX5FfDxKBLbaAwHwYDVR0jBBgwFoAUm2X66jmB+eBCaZHSjGYzHM/x6fgw"
+ + "EgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL"
+ + "BQADggIBAFgShhuW+WVTowN080PLf0TWPlHACHHUPghf7rFGxgUjJypCloE84Beg"
+ + "3ROpP5l19CDqZ9OyPzA1z6VAzeGXyFhZvby7G2tZDBRP/v0u8pnSAdC5F8l8Vh2Y"
+ + "GdgE3sZD25vpdBi7P0Ef6LYQetOJXn86PqgmgW1F6lzxDjKCsi9kpeU0AWwDdOVg"
+ + "748wku50o8UEzsVwxzFd9toGlge/nn3FH5J7EuGzAlFwToHqpwTVEegaAd0l9mr5"
+ + "+rS7Urd3X80BHDqCBcXE7Uqbtzw5Y+lmowMCnW0kFN02dC9dLt2c9IxC+9sPIA5e"
+ + "TkrZBkrkTVRGLj2r29j7nC9m5VaKcBqcLZDWy8pRna8yaZprgNdE8d/WTY9nVsic"
+ + "09N8zNF5Q0bhhWa3QonlB9XW5ZqDguiclvn+5TtREzSAtSOyxM+gfG3l0wjOywIk"
+ + "1aFa52RaqAWPL67KOM6G3vKNpMnW5hrmHrijuKxiarGIoZfkZMR5ijK0uFgv3/p6"
+ + "NHL/YQBaHJJhkKet5ThiPxwW9+1k/ZcXVeY26Xh+22Gp/8to7ZW8guPPiN1hfpD+"
+ + "7f1IdSmHDrsZQQ7bfzV0bppsyNNB7e2Ecyw+GQny27nytBLJDGdRBurbwQvzppQO"
+ + "6Qmlk0rfCszh7bGCoCQNxXmuDsQ5BC+pQUqJplTqds1smyi29xs3";
+
+ private static final String THM_CERT_XML_BEFORE_SERIAL = ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<certificates>\n"
+ + " <metadata>\n"
+ + " <serial>\n";
+ private static final String THM_CERT_XML_AFTER_SERIAL = ""
+ + " </serial>\n"
+ + " <creation-time>\n"
+ + " 1515697631\n"
+ + " </creation-time>\n"
+ + " <refresh-interval>\n"
+ + " 2592000\n"
+ + " </refresh-interval>\n"
+ + " <previous>\n"
+ + " <serial>\n"
+ + " 0\n"
+ + " </serial>\n"
+ + " <hash>\n"
+ + " 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\n"
+ + " </hash>\n"
+ + " </previous>\n"
+ + " </metadata>\n"
+ + " <intermediates>\n"
+ + " <cert>\n"
+ + " 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\n"
+ + " </cert>\n"
+ + " </intermediates>\n"
+ + " <endpoints>\n"
+ + " <cert>\n"
+ + " MIIDCDCB8aADAgECAgYBYVkuU0cwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAwwi\n"
+ + " R29vZ2xlIENyeXB0QXV0aFZhdWx0IEludGVybWVkaWF0ZTAeFw0xODAyMDIwMTAx\n"
+ + " MDNaFw0yMDAyMDMwMTAxMDNaMCkxJzAlBgNVBAMTHkdvb2dsZSBDcnlwdEF1dGhW\n"
+ + " YXVsdCBJbnN0YW5jZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgAERiYHfBu\n"
+ + " tJT+htocB40BtDr2jdxh0EZJlQ8QhpMkZuA/0t/zeSAdkVWw5b16izJ9JVOi/KVl\n"
+ + " 4b0hRH54UvowDQYJKoZIhvcNAQELBQADggIBAJ3PM4GNTNYzMr8E/IGsWZkLx9AR\n"
+ + " AALqBXz7As59F8y5UcLMqkXD/ewOfBZgF5VzjlAePyE/wSw0wc3xzvrDVVDiZaMB\n"
+ + " W1DVtSlbn25q00m00mmcUeyyMc7vuRkPoDshIMQTc8+U3yyYsVScSV+B4TvSx6wP\n"
+ + " Z9FpwnSPjVPD2GkqeMTWszuxNVEWq0wmm0K5lMaX0hfiak+4/IZxOPPGIg2py1KL\n"
+ + " A/H2gdyeqyJRcAsyEkfwLlushR5T9abSiPsIRcYoX8Ck8Lt+gQ7RCMefnm8CoOBK\n"
+ + " IfcjuV4PGOoeXrq57VR5SsOeT07bL+D7B+mohYFI1v2G3WClAE8XgM3q8NoFFvaY\n"
+ + " moi0+UcTduil47qvozjdNmjRAgu5j6vMKXEdG5Rqsja8hy0LG1hwfnR0gNiwcZ5L\n"
+ + " e3GyFnwH1IgqvsGOUM0ohnDUAU0zJY7nG0QYrDYe5/QPRNhWDpYkwHDiqcG28wIQ\n"
+ + " COTPAZHU2EoSKjSqEG2l0S5JPcor2BEde9ikSkcmK8foxlOHIdFn+n7RNF3bSEfK\n"
+ + " n1IOuXoqPidmeBQLevqG8KTy/C9CHqlaCNlpbIA9h+WVfsjm2s6JXBu0YbcfoIbJ\n"
+ + " AmSuZVeqB/+ZVvpfiad/jQWzY49fRnsSmV7VveTFPGtJxC89EadbMAinMZo+72u5\n"
+ + " 9319RqN5wsP2Zus8\n"
+ + " </cert>\n"
+ + " </endpoints>\n"
+ + "</certificates>\n";
+
+ public static byte[] getCertPath1Bytes() {
+ try {
+ return CertUtils.decodeBase64(CERT_PATH_1_BASE64);
+ } catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static byte[] getCertPath2Bytes() {
+ try {
+ return CertUtils.decodeBase64(CERT_PATH_2_BASE64);
+ } catch (Exception e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static final CertPath CERT_PATH_1;
+ public static final CertPath CERT_PATH_2;
+
+ static {
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ CERT_PATH_1 = certFactory.generateCertPath(
+ new ByteArrayInputStream(getCertPath1Bytes()), CERT_PATH_ENCODING);
+ CERT_PATH_2 = certFactory.generateCertPath(
+ new ByteArrayInputStream(getCertPath2Bytes()), CERT_PATH_ENCODING);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static byte[] getCertXmlWithSerial(long serial) {
+ String xml = THM_CERT_XML_BEFORE_SERIAL + serial + THM_CERT_XML_AFTER_SERIAL;
+ return xml.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
new file mode 100644
index 000000000000..37482a37003c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.locksettings.recoverablekeystore.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreDbHelperTest {
+
+ private static final long TEST_USER_ID = 10L;
+ private static final long TEST_UID = 60001L;
+ private static final String TEST_ALIAS = "test-alias";
+ private static final byte[] TEST_NONCE = "test-nonce".getBytes(UTF_8);
+ private static final byte[] TEST_WRAPPED_KEY = "test-wrapped-key".getBytes(UTF_8);
+ private static final long TEST_GENERATION_ID = 13L;
+ private static final long TEST_LAST_SYNCED_AT = 1517990732000L;
+ private static final int TEST_RECOVERY_STATUS = 3;
+ private static final int TEST_PLATFORM_KEY_GENERATION_ID = 11;
+ private static final int TEST_SNAPSHOT_VERSION = 31;
+ private static final int TEST_SHOULD_CREATE_SNAPSHOT = 1;
+ private static final byte[] TEST_PUBLIC_KEY = "test-public-key".getBytes(UTF_8);
+ private static final String TEST_SECRET_TYPES = "test-secret-types";
+ private static final long TEST_COUNTER_ID = -3981205205038476415L;
+ private static final byte[] TEST_SERVER_PARAMS = "test-server-params".getBytes(UTF_8);
+ private static final byte[] TEST_CERT_PATH = "test-cert-path".getBytes(UTF_8);
+ private static final long TEST_CERT_SERIAL = 1000L;
+
+ private static final String SQL_CREATE_V2_TABLE_KEYS =
+ "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
+ + KeysEntry._ID + " INTEGER PRIMARY KEY,"
+ + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
+ + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
+ + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
+ + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+ + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
+ + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+ + KeysEntry.COLUMN_NAME_ALIAS + "))";
+
+ private static final String SQL_CREATE_V2_TABLE_USER_METADATA =
+ "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+ + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+ + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+ private static final String SQL_CREATE_V2_TABLE_RECOVERY_SERVICE_METADATA =
+ "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
+ + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
+ + "UNIQUE("
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + ","
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
+
+ private SQLiteDatabase mDatabase;
+ private RecoverableKeyStoreDbHelper mDatabaseHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseHelper = new RecoverableKeyStoreDbHelper(context);
+ mDatabase = SQLiteDatabase.create(null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mDatabase.close();
+ }
+
+ private void createV2Tables() throws Exception {
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_KEYS);
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_USER_METADATA);
+ mDatabase.execSQL(SQL_CREATE_V2_TABLE_RECOVERY_SERVICE_METADATA);
+ }
+
+ @Test
+ public void onCreate() throws Exception {
+ mDatabaseHelper.onCreate(mDatabase);
+ checkAllColumns();
+ }
+
+ @Test
+ public void onUpgrade_beforeV2() throws Exception {
+ mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 1,
+ RecoverableKeyStoreDbHelper.DATABASE_VERSION);
+ checkAllColumns();
+ }
+
+ @Test
+ public void onUpgrade_fromV2() throws Exception {
+ createV2Tables();
+ mDatabaseHelper.onUpgrade(mDatabase, /*oldVersion=*/ 2,
+ RecoverableKeyStoreDbHelper.DATABASE_VERSION);
+ checkAllColumns();
+ }
+
+ private void checkAllColumns() throws Exception {
+ // Check the table containing encrypted application keys
+ ContentValues values = new ContentValues();
+ values.put(KeysEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(KeysEntry.COLUMN_NAME_UID, TEST_UID);
+ values.put(KeysEntry.COLUMN_NAME_ALIAS, TEST_ALIAS);
+ values.put(KeysEntry.COLUMN_NAME_NONCE, TEST_NONCE);
+ values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, TEST_WRAPPED_KEY);
+ values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, TEST_GENERATION_ID);
+ values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, TEST_LAST_SYNCED_AT);
+ values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, TEST_RECOVERY_STATUS);
+ assertThat(mDatabase.insert(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
+ .isGreaterThan(-1L);
+
+ // Check the table about user metadata
+ values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID,
+ TEST_PLATFORM_KEY_GENERATION_ID);
+ assertThat(mDatabase.insert(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
+ .isGreaterThan(-1L);
+
+ // Check the table about recovery service metadata
+ values = new ContentValues();
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, TEST_UID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION,
+ TEST_SNAPSHOT_VERSION);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT,
+ TEST_SHOULD_CREATE_SNAPSHOT);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, TEST_PUBLIC_KEY);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, TEST_SECRET_TYPES);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, TEST_COUNTER_ID);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, TEST_SERVER_PARAMS);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, TEST_CERT_PATH);
+ values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, TEST_CERT_SERIAL);
+ assertThat(
+ mDatabase.insert(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+ values))
+ .isGreaterThan(-1L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 097d2141d9e0..1c5bcd498f5c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -32,6 +32,8 @@ import android.security.keystore.RecoveryController;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.TestData;
import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import java.io.File;
@@ -370,6 +372,57 @@ public class RecoverableKeyStoreDbTest {
pubkey);
}
+ public void setRecoveryServiceCertPath_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_2);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_2);
+ }
+
+ @Test
+ public void getRecoveryServiceCertPath_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServiceCertPath_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(userId, uid, TestData.CERT_PATH_1);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid)).isEqualTo(
+ TestData.CERT_PATH_1);
+ }
+
+ @Test
+ public void setRecoveryServiceCertSerial_replaceOldValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1L);
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 3L);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(3L);
+ }
+
+ @Test
+ public void getRecoveryServiceCertSerial_returnsNullIfNoValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isNull();
+ }
+
+ @Test
+ public void getRecoveryServiceCertSerial_returnsInsertedValue() throws Exception {
+ int userId = 12;
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid, 1234L);
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid)).isEqualTo(
+ 1234L);
+ }
+
@Test
public void getRecoveryAgents_returnsUidIfSet() throws Exception {
int userId = 12;
@@ -493,17 +546,6 @@ public class RecoverableKeyStoreDbTest {
}
@Test
- public void getRecoveryServicePublicKey_returnsFirstKey() throws Exception {
- int userId = 68;
- int uid = 12904;
- PublicKey publicKey = genRandomPublicKey();
-
- mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, publicKey);
-
- assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId)).isEqualTo(publicKey);
- }
-
- @Test
public void setServerParams_replaceOldValue() throws Exception {
int userId = 12;
int uid = 10009;