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 return personalize(mBinder, personalizationData); 80 } 81 82 // Used by both personalize() and CredstoreIdentityCredential.update(). 83 // 84 @NonNull personalize(IWritableCredential binder, @NonNull PersonalizationData personalizationData)85 static byte[] personalize(IWritableCredential binder, 86 @NonNull PersonalizationData personalizationData) { 87 Collection<AccessControlProfile> accessControlProfiles = 88 personalizationData.getAccessControlProfiles(); 89 90 AccessControlProfileParcel[] acpParcels = 91 new AccessControlProfileParcel[accessControlProfiles.size()]; 92 boolean usingUserAuthentication = false; 93 int n = 0; 94 for (AccessControlProfile profile : accessControlProfiles) { 95 acpParcels[n] = new AccessControlProfileParcel(); 96 acpParcels[n].id = profile.getAccessControlProfileId().getId(); 97 X509Certificate cert = profile.getReaderCertificate(); 98 if (cert != null) { 99 try { 100 acpParcels[n].readerCertificate = cert.getEncoded(); 101 } catch (CertificateException e) { 102 throw new RuntimeException("Error encoding reader certificate", e); 103 } 104 } else { 105 acpParcels[n].readerCertificate = new byte[0]; 106 } 107 acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired(); 108 acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout(); 109 if (profile.isUserAuthenticationRequired()) { 110 usingUserAuthentication = true; 111 } 112 n++; 113 } 114 115 Collection<String> namespaces = personalizationData.getNamespaces(); 116 117 EntryNamespaceParcel[] ensParcels = new EntryNamespaceParcel[namespaces.size()]; 118 n = 0; 119 for (String namespaceName : namespaces) { 120 PersonalizationData.NamespaceData nsd = 121 personalizationData.getNamespaceData(namespaceName); 122 123 ensParcels[n] = new EntryNamespaceParcel(); 124 ensParcels[n].namespaceName = namespaceName; 125 126 Collection<String> entryNames = nsd.getEntryNames(); 127 EntryParcel[] eParcels = new EntryParcel[entryNames.size()]; 128 int m = 0; 129 for (String entryName : entryNames) { 130 eParcels[m] = new EntryParcel(); 131 eParcels[m].name = entryName; 132 eParcels[m].value = nsd.getEntryValue(entryName); 133 Collection<AccessControlProfileId> acpIds = 134 nsd.getAccessControlProfileIds(entryName); 135 eParcels[m].accessControlProfileIds = new int[acpIds.size()]; 136 int o = 0; 137 for (AccessControlProfileId acpId : acpIds) { 138 eParcels[m].accessControlProfileIds[o++] = acpId.getId(); 139 } 140 m++; 141 } 142 ensParcels[n].entries = eParcels; 143 n++; 144 } 145 146 // Note: The value 0 is used to convey that no user-authentication is needed for this 147 // credential. This is to allow creating credentials w/o user authentication on devices 148 // where Secure lock screen is not enabled. 149 long secureUserId = 0; 150 if (usingUserAuthentication) { 151 secureUserId = getRootSid(); 152 } 153 try { 154 byte[] personalizationReceipt = binder.personalize(acpParcels, ensParcels, 155 secureUserId); 156 return personalizationReceipt; 157 } catch (android.os.RemoteException e) { 158 throw new RuntimeException("Unexpected RemoteException ", e); 159 } catch (android.os.ServiceSpecificException e) { 160 throw new RuntimeException("Unexpected ServiceSpecificException with code " 161 + e.errorCode, e); 162 } 163 } 164 getRootSid()165 private static long getRootSid() { 166 long rootSid = GateKeeper.getSecureUserId(); 167 if (rootSid == 0) { 168 throw new IllegalStateException("Secure lock screen must be enabled" 169 + " to create credentials requiring user authentication"); 170 } 171 return rootSid; 172 } 173 174 } 175