• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.security.InvalidKeyException;
27 import java.security.KeyPair;
28 import java.security.KeyStore;
29 import java.security.KeyStoreException;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.PrivateKey;
32 import java.security.PublicKey;
33 import java.security.UnrecoverableKeyException;
34 import java.security.cert.Certificate;
35 import java.security.cert.CertificateException;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 
39 class CredstorePresentationSession extends PresentationSession {
40     private static final String TAG = "CredstorePresentationSession";
41 
42     private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
43     private Context mContext;
44     private CredstoreIdentityCredentialStore mStore;
45     private ISession mBinder;
46     private Map<String, CredstoreIdentityCredential> mCredentialCache = new LinkedHashMap<>();
47     private KeyPair mEphemeralKeyPair = null;
48     private byte[] mSessionTranscript = null;
49     private boolean mOperationHandleSet = false;
50     private long mOperationHandle = 0;
51     private int mFeatureVersion = 0;
52 
CredstorePresentationSession(Context context, @IdentityCredentialStore.Ciphersuite int cipherSuite, CredstoreIdentityCredentialStore store, ISession binder, int featureVersion)53     CredstorePresentationSession(Context context,
54             @IdentityCredentialStore.Ciphersuite int cipherSuite,
55             CredstoreIdentityCredentialStore store,
56             ISession binder,
57             int featureVersion) {
58         mContext = context;
59         mCipherSuite = cipherSuite;
60         mStore = store;
61         mBinder = binder;
62         mFeatureVersion = featureVersion;
63     }
64 
ensureEphemeralKeyPair()65     private void ensureEphemeralKeyPair() {
66         if (mEphemeralKeyPair != null) {
67             return;
68         }
69         try {
70             // This PKCS#12 blob is generated in credstore, using BoringSSL.
71             //
72             // The main reason for this convoluted approach and not just sending the decomposed
73             // key-pair is that this would require directly using (device-side) BouncyCastle which
74             // is tricky due to various API hiding efforts. So instead we have credstore generate
75             // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
76             // doesn't support not using encryption when building a PKCS#12 blob).
77             //
78             byte[] pkcs12 = mBinder.getEphemeralKeyPair();
79             String alias = "ephemeralKey";
80             char[] password = {};
81 
82             KeyStore ks = KeyStore.getInstance("PKCS12");
83             ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
84             ks.load(bais, password);
85             PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
86 
87             Certificate cert = ks.getCertificate(alias);
88             PublicKey pubKey = cert.getPublicKey();
89 
90             mEphemeralKeyPair = new KeyPair(pubKey, privKey);
91         } catch (android.os.ServiceSpecificException e) {
92             throw new RuntimeException("Unexpected ServiceSpecificException with code "
93                     + e.errorCode, e);
94         } catch (android.os.RemoteException
95                 | KeyStoreException
96                 | CertificateException
97                 | UnrecoverableKeyException
98                 | NoSuchAlgorithmException
99                 | IOException e) {
100             throw new RuntimeException("Unexpected exception ", e);
101         }
102     }
103 
104     @Override
getEphemeralKeyPair()105     public @NonNull KeyPair getEphemeralKeyPair() {
106         ensureEphemeralKeyPair();
107         return mEphemeralKeyPair;
108     }
109 
110     @Override
setReaderEphemeralPublicKey(@onNull PublicKey readerEphemeralPublicKey)111     public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
112             throws InvalidKeyException {
113         try {
114             byte[] uncompressedForm =
115                     Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
116             mBinder.setReaderEphemeralPublicKey(uncompressedForm);
117         } catch (android.os.RemoteException e) {
118             throw new RuntimeException("Unexpected RemoteException ", e);
119         } catch (android.os.ServiceSpecificException e) {
120             throw new RuntimeException("Unexpected ServiceSpecificException with code "
121                     + e.errorCode, e);
122         }
123     }
124 
125     @Override
setSessionTranscript(@onNull byte[] sessionTranscript)126     public void setSessionTranscript(@NonNull byte[] sessionTranscript) {
127         try {
128             mBinder.setSessionTranscript(sessionTranscript);
129             mSessionTranscript = sessionTranscript;
130         } catch (android.os.RemoteException e) {
131             throw new RuntimeException("Unexpected RemoteException ", e);
132         } catch (android.os.ServiceSpecificException e) {
133             throw new RuntimeException("Unexpected ServiceSpecificException with code "
134                     + e.errorCode, e);
135         }
136     }
137 
138     @Override
getCredentialData(@onNull String credentialName, @NonNull CredentialDataRequest request)139     public @Nullable CredentialDataResult getCredentialData(@NonNull String credentialName,
140                                                             @NonNull CredentialDataRequest request)
141             throws NoAuthenticationKeyAvailableException, InvalidReaderSignatureException,
142             InvalidRequestMessageException, EphemeralPublicKeyNotFoundException {
143         try {
144             // Cache the IdentityCredential to satisfy the property that AuthKey usage counts are
145             // incremented on only the _first_ getCredentialData() call.
146             //
147             CredstoreIdentityCredential credential = mCredentialCache.get(credentialName);
148             if (credential == null) {
149                 ICredential credstoreCredential =
150                     mBinder.getCredentialForPresentation(credentialName);
151                 credential = new CredstoreIdentityCredential(mContext, credentialName,
152                                                              mCipherSuite, credstoreCredential,
153                                                              this, mFeatureVersion);
154                 mCredentialCache.put(credentialName, credential);
155 
156                 credential.setAllowUsingExhaustedKeys(request.isAllowUsingExhaustedKeys());
157                 credential.setAllowUsingExpiredKeys(request.isAllowUsingExpiredKeys());
158                 credential.setIncrementKeyUsageCount(request.isIncrementUseCount());
159             }
160 
161             ResultData deviceSignedResult = credential.getEntries(
162                     request.getRequestMessage(),
163                     request.getDeviceSignedEntriesToRequest(),
164                     mSessionTranscript,
165                     request.getReaderSignature());
166 
167             // By design this second getEntries() call consumes the same auth-key.
168 
169             ResultData issuerSignedResult = credential.getEntries(
170                     request.getRequestMessage(),
171                     request.getIssuerSignedEntriesToRequest(),
172                     mSessionTranscript,
173                     request.getReaderSignature());
174 
175             return new CredstoreCredentialDataResult(deviceSignedResult, issuerSignedResult);
176 
177         } catch (SessionTranscriptMismatchException e) {
178             throw new RuntimeException("Unexpected ", e);
179         } catch (android.os.RemoteException e) {
180             throw new RuntimeException("Unexpected RemoteException ", e);
181         } catch (android.os.ServiceSpecificException e) {
182             if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
183                 return null;
184             } else {
185                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
186                         + e.errorCode, e);
187             }
188         }
189     }
190 
191     /**
192      * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
193      * operation handle.
194      *
195      * @hide
196      */
197     @Override
getCredstoreOperationHandle()198     public long getCredstoreOperationHandle() {
199         if (!mOperationHandleSet) {
200             try {
201                 mOperationHandle = mBinder.getAuthChallenge();
202                 mOperationHandleSet = true;
203             } catch (android.os.RemoteException e) {
204                 throw new RuntimeException("Unexpected RemoteException ", e);
205             } catch (android.os.ServiceSpecificException e) {
206                 if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
207                     // The NoAuthenticationKeyAvailableException will be thrown when
208                     // the caller proceeds to call getEntries().
209                 }
210                 throw new RuntimeException("Unexpected ServiceSpecificException with code "
211                         + e.errorCode, e);
212             }
213         }
214         return mOperationHandle;
215     }
216 
217 }
218