• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 package com.google.crypto.tink.subtle;
18 
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.channels.ReadableByteChannel;
22 import java.security.GeneralSecurityException;
23 import java.util.Arrays;
24 
25 /** An instance of {@link ReadableByteChannel} that returns the plaintext for some ciphertext. */
26 class StreamingAeadDecryptingChannel implements ReadableByteChannel {
27   // Each plaintext segment has 16 bytes more of memory than the actual plaintext that it contains.
28   // This is a workaround for an incompatibility between Conscrypt and OpenJDK in their
29   // AES-GCM implementations, see b/67416642, b/31574439, and cr/170969008 for more information.
30   // Conscrypt refused to fix this issue, but even if they fixed it, there are always Android phones
31   // running old versions of Conscrypt, so we decided to take matters into our own hands.
32   // Why 16? Actually any number larger than 16 should work. 16 is the lower bound because it's the
33   // size of the tags of each AES-GCM ciphertext segment.
34   private static final int PLAINTEXT_SEGMENT_EXTRA_SIZE = 16;
35 
36   /* The stream containing the ciphertext */
37   private ReadableByteChannel ciphertextChannel;
38 
39   /**
40    * A buffer containing ciphertext that has not yet been decrypted.
41    * The limit of ciphertextSegment is set such that it can contain segment plus the first
42    * character of the next segment. It is necessary to read a segment plus one more byte
43    * to decrypt a segment, since the last segment of a ciphertext is encrypted differently.
44    */
45   private ByteBuffer ciphertextSegment;
46 
47   /**
48    * A buffer containing a plaintext segment.
49    * The bytes in the range plaintexSegment.position() .. plaintextSegment.limit() - 1
50    * are plaintext that have been decrypted but not yet read out of AesGcmInputStream.
51    */
52   private ByteBuffer plaintextSegment;
53 
54   /* A buffer containg the header information from the ciphertext. */
55   private ByteBuffer header;
56 
57   /* Determines whether the header has been completely read. */
58   private boolean headerRead;
59 
60   /* Indicates whether the end of this InputStream has been reached. */
61   private boolean endOfCiphertext;
62 
63   /* Indicates whether the end of the plaintext has been reached. */
64   private boolean endOfPlaintext;
65 
66   /**
67    * Indicates whether this stream is in a defined state.
68    * Currently the state of this instance becomes undefined when
69    * an authentication error has occurred.
70    */
71   private boolean definedState;
72 
73   /** The associated data that is authenticated with the ciphertext. */
74   private final byte[] associatedData;
75 
76   /**
77    * The number of the current segment of ciphertext buffered in ciphertexSegment.
78    */
79   private int segmentNr;
80 
81   private final StreamSegmentDecrypter decrypter;
82   private final int ciphertextSegmentSize;
83   private final int firstCiphertextSegmentSize;
84 
StreamingAeadDecryptingChannel( NonceBasedStreamingAead streamAead, ReadableByteChannel ciphertextChannel, byte[] associatedData)85   public StreamingAeadDecryptingChannel(
86       NonceBasedStreamingAead streamAead,
87       ReadableByteChannel ciphertextChannel,
88       byte[] associatedData)
89       throws GeneralSecurityException, IOException {
90     decrypter = streamAead.newStreamSegmentDecrypter();
91     this.ciphertextChannel = ciphertextChannel;
92     header = ByteBuffer.allocate(streamAead.getHeaderLength());
93     this.associatedData = Arrays.copyOf(associatedData, associatedData.length);
94 
95     // ciphertextSegment is one byte longer than a ciphertext segment,
96     // so that the code can decide if the current segment is the last segment in the
97     // stream.
98     ciphertextSegmentSize = streamAead.getCiphertextSegmentSize();
99     ciphertextSegment = ByteBuffer.allocate(ciphertextSegmentSize + 1);
100     ciphertextSegment.limit(0);
101     firstCiphertextSegmentSize = ciphertextSegmentSize - streamAead.getCiphertextOffset();
102     plaintextSegment = ByteBuffer.allocate(
103         streamAead.getPlaintextSegmentSize() + PLAINTEXT_SEGMENT_EXTRA_SIZE);
104     plaintextSegment.limit(0);
105     headerRead = false;
106     endOfCiphertext = false;
107     endOfPlaintext = false;
108     segmentNr = 0;
109     definedState = true;
110   }
111 
112   /**
113    * Reads some ciphertext.
114    * @param buffer the destination for the ciphertext.
115    * @throws IOException when an exception reading the ciphertext stream occurs.
116    */
readSomeCiphertext(ByteBuffer buffer)117   private void readSomeCiphertext(ByteBuffer buffer) throws IOException {
118     int read;
119     do {
120       read = ciphertextChannel.read(buffer);
121     } while (read > 0 && buffer.remaining() > 0);
122     if (read == -1) {
123       endOfCiphertext = true;
124     }
125   }
126 
127   /**
128    * Tries to read the header of the ciphertext.
129    * @return true if the header has been fully read and false if not enough bytes were available
130    *          from the ciphertext stream.
131    * @throws IOException when an exception occurs while reading the ciphertextStream or when
132    *         the header is too short.
133    */
tryReadHeader()134   private boolean tryReadHeader() throws IOException {
135     if (endOfCiphertext) {
136       throw new IOException("Ciphertext is too short");
137     }
138     readSomeCiphertext(header);
139     if (header.remaining() > 0) {
140       return false;
141     } else {
142       header.flip();
143       try {
144         decrypter.init(header, associatedData);
145         headerRead = true;
146       } catch (GeneralSecurityException ex) {
147         // TODO(b/74249330): Try to define the state of this.
148         setUndefinedState();
149         throw new IOException(ex);
150       }
151       return true;
152     }
153   }
154 
setUndefinedState()155   private void setUndefinedState() {
156     definedState = false;
157     plaintextSegment.limit(0);
158   }
159 
160   /**
161    * Tries to load the next plaintext segment.
162    */
tryLoadSegment()163   private boolean tryLoadSegment() throws IOException {
164     // Try filling the ciphertextSegment
165     if (!endOfCiphertext) {
166       readSomeCiphertext(ciphertextSegment);
167     }
168     if (ciphertextSegment.remaining() > 0 && !endOfCiphertext) {
169       // we have not enough ciphertext for the next segment
170       return false;
171     }
172     byte lastByte = 0;
173     if (!endOfCiphertext) {
174       lastByte = ciphertextSegment.get(ciphertextSegment.position() - 1);
175       ciphertextSegment.position(ciphertextSegment.position() - 1);
176     }
177     ciphertextSegment.flip();
178     plaintextSegment.clear();
179     try {
180       decrypter.decryptSegment(
181           ciphertextSegment, segmentNr, endOfCiphertext, plaintextSegment);
182     } catch (GeneralSecurityException ex) {
183       // The current segment did not validate.
184       // Currently this means that decryption cannot resume.
185       setUndefinedState();
186       throw new IOException(ex.getMessage() + "\n" + toString()
187                                 + "\nsegmentNr:" + segmentNr
188                                 + " endOfCiphertext:" + endOfCiphertext,
189                             ex);
190     }
191     segmentNr += 1;
192     plaintextSegment.flip();
193     ciphertextSegment.clear();
194     if (!endOfCiphertext) {
195       ciphertextSegment.clear();
196       ciphertextSegment.limit(ciphertextSegmentSize + 1);
197       ciphertextSegment.put(lastByte);
198     }
199     return true;
200   }
201 
202   @Override
read(ByteBuffer dst)203   public synchronized int read(ByteBuffer dst) throws IOException {
204     if (!definedState) {
205       throw new IOException("This StreamingAeadDecryptingChannel is in an undefined state");
206     }
207     if (!headerRead) {
208       if (!tryReadHeader()) {
209         return 0;
210       }
211       ciphertextSegment.clear();
212       ciphertextSegment.limit(firstCiphertextSegmentSize + 1);
213     }
214     if (endOfPlaintext) {
215       return -1;
216     }
217     int startPosition = dst.position();
218     while (dst.remaining() > 0) {
219       if (plaintextSegment.remaining() == 0) {
220         if (endOfCiphertext) {
221           endOfPlaintext = true;
222           break;
223         }
224         if (!tryLoadSegment()) {
225           break;
226         }
227       }
228       if (plaintextSegment.remaining() <= dst.remaining()) {
229         dst.put(plaintextSegment);
230       } else {
231         int sliceSize = dst.remaining();
232         ByteBuffer slice = plaintextSegment.duplicate();
233         slice.limit(slice.position() + sliceSize);
234         dst.put(slice);
235         plaintextSegment.position(plaintextSegment.position() + sliceSize);
236       }
237     }
238     int bytesRead = dst.position() - startPosition;
239     if (bytesRead == 0 && endOfPlaintext) {
240       return -1;
241     } else {
242       return bytesRead;
243     }
244   }
245 
246   @Override
close()247   public synchronized void close() throws IOException {
248     ciphertextChannel.close();
249   }
250 
251   @Override
isOpen()252   public synchronized boolean isOpen() {
253     return ciphertextChannel.isOpen();
254   }
255 
256 
257   /* Returns the state of the channel. */
258   @Override
toString()259   public synchronized String toString() {
260     StringBuilder res =
261       new StringBuilder();
262     res.append("StreamingAeadDecryptingChannel")
263        .append("\nsegmentNr:").append(segmentNr)
264        .append("\nciphertextSegmentSize:").append(ciphertextSegmentSize)
265        .append("\nheaderRead:").append(headerRead)
266        .append("\nendOfCiphertext:").append(endOfCiphertext)
267        .append("\nendOfPlaintext:").append(endOfPlaintext)
268        .append("\ndefinedState:").append(definedState)
269        .append("\nHeader")
270        .append(" position:").append(header.position())
271        .append(" limit:").append(header.position())
272        .append("\nciphertextSgement")
273        .append(" position:").append(ciphertextSegment.position())
274        .append(" limit:").append(ciphertextSegment.limit())
275        .append("\nplaintextSegment")
276        .append(" position:").append(plaintextSegment.position())
277        .append(" limit:").append(plaintextSegment.limit());
278     return res.toString();
279   }
280 }
281