Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow instantiating SecureArea.CreateKeySettings object. #409

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ object PreferencesHelper {
// As per the docs, the credential data contains reference to Keystore aliases so ensure
// this is stored in a location where it's not automatically backed up and restored by
// Android Backup as per https://developer.android.com/guide/topics/data/autobackup
return context.noBackupFilesDir

val storageDir = File(context.noBackupFilesDir, "appholder")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning for changing the storage dir here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we're breaking the on-disk format. Without this change, the application will load old data and crash. With this change, the app won't crash but it'll no credentials. At this point, such a breaking change is fine (it's just a test app after all) but as the commit message says, in the future we'll commit to stable file formats.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha, thanks!

if (!storageDir.exists()) {
storageDir.mkdir()
}
return storageDir;
}

fun isBleDataRetrievalEnabled(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,29 +92,33 @@ class ProvisioningUtil private constructor(
nameSpacedData: NameSpacedData,
provisionInfo: ProvisionInfo,
) {
val settings = when (provisionInfo.secureAreaImplementationStateType) {
SecureAreaImplementationState.Android -> createAndroidKeystoreSettings(
userAuthenticationRequired = provisionInfo.userAuthentication,
mDocAuthOption = provisionInfo.mDocAuthenticationOption,
authTimeoutMillis = provisionInfo.userAuthenticationTimeoutSeconds * 1000L,
userAuthenticationType = provisionInfo.userAuthType(),
useStrongBox = provisionInfo.useStrongBox,
ecCurve = provisionInfo.authKeyCurve,
validUntil = provisionInfo.validityInDays.toTimestampFromNow()
)

SecureAreaImplementationState.BouncyCastle -> createBouncyCastleKeystoreSettings(
passphrase = provisionInfo.passphrase,
mDocAuthOption = provisionInfo.mDocAuthenticationOption,
ecCurve = provisionInfo.authKeyCurve
)
val (secureArea, settings) = when (provisionInfo.secureAreaImplementationStateType) {
SecureAreaImplementationState.Android -> Pair(
androidKeystoreSecureArea,
createAndroidKeystoreSettings(
userAuthenticationRequired = provisionInfo.userAuthentication,
mDocAuthOption = provisionInfo.mDocAuthenticationOption,
authTimeoutMillis = provisionInfo.userAuthenticationTimeoutSeconds * 1000L,
userAuthenticationType = provisionInfo.userAuthType(),
useStrongBox = provisionInfo.useStrongBox,
ecCurve = provisionInfo.authKeyCurve,
validUntil = provisionInfo.validityInDays.toTimestampFromNow()
))

SecureAreaImplementationState.BouncyCastle -> Pair(
softwareSecureArea,
createBouncyCastleKeystoreSettings(
passphrase = provisionInfo.passphrase,
mDocAuthOption = provisionInfo.mDocAuthenticationOption,
ecCurve = provisionInfo.authKeyCurve
))
}

val credential = credentialStore.createCredential(provisionInfo.credentialName(), settings)
val credential = credentialStore.createCredential(provisionInfo.credentialName(), secureArea, settings)
credential.nameSpacedData = nameSpacedData

repeat(provisionInfo.numberMso) {
val pendingKey = credential.createPendingAuthenticationKey(settings, null)
val pendingKey = credential.createPendingAuthenticationKey(secureArea, settings, null)
pendingKey.applicationData.setBoolean(AUTH_KEY_DOMAIN, true)
}
provisionAuthKeys(credential, provisionInfo.docType, provisionInfo.validityInDays)
Expand Down Expand Up @@ -284,26 +288,30 @@ class ProvisioningUtil private constructor(

private fun manageKeysFor(credential: Credential): Int {
val mDocAuthOption = credential.applicationData.getString(MDOC_AUTHENTICATION)
val settings = when (credential.credentialSecureArea) {
val (secureArea, settings) = when (credential.credentialSecureArea) {
is AndroidKeystoreSecureArea -> {
val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as AndroidKeystoreSecureArea.KeyInfo
createAndroidKeystoreSettings(
keyInfo.isUserAuthenticationRequired,
AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption),
keyInfo.userAuthenticationTimeoutMillis,
keyInfo.userAuthenticationType,
keyInfo.isStrongBoxBacked,
keyInfo.ecCurve,
keyInfo.validUntil ?: Timestamp.now()
)
Pair(
androidKeystoreSecureArea,
createAndroidKeystoreSettings(
keyInfo.isUserAuthenticationRequired,
AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption),
keyInfo.userAuthenticationTimeoutMillis,
keyInfo.userAuthenticationType,
keyInfo.isStrongBoxBacked,
keyInfo.ecCurve,
keyInfo.validUntil ?: Timestamp.now()
))
}

is SoftwareSecureArea -> {
val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as SoftwareSecureArea.KeyInfo
createBouncyCastleKeystoreSettings(
mDocAuthOption = AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption),
ecCurve = keyInfo.ecCurve
)
Pair(
softwareSecureArea,
createBouncyCastleKeystoreSettings(
mDocAuthOption = AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption),
ecCurve = keyInfo.ecCurve
))
}

else -> throw IllegalStateException("Unknown keystore secure area implementation")
Expand All @@ -312,6 +320,7 @@ class ProvisioningUtil private constructor(
val maxUsagesPerKey = credential.applicationData.getNumber(MAX_USAGES_PER_KEY)
return CredentialUtil.managedAuthenticationKeyHelper(
credential,
secureArea,
settings,
AUTH_KEY_DOMAIN,
Timestamp.now(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public void testBasic() throws IOException {

Credential credential = credentialStore.createCredential(
"testCredential",
mSecureArea,
new AndroidKeystoreSecureArea.CreateKeySettings.Builder(credentialKeyAttestationChallenge).build());
Assert.assertEquals("testCredential", credential.getName());
List<X509Certificate> certChain = credential.getAttestation();
Expand All @@ -82,11 +83,13 @@ public void testBasic() throws IOException {
// Create pending authentication key and check its attestation
byte[] authKeyChallenge = new byte[] {20, 21, 22};
Credential.PendingAuthenticationKey pendingAuthenticationKey =
credential.createPendingAuthenticationKey(new AndroidKeystoreSecureArea.CreateKeySettings.Builder(authKeyChallenge)
.setUserAuthenticationRequired(true, 30*1000,
AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_LSKF
| AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_BIOMETRIC)
.build(),
credential.createPendingAuthenticationKey(
mSecureArea,
new AndroidKeystoreSecureArea.CreateKeySettings.Builder(authKeyChallenge)
.setUserAuthenticationRequired(true, 30*1000,
AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_LSKF
| AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_BIOMETRIC)
.build(),
null);
parser = new AndroidAttestationExtensionParser(pendingAuthenticationKey.getAttestation().get(0));
Assert.assertArrayEquals(authKeyChallenge,
Expand All @@ -109,6 +112,7 @@ public void testBasic() throws IOException {
// Check creating a credential with an existing name overwrites the existing one
credential = credentialStore.createCredential(
"testCredential",
mSecureArea,
new AndroidKeystoreSecureArea.CreateKeySettings.Builder(credentialKeyAttestationChallenge).build());
Assert.assertEquals("testCredential", credential.getName());
// At least the leaf certificate should be different
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,19 @@ public class MigrateFromKeystoreICStoreTest {
IdentityCredentialStore mICStore;
StorageEngine mStorageEngine;

AndroidKeystoreSecureArea mKeystoreEngine;
AndroidKeystoreSecureArea mAndroidKeystoreSecureArea;

SecureAreaRepository mKeystoreEngineRepository;
SecureAreaRepository mSecureAreaRepository;

@Before
public void setup() {
Context context = androidx.test.InstrumentationRegistry.getTargetContext();
File storageDir = new File(context.getDataDir(), "ic-testing");
mStorageEngine = new AndroidStorageEngine.Builder(context, storageDir).build();

mKeystoreEngineRepository = new SecureAreaRepository();
mKeystoreEngine = new AndroidKeystoreSecureArea(context, mStorageEngine);
mKeystoreEngineRepository.addImplementation(mKeystoreEngine);
mSecureAreaRepository = new SecureAreaRepository();
mAndroidKeystoreSecureArea = new AndroidKeystoreSecureArea(context, mStorageEngine);
mSecureAreaRepository.addImplementation(mAndroidKeystoreSecureArea);

mICStore = Utility.getIdentityCredentialStore(context);
}
Expand Down Expand Up @@ -240,8 +240,8 @@ private void migrateAndCheckResults(String credName,
// migrate
CredentialStore credentialStore = new CredentialStore(
mStorageEngine,
mKeystoreEngineRepository);
Credential migratedCred = keystoreCred.migrateToCredentialStore(credentialStore);
mSecureAreaRepository);
Credential migratedCred = keystoreCred.migrateToCredentialStore(mAndroidKeystoreSecureArea, credentialStore);

// check deletion
assertNull(mICStore.getCredentialByName(
Expand Down Expand Up @@ -302,9 +302,12 @@ private void migrateAndCheckResults(String credName,
assertNull(migratedCred.findAuthenticationKey(Timestamp.ofEpochMilli(100)));
byte[] authKeyChallenge = new byte[] {20, 21, 22};
Credential.PendingAuthenticationKey pendingAuthenticationKey =
migratedCred.createPendingAuthenticationKey(new AndroidKeystoreSecureArea.CreateKeySettings.Builder(authKeyChallenge)
migratedCred.createPendingAuthenticationKey(
mAndroidKeystoreSecureArea,
new AndroidKeystoreSecureArea.CreateKeySettings.Builder(authKeyChallenge)
.setUserAuthenticationRequired(true, 30*1000,
AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_LSKF)
AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_LSKF |
AndroidKeystoreSecureArea.USER_AUTHENTICATION_TYPE_BIOMETRIC)
.build(),
null);
parser = new AndroidAttestationExtensionParser(pendingAuthenticationKey.getAttestation().get(0));
Expand Down Expand Up @@ -547,8 +550,8 @@ public void deleteBeforeMigrationTest() throws Exception {
mICStore.deleteCredentialByName(credName);
CredentialStore credentialStore = new CredentialStore(
mStorageEngine,
mKeystoreEngineRepository);
mSecureAreaRepository);
assertNull(mICStore.getCredentialByName(credName, IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256));
assertThrows(IllegalStateException.class, () -> keystoreCred.migrateToCredentialStore(credentialStore));
assertThrows(IllegalStateException.class, () -> keystoreCred.migrateToCredentialStore(mAndroidKeystoreSecureArea, credentialStore));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import com.android.identity.AndroidAttestationExtensionParser;
import com.android.identity.android.storage.AndroidStorageEngine;
import com.android.identity.securearea.SecureArea;
import com.android.identity.securearea.SoftwareSecureArea;
import com.android.identity.storage.EphemeralStorageEngine;
import com.android.identity.storage.StorageEngine;
import com.android.identity.util.Timestamp;

Expand Down Expand Up @@ -882,4 +884,27 @@ public void testAttestKeyHelper(Context context, boolean useStrongBox) throws IO
: AndroidAttestationExtensionParser.SecurityLevel.TRUSTED_ENVIRONMENT, securityLevel);
}

@Test
public void testUsingGenericCreateKeySettings() throws IOException {
Context context = androidx.test.InstrumentationRegistry.getTargetContext();
File storageDir = new File(context.getDataDir(), "ic-testing");
StorageEngine storageEngine = new AndroidStorageEngine.Builder(context, storageDir).build();
AndroidKeystoreSecureArea ks = new AndroidKeystoreSecureArea(context, storageEngine);

byte[] challenge = new byte[] {1, 2, 3, 4};
ks.createKey("testKey", new SecureArea.CreateKeySettings(challenge));

AndroidKeystoreSecureArea.KeyInfo keyInfo = ks.getKeyInfo("testKey");
Assert.assertNotNull(keyInfo);
Assert.assertEquals(SecureArea.KEY_PURPOSE_SIGN, keyInfo.getKeyPurposes());
Assert.assertEquals(SecureArea.EC_CURVE_P256, keyInfo.getEcCurve());
Assert.assertTrue(keyInfo.isHardwareBacked());

AndroidAttestationExtensionParser parser =
new AndroidAttestationExtensionParser(keyInfo.getAttestation().get(0));
Assert.assertArrayEquals(challenge, parser.getAttestationChallenge());

// Now delete it...
ks.deleteKey("testKey");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -840,21 +840,26 @@ List<Calendar> getAuthenticationDataExpirations() {
* new {@link Credential}, while the access control profile information, per reader session/acp
* timeout/auth keys will not be transferred.
*
* @param androidKeystoreSecureArea an {@link AndroidKeystoreSecureArea} instance.
* @param credentialStore the credential store where the new {@link Credential} should be stored.
* @return the new {@link Credential}.
*/
public @NonNull Credential migrateToCredentialStore(@NonNull CredentialStore credentialStore) {
public @NonNull Credential migrateToCredentialStore(
@NonNull AndroidKeystoreSecureArea androidKeystoreSecureArea,
@NonNull CredentialStore credentialStore) {
loadData();

if (mData == null) {
throw new IllegalStateException("The credential has been deleted prior to migration.");
}
String aliasForOldCredKey = mData.getCredentialKeyAlias();
AndroidKeystoreSecureArea.CreateKeySettings.Builder keySettingsBuilder = Utility.extractKeySettings(aliasForOldCredKey);
keySettingsBuilder.setEcCurve(SecureArea.EC_CURVE_P256);
AndroidKeystoreSecureArea.CreateKeySettings.Builder ksSettingsBuilder = Utility.extractKeySettings(aliasForOldCredKey);
ksSettingsBuilder.setEcCurve(SecureArea.EC_CURVE_P256);

Credential newCred = credentialStore.createCredentialWithExistingKey(mCredentialName,
keySettingsBuilder.build(), aliasForOldCredKey);
androidKeystoreSecureArea,
ksSettingsBuilder.build(),
aliasForOldCredKey);

NameSpacedData.Builder nsBuilder = new NameSpacedData.Builder();
for (PersonalizationData.NamespaceData namespaceData : mData.getNamespaceDatas()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import com.android.identity.internal.Util;
import com.android.identity.securearea.SecureArea;
import com.android.identity.securearea.SoftwareSecureArea;
import com.android.identity.storage.StorageEngine;
import com.android.identity.util.Logger;
import com.android.identity.util.Timestamp;
Expand Down Expand Up @@ -159,10 +160,31 @@ public AndroidKeystoreSecureArea(@NonNull Context context,
mKeymintSbFeatureLevel = getFeatureVersionKeystore(context, true);
}

@NonNull
@Override
public String getIdentifier() {
return "AndroidKeystoreSecureArea";
}

@NonNull
@Override
public String getDisplayName() {
return "Android Keystore Secure Area";
}

@Override
public void createKey(@NonNull String alias,
@NonNull SecureArea.CreateKeySettings createKeySettings) {
CreateKeySettings aSettings = (CreateKeySettings) createKeySettings;
CreateKeySettings aSettings;
if (createKeySettings instanceof CreateKeySettings) {
aSettings = (CreateKeySettings) createKeySettings;
} else {
// Use default settings if user passed in a generic SecureArea.CreateKeySettings.
aSettings = new AndroidKeystoreSecureArea.CreateKeySettings.Builder(
createKeySettings.getAttestationChallenge())
.build();
}

if (aSettings.getExistingKeyAlias() != null) {
createFromExistingKey(aSettings.getExistingKeyAlias(), aSettings);
return;
Expand Down Expand Up @@ -858,7 +880,6 @@ private void saveKeyMetadata(@NonNull String alias,
public static class CreateKeySettings extends SecureArea.CreateKeySettings {
private final @KeyPurpose int mKeyPurposes;
private final @EcCurve int mEcCurve;
private final byte[] mAttestationChallenge;
private final boolean mUserAuthenticationRequired;
private final long mUserAuthenticationTimeoutMillis;
private final @UserAuthenticationType int mUserAuthenticationType;
Expand All @@ -879,10 +900,9 @@ private CreateKeySettings(@KeyPurpose int keyPurpose,
@Nullable Timestamp validFrom,
@Nullable Timestamp validUntil,
@Nullable String existingKeyAlias) {
super(AndroidKeystoreSecureArea.class);
super(attestationChallenge);
mKeyPurposes = keyPurpose;
mEcCurve = ecCurve;
mAttestationChallenge = attestationChallenge;
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationTimeoutMillis = userAuthenticationTimeoutMillis;
mUserAuthenticationType = userAuthenticationType;
Expand All @@ -893,15 +913,6 @@ private CreateKeySettings(@KeyPurpose int keyPurpose,
mExistingKeyAlias = existingKeyAlias;
}

/**
* Gets the attestation challenge.
*
* @return the attestation challenge.
*/
public @NonNull byte[] getAttestationChallenge() {
return mAttestationChallenge;
}

/**
* Gets whether user authentication is required.
*
Expand Down
Loading
Loading