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