176 lines
6.9 KiB
Java
176 lines
6.9 KiB
Java
/*
|
|
* 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.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
|
|
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);
|
|
}
|
|
|
|
ArrayList<X509Certificate> x509Certs = new ArrayList<>();
|
|
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) {
|
|
return personalize(mBinder, personalizationData);
|
|
}
|
|
|
|
// Used by both personalize() and CredstoreIdentityCredential.update().
|
|
//
|
|
@NonNull
|
|
static byte[] personalize(IWritableCredential binder,
|
|
@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> namespaces = personalizationData.getNamespaces();
|
|
|
|
EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaces.size()];
|
|
n = 0;
|
|
for (String namespaceName : namespaces) {
|
|
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 = binder.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;
|
|
}
|
|
|
|
}
|