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
26package com.oracle.security.ucrypto;
27
28import java.io.ByteArrayOutputStream;
29import java.nio.ByteBuffer;
30
31import java.util.Set;
32import java.util.Arrays;
33import java.security.*;
34import java.security.spec.*;
35import javax.crypto.*;
36import javax.crypto.spec.SecretKeySpec;
37import javax.crypto.spec.GCMParameterSpec;
38
39import sun.security.jca.JCAUtil;
40
41/**
42 * Cipher wrapper class utilizing ucrypto APIs. This class currently supports
43 * - AES/GCM/NoPADDING
44 *
45 * @since 9
46 */
47class NativeGCMCipher extends NativeCipher {
48
49    public static final class AesGcmNoPadding extends NativeGCMCipher {
50        public AesGcmNoPadding() throws NoSuchAlgorithmException {
51            super(-1);
52        }
53        public AesGcmNoPadding(int keySize) throws NoSuchAlgorithmException {
54            super(keySize);
55        }
56    }
57
58    private static final int DEFAULT_TAG_LEN = 128; // same as SunJCE provider
59
60    // same as SunJCE provider, see GaloisCounterMode.java for details
61    private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
62
63    // buffer for storing AAD data; if null, meaning buffer content has been
64    // supplied to native context
65    private ByteArrayOutputStream aadBuffer;
66
67    // buffer for storing input in decryption, not used for encryption
68    private ByteArrayOutputStream ibuffer;
69
70    // needed for checking against MAX_BUF_SIZE
71    private int processed;
72
73    private int tagLen = DEFAULT_TAG_LEN;
74
75    /*
76     * variables used for performing the GCM (key+iv) uniqueness check.
77     * To use GCM mode safely, the cipher object must be re-initialized
78     * with a different combination of key + iv values for each
79     * ENCRYPTION operation. However, checking all past key + iv values
80     * isn't feasible. Thus, we only do a per-instance check of the
81     * key + iv values used in previous encryption.
82     * For decryption operations, no checking is necessary.
83     */
84    private boolean requireReinit;
85    private byte[] lastEncKey = null;
86    private byte[] lastEncIv = null;
87
88    private void checkAndUpdateProcessed(int len) {
89        // Currently, cipher text and tag are packed in one byte array, so
90        // the impl-specific limit for input data size is (MAX_BUF_SIZE - tagLen)
91        int inputDataLimit = MAX_BUF_SIZE - tagLen;
92
93        if (processed > inputDataLimit - len) {
94            throw new ProviderException("OracleUcrypto provider only supports " +
95                "input size up to " + inputDataLimit + " bytes");
96        }
97        processed += len;
98    }
99
100    NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException {
101        super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize);
102    }
103
104    @Override
105    protected void ensureInitialized() {
106        if (!initialized) {
107            byte[] aad = null;
108            if (aadBuffer != null) {
109                if (aadBuffer.size() > 0) {
110                    aad = aadBuffer.toByteArray();
111                }
112            }
113            init(encrypt, keyValue, iv, tagLen, aad);
114            aadBuffer = null;
115            if (!initialized) {
116                throw new UcryptoException("Cannot initialize Cipher");
117            }
118        }
119    }
120
121    @Override
122    protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) {
123        if (inLen < 0) return 0;
124
125        if (!isDoFinal && (inLen == 0)) {
126            return 0;
127        }
128
129        int result = inLen + bytesBuffered;
130        if (encrypt) {
131            if (isDoFinal) {
132                result += tagLen/8;
133            }
134        } else {
135            if (ibuffer != null) {
136                result += ibuffer.size();
137            }
138            result -= tagLen/8;
139        }
140        if (result < 0) {
141            result = 0;
142        }
143        return result;
144    }
145
146    @Override
147    protected void reset(boolean doCancel) {
148        super.reset(doCancel);
149        if (aadBuffer == null) {
150            aadBuffer = new ByteArrayOutputStream();
151        } else {
152            aadBuffer.reset();
153        }
154
155        if (ibuffer != null) {
156            ibuffer.reset();
157        }
158        if (!encrypt) requireReinit = false;
159        processed = 0;
160    }
161
162    // actual init() implementation - caller should clone key and iv if needed
163    protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal, int tLen, byte[] aad) {
164        reset(true);
165        this.encrypt = encrypt;
166        this.keyValue = keyVal;
167        this.iv = ivVal;
168        long pCtxtVal = NativeCipher.nativeInit(mech.value(), encrypt, keyValue, iv,
169            tLen, aad);
170        initialized = (pCtxtVal != 0L);
171        if (initialized) {
172            pCtxt = new CipherContextRef(this, pCtxtVal, encrypt);
173        } else {
174            throw new UcryptoException("Cannot initialize Cipher");
175        }
176    }
177
178    // see JCE spec
179    @Override
180    protected synchronized AlgorithmParameters engineGetParameters() {
181        AlgorithmParameters params = null;
182        try {
183            if (iv != null) {
184                GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLen, iv.clone());
185                params = AlgorithmParameters.getInstance("GCM");
186                params.init(gcmSpec);
187            }
188        } catch (GeneralSecurityException e) {
189            // NoSuchAlgorithmException, NoSuchProviderException
190            // InvalidParameterSpecException
191            throw new UcryptoException("Could not encode parameters", e);
192        }
193        return params;
194    }
195
196    // see JCE spec
197    @Override
198    protected synchronized void engineInit(int opmode, Key key,
199            AlgorithmParameterSpec params, SecureRandom random)
200            throws InvalidKeyException, InvalidAlgorithmParameterException {
201        checkKey(key);
202        if (opmode != Cipher.ENCRYPT_MODE &&
203            opmode != Cipher.DECRYPT_MODE &&
204            opmode != Cipher.WRAP_MODE &&
205            opmode != Cipher.UNWRAP_MODE) {
206            throw new InvalidAlgorithmParameterException
207                ("Unsupported mode: " + opmode);
208        }
209        aadBuffer = new ByteArrayOutputStream();
210        boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE);
211        byte[] keyBytes = key.getEncoded().clone();
212        byte[] ivBytes = null;
213        if (params != null) {
214            if (!(params instanceof GCMParameterSpec)) {
215                throw new InvalidAlgorithmParameterException("GCMParameterSpec required." +
216                    " Received: " + params.getClass().getName());
217            } else {
218                tagLen = ((GCMParameterSpec) params).getTLen();
219                ivBytes = ((GCMParameterSpec) params).getIV();
220            }
221        } else {
222            if (doEncrypt) {
223                tagLen = DEFAULT_TAG_LEN;
224
225                // generate IV if none supplied for encryption
226                ivBytes = new byte[blockSize];
227                if (random == null) {
228                    random = JCAUtil.getSecureRandom();
229                }
230                random.nextBytes(ivBytes);
231            } else {
232                throw new InvalidAlgorithmParameterException("Parameters required for decryption");
233            }
234        }
235        if (doEncrypt) {
236            requireReinit = Arrays.equals(ivBytes, lastEncIv) &&
237                MessageDigest.isEqual(keyBytes, lastEncKey);
238            if (requireReinit) {
239                throw new InvalidAlgorithmParameterException
240                    ("Cannot reuse iv for GCM encryption");
241            }
242            lastEncIv = ivBytes;
243            lastEncKey = keyBytes;
244            ibuffer = null;
245        } else {
246            requireReinit = false;
247            ibuffer = new ByteArrayOutputStream();
248        }
249        init(doEncrypt, keyBytes, ivBytes, tagLen, null);
250    }
251
252    // see JCE spec
253    @Override
254    protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params,
255            SecureRandom random)
256            throws InvalidKeyException, InvalidAlgorithmParameterException {
257        AlgorithmParameterSpec spec = null;
258        if (params != null) {
259            try {
260                // mech must be UcryptoMech.CRYPTO_AES_GCM
261                spec = params.getParameterSpec(GCMParameterSpec.class);
262            } catch (InvalidParameterSpecException iaps) {
263                throw new InvalidAlgorithmParameterException(iaps);
264            }
265        }
266        engineInit(opmode, key, spec, random);
267    }
268
269    // see JCE spec
270    @Override
271    protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) {
272        if (aadBuffer != null) {
273            if (aadBuffer.size() > 0) {
274                // init again with AAD data
275                init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
276            }
277            aadBuffer = null;
278        }
279        if (requireReinit) {
280            throw new IllegalStateException
281                ("Must use either different key or iv for GCM encryption");
282        }
283        checkAndUpdateProcessed(inLen);
284        if (inLen > 0) {
285            if (!encrypt) {
286                ibuffer.write(in, inOfs, inLen);
287                return null;
288            }
289            return super.engineUpdate(in, inOfs, inLen);
290        } else return null;
291    }
292
293    // see JCE spec
294    @Override
295    protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out,
296            int outOfs) throws ShortBufferException {
297        int len = getOutputSizeByOperation(inLen, false);
298        if (out.length - outOfs < len) {
299            throw new ShortBufferException("Output buffer must be " +
300                 "(at least) " + len + " bytes long. Got: " +
301                 (out.length - outOfs));
302        }
303        if (aadBuffer != null) {
304            if (aadBuffer.size() > 0) {
305                // init again with AAD data
306                init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
307            }
308            aadBuffer = null;
309        }
310        if (requireReinit) {
311            throw new IllegalStateException
312                ("Must use either different key or iv for GCM encryption");
313        }
314        checkAndUpdateProcessed(inLen);
315        if (inLen > 0) {
316            if (!encrypt) {
317                ibuffer.write(in, inOfs, inLen);
318                return 0;
319            } else {
320                return super.engineUpdate(in, inOfs, inLen, out, outOfs);
321            }
322        }
323        return 0;
324    }
325
326    // see JCE spec
327    @Override
328    protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen)
329            throws IllegalStateException {
330
331        if ((src == null) || (srcOfs < 0) || (srcOfs + srcLen > src.length)) {
332            throw new IllegalArgumentException("Invalid AAD");
333        }
334        if (keyValue == null) {
335            throw new IllegalStateException("Need to initialize Cipher first");
336        }
337        if (requireReinit) {
338            throw new IllegalStateException
339                ("Must use either different key or iv for GCM encryption");
340        }
341        if (aadBuffer != null) {
342            aadBuffer.write(src, srcOfs, srcLen);
343        } else {
344            // update has already been called
345            throw new IllegalStateException
346                ("Update has been called; no more AAD data");
347        }
348    }
349
350    // see JCE spec
351    @Override
352    protected void engineUpdateAAD(ByteBuffer src)
353            throws IllegalStateException {
354        if (src == null) {
355            throw new IllegalArgumentException("Invalid AAD");
356        }
357        if (keyValue == null) {
358            throw new IllegalStateException("Need to initialize Cipher first");
359        }
360        if (requireReinit) {
361            throw new IllegalStateException
362                ("Must use either different key or iv for GCM encryption");
363        }
364        if (aadBuffer != null) {
365            if (src.hasRemaining()) {
366                byte[] srcBytes = new byte[src.remaining()];
367                src.get(srcBytes);
368                aadBuffer.write(srcBytes, 0, srcBytes.length);
369            }
370        } else {
371            // update has already been called
372            throw new IllegalStateException
373                ("Update has been called; no more AAD data");
374        }
375    }
376
377    // see JCE spec
378    @Override
379    protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen)
380            throws IllegalBlockSizeException, BadPaddingException {
381        byte[] out = new byte[getOutputSizeByOperation(inLen, true)];
382        try {
383            // delegate to the other engineDoFinal(...) method
384            int k = engineDoFinal(in, inOfs, inLen, out, 0);
385            if (out.length != k) {
386                out = Arrays.copyOf(out, k);
387            }
388            return out;
389        } catch (ShortBufferException e) {
390            throw new UcryptoException("Internal Error", e);
391        }
392    }
393
394    // see JCE spec
395    @Override
396    protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen,
397                                             byte[] out, int outOfs)
398        throws ShortBufferException, IllegalBlockSizeException,
399               BadPaddingException {
400        int len = getOutputSizeByOperation(inLen, true);
401        if (out.length - outOfs < len) {
402            throw new ShortBufferException("Output buffer must be "
403                + "(at least) " + len + " bytes long. Got: " +
404                (out.length - outOfs));
405        }
406        if (aadBuffer != null) {
407            if (aadBuffer.size() > 0) {
408                // init again with AAD data
409                init(encrypt, keyValue, iv, tagLen, aadBuffer.toByteArray());
410            }
411            aadBuffer = null;
412        }
413        if (requireReinit) {
414            throw new IllegalStateException
415                ("Must use either different key or iv for GCM encryption");
416        }
417
418        checkAndUpdateProcessed(inLen);
419        if (!encrypt) {
420            if (inLen > 0) {
421                ibuffer.write(in, inOfs, inLen);
422            }
423            inLen = ibuffer.size();
424            if (inLen < tagLen/8) {
425                // Otherwise, Solaris lib will error out w/ CRYPTO_BUFFER_TOO_SMALL
426                // when ucrypto_decrypt_final() is called
427                throw new AEADBadTagException("Input too short - need tag." +
428                    " inLen: " + inLen + ". tagLen: " + tagLen);
429            }
430            // refresh 'in' to all buffered-up bytes
431            in = ibuffer.toByteArray();
432            inOfs = 0;
433            ibuffer.reset();
434        }
435        try {
436            return super.engineDoFinal(in, inOfs, inLen, out, outOfs);
437        } catch (UcryptoException ue) {
438            if (ue.getMessage().equals("CRYPTO_INVALID_MAC")) {
439                throw new AEADBadTagException("Tag does not match");
440            } else {
441                // pass it up
442                throw ue;
443            }
444        } finally {
445            requireReinit = encrypt;
446        }
447    }
448}
449