diff options
| author | 2018-02-08 21:47:50 +0000 | |
|---|---|---|
| committer | 2018-02-08 21:47:50 +0000 | |
| commit | 5ec5a6929b2944f052d5348e13dc210c093759fb (patch) | |
| tree | e8dcbccb45125f66fa0411351877745dc0a4a9e5 | |
| parent | 793a823467f48ea8b92f0b53578b603f50a560ba (diff) | |
| parent | 14d993dc2c0bbdee6a6ae0c270a92107c9f57a84 (diff) | |
Merge "Accept an XML file containing a list of THM certificates instead of the temporary solution using the raw public key"
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; |