blob: fdc00f4a99e86b6d9e2d70d16d2565fc8ee3a36f [file] [log] [blame]
/*
* 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 android.security.keystore.recovery;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.BadParcelableException;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Objects;
/**
* A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot:
*
* <ul>
* <li>The user's lock screen changes. (A key derived from the user's lock screen is used to
* protected the keychain, which is why this forces a new snapshot.)
* <li>A key is added to or removed from the recoverable keychain.
* </ul>
*
* <p>The snapshot data is also encrypted with the remote trusted hardware's public key, so even
* the recovery agent itself should not be able to decipher the data. The recovery agent sends an
* instance of this to the remote trusted hardware whenever a new snapshot is generated. During a
* recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then
* sends it to the framework, where it is decrypted using the user's lock screen from their previous
* device.
*
* @hide
*/
@SystemApi
public final class KeyChainSnapshot implements Parcelable {
// IMPORTANT! PLEASE READ!
// -----------------------
// If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following:
// - Update the #writeToParcel(Parcel) method below
// - Update the #(Parcel) constructor below
// - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody
// accidentally breaks your fields in the Parcel in the future.
// - Update com.android.server.locksettings.recoverablekeystore.serialization
// .KeyChainSnapshotSerializer to correctly serialize your new field
// - Update com.android.server.locksettings.recoverablekeystore.serialization
// .KeyChainSnapshotSerializer to correctly deserialize your new field
// - Update com.android.server.locksettings.recoverablekeystore.serialization
// .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field
// in the future.
private static final int DEFAULT_MAX_ATTEMPTS = 10;
private static final long DEFAULT_COUNTER_ID = 1L;
private int mSnapshotVersion;
private int mMaxAttempts = DEFAULT_MAX_ATTEMPTS;
private long mCounterId = DEFAULT_COUNTER_ID;
private byte[] mServerParams;
private RecoveryCertPath mCertPath; // The cert path including necessary intermediate certs
private List<KeyChainProtectionParams> mKeyChainProtectionParams;
private List<WrappedApplicationKey> mEntryRecoveryData;
private byte[] mEncryptedRecoveryKeyBlob;
/**
* Use builder to create an instance of the class.
*/
private KeyChainSnapshot() {
}
/**
* Snapshot version for given recovery agent. It is incremented when user secret or list of
* application keys changes.
*/
public int getSnapshotVersion() {
return mSnapshotVersion;
}
/**
* Number of user secret guesses allowed during KeyChain recovery.
*/
public int getMaxAttempts() {
return mMaxAttempts;
}
/**
* CounterId which is rotated together with user secret.
*/
public long getCounterId() {
return mCounterId;
}
/**
* Server parameters.
*/
public @NonNull byte[] getServerParams() {
return mServerParams;
}
/**
* CertPath containing the public key used to encrypt {@code encryptedRecoveryKeyBlob}.
*/
public @NonNull CertPath getTrustedHardwareCertPath() {
try {
return mCertPath.getCertPath();
} catch (CertificateException e) {
// Rethrow an unchecked exception as it should not happen. If such an issue exists,
// an exception should have been thrown during service initialization.
throw new BadParcelableException(e);
}
}
/**
* UI and key derivation parameters. Note that combination of secrets may be used.
*/
public @NonNull List<KeyChainProtectionParams> getKeyChainProtectionParams() {
return mKeyChainProtectionParams;
}
/**
* List of application keys, with key material encrypted by
* the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
*/
public @NonNull List<WrappedApplicationKey> getWrappedApplicationKeys() {
return mEntryRecoveryData;
}
/**
* Recovery key blob, encrypted by user secret and recovery service public key.
*/
public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
return mEncryptedRecoveryKeyBlob;
}
public static final @NonNull Creator<KeyChainSnapshot> CREATOR =
new Creator<KeyChainSnapshot>() {
public KeyChainSnapshot createFromParcel(Parcel in) {
return new KeyChainSnapshot(in);
}
public KeyChainSnapshot[] newArray(int length) {
return new KeyChainSnapshot[length];
}
};
/**
* Builder for creating {@link KeyChainSnapshot}.
* @hide
*/
public static class Builder {
private KeyChainSnapshot mInstance = new KeyChainSnapshot();
/**
* Snapshot version for the recovery agent.
*
* @param snapshotVersion The snapshot version
* @return This builder.
*/
public @NonNull Builder setSnapshotVersion(int snapshotVersion) {
mInstance.mSnapshotVersion = snapshotVersion;
return this;
}
/**
* Sets the number of user secret guesses allowed during Keychain recovery.
*
* @param maxAttempts The maximum number of guesses.
* @return This builder.
*/
public @NonNull Builder setMaxAttempts(int maxAttempts) {
mInstance.mMaxAttempts = maxAttempts;
return this;
}
/**
* Sets counter id.
*
* @param counterId The counter id.
* @return This builder.
*/
public @NonNull Builder setCounterId(long counterId) {
mInstance.mCounterId = counterId;
return this;
}
/**
* Sets server parameters.
*
* @param serverParams The server parameters
* @return This builder.
*/
public @NonNull Builder setServerParams(byte[] serverParams) {
mInstance.mServerParams = serverParams;
return this;
}
/**
* Sets CertPath used to validate the trusted hardware public key. The CertPath should
* contain a certificate of the trusted hardware public key and any necessary intermediate
* certificates.
*
* @param certPath The certificate path
* @throws CertificateException if the given certificate path cannot be encoded properly
* @return This builder.
*/
public @NonNull Builder setTrustedHardwareCertPath(@NonNull CertPath certPath)
throws CertificateException {
mInstance.mCertPath = RecoveryCertPath.createRecoveryCertPath(certPath);
return this;
}
/**
* Sets UI and key derivation parameters
*
* @param keyChainProtectionParams The UI and key derivation parameters
* @return This builder.
*/
public @NonNull Builder setKeyChainProtectionParams(
@NonNull List<KeyChainProtectionParams> keyChainProtectionParams) {
mInstance.mKeyChainProtectionParams = keyChainProtectionParams;
return this;
}
/**
* List of application keys.
*
* @param entryRecoveryData List of application keys
* @return This builder.
*/
public @NonNull Builder setWrappedApplicationKeys(
@NonNull List<WrappedApplicationKey> entryRecoveryData) {
mInstance.mEntryRecoveryData = entryRecoveryData;
return this;
}
/**
* Sets recovery key blob.
*
* @param encryptedRecoveryKeyBlob The recovery key blob.
* @return This builder.
*/
public @NonNull Builder setEncryptedRecoveryKeyBlob(
@NonNull byte[] encryptedRecoveryKeyBlob) {
mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob;
return this;
}
/**
* Creates a new {@link KeyChainSnapshot} instance.
*
* @return new instance
* @throws NullPointerException if some of the required fields were not set.
*/
public @NonNull KeyChainSnapshot build() {
Preconditions.checkCollectionElementsNotNull(mInstance.mKeyChainProtectionParams,
"keyChainProtectionParams");
Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
"entryRecoveryData");
Objects.requireNonNull(mInstance.mEncryptedRecoveryKeyBlob);
Objects.requireNonNull(mInstance.mServerParams);
Objects.requireNonNull(mInstance.mCertPath);
return mInstance;
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mSnapshotVersion);
out.writeTypedList(mKeyChainProtectionParams);
out.writeByteArray(mEncryptedRecoveryKeyBlob);
out.writeTypedList(mEntryRecoveryData);
out.writeInt(mMaxAttempts);
out.writeLong(mCounterId);
out.writeByteArray(mServerParams);
out.writeTypedObject(mCertPath, /* no flags */ 0);
}
/**
* @hide
*/
protected KeyChainSnapshot(Parcel in) {
mSnapshotVersion = in.readInt();
mKeyChainProtectionParams = in.createTypedArrayList(KeyChainProtectionParams.CREATOR);
mEncryptedRecoveryKeyBlob = in.createByteArray();
mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR);
mMaxAttempts = in.readInt();
mCounterId = in.readLong();
mServerParams = in.createByteArray();
mCertPath = in.readTypedObject(RecoveryCertPath.CREATOR);
}
@Override
public int describeContents() {
return 0;
}
}