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