• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package sun.security.ssl;
28 
29 import java.io.*;
30 import java.nio.*;
31 import javax.net.ssl.*;
32 import javax.crypto.BadPaddingException;
33 import sun.misc.HexDumpEncoder;
34 
35 
36 /**
37  * Wrapper class around InputRecord.
38  *
39  * Application data is kept external to the InputRecord,
40  * but handshake data (alert/change_cipher_spec/handshake) will
41  * be kept internally in the ByteArrayInputStream.
42  *
43  * @author Brad Wetmore
44  */
45 final class EngineInputRecord extends InputRecord {
46 
47     private SSLEngineImpl engine;
48 
49     /*
50      * A dummy ByteBuffer we'll pass back even when the data
51      * is stored internally.  It'll never actually be used.
52      */
53     static private ByteBuffer tmpBB = ByteBuffer.allocate(0);
54 
55     /*
56      * Flag to tell whether the last read/parsed data resides
57      * internal in the ByteArrayInputStream, or in the external
58      * buffers.
59      */
60     private boolean internalData;
61 
EngineInputRecord(SSLEngineImpl engine)62     EngineInputRecord(SSLEngineImpl engine) {
63         super();
64         this.engine = engine;
65     }
66 
contentType()67     byte contentType() {
68         if (internalData) {
69             return super.contentType();
70         } else {
71             return ct_application_data;
72         }
73     }
74 
75     /*
76      * Check if there is enough inbound data in the ByteBuffer
77      * to make a inbound packet.  Look for both SSLv2 and SSLv3.
78      *
79      * @return -1 if there are not enough bytes to tell (small header),
80      */
bytesInCompletePacket(ByteBuffer buf)81     int bytesInCompletePacket(ByteBuffer buf) throws SSLException {
82 
83         /*
84          * SSLv2 length field is in bytes 0/1
85          * SSLv3/TLS length field is in bytes 3/4
86          */
87         if (buf.remaining() < 5) {
88             return -1;
89         }
90 
91         int pos = buf.position();
92         byte byteZero = buf.get(pos);
93 
94         int len = 0;
95 
96         /*
97          * If we have already verified previous packets, we can
98          * ignore the verifications steps, and jump right to the
99          * determination.  Otherwise, try one last hueristic to
100          * see if it's SSL/TLS.
101          */
102         if (formatVerified ||
103                 (byteZero == ct_handshake) ||
104                 (byteZero == ct_alert)) {
105             /*
106              * Last sanity check that it's not a wild record
107              */
108             ProtocolVersion recordVersion =
109                 ProtocolVersion.valueOf(buf.get(pos + 1), buf.get(pos + 2));
110 
111             // Check if too old (currently not possible)
112             // or if the major version does not match.
113             // The actual version negotiation is in the handshaker classes
114             if ((recordVersion.v < ProtocolVersion.MIN.v)
115                     || (recordVersion.major > ProtocolVersion.MAX.major)) {
116                 throw new SSLException(
117                     "Unsupported record version " + recordVersion);
118             }
119 
120             /*
121              * Reasonably sure this is a V3, disable further checks.
122              * We can't do the same in the v2 check below, because
123              * read still needs to parse/handle the v2 clientHello.
124              */
125             formatVerified = true;
126 
127             /*
128              * One of the SSLv3/TLS message types.
129              */
130             len = ((buf.get(pos + 3) & 0xff) << 8) +
131                 (buf.get(pos + 4) & 0xff) + headerSize;
132 
133         } else {
134             /*
135              * Must be SSLv2 or something unknown.
136              * Check if it's short (2 bytes) or
137              * long (3) header.
138              *
139              * Internals can warn about unsupported SSLv2
140              */
141             boolean isShort = ((byteZero & 0x80) != 0);
142 
143             if (isShort &&
144                     ((buf.get(pos + 2) == 1) || buf.get(pos + 2) == 4)) {
145 
146                 ProtocolVersion recordVersion =
147                     ProtocolVersion.valueOf(buf.get(pos + 3), buf.get(pos + 4));
148 
149                 // Check if too old (currently not possible)
150                 // or if the major version does not match.
151                 // The actual version negotiation is in the handshaker classes
152                 if ((recordVersion.v < ProtocolVersion.MIN.v)
153                         || (recordVersion.major > ProtocolVersion.MAX.major)) {
154 
155                     // if it's not SSLv2, we're out of here.
156                     if (recordVersion.v != ProtocolVersion.SSL20Hello.v) {
157                         throw new SSLException(
158                             "Unsupported record version " + recordVersion);
159                     }
160                 }
161 
162                 /*
163                  * Client or Server Hello
164                  */
165                 int mask = (isShort ? 0x7f : 0x3f);
166                 len = ((byteZero & mask) << 8) + (buf.get(pos + 1) & 0xff) +
167                     (isShort ? 2 : 3);
168 
169             } else {
170                 // Gobblygook!
171                 throw new SSLException(
172                     "Unrecognized SSL message, plaintext connection?");
173             }
174         }
175 
176         return len;
177     }
178 
179     /*
180      * Pass the data down if it's internally cached, otherwise
181      * do it here.
182      *
183      * If internal data, data is decrypted internally.
184      *
185      * If external data(app), return a new ByteBuffer with data to
186      * process.
187      */
decrypt(MAC signer, CipherBox box, ByteBuffer bb)188     ByteBuffer decrypt(MAC signer,
189             CipherBox box, ByteBuffer bb) throws BadPaddingException {
190 
191         if (internalData) {
192             decrypt(signer, box);   // MAC is checked during decryption
193             return tmpBB;
194         }
195 
196         BadPaddingException reservedBPE = null;
197         int tagLen = signer.MAClen();
198         int cipheredLength = bb.remaining();
199 
200         if (!box.isNullCipher()) {
201             // sanity check length of the ciphertext
202             if (!box.sanityCheck(tagLen, cipheredLength)) {
203                 throw new BadPaddingException(
204                     "ciphertext sanity check failed");
205             }
206 
207             try {
208                 // Note that the CipherBox.decrypt() does not change
209                 // the capacity of the buffer.
210                 box.decrypt(bb, tagLen);
211             } catch (BadPaddingException bpe) {
212                 // RFC 2246 states that decryption_failed should be used
213                 // for this purpose. However, that allows certain attacks,
214                 // so we just send bad record MAC. We also need to make
215                 // sure to always check the MAC to avoid a timing attack
216                 // for the same issue. See paper by Vaudenay et al and the
217                 // update in RFC 4346/5246.
218                 //
219                 // Failover to message authentication code checking.
220                 reservedBPE = bpe;
221             } finally {
222                 bb.rewind();
223             }
224         }
225 
226         if (tagLen != 0) {
227             int macOffset = bb.limit() - tagLen;
228 
229             // Note that although it is not necessary, we run the same MAC
230             // computation and comparison on the payload for both stream
231             // cipher and CBC block cipher.
232             if (bb.remaining() < tagLen) {
233                 // negative data length, something is wrong
234                 if (reservedBPE == null) {
235                     reservedBPE = new BadPaddingException("bad record");
236                 }
237 
238                 // set offset of the dummy MAC
239                 macOffset = cipheredLength - tagLen;
240                 bb.limit(cipheredLength);
241             }
242 
243             // Run MAC computation and comparison on the payload.
244             if (checkMacTags(contentType(), bb, signer, false)) {
245                 if (reservedBPE == null) {
246                     reservedBPE = new BadPaddingException("bad record MAC");
247                 }
248             }
249 
250             // Run MAC computation and comparison on the remainder.
251             //
252             // It is only necessary for CBC block cipher.  It is used to get a
253             // constant time of MAC computation and comparison on each record.
254             if (box.isCBCMode()) {
255                 int remainingLen = calculateRemainingLen(
256                                         signer, cipheredLength, macOffset);
257 
258                 // NOTE: here we use the InputRecord.buf because I did not find
259                 // an effective way to work on ByteBuffer when its capacity is
260                 // less than remainingLen.
261 
262                 // NOTE: remainingLen may be bigger (less than 1 block of the
263                 // hash algorithm of the MAC) than the cipheredLength. However,
264                 // We won't need to worry about it because we always use a
265                 // maximum buffer for every record.  We need a change here if
266                 // we use small buffer size in the future.
267                 if (remainingLen > buf.length) {
268                     // unlikely to happen, just a placehold
269                     throw new RuntimeException(
270                         "Internal buffer capacity error");
271                 }
272 
273                 // Won't need to worry about the result on the remainder. And
274                 // then we won't need to worry about what's actual data to
275                 // check MAC tag on.  We start the check from the header of the
276                 // buffer so that we don't need to construct a new byte buffer.
277                 checkMacTags(contentType(), buf, 0, remainingLen, signer, true);
278             }
279 
280             bb.limit(macOffset);
281         }
282 
283         // Is it a failover?
284         if (reservedBPE != null) {
285             throw reservedBPE;
286         }
287 
288         return bb.slice();
289     }
290 
291     /*
292      * Run MAC computation and comparison
293      *
294      * Please DON'T change the content of the ByteBuffer parameter!
295      */
checkMacTags(byte contentType, ByteBuffer bb, MAC signer, boolean isSimulated)296     private static boolean checkMacTags(byte contentType, ByteBuffer bb,
297             MAC signer, boolean isSimulated) {
298 
299         int tagLen = signer.MAClen();
300         int lim = bb.limit();
301         int macData = lim - tagLen;
302 
303         bb.limit(macData);
304         byte[] hash = signer.compute(contentType, bb, isSimulated);
305         if (hash == null || tagLen != hash.length) {
306             // Something is wrong with MAC implementation.
307             throw new RuntimeException("Internal MAC error");
308         }
309 
310         bb.position(macData);
311         bb.limit(lim);
312         try {
313             int[] results = compareMacTags(bb, hash);
314             return (results[0] != 0);
315         } finally {
316             bb.rewind();
317             bb.limit(macData);
318         }
319     }
320 
321     /*
322      * A constant-time comparison of the MAC tags.
323      *
324      * Please DON'T change the content of the ByteBuffer parameter!
325      */
compareMacTags(ByteBuffer bb, byte[] tag)326     private static int[] compareMacTags(ByteBuffer bb, byte[] tag) {
327 
328         // An array of hits is used to prevent Hotspot optimization for
329         // the purpose of a constant-time check.
330         int[] results = {0, 0};     // {missed #, matched #}
331 
332         // The caller ensures there are enough bytes available in the buffer.
333         // So we won't need to check the remaining of the buffer.
334         for (int i = 0; i < tag.length; i++) {
335             if (bb.get() != tag[i]) {
336                 results[0]++;       // mismatched bytes
337             } else {
338                 results[1]++;       // matched bytes
339             }
340         }
341 
342         return results;
343     }
344 
345     /*
346      * Override the actual write below.  We do things this way to be
347      * consistent with InputRecord.  InputRecord may try to write out
348      * data to the peer, and *then* throw an Exception.  This forces
349      * data to be generated/output before the exception is ever
350      * generated.
351      */
writeBuffer(OutputStream s, byte [] buf, int off, int len)352     void writeBuffer(OutputStream s, byte [] buf, int off, int len)
353             throws IOException {
354         /*
355          * Copy data out of buffer, it's ready to go.
356          */
357         ByteBuffer netBB = (ByteBuffer)
358             (ByteBuffer.allocate(len).put(buf, 0, len).flip());
359         engine.writer.putOutboundDataSync(netBB);
360     }
361 
362     /*
363      * Delineate or read a complete packet from src.
364      *
365      * If internal data (hs, alert, ccs), the data is read and
366      * stored internally.
367      *
368      * If external data (app), return a new ByteBuffer which points
369      * to the data to process.
370      */
read(ByteBuffer srcBB)371     ByteBuffer read(ByteBuffer srcBB) throws IOException {
372         /*
373          * Could have a src == null/dst == null check here,
374          * but that was already checked by SSLEngine.unwrap before
375          * ever attempting to read.
376          */
377 
378         /*
379          * If we have anything besides application data,
380          * or if we haven't even done the initial v2 verification,
381          * we send this down to be processed by the underlying
382          * internal cache.
383          */
384         if (!formatVerified ||
385                 (srcBB.get(srcBB.position()) != ct_application_data)) {
386             internalData = true;
387             read(new ByteBufferInputStream(srcBB), (OutputStream) null);
388             return tmpBB;
389         }
390 
391         internalData = false;
392 
393         int srcPos = srcBB.position();
394         int srcLim = srcBB.limit();
395 
396         ProtocolVersion recordVersion = ProtocolVersion.valueOf(
397                 srcBB.get(srcPos + 1), srcBB.get(srcPos + 2));
398         // Check if too old (currently not possible)
399         // or if the major version does not match.
400         // The actual version negotiation is in the handshaker classes
401         if ((recordVersion.v < ProtocolVersion.MIN.v)
402                 || (recordVersion.major > ProtocolVersion.MAX.major)) {
403             throw new SSLException(
404                 "Unsupported record version " + recordVersion);
405         }
406 
407         /*
408          * It's really application data.  How much to consume?
409          * Jump over the header.
410          */
411         int len = bytesInCompletePacket(srcBB);
412         assert(len > 0);
413 
414         if (debug != null && Debug.isOn("packet")) {
415             try {
416                 HexDumpEncoder hd = new HexDumpEncoder();
417                 srcBB.limit(srcPos + len);
418                 ByteBuffer bb = srcBB.duplicate();  // Use copy of BB
419 
420                 System.out.println("[Raw read (bb)]: length = " + len);
421                 hd.encodeBuffer(bb, System.out);
422             } catch (IOException e) { }
423         }
424 
425         // Demarcate past header to end of packet.
426         srcBB.position(srcPos + headerSize);
427         srcBB.limit(srcPos + len);
428 
429         // Protect remainder of buffer, create slice to actually
430         // operate on.
431         ByteBuffer bb = srcBB.slice();
432 
433         srcBB.position(srcBB.limit());
434         srcBB.limit(srcLim);
435 
436         return bb;
437     }
438 }
439