identity: Add support for ECDSA auth and don't require session encryption.

This adds a new method which allows applications to use mdoc ECDSA
authentication instead of mdoc MAC authentication. Additionally, also
relax requirements on SessionTranscript so the APIs can be used even
when mdoc session encryption isn't being used.

Bug: 241912421
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts

Change-Id: I25336f1352102208887531d066ec432a9ae3cd36
This commit is contained in:
David Zeuthen
2022-10-04 13:18:12 -04:00
parent 9375c9ce61
commit 55f62fc125
10 changed files with 100 additions and 19 deletions

View File

@@ -37682,6 +37682,7 @@ package android.security.identity {
public abstract class CredentialDataResult { public abstract class CredentialDataResult {
method @Nullable public abstract byte[] getDeviceMac(); method @Nullable public abstract byte[] getDeviceMac();
method @NonNull public abstract byte[] getDeviceNameSpaces(); method @NonNull public abstract byte[] getDeviceNameSpaces();
method @Nullable public byte[] getDeviceSignature();
method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries(); method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getDeviceSignedEntries();
method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries(); method @NonNull public abstract android.security.identity.CredentialDataResult.Entries getIssuerSignedEntries();
method @NonNull public abstract byte[] getStaticAuthenticationData(); method @NonNull public abstract byte[] getStaticAuthenticationData();

View File

@@ -105,6 +105,30 @@ public abstract class CredentialDataResult {
*/ */
public abstract @Nullable byte[] getDeviceMac(); public abstract @Nullable byte[] getDeviceMac();
/**
* Returns a signature over the {@code DeviceAuthenticationBytes} CBOR
* specified in {@link #getDeviceNameSpaces()}, to prove to the reader that the data
* is from a trusted credential.
*
* <p>The signature is made using the authentication private key. See section 9.1.3.4 of
* ISO/IEC 18013-5:2021 for details of this operation.
*
* <p>If the session transcript or reader ephemeral key wasn't set on the {@link
* PresentationSession} used to obtain this data no signature will be produced and this method
* will return {@code null}.
*
* <p>This is only implemented in feature version 202301 or later. If not implemented, the call
* fails with {@link UnsupportedOperationException}. See
* {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
* feature versions.
*
* @return A COSE_Sign1 structure as described above or {@code null} if the conditions
* specified above are not met.
*/
public @Nullable byte[] getDeviceSignature() {
throw new UnsupportedOperationException();
}
/** /**
* Returns the static authentication data associated with the dynamic authentication * Returns the static authentication data associated with the dynamic authentication
* key used to MAC the data returned by {@link #getDeviceNameSpaces()}. * key used to MAC the data returned by {@link #getDeviceNameSpaces()}.

View File

@@ -46,6 +46,11 @@ class CredstoreCredentialDataResult extends CredentialDataResult {
return mDeviceSignedResult.getMessageAuthenticationCode(); return mDeviceSignedResult.getMessageAuthenticationCode();
} }
@Override
public @Nullable byte[] getDeviceSignature() {
return mDeviceSignedResult.getSignature();
}
@Override @Override
public @NonNull byte[] getStaticAuthenticationData() { public @NonNull byte[] getStaticAuthenticationData() {
return mDeviceSignedResult.getStaticAuthenticationData(); return mDeviceSignedResult.getStaticAuthenticationData();

View File

@@ -60,16 +60,19 @@ class CredstoreIdentityCredential extends IdentityCredential {
private Context mContext; private Context mContext;
private ICredential mBinder; private ICredential mBinder;
private CredstorePresentationSession mSession; private CredstorePresentationSession mSession;
private int mFeatureVersion;
CredstoreIdentityCredential(Context context, String credentialName, CredstoreIdentityCredential(Context context, String credentialName,
@IdentityCredentialStore.Ciphersuite int cipherSuite, @IdentityCredentialStore.Ciphersuite int cipherSuite,
ICredential binder, ICredential binder,
@Nullable CredstorePresentationSession session) { @Nullable CredstorePresentationSession session,
int featureVersion) {
mContext = context; mContext = context;
mCredentialName = credentialName; mCredentialName = credentialName;
mCipherSuite = cipherSuite; mCipherSuite = cipherSuite;
mBinder = binder; mBinder = binder;
mSession = session; mSession = session;
mFeatureVersion = featureVersion;
} }
private KeyPair mEphemeralKeyPair = null; private KeyPair mEphemeralKeyPair = null;
@@ -347,12 +350,18 @@ class CredstoreIdentityCredential extends IdentityCredential {
} }
} }
byte[] signature = resultParcel.signature;
if (signature != null && signature.length == 0) {
signature = null;
}
byte[] mac = resultParcel.mac; byte[] mac = resultParcel.mac;
if (mac != null && mac.length == 0) { if (mac != null && mac.length == 0) {
mac = null; mac = null;
} }
CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder( CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac); mFeatureVersion, resultParcel.staticAuthenticationData,
resultParcel.deviceNameSpaces, mac, signature);
for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) { for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) { for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {

View File

@@ -19,6 +19,8 @@ package android.security.identity;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.security.GenerateRkpKey; import android.security.GenerateRkpKey;
@@ -30,10 +32,28 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
private Context mContext = null; private Context mContext = null;
private ICredentialStore mStore = null; private ICredentialStore mStore = null;
private int mFeatureVersion;
static int getFeatureVersion(@NonNull Context context) {
PackageManager pm = context.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
FeatureInfo[] infos = pm.getSystemAvailableFeatures();
for (int n = 0; n < infos.length; n++) {
FeatureInfo info = infos[n];
if (info.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
return info.version;
}
}
}
// Use of the system feature is not required since Android 12. So for Android 11
// return 202009 which is the feature version shipped with Android 11.
return 202009;
}
private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) { private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
mContext = context; mContext = context;
mStore = store; mStore = store;
mFeatureVersion = getFeatureVersion(mContext);
} }
static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context, static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
@@ -139,8 +159,7 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
ICredential credstoreCredential; ICredential credstoreCredential;
credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite); credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite, return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
credstoreCredential, credstoreCredential, null, mFeatureVersion);
null);
} catch (android.os.RemoteException e) { } catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e); throw new RuntimeException("Unexpected RemoteException ", e);
} catch (android.os.ServiceSpecificException e) { } catch (android.os.ServiceSpecificException e) {
@@ -182,7 +201,8 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
throws CipherSuiteNotSupportedException { throws CipherSuiteNotSupportedException {
try { try {
ISession credstoreSession = mStore.createPresentationSession(cipherSuite); ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession); return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession,
mFeatureVersion);
} catch (android.os.RemoteException e) { } catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e); throw new RuntimeException("Unexpected RemoteException ", e);
} catch (android.os.ServiceSpecificException e) { } catch (android.os.ServiceSpecificException e) {

View File

@@ -48,15 +48,18 @@ class CredstorePresentationSession extends PresentationSession {
private byte[] mSessionTranscript = null; private byte[] mSessionTranscript = null;
private boolean mOperationHandleSet = false; private boolean mOperationHandleSet = false;
private long mOperationHandle = 0; private long mOperationHandle = 0;
private int mFeatureVersion = 0;
CredstorePresentationSession(Context context, CredstorePresentationSession(Context context,
@IdentityCredentialStore.Ciphersuite int cipherSuite, @IdentityCredentialStore.Ciphersuite int cipherSuite,
CredstoreIdentityCredentialStore store, CredstoreIdentityCredentialStore store,
ISession binder) { ISession binder,
int featureVersion) {
mContext = context; mContext = context;
mCipherSuite = cipherSuite; mCipherSuite = cipherSuite;
mStore = store; mStore = store;
mBinder = binder; mBinder = binder;
mFeatureVersion = featureVersion;
} }
private void ensureEphemeralKeyPair() { private void ensureEphemeralKeyPair() {
@@ -147,7 +150,7 @@ class CredstorePresentationSession extends PresentationSession {
mBinder.getCredentialForPresentation(credentialName); mBinder.getCredentialForPresentation(credentialName);
credential = new CredstoreIdentityCredential(mContext, credentialName, credential = new CredstoreIdentityCredential(mContext, credentialName,
mCipherSuite, credstoreCredential, mCipherSuite, credstoreCredential,
this); this, mFeatureVersion);
mCredentialCache.put(credentialName, credential); mCredentialCache.put(credentialName, credential);
credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys()); credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());

View File

@@ -30,10 +30,11 @@ import java.util.Map;
* data requested from a {@link IdentityCredential}. * data requested from a {@link IdentityCredential}.
*/ */
class CredstoreResultData extends ResultData { class CredstoreResultData extends ResultData {
int mFeatureVersion = 0;
byte[] mStaticAuthenticationData = null; byte[] mStaticAuthenticationData = null;
byte[] mAuthenticatedData = null; byte[] mAuthenticatedData = null;
byte[] mMessageAuthenticationCode = null; byte[] mMessageAuthenticationCode = null;
byte[] mSignature = null;
private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>(); private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
@@ -60,6 +61,14 @@ class CredstoreResultData extends ResultData {
return mMessageAuthenticationCode; return mMessageAuthenticationCode;
} }
@Override
@Nullable byte[] getSignature() {
if (mFeatureVersion < 202301) {
throw new UnsupportedOperationException();
}
return mSignature;
}
@Override @Override
public @NonNull byte[] getStaticAuthenticationData() { public @NonNull byte[] getStaticAuthenticationData() {
return mStaticAuthenticationData; return mStaticAuthenticationData;
@@ -124,13 +133,17 @@ class CredstoreResultData extends ResultData {
static class Builder { static class Builder {
private CredstoreResultData mResultData; private CredstoreResultData mResultData;
Builder(byte[] staticAuthenticationData, Builder(int featureVersion,
byte[] staticAuthenticationData,
byte[] authenticatedData, byte[] authenticatedData,
byte[] messageAuthenticationCode) { byte[] messageAuthenticationCode,
byte[] signature) {
this.mResultData = new CredstoreResultData(); this.mResultData = new CredstoreResultData();
this.mResultData.mFeatureVersion = featureVersion;
this.mResultData.mStaticAuthenticationData = staticAuthenticationData; this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
this.mResultData.mAuthenticatedData = authenticatedData; this.mResultData.mAuthenticatedData = authenticatedData;
this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode; this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
this.mResultData.mSignature = signature;
} }
private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) { private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {

View File

@@ -236,14 +236,15 @@ public abstract class IdentityCredential {
* IntentToRetain = bool * IntentToRetain = bool
* </pre> * </pre>
* *
* <p>If the {@code sessionTranscript} parameter is not {@code null}, the X and Y coordinates * <p>If mdoc session encryption is used (e.g. if {@link #createEphemeralKeyPair()} has been
* of the public part of the key-pair previously generated by {@link #createEphemeralKeyPair()} * called) and if the {@code sessionTranscript} parameter is not {@code null}, the X and Y
* must appear somewhere in the bytes of the CBOR. Each of these coordinates must appear * coordinates of the public part of the key-pair previously generated by
* encoded with the most significant bits first and use the exact amount of bits indicated by * {@link #createEphemeralKeyPair()} must appear somewhere in the bytes of the CBOR. Each of
* the key size of the ephemeral keys. For example, if the ephemeral key is using the P-256 * these coordinates must appear encoded with the most significant bits first and use the
* curve then the 32 bytes for the X coordinate encoded with the most significant bits first * exact amount of bits indicated by the key size of the ephemeral keys. For example, if the
* must appear somewhere in {@code sessionTranscript} and ditto for the 32 bytes for the Y * ephemeral key is using the P-256 curve then the 32 bytes for the X coordinate encoded with
* coordinate. * the most significant bits first must appear somewhere in {@code sessionTranscript} and
* ditto for the 32 bytes for the Y coordinate.
* *
* <p>If {@code readerSignature} is not {@code null} it must be the bytes of a * <p>If {@code readerSignature} is not {@code null} it must be the bytes of a
* {@code COSE_Sign1} structure as defined in RFC 8152. For the payload nil shall be used and * {@code COSE_Sign1} structure as defined in RFC 8152. For the payload nil shall be used and

View File

@@ -73,7 +73,8 @@ public abstract class PresentationSession {
* <p>If called, this must be called before any calls to * <p>If called, this must be called before any calls to
* {@link #getCredentialData(String, CredentialDataRequest)}. * {@link #getCredentialData(String, CredentialDataRequest)}.
* *
* <p>The X and Y coordinates of the public part of the key-pair returned by {@link * <p>If mdoc session encryption is used (e.g. if {@link #getEphemeralKeyPair()} has been
* called) then the X and Y coordinates of the public part of the key-pair returned by {@link
* #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR. Each of * #getEphemeralKeyPair()} must appear somewhere in the bytes of the passed in CBOR. Each of
* these coordinates must appear encoded with the most significant bits first and use the exact * these coordinates must appear encoded with the most significant bits first and use the exact
* amount of bits indicated by the key size of the ephemeral keys. For example, if the * amount of bits indicated by the key size of the ephemeral keys. For example, if the

View File

@@ -134,6 +134,10 @@ public abstract class ResultData {
*/ */
public abstract @Nullable byte[] getMessageAuthenticationCode(); public abstract @Nullable byte[] getMessageAuthenticationCode();
@Nullable byte[] getSignature() {
throw new UnsupportedOperationException();
}
/** /**
* Returns the static authentication data associated with the dynamic authentication * Returns the static authentication data associated with the dynamic authentication
* key used to sign or MAC the data returned by {@link #getAuthenticatedData()}. * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.