Merge "Add Framework APIs for Identity Credential."
This commit is contained in:
commit
2fb5112a35
11
Android.bp
11
Android.bp
@ -73,6 +73,14 @@ filegroup {
|
||||
path: "graphics/java",
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-identity-sources",
|
||||
srcs: [
|
||||
"identity/java/**/*.java",
|
||||
],
|
||||
path: "identity/java",
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-keystore-sources",
|
||||
srcs: [
|
||||
@ -217,6 +225,7 @@ filegroup {
|
||||
":framework-drm-sources",
|
||||
":framework-graphics-sources",
|
||||
":framework-keystore-sources",
|
||||
":framework-identity-sources",
|
||||
":framework-location-sources",
|
||||
":framework-lowpan-sources",
|
||||
":framework-media-sources",
|
||||
@ -239,6 +248,7 @@ filegroup {
|
||||
":platform-compat-native-aidl",
|
||||
|
||||
// AIDL sources from external directories
|
||||
":credstore_aidl",
|
||||
":dumpstate_aidl",
|
||||
":framework_native_aidl",
|
||||
":gatekeeper_aidl",
|
||||
@ -290,6 +300,7 @@ java_defaults {
|
||||
"core/java",
|
||||
"drm/java",
|
||||
"graphics/java",
|
||||
"identity/java",
|
||||
"keystore/java",
|
||||
"location/java",
|
||||
"lowpan/java",
|
||||
|
137
api/current.txt
137
api/current.txt
@ -16637,7 +16637,9 @@ package android.hardware.biometrics {
|
||||
ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature);
|
||||
ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher);
|
||||
ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac);
|
||||
ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
|
||||
method public javax.crypto.Cipher getCipher();
|
||||
method @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
|
||||
method public javax.crypto.Mac getMac();
|
||||
method public java.security.Signature getSignature();
|
||||
}
|
||||
@ -17575,7 +17577,9 @@ package android.hardware.fingerprint {
|
||||
ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
|
||||
ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
|
||||
ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
|
||||
ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential);
|
||||
method @Deprecated public javax.crypto.Cipher getCipher();
|
||||
method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
|
||||
method @Deprecated public javax.crypto.Mac getMac();
|
||||
method @Deprecated public java.security.Signature getSignature();
|
||||
}
|
||||
@ -41059,6 +41063,139 @@ package android.security {
|
||||
|
||||
}
|
||||
|
||||
package android.security.identity {
|
||||
|
||||
public class AccessControlProfile {
|
||||
}
|
||||
|
||||
public static final class AccessControlProfile.Builder {
|
||||
ctor public AccessControlProfile.Builder(@NonNull android.security.identity.AccessControlProfileId);
|
||||
method @NonNull public android.security.identity.AccessControlProfile build();
|
||||
method @NonNull public android.security.identity.AccessControlProfile.Builder setReaderCertificate(@NonNull java.security.cert.X509Certificate);
|
||||
method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean);
|
||||
method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long);
|
||||
}
|
||||
|
||||
public class AccessControlProfileId {
|
||||
ctor public AccessControlProfileId(int);
|
||||
method public int getId();
|
||||
}
|
||||
|
||||
public class AlreadyPersonalizedException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public AlreadyPersonalizedException(@NonNull String);
|
||||
ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public CipherSuiteNotSupportedException(@NonNull String);
|
||||
ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public DocTypeNotSupportedException(@NonNull String);
|
||||
ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class EphemeralPublicKeyNotFoundException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public EphemeralPublicKeyNotFoundException(@NonNull String);
|
||||
ctor public EphemeralPublicKeyNotFoundException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public abstract class IdentityCredential {
|
||||
method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
|
||||
method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
|
||||
method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
|
||||
method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
|
||||
method @NonNull public abstract int[] getAuthenticationDataUsageCount();
|
||||
method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
|
||||
method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
|
||||
method public abstract void setAllowUsingExhaustedKeys(boolean);
|
||||
method public abstract void setAvailableAuthenticationKeys(int, int);
|
||||
method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
|
||||
method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
|
||||
}
|
||||
|
||||
public class IdentityCredentialException extends java.lang.Exception {
|
||||
ctor public IdentityCredentialException(@NonNull String);
|
||||
ctor public IdentityCredentialException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public abstract class IdentityCredentialStore {
|
||||
method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
|
||||
method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
|
||||
method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
|
||||
method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
|
||||
method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context);
|
||||
method @NonNull public abstract String[] getSupportedDocTypes();
|
||||
field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1
|
||||
}
|
||||
|
||||
public class InvalidReaderSignatureException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public InvalidReaderSignatureException(@NonNull String);
|
||||
ctor public InvalidReaderSignatureException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class InvalidRequestMessageException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public InvalidRequestMessageException(@NonNull String);
|
||||
ctor public InvalidRequestMessageException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class MessageDecryptionException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public MessageDecryptionException(@NonNull String);
|
||||
ctor public MessageDecryptionException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class NoAuthenticationKeyAvailableException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public NoAuthenticationKeyAvailableException(@NonNull String);
|
||||
ctor public NoAuthenticationKeyAvailableException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class PersonalizationData {
|
||||
}
|
||||
|
||||
public static final class PersonalizationData.Builder {
|
||||
ctor public PersonalizationData.Builder();
|
||||
method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile);
|
||||
method @NonNull public android.security.identity.PersonalizationData build();
|
||||
method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
|
||||
}
|
||||
|
||||
public abstract class ResultData {
|
||||
method @NonNull public abstract byte[] getAuthenticatedData();
|
||||
method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
|
||||
method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
|
||||
method @Nullable public abstract byte[] getMessageAuthenticationCode();
|
||||
method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames();
|
||||
method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
|
||||
method @NonNull public abstract byte[] getStaticAuthenticationData();
|
||||
method public abstract int getStatus(@NonNull String, @NonNull String);
|
||||
field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
|
||||
field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
|
||||
field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
|
||||
field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
|
||||
field public static final int STATUS_OK = 0; // 0x0
|
||||
field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
|
||||
field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
|
||||
}
|
||||
|
||||
public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public SessionTranscriptMismatchException(@NonNull String);
|
||||
ctor public SessionTranscriptMismatchException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public class UnknownAuthenticationKeyException extends android.security.identity.IdentityCredentialException {
|
||||
ctor public UnknownAuthenticationKeyException(@NonNull String);
|
||||
ctor public UnknownAuthenticationKeyException(@NonNull String, @NonNull Throwable);
|
||||
}
|
||||
|
||||
public abstract class WritableIdentityCredential {
|
||||
ctor public WritableIdentityCredential();
|
||||
method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[]);
|
||||
method @NonNull public abstract byte[] personalize(@NonNull android.security.identity.PersonalizationData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.security.keystore {
|
||||
|
||||
public class KeyExpiredException extends java.security.InvalidKeyException {
|
||||
|
@ -32,6 +32,7 @@ import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.security.identity.IdentityCredential;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@ -401,6 +402,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
|
||||
super(mac);
|
||||
}
|
||||
|
||||
public CryptoObject(@NonNull IdentityCredential credential) {
|
||||
super(credential);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Signature} object.
|
||||
* @return {@link Signature} object or null if this doesn't contain one.
|
||||
@ -424,6 +429,14 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
|
||||
public Mac getMac() {
|
||||
return super.getMac();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link IdentityCredential} object.
|
||||
* @return {@link IdentityCredential} object or null if this doesn't contain one.
|
||||
*/
|
||||
public @Nullable IdentityCredential getIdentityCredential() {
|
||||
return super.getIdentityCredential();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@
|
||||
package android.hardware.biometrics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.security.identity.IdentityCredential;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
|
||||
import java.security.Signature;
|
||||
@ -26,7 +27,8 @@ import javax.crypto.Mac;
|
||||
|
||||
/**
|
||||
* A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
|
||||
* Currently the framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
|
||||
* Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and
|
||||
* {@link IdentityCredential} objects.
|
||||
* @hide
|
||||
*/
|
||||
public class CryptoObject {
|
||||
@ -44,6 +46,10 @@ public class CryptoObject {
|
||||
mCrypto = mac;
|
||||
}
|
||||
|
||||
public CryptoObject(@NonNull IdentityCredential credential) {
|
||||
mCrypto = credential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Signature} object.
|
||||
* @return {@link Signature} object or null if this doesn't contain one.
|
||||
@ -68,12 +74,24 @@ public class CryptoObject {
|
||||
return mCrypto instanceof Mac ? (Mac) mCrypto : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link IdentityCredential} object.
|
||||
* @return {@link IdentityCredential} object or null if this doesn't contain one.
|
||||
*/
|
||||
public IdentityCredential getIdentityCredential() {
|
||||
return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* @return the opId associated with this object or 0 if none
|
||||
*/
|
||||
public final long getOpId() {
|
||||
return mCrypto != null
|
||||
? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0;
|
||||
if (mCrypto == null) {
|
||||
return 0;
|
||||
} else if (mCrypto instanceof IdentityCredential) {
|
||||
return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
|
||||
}
|
||||
return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
|
||||
}
|
||||
};
|
||||
|
@ -44,6 +44,7 @@ import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.security.identity.IdentityCredential;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.security.Signature;
|
||||
@ -125,6 +126,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
||||
super(mac);
|
||||
}
|
||||
|
||||
public CryptoObject(@NonNull IdentityCredential credential) {
|
||||
super(credential);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Signature} object.
|
||||
* @return {@link Signature} object or null if this doesn't contain one.
|
||||
@ -148,6 +153,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
||||
public Mac getMac() {
|
||||
return super.getMac();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link IdentityCredential} object.
|
||||
* @return {@link IdentityCredential} object or null if this doesn't contain one.
|
||||
*/
|
||||
public @Nullable IdentityCredential getIdentityCredential() {
|
||||
return super.getIdentityCredential();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
0
identity/MODULE_LICENSE_APACHE2
Normal file
0
identity/MODULE_LICENSE_APACHE2
Normal file
190
identity/NOTICE
Normal file
190
identity/NOTICE
Normal file
@ -0,0 +1,190 @@
|
||||
|
||||
Copyright (c) 2009, 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
3
identity/OWNERS
Normal file
3
identity/OWNERS
Normal file
@ -0,0 +1,3 @@
|
||||
swillden@google.com
|
||||
zeuthen@google.com
|
||||
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2020 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* A class used to specify access controls.
|
||||
*/
|
||||
public class AccessControlProfile {
|
||||
private AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0);
|
||||
private X509Certificate mReaderCertificate = null;
|
||||
private boolean mUserAuthenticationRequired = true;
|
||||
private long mUserAuthenticationTimeout = 0;
|
||||
|
||||
private AccessControlProfile() {
|
||||
}
|
||||
|
||||
AccessControlProfileId getAccessControlProfileId() {
|
||||
return mAccessControlProfileId;
|
||||
}
|
||||
|
||||
long getUserAuthenticationTimeout() {
|
||||
return mUserAuthenticationTimeout;
|
||||
}
|
||||
|
||||
boolean isUserAuthenticationRequired() {
|
||||
return mUserAuthenticationRequired;
|
||||
}
|
||||
|
||||
X509Certificate getReaderCertificate() {
|
||||
return mReaderCertificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link AccessControlProfile}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private AccessControlProfile mProfile;
|
||||
|
||||
/**
|
||||
* Each access control profile has numeric identifier that must be unique within the
|
||||
* context of a Credential and may be used to reference the profile.
|
||||
*
|
||||
* <p>By default, the resulting {@link AccessControlProfile} will require user
|
||||
* authentication with a timeout of zero, thus requiring the holder to authenticate for
|
||||
* every presentation where data elements using this access control profile is used.</p>
|
||||
*
|
||||
* @param accessControlProfileId the access control profile identifier.
|
||||
*/
|
||||
public Builder(@NonNull AccessControlProfileId accessControlProfileId) {
|
||||
mProfile = new AccessControlProfile();
|
||||
mProfile.mAccessControlProfileId = accessControlProfileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether user authentication is required.
|
||||
*
|
||||
* <p>This should be used sparingly since disabling user authentication on just a single
|
||||
* data element can easily create a
|
||||
* <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device
|
||||
* on which the credential is stored is compromised.</p>
|
||||
*
|
||||
* @param userAuthenticationRequired Set to true if user authentication is required,
|
||||
* false otherwise.
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) {
|
||||
mProfile.mUserAuthenticationRequired = userAuthenticationRequired;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication timeout to use.
|
||||
*
|
||||
* <p>The authentication timeout specifies the amount of time, in milliseconds, for which a
|
||||
* user authentication is valid, if user authentication is required (see
|
||||
* {@link #setUserAuthenticationRequired(boolean)}).</p>
|
||||
*
|
||||
* <p>If the timeout is zero, then authentication is always required for each reader
|
||||
* session.</p>
|
||||
*
|
||||
* @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds.
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) {
|
||||
mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reader certificate to use when checking access control.
|
||||
*
|
||||
* <p>If set, this is checked against the certificate chain presented by
|
||||
* reader. The access check is fulfilled only if one of the certificates
|
||||
* in the chain, matches the certificate set by this method.</p>
|
||||
*
|
||||
* @param readerCertificate the certificate to use for the access control check.
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) {
|
||||
mProfile.mReaderCertificate = readerCertificate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AccessControlProfile} from the data supplied to the builder.
|
||||
*
|
||||
* @return The created {@link AccessControlProfile} object.
|
||||
*/
|
||||
public @NonNull AccessControlProfile build() {
|
||||
return mProfile;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 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.identity;
|
||||
|
||||
/**
|
||||
* A class used to wrap an access control profile identifiers.
|
||||
*/
|
||||
public class AccessControlProfileId {
|
||||
private int mId = 0;
|
||||
|
||||
/**
|
||||
* Constructs a new object holding a numerical identifier.
|
||||
*
|
||||
* @param id the identifier.
|
||||
*/
|
||||
public AccessControlProfileId(int id) {
|
||||
this.mId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the numerical identifier wrapped by this object.
|
||||
*
|
||||
* @return the identifier.
|
||||
*/
|
||||
public int getId() {
|
||||
return this.mId;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if trying to create a credential which already exists.
|
||||
*/
|
||||
public class AlreadyPersonalizedException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link AlreadyPersonalizedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public AlreadyPersonalizedException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link AlreadyPersonalizedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if trying to use a cipher suite which isn't supported.
|
||||
*/
|
||||
public class CipherSuiteNotSupportedException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link CipherSuiteNotSupportedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public CipherSuiteNotSupportedException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link CipherSuiteNotSupportedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
class CredstoreIdentityCredential extends IdentityCredential {
|
||||
|
||||
private static final String TAG = "CredstoreIdentityCredential";
|
||||
private String mCredentialName;
|
||||
private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
|
||||
private Context mContext;
|
||||
private ICredential mBinder;
|
||||
|
||||
CredstoreIdentityCredential(Context context, String credentialName,
|
||||
@IdentityCredentialStore.Ciphersuite int cipherSuite,
|
||||
ICredential binder) {
|
||||
mContext = context;
|
||||
mCredentialName = credentialName;
|
||||
mCipherSuite = cipherSuite;
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
private KeyPair mEphemeralKeyPair = null;
|
||||
private SecretKey mSecretKey = null;
|
||||
private SecretKey mReaderSecretKey = null;
|
||||
private int mEphemeralCounter;
|
||||
private int mReadersExpectedEphemeralCounter;
|
||||
|
||||
private void ensureEphemeralKeyPair() {
|
||||
if (mEphemeralKeyPair != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// This PKCS#12 blob is generated in credstore, using BoringSSL.
|
||||
//
|
||||
// The main reason for this convoluted approach and not just sending the decomposed
|
||||
// key-pair is that this would require directly using (device-side) BouncyCastle which
|
||||
// is tricky due to various API hiding efforts. So instead we have credstore generate
|
||||
// this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
|
||||
// doesn't support not using encryption when building a PKCS#12 blob).
|
||||
//
|
||||
byte[] pkcs12 = mBinder.createEphemeralKeyPair();
|
||||
String alias = "ephemeralKey";
|
||||
char[] password = {};
|
||||
|
||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
|
||||
ks.load(bais, password);
|
||||
PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
|
||||
|
||||
Certificate cert = ks.getCertificate(alias);
|
||||
PublicKey pubKey = cert.getPublicKey();
|
||||
|
||||
mEphemeralKeyPair = new KeyPair(pubKey, privKey);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
} catch (KeyStoreException
|
||||
| CertificateException
|
||||
| UnrecoverableKeyException
|
||||
| NoSuchAlgorithmException
|
||||
| IOException e) {
|
||||
throw new RuntimeException("Unexpected exception ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull KeyPair createEphemeralKeyPair() {
|
||||
ensureEphemeralKeyPair();
|
||||
return mEphemeralKeyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
|
||||
throws InvalidKeyException {
|
||||
try {
|
||||
byte[] uncompressedForm =
|
||||
Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
|
||||
mBinder.setReaderEphemeralPublicKey(uncompressedForm);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
|
||||
ensureEphemeralKeyPair();
|
||||
|
||||
try {
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
|
||||
ka.init(mEphemeralKeyPair.getPrivate());
|
||||
ka.doPhase(readerEphemeralPublicKey, true);
|
||||
byte[] sharedSecret = ka.generateSecret();
|
||||
|
||||
byte[] salt = new byte[1];
|
||||
byte[] info = new byte[0];
|
||||
|
||||
salt[0] = 0x01;
|
||||
byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
|
||||
mSecretKey = new SecretKeySpec(derivedKey, "AES");
|
||||
|
||||
salt[0] = 0x00;
|
||||
derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
|
||||
mReaderSecretKey = new SecretKeySpec(derivedKey, "AES");
|
||||
|
||||
mEphemeralCounter = 0;
|
||||
mReadersExpectedEphemeralCounter = 0;
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Error performing key agreement", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext) {
|
||||
byte[] messageCiphertextAndAuthTag = null;
|
||||
try {
|
||||
ByteBuffer iv = ByteBuffer.allocate(12);
|
||||
iv.putInt(0, 0x00000000);
|
||||
iv.putInt(4, 0x00000001);
|
||||
iv.putInt(8, mEphemeralCounter);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
|
||||
cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, encryptionParameterSpec);
|
||||
messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext);
|
||||
} catch (BadPaddingException
|
||||
| IllegalBlockSizeException
|
||||
| NoSuchPaddingException
|
||||
| InvalidKeyException
|
||||
| NoSuchAlgorithmException
|
||||
| InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException("Error encrypting message", e);
|
||||
}
|
||||
mEphemeralCounter += 1;
|
||||
return messageCiphertextAndAuthTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
|
||||
throws MessageDecryptionException {
|
||||
ByteBuffer iv = ByteBuffer.allocate(12);
|
||||
iv.putInt(0, 0x00000000);
|
||||
iv.putInt(4, 0x00000000);
|
||||
iv.putInt(8, mReadersExpectedEphemeralCounter);
|
||||
byte[] plainText = null;
|
||||
try {
|
||||
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, mReaderSecretKey,
|
||||
new GCMParameterSpec(128, iv.array()));
|
||||
plainText = cipher.doFinal(messageCiphertext);
|
||||
} catch (BadPaddingException
|
||||
| IllegalBlockSizeException
|
||||
| InvalidAlgorithmParameterException
|
||||
| InvalidKeyException
|
||||
| NoSuchAlgorithmException
|
||||
| NoSuchPaddingException e) {
|
||||
throw new MessageDecryptionException("Error decrypting message", e);
|
||||
}
|
||||
mReadersExpectedEphemeralCounter += 1;
|
||||
return plainText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() {
|
||||
try {
|
||||
byte[] certsBlob = mBinder.getCredentialKeyCertificateChain();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
|
||||
|
||||
Collection<? extends Certificate> certs = null;
|
||||
try {
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
certs = factory.generateCertificates(bais);
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error decoding certificates", e);
|
||||
}
|
||||
|
||||
LinkedList<X509Certificate> x509Certs = new LinkedList<>();
|
||||
for (Certificate cert : certs) {
|
||||
x509Certs.add((X509Certificate) cert);
|
||||
}
|
||||
return x509Certs;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mAllowUsingExhaustedKeys = true;
|
||||
|
||||
@Override
|
||||
public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
|
||||
mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
|
||||
}
|
||||
|
||||
private boolean mOperationHandleSet = false;
|
||||
private long mOperationHandle = 0;
|
||||
|
||||
/**
|
||||
* Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
|
||||
* operation handle.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public long getCredstoreOperationHandle() {
|
||||
if (!mOperationHandleSet) {
|
||||
try {
|
||||
mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys);
|
||||
mOperationHandleSet = true;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
|
||||
// The NoAuthenticationKeyAvailableException will be thrown when
|
||||
// the caller proceeds to call getEntries().
|
||||
}
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
return mOperationHandle;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ResultData getEntries(
|
||||
@Nullable byte[] requestMessage,
|
||||
@NonNull Map<String, Collection<String>> entriesToRequest,
|
||||
@Nullable byte[] sessionTranscript,
|
||||
@Nullable byte[] readerSignature)
|
||||
throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
|
||||
InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
|
||||
InvalidRequestMessageException {
|
||||
|
||||
RequestNamespaceParcel[] rnsParcels = new RequestNamespaceParcel[entriesToRequest.size()];
|
||||
int n = 0;
|
||||
for (String namespaceName : entriesToRequest.keySet()) {
|
||||
Collection<String> entryNames = entriesToRequest.get(namespaceName);
|
||||
rnsParcels[n] = new RequestNamespaceParcel();
|
||||
rnsParcels[n].namespaceName = namespaceName;
|
||||
rnsParcels[n].entries = new RequestEntryParcel[entryNames.size()];
|
||||
int m = 0;
|
||||
for (String entryName : entryNames) {
|
||||
rnsParcels[n].entries[m] = new RequestEntryParcel();
|
||||
rnsParcels[n].entries[m].name = entryName;
|
||||
m++;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
GetEntriesResultParcel resultParcel = null;
|
||||
try {
|
||||
resultParcel = mBinder.getEntries(
|
||||
requestMessage != null ? requestMessage : new byte[0],
|
||||
rnsParcels,
|
||||
sessionTranscript != null ? sessionTranscript : new byte[0],
|
||||
readerSignature != null ? readerSignature : new byte[0],
|
||||
mAllowUsingExhaustedKeys);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) {
|
||||
throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e);
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_INVALID_READER_SIGNATURE) {
|
||||
throw new InvalidReaderSignatureException(e.getMessage(), e);
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
|
||||
throw new NoAuthenticationKeyAvailableException(e.getMessage(), e);
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_INVALID_ITEMS_REQUEST_MESSAGE) {
|
||||
throw new InvalidRequestMessageException(e.getMessage(), e);
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_SESSION_TRANSCRIPT_MISMATCH) {
|
||||
throw new SessionTranscriptMismatchException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] mac = resultParcel.mac;
|
||||
if (mac != null && mac.length == 0) {
|
||||
mac = null;
|
||||
}
|
||||
CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
|
||||
resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac);
|
||||
|
||||
for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
|
||||
for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {
|
||||
if (resultEntryParcel.status == ICredential.STATUS_OK) {
|
||||
resultDataBuilder.addEntry(resultNamespaceParcel.namespaceName,
|
||||
resultEntryParcel.name, resultEntryParcel.value);
|
||||
} else {
|
||||
resultDataBuilder.addErrorStatus(resultNamespaceParcel.namespaceName,
|
||||
resultEntryParcel.name,
|
||||
resultEntryParcel.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultDataBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
|
||||
try {
|
||||
mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
|
||||
try {
|
||||
AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification();
|
||||
LinkedList<X509Certificate> x509Certs = new LinkedList<>();
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
for (AuthKeyParcel authKeyParcel : authKeyParcels) {
|
||||
Collection<? extends Certificate> certs = null;
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(authKeyParcel.x509cert);
|
||||
certs = factory.generateCertificates(bais);
|
||||
if (certs.size() != 1) {
|
||||
throw new RuntimeException("Returned blob yields more than one X509 cert");
|
||||
}
|
||||
X509Certificate authKeyCert = (X509Certificate) certs.iterator().next();
|
||||
x509Certs.add(authKeyCert);
|
||||
}
|
||||
return x509Certs;
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error decoding authenticationKey", e);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeStaticAuthenticationData(X509Certificate authenticationKey,
|
||||
byte[] staticAuthData)
|
||||
throws UnknownAuthenticationKeyException {
|
||||
try {
|
||||
AuthKeyParcel authKeyParcel = new AuthKeyParcel();
|
||||
authKeyParcel.x509cert = authenticationKey.getEncoded();
|
||||
mBinder.storeStaticAuthenticationData(authKeyParcel, staticAuthData);
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new RuntimeException("Error encoding authenticationKey", e);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) {
|
||||
throw new UnknownAuthenticationKeyException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull int[] getAuthenticationDataUsageCount() {
|
||||
try {
|
||||
int[] usageCount = mBinder.getAuthenticationDataUsageCount();
|
||||
return usageCount;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.ServiceManager;
|
||||
|
||||
class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
|
||||
|
||||
private static final String TAG = "CredstoreIdentityCredentialStore";
|
||||
|
||||
private Context mContext = null;
|
||||
private ICredentialStore mStore = null;
|
||||
|
||||
private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
|
||||
mContext = context;
|
||||
mStore = store;
|
||||
}
|
||||
|
||||
static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
|
||||
int credentialStoreType) {
|
||||
ICredentialStoreFactory storeFactory =
|
||||
ICredentialStoreFactory.Stub.asInterface(
|
||||
ServiceManager.getService("android.security.identity"));
|
||||
|
||||
ICredentialStore credStore = null;
|
||||
try {
|
||||
credStore = storeFactory.getCredentialStore(credentialStoreType);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_GENERIC) {
|
||||
return null;
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
if (credStore == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CredstoreIdentityCredentialStore(context, credStore);
|
||||
}
|
||||
|
||||
private static CredstoreIdentityCredentialStore sInstanceDefault = null;
|
||||
private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null;
|
||||
|
||||
public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
|
||||
if (sInstanceDefault == null) {
|
||||
sInstanceDefault = getInstanceForType(context,
|
||||
ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT);
|
||||
}
|
||||
return sInstanceDefault;
|
||||
}
|
||||
|
||||
public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
|
||||
Context context) {
|
||||
if (sInstanceDirectAccess == null) {
|
||||
sInstanceDirectAccess = getInstanceForType(context,
|
||||
ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS);
|
||||
}
|
||||
return sInstanceDirectAccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String[] getSupportedDocTypes() {
|
||||
try {
|
||||
SecurityHardwareInfoParcel info;
|
||||
info = mStore.getSecurityHardwareInfo();
|
||||
return info.supportedDocTypes;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public @NonNull WritableIdentityCredential createCredential(
|
||||
@NonNull String credentialName,
|
||||
@NonNull String docType) throws AlreadyPersonalizedException,
|
||||
DocTypeNotSupportedException {
|
||||
try {
|
||||
IWritableCredential wc;
|
||||
wc = mStore.createCredential(credentialName, docType);
|
||||
return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) {
|
||||
throw new AlreadyPersonalizedException(e.getMessage(), e);
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) {
|
||||
throw new DocTypeNotSupportedException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public @Nullable IdentityCredential getCredentialByName(
|
||||
@NonNull String credentialName,
|
||||
@Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
|
||||
try {
|
||||
ICredential credstoreCredential;
|
||||
credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
|
||||
return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
|
||||
credstoreCredential);
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
|
||||
return null;
|
||||
} else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
|
||||
throw new CipherSuiteNotSupportedException(e.getMessage(), e);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) {
|
||||
ICredential credstoreCredential = null;
|
||||
try {
|
||||
try {
|
||||
credstoreCredential = mStore.getCredentialByName(credentialName,
|
||||
CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
byte[] proofOfDeletion = credstoreCredential.deleteCredential();
|
||||
return proofOfDeletion;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
162
identity/java/android/security/identity/CredstoreResultData.java
Normal file
162
identity/java/android/security/identity/CredstoreResultData.java
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2020 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An object that contains the result of retrieving data from a credential. This is used to return
|
||||
* data requested from a {@link IdentityCredential}.
|
||||
*/
|
||||
class CredstoreResultData extends ResultData {
|
||||
|
||||
byte[] mStaticAuthenticationData = null;
|
||||
byte[] mAuthenticatedData = null;
|
||||
byte[] mMessageAuthenticationCode = null;
|
||||
|
||||
private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
|
||||
|
||||
private static class EntryData {
|
||||
@Status
|
||||
int mStatus;
|
||||
byte[] mValue;
|
||||
|
||||
EntryData(byte[] value, @Status int status) {
|
||||
this.mValue = value;
|
||||
this.mStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
CredstoreResultData() {}
|
||||
|
||||
@Override
|
||||
public @NonNull byte[] getAuthenticatedData() {
|
||||
return mAuthenticatedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] getMessageAuthenticationCode() {
|
||||
return mMessageAuthenticationCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull byte[] getStaticAuthenticationData() {
|
||||
return mStaticAuthenticationData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Collection<String> getNamespaceNames() {
|
||||
return Collections.unmodifiableCollection(mData.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) {
|
||||
Map<String, EntryData> innerMap = mData.get(namespaceName);
|
||||
if (innerMap == null) {
|
||||
return null;
|
||||
}
|
||||
return Collections.unmodifiableCollection(innerMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
|
||||
Map<String, EntryData> innerMap = mData.get(namespaceName);
|
||||
if (innerMap == null) {
|
||||
return null;
|
||||
}
|
||||
LinkedList<String> result = new LinkedList<String>();
|
||||
for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) {
|
||||
if (entry.getValue().mStatus == STATUS_OK) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) {
|
||||
Map<String, EntryData> innerMap = mData.get(namespaceName);
|
||||
if (innerMap == null) {
|
||||
return null;
|
||||
}
|
||||
return innerMap.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Status
|
||||
public int getStatus(@NonNull String namespaceName, @NonNull String name) {
|
||||
EntryData value = getEntryData(namespaceName, name);
|
||||
if (value == null) {
|
||||
return STATUS_NOT_REQUESTED;
|
||||
}
|
||||
return value.mStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) {
|
||||
EntryData value = getEntryData(namespaceName, name);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.mValue;
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
private CredstoreResultData mResultData;
|
||||
|
||||
Builder(byte[] staticAuthenticationData,
|
||||
byte[] authenticatedData,
|
||||
byte[] messageAuthenticationCode) {
|
||||
this.mResultData = new CredstoreResultData();
|
||||
this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
|
||||
this.mResultData.mAuthenticatedData = authenticatedData;
|
||||
this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
|
||||
}
|
||||
|
||||
private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
|
||||
Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName);
|
||||
if (innerMap == null) {
|
||||
innerMap = new LinkedHashMap<>();
|
||||
mResultData.mData.put(namespaceName, innerMap);
|
||||
}
|
||||
return innerMap;
|
||||
}
|
||||
|
||||
Builder addEntry(String namespaceName, String name, byte[] value) {
|
||||
Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
|
||||
innerMap.put(name, new EntryData(value, STATUS_OK));
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder addErrorStatus(String namespaceName, String name, @Status int status) {
|
||||
Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
|
||||
innerMap.put(name, new EntryData(null, status));
|
||||
return this;
|
||||
}
|
||||
|
||||
CredstoreResultData build() {
|
||||
return mResultData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.security.GateKeeper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
|
||||
|
||||
private static final String TAG = "CredstoreWritableIdentityCredential";
|
||||
|
||||
private String mDocType;
|
||||
private String mCredentialName;
|
||||
private Context mContext;
|
||||
private IWritableCredential mBinder;
|
||||
|
||||
CredstoreWritableIdentityCredential(Context context,
|
||||
@NonNull String credentialName,
|
||||
@NonNull String docType,
|
||||
IWritableCredential binder) {
|
||||
mContext = context;
|
||||
mDocType = docType;
|
||||
mCredentialName = credentialName;
|
||||
mBinder = binder;
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) {
|
||||
try {
|
||||
byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
|
||||
|
||||
Collection<? extends Certificate> certs = null;
|
||||
try {
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
certs = factory.generateCertificates(bais);
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error decoding certificates", e);
|
||||
}
|
||||
|
||||
LinkedList<X509Certificate> x509Certs = new LinkedList<>();
|
||||
for (Certificate cert : certs) {
|
||||
x509Certs.add((X509Certificate) cert);
|
||||
}
|
||||
return x509Certs;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public byte[] personalize(@NonNull PersonalizationData personalizationData) {
|
||||
|
||||
Collection<AccessControlProfile> accessControlProfiles =
|
||||
personalizationData.getAccessControlProfiles();
|
||||
|
||||
AccessControlProfileParcel[] acpParcels =
|
||||
new AccessControlProfileParcel[accessControlProfiles.size()];
|
||||
boolean usingUserAuthentication = false;
|
||||
int n = 0;
|
||||
for (AccessControlProfile profile : accessControlProfiles) {
|
||||
acpParcels[n] = new AccessControlProfileParcel();
|
||||
acpParcels[n].id = profile.getAccessControlProfileId().getId();
|
||||
X509Certificate cert = profile.getReaderCertificate();
|
||||
if (cert != null) {
|
||||
try {
|
||||
acpParcels[n].readerCertificate = cert.getEncoded();
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error encoding reader certificate", e);
|
||||
}
|
||||
} else {
|
||||
acpParcels[n].readerCertificate = new byte[0];
|
||||
}
|
||||
acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired();
|
||||
acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout();
|
||||
if (profile.isUserAuthenticationRequired()) {
|
||||
usingUserAuthentication = true;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
Collection<String> namespaceNames = personalizationData.getNamespaceNames();
|
||||
|
||||
EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaceNames.size()];
|
||||
n = 0;
|
||||
for (String namespaceName : namespaceNames) {
|
||||
PersonalizationData.NamespaceData nsd =
|
||||
personalizationData.getNamespaceData(namespaceName);
|
||||
|
||||
ensParcels[n] = new EntryNamespaceParcel();
|
||||
ensParcels[n].namespaceName = namespaceName;
|
||||
|
||||
Collection<String> entryNames = nsd.getEntryNames();
|
||||
EntryParcel[] eParcels = new EntryParcel[entryNames.size()];
|
||||
int m = 0;
|
||||
for (String entryName : entryNames) {
|
||||
eParcels[m] = new EntryParcel();
|
||||
eParcels[m].name = entryName;
|
||||
eParcels[m].value = nsd.getEntryValue(entryName);
|
||||
Collection<AccessControlProfileId> acpIds =
|
||||
nsd.getAccessControlProfileIds(entryName);
|
||||
eParcels[m].accessControlProfileIds = new int[acpIds.size()];
|
||||
int o = 0;
|
||||
for (AccessControlProfileId acpId : acpIds) {
|
||||
eParcels[m].accessControlProfileIds[o++] = acpId.getId();
|
||||
}
|
||||
m++;
|
||||
}
|
||||
ensParcels[n].entries = eParcels;
|
||||
n++;
|
||||
}
|
||||
|
||||
// Note: The value 0 is used to convey that no user-authentication is needed for this
|
||||
// credential. This is to allow creating credentials w/o user authentication on devices
|
||||
// where Secure lock screen is not enabled.
|
||||
long secureUserId = 0;
|
||||
if (usingUserAuthentication) {
|
||||
secureUserId = getRootSid();
|
||||
}
|
||||
try {
|
||||
byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels,
|
||||
secureUserId);
|
||||
return personalizationReceipt;
|
||||
} catch (android.os.RemoteException e) {
|
||||
throw new RuntimeException("Unexpected RemoteException ", e);
|
||||
} catch (android.os.ServiceSpecificException e) {
|
||||
throw new RuntimeException("Unexpected ServiceSpecificException with code "
|
||||
+ e.errorCode, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long getRootSid() {
|
||||
long rootSid = GateKeeper.getSecureUserId();
|
||||
if (rootSid == 0) {
|
||||
throw new IllegalStateException("Secure lock screen must be enabled"
|
||||
+ " to create credentials requiring user authentication");
|
||||
}
|
||||
return rootSid;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if trying to create a credential with an unsupported document type.
|
||||
*/
|
||||
public class DocTypeNotSupportedException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link DocTypeNotSupportedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public DocTypeNotSupportedException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link DocTypeNotSupportedException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if the ephemeral public key was not found in the session transcript
|
||||
* passed to {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
|
||||
*/
|
||||
public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public EphemeralPublicKeyNotFoundException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
309
identity/java/android/security/identity/IdentityCredential.java
Normal file
309
identity/java/android/security/identity/IdentityCredential.java
Normal file
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class used to read data from a previously provisioned credential.
|
||||
*
|
||||
* Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a
|
||||
* {@link IdentityCredential} instance.
|
||||
*/
|
||||
public abstract class IdentityCredential {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected IdentityCredential() {}
|
||||
|
||||
/**
|
||||
* Create an ephemeral key pair to use to establish a secure channel with a reader.
|
||||
*
|
||||
* <p>Most applications will use only the public key, and only to send it to the reader,
|
||||
* allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
|
||||
* and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
|
||||
* applications that wish to use a cipher suite that is not supported by
|
||||
* {@link IdentityCredentialStore}.
|
||||
*
|
||||
* @return ephemeral key pair to use to establish a secure channel with a reader.
|
||||
*/
|
||||
public @NonNull abstract KeyPair createEphemeralKeyPair();
|
||||
|
||||
/**
|
||||
* Set the ephemeral public key provided by the reader. This must be called before
|
||||
* {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
|
||||
*
|
||||
* @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
|
||||
* establish a secure session.
|
||||
* @throws InvalidKeyException if the given key is invalid.
|
||||
*/
|
||||
public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
|
||||
throws InvalidKeyException;
|
||||
|
||||
/**
|
||||
* Encrypt a message for transmission to the reader.
|
||||
*
|
||||
* @param messagePlaintext unencrypted message to encrypt.
|
||||
* @return encrypted message.
|
||||
*/
|
||||
public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext);
|
||||
|
||||
/**
|
||||
* Decrypt a message received from the reader.
|
||||
*
|
||||
* @param messageCiphertext encrypted message to decrypt.
|
||||
* @return decrypted message.
|
||||
* @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
|
||||
*/
|
||||
public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
|
||||
throws MessageDecryptionException;
|
||||
|
||||
/**
|
||||
* Gets the X.509 certificate chain for the CredentialKey which identifies this
|
||||
* credential to the issuing authority. This is the same certificate chain that
|
||||
* was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])}
|
||||
* when the credential was first created and its Android Keystore extension will
|
||||
* contain the <code>challenge</code> data set at that time. See the documentation
|
||||
* for that method for important information about this certificate chain.
|
||||
*
|
||||
* @return the certificate chain for this credential's CredentialKey.
|
||||
*/
|
||||
public @NonNull abstract Collection<X509Certificate> getCredentialKeyCertificateChain();
|
||||
|
||||
/**
|
||||
* Sets whether to allow using an authentication key which use count has been exceeded if no
|
||||
* other key is available. This must be called prior to calling
|
||||
* {@link #getEntries(byte[], Map, byte[], byte[])} or using a
|
||||
* {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
|
||||
* object.
|
||||
*
|
||||
* By default this is set to true.
|
||||
*
|
||||
* @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
|
||||
* has been exceeded if no other key is available.
|
||||
*/
|
||||
public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
|
||||
|
||||
/**
|
||||
* Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
|
||||
* operation handle.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract long getCredstoreOperationHandle();
|
||||
|
||||
/**
|
||||
* Retrieve data entries and associated data from this {@code IdentityCredential}.
|
||||
*
|
||||
* <p>If an access control check fails for one of the requested entries or if the entry
|
||||
* doesn't exist, the entry is simply not returned. The application can detect this
|
||||
* by using the {@link ResultData#getStatus(String, String)} method on each of the requested
|
||||
* entries.
|
||||
*
|
||||
* <p>It is the responsibility of the calling application to know if authentication is needed
|
||||
* and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user
|
||||
* authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
|
||||
* references this object. If needed, this must be done before calling
|
||||
* {@link #getEntries(byte[], Map, byte[], byte[])}.
|
||||
*
|
||||
* <p>If this method returns successfully (i.e. without throwing an exception), it must not be
|
||||
* called again on this instance.
|
||||
*
|
||||
* <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
|
||||
* from the verifier. The content can be defined in the way appropriate for the credential, byt
|
||||
* there are three requirements that must be met to work with this API:
|
||||
* <ul>
|
||||
* <li>The content must be a CBOR-encoded structure.</li>
|
||||
* <li>The CBOR structure must be a map.</li>
|
||||
* <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
|
||||
* the example below.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Here's an example of CBOR which conforms to this requirement:
|
||||
* <pre>
|
||||
* ItemsRequest = {
|
||||
* ? "docType" : DocType,
|
||||
* "nameSpaces" : NameSpaces,
|
||||
* ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
|
||||
* }
|
||||
*
|
||||
* NameSpaces = {
|
||||
* + NameSpace => DataElements ; Requested data elements for each NameSpace
|
||||
* }
|
||||
*
|
||||
* NameSpace = tstr
|
||||
*
|
||||
* DataElements = {
|
||||
* + DataElement => IntentToRetain
|
||||
* }
|
||||
*
|
||||
* DataElement = tstr
|
||||
* IntentToRetain = bool
|
||||
* </pre>
|
||||
*
|
||||
* <p>If the {@code sessionTranscript} parameter is not {@code null}, it must contain CBOR
|
||||
* data conforming to the following CDDL schema:
|
||||
*
|
||||
* <pre>
|
||||
* SessionTranscript = [
|
||||
* DeviceEngagementBytes,
|
||||
* EReaderKeyBytes
|
||||
* ]
|
||||
*
|
||||
* DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
|
||||
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
|
||||
* </pre>
|
||||
*
|
||||
* <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part
|
||||
* of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear
|
||||
* somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present
|
||||
* in uncompressed form.
|
||||
*
|
||||
* <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1
|
||||
* structure as defined in RFC 8152. For the payload nil shall be used and the
|
||||
* detached payload is the ReaderAuthentication CBOR described below.
|
||||
* <pre>
|
||||
* ReaderAuthentication = [
|
||||
* "ReaderAuthentication",
|
||||
* SessionTranscript,
|
||||
* ItemsRequestBytes
|
||||
* ]
|
||||
*
|
||||
* ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest
|
||||
* </pre>
|
||||
*
|
||||
* <p>The public key corresponding to the key used to made signature, can be
|
||||
* found in the {@code x5chain} unprotected header element of the COSE_Sign1
|
||||
* structure (as as described in 'draft-ietf-cose-x509-04'). There will be at
|
||||
* least one certificate in said element and there may be more (and if so,
|
||||
* each certificate must be signed by its successor).
|
||||
*
|
||||
* <p>Data elements protected by reader authentication is returned if, and only if, they are
|
||||
* mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
|
||||
* certificate in {@code readerCertificateChain}, and the data element is configured
|
||||
* with an {@link AccessControlProfile} with a {@link X509Certificate} in
|
||||
* {@code readerCertificateChain}.
|
||||
*
|
||||
* <p>Note that only items referenced in {@code entriesToRequest} are returned - the
|
||||
* {@code requestMessage} parameter is only used to for enforcing reader authentication.
|
||||
*
|
||||
* @param requestMessage If not {@code null}, must contain CBOR data conforming to
|
||||
* the schema mentioned above.
|
||||
* @param entriesToRequest The entries to request, organized as a map of namespace
|
||||
* names with each value being a collection of data elements
|
||||
* in the given namespace.
|
||||
* @param readerSignature COSE_Sign1 structure as described above or {@code null}
|
||||
* if reader authentication is not being used.
|
||||
* @return A {@link ResultData} object containing entry data organized by namespace and a
|
||||
* cryptographically authenticated representation of the same data.
|
||||
* @throws SessionTranscriptMismatchException Thrown when trying use multiple different
|
||||
* session transcripts in the same presentation
|
||||
* session.
|
||||
* @throws NoAuthenticationKeyAvailableException if authentication keys were never
|
||||
* provisioned, the method
|
||||
* {@link #setAvailableAuthenticationKeys(int, int)}
|
||||
* was called with {@code keyCount} set to 0,
|
||||
* the method
|
||||
* {@link #setAllowUsingExhaustedKeys(boolean)}
|
||||
* was called with {@code false} and all
|
||||
* available authentication keys have been
|
||||
* exhausted.
|
||||
* @throws InvalidReaderSignatureException if the reader signature is invalid, or it
|
||||
* doesn't contain a certificate chain, or if
|
||||
* the signature failed to validate.
|
||||
* @throws InvalidRequestMessageException if the requestMessage is malformed.
|
||||
* @throws EphemeralPublicKeyNotFoundException if the ephemeral public key was not found in
|
||||
* the session transcript.
|
||||
*/
|
||||
public abstract @NonNull ResultData getEntries(
|
||||
@Nullable byte[] requestMessage,
|
||||
@NonNull Map<String, Collection<String>> entriesToRequest,
|
||||
@Nullable byte[] sessionTranscript,
|
||||
@Nullable byte[] readerSignature)
|
||||
throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
|
||||
InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
|
||||
InvalidRequestMessageException;
|
||||
|
||||
/**
|
||||
* Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
|
||||
* and the number of times each should be used.
|
||||
*
|
||||
* <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
|
||||
* time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s
|
||||
* for which this method has not been called behave as though it had been called wit
|
||||
* {@code keyCount} 0 and {@code maxUsesPerKey} 1.
|
||||
*
|
||||
* @param keyCount The number of active, certified dynamic authentication keys the
|
||||
* {@code IdentityCredential} will try to keep available. This value
|
||||
* must be non-negative.
|
||||
* @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
|
||||
* eligible for replacement. This value must be greater than zero.
|
||||
*/
|
||||
public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
|
||||
|
||||
/**
|
||||
* Gets a collection of dynamic authentication keys that need certification.
|
||||
*
|
||||
* <p>When there aren't enough certified dynamic authentication keys, either because the key
|
||||
* count has been increased or because one or more keys have reached their usage count, this
|
||||
* method will generate replacement keys and certificates and return them for issuer
|
||||
* certification. The issuer certificates and associated static authentication data must then
|
||||
* be provided back to the {@code IdentityCredential} using
|
||||
* {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.
|
||||
*
|
||||
* <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
|
||||
* can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
|
||||
*
|
||||
* @return A collection of X.509 certificates for dynamic authentication keys that need issuer
|
||||
* certification.
|
||||
*/
|
||||
public @NonNull abstract Collection<X509Certificate> getAuthKeysNeedingCertification();
|
||||
|
||||
/**
|
||||
* Store authentication data associated with a dynamic authentication key.
|
||||
*
|
||||
* This should only be called for an authenticated key returned by
|
||||
* {@link #getAuthKeysNeedingCertification()}.
|
||||
*
|
||||
* @param authenticationKey The dynamic authentication key for which certification and
|
||||
* associated static
|
||||
* authentication data is being provided.
|
||||
* @param staticAuthData Static authentication data provided by the issuer that validates
|
||||
* the authenticity
|
||||
* and integrity of the credential data fields.
|
||||
* @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
|
||||
*/
|
||||
public abstract void storeStaticAuthenticationData(
|
||||
@NonNull X509Certificate authenticationKey,
|
||||
@NonNull byte[] staticAuthData)
|
||||
throws UnknownAuthenticationKeyException;
|
||||
|
||||
/**
|
||||
* Get the number of times the dynamic authentication keys have been used.
|
||||
*
|
||||
* @return int array of dynamic authentication key usage counts.
|
||||
*/
|
||||
public @NonNull abstract int[] getAuthenticationDataUsageCount();
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Base class for all Identity Credential exceptions.
|
||||
*/
|
||||
public class IdentityCredentialException extends Exception {
|
||||
/**
|
||||
* Constructs a new {@link IdentityCredentialException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public IdentityCredentialException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link IdentityCredentialException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* An interface to a secure store for user identity documents.
|
||||
*
|
||||
* <p>This interface is deliberately fairly general and abstract. To the extent possible,
|
||||
* specification of the message formats and semantics of communication with credential
|
||||
* verification devices and issuing authorities (IAs) is out of scope. It provides the
|
||||
* interface with secure storage but a credential-specific Android application will be
|
||||
* required to implement the presentation and verification protocols and processes
|
||||
* appropriate for the specific credential type.
|
||||
*
|
||||
* <p>Multiple credentials can be created. Each credential comprises:</p>
|
||||
* <ul>
|
||||
* <li>A document type, which is a string.</li>
|
||||
*
|
||||
* <li>A set of namespaces, which serve to disambiguate value names. It is recommended
|
||||
* that namespaces be structured as reverse domain names so that IANA effectively serves
|
||||
* as the namespace registrar.</li>
|
||||
*
|
||||
* <li>For each namespace, a set of name/value pairs, each with an associated set of
|
||||
* access control profile IDs. Names are strings and values are typed and can be any
|
||||
* value supported by <a href="http://cbor.io/">CBOR</a>.</li>
|
||||
*
|
||||
* <li>A set of access control profiles, each with a profile ID and a specification
|
||||
* of the conditions which satisfy the profile's requirements.</li>
|
||||
*
|
||||
* <li>An asymmetric key pair which is used to authenticate the credential to the Issuing
|
||||
* Authority, called the <em>CredentialKey</em>.</li>
|
||||
*
|
||||
* <li>A set of zero or more named reader authentication public keys, which are used to
|
||||
* authenticate an authorized reader to the credential.</li>
|
||||
*
|
||||
* <li>A set of named signing keys, which are used to sign collections of values and session
|
||||
* transcripts.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Implementing support for user identity documents in secure storage requires dedicated
|
||||
* hardware-backed support and may not always be available.
|
||||
*
|
||||
* <p>Two different credential stores exist - the <em>default</em> store and the
|
||||
* <em>direct access</em> store. Most often credentials will be accessed through the default
|
||||
* store but that requires that the Android device be powered up and fully functional.
|
||||
* It is desirable to allow identity credential usage when the Android device's battery is too
|
||||
* low to boot the Android operating system, so direct access to the secure hardware via NFC
|
||||
* may allow data retrieval, if the secure hardware chooses to implement it.
|
||||
*
|
||||
* <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader
|
||||
* authentication to protect data elements. The reason for this is user authentication or user
|
||||
* approval of data release is not possible when the device is off.
|
||||
*/
|
||||
public abstract class IdentityCredentialStore {
|
||||
IdentityCredentialStore() {}
|
||||
|
||||
/**
|
||||
* Specifies that the cipher suite that will be used to secure communications between the reader
|
||||
* is:
|
||||
*
|
||||
* <ul>
|
||||
* <li>ECDHE with HKDF-SHA-256 for key agreement.</li>
|
||||
* <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
|
||||
* for every message).</li>
|
||||
* <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
|
||||
* man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential}
|
||||
* for details on reader and prover signing keys.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* At present this is the only supported cipher suite.
|
||||
*/
|
||||
public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1;
|
||||
|
||||
/**
|
||||
* Gets the default {@link IdentityCredentialStore}.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @return the {@link IdentityCredentialStore} or {@code null} if the device doesn't
|
||||
* have hardware-backed support for secure storage of user identity documents.
|
||||
*/
|
||||
public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
|
||||
return CredstoreIdentityCredentialStore.getInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link IdentityCredentialStore} for direct access.
|
||||
*
|
||||
* <p>Direct access requires specialized NFC hardware and may not be supported on all
|
||||
* devices even if default store is available. Credentials provisioned to the direct
|
||||
* access store should <strong>always</strong> use reader authentication to protect
|
||||
* data elements.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @return the {@link IdentityCredentialStore} or {@code null} if direct access is not
|
||||
* supported on this device.
|
||||
*/
|
||||
public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
|
||||
Context context) {
|
||||
return CredstoreIdentityCredentialStore.getDirectAccessInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of supported document types.
|
||||
*
|
||||
* <p>Only the direct-access store may restrict the kind of document types that can be used for
|
||||
* credentials. The default store always supports any document type.
|
||||
*
|
||||
* @return The supported document types or the empty array if any document type is supported.
|
||||
*/
|
||||
public abstract @NonNull String[] getSupportedDocTypes();
|
||||
|
||||
/**
|
||||
* Creates a new credential.
|
||||
*
|
||||
* @param credentialName The name used to identify the credential.
|
||||
* @param docType The document type for the credential.
|
||||
* @return A @{link WritableIdentityCredential} that can be used to create a new credential.
|
||||
* @throws AlreadyPersonalizedException if a credential with the given name already exists.
|
||||
* @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
|
||||
*/
|
||||
public abstract @NonNull WritableIdentityCredential createCredential(
|
||||
@NonNull String credentialName, @NonNull String docType)
|
||||
throws AlreadyPersonalizedException, DocTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Retrieve a named credential.
|
||||
*
|
||||
* @param credentialName the name of the credential to retrieve.
|
||||
* @param cipherSuite the cipher suite to use for communicating with the verifier.
|
||||
* @return The named credential, or null if not found.
|
||||
*/
|
||||
public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName,
|
||||
@Ciphersuite int cipherSuite)
|
||||
throws CipherSuiteNotSupportedException;
|
||||
|
||||
/**
|
||||
* Delete a named credential.
|
||||
*
|
||||
* <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
|
||||
* with payload set to {@code ProofOfDeletion} as defined below:
|
||||
*
|
||||
* <pre>
|
||||
* ProofOfDeletion = [
|
||||
* "ProofOfDeletion", ; tstr
|
||||
* tstr, ; DocType
|
||||
* bool ; true if this is a test credential, should
|
||||
* ; always be false.
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* @param credentialName the name of the credential to delete.
|
||||
* @return {@code null} if the credential was not found, the COSE_Sign1 data structure above
|
||||
* if the credential was found and deleted.
|
||||
*/
|
||||
public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
|
||||
|
||||
/** @hide */
|
||||
@IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Ciphersuite {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the
|
||||
* signature failed to validate.
|
||||
*/
|
||||
public class InvalidReaderSignatureException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link InvalidReaderSignatureException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public InvalidReaderSignatureException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@link InvalidReaderSignatureException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public InvalidReaderSignatureException(@NonNull String message,
|
||||
@NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if message with the request doesn't satisfy the requirements documented in
|
||||
* {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
|
||||
*/
|
||||
public class InvalidRequestMessageException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link InvalidRequestMessageException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public InvalidRequestMessageException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@link InvalidRequestMessageException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public InvalidRequestMessageException(@NonNull String message,
|
||||
@NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown when failing to decrypt a message from the reader device.
|
||||
*/
|
||||
public class MessageDecryptionException extends IdentityCredentialException {
|
||||
|
||||
/**
|
||||
* Constructs a new {@link MessageDecryptionException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public MessageDecryptionException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link MessageDecryptionException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if no dynamic authentication keys are available.
|
||||
*/
|
||||
public class NoAuthenticationKeyAvailableException extends IdentityCredentialException {
|
||||
|
||||
/**
|
||||
* Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public NoAuthenticationKeyAvailableException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public NoAuthenticationKeyAvailableException(@NonNull String message,
|
||||
@NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
157
identity/java/android/security/identity/PersonalizationData.java
Normal file
157
identity/java/android/security/identity/PersonalizationData.java
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* An object that holds personalization data.
|
||||
*
|
||||
* This data includes access control profiles and a set of data entries and values, grouped by
|
||||
* namespace.
|
||||
*
|
||||
* This is used to provision data into a {@link WritableIdentityCredential}.
|
||||
*
|
||||
* @see WritableIdentityCredential#personalize
|
||||
*/
|
||||
public class PersonalizationData {
|
||||
|
||||
private PersonalizationData() {
|
||||
}
|
||||
|
||||
private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>();
|
||||
|
||||
private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
|
||||
|
||||
Collection<AccessControlProfile> getAccessControlProfiles() {
|
||||
return Collections.unmodifiableCollection(mProfiles);
|
||||
}
|
||||
|
||||
Collection<String> getNamespaceNames() {
|
||||
return Collections.unmodifiableCollection(mNamespaces.keySet());
|
||||
}
|
||||
|
||||
NamespaceData getNamespaceData(String namespace) {
|
||||
return mNamespaces.get(namespace);
|
||||
}
|
||||
|
||||
static class NamespaceData {
|
||||
|
||||
private String mNamespace;
|
||||
private LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>();
|
||||
|
||||
private NamespaceData(String namespace) {
|
||||
this.mNamespace = namespace;
|
||||
}
|
||||
|
||||
String getNamespaceName() {
|
||||
return mNamespace;
|
||||
}
|
||||
|
||||
Collection<String> getEntryNames() {
|
||||
return Collections.unmodifiableCollection(mEntries.keySet());
|
||||
}
|
||||
|
||||
Collection<AccessControlProfileId> getAccessControlProfileIds(String name) {
|
||||
EntryData value = mEntries.get(name);
|
||||
if (value != null) {
|
||||
return value.mAccessControlProfileIds;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] getEntryValue(String name) {
|
||||
EntryData value = mEntries.get(name);
|
||||
if (value != null) {
|
||||
return value.mValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EntryData {
|
||||
byte[] mValue;
|
||||
Collection<AccessControlProfileId> mAccessControlProfileIds;
|
||||
|
||||
EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) {
|
||||
this.mValue = value;
|
||||
this.mAccessControlProfileIds = accessControlProfileIds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link PersonalizationData}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private PersonalizationData mData;
|
||||
|
||||
/**
|
||||
* Creates a new builder for a given namespace.
|
||||
*/
|
||||
public Builder() {
|
||||
this.mData = new PersonalizationData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new entry to the builder.
|
||||
*
|
||||
* @param namespace The namespace to use, e.g. {@code org.iso.18013-5.2019}.
|
||||
* @param name The name of the entry, e.g. {@code height}.
|
||||
* @param accessControlProfileIds A set of access control profiles to use.
|
||||
* @param value The value to add, in CBOR encoding.
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name,
|
||||
@NonNull Collection<AccessControlProfileId> accessControlProfileIds,
|
||||
@NonNull byte[] value) {
|
||||
NamespaceData namespaceData = mData.mNamespaces.get(namespace);
|
||||
if (namespaceData == null) {
|
||||
namespaceData = new NamespaceData(namespace);
|
||||
mData.mNamespaces.put(namespace, namespaceData);
|
||||
}
|
||||
// TODO: validate/verify that value is proper CBOR.
|
||||
namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new access control profile to the builder.
|
||||
*
|
||||
* @param profile The access control profile.
|
||||
* @return The builder.
|
||||
*/
|
||||
public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) {
|
||||
mData.mProfiles.add(profile);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersonalizationData} with all the entries added to the builder.
|
||||
*
|
||||
* @return A new {@link PersonalizationData} instance.
|
||||
*/
|
||||
public @NonNull PersonalizationData build() {
|
||||
return mData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
224
identity/java/android/security/identity/ResultData.java
Normal file
224
identity/java/android/security/identity/ResultData.java
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2020 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.identity;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An object that contains the result of retrieving data from a credential. This is used to return
|
||||
* data requested from a {@link IdentityCredential}.
|
||||
*/
|
||||
public abstract class ResultData {
|
||||
|
||||
/** Value was successfully retrieved. */
|
||||
public static final int STATUS_OK = 0;
|
||||
|
||||
/** Requested entry does not exist. */
|
||||
public static final int STATUS_NO_SUCH_ENTRY = 1;
|
||||
|
||||
/** Requested entry was not requested. */
|
||||
public static final int STATUS_NOT_REQUESTED = 2;
|
||||
|
||||
/** Requested entry wasn't in the request message. */
|
||||
public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
|
||||
|
||||
/** The requested entry was not retrieved because user authentication wasn't performed. */
|
||||
public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
|
||||
|
||||
/** The requested entry was not retrieved because reader authentication wasn't performed. */
|
||||
public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
|
||||
|
||||
/**
|
||||
* The requested entry was not retrieved because it was configured without any access
|
||||
* control profile.
|
||||
*/
|
||||
public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected ResultData() {}
|
||||
|
||||
/**
|
||||
* Returns a CBOR structure containing the retrieved data.
|
||||
*
|
||||
* <p>This structure - along with the session transcript - may be cryptographically
|
||||
* authenticated to prove to the reader that the data is from a trusted credential and
|
||||
* {@link #getMessageAuthenticationCode()} can be used to get a MAC.
|
||||
*
|
||||
* <p>The CBOR structure which is cryptographically authenticated is the
|
||||
* {@code DeviceAuthentication} structure according to the following
|
||||
* <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema:
|
||||
*
|
||||
* <pre>
|
||||
* DeviceAuthentication = [
|
||||
* "DeviceAuthentication",
|
||||
* SessionTranscript,
|
||||
* DocType,
|
||||
* DeviceNameSpacesBytes
|
||||
* ]
|
||||
*
|
||||
* DocType = tstr
|
||||
*
|
||||
* SessionTranscript = [
|
||||
* DeviceEngagementBytes,
|
||||
* EReaderKeyBytes
|
||||
* ]
|
||||
*
|
||||
* DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
|
||||
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
|
||||
*
|
||||
* DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
|
||||
* </pre>
|
||||
*
|
||||
* where
|
||||
*
|
||||
* <pre>
|
||||
* DeviceNameSpaces = {
|
||||
* * NameSpace => DeviceSignedItems
|
||||
* }
|
||||
*
|
||||
* DeviceSignedItems = {
|
||||
* + DataItemName => DataItemValue
|
||||
* }
|
||||
*
|
||||
* NameSpace = tstr
|
||||
* DataItemName = tstr
|
||||
* DataItemValue = any
|
||||
* </pre>
|
||||
*
|
||||
* <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
|
||||
* as defined above.
|
||||
*
|
||||
* @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
|
||||
*/
|
||||
public abstract @NonNull byte[] getAuthenticatedData();
|
||||
|
||||
/**
|
||||
* Returns a message authentication code over the data returned by
|
||||
* {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted
|
||||
* credential.
|
||||
*
|
||||
* <p>The MAC proves to the reader that the data is from a trusted credential. This code is
|
||||
* produced by using the key agreement and key derivation function from the ciphersuite
|
||||
* with the authentication private key and the reader ephemeral public key to compute a
|
||||
* shared message authentication code (MAC) key, then using the MAC function from the
|
||||
* ciphersuite to compute a MAC of the authenticated data.
|
||||
*
|
||||
* <p>If the {@code sessionTranscript} parameter passed to
|
||||
* {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null}
|
||||
* or the reader ephmeral public key was never set using
|
||||
* {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message
|
||||
* authencation code will be produced and this method will return {@code null}.
|
||||
*
|
||||
* @return A COSE_Mac0 structure with the message authentication code as described above
|
||||
* or {@code null} if the conditions specified above are not met.
|
||||
*/
|
||||
public abstract @Nullable byte[] getMessageAuthenticationCode();
|
||||
|
||||
/**
|
||||
* Returns the static authentication data associated with the dynamic authentication
|
||||
* key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
|
||||
*
|
||||
* @return The static authentication data associated with dynamic authentication key used to
|
||||
* MAC the data.
|
||||
*/
|
||||
public abstract @NonNull byte[] getStaticAuthenticationData();
|
||||
|
||||
/**
|
||||
* Gets the names of namespaces with retrieved entries.
|
||||
*
|
||||
* @return collection of name of namespaces containing retrieved entries. May be empty if no
|
||||
* data was retrieved.
|
||||
*/
|
||||
public abstract @NonNull Collection<String> getNamespaceNames();
|
||||
|
||||
/**
|
||||
* Get the names of all entries.
|
||||
*
|
||||
* This includes the name of entries that wasn't successfully retrieved.
|
||||
*
|
||||
* @param namespaceName the namespace name to get entries for.
|
||||
* @return A collection of names or {@code null} if there are no entries for the given
|
||||
* namespace.
|
||||
*/
|
||||
public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName);
|
||||
|
||||
/**
|
||||
* Get the names of all entries that was successfully retrieved.
|
||||
*
|
||||
* This only return entries for which {@link #getStatus(String, String)} will return
|
||||
* {@link #STATUS_OK}.
|
||||
*
|
||||
* @param namespaceName the namespace name to get entries for.
|
||||
* @return A collection of names or {@code null} if there are no entries for the given
|
||||
* namespace.
|
||||
*/
|
||||
public abstract @Nullable Collection<String> getRetrievedEntryNames(
|
||||
@NonNull String namespaceName);
|
||||
|
||||
/**
|
||||
* Gets the status of an entry.
|
||||
*
|
||||
* This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
|
||||
* if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
|
||||
* {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
|
||||
* present in the request message,
|
||||
* {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
|
||||
* wasn't retrieved because the necessary user authentication wasn't performed,
|
||||
* {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
|
||||
* didn't match the set of certificates the entry was provisioned with, or
|
||||
* {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
|
||||
* access control profiles.
|
||||
*
|
||||
* @param namespaceName the namespace name of the entry.
|
||||
* @param name the name of the entry to get the value for.
|
||||
* @return the status indicating whether the value was retrieved and if not, why.
|
||||
*/
|
||||
@Status
|
||||
public abstract int getStatus(@NonNull String namespaceName, @NonNull String name);
|
||||
|
||||
/**
|
||||
* Gets the raw CBOR data for the value of an entry.
|
||||
*
|
||||
* This should only be called on an entry for which the {@link #getStatus(String, String)}
|
||||
* method returns {@link #STATUS_OK}.
|
||||
*
|
||||
* @param namespaceName the namespace name of the entry.
|
||||
* @param name the name of the entry to get the value for.
|
||||
* @return the raw CBOR data or {@code null} if no entry with the given name exists.
|
||||
*/
|
||||
public abstract @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name);
|
||||
|
||||
/**
|
||||
* The type of the entry status.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE,
|
||||
STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED,
|
||||
STATUS_NO_ACCESS_CONTROL_PROFILES})
|
||||
public @interface Status {
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown when trying use multiple different session transcripts in the same presentation session.
|
||||
*/
|
||||
public class SessionTranscriptMismatchException extends IdentityCredentialException {
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SessionTranscriptMismatchException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public SessionTranscriptMismatchException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SessionTranscriptMismatchException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public SessionTranscriptMismatchException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Thrown if trying to certify an unknown dynamic authentication key.
|
||||
*/
|
||||
public class UnknownAuthenticationKeyException extends IdentityCredentialException {
|
||||
/**
|
||||
* Constructs a new {@link UnknownAuthenticationKeyException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
*/
|
||||
public UnknownAuthenticationKeyException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link UnknownAuthenticationKeyException} exception.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause.
|
||||
*/
|
||||
public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
140
identity/java/android/security/identity/Util.java
Normal file
140
identity/java/android/security/identity/Util.java
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
class Util {
|
||||
private static final String TAG = "Util";
|
||||
|
||||
static int[] integerCollectionToArray(Collection<Integer> collection) {
|
||||
int[] result = new int[collection.size()];
|
||||
int n = 0;
|
||||
for (int item : collection) {
|
||||
result[n++] = item;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static byte[] stripLeadingZeroes(byte[] value) {
|
||||
int n = 0;
|
||||
while (n < value.length && value[n] == 0) {
|
||||
n++;
|
||||
}
|
||||
int newLen = value.length - n;
|
||||
byte[] ret = new byte[newLen];
|
||||
int m = 0;
|
||||
while (n < value.length) {
|
||||
ret[m++] = value[n++];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) {
|
||||
ECPoint w = ((ECPublicKey) publicKey).getW();
|
||||
// X and Y are always positive so for interop we remove any leading zeroes
|
||||
// inserted by the BigInteger encoder.
|
||||
byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
|
||||
byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write(0x04);
|
||||
baos.write(x);
|
||||
baos.write(y);
|
||||
return baos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unexpected IOException", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes an HKDF.
|
||||
*
|
||||
* This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
|
||||
* /crypto/tink/subtle/Hkdf.java
|
||||
* which is also Copyright (c) Google and also licensed under the Apache 2 license.
|
||||
*
|
||||
* @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
|
||||
* "HMACSHA256".
|
||||
* @param ikm the input keying material.
|
||||
* @param salt optional salt. A possibly non-secret random value. If no salt is
|
||||
* provided (i.e. if
|
||||
* salt has length 0) then an array of 0s of the same size as the hash
|
||||
* digest is used as salt.
|
||||
* @param info optional context and application specific information.
|
||||
* @param size The length of the generated pseudorandom string in bytes. The maximal
|
||||
* size is
|
||||
* 255.DigestSize, where DigestSize is the size of the underlying HMAC.
|
||||
* @return size pseudorandom bytes.
|
||||
*/
|
||||
static byte[] computeHkdf(
|
||||
String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
|
||||
Mac mac = null;
|
||||
try {
|
||||
mac = Mac.getInstance(macAlgorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
|
||||
}
|
||||
if (size > 255 * mac.getMacLength()) {
|
||||
throw new RuntimeException("size too large");
|
||||
}
|
||||
try {
|
||||
if (salt == null || salt.length == 0) {
|
||||
// According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
|
||||
// then HKDF uses a salt that is an array of zeros of the same length as the hash
|
||||
// digest.
|
||||
mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
|
||||
} else {
|
||||
mac.init(new SecretKeySpec(salt, macAlgorithm));
|
||||
}
|
||||
byte[] prk = mac.doFinal(ikm);
|
||||
byte[] result = new byte[size];
|
||||
int ctr = 1;
|
||||
int pos = 0;
|
||||
mac.init(new SecretKeySpec(prk, macAlgorithm));
|
||||
byte[] digest = new byte[0];
|
||||
while (true) {
|
||||
mac.update(digest);
|
||||
mac.update(info);
|
||||
mac.update((byte) ctr);
|
||||
digest = mac.doFinal();
|
||||
if (pos + digest.length < size) {
|
||||
System.arraycopy(digest, 0, result, pos, digest.length);
|
||||
pos += digest.length;
|
||||
ctr++;
|
||||
} else {
|
||||
System.arraycopy(digest, 0, result, pos, size - pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new RuntimeException("Error MACing", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2019 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.identity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class used to personalize a new identity credential.
|
||||
*
|
||||
* <p>Credentials cannot be updated or modified after creation; any changes require deletion and
|
||||
* re-creation.
|
||||
*
|
||||
* Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential.
|
||||
*/
|
||||
public abstract class WritableIdentityCredential {
|
||||
/**
|
||||
* Generates and returns an X.509 certificate chain for the CredentialKey which identifies this
|
||||
* credential to the issuing authority. The certificate contains an
|
||||
* <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
|
||||
* attestation extension which describes the key and the security hardware in which it lives.
|
||||
*
|
||||
* <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates
|
||||
* it is an Identity Credential key (which can only sign/MAC very specific messages) and not
|
||||
* an Android Keystore key (which can be used to sign/MAC anything).
|
||||
*
|
||||
* <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
|
||||
* limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is
|
||||
* present, the passed in challenge is present, the device has verified boot enabled, that each
|
||||
* certificate in the chain is signed by its successor, that none of the certificates have been
|
||||
* revoked and so on.
|
||||
*
|
||||
* <p>It is not strictly necessary to use this method to provision a credential if the issuing
|
||||
* authority doesn't care about the nature of the security hardware. If called, however, this
|
||||
* method must be called before {@link #personalize(PersonalizationData)}.
|
||||
*
|
||||
* @param challenge is a byte array whose contents should be unique, fresh and provided by
|
||||
* the issuing authority. The value provided is embedded in the attestation
|
||||
* extension and enables the issuing authority to verify that the attestation
|
||||
* certificate is fresh.
|
||||
* @return the X.509 certificate for this credential's CredentialKey.
|
||||
*/
|
||||
public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
|
||||
@NonNull byte[] challenge);
|
||||
|
||||
/**
|
||||
* Stores all of the data in the credential, with the specified access control profiles.
|
||||
*
|
||||
* <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload
|
||||
* set to {@code ProofOfProvisioning} as defined below.
|
||||
*
|
||||
* <pre>
|
||||
* ProofOfProvisioning = [
|
||||
* "ProofOfProvisioning", ; tstr
|
||||
* tstr, ; DocType
|
||||
* [ * AccessControlProfile ],
|
||||
* ProvisionedData,
|
||||
* bool ; true if this is a test credential, should
|
||||
* ; always be false.
|
||||
* ]
|
||||
*
|
||||
* AccessControlProfile = {
|
||||
* "id": uint,
|
||||
* ? "readerCertificate" : bstr,
|
||||
* ? (
|
||||
* "userAuthenticationRequired" : bool,
|
||||
* "timeoutMillis" : uint,
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* ProvisionedData = {
|
||||
* * Namespace => [ + Entry ]
|
||||
* },
|
||||
*
|
||||
* Namespace = tstr
|
||||
*
|
||||
* Entry = {
|
||||
* "name" : tstr,
|
||||
* "value" : any,
|
||||
* "accessControlProfiles" : [ * uint ],
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>This data structure provides a guarantee to the issuer about the data which may be
|
||||
* returned in the CBOR returned by
|
||||
* {@link ResultData#getAuthenticatedData()} during a credential
|
||||
* presentation.
|
||||
*
|
||||
* @param personalizationData The data to provision, including access control profiles
|
||||
* and data elements and their values, grouped into namespaces.
|
||||
* @return A COSE_Sign1 data structure, see above.
|
||||
*/
|
||||
public abstract @NonNull byte[] personalize(
|
||||
@NonNull PersonalizationData personalizationData);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user