• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt;
18 
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.InvocationTargetException;
21 import java.nio.ByteBuffer;
22 import java.security.InvalidAlgorithmParameterException;
23 import java.security.InvalidKeyException;
24 import java.security.SecureRandom;
25 import java.security.spec.AlgorithmParameterSpec;
26 import java.util.Arrays;
27 import javax.crypto.BadPaddingException;
28 import javax.crypto.IllegalBlockSizeException;
29 import javax.crypto.NoSuchPaddingException;
30 import javax.crypto.ShortBufferException;
31 import javax.crypto.spec.IvParameterSpec;
32 
33 @Internal
34 public abstract class OpenSSLAeadCipher extends OpenSSLCipher {
35     /**
36      * Controls whether no-copy optimizations for direct ByteBuffers are enabled.
37      */
38     private static final boolean ENABLE_BYTEBUFFER_OPTIMIZATIONS = true;
39 
40     /**
41      * The default tag size when one is not specified. Default to
42      * full-length tags (128-bits or 16 octets).
43      */
44     static final int DEFAULT_TAG_SIZE_BITS = 16 * 8;
45 
46     /**
47      * Keeps track of the last used block size.
48      */
49     private static int lastGlobalMessageSize = 32;
50 
51     /**
52      * The previously used key to prevent key + nonce (IV) reuse.
53      */
54     private byte[] previousKey;
55 
56     /**
57      * The previously used nonce (IV) to prevent key + nonce reuse.
58      */
59     private byte[] previousIv;
60 
61     /**
62      * When set this instance must be initialized before use again. This prevents key
63      * and IV reuse.
64      */
65     private boolean mustInitialize;
66 
67     /**
68      * The byte array containing the bytes written.
69      */
70     byte[] buf;
71 
72     /**
73      * The number of bytes written.
74      */
75     int bufCount;
76 
77     /**
78      * AEAD cipher reference.
79      */
80     long evpAead;
81 
82     /**
83      * Additional authenticated data.
84      */
85     private byte[] aad;
86 
87     /**
88      * The length of the AEAD cipher tag in bytes.
89      */
90     int tagLengthInBytes;
91 
OpenSSLAeadCipher(Mode mode)92     protected OpenSSLAeadCipher(Mode mode) {
93         super(mode, Padding.NOPADDING);
94     }
95 
checkInitialization()96     private void checkInitialization() {
97         if (mustInitialize) {
98             throw new IllegalStateException(
99                     "Cannot re-use same key and IV for multiple encryptions");
100         }
101     }
102 
103     /** Constant-time array comparison.  Since we are using this to compare keys, we want to
104      * ensure there's no opportunity for a timing attack. */
arraysAreEqual(byte[] a, byte[] b)105     private boolean arraysAreEqual(byte[] a, byte[] b) {
106         if (a.length != b.length) {
107             return false;
108         }
109 
110         int diff = 0;
111         for (int i = 0; i < a.length; i++) {
112             diff |= a[i] ^ b[i];
113         }
114         return diff == 0;
115     }
116 
expand(int i)117     private void expand(int i) {
118         /* Can the buffer handle i more bytes, if not expand it */
119         if (bufCount + i <= buf.length) {
120             return;
121         }
122 
123         byte[] newbuf = new byte[(bufCount + i) * 2];
124         System.arraycopy(buf, 0, newbuf, 0, bufCount);
125         buf = newbuf;
126     }
127 
reset()128     private void reset() {
129         aad = null;
130         final int lastBufSize = lastGlobalMessageSize;
131         if (buf == null) {
132             buf = new byte[lastBufSize];
133         } else if (bufCount > 0 && bufCount != lastBufSize) {
134             lastGlobalMessageSize = bufCount;
135             if (buf.length != bufCount) {
136                 buf = new byte[bufCount];
137             }
138         }
139         bufCount = 0;
140     }
141 
142     @Override
engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, SecureRandom random)143     void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params,
144             SecureRandom random) throws InvalidKeyException,
145         InvalidAlgorithmParameterException {
146         byte[] iv;
147         final int tagLenBits;
148         if (params == null) {
149             iv = null;
150             tagLenBits = DEFAULT_TAG_SIZE_BITS;
151         } else {
152             GCMParameters gcmParams = Platform.fromGCMParameterSpec(params);
153             if (gcmParams != null) {
154                 iv = gcmParams.getIV();
155                 tagLenBits = gcmParams.getTLen();
156             } else if (params instanceof IvParameterSpec) {
157                 IvParameterSpec ivParams = (IvParameterSpec) params;
158                 iv = ivParams.getIV();
159                 tagLenBits = DEFAULT_TAG_SIZE_BITS;
160             } else {
161                 iv = null;
162                 tagLenBits = DEFAULT_TAG_SIZE_BITS;
163             }
164         }
165 
166         checkSupportedTagLength(tagLenBits);
167 
168         tagLengthInBytes = tagLenBits / 8;
169 
170         final boolean encrypting = isEncrypting();
171 
172         evpAead = getEVP_AEAD(encodedKey.length);
173 
174         final int expectedIvLength = NativeCrypto.EVP_AEAD_nonce_length(evpAead);
175         if (iv == null && expectedIvLength != 0) {
176             if (!encrypting) {
177                 throw new InvalidAlgorithmParameterException("IV must be specified in " + mode
178                         + " mode");
179             }
180 
181             iv = new byte[expectedIvLength];
182             if (random != null) {
183                 random.nextBytes(iv);
184             } else {
185                 NativeCrypto.RAND_bytes(iv);
186             }
187         } else if (expectedIvLength == 0 && iv != null) {
188             throw new InvalidAlgorithmParameterException("IV not used in " + mode + " mode");
189         } else if (iv != null && iv.length != expectedIvLength) {
190             throw new InvalidAlgorithmParameterException("Expected IV length of "
191                     + expectedIvLength + " but was " + iv.length);
192         }
193 
194         if (isEncrypting() && iv != null && !allowsNonceReuse()) {
195             if (previousKey != null && previousIv != null
196                     && arraysAreEqual(previousKey, encodedKey)
197                     && arraysAreEqual(previousIv, iv)) {
198                 mustInitialize = true;
199                 throw new InvalidAlgorithmParameterException(
200                         "When using AEAD key and IV must not be re-used");
201             }
202 
203             this.previousKey = encodedKey;
204             this.previousIv = iv;
205         }
206         mustInitialize = false;
207         this.iv = iv;
208         reset();
209     }
210 
checkSupportedTagLength(int tagLenBits)211     void checkSupportedTagLength(int tagLenBits)
212             throws InvalidAlgorithmParameterException {
213         if (tagLenBits % 8 != 0) {
214             throw new InvalidAlgorithmParameterException(
215                     "Tag length must be a multiple of 8; was " + tagLenBits);
216         }
217     }
218 
219     /**
220      * Returns whether reusing nonces is allowed (aka, whether this is nonce misuse-resistant).
221      * Most AEAD ciphers are not, but some are specially constructed so that reusing a key/nonce
222      * pair is safe.
223      */
allowsNonceReuse()224     boolean allowsNonceReuse() {
225         return false;
226     }
227 
228     @Override
engineDoFinal(ByteBuffer input, ByteBuffer output)229     protected int engineDoFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException,
230             IllegalBlockSizeException, BadPaddingException {
231         if (!ENABLE_BYTEBUFFER_OPTIMIZATIONS) {
232             return super.engineDoFinal(input, output);
233         }
234         if (input == null || output == null) {
235             throw new NullPointerException("Null ByteBuffer Error");
236         }
237         if (getOutputSizeForFinal(input.remaining()) > output.remaining()) {
238             throw new ShortBufferWithoutStackTraceException("Insufficient Bytes for Output Buffer");
239         }
240         if (output.isReadOnly()) {
241             throw new IllegalArgumentException("Cannot write to Read Only ByteBuffer");
242         }
243         if (bufCount != 0) {
244             return super.engineDoFinal(input, output); // traditional case
245         }
246         int bytesWritten;
247         if (!input.isDirect()) {
248             int incap = input.remaining();
249             ByteBuffer inputClone = ByteBuffer.allocateDirect(incap);
250             inputClone.mark();
251             inputClone.put(input);
252             inputClone.reset();
253             input = inputClone;
254         }
255         if (!output.isDirect()) {
256             ByteBuffer outputClone = ByteBuffer.allocateDirect(
257                     getOutputSizeForFinal(input.remaining()));
258             bytesWritten = doFinalInternal(input, outputClone);
259             output.put(outputClone);
260             input.position(input.limit()); // API reasons
261         }
262         else {
263             bytesWritten =  doFinalInternal(input, output);
264             output.position(output.position() + bytesWritten);
265             input.position(input.limit()); // API reasons
266         }
267 
268         return bytesWritten;
269     }
270 
271     @Override
engineDoFinal(byte[] input, int inputOffset, int inputLen)272     protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
273             throws IllegalBlockSizeException, BadPaddingException {
274         final int maximumLen = getOutputSizeForFinal(inputLen);
275         /* Assume that we'll output exactly on a byte boundary. */
276         final byte[] output = new byte[maximumLen];
277 
278         int bytesWritten;
279         try {
280             bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0);
281         } catch (ShortBufferException e) {
282             /* This should not happen since we sized our own buffer. */
283             throw new RuntimeException("our calculated buffer was too small", e);
284         }
285 
286         if (bytesWritten == output.length) {
287             return output;
288         } else if (bytesWritten == 0) {
289             return EmptyArray.BYTE;
290         } else {
291             return Arrays.copyOf(output, bytesWritten);
292         }
293     }
294 
295     @Override
engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)296     protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
297             int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
298             BadPaddingException {
299         if (output == null) {
300             throw new NullPointerException("output == null");
301         }
302         if (getOutputSizeForFinal(inputLen) > output.length - outputOffset) {
303             throw new ShortBufferWithoutStackTraceException("Insufficient output space");
304         }
305         return doFinalInternal(input, inputOffset, inputLen, output, outputOffset);
306     }
307 
appendToBuf(byte[] input, int inputOffset, int inputLen)308     void appendToBuf(byte[] input, int inputOffset, int inputLen) {
309         if (buf == null) {
310             throw new IllegalStateException("Cipher not initialized");
311         }
312 
313         ArrayUtils.checkOffsetAndCount(input.length, inputOffset, inputLen);
314         if (inputLen > 0) {
315             expand(inputLen);
316             System.arraycopy(input, inputOffset, buf, this.bufCount, inputLen);
317             this.bufCount += inputLen;
318         }
319     }
320 
321     @Override
updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset, int maximumLen)322     int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
323             int outputOffset, int maximumLen) throws ShortBufferException {
324         checkInitialization();
325         appendToBuf(input, inputOffset, inputLen);
326         return 0;
327     }
328 
329     @SuppressWarnings("LiteralClassName")
throwAEADBadTagExceptionIfAvailable(String message, Throwable cause)330     private void throwAEADBadTagExceptionIfAvailable(String message, Throwable cause)
331             throws BadPaddingException {
332         Constructor<?> aeadBadTagConstructor;
333         try {
334             aeadBadTagConstructor = Class.forName("javax.crypto.AEADBadTagException")
335                                             .getConstructor(String.class);
336         } catch (Exception ignored) {
337             return;
338         }
339 
340         BadPaddingException badTagException = null;
341         try {
342             badTagException = (BadPaddingException) aeadBadTagConstructor.newInstance(message);
343             badTagException.initCause(cause);
344         } catch (IllegalAccessException e2) {
345             // Fall through
346         } catch (InstantiationException e2) {
347             // Fall through
348         } catch (InvocationTargetException e2) {
349             throw(BadPaddingException) new BadPaddingException().initCause(
350                     e2.getTargetException());
351         }
352         if (badTagException != null) {
353             throw badTagException;
354         }
355     }
356 
doFinalInternal(ByteBuffer input, ByteBuffer output)357     int doFinalInternal(ByteBuffer input, ByteBuffer output)
358             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
359         checkInitialization();
360         final int bytesWritten;
361         try {
362             if (isEncrypting()) {
363                 bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal_buf(
364                         evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
365             } else {
366                 bytesWritten = NativeCrypto.EVP_AEAD_CTX_open_buf(
367                         evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
368             }
369         } catch (BadPaddingException e) {
370             throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause());
371             throw e;
372         }
373         if (isEncrypting()) {
374             mustInitialize = true;
375         }
376         return bytesWritten;
377     }
378 
doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)379     int doFinalInternal(byte[] input, int inputOffset, int inputLen,
380             byte[] output, int outputOffset)
381             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
382         checkInitialization();
383 
384         byte[] in;
385         int inOffset;
386         int inLen;
387         if (bufCount > 0) {
388             if (inputLen > 0) {
389                 appendToBuf(input, inputOffset, inputLen);
390             }
391             in = buf;
392             inOffset = 0;
393             inLen = bufCount;
394         } else {
395             if (inputLen == 0 && input == null) {
396                 in = EmptyArray.BYTE; // input can be null when inputLen == 0
397             } else {
398                 in = input;
399             }
400             inOffset = inputOffset;
401             inLen = inputLen;
402         }
403 
404         final int bytesWritten;
405         try {
406             if (isEncrypting()) {
407                 bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal(evpAead, encodedKey,
408                         tagLengthInBytes, output, outputOffset, iv, in, inOffset, inLen, aad);
409             } else {
410                 bytesWritten = NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey,
411                         tagLengthInBytes, output, outputOffset, iv, in, inOffset, inLen, aad);
412             }
413         } catch (BadPaddingException e) {
414             throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause());
415             throw e;
416         }
417         if (isEncrypting()) {
418             mustInitialize = true;
419         }
420         reset();
421         return bytesWritten;
422     }
423 
424     @Override
checkSupportedPadding(Padding padding)425     void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
426         if (padding != Padding.NOPADDING) {
427             throw new NoSuchPaddingException("Must be NoPadding for AEAD ciphers");
428         }
429     }
430 
431     /**
432      * AEAD buffers everything until a final output.
433      */
434     @Override
getOutputSizeForUpdate(int inputLen)435     int getOutputSizeForUpdate(int inputLen) {
436         return 0;
437     }
438 
439     // Intentionally missing Override to compile on old versions of Android
440     @SuppressWarnings("MissingOverride")
engineUpdateAAD(byte[] input, int inputOffset, int inputLen)441     protected void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
442         checkInitialization();
443         if (aad == null) {
444             aad = Arrays.copyOfRange(input, inputOffset, inputOffset + inputLen);
445         } else {
446             int newSize = aad.length + inputLen;
447             byte[] newaad = new byte[newSize];
448             System.arraycopy(aad, 0, newaad, 0, aad.length);
449             System.arraycopy(input, inputOffset, newaad, aad.length, inputLen);
450             aad = newaad;
451         }
452     }
453 
454     // Intentionally missing Override to compile on old versions of Android
455     @SuppressWarnings("MissingOverride")
engineUpdateAAD(ByteBuffer buf)456     protected void engineUpdateAAD(ByteBuffer buf) {
457         checkInitialization();
458         if (aad == null) {
459             aad = new byte[buf.remaining()];
460             buf.get(aad);
461         } else {
462             int newSize = aad.length + buf.remaining();
463             byte[] newaad = new byte[newSize];
464             System.arraycopy(aad, 0, newaad, 0, aad.length);
465             buf.get(newaad, aad.length, buf.remaining());
466             aad = newaad;
467         }
468     }
469 
getEVP_AEAD(int keyLength)470     abstract long getEVP_AEAD(int keyLength) throws InvalidKeyException;
471 
472 }
473 
474