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.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.os.IBinder;
     22 import android.security.KeyStore;
     23 import android.security.KeyStoreException;
     24 import android.security.keymaster.KeymasterArguments;
     25 import android.security.keymaster.KeymasterDefs;
     26 import android.security.keymaster.OperationResult;
     27 import android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.Stream;
     28 
     29 import libcore.util.EmptyArray;
     30 
     31 import java.io.ByteArrayOutputStream;
     32 import java.io.IOException;
     33 import java.security.AlgorithmParameters;
     34 import java.security.InvalidAlgorithmParameterException;
     35 import java.security.InvalidKeyException;
     36 import java.security.Key;
     37 import java.security.NoSuchAlgorithmException;
     38 import java.security.ProviderException;
     39 import java.security.spec.AlgorithmParameterSpec;
     40 import java.security.spec.InvalidParameterSpecException;
     41 import java.util.Arrays;
     42 
     43 import javax.crypto.CipherSpi;
     44 import javax.crypto.spec.GCMParameterSpec;
     45 
     46 /**
     47  * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
     48  *
     49  * @hide
     50  */
     51 abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
     52 
     53     abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
     54         static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
     55         private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
     56         private static final int DEFAULT_TAG_LENGTH_BITS = 128;
     57         private static final int IV_LENGTH_BYTES = 12;
     58 
     59         private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
     60 
     61         GCM(int keymasterPadding) {
     62             super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
     63         }
     64 
     65         @Override
     66         protected final void resetAll() {
     67             mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
     68             super.resetAll();
     69         }
     70 
     71         @Override
     72         protected final void resetWhilePreservingInitState() {
     73             super.resetWhilePreservingInitState();
     74         }
     75 
     76         @Override
     77         protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
     78             if (!isEncrypting()) {
     79                 throw new InvalidKeyException("IV required when decrypting"
     80                         + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
     81             }
     82         }
     83 
     84         @Override
     85         protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
     86                 throws InvalidAlgorithmParameterException {
     87             // IV is used
     88             if (params == null) {
     89                 if (!isEncrypting()) {
     90                     // IV must be provided by the caller
     91                     throw new InvalidAlgorithmParameterException(
     92                             "GCMParameterSpec must be provided when decrypting");
     93                 }
     94                 return;
     95             }
     96             if (!(params instanceof GCMParameterSpec)) {
     97                 throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
     98             }
     99             GCMParameterSpec spec = (GCMParameterSpec) params;
    100             byte[] iv = spec.getIV();
    101             if (iv == null) {
    102                 throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
    103             } else if (iv.length != IV_LENGTH_BYTES) {
    104                 throw new InvalidAlgorithmParameterException("Unsupported IV length: "
    105                         + iv.length + " bytes. Only " + IV_LENGTH_BYTES
    106                         + " bytes long IV supported");
    107             }
    108             int tagLengthBits = spec.getTLen();
    109             if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
    110                     || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
    111                     || ((tagLengthBits % 8) != 0)) {
    112                 throw new InvalidAlgorithmParameterException(
    113                         "Unsupported tag length: " + tagLengthBits + " bits"
    114                         + ". Supported lengths: 96, 104, 112, 120, 128");
    115             }
    116             setIv(iv);
    117             mTagLengthBits = tagLengthBits;
    118         }
    119 
    120         @Override
    121         protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
    122                 throws InvalidAlgorithmParameterException {
    123             if (params == null) {
    124                 if (!isEncrypting()) {
    125                     // IV must be provided by the caller
    126                     throw new InvalidAlgorithmParameterException("IV required when decrypting"
    127                             + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
    128                 }
    129                 return;
    130             }
    131 
    132             if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) {
    133                 throw new InvalidAlgorithmParameterException(
    134                         "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
    135                         + ". Supported: GCM");
    136             }
    137 
    138             GCMParameterSpec spec;
    139             try {
    140                 spec = params.getParameterSpec(GCMParameterSpec.class);
    141             } catch (InvalidParameterSpecException e) {
    142                 if (!isEncrypting()) {
    143                     // IV must be provided by the caller
    144                     throw new InvalidAlgorithmParameterException("IV and tag length required when"
    145                             + " decrypting, but not found in parameters: " + params, e);
    146                 }
    147                 setIv(null);
    148                 return;
    149             }
    150             initAlgorithmSpecificParameters(spec);
    151         }
    152 
    153         @Nullable
    154         @Override
    155         protected final AlgorithmParameters engineGetParameters() {
    156             byte[] iv = getIv();
    157             if ((iv != null) && (iv.length > 0)) {
    158                 try {
    159                     AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
    160                     params.init(new GCMParameterSpec(mTagLengthBits, iv));
    161                     return params;
    162                 } catch (NoSuchAlgorithmException e) {
    163                     throw new ProviderException(
    164                             "Failed to obtain GCM AlgorithmParameters", e);
    165                 } catch (InvalidParameterSpecException e) {
    166                     throw new ProviderException(
    167                             "Failed to initialize GCM AlgorithmParameters", e);
    168                 }
    169             }
    170             return null;
    171         }
    172 
    173         @NonNull
    174         @Override
    175         protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
    176                 KeyStore keyStore, IBinder operationToken) {
    177             KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
    178                     new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
    179                             keyStore, operationToken));
    180             if (isEncrypting()) {
    181                 return streamer;
    182             } else {
    183                 // When decrypting, to avoid leaking unauthenticated plaintext, do not return any
    184                 // plaintext before ciphertext is authenticated by KeyStore.finish.
    185                 return new BufferAllOutputUntilDoFinalStreamer(streamer);
    186             }
    187         }
    188 
    189         @NonNull
    190         @Override
    191         protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
    192                 KeyStore keyStore, IBinder operationToken) {
    193             return new KeyStoreCryptoOperationChunkedStreamer(
    194                     new AdditionalAuthenticationDataStream(keyStore, operationToken));
    195         }
    196 
    197         @Override
    198         protected final int getAdditionalEntropyAmountForBegin() {
    199             if ((getIv() == null) && (isEncrypting())) {
    200                 // IV will need to be generated
    201                 return IV_LENGTH_BYTES;
    202             }
    203 
    204             return 0;
    205         }
    206 
    207         @Override
    208         protected final int getAdditionalEntropyAmountForFinish() {
    209             return 0;
    210         }
    211 
    212         @Override
    213         protected final void addAlgorithmSpecificParametersToBegin(
    214                 @NonNull KeymasterArguments keymasterArgs) {
    215             super.addAlgorithmSpecificParametersToBegin(keymasterArgs);
    216             keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits);
    217         }
    218 
    219         protected final int getTagLengthBits() {
    220             return mTagLengthBits;
    221         }
    222 
    223         public static final class NoPadding extends GCM {
    224             public NoPadding() {
    225                 super(KeymasterDefs.KM_PAD_NONE);
    226             }
    227 
    228             @Override
    229             protected final int engineGetOutputSize(int inputLen) {
    230                 int tagLengthBytes = (getTagLengthBits() + 7) / 8;
    231                 long result;
    232                 if (isEncrypting()) {
    233                     result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
    234                             + tagLengthBytes;
    235                 } else {
    236                     result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
    237                             - tagLengthBytes;
    238                 }
    239                 if (result < 0) {
    240                     return 0;
    241                 } else if (result > Integer.MAX_VALUE) {
    242                     return Integer.MAX_VALUE;
    243                 }
    244                 return (int) result;
    245             }
    246         }
    247     }
    248 
    249     private static final int BLOCK_SIZE_BYTES = 16;
    250 
    251     private final int mKeymasterBlockMode;
    252     private final int mKeymasterPadding;
    253 
    254     private byte[] mIv;
    255 
    256     /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
    257     private boolean mIvHasBeenUsed;
    258 
    259     AndroidKeyStoreAuthenticatedAESCipherSpi(
    260             int keymasterBlockMode,
    261             int keymasterPadding) {
    262         mKeymasterBlockMode = keymasterBlockMode;
    263         mKeymasterPadding = keymasterPadding;
    264     }
    265 
    266     @Override
    267     protected void resetAll() {
    268         mIv = null;
    269         mIvHasBeenUsed = false;
    270         super.resetAll();
    271     }
    272 
    273     @Override
    274     protected final void initKey(int opmode, Key key) throws InvalidKeyException {
    275         if (!(key instanceof AndroidKeyStoreSecretKey)) {
    276             throw new InvalidKeyException(
    277                     "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
    278         }
    279         if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
    280             throw new InvalidKeyException(
    281                     "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
    282                     KeyProperties.KEY_ALGORITHM_AES + " supported");
    283         }
    284         setKey((AndroidKeyStoreSecretKey) key);
    285     }
    286 
    287     @Override
    288     protected void addAlgorithmSpecificParametersToBegin(
    289             @NonNull KeymasterArguments keymasterArgs) {
    290         if ((isEncrypting()) && (mIvHasBeenUsed)) {
    291             // IV is being reused for encryption: this violates security best practices.
    292             throw new IllegalStateException(
    293                     "IV has already been used. Reusing IV in encryption mode violates security best"
    294                     + " practices.");
    295         }
    296 
    297         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
    298         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
    299         keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
    300         if (mIv != null) {
    301             keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
    302         }
    303     }
    304 
    305     @Override
    306     protected final void loadAlgorithmSpecificParametersFromBeginResult(
    307             @NonNull KeymasterArguments keymasterArgs) {
    308         mIvHasBeenUsed = true;
    309 
    310         // NOTE: Keymaster doesn't always return an IV, even if it's used.
    311         byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
    312         if ((returnedIv != null) && (returnedIv.length == 0)) {
    313             returnedIv = null;
    314         }
    315 
    316         if (mIv == null) {
    317             mIv = returnedIv;
    318         } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
    319             throw new ProviderException("IV in use differs from provided IV");
    320         }
    321     }
    322 
    323     @Override
    324     protected final int engineGetBlockSize() {
    325         return BLOCK_SIZE_BYTES;
    326     }
    327 
    328     @Override
    329     protected final byte[] engineGetIV() {
    330         return ArrayUtils.cloneIfNotEmpty(mIv);
    331     }
    332 
    333     protected void setIv(byte[] iv) {
    334         mIv = iv;
    335     }
    336 
    337     protected byte[] getIv() {
    338         return mIv;
    339     }
    340 
    341     /**
    342      * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
    343      * which it returns all output in one go, provided {@code doFinal} succeeds.
    344      */
    345     private static class BufferAllOutputUntilDoFinalStreamer
    346         implements KeyStoreCryptoOperationStreamer {
    347 
    348         private final KeyStoreCryptoOperationStreamer mDelegate;
    349         private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
    350         private long mProducedOutputSizeBytes;
    351 
    352         private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
    353             mDelegate = delegate;
    354         }
    355 
    356         @Override
    357         public byte[] update(byte[] input, int inputOffset, int inputLength)
    358                 throws KeyStoreException {
    359             byte[] output = mDelegate.update(input, inputOffset, inputLength);
    360             if (output != null) {
    361                 try {
    362                     mBufferedOutput.write(output);
    363                 } catch (IOException e) {
    364                     throw new ProviderException("Failed to buffer output", e);
    365                 }
    366             }
    367             return EmptyArray.BYTE;
    368         }
    369 
    370         @Override
    371         public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
    372                 byte[] signature, byte[] additionalEntropy) throws KeyStoreException {
    373             byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature,
    374                     additionalEntropy);
    375             if (output != null) {
    376                 try {
    377                     mBufferedOutput.write(output);
    378                 } catch (IOException e) {
    379                     throw new ProviderException("Failed to buffer output", e);
    380                 }
    381             }
    382             byte[] result = mBufferedOutput.toByteArray();
    383             mBufferedOutput.reset();
    384             mProducedOutputSizeBytes += result.length;
    385             return result;
    386         }
    387 
    388         @Override
    389         public long getConsumedInputSizeBytes() {
    390             return mDelegate.getConsumedInputSizeBytes();
    391         }
    392 
    393         @Override
    394         public long getProducedOutputSizeBytes() {
    395             return mProducedOutputSizeBytes;
    396         }
    397     }
    398 
    399     /**
    400      * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
    401      * sends AAD into the KeyStore.
    402      */
    403     private static class AdditionalAuthenticationDataStream implements Stream {
    404 
    405         private final KeyStore mKeyStore;
    406         private final IBinder mOperationToken;
    407 
    408         private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) {
    409             mKeyStore = keyStore;
    410             mOperationToken = operationToken;
    411         }
    412 
    413         @Override
    414         public OperationResult update(byte[] input) {
    415             KeymasterArguments keymasterArgs = new KeymasterArguments();
    416             keymasterArgs.addBytes(KeymasterDefs.KM_TAG_ASSOCIATED_DATA, input);
    417 
    418             // KeyStore does not reflect AAD in inputConsumed, but users of Stream rely on this
    419             // field. We fix this discrepancy here. KeyStore.update contract is that all of AAD
    420             // has been consumed if the method succeeds.
    421             OperationResult result = mKeyStore.update(mOperationToken, keymasterArgs, null);
    422             if (result.resultCode == KeyStore.NO_ERROR) {
    423                 result = new OperationResult(
    424                         result.resultCode,
    425                         result.token,
    426                         result.operationHandle,
    427                         input.length, // inputConsumed
    428                         result.output,
    429                         result.outParams);
    430             }
    431             return result;
    432         }
    433 
    434         @Override
    435         public OperationResult finish(byte[] signature, byte[] additionalEntropy) {
    436             if ((additionalEntropy != null) && (additionalEntropy.length > 0)) {
    437                 throw new ProviderException("AAD stream does not support additional entropy");
    438             }
    439             return new OperationResult(
    440                     KeyStore.NO_ERROR,
    441                     mOperationToken,
    442                     0, // operation handle -- nobody cares about this being returned from finish
    443                     0, // inputConsumed
    444                     EmptyArray.BYTE, // output
    445                     new KeymasterArguments() // additional params returned by finish
    446                     );
    447         }
    448     }
    449 }