View Javadoc
1    /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.crypto.cipher;
19  
20  import java.io.ByteArrayOutputStream;
21  import java.nio.ByteBuffer;
22  import java.nio.ByteOrder;
23  import java.security.InvalidAlgorithmParameterException;
24  import java.security.spec.AlgorithmParameterSpec;
25  
26  import javax.crypto.AEADBadTagException;
27  import javax.crypto.BadPaddingException;
28  import javax.crypto.IllegalBlockSizeException;
29  import javax.crypto.ShortBufferException;
30  import javax.crypto.spec.GCMParameterSpec;
31  
32  /**
33   * This class do the real work(Encryption/Decryption/Authentication) for the authenticated mode: GCM.
34   *
35   * It calls the OpenSSL API to implement the JCE-like behavior
36   *
37   * @since 1.1
38   */
39  final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher {
40  
41      static final int DEFAULT_TAG_LEN = 16;
42      // buffer for AAD data; if consumed, set as null
43      private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
44  
45      private int tagBitLen = -1;
46  
47      // buffer for storing input in decryption, not used for encryption
48      private ByteArrayOutputStream inBuffer;
49  
50      public OpenSslGaloisCounterMode(final long context, final int algorithmMode, final int padding) {
51          super(context, algorithmMode, padding);
52      }
53  
54      @Override
55      public void clean() {
56          super.clean();
57          aadBuffer = null;
58      }
59  
60      @Override
61      public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
62              throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
63          checkState();
64  
65          processAAD();
66  
67          final int outputLength = output.length;
68          int len;
69          if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
70              // if GCM-DECRYPT, we have to handle the buffered input
71              // and the retrieve the trailing tag from input
72              int inputOffsetFinal = inputOffset;
73              int inputLenFinal = inputLen;
74              final byte[] inputFinal;
75              if (inBuffer != null && inBuffer.size() > 0) {
76                  inBuffer.write(input, inputOffset, inputLen);
77                  inputFinal = inBuffer.toByteArray();
78                  inputOffsetFinal = 0;
79                  inputLenFinal = inputFinal.length;
80                  inBuffer.reset();
81              } else {
82                  inputFinal = input;
83              }
84  
85              if (inputFinal.length < getTagLen()) {
86                  throw new AEADBadTagException("Input too short - need tag");
87              }
88  
89              final int inputDataLen = inputLenFinal - getTagLen();
90              len = OpenSslNative.updateByteArray(context, inputFinal, inputOffsetFinal,
91                      inputDataLen, output, outputOffset, outputLength - outputOffset);
92  
93              // set tag to EVP_Cipher for integrity verification in doFinal
94              final ByteBuffer tag = ByteBuffer.allocate(getTagLen());
95              tag.put(input, input.length - getTagLen(), getTagLen());
96              tag.flip();
97              evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(), getTagLen(), tag);
98          } else {
99              len = OpenSslNative.updateByteArray(context, input, inputOffset,
100                     inputLen, output, outputOffset, outputLength - outputOffset);
101         }
102 
103         len += OpenSslNative.doFinalByteArray(context, output, outputOffset + len,
104                 outputLength - outputOffset - len);
105 
106         // Keep the similar behavior as JCE, append the tag to end of output
107         if (this.cipherMode == OpenSsl.ENCRYPT_MODE) {
108             final ByteBuffer tag;
109             tag = ByteBuffer.allocate(getTagLen());
110             evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag);
111             tag.get(output, outputLength - getTagLen(), getTagLen());
112             len += getTagLen();
113         }
114 
115         return len;
116     }
117 
118     @Override
119     public int doFinal(final ByteBuffer input, final ByteBuffer output)
120             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
121         checkState();
122 
123         processAAD();
124 
125         int totalLen = 0;
126         int len;
127         if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
128             final ByteBuffer tag = ByteBuffer.allocate(getTagLen());
129 
130             // if GCM-DECRYPT, we have to handle the buffered input
131             // and the retrieve the trailing tag from input
132             if (inBuffer != null && inBuffer.size() > 0) {
133                 final byte[] inputBytes = new byte[input.remaining()];
134                 input.get(inputBytes, 0, inputBytes.length);
135                 inBuffer.write(inputBytes, 0, inputBytes.length);
136                 final byte[] inputFinal = inBuffer.toByteArray();
137                 inBuffer.reset();
138 
139                 if (inputFinal.length < getTagLen()) {
140                     throw new AEADBadTagException("Input too short - need tag");
141                 }
142 
143                 len = OpenSslNative.updateByteArrayByteBuffer(context, inputFinal, 0,
144                         inputFinal.length - getTagLen(),
145                         output, output.position(), output.remaining());
146 
147                 // retrieve tag
148                 tag.put(inputFinal, inputFinal.length - getTagLen(), getTagLen());
149 
150             } else {
151                 // if no buffered input, just use the input directly
152                 if (input.remaining() < getTagLen()) {
153                     throw new AEADBadTagException("Input too short - need tag");
154                 }
155 
156                 len = OpenSslNative.update(context, input, input.position(),
157                         input.remaining() - getTagLen(), output, output.position(),
158                         output.remaining());
159 
160                 input.position(input.position() + len);
161 
162                 // retrieve tag
163                 tag.put(input);
164             }
165             tag.flip();
166 
167             // set tag to EVP_Cipher for integrity verification in doFinal
168             evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(),
169                     getTagLen(), tag);
170         } else {
171             len = OpenSslNative.update(context, input, input.position(),
172                     input.remaining(), output, output.position(),
173                     output.remaining());
174             input.position(input.limit());
175         }
176 
177         totalLen += len;
178         output.position(output.position() + len);
179 
180         len = OpenSslNative.doFinal(context, output, output.position(),
181                 output.remaining());
182         output.position(output.position() + len);
183         totalLen += len;
184 
185         // Keep the similar behavior as JCE, append the tag to end of output
186         if (this.cipherMode == OpenSsl.ENCRYPT_MODE) {
187             final ByteBuffer tag;
188             tag = ByteBuffer.allocate(getTagLen());
189             evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag);
190             output.put(tag);
191             totalLen += getTagLen();
192         }
193 
194         return totalLen;
195     }
196 
197     /**
198      * Wraps of OpenSslNative.ctrl(long context, int type, int arg, byte[] data)
199      * Since native interface EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) is generic,
200      * it may set/get any native char or long type to the data buffer(ptr).
201      * Here we use ByteBuffer and set nativeOrder to handle the endianness.
202      *
203      * @param context The cipher context address
204      * @param type CtrlValues
205      * @param arg argument like a tag length
206      * @param data byte buffer or null
207      * @return return 0 if there is any error, else return 1.
208      */
209     private int evpCipherCtxCtrl(final long context, final int type, final int arg, final ByteBuffer data) {
210         checkState();
211         try {
212             if (data != null) {
213                 data.order(ByteOrder.nativeOrder());
214                 return OpenSslNative.ctrl(context, type, arg, data.array());
215             }
216             return OpenSslNative.ctrl(context, type, arg, null);
217         } catch (final Exception e) {
218             System.out.println(e.getMessage());
219             return 0;
220         }
221     }
222 
223     private int getTagLen() {
224         return tagBitLen < 0 ? DEFAULT_TAG_LEN : tagBitLen >> 3;
225     }
226 
227     @Override
228     public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params)
229             throws InvalidAlgorithmParameterException {
230 
231         if (aadBuffer == null) {
232             aadBuffer = new ByteArrayOutputStream();
233         } else {
234             aadBuffer.reset();
235         }
236 
237         this.cipherMode = mode;
238         final byte[] iv;
239         if (!(params instanceof GCMParameterSpec)) {
240             // other AlgorithmParameterSpec is not supported now.
241             throw new InvalidAlgorithmParameterException("Illegal parameters");
242         }
243         final GCMParameterSpec gcmParam = (GCMParameterSpec) params;
244         iv = gcmParam.getIV();
245         this.tagBitLen = gcmParam.getTLen();
246 
247         if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
248             inBuffer = new ByteArrayOutputStream();
249         }
250 
251         context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv);
252     }
253 
254     private void processAAD() {
255         if (aadBuffer != null && aadBuffer.size() > 0) {
256             OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), 0, aadBuffer.size(), null, 0, 0);
257             aadBuffer = null;
258         }
259     }
260 
261     @Override
262     public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
263             throws ShortBufferException {
264         checkState();
265 
266         processAAD();
267 
268         if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
269             // store internally until doFinal(decrypt) is called because
270             // spec mentioned that only return recovered data after tag
271             // is successfully verified
272             inBuffer.write(input, inputOffset, inputLen);
273             return 0;
274         }
275         return OpenSslNative.updateByteArray(context, input, inputOffset,
276                 inputLen, output, outputOffset, output.length - outputOffset);
277     }
278 
279     @Override
280     public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
281         checkState();
282 
283         processAAD();
284 
285         final int len;
286         if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
287             // store internally until doFinal(decrypt) is called because
288             // spec mentioned that only return recovered data after tag
289             // is successfully verified
290             final int inputLen = input.remaining();
291             final byte[] inputBuf = new byte[inputLen];
292             input.get(inputBuf, 0, inputLen);
293             inBuffer.write(inputBuf, 0, inputLen);
294             return 0;
295         }
296         len = OpenSslNative.update(context, input, input.position(),
297                 input.remaining(), output, output.position(),
298                 output.remaining());
299         input.position(input.limit());
300         output.position(output.position() + len);
301 
302         return len;
303     }
304 
305     @Override
306     public void updateAAD(final byte[] aad) {
307         // must be called after initialized.
308         if (aadBuffer == null) {
309             // update has already been called
310             throw new IllegalStateException("Update has been called; no more AAD data");
311         }
312         aadBuffer.write(aad, 0, aad.length);
313     }
314 }