/* * 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 getCredentialKeyCertificateChain(@NonNull byte[] challenge) { try { byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge); ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); Collection certs = null; try { CertificateFactory factory = CertificateFactory.getInstance("X.509"); certs = factory.generateCertificates(bais); } catch (CertificateException e) { throw new RuntimeException("Error decoding certificates", e); } ArrayList 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 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 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 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 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; } }