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