1 /*
2  * Copyright (C) 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.keystore2;
18 
19 import android.hardware.security.keymint.Algorithm;
20 import android.hardware.security.keymint.KeyParameter;
21 import android.hardware.security.keymint.KeyPurpose;
22 import android.hardware.security.keymint.Tag;
23 import android.security.KeyStoreException;
24 import android.security.KeyStoreOperation;
25 import android.security.keystore.KeyStoreCryptoOperation;
26 
27 import java.security.InvalidAlgorithmParameterException;
28 import java.security.InvalidKeyException;
29 import java.security.Key;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.ProviderException;
32 import java.security.PublicKey;
33 import java.security.SecureRandom;
34 import java.security.interfaces.ECKey;
35 import java.security.spec.AlgorithmParameterSpec;
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 import javax.crypto.KeyAgreementSpi;
40 import javax.crypto.SecretKey;
41 import javax.crypto.ShortBufferException;
42 import javax.crypto.spec.SecretKeySpec;
43 
44 /**
45  * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore.
46  *
47  * @hide
48  */
49 public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi
50         implements KeyStoreCryptoOperation {
51 
52     private static final String TAG = "AndroidKeyStoreKeyAgreementSpi";
53 
54     /**
55      * ECDH implementation.
56      *
57      * @hide
58      */
59     public static class ECDH extends AndroidKeyStoreKeyAgreementSpi {
ECDH()60         public ECDH() {
61             super(Algorithm.EC);
62         }
63     }
64 
65     /**
66      * X25519 key agreement support.
67      *
68      * @hide
69      */
70     public static class XDH extends AndroidKeyStoreKeyAgreementSpi {
XDH()71         public XDH() {
72             super(Algorithm.EC);
73         }
74     }
75 
76     private final int mKeymintAlgorithm;
77 
78     // Fields below are populated by engineInit and should be preserved after engineDoFinal.
79     private AndroidKeyStorePrivateKey mKey;
80     private PublicKey mOtherPartyKey;
81 
82     // Fields below are reset when engineDoFinal succeeds.
83     private KeyStoreOperation mOperation;
84     private long mOperationHandle;
85 
AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm)86     protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) {
87         resetAll();
88 
89         mKeymintAlgorithm = keymintAlgorithm;
90     }
91 
92     @Override
engineInit(Key key, SecureRandom random)93     protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
94         resetAll();
95 
96         if (key == null) {
97             throw new InvalidKeyException("key == null");
98         } else if (!(key instanceof AndroidKeyStorePrivateKey)) {
99             throw new InvalidKeyException(
100                     "Only Android KeyStore private keys supported. Key: " + key);
101         }
102         // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in
103         // ensureKeystoreOperationInitialized() below.
104         mKey = (AndroidKeyStorePrivateKey) key;
105 
106         boolean success = false;
107         try {
108             ensureKeystoreOperationInitialized();
109             success = true;
110         } finally {
111             if (!success) {
112                 resetAll();
113             }
114         }
115     }
116 
117     @Override
engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)118     protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)
119             throws InvalidKeyException, InvalidAlgorithmParameterException {
120         if (params != null) {
121             throw new InvalidAlgorithmParameterException(
122                     "Unsupported algorithm parameters: " + params);
123         }
124         engineInit(key, random);
125     }
126 
127     @Override
engineDoPhase(Key key, boolean lastPhase)128     protected Key engineDoPhase(Key key, boolean lastPhase)
129             throws InvalidKeyException, IllegalStateException {
130         ensureKeystoreOperationInitialized();
131 
132         if (key == null) {
133             throw new InvalidKeyException("key == null");
134         } else if (!(key instanceof PublicKey)) {
135             throw new InvalidKeyException("Only public keys supported. Key: " + key);
136         } else if (mKey instanceof ECKey && !(key instanceof ECKey)
137                 /*&& !(mKey instanceof XECKey && key instanceof XECKey)*/) {
138         /** TODO This condition is temporary modified, because OpenSSL implementation does not
139          * implement OpenSSLX25519PublicKey from XECKey interface (b/214203951).
140          * This change has to revert once conscrypt implements OpenSSLX25519PublicKey from
141          * XECKey interface.
142          */
143             throw new InvalidKeyException(
144                     "Public and Private key should be of the same type.");
145         } else if (mKey instanceof ECKey
146                 && !((ECKey) key).getParams().getCurve()
147                 .equals(((ECKey) mKey).getParams().getCurve())) {
148             throw new InvalidKeyException(
149                     "Public and Private key parameters should be same.");
150         } else if (!lastPhase) {
151             throw new IllegalStateException(
152                     "Only one other party supported. lastPhase must be set to true.");
153         } else if (mOtherPartyKey != null) {
154             throw new IllegalStateException(
155                     "Only one other party supported. doPhase() must only be called exactly once.");
156         }
157         // The other party key will be passed as part of the doFinal() call, to prevent an
158         // additional IPC.
159         mOtherPartyKey = (PublicKey) key;
160 
161         return null; // No intermediate key
162     }
163 
164     @Override
engineGenerateSecret()165     protected byte[] engineGenerateSecret() throws IllegalStateException {
166         try {
167             ensureKeystoreOperationInitialized();
168         } catch (InvalidKeyException e) {
169             throw new IllegalStateException("Not initialized", e);
170         }
171 
172         if (mOtherPartyKey == null) {
173             throw new IllegalStateException("Other party key not provided. Call doPhase() first.");
174         }
175         byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded();
176 
177         try {
178             return mOperation.finish(otherPartyKeyEncoded, null);
179         } catch (KeyStoreException e) {
180             throw new ProviderException("Keystore operation failed", e);
181         } finally {
182             resetWhilePreservingInitState();
183         }
184     }
185 
186     @Override
engineGenerateSecret(String algorithm)187     protected SecretKey engineGenerateSecret(String algorithm)
188             throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException {
189         byte[] generatedSecret = engineGenerateSecret();
190 
191         return new SecretKeySpec(generatedSecret, algorithm);
192     }
193 
194     @Override
engineGenerateSecret(byte[] sharedSecret, int offset)195     protected int engineGenerateSecret(byte[] sharedSecret, int offset)
196             throws IllegalStateException, ShortBufferException {
197         byte[] generatedSecret = engineGenerateSecret();
198 
199         if (generatedSecret.length > sharedSecret.length - offset) {
200             throw new ShortBufferException("Needed: " + generatedSecret.length);
201         }
202         System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length);
203         return generatedSecret.length;
204     }
205 
206     @Override
getOperationHandle()207     public long getOperationHandle() {
208         return mOperationHandle;
209     }
210 
211     @Override
finalize()212     protected void finalize() throws Throwable {
213         try {
214             resetAll();
215         } finally {
216             super.finalize();
217         }
218     }
219 
resetWhilePreservingInitState()220     private void resetWhilePreservingInitState() {
221         KeyStoreCryptoOperationUtils.abortOperation(mOperation);
222         mOperationHandle = 0;
223         mOperation = null;
224         mOtherPartyKey = null;
225     }
226 
resetAll()227     private void resetAll() {
228         resetWhilePreservingInitState();
229         mKey = null;
230     }
231 
ensureKeystoreOperationInitialized()232     private void ensureKeystoreOperationInitialized()
233             throws InvalidKeyException, IllegalStateException {
234         if (mKey == null) {
235             throw new IllegalStateException("Not initialized");
236         }
237         if (mOperation != null) {
238             return;
239         }
240 
241         // We don't need to explicitly pass in any other parameters here, as they're part of the
242         // private key that is available to Keymint.
243         List<KeyParameter> parameters = new ArrayList<>();
244         parameters.add(KeyStore2ParameterUtils.makeEnum(
245                 Tag.PURPOSE, KeyPurpose.AGREE_KEY
246         ));
247 
248         try {
249             mOperation =
250                     mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters);
251         } catch (KeyStoreException keyStoreException) {
252             // If necessary, throw an exception due to KeyStore operation having failed.
253             InvalidKeyException e =
254                     KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException);
255             if (e != null) {
256                 throw e;
257             }
258         }
259 
260         // Set the operation handle. This will be a random number, or the operation challenge if
261         // user authentication is required. If we got a challenge we check if the authorization can
262         // possibly succeed.
263         mOperationHandle =
264                 KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey);
265     }
266 }
267