1 /* 2 * Copyright (C) 2019 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 org.conscrypt; 18 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.InvocationTargetException; 21 import java.nio.ByteBuffer; 22 import java.security.InvalidAlgorithmParameterException; 23 import java.security.InvalidKeyException; 24 import java.security.SecureRandom; 25 import java.security.spec.AlgorithmParameterSpec; 26 import java.util.Arrays; 27 import javax.crypto.BadPaddingException; 28 import javax.crypto.IllegalBlockSizeException; 29 import javax.crypto.NoSuchPaddingException; 30 import javax.crypto.ShortBufferException; 31 import javax.crypto.spec.IvParameterSpec; 32 33 @Internal 34 public abstract class OpenSSLAeadCipher extends OpenSSLCipher { 35 /** 36 * Controls whether no-copy optimizations for direct ByteBuffers are enabled. 37 */ 38 private static final boolean ENABLE_BYTEBUFFER_OPTIMIZATIONS = true; 39 40 /** 41 * The default tag size when one is not specified. Default to 42 * full-length tags (128-bits or 16 octets). 43 */ 44 static final int DEFAULT_TAG_SIZE_BITS = 16 * 8; 45 46 /** 47 * Keeps track of the last used block size. 48 */ 49 private static int lastGlobalMessageSize = 32; 50 51 /** 52 * The previously used key to prevent key + nonce (IV) reuse. 53 */ 54 private byte[] previousKey; 55 56 /** 57 * The previously used nonce (IV) to prevent key + nonce reuse. 58 */ 59 private byte[] previousIv; 60 61 /** 62 * When set this instance must be initialized before use again. This prevents key 63 * and IV reuse. 64 */ 65 private boolean mustInitialize; 66 67 /** 68 * The byte array containing the bytes written. 69 */ 70 byte[] buf; 71 72 /** 73 * The number of bytes written. 74 */ 75 int bufCount; 76 77 /** 78 * AEAD cipher reference. 79 */ 80 long evpAead; 81 82 /** 83 * Additional authenticated data. 84 */ 85 private byte[] aad; 86 87 /** 88 * The length of the AEAD cipher tag in bytes. 89 */ 90 int tagLengthInBytes; 91 OpenSSLAeadCipher(Mode mode)92 protected OpenSSLAeadCipher(Mode mode) { 93 super(mode, Padding.NOPADDING); 94 } 95 checkInitialization()96 private void checkInitialization() { 97 if (mustInitialize) { 98 throw new IllegalStateException( 99 "Cannot re-use same key and IV for multiple encryptions"); 100 } 101 } 102 103 /** Constant-time array comparison. Since we are using this to compare keys, we want to 104 * ensure there's no opportunity for a timing attack. */ arraysAreEqual(byte[] a, byte[] b)105 private boolean arraysAreEqual(byte[] a, byte[] b) { 106 if (a.length != b.length) { 107 return false; 108 } 109 110 int diff = 0; 111 for (int i = 0; i < a.length; i++) { 112 diff |= a[i] ^ b[i]; 113 } 114 return diff == 0; 115 } 116 expand(int i)117 private void expand(int i) { 118 /* Can the buffer handle i more bytes, if not expand it */ 119 if (bufCount + i <= buf.length) { 120 return; 121 } 122 123 byte[] newbuf = new byte[(bufCount + i) * 2]; 124 System.arraycopy(buf, 0, newbuf, 0, bufCount); 125 buf = newbuf; 126 } 127 reset()128 private void reset() { 129 aad = null; 130 final int lastBufSize = lastGlobalMessageSize; 131 if (buf == null) { 132 buf = new byte[lastBufSize]; 133 } else if (bufCount > 0 && bufCount != lastBufSize) { 134 lastGlobalMessageSize = bufCount; 135 if (buf.length != bufCount) { 136 buf = new byte[bufCount]; 137 } 138 } 139 bufCount = 0; 140 } 141 142 @Override engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, SecureRandom random)143 void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, 144 SecureRandom random) throws InvalidKeyException, 145 InvalidAlgorithmParameterException { 146 byte[] iv; 147 final int tagLenBits; 148 if (params == null) { 149 iv = null; 150 tagLenBits = DEFAULT_TAG_SIZE_BITS; 151 } else { 152 GCMParameters gcmParams = Platform.fromGCMParameterSpec(params); 153 if (gcmParams != null) { 154 iv = gcmParams.getIV(); 155 tagLenBits = gcmParams.getTLen(); 156 } else if (params instanceof IvParameterSpec) { 157 IvParameterSpec ivParams = (IvParameterSpec) params; 158 iv = ivParams.getIV(); 159 tagLenBits = DEFAULT_TAG_SIZE_BITS; 160 } else { 161 iv = null; 162 tagLenBits = DEFAULT_TAG_SIZE_BITS; 163 } 164 } 165 166 checkSupportedTagLength(tagLenBits); 167 168 tagLengthInBytes = tagLenBits / 8; 169 170 final boolean encrypting = isEncrypting(); 171 172 evpAead = getEVP_AEAD(encodedKey.length); 173 174 final int expectedIvLength = NativeCrypto.EVP_AEAD_nonce_length(evpAead); 175 if (iv == null && expectedIvLength != 0) { 176 if (!encrypting) { 177 throw new InvalidAlgorithmParameterException("IV must be specified in " + mode 178 + " mode"); 179 } 180 181 iv = new byte[expectedIvLength]; 182 if (random != null) { 183 random.nextBytes(iv); 184 } else { 185 NativeCrypto.RAND_bytes(iv); 186 } 187 } else if (expectedIvLength == 0 && iv != null) { 188 throw new InvalidAlgorithmParameterException("IV not used in " + mode + " mode"); 189 } else if (iv != null && iv.length != expectedIvLength) { 190 throw new InvalidAlgorithmParameterException("Expected IV length of " 191 + expectedIvLength + " but was " + iv.length); 192 } 193 194 if (isEncrypting() && iv != null && !allowsNonceReuse()) { 195 if (previousKey != null && previousIv != null 196 && arraysAreEqual(previousKey, encodedKey) 197 && arraysAreEqual(previousIv, iv)) { 198 mustInitialize = true; 199 throw new InvalidAlgorithmParameterException( 200 "When using AEAD key and IV must not be re-used"); 201 } 202 203 this.previousKey = encodedKey; 204 this.previousIv = iv; 205 } 206 mustInitialize = false; 207 this.iv = iv; 208 reset(); 209 } 210 checkSupportedTagLength(int tagLenBits)211 void checkSupportedTagLength(int tagLenBits) 212 throws InvalidAlgorithmParameterException { 213 if (tagLenBits % 8 != 0) { 214 throw new InvalidAlgorithmParameterException( 215 "Tag length must be a multiple of 8; was " + tagLenBits); 216 } 217 } 218 219 /** 220 * Returns whether reusing nonces is allowed (aka, whether this is nonce misuse-resistant). 221 * Most AEAD ciphers are not, but some are specially constructed so that reusing a key/nonce 222 * pair is safe. 223 */ allowsNonceReuse()224 boolean allowsNonceReuse() { 225 return false; 226 } 227 228 @Override engineDoFinal(ByteBuffer input, ByteBuffer output)229 protected int engineDoFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, 230 IllegalBlockSizeException, BadPaddingException { 231 if (!ENABLE_BYTEBUFFER_OPTIMIZATIONS) { 232 return super.engineDoFinal(input, output); 233 } 234 if (input == null || output == null) { 235 throw new NullPointerException("Null ByteBuffer Error"); 236 } 237 if (getOutputSizeForFinal(input.remaining()) > output.remaining()) { 238 throw new ShortBufferWithoutStackTraceException("Insufficient Bytes for Output Buffer"); 239 } 240 if (output.isReadOnly()) { 241 throw new IllegalArgumentException("Cannot write to Read Only ByteBuffer"); 242 } 243 if (bufCount != 0) { 244 return super.engineDoFinal(input, output); // traditional case 245 } 246 int bytesWritten; 247 if (!input.isDirect()) { 248 int incap = input.remaining(); 249 ByteBuffer inputClone = ByteBuffer.allocateDirect(incap); 250 inputClone.mark(); 251 inputClone.put(input); 252 inputClone.reset(); 253 input = inputClone; 254 } 255 if (!output.isDirect()) { 256 ByteBuffer outputClone = ByteBuffer.allocateDirect( 257 getOutputSizeForFinal(input.remaining())); 258 bytesWritten = doFinalInternal(input, outputClone); 259 output.put(outputClone); 260 input.position(input.limit()); // API reasons 261 } 262 else { 263 bytesWritten = doFinalInternal(input, output); 264 output.position(output.position() + bytesWritten); 265 input.position(input.limit()); // API reasons 266 } 267 268 return bytesWritten; 269 } 270 271 @Override engineDoFinal(byte[] input, int inputOffset, int inputLen)272 protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) 273 throws IllegalBlockSizeException, BadPaddingException { 274 final int maximumLen = getOutputSizeForFinal(inputLen); 275 /* Assume that we'll output exactly on a byte boundary. */ 276 final byte[] output = new byte[maximumLen]; 277 278 int bytesWritten; 279 try { 280 bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0); 281 } catch (ShortBufferException e) { 282 /* This should not happen since we sized our own buffer. */ 283 throw new RuntimeException("our calculated buffer was too small", e); 284 } 285 286 if (bytesWritten == output.length) { 287 return output; 288 } else if (bytesWritten == 0) { 289 return EmptyArray.BYTE; 290 } else { 291 return Arrays.copyOf(output, bytesWritten); 292 } 293 } 294 295 @Override engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)296 protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, 297 int outputOffset) throws ShortBufferException, IllegalBlockSizeException, 298 BadPaddingException { 299 if (output == null) { 300 throw new NullPointerException("output == null"); 301 } 302 if (getOutputSizeForFinal(inputLen) > output.length - outputOffset) { 303 throw new ShortBufferWithoutStackTraceException("Insufficient output space"); 304 } 305 return doFinalInternal(input, inputOffset, inputLen, output, outputOffset); 306 } 307 appendToBuf(byte[] input, int inputOffset, int inputLen)308 void appendToBuf(byte[] input, int inputOffset, int inputLen) { 309 if (buf == null) { 310 throw new IllegalStateException("Cipher not initialized"); 311 } 312 313 ArrayUtils.checkOffsetAndCount(input.length, inputOffset, inputLen); 314 if (inputLen > 0) { 315 expand(inputLen); 316 System.arraycopy(input, inputOffset, buf, this.bufCount, inputLen); 317 this.bufCount += inputLen; 318 } 319 } 320 321 @Override updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset, int maximumLen)322 int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, 323 int outputOffset, int maximumLen) throws ShortBufferException { 324 checkInitialization(); 325 appendToBuf(input, inputOffset, inputLen); 326 return 0; 327 } 328 329 @SuppressWarnings("LiteralClassName") throwAEADBadTagExceptionIfAvailable(String message, Throwable cause)330 private void throwAEADBadTagExceptionIfAvailable(String message, Throwable cause) 331 throws BadPaddingException { 332 Constructor<?> aeadBadTagConstructor; 333 try { 334 aeadBadTagConstructor = Class.forName("javax.crypto.AEADBadTagException") 335 .getConstructor(String.class); 336 } catch (Exception ignored) { 337 return; 338 } 339 340 BadPaddingException badTagException = null; 341 try { 342 badTagException = (BadPaddingException) aeadBadTagConstructor.newInstance(message); 343 badTagException.initCause(cause); 344 } catch (IllegalAccessException e2) { 345 // Fall through 346 } catch (InstantiationException e2) { 347 // Fall through 348 } catch (InvocationTargetException e2) { 349 throw(BadPaddingException) new BadPaddingException().initCause( 350 e2.getTargetException()); 351 } 352 if (badTagException != null) { 353 throw badTagException; 354 } 355 } 356 doFinalInternal(ByteBuffer input, ByteBuffer output)357 int doFinalInternal(ByteBuffer input, ByteBuffer output) 358 throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { 359 checkInitialization(); 360 final int bytesWritten; 361 try { 362 if (isEncrypting()) { 363 bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal_buf( 364 evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad); 365 } else { 366 bytesWritten = NativeCrypto.EVP_AEAD_CTX_open_buf( 367 evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad); 368 } 369 } catch (BadPaddingException e) { 370 throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause()); 371 throw e; 372 } 373 if (isEncrypting()) { 374 mustInitialize = true; 375 } 376 return bytesWritten; 377 } 378 doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)379 int doFinalInternal(byte[] input, int inputOffset, int inputLen, 380 byte[] output, int outputOffset) 381 throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { 382 checkInitialization(); 383 384 byte[] in; 385 int inOffset; 386 int inLen; 387 if (bufCount > 0) { 388 if (inputLen > 0) { 389 appendToBuf(input, inputOffset, inputLen); 390 } 391 in = buf; 392 inOffset = 0; 393 inLen = bufCount; 394 } else { 395 if (inputLen == 0 && input == null) { 396 in = EmptyArray.BYTE; // input can be null when inputLen == 0 397 } else { 398 in = input; 399 } 400 inOffset = inputOffset; 401 inLen = inputLen; 402 } 403 404 final int bytesWritten; 405 try { 406 if (isEncrypting()) { 407 bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal(evpAead, encodedKey, 408 tagLengthInBytes, output, outputOffset, iv, in, inOffset, inLen, aad); 409 } else { 410 bytesWritten = NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey, 411 tagLengthInBytes, output, outputOffset, iv, in, inOffset, inLen, aad); 412 } 413 } catch (BadPaddingException e) { 414 throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause()); 415 throw e; 416 } 417 if (isEncrypting()) { 418 mustInitialize = true; 419 } 420 reset(); 421 return bytesWritten; 422 } 423 424 @Override checkSupportedPadding(Padding padding)425 void checkSupportedPadding(Padding padding) throws NoSuchPaddingException { 426 if (padding != Padding.NOPADDING) { 427 throw new NoSuchPaddingException("Must be NoPadding for AEAD ciphers"); 428 } 429 } 430 431 /** 432 * AEAD buffers everything until a final output. 433 */ 434 @Override getOutputSizeForUpdate(int inputLen)435 int getOutputSizeForUpdate(int inputLen) { 436 return 0; 437 } 438 439 // Intentionally missing Override to compile on old versions of Android 440 @SuppressWarnings("MissingOverride") engineUpdateAAD(byte[] input, int inputOffset, int inputLen)441 protected void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { 442 checkInitialization(); 443 if (aad == null) { 444 aad = Arrays.copyOfRange(input, inputOffset, inputOffset + inputLen); 445 } else { 446 int newSize = aad.length + inputLen; 447 byte[] newaad = new byte[newSize]; 448 System.arraycopy(aad, 0, newaad, 0, aad.length); 449 System.arraycopy(input, inputOffset, newaad, aad.length, inputLen); 450 aad = newaad; 451 } 452 } 453 454 // Intentionally missing Override to compile on old versions of Android 455 @SuppressWarnings("MissingOverride") engineUpdateAAD(ByteBuffer buf)456 protected void engineUpdateAAD(ByteBuffer buf) { 457 checkInitialization(); 458 if (aad == null) { 459 aad = new byte[buf.remaining()]; 460 buf.get(aad); 461 } else { 462 int newSize = aad.length + buf.remaining(); 463 byte[] newaad = new byte[newSize]; 464 System.arraycopy(aad, 0, newaad, 0, aad.length); 465 buf.get(newaad, aad.length, buf.remaining()); 466 aad = newaad; 467 } 468 } 469 getEVP_AEAD(int keyLength)470 abstract long getEVP_AEAD(int keyLength) throws InvalidKeyException; 471 472 } 473 474