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.ArrayList; 29 import java.util.Collection; 30 import java.util.List; 31 32 class CredstoreWritableIdentityCredential extends WritableIdentityCredential { 33 34 private static final String TAG = "CredstoreWritableIdentityCredential"; 35 36 private String mDocType; 37 private String mCredentialName; 38 private Context mContext; 39 private IWritableCredential mBinder; 40 CredstoreWritableIdentityCredential(Context context, @NonNull String credentialName, @NonNull String docType, IWritableCredential binder)41 CredstoreWritableIdentityCredential(Context context, 42 @NonNull String credentialName, 43 @NonNull String docType, 44 IWritableCredential binder) { 45 mContext = context; 46 mDocType = docType; 47 mCredentialName = credentialName; 48 mBinder = binder; 49 } 50 51 @NonNull @Override getCredentialKeyCertificateChain(@onNull byte[] challenge)52 public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) { 53 try { 54 byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge); 55 ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob); 56 57 Collection<? extends Certificate> certs = null; 58 try { 59 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 60 certs = factory.generateCertificates(bais); 61 } catch (CertificateException e) { 62 throw new RuntimeException("Error decoding certificates", e); 63 } 64 65 ArrayList<X509Certificate> x509Certs = new ArrayList<>(); 66 for (Certificate cert : certs) { 67 x509Certs.add((X509Certificate) cert); 68 } 69 return x509Certs; 70 } catch (android.os.RemoteException e) { 71 throw new RuntimeException("Unexpected RemoteException ", e); 72 } catch (android.os.ServiceSpecificException e) { 73 throw new RuntimeException("Unexpected ServiceSpecificException with code " 74 + e.errorCode, e); 75 } 76 } 77 78 @NonNull @Override personalize(@onNull PersonalizationData personalizationData)79 public byte[] personalize(@NonNull PersonalizationData personalizationData) { 80 return personalize(mBinder, personalizationData); 81 } 82 83 // Used by both personalize() and CredstoreIdentityCredential.update(). 84 // 85 @NonNull personalize(IWritableCredential binder, @NonNull PersonalizationData personalizationData)86 static byte[] personalize(IWritableCredential binder, 87 @NonNull PersonalizationData personalizationData) { 88 Collection<AccessControlProfile> accessControlProfiles = 89 personalizationData.getAccessControlProfiles(); 90 91 AccessControlProfileParcel[] acpParcels = 92 new AccessControlProfileParcel[accessControlProfiles.size()]; 93 boolean usingUserAuthentication = false; 94 int n = 0; 95 for (AccessControlProfile profile : accessControlProfiles) { 96 acpParcels[n] = new AccessControlProfileParcel(); 97 acpParcels[n].id = profile.getAccessControlProfileId().getId(); 98 X509Certificate cert = profile.getReaderCertificate(); 99 if (cert != null) { 100 try { 101 acpParcels[n].readerCertificate = cert.getEncoded(); 102 } catch (CertificateException e) { 103 throw new RuntimeException("Error encoding reader certificate", e); 104 } 105 } else { 106 acpParcels[n].readerCertificate = new byte[0]; 107 } 108 acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired(); 109 acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout(); 110 if (profile.isUserAuthenticationRequired()) { 111 usingUserAuthentication = true; 112 } 113 n++; 114 } 115 116 Collection<String> namespaces = personalizationData.getNamespaces(); 117 118 EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaces.size()]; 119 n = 0; 120 for (String namespaceName : namespaces) { 121 PersonalizationData.NamespaceData nsd = 122 personalizationData.getNamespaceData(namespaceName); 123 124 ensParcels[n] = new EntryNamespaceParcel(); 125 ensParcels[n].namespaceName = namespaceName; 126 127 Collection<String> entryNames = nsd.getEntryNames(); 128 EntryParcel[] eParcels = new EntryParcel[entryNames.size()]; 129 int m = 0; 130 for (String entryName : entryNames) { 131 eParcels[m] = new EntryParcel(); 132 eParcels[m].name = entryName; 133 eParcels[m].value = nsd.getEntryValue(entryName); 134 Collection<AccessControlProfileId> acpIds = 135 nsd.getAccessControlProfileIds(entryName); 136 eParcels[m].accessControlProfileIds = new int[acpIds.size()]; 137 int o = 0; 138 for (AccessControlProfileId acpId : acpIds) { 139 eParcels[m].accessControlProfileIds[o++] = acpId.getId(); 140 } 141 m++; 142 } 143 ensParcels[n].entries = eParcels; 144 n++; 145 } 146 147 // Note: The value 0 is used to convey that no user-authentication is needed for this 148 // credential. This is to allow creating credentials w/o user authentication on devices 149 // where Secure lock screen is not enabled. 150 long secureUserId = 0; 151 if (usingUserAuthentication) { 152 secureUserId = getRootSid(); 153 } 154 try { 155 byte[] personalizationReceipt = binder.personalize(acpParcels, ensParcels, 156 secureUserId); 157 return personalizationReceipt; 158 } catch (android.os.RemoteException e) { 159 throw new RuntimeException("Unexpected RemoteException ", e); 160 } catch (android.os.ServiceSpecificException e) { 161 throw new RuntimeException("Unexpected ServiceSpecificException with code " 162 + e.errorCode, e); 163 } 164 } 165 getRootSid()166 private static long getRootSid() { 167 long rootSid = GateKeeper.getSecureUserId(); 168 if (rootSid == 0) { 169 throw new IllegalStateException("Secure lock screen must be enabled" 170 + " to create credentials requiring user authentication"); 171 } 172 return rootSid; 173 } 174 175 } 176