1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.identity; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.security.GateKeeper; 22 23 import java.io.ByteArrayInputStream; 24 import java.security.cert.Certificate; 25 import java.security.cert.CertificateException; 26 import java.security.cert.CertificateFactory; 27 import java.security.cert.X509Certificate; 28 import java.util.Collection; 29 import java.util.LinkedList; 30 31 class CredstoreWritableIdentityCredential extends WritableIdentityCredential { 32 33 private static final String TAG = "CredstoreWritableIdentityCredential"; 34 35 private String mDocType; 36 private String mCredentialName; 37 private Context mContext; 38 private IWritableCredential mBinder; 39 CredstoreWritableIdentityCredential(Context context, @NonNull String credentialName, @NonNull String docType, IWritableCredential binder)40 CredstoreWritableIdentityCredential(Context context, 41 @NonNull String credentialName, 42 @NonNull String docType, 43 IWritableCredential binder) { 44 mContext = context; 45 mDocType = docType; 46 mCredentialName = credentialName; 47 mBinder = binder; 48 } 49 50 @NonNull @Override getCredentialKeyCertificateChain(@onNull byte[] challenge)51 public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) { 52 try { 53 byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge); 54 ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); 55 56 Collection<? extends Certificate> certs = null; 57 try { 58 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 59 certs = factory.generateCertificates(bais); 60 } catch (CertificateException e) { 61 throw new RuntimeException("Error decoding certificates", e); 62 } 63 64 LinkedList<X509Certificate> x509Certs = new LinkedList<>(); 65 for (Certificate cert : certs) { 66 x509Certs.add((X509Certificate) cert); 67 } 68 return x509Certs; 69 } catch (android.os.RemoteException e) { 70 throw new RuntimeException("Unexpected RemoteException ", e); 71 } catch (android.os.ServiceSpecificException e) { 72 throw new RuntimeException("Unexpected ServiceSpecificException with code " 73 + e.errorCode, e); 74 } 75 } 76 77 @NonNull @Override personalize(@onNull PersonalizationData personalizationData)78 public byte[] personalize(@NonNull PersonalizationData personalizationData) { 79 80 Collection<AccessControlProfile> accessControlProfiles = 81 personalizationData.getAccessControlProfiles(); 82 83 AccessControlProfileParcel[] acpParcels = 84 new AccessControlProfileParcel[accessControlProfiles.size()]; 85 boolean usingUserAuthentication = false; 86 int n = 0; 87 for (AccessControlProfile profile : accessControlProfiles) { 88 acpParcels[n] = new AccessControlProfileParcel(); 89 acpParcels[n].id = profile.getAccessControlProfileId().getId(); 90 X509Certificate cert = profile.getReaderCertificate(); 91 if (cert != null) { 92 try { 93 acpParcels[n].readerCertificate = cert.getEncoded(); 94 } catch (CertificateException e) { 95 throw new RuntimeException("Error encoding reader certificate", e); 96 } 97 } else { 98 acpParcels[n].readerCertificate = new byte[0]; 99 } 100 acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired(); 101 acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout(); 102 if (profile.isUserAuthenticationRequired()) { 103 usingUserAuthentication = true; 104 } 105 n++; 106 } 107 108 Collection<String> namespaces = personalizationData.getNamespaces(); 109 110 EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaces.size()]; 111 n = 0; 112 for (String namespaceName : namespaces) { 113 PersonalizationData.NamespaceData nsd = 114 personalizationData.getNamespaceData(namespaceName); 115 116 ensParcels[n] = new EntryNamespaceParcel(); 117 ensParcels[n].namespaceName = namespaceName; 118 119 Collection<String> entryNames = nsd.getEntryNames(); 120 EntryParcel[] eParcels = new EntryParcel[entryNames.size()]; 121 int m = 0; 122 for (String entryName : entryNames) { 123 eParcels[m] = new EntryParcel(); 124 eParcels[m].name = entryName; 125 eParcels[m].value = nsd.getEntryValue(entryName); 126 Collection<AccessControlProfileId> acpIds = 127 nsd.getAccessControlProfileIds(entryName); 128 eParcels[m].accessControlProfileIds = new int[acpIds.size()]; 129 int o = 0; 130 for (AccessControlProfileId acpId : acpIds) { 131 eParcels[m].accessControlProfileIds[o++] = acpId.getId(); 132 } 133 m++; 134 } 135 ensParcels[n].entries = eParcels; 136 n++; 137 } 138 139 // Note: The value 0 is used to convey that no user-authentication is needed for this 140 // credential. This is to allow creating credentials w/o user authentication on devices 141 // where Secure lock screen is not enabled. 142 long secureUserId = 0; 143 if (usingUserAuthentication) { 144 secureUserId = getRootSid(); 145 } 146 try { 147 byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels, 148 secureUserId); 149 return personalizationReceipt; 150 } catch (android.os.RemoteException e) { 151 throw new RuntimeException("Unexpected RemoteException ", e); 152 } catch (android.os.ServiceSpecificException e) { 153 throw new RuntimeException("Unexpected ServiceSpecificException with code " 154 + e.errorCode, e); 155 } 156 } 157 getRootSid()158 private static long getRootSid() { 159 long rootSid = GateKeeper.getSecureUserId(); 160 if (rootSid == 0) { 161 throw new IllegalStateException("Secure lock screen must be enabled" 162 + " to create credentials requiring user authentication"); 163 } 164 return rootSid; 165 } 166 167 168 } 169