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.annotation.Nullable;
21 import android.content.Context;
22 import android.content.pm.FeatureInfo;
23 import android.content.pm.PackageManager;
24 import android.os.ServiceManager;
25 
26 class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
27 
28     private static final String TAG = "CredstoreIdentityCredentialStore";
29 
30     private Context mContext = null;
31     private ICredentialStore mStore = null;
32     private int mFeatureVersion;
33 
getFeatureVersion(@onNull Context context)34     static int getFeatureVersion(@NonNull Context context) {
35         PackageManager pm = context.getPackageManager();
36         if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
37             FeatureInfo[] infos = pm.getSystemAvailableFeatures();
38             for (int n = 0; n < infos.length; n++) {
39                 FeatureInfo info = infos[n];
40                 if (info.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
41                     return info.version;
42                 }
43             }
44         }
45         // Use of the system feature is not required since Android 12. So for Android 11
46         // return 202009 which is the feature version shipped with Android 11.
47         return 202009;
48     }
49 
CredstoreIdentityCredentialStore(@onNull Context context, ICredentialStore store)50     private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
51         mContext = context;
52         mStore = store;
53         mFeatureVersion = getFeatureVersion(mContext);
54     }
55 
getInstanceForType(@onNull Context context, int credentialStoreType)56     static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
57             int credentialStoreType) {
58         ICredentialStoreFactory storeFactory =
59                 ICredentialStoreFactory.Stub.asInterface(
60                     ServiceManager.getService("android.security.identity"));
61         if (storeFactory == null) {
62             // This can happen if credstore is not running or not installed.
63             return null;
64         }
65 
66         ICredentialStore credStore = null;
67         try {
68             credStore = storeFactory.getCredentialStore(credentialStoreType);
69         } catch (android.os.RemoteException e) {
70             throw new RuntimeException("Unexpected RemoteException ", e);
71         } catch (android.os.ServiceSpecificException e) {
72             if (e.errorCode == ICredentialStore.ERROR_GENERIC) {
73                 return null;
74             } else {
75                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
76                         + e.errorCode, e);
77             }
78         }
79         if (credStore == null) {
80             return null;
81         }
82 
83         return new CredstoreIdentityCredentialStore(context, credStore);
84     }
85 
86     private static CredstoreIdentityCredentialStore sInstanceDefault = null;
87     private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null;
88 
getInstance(@onNull Context context)89     public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
90         if (sInstanceDefault == null) {
91             sInstanceDefault = getInstanceForType(context,
92                     ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT);
93         }
94         return sInstanceDefault;
95     }
96 
getDirectAccessInstance(@onNull Context context)97     public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
98             Context context) {
99         if (sInstanceDirectAccess == null) {
100             sInstanceDirectAccess = getInstanceForType(context,
101                     ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS);
102         }
103         return sInstanceDirectAccess;
104     }
105 
106     @Override
getSupportedDocTypes()107     public @NonNull String[] getSupportedDocTypes() {
108         try {
109             SecurityHardwareInfoParcel info;
110             info = mStore.getSecurityHardwareInfo();
111             return info.supportedDocTypes;
112         } catch (android.os.RemoteException e) {
113             throw new RuntimeException("Unexpected RemoteException ", e);
114         } catch (android.os.ServiceSpecificException e) {
115             throw new RuntimeException("Unexpected ServiceSpecificException with code "
116                     + e.errorCode, e);
117         }
118     }
119 
createCredential( @onNull String credentialName, @NonNull String docType)120     @Override public @NonNull WritableIdentityCredential createCredential(
121             @NonNull String credentialName,
122             @NonNull String docType) throws AlreadyPersonalizedException,
123             DocTypeNotSupportedException {
124         try {
125             IWritableCredential wc = mStore.createCredential(credentialName, docType);
126             return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
127         } catch (android.os.RemoteException e) {
128             throw new RuntimeException("Unexpected RemoteException ", e);
129         } catch (android.os.ServiceSpecificException e) {
130             if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) {
131                 throw new AlreadyPersonalizedException(e.getMessage(), e);
132             } else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) {
133                 throw new DocTypeNotSupportedException(e.getMessage(), e);
134             } else {
135                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
136                         + e.errorCode, e);
137             }
138         }
139     }
140 
getCredentialByName( @onNull String credentialName, @Ciphersuite int cipherSuite)141     @Override public @Nullable IdentityCredential getCredentialByName(
142             @NonNull String credentialName,
143             @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
144         try {
145             ICredential credstoreCredential;
146             credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
147             return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
148                     credstoreCredential, null, mFeatureVersion);
149         } catch (android.os.RemoteException e) {
150             throw new RuntimeException("Unexpected RemoteException ", e);
151         } catch (android.os.ServiceSpecificException e) {
152             if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
153                 return null;
154             } else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
155                 throw new CipherSuiteNotSupportedException(e.getMessage(), e);
156             } else {
157                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
158                         + e.errorCode, e);
159             }
160         }
161     }
162 
163     @Override
deleteCredentialByName(@onNull String credentialName)164     public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) {
165         ICredential credstoreCredential = null;
166         try {
167             try {
168                 credstoreCredential = mStore.getCredentialByName(credentialName,
169                         CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
170             } catch (android.os.ServiceSpecificException e) {
171                 if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
172                     return null;
173                 }
174             }
175             byte[] proofOfDeletion = credstoreCredential.deleteCredential();
176             return proofOfDeletion;
177         } catch (android.os.RemoteException e) {
178             throw new RuntimeException("Unexpected RemoteException ", e);
179         } catch (android.os.ServiceSpecificException e) {
180             throw new RuntimeException("Unexpected ServiceSpecificException with code "
181                     + e.errorCode, e);
182         }
183     }
184 
185     @Override
createPresentationSession(@iphersuite int cipherSuite)186     public @NonNull PresentationSession createPresentationSession(@Ciphersuite int cipherSuite)
187             throws CipherSuiteNotSupportedException {
188         try {
189             ISession credstoreSession = mStore.createPresentationSession(cipherSuite);
190             return new CredstorePresentationSession(mContext, cipherSuite, this, credstoreSession,
191                                                     mFeatureVersion);
192         } catch (android.os.RemoteException e) {
193             throw new RuntimeException("Unexpected RemoteException ", e);
194         } catch (android.os.ServiceSpecificException e) {
195             if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
196                 throw new CipherSuiteNotSupportedException(e.getMessage(), e);
197             } else {
198                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
199                         + e.errorCode, e);
200             }
201         }
202     }
203 
204 }
205