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