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 }