Merge "Move APIs to final location in preparation for API review"

This commit is contained in:
Dmitry Dementyev
2018-01-23 20:57:14 +00:00
committed by Android (Google) Code Review
28 changed files with 1863 additions and 59 deletions

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2018 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;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
/**
* Helpers for converting classes between old and new API, so we can preserve backwards
* compatibility while teamfooding. This will be removed soon.
*
* @hide
*/
class BackwardsCompat {
static KeychainProtectionParams toLegacyKeychainProtectionParams(
android.security.keystore.recovery.KeychainProtectionParams keychainProtectionParams
) {
return new KeychainProtectionParams.Builder()
.setUserSecretType(keychainProtectionParams.getUserSecretType())
.setSecret(keychainProtectionParams.getSecret())
.setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat())
.setKeyDerivationParams(
toLegacyKeyDerivationParams(
keychainProtectionParams.getKeyDerivationParams()))
.build();
}
static KeyDerivationParams toLegacyKeyDerivationParams(
android.security.keystore.recovery.KeyDerivationParams keyDerivationParams
) {
return new KeyDerivationParams(
keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt());
}
static WrappedApplicationKey toLegacyWrappedApplicationKey(
android.security.keystore.recovery.WrappedApplicationKey wrappedApplicationKey
) {
return new WrappedApplicationKey.Builder()
.setAlias(wrappedApplicationKey.getAlias())
.setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial())
.build();
}
static android.security.keystore.recovery.KeyDerivationParams fromLegacyKeyDerivationParams(
KeyDerivationParams keyDerivationParams
) {
return new android.security.keystore.recovery.KeyDerivationParams(
keyDerivationParams.getAlgorithm(), keyDerivationParams.getSalt());
}
static android.security.keystore.recovery.WrappedApplicationKey fromLegacyWrappedApplicationKey(
WrappedApplicationKey wrappedApplicationKey
) {
return new android.security.keystore.recovery.WrappedApplicationKey.Builder()
.setAlias(wrappedApplicationKey.getAlias())
.setEncryptedKeyMaterial(wrappedApplicationKey.getEncryptedKeyMaterial())
.build();
}
static List<android.security.keystore.recovery.WrappedApplicationKey>
fromLegacyWrappedApplicationKeys(List<WrappedApplicationKey> wrappedApplicationKeys
) {
return map(wrappedApplicationKeys, BackwardsCompat::fromLegacyWrappedApplicationKey);
}
static List<android.security.keystore.recovery.KeychainProtectionParams>
fromLegacyKeychainProtectionParams(
List<KeychainProtectionParams> keychainProtectionParams) {
return map(keychainProtectionParams, BackwardsCompat::fromLegacyKeychainProtectionParam);
}
static android.security.keystore.recovery.KeychainProtectionParams
fromLegacyKeychainProtectionParam(KeychainProtectionParams keychainProtectionParams) {
return new android.security.keystore.recovery.KeychainProtectionParams.Builder()
.setUserSecretType(keychainProtectionParams.getUserSecretType())
.setSecret(keychainProtectionParams.getSecret())
.setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat())
.setKeyDerivationParams(
fromLegacyKeyDerivationParams(
keychainProtectionParams.getKeyDerivationParams()))
.build();
}
static KeychainSnapshot toLegacyKeychainSnapshot(
android.security.keystore.recovery.KeychainSnapshot keychainSnapshot
) {
return new KeychainSnapshot.Builder()
.setCounterId(keychainSnapshot.getCounterId())
.setEncryptedRecoveryKeyBlob(keychainSnapshot.getEncryptedRecoveryKeyBlob())
.setTrustedHardwarePublicKey(keychainSnapshot.getTrustedHardwarePublicKey())
.setSnapshotVersion(keychainSnapshot.getSnapshotVersion())
.setMaxAttempts(keychainSnapshot.getMaxAttempts())
.setServerParams(keychainSnapshot.getServerParams())
.setKeychainProtectionParams(
map(keychainSnapshot.getKeychainProtectionParams(),
BackwardsCompat::toLegacyKeychainProtectionParams))
.setWrappedApplicationKeys(
map(keychainSnapshot.getWrappedApplicationKeys(),
BackwardsCompat::toLegacyWrappedApplicationKey))
.build();
}
static <A, B> List<B> map(List<A> as, Function<A, B> f) {
ArrayList<B> bs = new ArrayList<>(as.size());
for (A a : as) {
bs.add(f.apply(a));
}
return bs;
}
}

View File

