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
getPadLen(int dataLen)69         int getPadLen(int dataLen);
70 
71         // ENC: generate and return the necessary padding bytes
getPaddingBytes(int dataLen)72         byte[] getPaddingBytes(int dataLen);
73 
74         // DEC: process the decrypted data and buffer up the potential padding
75         // bytes
bufferBytes(byte[] intermediateData)76         byte[] bufferBytes(byte[] intermediateData);
77 
78         // DEC: return the length of internally buffered pad bytes
getBufferedLength()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'.
unpad(byte[] paddedData, byte[] out, int outOfs)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
clear()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 
PKCS5Padding(int blockSize)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 
getPadLen(int dataLen)105         public int getPadLen(int dataLen) {
106             return (blockSize - (dataLen & (blockSize - 1)));
107         }
108 
getPaddingBytes(int dataLen)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 
bufferBytes(byte[] dataFromUpdate)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 
getBufferedLength()152         public int getBufferedLength() {
153             if (trailingBytes != null) {
154                 return trailingBytes.position();
155             }
156             return 0;
157         }
158 
unpad(byte[] lastData, byte[] out, int outOfs)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 
clear()232         public void clear() {
233             if (trailingBytes != null) trailingBytes.clear();
234         }
235     }
236 
237     public static final class AesEcbPKCS5 extends NativeCipherWithJavaPadding {
AesEcbPKCS5()238         public AesEcbPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException {
239             super(new NativeCipher.AesEcbNoPadding(), "PKCS5Padding");
240         }
241     }
242 
243     public static final class AesCbcPKCS5 extends NativeCipherWithJavaPadding {
AesCbcPKCS5()244         public AesCbcPKCS5() throws NoSuchAlgorithmException, NoSuchPaddingException {
245             super(new NativeCipher.AesCbcNoPadding(), "PKCS5Padding");
246         }
247     }
248 
249     public static final class AesCfb128PKCS5 extends NativeCipherWithJavaPadding {
AesCfb128PKCS5()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
NativeCipherWithJavaPadding(NativeCipher nc, String paddingScheme)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 
reset()273     void reset() {
274         padding.clear();
275         lastBlockLen = 0;
276     }
277 
278     @Override
engineSetMode(String mode)279     protected synchronized void engineSetMode(String mode) throws NoSuchAlgorithmException {
280         nc.engineSetMode(mode);
281     }
282 
283     // see JCE spec
284     @Override
engineSetPadding(String padding)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
engineGetBlockSize()294     protected int engineGetBlockSize() {
295         return blockSize;
296     }
297 
298     // see JCE spec
299     @Override
engineGetOutputSize(int inputLen)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
engineGetIV()312     protected synchronized byte[] engineGetIV() {
313         return nc.engineGetIV();
314     }
315 
316     // see JCE spec
317     @Override
engineGetParameters()318     protected synchronized AlgorithmParameters engineGetParameters() {
319         return nc.engineGetParameters();
320     }
321 
322     @Override
engineGetKeySize(Key key)323     protected int engineGetKeySize(Key key) throws InvalidKeyException {
324         return nc.engineGetKeySize(key);
325     }
326 
327     // see JCE spec
328     @Override
engineInit(int opmode, Key key, SecureRandom random)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
engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random)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
engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)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
engineUpdate(byte[] in, int inOfs, int inLen)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
engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, int outOfs)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
engineDoFinal(byte[] in, int inOfs, int inLen)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
engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs)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
engineWrap(Key key)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
engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)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 }
469