| /* |
| * 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; |
| } |
| |
| } |