OpenSSLCipher.java revision 13cf08b2f06e1f5f0278c449072898f5e147db49
1/* 2 * Copyright (C) 2012 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 org.apache.harmony.xnet.provider.jsse; 18 19import java.security.AlgorithmParameters; 20import java.security.InvalidAlgorithmParameterException; 21import java.security.InvalidKeyException; 22import java.security.InvalidParameterException; 23import java.security.Key; 24import java.security.KeyFactory; 25import java.security.NoSuchAlgorithmException; 26import java.security.SecureRandom; 27import java.security.spec.AlgorithmParameterSpec; 28import java.security.spec.InvalidKeySpecException; 29import java.security.spec.InvalidParameterSpecException; 30import java.security.spec.PKCS8EncodedKeySpec; 31import java.security.spec.X509EncodedKeySpec; 32import java.util.Arrays; 33import java.util.Locale; 34 35import javax.crypto.BadPaddingException; 36import javax.crypto.Cipher; 37import javax.crypto.CipherSpi; 38import javax.crypto.IllegalBlockSizeException; 39import javax.crypto.NoSuchPaddingException; 40import javax.crypto.SecretKey; 41import javax.crypto.ShortBufferException; 42import javax.crypto.spec.IvParameterSpec; 43import javax.crypto.spec.SecretKeySpec; 44 45import libcore.util.EmptyArray; 46 47public abstract class OpenSSLCipher extends CipherSpi { 48 49 /** 50 * Modes that a block cipher may support. 51 */ 52 protected static enum Mode { 53 CBC, 54 CFB, CFB1, CFB8, CFB128, 55 CTR, 56 CTS, 57 ECB, 58 OFB, OFB64, OFB128, 59 PCBC, 60 } 61 62 /** 63 * Paddings that a block cipher may support. 64 */ 65 protected static enum Padding { 66 NOPADDING, 67 PKCS5PADDING, 68 ISO10126PADDING, 69 } 70 71 /** 72 * Native pointer for the OpenSSL EVP_CIPHER context. 73 */ 74 private OpenSSLCipherContext cipherCtx = new OpenSSLCipherContext( 75 NativeCrypto.EVP_CIPHER_CTX_new()); 76 77 /** 78 * The current cipher mode. 79 */ 80 private Mode mode = Mode.ECB; 81 82 /** 83 * The current cipher padding. 84 */ 85 private Padding padding = Padding.PKCS5PADDING; 86 87 /** 88 * The Initial Vector (IV) used for the current cipher. 89 */ 90 private byte[] iv; 91 92 /** 93 * Current cipher mode: encrypting or decrypting. 94 */ 95 private boolean encrypting; 96 97 /** 98 * The block size of the current cipher. 99 */ 100 private int blockSize; 101 102 /** 103 * The block size of the current mode. 104 */ 105 private int modeBlockSize; 106 107 /** 108 * Buffer to hold a block-sized entry before calling into OpenSSL. 109 */ 110 private byte[] buffer; 111 112 /** 113 * Current offset in the buffer. 114 */ 115 private int bufferOffset; 116 117 protected OpenSSLCipher() { 118 } 119 120 protected OpenSSLCipher(Mode mode, Padding padding) { 121 this.mode = mode; 122 this.padding = padding; 123 blockSize = getCipherBlockSize(); 124 } 125 126 /** 127 * Returns the OpenSSL cipher name for the particular {@code keySize} and 128 * cipher {@code mode}. 129 */ 130 protected abstract String getCipherName(int keySize, Mode mode); 131 132 /** 133 * Checks whether the cipher supports this particular {@code keySize} (in 134 * bytes) and throws {@code InvalidKeyException} if it doesn't. 135 */ 136 protected abstract void checkSupportedKeySize(int keySize) throws InvalidKeyException; 137 138 /** 139 * Checks whether the cipher supports this particular cipher {@code mode} 140 * and throws {@code NoSuchAlgorithmException} if it doesn't. 141 */ 142 protected abstract void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException; 143 144 /** 145 * Checks whether the cipher supports this particular cipher {@code padding} 146 * and throws {@code NoSuchPaddingException} if it doesn't. 147 */ 148 protected abstract void checkSupportedPadding(Padding padding) throws NoSuchPaddingException; 149 150 protected abstract int getCipherBlockSize(); 151 152 @Override 153 protected void engineSetMode(String modeStr) throws NoSuchAlgorithmException { 154 final Mode mode; 155 try { 156 mode = Mode.valueOf(modeStr.toUpperCase(Locale.US)); 157 } catch (IllegalArgumentException e) { 158 NoSuchAlgorithmException newE = new NoSuchAlgorithmException("No such mode: " 159 + modeStr); 160 newE.initCause(e); 161 throw newE; 162 } 163 checkSupportedMode(mode); 164 this.mode = mode; 165 } 166 167 @Override 168 protected void engineSetPadding(String paddingStr) throws NoSuchPaddingException { 169 final String paddingStrUpper = paddingStr.toUpperCase(Locale.US); 170 final Padding padding; 171 try { 172 padding = Padding.valueOf(paddingStrUpper); 173 } catch (IllegalArgumentException e) { 174 NoSuchPaddingException newE = new NoSuchPaddingException("No such padding: " 175 + paddingStr); 176 newE.initCause(e); 177 throw newE; 178 } 179 checkSupportedPadding(padding); 180 this.padding = padding; 181 } 182 183 @Override 184 protected int engineGetBlockSize() { 185 return blockSize; 186 } 187 188 /** 189 * The size of output if {@code doFinal()} is called with this 190 * {@code inputLen}. If padding is enabled and the size of the input puts it 191 * right at the block size, it will add another block for the padding. 192 */ 193 private final int getFinalOutputSize(int inputLen) { 194 final int totalLen = bufferOffset + inputLen; 195 final int overrunLen = totalLen % blockSize; 196 197 if (overrunLen == 0) { 198 if ((padding == Padding.NOPADDING) && (totalLen > 0)) { 199 return totalLen; 200 } else { 201 return totalLen + blockSize; 202 } 203 } else { 204 return totalLen - overrunLen + blockSize; 205 } 206 } 207 208 @Override 209 protected int engineGetOutputSize(int inputLen) { 210 return getFinalOutputSize(inputLen); 211 } 212 213 @Override 214 protected byte[] engineGetIV() { 215 return iv; 216 } 217 218 @Override 219 protected AlgorithmParameters engineGetParameters() { 220 return null; 221 } 222 223 private void engineInitInternal(int opmode, Key key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException { 224 if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) { 225 encrypting = true; 226 } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) { 227 encrypting = false; 228 } else { 229 throw new InvalidParameterException("Unsupported opmode " + opmode); 230 } 231 232 if (!(key instanceof SecretKey)) { 233 throw new InvalidKeyException("Only SecretKey is supported"); 234 } 235 236 final byte[] encodedKey = key.getEncoded(); 237 if (encodedKey == null) { 238 throw new InvalidKeyException("key.getEncoded() == null"); 239 } 240 241 checkSupportedKeySize(encodedKey.length); 242 243 final int cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length, 244 mode)); 245 if (cipherType == 0) { 246 throw new InvalidAlgorithmParameterException("Cannot find name for key length = " 247 + (encodedKey.length * 8) + " and mode = " + mode); 248 } 249 250 final int ivLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType); 251 if (iv == null) { 252 iv = new byte[ivLength]; 253 } else if (iv.length != ivLength) { 254 throw new InvalidAlgorithmParameterException("expected IV length of " + ivLength); 255 } 256 257 this.iv = iv; 258 259 NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, encodedKey, iv, 260 encrypting); 261 262 // OpenSSL only supports PKCS5 Padding. 263 NativeCrypto.EVP_CIPHER_CTX_set_padding(cipherCtx.getContext(), 264 padding == Padding.PKCS5PADDING); 265 modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx.getContext()); 266 267 buffer = new byte[blockSize]; 268 bufferOffset = 0; 269 } 270 271 @Override 272 protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { 273 try { 274 engineInitInternal(opmode, key, null); 275 } catch (InvalidAlgorithmParameterException e) { 276 throw new RuntimeException(e); 277 } 278 } 279 280 @Override 281 protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, 282 SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { 283 final byte[] iv; 284 if (params instanceof IvParameterSpec) { 285 IvParameterSpec ivParams = (IvParameterSpec) params; 286 iv = ivParams.getIV(); 287 } else { 288 iv = null; 289 } 290 291 engineInitInternal(opmode, key, iv); 292 } 293 294 @Override 295 protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) 296 throws InvalidKeyException, InvalidAlgorithmParameterException { 297 final AlgorithmParameterSpec spec; 298 try { 299 spec = params.getParameterSpec(IvParameterSpec.class); 300 } catch (InvalidParameterSpecException e) { 301 throw new InvalidAlgorithmParameterException(e); 302 } 303 304 engineInit(opmode, key, spec, random); 305 } 306 307 private final int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, 308 int outputOffset, int totalLen, int fullBlocksSize) throws ShortBufferException { 309 final int intialOutputOffset = outputOffset; 310 311 /* Take care of existing buffered bytes. */ 312 final int remainingBuffer = buffer.length - bufferOffset; 313 if (bufferOffset > 0 && inputLen >= remainingBuffer) { 314 System.arraycopy(input, inputOffset, buffer, bufferOffset, remainingBuffer); 315 final int writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, 316 outputOffset, buffer, 0, blockSize); 317 fullBlocksSize -= writtenBytes; 318 outputOffset += writtenBytes; 319 320 inputLen -= remainingBuffer; 321 inputOffset += remainingBuffer; 322 323 bufferOffset = 0; 324 } 325 326 /* Take care of the bytes that would fill up our block-sized buffer. */ 327 if (fullBlocksSize > 0) { 328 final int bytesLeft = output.length - outputOffset; 329 if (bytesLeft < fullBlocksSize) { 330 throw new ShortBufferException("output buffer too small during update: " 331 + bytesLeft + " < " + fullBlocksSize); 332 } 333 334 outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, 335 outputOffset, input, inputOffset, fullBlocksSize); 336 inputLen -= fullBlocksSize; 337 inputOffset += fullBlocksSize; 338 } 339 340 /* Put the rest into the buffer for next time. */ 341 if (inputLen > 0) { 342 System.arraycopy(input, inputOffset, buffer, bufferOffset, inputLen); 343 bufferOffset += inputLen; 344 } 345 346 return outputOffset - intialOutputOffset; 347 } 348 349 @Override 350 protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { 351 final int totalLen = bufferOffset + inputLen; 352 final int fullBlocksSize = totalLen - (totalLen % blockSize); 353 354 /* See how large our output buffer would need to be. */ 355 final byte[] output; 356 if (fullBlocksSize > 0) { 357 output = new byte[fullBlocksSize]; 358 } else { 359 output = EmptyArray.BYTE; 360 } 361 362 try { 363 updateInternal(input, inputOffset, inputLen, output, 0, totalLen, fullBlocksSize); 364 } catch (ShortBufferException e) { 365 /* This shouldn't happen. */ 366 throw new AssertionError("calculated buffer size was wrong: " + fullBlocksSize); 367 } 368 369 return output; 370 } 371 372 @Override 373 protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, 374 int outputOffset) throws ShortBufferException { 375 final int totalLen = bufferOffset + inputLen; 376 final int fullBlocksSize = totalLen - (totalLen % modeBlockSize); 377 return updateInternal(input, inputOffset, inputLen, output, outputOffset, totalLen, fullBlocksSize); 378 } 379 380 private int doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output, 381 int outputOffset, int totalLen, int trailingLen, int maximumPossibleSize) 382 throws IllegalBlockSizeException, BadPaddingException, ShortBufferException { 383 if ((trailingLen != 0) && (padding == Padding.NOPADDING)) { 384 throw new IllegalBlockSizeException("not multiple of block size " + trailingLen 385 + " != " + modeBlockSize); 386 } 387 388 /* Remember this so we can tell how many characters were written. */ 389 final int initialOutputOffset = outputOffset; 390 391 if (inputLen > 0) { 392 /* 393 * First run update to set up our invariant that we have less than 394 * {@code blockSize} worth of bytes for the next CipherUpdate call. 395 */ 396 final int updateSize; 397 if (trailingLen == 0 && maximumPossibleSize >= blockSize) { 398 updateSize = maximumPossibleSize - blockSize; 399 } else { 400 updateSize = maximumPossibleSize - trailingLen; 401 } 402 final int updateBytesWritten = updateInternal(input, inputOffset, inputLen, output, 403 outputOffset, totalLen, updateSize); 404 outputOffset += updateBytesWritten; 405 } 406 407 /* Take care of existing buffered bytes. */ 408 if (bufferOffset > 0) { 409 final int bytesLeft = output.length - outputOffset; 410 411 final int bytesNeeded = bufferOffset + modeBlockSize - 1; 412 final int writtenBytes; 413 if (bytesLeft < bytesNeeded) { 414 final byte[] tmpBuf = new byte[bytesNeeded]; 415 writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), tmpBuf, 0, 416 buffer, 0, bufferOffset); 417 if (writtenBytes > 0) { 418 if (writtenBytes > bytesLeft) { 419 System.arraycopy(tmpBuf, 0, output, outputOffset, bytesLeft); 420 } else { 421 System.arraycopy(tmpBuf, 0, output, outputOffset, writtenBytes); 422 } 423 } 424 } else { 425 writtenBytes = NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, 426 outputOffset, buffer, 0, bufferOffset); 427 } 428 429 outputOffset += writtenBytes; 430 bufferOffset = 0; 431 } 432 433 /* Allow OpenSSL to pad if necessary and clean up state. */ 434 final int bytesLeft = output.length - outputOffset; 435 final int writtenBytes; 436 if (bytesLeft >= blockSize) { 437 writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), output, 438 outputOffset); 439 } else { 440 writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), buffer, 0); 441 if (writtenBytes > bytesLeft) { 442 throw new ShortBufferException("buffer is too short: " + writtenBytes + " > " 443 + bytesLeft); 444 } else if (writtenBytes > 0) { 445 System.arraycopy(buffer, 0, output, outputOffset, writtenBytes); 446 } 447 } 448 outputOffset += writtenBytes; 449 450 /* Re-initialize the cipher for the next time. */ 451 NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting); 452 453 return outputOffset - initialOutputOffset; 454 } 455 456 @Override 457 protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) 458 throws IllegalBlockSizeException, BadPaddingException { 459 final int totalLen = bufferOffset + inputLen; 460 final int trailingLen = totalLen % modeBlockSize; 461 462 final int maximumPossibleSize = calculateMaximumPossibleSize(totalLen, trailingLen); 463 /* Assume that we'll output exactly on a byte boundary. */ 464 byte[] output = new byte[maximumPossibleSize]; 465 final int bytesWritten; 466 try { 467 bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0, totalLen, 468 trailingLen, maximumPossibleSize); 469 } catch (ShortBufferException e) { 470 /* This should not happen since we sized our own buffer. */ 471 throw new RuntimeException("our calculated buffer was too small", e); 472 } 473 474 if (bytesWritten == output.length) { 475 return output; 476 } else { 477 return Arrays.copyOfRange(output, 0, bytesWritten); 478 } 479 } 480 481 @Override 482 protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, 483 int outputOffset) throws ShortBufferException, IllegalBlockSizeException, 484 BadPaddingException { 485 if (output == null) { 486 throw new NullPointerException("output == null"); 487 } 488 489 final int totalLen = bufferOffset + inputLen; 490 final int trailingLen = totalLen % modeBlockSize; 491 492 final int maximumPossibleSize = calculateMaximumPossibleSize(totalLen, trailingLen); 493 494 return doFinalInternal(input, inputOffset, inputLen, output, outputOffset, totalLen, 495 trailingLen, maximumPossibleSize); 496 } 497 498 private int calculateMaximumPossibleSize(final int totalLen, final int trailingLen) { 499 if (encrypting && (modeBlockSize > 1) && (padding != Padding.NOPADDING)) { 500 return totalLen - trailingLen + modeBlockSize; 501 } else { 502 return totalLen; 503 } 504 } 505 506 @Override 507 protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException { 508 try { 509 byte[] encoded = key.getEncoded(); 510 return engineDoFinal(encoded, 0, encoded.length); 511 } catch (BadPaddingException e) { 512 IllegalBlockSizeException newE = new IllegalBlockSizeException(); 513 newE.initCause(e); 514 throw newE; 515 } 516 } 517 518 @Override 519 protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) 520 throws InvalidKeyException, NoSuchAlgorithmException { 521 try { 522 byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); 523 if (wrappedKeyType == Cipher.PUBLIC_KEY) { 524 KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); 525 return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); 526 } else if (wrappedKeyType == Cipher.PRIVATE_KEY) { 527 KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); 528 return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); 529 } else if (wrappedKeyType == Cipher.SECRET_KEY) { 530 return new SecretKeySpec(encoded, wrappedKeyAlgorithm); 531 } else { 532 throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType); 533 } 534 } catch (IllegalBlockSizeException e) { 535 throw new InvalidKeyException(e); 536 } catch (BadPaddingException e) { 537 throw new InvalidKeyException(e); 538 } catch (InvalidKeySpecException e) { 539 throw new InvalidKeyException(e); 540 } 541 } 542 543 public static class AES extends OpenSSLCipher { 544 private static final int AES_BLOCK_SIZE = 16; 545 546 protected AES(Mode mode, Padding padding) { 547 super(mode, padding); 548 } 549 550 public static class CBC extends AES { 551 public CBC(Padding padding) { 552 super(Mode.CBC, padding); 553 } 554 555 public static class NoPadding extends CBC { 556 public NoPadding() { 557 super(Padding.NOPADDING); 558 } 559 } 560 561 public static class PKCS5Padding extends CBC { 562 public PKCS5Padding() { 563 super(Padding.PKCS5PADDING); 564 } 565 } 566 } 567 568 public static class CFB extends AES { 569 public CFB(Padding padding) { 570 super(Mode.CFB, padding); 571 } 572 573 public static class NoPadding extends CFB { 574 public NoPadding() { 575 super(Padding.NOPADDING); 576 } 577 } 578 579 public static class PKCS5Padding extends CFB { 580 public PKCS5Padding() { 581 super(Padding.PKCS5PADDING); 582 } 583 } 584 } 585 586 public static class CTR extends AES { 587 public CTR(Padding padding) { 588 super(Mode.CTR, padding); 589 } 590 591 public static class NoPadding extends CTR { 592 public NoPadding() { 593 super(Padding.NOPADDING); 594 } 595 } 596 597 public static class PKCS5Padding extends CTR { 598 public PKCS5Padding() { 599 super(Padding.PKCS5PADDING); 600 } 601 } 602 } 603 604 public static class ECB extends AES { 605 public ECB(Padding padding) { 606 super(Mode.ECB, padding); 607 } 608 609 public static class NoPadding extends ECB { 610 public NoPadding() { 611 super(Padding.NOPADDING); 612 } 613 } 614 615 public static class PKCS5Padding extends ECB { 616 public PKCS5Padding() { 617 super(Padding.PKCS5PADDING); 618 } 619 } 620 } 621 622 public static class OFB extends AES { 623 public OFB(Padding padding) { 624 super(Mode.OFB, padding); 625 } 626 627 public static class NoPadding extends OFB { 628 public NoPadding() { 629 super(Padding.NOPADDING); 630 } 631 } 632 633 public static class PKCS5Padding extends OFB { 634 public PKCS5Padding() { 635 super(Padding.PKCS5PADDING); 636 } 637 } 638 } 639 640 @Override 641 protected void checkSupportedKeySize(int keyLength) throws InvalidKeyException { 642 switch (keyLength) { 643 case 16: // AES 128 644 case 24: // AES 192 645 case 32: // AES 256 646 return; 647 default: 648 throw new InvalidKeyException("Unsupported key size: " + keyLength + " bytes"); 649 } 650 } 651 652 @Override 653 protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException { 654 switch (mode) { 655 case CBC: 656 case CFB: 657 case CFB1: 658 case CFB8: 659 case CFB128: 660 case CTR: 661 case ECB: 662 case OFB: 663 return; 664 default: 665 throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString()); 666 } 667 } 668 669 @Override 670 protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException { 671 switch (padding) { 672 case NOPADDING: 673 case PKCS5PADDING: 674 return; 675 default: 676 throw new NoSuchPaddingException("Unsupported padding " + padding.toString()); 677 } 678 } 679 680 @Override 681 protected String getCipherName(int keyLength, Mode mode) { 682 return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US); 683 } 684 685 @Override 686 protected int getCipherBlockSize() { 687 return AES_BLOCK_SIZE; 688 } 689 } 690} 691