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 GCM(int keymasterPadding)61 GCM(int keymasterPadding) { 62 super(KeymasterDefs.KM_MODE_GCM, keymasterPadding); 63 } 64 65 @Override resetAll()66 protected final void resetAll() { 67 mTagLengthBits = DEFAULT_TAG_LENGTH_BITS; 68 super.resetAll(); 69 } 70 71 @Override resetWhilePreservingInitState()72 protected final void resetWhilePreservingInitState() { 73 super.resetWhilePreservingInitState(); 74 } 75 76 @Override initAlgorithmSpecificParameters()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 initAlgorithmSpecificParameters(AlgorithmParameterSpec params)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 initAlgorithmSpecificParameters(AlgorithmParameters params)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 engineGetParameters()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 createMainDataStreamer( KeyStore keyStore, IBinder operationToken)175 protected KeyStoreCryptoOperationStreamer createMainDataStreamer( 176 KeyStore keyStore, IBinder operationToken) { 177 KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer( 178 new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( 179 keyStore, operationToken), 0); 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 createAdditionalAuthenticationDataStreamer( KeyStore keyStore, IBinder operationToken)191 protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( 192 KeyStore keyStore, IBinder operationToken) { 193 return new KeyStoreCryptoOperationChunkedStreamer( 194 new AdditionalAuthenticationDataStream(keyStore, operationToken), 0); 195 } 196 197 @Override getAdditionalEntropyAmountForBegin()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 getAdditionalEntropyAmountForFinish()208 protected final int getAdditionalEntropyAmountForFinish() { 209 return 0; 210 } 211 212 @Override addAlgorithmSpecificParametersToBegin( @onNull KeymasterArguments keymasterArgs)213 protected final void addAlgorithmSpecificParametersToBegin( 214 @NonNull KeymasterArguments keymasterArgs) { 215 super.addAlgorithmSpecificParametersToBegin(keymasterArgs); 216 keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mTagLengthBits); 217 } 218 getTagLengthBits()219 protected final int getTagLengthBits() { 220 return mTagLengthBits; 221 } 222 223 public static final class NoPadding extends GCM { NoPadding()224 public NoPadding() { 225 super(KeymasterDefs.KM_PAD_NONE); 226 } 227 228 @Override engineGetOutputSize(int inputLen)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 AndroidKeyStoreAuthenticatedAESCipherSpi( int keymasterBlockMode, int keymasterPadding)259 AndroidKeyStoreAuthenticatedAESCipherSpi( 260 int keymasterBlockMode, 261 int keymasterPadding) { 262 mKeymasterBlockMode = keymasterBlockMode; 263 mKeymasterPadding = keymasterPadding; 264 } 265 266 @Override resetAll()267 protected void resetAll() { 268 mIv = null; 269 mIvHasBeenUsed = false; 270 super.resetAll(); 271 } 272 273 @Override initKey(int opmode, Key key)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 addAlgorithmSpecificParametersToBegin( @onNull KeymasterArguments keymasterArgs)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 loadAlgorithmSpecificParametersFromBeginResult( @onNull KeymasterArguments keymasterArgs)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 engineGetBlockSize()324 protected final int engineGetBlockSize() { 325 return BLOCK_SIZE_BYTES; 326 } 327 328 @Override engineGetIV()329 protected final byte[] engineGetIV() { 330 return ArrayUtils.cloneIfNotEmpty(mIv); 331 } 332 setIv(byte[] iv)333 protected void setIv(byte[] iv) { 334 mIv = iv; 335 } 336 getIv()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 BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate)352 private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) { 353 mDelegate = delegate; 354 } 355 356 @Override update(byte[] input, int inputOffset, int inputLength)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 doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature, byte[] additionalEntropy)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 getConsumedInputSizeBytes()389 public long getConsumedInputSizeBytes() { 390 return mDelegate.getConsumedInputSizeBytes(); 391 } 392 393 @Override getProducedOutputSizeBytes()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 AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken)408 private AdditionalAuthenticationDataStream(KeyStore keyStore, IBinder operationToken) { 409 mKeyStore = keyStore; 410 mOperationToken = operationToken; 411 } 412 413 @Override update(byte[] input)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 finish(byte[] input, byte[] signature, byte[] additionalEntropy)435 public OperationResult finish(byte[] input, 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 }