@@ -61,7 +61,7 @@ public final class KeyDerivationParams implements Parcelable {
return new KeyDerivationParams(ALGORITHM_SHA256, salt);
}
private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
mAlgorithm = algorithm;
mSalt = Preconditions.checkNotNull(salt);
}

View File

@@ -167,7 +167,7 @@ public class RecoveryController {
public @NonNull KeychainSnapshot getRecoveryData(@NonNull byte[] account)
throws InternalRecoveryServiceException {
try {
return mBinder.getRecoveryData(account);
return BackwardsCompat.toLegacyKeychainSnapshot(mBinder.getRecoveryData(account));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
@@ -359,28 +359,6 @@ public class RecoveryController {
}
}
/**
* Method notifies KeyStore that a user-generated secret is available. This method generates a
* symmetric session key which a trusted remote device can use to return a recovery key. Caller
* should use {@link KeychainProtectionParams#clearSecret} to override the secret value in
* memory.
*
* @param recoverySecret user generated secret together with parameters necessary to regenerate
* it on a new device.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret)
throws InternalRecoveryServiceException {
try {
mBinder.recoverySecretAvailable(recoverySecret);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Initializes recovery session and returns a blob with proof of recovery secrets possession.
* The method generates symmetric key for a session, which trusted remote device can use to
@@ -417,7 +395,7 @@ public class RecoveryController {
verifierPublicKey,
vaultParams,
vaultChallenge,
secrets);
BackwardsCompat.fromLegacyKeychainProtectionParams(secrets));
return new RecoveryClaim(recoverySession, recoveryClaim);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -451,7 +429,9 @@ public class RecoveryController {
InternalRecoveryServiceException {
try {
return (Map<String, byte[]>) mBinder.recoverKeys(
session.getSessionId(), recoveryKeyBlob, applicationKeys);
session.getSessionId(),
recoveryKeyBlob,
BackwardsCompat.fromLegacyWrappedApplicationKeys(applicationKeys));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 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;
/**
* Error thrown when the recovery agent supplies an invalid X509 certificate.
*
* @hide
*/
public class BadCertificateFormatException extends RecoveryControllerException {
public BadCertificateFormatException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2018 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;
/**
* Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key,
* trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc.
*
* @hide
*/
public class DecryptionFailedException extends RecoveryControllerException {
public DecryptionFailedException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2018 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;
/**
* An error thrown when something went wrong internally in the recovery service.
*
* <p>This is an unexpected error, and indicates a problem with the service itself, rather than the
* caller having performed some kind of illegal action.
*
* @hide
*/
public class InternalRecoveryServiceException extends RecoveryControllerException {
public InternalRecoveryServiceException(String msg) {
super(msg);
}
public InternalRecoveryServiceException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.security.keystore;
package android.security.keystore.recovery;
/* @hide */
parcelable KeyDerivationParams;

View File

@@ -0,0 +1,116 @@
/*
* 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.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Collection of parameters which define a key derivation function.
* Currently only supports salted SHA-256
*
* @hide
*/
public final class KeyDerivationParams implements Parcelable {
private final int mAlgorithm;
private byte[] mSalt;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
public @interface KeyDerivationAlgorithm {
}
/**
* Salted SHA256
*/
public static final int ALGORITHM_SHA256 = 1;
/**
* Argon2ID
* @hide
*/
// TODO: add Argon2ID support.
public static final int ALGORITHM_ARGON2ID = 2;
/**
* Creates instance of the class to to derive key using salted SHA256 hash.
*/
public static KeyDerivationParams createSha256Params(@NonNull byte[] salt) {
return new KeyDerivationParams(ALGORITHM_SHA256, salt);
}
// TODO: Make private once legacy API is removed
public KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
mAlgorithm = algorithm;
mSalt = Preconditions.checkNotNull(salt);
}
/**
* Gets algorithm.
*/
public @KeyDerivationAlgorithm int getAlgorithm() {
return mAlgorithm;
}
/**
* Gets salt.
*/
public @NonNull byte[] getSalt() {
return mSalt;
}
public static final Creator<KeyDerivationParams> CREATOR =
new Creator<KeyDerivationParams>() {
public KeyDerivationParams createFromParcel(Parcel in) {
return new KeyDerivationParams(in);
}
public KeyDerivationParams[] newArray(int length) {
return new KeyDerivationParams[length];
}
};
/**
* @hide
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mAlgorithm);
out.writeByteArray(mSalt);
}
/**
* @hide
*/
protected KeyDerivationParams(Parcel in) {
mAlgorithm = in.readInt();
mSalt = in.createByteArray();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.security.keystore;
package android.security.keystore.recovery;
/* @hide */
parcelable KeychainProtectionParams;

View File

@@ -0,0 +1,289 @@
/*
* 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.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
* A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This
* class wraps all the data necessary to derive the same key on a recovering device:
*
* <ul>
* <li>UI parameters for the user's lock screen - so that if e.g., the user was using a pattern,
* the recovering device can display the pattern UI to the user when asking them to enter
* the lock screen from their previous device.
* <li>The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt.
* </ul>
*
* <p>As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current
* version of the keychain.
*
* <p>For now, the recoverable keychain only supports a single layer of protection, which is the
* user's lock screen. In the future, the keychain will support multiple layers of protection
* (e.g. an additional keychain password, along with the lock screen).
*
* @hide
*/
public final class KeychainProtectionParams implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
public @interface UserSecretType {
}
/**
* Lockscreen secret is required to recover KeyStore.
*/
public static final int TYPE_LOCKSCREEN = 100;
/**
* Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
*/
public static final int TYPE_CUSTOM_PASSWORD = 101;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
public @interface LockScreenUiFormat {
}
/**
* Pin with digits only.
*/
public static final int TYPE_PIN = 1;
/**
* Password. String with latin-1 characters only.
*/
public static final int TYPE_PASSWORD = 2;
/**
* Pattern with 3 by 3 grid.
*/
public static final int TYPE_PATTERN = 3;
@UserSecretType
private Integer mUserSecretType;
@LockScreenUiFormat
private Integer mLockScreenUiFormat;
/**
* Parameters of the key derivation function, including algorithm, difficulty, salt.
*/
private KeyDerivationParams mKeyDerivationParams;
private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
/**
* @param secret Constructor creates a reference to the secret. Caller must use
* @link {#clearSecret} to overwrite its value in memory.
* @hide
*/
public KeychainProtectionParams(@UserSecretType int userSecretType,
@LockScreenUiFormat int lockScreenUiFormat,
@NonNull KeyDerivationParams keyDerivationParams,
@NonNull byte[] secret) {
mUserSecretType = userSecretType;
mLockScreenUiFormat = lockScreenUiFormat;
mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams);
mSecret = Preconditions.checkNotNull(secret);
}
private KeychainProtectionParams() {
}
/**
* @see TYPE_LOCKSCREEN
* @see TYPE_CUSTOM_PASSWORD
*/
public @UserSecretType int getUserSecretType() {
return mUserSecretType;
}
/**
* Specifies UX shown to user during recovery.
* Default value is {@code TYPE_LOCKSCREEN}
*
* @see TYPE_PIN
* @see TYPE_PASSWORD
* @see TYPE_PATTERN
*/
public @LockScreenUiFormat int getLockScreenUiFormat() {
return mLockScreenUiFormat;
}
/**
* Specifies function used to derive symmetric key from user input
* Format is defined in separate util class.
*/
@NonNull public KeyDerivationParams getKeyDerivationParams() {
return mKeyDerivationParams;
}
/**
* Secret derived from user input.
* Default value is empty array
*
* @return secret or empty array
*/
public @NonNull byte[] getSecret() {
return mSecret;
}
/**
* Builder for creating {@link KeychainProtectionParams}.
*/
public static class Builder {
private KeychainProtectionParams
mInstance = new KeychainProtectionParams();
/**
* Sets user secret type.
*
* @see TYPE_LOCKSCREEN
* @see TYPE_CUSTOM_PASSWORD
* @param userSecretType The secret type
* @return This builder.
*/
public Builder setUserSecretType(@UserSecretType int userSecretType) {
mInstance.mUserSecretType = userSecretType;
return this;
}
/**
* Sets UI format.
*
* @see TYPE_PIN
* @see TYPE_PASSWORD
* @see TYPE_PATTERN
* @param lockScreenUiFormat The UI format
* @return This builder.
*/
public Builder setLockScreenUiFormat(@LockScreenUiFormat int lockScreenUiFormat) {
mInstance.mLockScreenUiFormat = lockScreenUiFormat;
return this;
}
/**
* Sets parameters of the key derivation function.
*
* @param keyDerivationParams Key derivation Params
* @return This builder.
*/
public Builder setKeyDerivationParams(@NonNull KeyDerivationParams
keyDerivationParams) {
mInstance.mKeyDerivationParams = keyDerivationParams;
return this;
}
/**
* Secret derived from user input, or empty array.
*
* @param secret The secret.
* @return This builder.
*/
public Builder setSecret(@NonNull byte[] secret) {
mInstance.mSecret = secret;
return this;
}
/**
* Creates a new {@link KeychainProtectionParams} instance.
* The instance will include default values, if {@link setSecret}
* or {@link setUserSecretType} were not called.
*
* @return new instance
* @throws NullPointerException if some required fields were not set.
*/
@NonNull public KeychainProtectionParams build() {
if (mInstance.mUserSecretType == null) {
mInstance.mUserSecretType = TYPE_LOCKSCREEN;
}
Preconditions.checkNotNull(mInstance.mLockScreenUiFormat);
Preconditions.checkNotNull(mInstance.mKeyDerivationParams);
if (mInstance.mSecret == null) {
mInstance.mSecret = new byte[]{};
}
return mInstance;
}
}
/**
* Removes secret from memory than object is no longer used.
* Since finalizer call is not reliable, please use @link {#clearSecret} directly.
*/
@Override
protected void finalize() throws Throwable {
clearSecret();
super.finalize();
}
/**
* Fills mSecret with zeroes.
*/
public void clearSecret() {
Arrays.fill(mSecret, (byte) 0);
}
public static final Creator<KeychainProtectionParams> CREATOR =
new Creator<KeychainProtectionParams>() {
public KeychainProtectionParams createFromParcel(Parcel in) {
return new KeychainProtectionParams(in);
}
public KeychainProtectionParams[] newArray(int length) {
return new KeychainProtectionParams[length];
}
};
/**
* @hide
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mUserSecretType);
out.writeInt(mLockScreenUiFormat);
out.writeTypedObject(mKeyDerivationParams, flags);
out.writeByteArray(mSecret);
}
/**
* @hide
*/
protected KeychainProtectionParams(Parcel in) {
mUserSecretType = in.readInt();
mLockScreenUiFormat = in.readInt();
mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR);
mSecret = in.createByteArray();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.security.keystore;
package android.security.keystore.recovery;
/* @hide */
parcelable KeychainSnapshot;

View File

@@ -0,0 +1,300 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.List;
/**
* 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
*/
public final class KeychainSnapshot implements Parcelable {
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 byte[] mPublicKey;
private List<KeychainProtectionParams> mKeychainProtectionParams;
private List<WrappedApplicationKey> mEntryRecoveryData;
private byte[] mEncryptedRecoveryKeyBlob;
/**
* @hide
* Deprecated, consider using builder.
*/
public KeychainSnapshot(
int snapshotVersion,
@NonNull List<KeychainProtectionParams> keychainProtectionParams,
@NonNull List<WrappedApplicationKey> wrappedApplicationKeys,
@NonNull byte[] encryptedRecoveryKeyBlob) {
mSnapshotVersion = snapshotVersion;
mKeychainProtectionParams =
Preconditions.checkCollectionElementsNotNull(keychainProtectionParams,
"keychainProtectionParams");
mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys,
"wrappedApplicationKeys");
mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
}
private KeychainSnapshot() {
}
/**
* Snapshot version for given account. 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;
}
/**
* Public key used to encrypt {@code encryptedRecoveryKeyBlob}.
*
* See implementation for binary key format
*/
// TODO: document key format.
public @NonNull byte[] getTrustedHardwarePublicKey() {
return mPublicKey;
}
/**
* 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 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}.
*/
public static class Builder {
private KeychainSnapshot
mInstance = new KeychainSnapshot();
/**
* Snapshot version for given account.
*
* @param snapshotVersion The snapshot version
* @return This builder.
*/
public 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 Builder setMaxAttempts(int maxAttempts) {
mInstance.mMaxAttempts = maxAttempts;
return this;
}
/**
* Sets counter id.
*
* @param counterId The counter id.
* @return This builder.
*/
public Builder setCounterId(long counterId) {
mInstance.mCounterId = counterId;
return this;
}
/**
* Sets server parameters.
*
* @param serverParams The server parameters
* @return This builder.
*/
public Builder setServerParams(byte[] serverParams) {
mInstance.mServerParams = serverParams;
return this;
}
/**
* Sets public key used to encrypt recovery blob.
*
* @param publicKey The public key
* @return This builder.
*/
public Builder setTrustedHardwarePublicKey(byte[] publicKey) {
mInstance.mPublicKey = publicKey;
return this;
}
/**
* Sets UI and key derivation parameters
*
* @param recoveryMetadata The UI and key derivation parameters
* @return This builder.
*/
public Builder setKeychainProtectionParams(
@NonNull List<KeychainProtectionParams> recoveryMetadata) {
mInstance.mKeychainProtectionParams = recoveryMetadata;
return this;
}
/**
* List of application keys.
*
* @param entryRecoveryData List of application keys
* @return This builder.
*/
public Builder setWrappedApplicationKeys(List<WrappedApplicationKey> entryRecoveryData) {
mInstance.mEntryRecoveryData = entryRecoveryData;
return this;
}
/**
* Sets recovery key blob
*
* @param encryptedRecoveryKeyBlob The recovery key blob.
* @return This builder.
*/
public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) {
mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob;
return this;
}
/**
* Creates a new {@link KeychainSnapshot} instance.
*
* @return new instance
* @throws NullPointerException if some required fields were not set.
*/
@NonNull public KeychainSnapshot build() {
Preconditions.checkCollectionElementsNotNull(mInstance.mKeychainProtectionParams,
"recoveryMetadata");
Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
"entryRecoveryData");
Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
Preconditions.checkNotNull(mInstance.mServerParams);
Preconditions.checkNotNull(mInstance.mPublicKey);
return mInstance;
}
}
/**
* @hide
*/
@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.writeByteArray(mPublicKey);
}
/**
* @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();
mPublicKey = in.createByteArray();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2018 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;
/**
* Error thrown when trying to generate keys for a profile that has no lock screen set.
*
* <p>A lock screen must be set, as the lock screen is used to encrypt the snapshot.
*
* @hide
*/
public class LockScreenRequiredException extends RecoveryControllerException {
public LockScreenRequiredException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 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;
/**
* An attempt to recover a keychain protected by remote secure hardware.
*
* @hide
*/
public class RecoveryClaim {
private final RecoverySession mRecoverySession;
private final byte[] mClaimBytes;
RecoveryClaim(RecoverySession recoverySession, byte[] claimBytes) {
mRecoverySession = recoverySession;
mClaimBytes = claimBytes;
}
/**
* Returns the session associated with the recovery attempt. This is used to match the symmetric
* key, which remains internal to the framework, for decrypting the claim response.
*
* @return The session data.
*/
public RecoverySession getRecoverySession() {
return mRecoverySession;
}
/**
* Returns the encrypted claim's bytes.
*
* <p>This should be sent by the recovery agent to the remote secure hardware, which will use
* it to decrypt the keychain, before sending it re-encrypted with the session's symmetric key
* to the device.
*/
public byte[] getClaimBytes() {
return mClaimBytes;
}
}

View File

@@ -0,0 +1,535 @@
/*
* 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.Nullable;
import android.app.PendingIntent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.util.Log;
import com.android.internal.widget.ILockSettings;
import java.util.List;
import java.util.Map;
/**
* An assistant for generating {@link javax.crypto.SecretKey} instances that can be recovered by
* other Android devices belonging to the user. The exported keychain is protected by the user's
* lock screen.
*
* <p>The RecoveryController must be paired with a recovery agent. The recovery agent is responsible
* for transporting the keychain to remote trusted hardware. This hardware must prevent brute force
* attempts against the user's lock screen by limiting the number of allowed guesses (to, e.g., 10).
* After that number of incorrect guesses, the trusted hardware no longer allows access to the
* key chain.
*
* <p>For now only the recovery agent itself is able to create keys, so it is expected that the
* recovery agent is itself the system app.
*
* <p>A recovery agent requires the privileged permission
* {@code android.Manifest.permission#RECOVER_KEYSTORE}.
*
* @hide
*/
public class RecoveryController {
private static final String TAG = "RecoveryController";
/** Key has been successfully synced. */
public static final int RECOVERY_STATUS_SYNCED = 0;
/** Waiting for recovery agent to sync the key. */
public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
/** Recovery account is not available. */
public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
/** Key cannot be synced. */
public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
/**
* Failed because no snapshot is yet pending to be synced for the user.
*
* @hide
*/
public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
/**
* Failed due to an error internal to the recovery service. This is unexpected and indicates
* either a problem with the logic in the service, or a problem with a dependency of the
* service (such as AndroidKeyStore).
*
* @hide
*/
public static final int ERROR_SERVICE_INTERNAL_ERROR = 22;
/**
* Failed because the user does not have a lock screen set.
*
* @hide
*/
public static final int ERROR_INSECURE_USER = 23;
/**
* Error thrown when attempting to use a recovery session that has since been closed.
*
* @hide
*/
public static final int ERROR_SESSION_EXPIRED = 24;
/**
* Failed because the provided certificate was not a valid X509 certificate.
*
* @hide
*/
public static final int ERROR_BAD_CERTIFICATE_FORMAT = 25;
/**
* Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
* the data has become corrupted, the data has been tampered with, etc.
*
* @hide
*/
public static final int ERROR_DECRYPTION_FAILED = 26;
private final ILockSettings mBinder;
private RecoveryController(ILockSettings binder) {
mBinder = binder;
}
/**
* Gets a new instance of the class.
*/
public static RecoveryController getInstance() {
ILockSettings lockSettings =
ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
return new RecoveryController(lockSettings);
}
/**
* Initializes key recovery service for the calling application. RecoveryController
* randomly chooses one of the keys from the list and keeps it to use for future key export
* operations. Collection of all keys in the list must be signed by the provided {@code
* rootCertificateAlias}, which must also be present in the list of root certificates
* preinstalled on the device. The random selection allows RecoveryController to select
* which of a set of remote recovery service devices will be used.
*
* <p>In addition, RecoveryController enforces a delay of three months between
* consecutive initialization attempts, to limit the ability of an attacker to often switch
* remote recovery devices and significantly increase number of recovery attempts.
*
* @param rootCertificateAlias alias of a root certificate preinstalled on the device
* @param signedPublicKeyList binary blob a list of X509 certificates and signature
* @throws BadCertificateFormatException if the {@code signedPublicKeyList} is in a bad format.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void initRecoveryService(
@NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
throws BadCertificateFormatException, InternalRecoveryServiceException {
try {
mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
throw new BadCertificateFormatException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Returns data necessary to store all recoverable keys for given account. Key material is
* encrypted with user secret and recovery public key.
*
* @param account specific to Recovery agent.
* @return Data necessary to recover keystore.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
@NonNull public KeychainSnapshot getRecoveryData(@NonNull byte[] account)
throws InternalRecoveryServiceException {
try {
return mBinder.getRecoveryData(account);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_NO_SNAPSHOT_PENDING) {
return null;
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
* #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
* most one registered listener at any time.
*
* @param intent triggered when new snapshot is available. Unregisters listener if the value is
* {@code null}.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
throws InternalRecoveryServiceException {
try {
mBinder.setSnapshotCreatedPendingIntent(intent);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
* version. Version zero is used, if no snapshots were created for the account.
*
* @return Map from recovery agent accounts to snapshot versions.
* @see KeychainSnapshot#getSnapshotVersion
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
throws InternalRecoveryServiceException {
try {
// IPC doesn't support generic Maps.
@SuppressWarnings("unchecked")
Map<byte[], Integer> result =
(Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
return result;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Server parameters used to generate new recovery key blobs. This value will be included in
* {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
* in vaultParams {@link #startRecoverySession}
*
* @param serverParams included in recovery key blob.
* @see #getRecoveryData
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException {
try {
mBinder.setServerParams(serverParams);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Updates recovery status for given keys. It is used to notify keystore that key was
* successfully stored on the server or there were an error. Application can check this value
* using {@code getRecoveyStatus}.
*
* @param packageName Application whose recoverable keys' statuses are to be updated.
* @param aliases List of application-specific key aliases. If the array is empty, updates the
* status for all existing recoverable keys.
* @param status Status specific to recovery agent.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void setRecoveryStatus(
@NonNull String packageName, @Nullable String[] aliases, int status)
throws NameNotFoundException, InternalRecoveryServiceException {
try {
mBinder.setRecoveryStatus(packageName, aliases, status);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
* Negative status values are reserved for recovery agent specific codes. List of common codes:
*
* <ul>
* <li>{@link #RECOVERY_STATUS_SYNCED}
* <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
* <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
* <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
* </ul>
*
* @return {@code Map} from KeyStore alias to recovery status.
* @see #setRecoveryStatus
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public Map<String, Integer> getRecoveryStatus() throws InternalRecoveryServiceException {
try {
// IPC doesn't support generic Maps.
@SuppressWarnings("unchecked")
Map<String, Integer> result =
(Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
return result;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
* is necessary to recover data.
*
* @param secretTypes {@link KeychainProtectionParams#TYPE_LOCKSCREEN} or {@link
* KeychainProtectionParams#TYPE_CUSTOM_PASSWORD}
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void setRecoverySecretTypes(
@NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes)
throws InternalRecoveryServiceException {
try {
mBinder.setRecoverySecretTypes(secretTypes);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
* necessary to generate KeychainSnapshot.
*
* @return list of recovery secret types
* @see KeychainSnapshot
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public @NonNull @KeychainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
throws InternalRecoveryServiceException {
try {
return mBinder.getRecoverySecretTypes();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
* When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
* called.
*
* @return list of recovery secret types
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
@NonNull
public @KeychainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
throws InternalRecoveryServiceException {
try {
return mBinder.getPendingRecoverySecretTypes();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Method notifies KeyStore that a user-generated secret is available. This method generates a
* symmetric session key which a trusted remote device can use to return a recovery key. Caller
* should use {@link KeychainProtectionParams#clearSecret} to override the secret value in
* memory.
*
* @param recoverySecret user generated secret together with parameters necessary to regenerate
* it on a new device.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret)
throws InternalRecoveryServiceException {
try {
mBinder.recoverySecretAvailable(recoverySecret);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Initializes recovery session and returns a blob with proof of recovery secrets possession.
* The method generates symmetric key for a session, which trusted remote device can use to
* return recovery key.
*
* @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
* used to create the recovery blob on the source device.
* Keystore will verify the certificate using root of trust.
* @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
* Used to limit number of guesses.
* @param vaultChallenge Data passed from server for this recovery session and used to prevent
* replay attacks
* @param secrets Secrets provided by user, the method only uses type and secret fields.
* @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
* encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
* key and parameters necessary to identify the counter with the number of failed recovery
* attempts.
* @throws BadCertificateFormatException if the {@code verifierPublicKey} is in an incorrect
* format.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
@NonNull public RecoveryClaim startRecoverySession(
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
@NonNull List<KeychainProtectionParams> secrets)
throws BadCertificateFormatException, InternalRecoveryServiceException {
try {
RecoverySession recoverySession = RecoverySession.newInstance(this);
byte[] recoveryClaim =
mBinder.startRecoverySession(
recoverySession.getSessionId(),
verifierPublicKey,
vaultParams,
vaultChallenge,
secrets);
return new RecoveryClaim(recoverySession, recoveryClaim);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
throw new BadCertificateFormatException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Imports keys.
*
* @param session Related recovery session, as originally created by invoking
* {@link #startRecoverySession(byte[], byte[], byte[], List)}.
* @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
* @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
* and session. KeyStore only uses package names from the application info in {@link
* WrappedApplicationKey}. Caller is responsibility to perform certificates check.
* @return Map from alias to raw key material.
* @throws SessionExpiredException if {@code session} has since been closed.
* @throws DecryptionFailedException if unable to decrypt the snapshot.
* @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
*/
public Map<String, byte[]> recoverKeys(
@NonNull RecoverySession session,
@NonNull byte[] recoveryKeyBlob,
@NonNull List<WrappedApplicationKey> applicationKeys)
throws SessionExpiredException, DecryptionFailedException,
InternalRecoveryServiceException {
try {
return (Map<String, byte[]>) mBinder.recoverKeys(
session.getSessionId(), recoveryKeyBlob, applicationKeys);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_DECRYPTION_FAILED) {
throw new DecryptionFailedException(e.getMessage());
}
if (e.errorCode == ERROR_SESSION_EXPIRED) {
throw new SessionExpiredException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Deletes all data associated with {@code session}. Should not be invoked directly but via
* {@link RecoverySession#close()}.
*
* @hide
*/
void closeSession(RecoverySession session) {
try {
mBinder.closeSession(session.getSessionId());
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Unexpected error trying to close session", e);
}
}
/**
* Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
* raw material of the key.
*
* @param alias The key alias.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
* @throws LockScreenRequiredException if the user has not set a lock screen. This is required
* to generate recoverable keys, as the snapshots are encrypted using a key derived from the
* lock screen.
*/
public byte[] generateAndStoreKey(@NonNull String alias)
throws InternalRecoveryServiceException, LockScreenRequiredException {
try {
return mBinder.generateAndStoreKey(alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_INSECURE_USER) {
throw new LockScreenRequiredException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
* Removes a key called {@code alias} from the recoverable key store.
*
* @param alias The key alias.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void removeKey(@NonNull String alias) throws InternalRecoveryServiceException {
try {
mBinder.removeKey(alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
throw wrapUnexpectedServiceSpecificException(e);
}
}
private InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
ServiceSpecificException e) {
if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
return new InternalRecoveryServiceException(e.getMessage());
}
// Should never happen. If it does, it's a bug, and we need to update how the method that
// called this throws its exceptions.
return new InternalRecoveryServiceException("Unexpected error code for method: "
+ e.errorCode, e);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2018 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 java.security.GeneralSecurityException;
/**
* Base exception for errors thrown by {@link RecoveryController}.
*
* @hide
*/
public abstract class RecoveryControllerException extends GeneralSecurityException {
RecoveryControllerException() { }
RecoveryControllerException(String msg) {
super(msg);
}
public RecoveryControllerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 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 java.security.SecureRandom;
/**
* Session to recover a {@link KeychainSnapshot} from the remote trusted hardware, initiated by a
* recovery agent.
*
* @hide
*/
public class RecoverySession implements AutoCloseable {
private static final int SESSION_ID_LENGTH_BYTES = 16;
private final String mSessionId;
private final RecoveryController mRecoveryController;
private RecoverySession(RecoveryController recoveryController, String sessionId) {
mRecoveryController = recoveryController;
mSessionId = sessionId;
}
/**
* A new session, started by {@code recoveryManager}.
*/
static RecoverySession newInstance(RecoveryController recoveryController) {
return new RecoverySession(recoveryController, newSessionId());
}
/**
* Returns a new random session ID.
*/
private static String newSessionId() {
SecureRandom secureRandom = new SecureRandom();
byte[] sessionId = new byte[SESSION_ID_LENGTH_BYTES];
secureRandom.nextBytes(sessionId);
StringBuilder sb = new StringBuilder();
for (byte b : sessionId) {
sb.append(Byte.toHexString(b, /*upperCase=*/ false));
}
return sb.toString();
}
/**
* An internal session ID, used by the framework to match recovery claims to snapshot responses.
*/
String getSessionId() {
return mSessionId;
}
@Override
public void close() {
mRecoveryController.closeSession(this);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 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;
/**
* Error thrown when attempting to use a {@link RecoverySession} that has since expired.
*
* @hide
*/
public class SessionExpiredException extends RecoveryControllerException {
public SessionExpiredException(String msg) {
super(msg);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package android.security.keystore;
package android.security.keystore.recovery;
/* @hide */
parcelable WrappedApplicationKey;

View File

@@ -0,0 +1,145 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Helper class with data necessary recover a single application key, given a recovery key.
*
* <ul>
* <li>Alias - Keystore alias of the key.
* <li>Encrypted key material.
* </ul>
*
* Note that Application info is not included. Recovery Agent can only make its own keys
* recoverable.
*
* @hide
*/
public final class WrappedApplicationKey implements Parcelable {
private String mAlias;
// The only supported format is AES-256 symmetric key.
private byte[] mEncryptedKeyMaterial;
/**
* Builder for creating {@link WrappedApplicationKey}.
*/
public static class Builder {
private WrappedApplicationKey
mInstance = new WrappedApplicationKey();
/**
* Sets Application-specific alias of the key.
*
* @param alias The alias.
* @return This builder.
*/
public Builder setAlias(@NonNull String alias) {
mInstance.mAlias = alias;
return this;
}
/**
* Sets key material encrypted by recovery key.
*
* @param encryptedKeyMaterial The key material
* @return This builder
*/
public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) {
mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial;
return this;
}
/**
* Creates a new {@link WrappedApplicationKey} instance.
*
* @return new instance
* @throws NullPointerException if some required fields were not set.
*/
@NonNull public WrappedApplicationKey build() {
Preconditions.checkNotNull(mInstance.mAlias);
Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial);
return mInstance;
}
}
private WrappedApplicationKey() {
}
/**
* Deprecated - consider using Builder.
* @hide
*/
public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
mAlias = Preconditions.checkNotNull(alias);
mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
}
/**
* Application-specific alias of the key.
*
* @see java.security.KeyStore.aliases
*/
public @NonNull String getAlias() {
return mAlias;
}
/** Key material encrypted by recovery key. */
public @NonNull byte[] getEncryptedKeyMaterial() {
return mEncryptedKeyMaterial;
}
public static final Creator<WrappedApplicationKey> CREATOR =
new Creator<WrappedApplicationKey>() {
public WrappedApplicationKey createFromParcel(Parcel in) {
return new WrappedApplicationKey(in);
}
public WrappedApplicationKey[] newArray(int length) {
return new WrappedApplicationKey[length];
}
};
/**
* @hide
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mAlias);
out.writeByteArray(mEncryptedKeyMaterial);
}
/**
* @hide
*/
protected WrappedApplicationKey(Parcel in) {
mAlias = in.readString();
mEncryptedKeyMaterial = in.createByteArray();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -19,9 +19,9 @@ package com.android.internal.widget;
import android.app.PendingIntent;
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
import android.security.keystore.WrappedApplicationKey;
import android.security.keystore.KeychainSnapshot;
import android.security.keystore.KeychainProtectionParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore.recovery.KeychainSnapshot;
import android.security.keystore.recovery.KeychainProtectionParams;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;