• 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.streamingaead;
18 
19 import com.google.crypto.tink.StreamingAead;
20 import com.google.crypto.tink.subtle.RewindableReadableByteChannel;
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.nio.channels.ReadableByteChannel;
24 import java.security.GeneralSecurityException;
25 import java.util.ArrayDeque;
26 import java.util.Deque;
27 import java.util.List;
28 import javax.annotation.concurrent.GuardedBy;
29 
30 /**
31  * A decrypter for ciphertext given in a {@link ReadableByteChannel}.
32  */
33 final class ReadableByteChannelDecrypter implements ReadableByteChannel {
34   @GuardedBy("this")
35   ReadableByteChannel attemptingChannel;
36   @GuardedBy("this")
37   ReadableByteChannel matchingChannel;
38   @GuardedBy("this")
39   RewindableReadableByteChannel ciphertextChannel;
40 
41   // The StreamingAeads that have not yet been tried in nextAttemptingChannel.
42   Deque<StreamingAead> remainingPrimitives;
43   byte[] associatedData;
44 
45   /**
46    * Constructs a new decrypter for {@code ciphertextChannel}.
47    *
48    * <p>The decrypter picks a matching {@code StreamingAead}-primitive from {@code primitives}, and
49    * uses it for decryption. The matching happens as follows: upon first {@code read()}-call each
50    * candidate primitive reads an initial portion of the channel, until it can determine whether the
51    * channel matches the key of the primitive. If a canditate does not match, then the channel is
52    * reset to its initial position, and the next candiate can attempt matching. The first successful
53    * candidate is then used exclusively on subsequent {@code read()}-calls.
54    *
55    * <p>The matching process uses a buffering wrapper around {@code ciphertextChannel} to enable
56    * resetting of the channel to the initial position. The buffering is removed once the matching is
57    * successful.
58    */
ReadableByteChannelDecrypter( List<StreamingAead> allPrimitives, ReadableByteChannel ciphertextChannel, final byte[] associatedData)59   public ReadableByteChannelDecrypter(
60       List<StreamingAead> allPrimitives,
61       ReadableByteChannel ciphertextChannel,
62       final byte[] associatedData) {
63     // There are 3 phases:
64     // 1) both matchingChannel and attemptingChannel are null. Rewind is enabled.
65     // 2) attemptingChannel is non-null, matchingChannel is null. Rewind is enabled.
66     // 3) attemptingChannel is null, matchingChannel is non-null. Rewind is disabled.
67     this.attemptingChannel = null;
68     this.matchingChannel = null;
69     this.remainingPrimitives = new ArrayDeque<>();
70     for (StreamingAead primitive : allPrimitives) {
71       this.remainingPrimitives.add(primitive);
72     }
73     this.ciphertextChannel = new RewindableReadableByteChannel(ciphertextChannel);
74     this.associatedData = associatedData.clone();
75   }
76 
77   @GuardedBy("this")
nextAttemptingChannel()78   private synchronized ReadableByteChannel nextAttemptingChannel() throws IOException {
79     while (!remainingPrimitives.isEmpty()) {
80       StreamingAead streamingAead = this.remainingPrimitives.removeFirst();
81       try {
82         ReadableByteChannel decChannel = streamingAead.newDecryptingChannel(
83             ciphertextChannel, associatedData);
84         return decChannel;
85       } catch (GeneralSecurityException e) {
86         // Try another primitive.
87         ciphertextChannel.rewind();
88       }
89     }
90     throw new IOException("No matching key found for the ciphertext in the stream.");
91   }
92 
93   @Override
read(ByteBuffer dst)94   public synchronized int read(ByteBuffer dst) throws IOException {
95     if (dst.remaining() == 0) {
96       return 0;
97     }
98     if (matchingChannel != null) {
99       return matchingChannel.read(dst);
100     } else {
101       if (attemptingChannel == null) {
102         attemptingChannel = nextAttemptingChannel();
103       }
104       while (true) {
105         try {
106           int retValue = attemptingChannel.read(dst);
107           if (retValue == 0) {
108             // No data at the moment. Not clear if decryption was successful.
109             // Try again with the same stream next time.
110             return 0;
111           }
112           // Found a matching channel.
113           matchingChannel = attemptingChannel;
114           attemptingChannel = null;
115           ciphertextChannel.disableRewinding();
116           return retValue;
117         } catch (IOException e) {
118           // Try another key.
119           // IOException is thrown e.g. when MAC is incorrect, but also in case
120           // of I/O failures.
121           ciphertextChannel.rewind();
122           attemptingChannel = nextAttemptingChannel();
123         }
124       }
125     }
126   }
127 
128   @Override
close()129   public synchronized void close() throws IOException {
130     ciphertextChannel.close();
131   }
132 
133   @Override
isOpen()134   public synchronized boolean isOpen() {
135     return ciphertextChannel.isOpen();
136   }
137 }
138