-
Notifications
You must be signed in to change notification settings - Fork 122
/
Copy pathFrameDecryptionHandler.java
275 lines (241 loc) · 10.4 KB
/
FrameDecryptionHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
* Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
* in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.amazonaws.encryptionsdk.internal;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.amazonaws.encryptionsdk.CryptoAlgorithm;
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
import com.amazonaws.encryptionsdk.model.CipherFrameHeaders;
/**
* The frame decryption handler is a subclass of the decryption handler and
* thereby provides an implementation of the Cryptography handler.
*
* <p>
* It implements methods for decrypting content that was encrypted and stored in
* frames.
*/
class FrameDecryptionHandler implements CryptoHandler {
private final SecretKey decryptionKey_;
private final CryptoAlgorithm cryptoAlgo_;
private final CipherHandler cipherHandler_;
private final byte[] messageId_;
private final short nonceLen_;
private CipherFrameHeaders currentFrameHeaders_;
private final int frameSize_;
private long frameNumber_ = 1;
boolean complete_ = false;
private byte[] unparsedBytes_ = new byte[0];
/**
* Construct a decryption handler for decrypting bytes stored in frames.
*
* @param customerMasterKey
* the master key to use when unwrapping the data key encoded in
* the ciphertext.
*/
public FrameDecryptionHandler(final SecretKey decryptionKey, final short nonceLen,
final CryptoAlgorithm cryptoAlgo, final byte[] messageId, final int frameLen) {
decryptionKey_ = decryptionKey;
nonceLen_ = nonceLen;
cryptoAlgo_ = cryptoAlgo;
messageId_ = messageId;
frameSize_ = frameLen;
cipherHandler_ = new CipherHandler(decryptionKey_, Cipher.DECRYPT_MODE, cryptoAlgo_);
}
/**
* Decrypt the ciphertext bytes containing content encrypted using frames and put the plaintext
* bytes into out.
*
* <p>
* It decrypts by performing the following operations:
* <ol>
* <li>parse the ciphertext headers</li>
* <li>parse the ciphertext until encrypted content in a frame is available</li>
* <li>decrypt the encrypted content</li>
* <li>return decrypted bytes as output</li>
* </ol>
*
* @param in
* the input byte array.
* @param inOff
* the offset into the in array where the data to be decrypted starts.
* @param inLen
* the number of bytes to be decrypted.
* @param out
* the output buffer the decrypted plaintext bytes go into.
* @param outOff
* the offset into the output byte array the decrypted data starts at.
* @return the number of bytes written to out and processed
* @throws InvalidCiphertextException
* if frame number is invalid/out-of-order or if the bytes do not decrypt correctly.
* @throws AwsCryptoException
* if the content type found in the headers is not of frame type.
*/
@Override
public ProcessingSummary processBytes(final byte[] in, final int off, final int len, final byte[] out,
final int outOff)
throws BadCiphertextException, AwsCryptoException {
final long totalBytesToParse = unparsedBytes_.length + (long) len;
if (totalBytesToParse > Integer.MAX_VALUE) {
throw new AwsCryptoException(
"Integer overflow of the total bytes to parse and decrypt occured.");
}
final byte[] bytesToParse = new byte[(int) totalBytesToParse];
// If there were previously unparsed bytes, add them as the first
// set of bytes to be parsed in this call.
System.arraycopy(unparsedBytes_, 0, bytesToParse, 0, unparsedBytes_.length);
System.arraycopy(in, off, bytesToParse, unparsedBytes_.length, len);
int actualOutLen = 0;
int totalParsedBytes = 0;
// Parse available bytes. Stop parsing when there aren't enough
// bytes to complete parsing:
// - the ciphertext headers
// - the cipher frame
while (!complete_ && totalParsedBytes < bytesToParse.length) {
if (currentFrameHeaders_ == null) {
currentFrameHeaders_ = new CipherFrameHeaders();
currentFrameHeaders_.setNonceLength(nonceLen_);
}
totalParsedBytes += currentFrameHeaders_.deserialize(bytesToParse, totalParsedBytes);
// if we have all frame fields, process the encrypted content.
if (currentFrameHeaders_.isComplete() == true) {
int protectedContentLen = -1;
if (currentFrameHeaders_.isFinalFrame()) {
protectedContentLen = currentFrameHeaders_.getFrameContentLength();
// The final frame should not be able to exceed the frameLength
if(protectedContentLen > frameSize_) {
throw new BadCiphertextException("Final frame length exceeds frame length.");
}
} else {
protectedContentLen = frameSize_;
}
// include the tag which is added by the underlying cipher.
protectedContentLen += cryptoAlgo_.getTagLen();
if ((bytesToParse.length - totalParsedBytes) < protectedContentLen) {
// if we don't have all of the encrypted bytes, break
// until they become available.
break;
}
final byte[] bytesToDecrypt_ = Arrays.copyOfRange(bytesToParse, totalParsedBytes, totalParsedBytes
+ protectedContentLen);
totalParsedBytes += protectedContentLen;
if (frameNumber_ == Constants.MAX_FRAME_NUMBER) {
throw new BadCiphertextException("Frame number exceeds the maximum allowed value.");
}
final byte[] decryptedBytes = decryptContent(bytesToDecrypt_, 0, bytesToDecrypt_.length);
System.arraycopy(decryptedBytes, 0, out, (outOff + actualOutLen), decryptedBytes.length);
actualOutLen += decryptedBytes.length;
frameNumber_++;
complete_ = currentFrameHeaders_.isFinalFrame();
// reset frame headers as we are done processing current frame.
currentFrameHeaders_ = null;
} else {
// if there aren't enough bytes to parse cipher frame,
// we can't continue parsing.
break;
}
}
if (!complete_) {
// buffer remaining bytes for parsing in the next round.
unparsedBytes_ = Arrays.copyOfRange(bytesToParse, totalParsedBytes, bytesToParse.length);
return new ProcessingSummary(actualOutLen, len);
} else {
final ProcessingSummary result = new ProcessingSummary(actualOutLen, totalParsedBytes
- unparsedBytes_.length);
unparsedBytes_ = new byte[0];
return result;
}
}
/**
* Finish processing of the bytes. This function does nothing since the
* final frame will be processed and decrypted in processBytes().
*
* @param out
* space for any resulting output data.
* @param outOff
* offset into out to start copying the data at.
* @return
* 0
*/
@Override
public int doFinal(final byte[] out, final int outOff) {
return 0;
}
/**
* Return the size of the output buffer required for a processBytes plus a
* doFinal with an input of inLen bytes.
*
* @param inLen
* the length of the input.
* @return
* the space required to accommodate a call to processBytes and
* doFinal with len bytes of input.
*/
@Override
public int estimateOutputSize(final int inLen) {
int outSize = 0;
final int totalBytesToDecrypt = unparsedBytes_.length + inLen;
if (totalBytesToDecrypt > 0) {
int frames = totalBytesToDecrypt / frameSize_;
frames += 1; // add one for final frame which might be < frame size.
outSize += (frameSize_ * frames);
}
return outSize;
}
@Override
public int estimatePartialOutputSize(int inLen) {
return estimateOutputSize(inLen);
}
@Override
public int estimateFinalOutputSize() {
return 0;
}
/**
* Returns the plaintext bytes of the encrypted content.
*
* @param input
* the input bytes containing the content
* @param off
* the offset into the input array where the data to be decrypted
* starts.
* @param len
* the number of bytes to be decrypted.
* @return
* the plaintext bytes of the encrypted content.
* @throws BadCiphertextException
* if the bytes do not decrypt correctly.
*/
private byte[] decryptContent(final byte[] input, final int off, final int len) throws BadCiphertextException {
final byte[] nonce = currentFrameHeaders_.getNonce();
byte[] contentAad = null;
if (currentFrameHeaders_.isFinalFrame() == true) {
contentAad = Utils.generateContentAad(
messageId_,
Constants.FINAL_FRAME_STRING_ID,
(int) frameNumber_,
currentFrameHeaders_.getFrameContentLength());
} else {
contentAad = Utils.generateContentAad(
messageId_,
Constants.FRAME_STRING_ID,
(int) frameNumber_,
frameSize_);
}
return cipherHandler_.cipherData(nonce, contentAad, input, off, len);
}
@Override
public boolean isComplete() {
return complete_;
}
}