1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.oracle.security.ucrypto; 27 28 import java.nio.ByteBuffer; 29 import java.util.Set; 30 import java.util.Arrays; 31 import java.util.concurrent.ConcurrentSkipListSet; 32 import java.lang.ref.*; 33 34 import java.security.AlgorithmParameters; 35 import java.security.GeneralSecurityException; 36 import java.security.InvalidAlgorithmParameterException; 37 import java.security.InvalidKeyException; 38 import java.security.Key; 39 import java.security.NoSuchAlgorithmException; 40 import java.security.SecureRandom; 41 42 43 import java.security.spec.AlgorithmParameterSpec; 44 import java.security.spec.InvalidParameterSpecException; 45 46 import javax.crypto.BadPaddingException; 47 import javax.crypto.Cipher; 48 import javax.crypto.CipherSpi; 49 import javax.crypto.IllegalBlockSizeException; 50 import javax.crypto.NoSuchPaddingException; 51 import javax.crypto.ShortBufferException; 52 53 import javax.crypto.spec.IvParameterSpec; 54 55 /** 56 * Wrapper class which uses NativeCipher class and Java impls of padding scheme. 57 * This class currently supports 58 * - AES/ECB/PKCS5PADDING 59 * - AES/CBC/PKCS5PADDING 60 * - AES/CFB128/PKCS5PADDING 61 * 62 * @since 1.9 63 */ 64 public class NativeCipherWithJavaPadding extends CipherSpi { 65 66 private static interface Padding { 67 // ENC: generate and return the necessary padding bytes 68 int getPadLen(int dataLen); 69 70 // ENC: generate and return the necessary padding bytes 71 byte[] getPaddingBytes(int dataLen); 72 73 // DEC: process the decrypted data and buffer up the potential padding 74 // bytes 75 byte[] bufferBytes(byte[] intermediateData); 76 77 // DEC: return the length of internally buffered pad bytes 78 int getBufferedLength(); 79 80 // DEC: unpad and place the output in 'out', starting from outOfs 81 // and return the number of bytes unpadded into 'out'. 82 int unpad(byte[] paddedData, byte[] out, int outOfs) 83 throws BadPaddingException, IllegalBlockSizeException, 84 ShortBufferException; 85 86 // DEC: Clears the padding object to the initial state 87 void clear(); 88 } 89 90 private static class PKCS5Padding implements Padding { 91 private final int blockSize; 92 // buffer for storing the the potential padding bytes 93 private ByteBuffer trailingBytes = null; 94 95 PKCS5Padding(int blockSize) 96 throws NoSuchPaddingException { 97 if (blockSize == 0) { 98 throw new NoSuchPaddingException 99 ("PKCS#5 padding not supported with stream ciphers"); 100 } 101 this.blockSize = blockSize; 102 } 103 104 public int getPadLen(int dataLen) { 105 return (blockSize - (dataLen & (blockSize - 1))); 106 } 107 108 public byte[] getPaddingBytes(int dataLen) { 109 byte padValue = (byte) getPadLen(dataLen); 110 byte[] paddingBytes = new byte[padValue]; 111 Arrays.fill(paddingBytes, padValue); 112 return paddingBytes; 113 } 114 115 public byte[] bufferBytes(byte[] dataFromUpdate) { 116 if (dataFromUpdate == null || dataFromUpdate.length == 0) { 117 return null; 118 } 119 byte[] result = null; 120 if (trailingBytes == null) { 121 trailingBytes = ByteBuffer.wrap(new byte[blockSize]); 122 } 123 int tbSize = trailingBytes.position(); 124 if (dataFromUpdate.length > trailingBytes.remaining()) { 125 int totalLen = dataFromUpdate.length + tbSize; 126 int newTBSize = totalLen % blockSize; 127 if (newTBSize == 0) { 128 newTBSize = blockSize; 129 } 130 if (tbSize == 0) { 131 result = Arrays.copyOf(dataFromUpdate, totalLen - newTBSize); 132 } else { 133 // combine 'trailingBytes' and 'dataFromUpdate' 134 result = Arrays.copyOf(trailingBytes.array(), 135 totalLen - newTBSize); 136 if (result.length != tbSize) { 137 System.arraycopy(dataFromUpdate, 0, result, tbSize, 138 result.length - tbSize); 139 } 140 } 141 // update 'trailingBytes' w/ remaining bytes in 'dataFromUpdate' 142 trailingBytes.clear(); 143 trailingBytes.put(dataFromUpdate, 144 dataFromUpdate.length - newTBSize, newTBSize); 145 } else { 146 trailingBytes.put(dataFromUpdate); 147 } 148 return result; 149 } 150 151 public int getBufferedLength() { 152 if (trailingBytes != null) { 153 return trailingBytes.position(); 154 } 155 return 0; 156 } 157 158 public int unpad(byte[] lastData, byte[] out, int outOfs) 159 throws BadPaddingException, IllegalBlockSizeException, 160 ShortBufferException { 161 int tbSize = (trailingBytes == null? 0:trailingBytes.position()); 162 int dataLen = tbSize + lastData.length; 163 // check total length 164 if ((dataLen < 1) || (dataLen % blockSize != 0)) { 165 UcryptoProvider.debug("PKCS5Padding: unpad, buffered " + tbSize + 166 " bytes, last block " + lastData.length + " bytes"); 167 168 throw new IllegalBlockSizeException 169 ("Input length must be multiples of " + blockSize); 170 } 171 172 // check padding bytes 173 if (lastData.length == 0) { 174 if (tbSize != 0) { 175 // work on 'trailingBytes' directly 176 lastData = Arrays.copyOf(trailingBytes.array(), tbSize); 177 trailingBytes.clear(); 178 tbSize = 0; 179 } else { 180 throw new BadPaddingException("No pad bytes found!"); 181 } 182 } 183 byte padValue = lastData[lastData.length - 1]; 184 if (padValue < 1 || padValue > blockSize) { 185 UcryptoProvider.debug("PKCS5Padding: unpad, lastData: " + Arrays.toString(lastData)); 186 UcryptoProvider.debug("PKCS5Padding: unpad, padValue=" + padValue); 187 throw new BadPaddingException("Invalid pad value!"); 188 } 189 190 // sanity check padding bytes 191 int padStartIndex = lastData.length - padValue; 192 for (int i = padStartIndex; i < lastData.length; i++) { 193 if (lastData[i] != padValue) { 194 UcryptoProvider.debug("PKCS5Padding: unpad, lastData: " + Arrays.toString(lastData)); 195 UcryptoProvider.debug("PKCS5Padding: unpad, padValue=" + padValue); 196 throw new BadPaddingException("Invalid padding bytes!"); 197 } 198 } 199 200 int actualOutLen = dataLen - padValue; 201 // check output buffer capacity 202 if (out.length - outOfs < actualOutLen) { 203 throw new ShortBufferException("Output buffer too small, need " + actualOutLen + 204 ", got " + (out.length - outOfs)); 205 } 206 try { 207 if (tbSize != 0) { 208 trailingBytes.rewind(); 209 if (tbSize < actualOutLen) { 210 trailingBytes.get(out, outOfs, tbSize); 211 outOfs += tbSize; 212 } else { 213 // copy from trailingBytes and we are done 214 trailingBytes.get(out, outOfs, actualOutLen); 215 return actualOutLen; 216 } 217 } 218 if (lastData.length > padValue) { 219 System.arraycopy(lastData, 0, out, outOfs, 220 lastData.length - padValue); 221 } 222 return actualOutLen; 223 } finally { 224 clear(); 225 } 226 } 227 228 public void clear() { 229 if (trailingBytes != null) trailingBytes.clear(); 230 } 231 } 232 233 public static final class AesEcbPKCS5 extends NativeCipherWithJavaPadding { 234 public AesEcbPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { 235 super(new NativeCipher.AesEcbNoPadding(), "PKCS5Padding"); 236 } 237 } 238 239 public static final class AesCbcPKCS5 extends NativeCipherWithJavaPadding { 240 public AesCbcPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { 241 super(new NativeCipher.AesCbcNoPadding(), "PKCS5Padding"); 242 } 243 } 244 245 public static final class AesCfb128PKCS5 extends NativeCipherWithJavaPadding { 246 public AesCfb128PKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException { 247 super(new NativeCipher.AesCfb128NoPadding(), "PKCS5Padding"); 248 } 249 } 250 251 // fields (re)set in every init() 252 private final NativeCipher nc; 253 private final Padding padding; 254 private final int blockSize; 255 private int lastBlockLen = 0; 256 257 // Only ECB, CBC, CTR, and CFB128 modes w/ NOPADDING for now 258 NativeCipherWithJavaPadding(NativeCipher nc, String paddingScheme) 259 throws NoSuchAlgorithmException, NoSuchPaddingException { 260 this.nc = nc; 261 this.blockSize = nc.engineGetBlockSize(); 262 if (paddingScheme.toUpperCase().equals("PKCS5PADDING")) { 263 padding = new PKCS5Padding(blockSize); 264 } else { 265 throw new NoSuchAlgorithmException("Unsupported padding scheme: " + paddingScheme); 266 } 267 } 268 269 void reset() { 270 padding.clear(); 271 lastBlockLen = 0; 272 } 273 274 @Override 275 protected synchronized void engineSetMode(String mode) throws NoSuchAlgorithmException { 276 nc.engineSetMode(mode); 277 } 278 279 // see JCE spec 280 @Override 281 protected void engineSetPadding(String padding) 282 throws NoSuchPaddingException { 283 // Disallow change of padding for now since currently it's explicitly 284 // defined in transformation strings 285 throw new NoSuchPaddingException("Unsupported padding " + padding); 286 } 287 288 // see JCE spec 289 @Override 290 protected int engineGetBlockSize() { 291 return blockSize; 292 } 293 294 // see JCE spec 295 @Override 296 protected synchronized int engineGetOutputSize(int inputLen) { 297 int result = nc.engineGetOutputSize(inputLen); 298 if (nc.encrypt) { 299 result += padding.getPadLen(result); 300 } else { 301 result += padding.getBufferedLength(); 302 } 303 return result; 304 } 305 306 // see JCE spec 307 @Override 308 protected synchronized byte[] engineGetIV() { 309 return nc.engineGetIV(); 310 } 311 312 // see JCE spec 313 @Override 314 protected synchronized AlgorithmParameters engineGetParameters() { 315 return nc.engineGetParameters(); 316 } 317 318 @Override 319 protected int engineGetKeySize(Key key) throws InvalidKeyException { 320 return nc.engineGetKeySize(key); 321 } 322 323 // see JCE spec 324 @Override 325 protected synchronized void engineInit(int opmode, Key key, SecureRandom random) 326 throws InvalidKeyException { 327 reset(); 328 nc.engineInit(opmode, key, random); 329 } 330 331 // see JCE spec 332 @Override 333 protected synchronized void engineInit(int opmode, Key key, 334 AlgorithmParameterSpec params, SecureRandom random) 335 throws InvalidKeyException, InvalidAlgorithmParameterException { 336 reset(); 337 nc.engineInit(opmode, key, params, random); 338 } 339 340 // see JCE spec 341 @Override 342 protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params, 343 SecureRandom random) 344 throws InvalidKeyException, InvalidAlgorithmParameterException { 345 reset(); 346 nc.engineInit(opmode, key, params, random); 347 } 348 349 // see JCE spec 350 @Override 351 protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) { 352 if (nc.encrypt) { 353 lastBlockLen += inLen; 354 lastBlockLen &= (blockSize - 1); 355 return nc.engineUpdate(in, inOfs, inLen); 356 } else { 357 return padding.bufferBytes(nc.engineUpdate(in, inOfs, inLen)); 358 } 359 } 360 361 // see JCE spec 362 @Override 363 protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, 364 int outOfs) throws ShortBufferException { 365 if (nc.encrypt) { 366 lastBlockLen += inLen; 367 lastBlockLen &= (blockSize - 1); 368 return nc.engineUpdate(in, inOfs, inLen, out, outOfs); 369 } else { 370 byte[] result = padding.bufferBytes(nc.engineUpdate(in, inOfs, inLen)); 371 if (result != null) { 372 System.arraycopy(result, 0, out, outOfs, result.length); 373 return result.length; 374 } else return 0; 375 } 376 } 377 378 // see JCE spec 379 @Override 380 protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen) 381 throws IllegalBlockSizeException, BadPaddingException { 382 int estimatedOutLen = engineGetOutputSize(inLen); 383 byte[] out = new byte[estimatedOutLen]; 384 try { 385 int actualOut = this.engineDoFinal(in, inOfs, inLen, out, 0); 386 // truncate off extra bytes 387 if (actualOut != out.length) { 388 out = Arrays.copyOf(out, actualOut); 389 } 390 } catch (ShortBufferException sbe) { 391 throw new UcryptoException("Internal Error"); 392 } finally { 393 reset(); 394 } 395 return out; 396 } 397 398 // see JCE spec 399 @Override 400 protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, 401 int outOfs) 402 throws ShortBufferException, IllegalBlockSizeException, 403 BadPaddingException { 404 int estimatedOutLen = engineGetOutputSize(inLen); 405 406 if (out.length - outOfs < estimatedOutLen) { 407 throw new ShortBufferException(); 408 } 409 try { 410 if (nc.encrypt) { 411 int k = nc.engineUpdate(in, inOfs, inLen, out, outOfs); 412 lastBlockLen += inLen; 413 lastBlockLen &= (blockSize - 1); 414 byte[] padBytes = padding.getPaddingBytes(lastBlockLen); 415 k += nc.engineDoFinal(padBytes, 0, padBytes.length, out, (outOfs + k)); 416 return k; 417 } else { 418 byte[] tempOut = nc.engineDoFinal(in, inOfs, inLen); 419 int len = padding.unpad(tempOut, out, outOfs); 420 return len; 421 } 422 } finally { 423 reset(); 424 } 425 } 426 427 // see JCE spec 428 @Override 429 protected synchronized byte[] engineWrap(Key key) throws IllegalBlockSizeException, 430 InvalidKeyException { 431 byte[] result = null; 432 try { 433 byte[] encodedKey = key.getEncoded(); 434 if ((encodedKey == null) || (encodedKey.length == 0)) { 435 throw new InvalidKeyException("Cannot get an encoding of " + 436 "the key to be wrapped"); 437 } 438 result = engineDoFinal(encodedKey, 0, encodedKey.length); 439 } catch (BadPaddingException e) { 440 // Should never happen for key wrapping 441 throw new UcryptoException("Internal Error", e); 442 } 443 return result; 444 } 445 446 // see JCE spec 447 @Override 448 protected synchronized Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, 449 int wrappedKeyType) 450 throws InvalidKeyException, NoSuchAlgorithmException { 451 452 byte[] encodedKey; 453 try { 454 encodedKey = engineDoFinal(wrappedKey, 0, 455 wrappedKey.length); 456 } catch (Exception e) { 457 throw (InvalidKeyException) 458 (new InvalidKeyException()).initCause(e); 459 } 460 461 return NativeCipher.constructKey(wrappedKeyType, encodedKey, 462 wrappedKeyAlgorithm); 463 } 464 }