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