1 /*
2  * Copyright (C) 2015 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.KeyParameter;
20 import android.security.KeyStoreException;
21 import android.security.KeyStoreOperation;
22 import android.security.keymaster.KeymasterDefs;
23 import android.security.keystore.KeyStoreCryptoOperation;
24 
25 import java.security.InvalidAlgorithmParameterException;
26 import java.security.InvalidKeyException;
27 import java.security.Key;
28 import java.security.ProviderException;
29 import java.security.spec.AlgorithmParameterSpec;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 import javax.crypto.MacSpi;
34 
35 /**
36  * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
37  *
38  * @hide
39  */
40 public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
41 
42     private static final String TAG = "AndroidKeyStoreHmacSpi";
43 
44     public static class HmacSHA1 extends AndroidKeyStoreHmacSpi {
HmacSHA1()45         public HmacSHA1() {
46             super(KeymasterDefs.KM_DIGEST_SHA1);
47         }
48     }
49 
50     public static class HmacSHA224 extends AndroidKeyStoreHmacSpi {
HmacSHA224()51         public HmacSHA224() {
52             super(KeymasterDefs.KM_DIGEST_SHA_2_224);
53         }
54     }
55 
56     public static class HmacSHA256 extends AndroidKeyStoreHmacSpi {
HmacSHA256()57         public HmacSHA256() {
58             super(KeymasterDefs.KM_DIGEST_SHA_2_256);
59         }
60     }
61 
62     public static class HmacSHA384 extends AndroidKeyStoreHmacSpi {
HmacSHA384()63         public HmacSHA384() {
64             super(KeymasterDefs.KM_DIGEST_SHA_2_384);
65         }
66     }
67 
68     public static class HmacSHA512 extends AndroidKeyStoreHmacSpi {
HmacSHA512()69         public HmacSHA512() {
70             super(KeymasterDefs.KM_DIGEST_SHA_2_512);
71         }
72     }
73 
74     private final int mKeymasterDigest;
75     private final int mMacSizeBits;
76 
77     // Fields below are populated by engineInit and should be preserved after engineDoFinal.
78     private AndroidKeyStoreSecretKey mKey;
79 
80     // Fields below are reset when engineDoFinal succeeds.
81     private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
82     private KeyStoreOperation mOperation;
83     private long mOperationChallenge;
84 
AndroidKeyStoreHmacSpi(int keymasterDigest)85     protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
86         mKeymasterDigest = keymasterDigest;
87         mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
88         mOperation = null;
89         mOperationChallenge = 0;
90         mKey = null;
91         mChunkedStreamer = null;
92     }
93 
94     @Override
engineGetMacLength()95     protected int engineGetMacLength() {
96         return (mMacSizeBits + 7) / 8;
97     }
98 
99     @Override
engineInit(Key key, AlgorithmParameterSpec params)100     protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
101             InvalidAlgorithmParameterException {
102         resetAll();
103 
104         boolean success = false;
105         try {
106             init(key, params);
107             ensureKeystoreOperationInitialized();
108             success = true;
109         } finally {
110             if (!success) {
111                 resetAll();
112             }
113         }
114     }
115 
init(Key key, AlgorithmParameterSpec params)116     private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
117         InvalidAlgorithmParameterException {
118         if (key == null) {
119             throw new InvalidKeyException("key == null");
120         } else if (!(key instanceof AndroidKeyStoreSecretKey)) {
121             throw new InvalidKeyException(
122                     "Only Android KeyStore secret keys supported. Key: " + key);
123         }
124         mKey = (AndroidKeyStoreSecretKey) key;
125 
126         if (params != null) {
127             throw new InvalidAlgorithmParameterException(
128                     "Unsupported algorithm parameters: " + params);
129         }
130 
131     }
132 
abortOperation()133     private void abortOperation() {
134         KeyStoreCryptoOperationUtils.abortOperation(mOperation);
135         mOperation = null;
136     }
137 
resetAll()138     private void resetAll() {
139         abortOperation();
140         mOperationChallenge = 0;
141         mKey = null;
142         mChunkedStreamer = null;
143     }
144 
resetWhilePreservingInitState()145     private void resetWhilePreservingInitState() {
146         abortOperation();
147         mOperationChallenge = 0;
148         mChunkedStreamer = null;
149     }
150 
151     @Override
engineReset()152     protected void engineReset() {
153         resetWhilePreservingInitState();
154     }
155 
ensureKeystoreOperationInitialized()156     private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
157         if (mChunkedStreamer != null) {
158             return;
159         }
160         if (mKey == null) {
161             throw new IllegalStateException("Not initialized");
162         }
163 
164         List<KeyParameter> parameters = new ArrayList<>();
165         parameters.add(KeyStore2ParameterUtils.makeEnum(
166                 KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN
167         ));
168         parameters.add(KeyStore2ParameterUtils.makeEnum(
169                 KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC
170         ));
171         parameters.add(KeyStore2ParameterUtils.makeEnum(
172                 KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
173         ));
174         parameters.add(KeyStore2ParameterUtils.makeInt(
175                 KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits
176         ));
177 
178         try {
179             mOperation = mKey.getSecurityLevel().createOperation(
180                     mKey.getKeyIdDescriptor(),
181                     parameters
182             );
183         } catch (KeyStoreException keyStoreException) {
184             // If necessary, throw an exception due to KeyStore operation having failed.
185             InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyException(
186                     mKey, keyStoreException);
187             if (e != null) {
188                 throw e;
189             }
190         }
191 
192         // Now we check if we got an operation challenge. This indicates that user authorization
193         // is required. And if we got a challenge we check if the authorization can possibly
194         // succeed.
195         mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
196                 mOperation, mKey);
197 
198         mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
199                 new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
200                         mOperation));
201     }
202 
203     @Override
engineUpdate(byte input)204     protected void engineUpdate(byte input) {
205         engineUpdate(new byte[] {input}, 0, 1);
206     }
207 
208     @Override
engineUpdate(byte[] input, int offset, int len)209     protected void engineUpdate(byte[] input, int offset, int len) {
210         try {
211             ensureKeystoreOperationInitialized();
212         } catch (InvalidKeyException e) {
213             throw new ProviderException("Failed to reinitialize MAC", e);
214         }
215 
216         byte[] output;
217         try {
218             output = mChunkedStreamer.update(input, offset, len);
219         } catch (KeyStoreException e) {
220             throw new ProviderException("Keystore operation failed", e);
221         }
222         if ((output != null) && (output.length != 0)) {
223             throw new ProviderException("Update operation unexpectedly produced output");
224         }
225     }
226 
227     @Override
engineDoFinal()228     protected byte[] engineDoFinal() {
229         try {
230             ensureKeystoreOperationInitialized();
231         } catch (InvalidKeyException e) {
232             throw new ProviderException("Failed to reinitialize MAC", e);
233         }
234 
235         byte[] result;
236         try {
237             result = mChunkedStreamer.doFinal(
238                     null, 0, 0,
239                     null); // no signature provided -- this invocation will generate one
240         } catch (KeyStoreException e) {
241             throw new ProviderException("Keystore operation failed", e);
242         }
243 
244         resetWhilePreservingInitState();
245         return result;
246     }
247 
248     @Override
finalize()249     public void finalize() throws Throwable {
250         try {
251             abortOperation();
252         } finally {
253             super.finalize();
254         }
255     }
256 
257     @Override
getOperationHandle()258     public long getOperationHandle() {
259         return mOperationChallenge;
260     }
261 }
262