--- a/jdk/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,7 @@
import java.util.List;
/**
- * Extends the <code>SSLSession</code> interface to support additional
+ * Extends the {@code SSLSession} interface to support additional
* session attributes.
*
* @since 1.7
@@ -39,8 +39,8 @@
* is willing to use.
* <p>
* Note: this method is used to indicate to the peer which signature
- * algorithms may be used for digital signatures in TLS 1.2. It is
- * not meaningful for TLS versions prior to 1.2.
+ * algorithms may be used for digital signatures in TLS/DTLS 1.2. It is
+ * not meaningful for TLS/DTLS versions prior to 1.2.
* <p>
* The signature algorithm name must be a standard Java Security
* name (such as "SHA1withRSA", "SHA256withECDSA", and so on).
@@ -52,7 +52,7 @@
* Note: the local supported signature algorithms should conform to
* the algorithm constraints specified by
* {@link SSLParameters#getAlgorithmConstraints getAlgorithmConstraints()}
- * method in <code>SSLParameters</code>.
+ * method in {@code SSLParameters}.
*
* @return An array of supported signature algorithms, in descending
* order of preference. The return value is an empty array if
@@ -67,8 +67,8 @@
* able to use.
* <p>
* Note: this method is used to indicate to the local side which signature
- * algorithms may be used for digital signatures in TLS 1.2. It is
- * not meaningful for TLS versions prior to 1.2.
+ * algorithms may be used for digital signatures in TLS/DTLS 1.2. It is
+ * not meaningful for TLS/DTLS versions prior to 1.2.
* <p>
* The signature algorithm name must be a standard Java Security
* name (such as "SHA1withRSA", "SHA256withECDSA", and so on).
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SNIServerName.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SNIServerName.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -31,7 +31,7 @@
* Instances of this class represent a server name in a Server Name
* Indication (SNI) extension.
* <P>
- * The SNI extension is a feature that extends the SSL/TLS protocols to
+ * The SNI extension is a feature that extends the SSL/TLS/DTLS protocols to
* indicate what server name the client is attempting to connect to during
* handshaking. See section 3, "Server Name Indication", of <A
* HREF="http://www.ietf.org/rfc/rfc6066.txt">TLS Extensions (RFC 6066)</A>.
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLContext.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLContext.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,12 +32,12 @@
/**
* Instances of this class represent a secure socket protocol
* implementation which acts as a factory for secure socket
- * factories or <code>SSLEngine</code>s. This class is initialized
+ * factories or {@code SSLEngine}s. This class is initialized
* with an optional set of key and trust managers and source of
* secure random bytes.
*
* <p> Every implementation of the Java platform is required to support the
- * following standard <code>SSLContext</code> protocol:
+ * following standard {@code SSLContext} protocol:
* <ul>
* <li><tt>TLSv1</tt></li>
* </ul>
@@ -79,7 +79,7 @@
* <p>If a default context was set using the {@link #setDefault
* SSLContext.setDefault()} method, it is returned. Otherwise, the first
* call of this method triggers the call
- * <code>SSLContext.getInstance("Default")</code>.
+ * {@code SSLContext.getInstance("Default")}.
* If successful, that object is made the default SSL context and returned.
*
* <p>The default context is immediately
@@ -106,8 +106,8 @@
* @param context the SSLContext
* @throws NullPointerException if context is null
* @throws SecurityException if a security manager exists and its
- * <code>checkPermission</code> method does not allow
- * <code>SSLPermission("setDefaultSSLContext")</code>
+ * {@code checkPermission} method does not allow
+ * {@code SSLPermission("setDefaultSSLContext")}
* @since 1.6
*/
public static synchronized void setDefault(SSLContext context) {
@@ -122,7 +122,7 @@
}
/**
- * Returns a <code>SSLContext</code> object that implements the
+ * Returns a {@code SSLContext} object that implements the
* specified secure socket protocol.
*
* <p> This method traverses the list of registered security Providers,
@@ -141,7 +141,7 @@
* Documentation</a>
* for information about standard protocol names.
*
- * @return the new <code>SSLContext</code> object.
+ * @return the new {@code SSLContext} object.
*
* @exception NoSuchAlgorithmException if no Provider supports a
* SSLContextSpi implementation for the
@@ -159,7 +159,7 @@
}
/**
- * Returns a <code>SSLContext</code> object that implements the
+ * Returns a {@code SSLContext} object that implements the
* specified secure socket protocol.
*
* <p> A new SSLContext object encapsulating the
@@ -179,7 +179,7 @@
*
* @param provider the name of the provider.
*
- * @return the new <code>SSLContext</code> object.
+ * @return the new {@code SSLContext} object.
*
* @throws NoSuchAlgorithmException if a SSLContextSpi
* implementation for the specified protocol is not
@@ -202,7 +202,7 @@
}
/**
- * Returns a <code>SSLContext</code> object that implements the
+ * Returns a {@code SSLContext} object that implements the
* specified secure socket protocol.
*
* <p> A new SSLContext object encapsulating the
@@ -219,7 +219,7 @@
*
* @param provider an instance of the provider.
*
- * @return the new <code>SSLContext</code> object.
+ * @return the new {@code SSLContext} object.
*
* @throws NoSuchAlgorithmException if a SSLContextSpi
* implementation for the specified protocol is not available
@@ -239,22 +239,22 @@
}
/**
- * Returns the protocol name of this <code>SSLContext</code> object.
+ * Returns the protocol name of this {@code SSLContext} object.
*
* <p>This is the same name that was specified in one of the
- * <code>getInstance</code> calls that created this
- * <code>SSLContext</code> object.
+ * {@code getInstance} calls that created this
+ * {@code SSLContext} object.
*
- * @return the protocol name of this <code>SSLContext</code> object.
+ * @return the protocol name of this {@code SSLContext} object.
*/
public final String getProtocol() {
return this.protocol;
}
/**
- * Returns the provider of this <code>SSLContext</code> object.
+ * Returns the provider of this {@code SSLContext} object.
*
- * @return the provider of this <code>SSLContext</code> object
+ * @return the provider of this {@code SSLContext} object
*/
public final Provider getProvider() {
return this.provider;
@@ -283,31 +283,35 @@
}
/**
- * Returns a <code>SocketFactory</code> object for this
+ * Returns a {@code SocketFactory} object for this
* context.
*
- * @return the <code>SocketFactory</code> object
+ * @return the {@code SocketFactory} object
+ * @throws UnsupportedOperationException if the underlying provider
+ * does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>init()</code> has not been called
+ * initialization and the {@code init()} has not been called
*/
public final SSLSocketFactory getSocketFactory() {
return contextSpi.engineGetSocketFactory();
}
/**
- * Returns a <code>ServerSocketFactory</code> object for
+ * Returns a {@code ServerSocketFactory} object for
* this context.
*
- * @return the <code>ServerSocketFactory</code> object
+ * @return the {@code ServerSocketFactory} object
+ * @throws UnsupportedOperationException if the underlying provider
+ * does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>init()</code> has not been called
+ * initialization and the {@code init()} has not been called
*/
public final SSLServerSocketFactory getServerSocketFactory() {
return contextSpi.engineGetServerSocketFactory();
}
/**
- * Creates a new <code>SSLEngine</code> using this context.
+ * Creates a new {@code SSLEngine} using this context.
* <P>
* Applications using this factory method are providing no hints
* for an internal session reuse strategy. If hints are desired,
@@ -317,11 +321,11 @@
* Some cipher suites (such as Kerberos) require remote hostname
* information, in which case this factory method should not be used.
*
- * @return the <code>SSLEngine</code> object
+ * @return the {@code SSLEngine} object
* @throws UnsupportedOperationException if the underlying provider
* does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>init()</code> has not been called
+ * initialization and the {@code init()} has not been called
* @since 1.5
*/
public final SSLEngine createSSLEngine() {
@@ -338,7 +342,7 @@
}
/**
- * Creates a new <code>SSLEngine</code> using this context using
+ * Creates a new {@code SSLEngine} using this context using
* advisory peer information.
* <P>
* Applications using this factory method are providing hints
@@ -349,11 +353,11 @@
*
* @param peerHost the non-authoritative name of the host
* @param peerPort the non-authoritative port
- * @return the new <code>SSLEngine</code> object
+ * @return the new {@code SSLEngine} object
* @throws UnsupportedOperationException if the underlying provider
* does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>init()</code> has not been called
+ * initialization and the {@code init()} has not been called
* @since 1.5
*/
public final SSLEngine createSSLEngine(String peerHost, int peerPort) {
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLContextSpi.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLContextSpi.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -29,7 +29,7 @@
/**
* This class defines the <i>Service Provider Interface</i> (<b>SPI</b>)
- * for the <code>SSLContext</code> class.
+ * for the {@code SSLContext} class.
*
* <p> All the abstract methods in this class must be implemented by each
* cryptographic service provider who wishes to supply the implementation
@@ -52,31 +52,35 @@
SecureRandom sr) throws KeyManagementException;
/**
- * Returns a <code>SocketFactory</code> object for this
+ * Returns a {@code SocketFactory} object for this
* context.
*
- * @return the <code>SocketFactory</code> object
+ * @return the {@code SocketFactory} object
+ * @throws UnsupportedOperationException if the underlying provider
+ * does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>engineInit()</code>
+ * initialization and the {@code engineInit()}
* has not been called
* @see javax.net.ssl.SSLContext#getSocketFactory()
*/
protected abstract SSLSocketFactory engineGetSocketFactory();
/**
- * Returns a <code>ServerSocketFactory</code> object for
+ * Returns a {@code ServerSocketFactory} object for
* this context.
*
- * @return the <code>ServerSocketFactory</code> object
+ * @return the {@code ServerSocketFactory} object
+ * @throws UnsupportedOperationException if the underlying provider
+ * does not implement the operation.
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>engineInit()</code>
+ * initialization and the {@code engineInit()}
* has not been called
* @see javax.net.ssl.SSLContext#getServerSocketFactory()
*/
protected abstract SSLServerSocketFactory engineGetServerSocketFactory();
/**
- * Creates a new <code>SSLEngine</code> using this context.
+ * Creates a new {@code SSLEngine} using this context.
* <P>
* Applications using this factory method are providing no hints
* for an internal session reuse strategy. If hints are desired,
@@ -86,9 +90,9 @@
* Some cipher suites (such as Kerberos) require remote hostname
* information, in which case this factory method should not be used.
*
- * @return the <code>SSLEngine</code> Object
+ * @return the {@code SSLEngine} Object
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>engineInit()</code>
+ * initialization and the {@code engineInit()}
* has not been called
*
* @see SSLContext#createSSLEngine()
@@ -98,7 +102,7 @@
protected abstract SSLEngine engineCreateSSLEngine();
/**
- * Creates a <code>SSLEngine</code> using this context.
+ * Creates a {@code SSLEngine} using this context.
* <P>
* Applications using this factory method are providing hints
* for an internal session reuse strategy.
@@ -108,9 +112,9 @@
*
* @param host the non-authoritative name of the host
* @param port the non-authoritative port
- * @return the <code>SSLEngine</code> Object
+ * @return the {@code SSLEngine} Object
* @throws IllegalStateException if the SSLContextImpl requires
- * initialization and the <code>engineInit()</code>
+ * initialization and the {@code engineInit()}
* has not been called
*
* @see SSLContext#createSSLEngine(String, int)
@@ -120,19 +124,19 @@
protected abstract SSLEngine engineCreateSSLEngine(String host, int port);
/**
- * Returns a server <code>SSLSessionContext</code> object for
+ * Returns a server {@code SSLSessionContext} object for
* this context.
*
- * @return the <code>SSLSessionContext</code> object
+ * @return the {@code SSLSessionContext} object
* @see javax.net.ssl.SSLContext#getServerSessionContext()
*/
protected abstract SSLSessionContext engineGetServerSessionContext();
/**
- * Returns a client <code>SSLSessionContext</code> object for
+ * Returns a client {@code SSLSessionContext} object for
* this context.
*
- * @return the <code>SSLSessionContext</code> object
+ * @return the {@code SSLSessionContext} object
* @see javax.net.ssl.SSLContext#getClientSessionContext()
*/
protected abstract SSLSessionContext engineGetClientSessionContext();
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngine.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -37,15 +37,15 @@
* <P>
* The secure communications modes include: <UL>
*
- * <LI> <em>Integrity Protection</em>. SSL/TLS protects against
+ * <LI> <em>Integrity Protection</em>. SSL/TLS/DTLS protects against
* modification of messages by an active wiretapper.
*
- * <LI> <em>Authentication</em>. In most modes, SSL/TLS provides
+ * <LI> <em>Authentication</em>. In most modes, SSL/TLS/DTLS provides
* peer authentication. Servers are usually authenticated, and
* clients may be authenticated as requested by servers.
*
* <LI> <em>Confidentiality (Privacy Protection)</em>. In most
- * modes, SSL/TLS encrypts data being sent between client and
+ * modes, SSL/TLS/DTLS encrypts data being sent between client and
* server. This protects the confidentiality of data, so that
* passive wiretappers won't see sensitive data such as financial
* information or personal information of many kinds.
@@ -65,19 +65,19 @@
* handshaking has completed, you can access session attributes by
* using the {@link #getSession()} method.
* <P>
- * The <code>SSLSocket</code> class provides much of the same security
+ * The {@code SSLSocket} class provides much of the same security
* functionality, but all of the inbound and outbound data is
* automatically transported using the underlying {@link
* java.net.Socket Socket}, which by design uses a blocking model.
* While this is appropriate for many applications, this model does not
* provide the scalability required by large servers.
* <P>
- * The primary distinction of an <code>SSLEngine</code> is that it
+ * The primary distinction of an {@code SSLEngine} is that it
* operates on inbound and outbound byte streams, independent of the
* transport mechanism. It is the responsibility of the
- * <code>SSLEngine</code> user to arrange for reliable I/O transport to
- * the peer. By separating the SSL/TLS abstraction from the I/O
- * transport mechanism, the <code>SSLEngine</code> can be used for a
+ * {@code SSLEngine} user to arrange for reliable I/O transport to
+ * the peer. By separating the SSL/TLS/DTLS abstraction from the I/O
+ * transport mechanism, the {@code SSLEngine} can be used for a
* wide variety of I/O types, such as {@link
* java.nio.channels.spi.AbstractSelectableChannel#configureBlocking(boolean)
* non-blocking I/O (polling)}, {@link java.nio.channels.Selector
@@ -87,7 +87,7 @@
* HREF="http://www.jcp.org/en/jsr/detail?id=203"> future asynchronous
* I/O models </A>, and so on.
* <P>
- * At a high level, the <code>SSLEngine</code> appears thus:
+ * At a high level, the {@code SSLEngine} appears thus:
*
* <pre>
* app data
@@ -115,18 +115,18 @@
* mechanism. Inbound data is data which has been received from the
* peer, and outbound data is destined for the peer.
* <P>
- * (In the context of an <code>SSLEngine</code>, the term "handshake
+ * (In the context of an {@code SSLEngine}, the term "handshake
* data" is taken to mean any data exchanged to establish and control a
- * secure connection. Handshake data includes the SSL/TLS messages
+ * secure connection. Handshake data includes the SSL/TLS/DTLS messages
* "alert", "change_cipher_spec," and "handshake.")
* <P>
- * There are five distinct phases to an <code>SSLEngine</code>.
+ * There are five distinct phases to an {@code SSLEngine}.
*
* <OL>
- * <li> Creation - The <code>SSLEngine</code> has been created and
+ * <li> Creation - The {@code SSLEngine} has been created and
* initialized, but has not yet been used. During this phase, an
- * application may set any <code>SSLEngine</code>-specific settings
- * (enabled cipher suites, whether the <code>SSLEngine</code> should
+ * application may set any {@code SSLEngine}-specific settings
+ * (enabled cipher suites, whether the {@code SSLEngine} should
* handshake in client or server mode, and so on). Once
* handshaking has begun, though, any new settings (except
* client/server mode, see below) will be used for
@@ -139,7 +139,7 @@
*
* <li> Application Data - Once the communication parameters have
* been established and the handshake is complete, application data
- * may flow through the <code>SSLEngine</code>. Outbound
+ * may flow through the {@code SSLEngine}. Outbound
* application messages are encrypted and integrity protected,
* and inbound messages reverse the process.
*
@@ -147,50 +147,50 @@
* the session at any time during the Application Data phase. New
* handshaking data can be intermixed among the application data.
* Before starting the rehandshake phase, the application may
- * reset the SSL/TLS communication parameters such as the list of
+ * reset the SSL/TLS/DTLS communication parameters such as the list of
* enabled ciphersuites and whether to use client authentication,
* but can not change between client/server modes. As before, once
- * handshaking has begun, any new <code>SSLEngine</code>
+ * handshaking has begun, any new {@code SSLEngine}
* configuration settings will not be used until the next
* handshake.
*
* <li> Closure - When the connection is no longer needed, the
- * application should close the <code>SSLEngine</code> and should
+ * application should close the {@code SSLEngine} and should
* send/receive any remaining messages to the peer before
* closing the underlying transport mechanism. Once an engine is
- * closed, it is not reusable: a new <code>SSLEngine</code> must
+ * closed, it is not reusable: a new {@code SSLEngine} must
* be created.
* </OL>
- * An <code>SSLEngine</code> is created by calling {@link
+ * An {@code SSLEngine} is created by calling {@link
* SSLContext#createSSLEngine()} from an initialized
- * <code>SSLContext</code>. Any configuration
+ * {@code SSLContext}. Any configuration
* parameters should be set before making the first call to
- * <code>wrap()</code>, <code>unwrap()</code>, or
- * <code>beginHandshake()</code>. These methods all trigger the
+ * {@code wrap()}, {@code unwrap()}, or
+ * {@code beginHandshake()}. These methods all trigger the
* initial handshake.
* <P>
* Data moves through the engine by calling {@link #wrap(ByteBuffer,
* ByteBuffer) wrap()} or {@link #unwrap(ByteBuffer, ByteBuffer)
* unwrap()} on outbound or inbound data, respectively. Depending on
- * the state of the <code>SSLEngine</code>, a <code>wrap()</code> call
+ * the state of the {@code SSLEngine}, a {@code wrap()} call
* may consume application data from the source buffer and may produce
* network data in the destination buffer. The outbound data
* may contain application and/or handshake data. A call to
- * <code>unwrap()</code> will examine the source buffer and may
+ * {@code unwrap()} will examine the source buffer and may
* advance the handshake if the data is handshaking information, or
* may place application data in the destination buffer if the data
- * is application. The state of the underlying SSL/TLS algorithm
+ * is application. The state of the underlying SSL/TLS/DTLS algorithm
* will determine when data is consumed and produced.
* <P>
- * Calls to <code>wrap()</code> and <code>unwrap()</code> return an
- * <code>SSLEngineResult</code> which indicates the status of the
+ * Calls to {@code wrap()} and {@code unwrap()} return an
+ * {@code SSLEngineResult} which indicates the status of the
* operation, and (optionally) how to interact with the engine to make
* progress.
* <P>
- * The <code>SSLEngine</code> produces/consumes complete SSL/TLS
+ * The {@code SSLEngine} produces/consumes complete SSL/TLS/DTLS
* packets only, and does not store application data internally between
- * calls to <code>wrap()/unwrap()</code>. Thus input and output
- * <code>ByteBuffer</code>s must be sized appropriately to hold the
+ * calls to {@code wrap()/unwrap()}. Thus input and output
+ * {@code ByteBuffer}s must be sized appropriately to hold the
* maximum record that can be produced. Calls to {@link
* SSLSession#getPacketBufferSize()} and {@link
* SSLSession#getApplicationBufferSize()} should be used to determine
@@ -200,12 +200,12 @@
* must determine (via {@link SSLEngineResult}) and correct the
* problem, and then try the call again.
* <P>
- * For example, <code>unwrap()</code> will return a {@link
+ * For example, {@code unwrap()} will return a {@link
* SSLEngineResult.Status#BUFFER_OVERFLOW} result if the engine
* determines that there is not enough destination buffer space available.
* Applications should call {@link SSLSession#getApplicationBufferSize()}
* and compare that value with the space available in the destination buffer,
- * enlarging the buffer if necessary. Similarly, if <code>unwrap()</code>
+ * enlarging the buffer if necessary. Similarly, if {@code unwrap()}
* were to return a {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}, the
* application should call {@link SSLSession#getPacketBufferSize()} to ensure
* that the source buffer has enough room to hold a record (enlarging if
@@ -241,8 +241,8 @@
* }</pre>
*
* <P>
- * Unlike <code>SSLSocket</code>, all methods of SSLEngine are
- * non-blocking. <code>SSLEngine</code> implementations may
+ * Unlike {@code SSLSocket}, all methods of SSLEngine are
+ * non-blocking. {@code SSLEngine} implementations may
* require the results of tasks that may take an extended period of
* time to complete, or may even block. For example, a TrustManager
* may need to connect to a remote certificate validation service,
@@ -252,8 +252,8 @@
* seemingly blocking.
* <P>
* For any operation which may potentially block, the
- * <code>SSLEngine</code> will create a {@link java.lang.Runnable}
- * delegated task. When <code>SSLEngineResult</code> indicates that a
+ * {@code SSLEngine} will create a {@link java.lang.Runnable}
+ * delegated task. When {@code SSLEngineResult} indicates that a
* delegated task result is needed, the application must call {@link
* #getDelegatedTask()} to obtain an outstanding delegated task and
* call its {@link java.lang.Runnable#run() run()} method (possibly using
@@ -262,16 +262,16 @@
* exist, and try the original operation again.
* <P>
* At the end of a communication session, applications should properly
- * close the SSL/TLS link. The SSL/TLS protocols have closure handshake
- * messages, and these messages should be communicated to the peer
- * before releasing the <code>SSLEngine</code> and closing the
+ * close the SSL/TLS/DTLS link. The SSL/TLS/DTLS protocols have closure
+ * handshake messages, and these messages should be communicated to the
+ * peer before releasing the {@code SSLEngine} and closing the
* underlying transport mechanism. A close can be initiated by one of:
* an SSLException, an inbound closure handshake message, or one of the
* close methods. In all cases, closure handshake messages are
- * generated by the engine, and <code>wrap()</code> should be repeatedly
- * called until the resulting <code>SSLEngineResult</code>'s status
+ * generated by the engine, and {@code wrap()} should be repeatedly
+ * called until the resulting {@code SSLEngineResult}'s status
* returns "CLOSED", or {@link #isOutboundDone()} returns true. All
- * data obtained from the <code>wrap()</code> method should be sent to the
+ * data obtained from the {@code wrap()} method should be sent to the
* peer.
* <P>
* {@link #closeOutbound()} is used to signal the engine that the
@@ -279,12 +279,12 @@
* <P>
* A peer will signal its intent to close by sending its own closure
* handshake message. After this message has been received and
- * processed by the local <code>SSLEngine</code>'s <code>unwrap()</code>
+ * processed by the local {@code SSLEngine}'s {@code unwrap()}
* call, the application can detect the close by calling
- * <code>unwrap()</code> and looking for a <code>SSLEngineResult</code>
+ * {@code unwrap()} and looking for a {@code SSLEngineResult}
* with status "CLOSED", or if {@link #isInboundDone()} returns true.
* If for some reason the peer closes the communication link without
- * sending the proper SSL/TLS closure message, the application can
+ * sending the proper SSL/TLS/DTLS closure message, the application can
* detect the end-of-stream and can signal the engine via {@link
* #closeInbound()} that there will no more inbound messages to
* process. Some applications might choose to require orderly shutdown
@@ -315,16 +315,16 @@
* and/or non-private (unencrypted) communications will such a
* cipher suite be selected.
* <P>
- * Each SSL/TLS connection must have one client and one server, thus
+ * Each SSL/TLS/DTLS connection must have one client and one server, thus
* each endpoint must decide which role to assume. This choice determines
* who begins the handshaking process as well as which type of messages
* should be sent by each party. The method {@link
* #setUseClientMode(boolean)} configures the mode. Once the initial
- * handshaking has started, an <code>SSLEngine</code> can not switch
+ * handshaking has started, an {@code SSLEngine} can not switch
* between client and server modes, even when performing renegotiations.
* <P>
* Applications might choose to process delegated tasks in different
- * threads. When an <code>SSLEngine</code>
+ * threads. When an {@code SSLEngine}
* is created, the current {@link java.security.AccessControlContext}
* is saved. All future delegated tasks will be processed using this
* context: that is, all access control decisions will be made using the
@@ -336,10 +336,10 @@
* There are two concurrency issues to be aware of:
*
* <OL>
- * <li>The <code>wrap()</code> and <code>unwrap()</code> methods
+ * <li>The {@code wrap()} and {@code unwrap()} methods
* may execute concurrently of each other.
*
- * <li> The SSL/TLS protocols employ ordered packets.
+ * <li> The SSL/TLS/DTLS protocols employ ordered packets.
* Applications must take care to ensure that generated packets
* are delivered in sequence. If packets arrive
* out-of-order, unexpected or fatal results may occur.
@@ -354,7 +354,7 @@
* </pre>
*
* As a corollary, two threads must not attempt to call the same method
- * (either <code>wrap()</code> or <code>unwrap()</code>) concurrently,
+ * (either {@code wrap()} or {@code unwrap()}) concurrently,
* because there is no way to guarantee the eventual packet ordering.
* </OL>
*
@@ -374,7 +374,7 @@
private int peerPort = -1;
/**
- * Constructor for an <code>SSLEngine</code> providing no hints
+ * Constructor for an {@code SSLEngine} providing no hints
* for an internal session reuse strategy.
*
* @see SSLContext#createSSLEngine()
@@ -384,10 +384,10 @@
}
/**
- * Constructor for an <code>SSLEngine</code>.
+ * Constructor for an {@code SSLEngine}.
* <P>
- * <code>SSLEngine</code> implementations may use the
- * <code>peerHost</code> and <code>peerPort</code> parameters as hints
+ * {@code SSLEngine} implementations may use the
+ * {@code peerHost} and {@code peerPort} parameters as hints
* for their internal session reuse strategy.
* <P>
* Some cipher suites (such as Kerberos) require remote hostname
@@ -395,7 +395,7 @@
* constructor to use Kerberos.
* <P>
* The parameters are not authenticated by the
- * <code>SSLEngine</code>.
+ * {@code SSLEngine}.
*
* @param peerHost the name of the peer host
* @param peerPort the port number of the peer
@@ -435,7 +435,7 @@
/**
* Attempts to encode a buffer of plaintext application data into
- * SSL/TLS network data.
+ * SSL/TLS/DTLS network data.
* <P>
* An invocation of this method behaves in exactly the same manner
* as the invocation:
@@ -445,20 +445,20 @@
* </pre></blockquote>
*
* @param src
- * a <code>ByteBuffer</code> containing outbound application data
+ * a {@code ByteBuffer} containing outbound application data
* @param dst
- * a <code>ByteBuffer</code> to hold outbound network data
- * @return an <code>SSLEngineResult</code> describing the result
+ * a {@code ByteBuffer} to hold outbound network data
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws ReadOnlyBufferException
- * if the <code>dst</code> buffer is read-only.
+ * if the {@code dst} buffer is read-only.
* @throws IllegalArgumentException
- * if either <code>src</code> or <code>dst</code>
+ * if either {@code src} or {@code dst}
* is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
@@ -471,7 +471,7 @@
/**
* Attempts to encode plaintext bytes from a sequence of data
- * buffers into SSL/TLS network data.
+ * buffers into SSL/TLS/DTLS network data.
* <P>
* An invocation of this method behaves in exactly the same manner
* as the invocation:
@@ -481,22 +481,22 @@
* </pre></blockquote>
*
* @param srcs
- * an array of <code>ByteBuffers</code> containing the
+ * an array of {@code ByteBuffers} containing the
* outbound application data
* @param dst
- * a <code>ByteBuffer</code> to hold outbound network data
- * @return an <code>SSLEngineResult</code> describing the result
+ * a {@code ByteBuffer} to hold outbound network data
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws ReadOnlyBufferException
- * if the <code>dst</code> buffer is read-only.
+ * if the {@code dst} buffer is read-only.
* @throws IllegalArgumentException
- * if either <code>srcs</code> or <code>dst</code>
- * is null, or if any element in <code>srcs</code> is null.
+ * if either {@code srcs} or {@code dst}
+ * is null, or if any element in {@code srcs} is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
* @see #wrap(ByteBuffer [], int, int, ByteBuffer)
@@ -512,7 +512,7 @@
/**
* Attempts to encode plaintext bytes from a subsequence of data
- * buffers into SSL/TLS network data. This <i>"gathering"</i>
+ * buffers into SSL/TLS/DTLS network data. This <i>"gathering"</i>
* operation encodes, in a single invocation, a sequence of bytes
* from one or more of a given sequence of buffers. Gathering
* wraps are often useful when implementing network protocols or
@@ -535,49 +535,49 @@
* it was generated. The application must properly synchronize
* multiple calls to this method.
* <P>
- * If this <code>SSLEngine</code> has not yet started its initial
+ * If this {@code SSLEngine} has not yet started its initial
* handshake, this method will automatically start the handshake.
* <P>
- * This method will attempt to produce SSL/TLS records, and will
+ * This method will attempt to produce SSL/TLS/DTLS records, and will
* consume as much source data as possible, but will never consume
* more than the sum of the bytes remaining in each buffer. Each
- * <code>ByteBuffer</code>'s position is updated to reflect the
+ * {@code ByteBuffer}'s position is updated to reflect the
* amount of data consumed or produced. The limits remain the
* same.
* <P>
- * The underlying memory used by the <code>srcs</code> and
- * <code>dst ByteBuffer</code>s must not be the same.
+ * The underlying memory used by the {@code srcs} and
+ * {@code dst ByteBuffer}s must not be the same.
* <P>
* See the class description for more information on engine closure.
*
* @param srcs
- * an array of <code>ByteBuffers</code> containing the
+ * an array of {@code ByteBuffers} containing the
* outbound application data
* @param offset
* The offset within the buffer array of the first buffer from
* which bytes are to be retrieved; it must be non-negative
- * and no larger than <code>srcs.length</code>
+ * and no larger than {@code srcs.length}
* @param length
* The maximum number of buffers to be accessed; it must be
* non-negative and no larger than
- * <code>srcs.length</code> - <code>offset</code>
+ * {@code srcs.length} - {@code offset}
* @param dst
- * a <code>ByteBuffer</code> to hold outbound network data
- * @return an <code>SSLEngineResult</code> describing the result
+ * a {@code ByteBuffer} to hold outbound network data
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws IndexOutOfBoundsException
- * if the preconditions on the <code>offset</code> and
- * <code>length</code> parameters do not hold.
+ * if the preconditions on the {@code offset} and
+ * {@code length} parameters do not hold.
* @throws ReadOnlyBufferException
- * if the <code>dst</code> buffer is read-only.
+ * if the {@code dst} buffer is read-only.
* @throws IllegalArgumentException
- * if either <code>srcs</code> or <code>dst</code>
- * is null, or if any element in the <code>srcs</code>
+ * if either {@code srcs} or {@code dst}
+ * is null, or if any element in the {@code srcs}
* subsequence specified is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
@@ -589,7 +589,7 @@
int length, ByteBuffer dst) throws SSLException;
/**
- * Attempts to decode SSL/TLS network data into a plaintext
+ * Attempts to decode SSL/TLS/DTLS network data into a plaintext
* application data buffer.
* <P>
* An invocation of this method behaves in exactly the same manner
@@ -600,20 +600,20 @@
* </pre></blockquote>
*
* @param src
- * a <code>ByteBuffer</code> containing inbound network data.
+ * a {@code ByteBuffer} containing inbound network data.
* @param dst
- * a <code>ByteBuffer</code> to hold inbound application data.
- * @return an <code>SSLEngineResult</code> describing the result
+ * a {@code ByteBuffer} to hold inbound application data.
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws ReadOnlyBufferException
- * if the <code>dst</code> buffer is read-only.
+ * if the {@code dst} buffer is read-only.
* @throws IllegalArgumentException
- * if either <code>src</code> or <code>dst</code>
+ * if either {@code src} or {@code dst}
* is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
@@ -625,7 +625,7 @@
}
/**
- * Attempts to decode SSL/TLS network data into a sequence of plaintext
+ * Attempts to decode SSL/TLS/DTLS network data into a sequence of plaintext
* application data buffers.
* <P>
* An invocation of this method behaves in exactly the same manner
@@ -636,22 +636,22 @@
* </pre></blockquote>
*
* @param src
- * a <code>ByteBuffer</code> containing inbound network data.
+ * a {@code ByteBuffer} containing inbound network data.
* @param dsts
- * an array of <code>ByteBuffer</code>s to hold inbound
+ * an array of {@code ByteBuffer}s to hold inbound
* application data.
- * @return an <code>SSLEngineResult</code> describing the result
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws ReadOnlyBufferException
- * if any of the <code>dst</code> buffers are read-only.
+ * if any of the {@code dst} buffers are read-only.
* @throws IllegalArgumentException
- * if either <code>src</code> or <code>dsts</code>
- * is null, or if any element in <code>dsts</code> is null.
+ * if either {@code src} or {@code dsts}
+ * is null, or if any element in {@code dsts} is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
* @see #unwrap(ByteBuffer, ByteBuffer [], int, int)
@@ -665,7 +665,7 @@
}
/**
- * Attempts to decode SSL/TLS network data into a subsequence of
+ * Attempts to decode SSL/TLS/DTLS network data into a subsequence of
* plaintext application data buffers. This <i>"scattering"</i>
* operation decodes, in a single invocation, a sequence of bytes
* into one or more of a given sequence of buffers. Scattering
@@ -688,55 +688,55 @@
* order it was received. The application must properly synchronize
* multiple calls to this method.
* <P>
- * If this <code>SSLEngine</code> has not yet started its initial
+ * If this {@code SSLEngine} has not yet started its initial
* handshake, this method will automatically start the handshake.
* <P>
- * This method will attempt to consume one complete SSL/TLS network
+ * This method will attempt to consume one complete SSL/TLS/DTLS network
* packet, but will never consume more than the sum of the bytes
- * remaining in the buffers. Each <code>ByteBuffer</code>'s
+ * remaining in the buffers. Each {@code ByteBuffer}'s
* position is updated to reflect the amount of data consumed or
* produced. The limits remain the same.
* <P>
- * The underlying memory used by the <code>src</code> and
- * <code>dsts ByteBuffer</code>s must not be the same.
+ * The underlying memory used by the {@code src} and
+ * {@code dsts ByteBuffer}s must not be the same.
* <P>
* The inbound network buffer may be modified as a result of this
* call: therefore if the network data packet is required for some
* secondary purpose, the data should be duplicated before calling this
* method. Note: the network data will not be useful to a second
* SSLEngine, as each SSLEngine contains unique random state which
- * influences the SSL/TLS messages.
+ * influences the SSL/TLS/DTLS messages.
* <P>
* See the class description for more information on engine closure.
*
* @param src
- * a <code>ByteBuffer</code> containing inbound network data.
+ * a {@code ByteBuffer} containing inbound network data.
* @param dsts
- * an array of <code>ByteBuffer</code>s to hold inbound
+ * an array of {@code ByteBuffer}s to hold inbound
* application data.
* @param offset
* The offset within the buffer array of the first buffer from
* which bytes are to be transferred; it must be non-negative
- * and no larger than <code>dsts.length</code>.
+ * and no larger than {@code dsts.length}.
* @param length
* The maximum number of buffers to be accessed; it must be
* non-negative and no larger than
- * <code>dsts.length</code> - <code>offset</code>.
- * @return an <code>SSLEngineResult</code> describing the result
+ * {@code dsts.length} - {@code offset}.
+ * @return an {@code SSLEngineResult} describing the result
* of this operation.
* @throws SSLException
* A problem was encountered while processing the
- * data that caused the <code>SSLEngine</code> to abort.
+ * data that caused the {@code SSLEngine} to abort.
* See the class description for more information on
* engine closure.
* @throws IndexOutOfBoundsException
- * If the preconditions on the <code>offset</code> and
- * <code>length</code> parameters do not hold.
+ * If the preconditions on the {@code offset} and
+ * {@code length} parameters do not hold.
* @throws ReadOnlyBufferException
- * if any of the <code>dst</code> buffers are read-only.
+ * if any of the {@code dst} buffers are read-only.
* @throws IllegalArgumentException
- * if either <code>src</code> or <code>dsts</code>
- * is null, or if any element in the <code>dsts</code>
+ * if either {@code src} or {@code dsts}
+ * is null, or if any element in the {@code dsts}
* subsequence specified is null.
* @throws IllegalStateException if the client/server mode
* has not yet been set.
@@ -749,19 +749,19 @@
/**
- * Returns a delegated <code>Runnable</code> task for
- * this <code>SSLEngine</code>.
+ * Returns a delegated {@code Runnable} task for
+ * this {@code SSLEngine}.
* <P>
- * <code>SSLEngine</code> operations may require the results of
+ * {@code SSLEngine} operations may require the results of
* operations that block, or may take an extended period of time to
* complete. This method is used to obtain an outstanding {@link
* java.lang.Runnable} operation (task). Each task must be assigned
* a thread (possibly the current) to perform the {@link
* java.lang.Runnable#run() run} operation. Once the
- * <code>run</code> method returns, the <code>Runnable</code> object
+ * {@code run} method returns, the {@code Runnable} object
* is no longer needed and may be discarded.
* <P>
- * Delegated tasks run in the <code>AccessControlContext</code>
+ * Delegated tasks run in the {@code AccessControlContext}
* in place when this object was created.
* <P>
* A call to this method will return each outstanding task
@@ -769,7 +769,7 @@
* <P>
* Multiple delegated tasks can be run in parallel.
*
- * @return a delegated <code>Runnable</code> task, or null
+ * @return a delegated {@code Runnable} task, or null
* if none are available.
*/
public abstract Runnable getDelegatedTask();
@@ -777,7 +777,7 @@
/**
* Signals that no more inbound network data will be sent
- * to this <code>SSLEngine</code>.
+ * to this {@code SSLEngine}.
* <P>
* If the application initiated the closing process by calling
* {@link #closeOutbound()}, under some circumstances it is not
@@ -789,9 +789,9 @@
* <P>
* But if the application did not initiate the closure process, or
* if the circumstances above do not apply, this method should be
- * called whenever the end of the SSL/TLS data stream is reached.
+ * called whenever the end of the SSL/TLS/DTLS data stream is reached.
* This ensures closure of the inbound side, and checks that the
- * peer followed the SSL/TLS close procedure properly, thus
+ * peer followed the SSL/TLS/DTLS close procedure properly, thus
* detecting possible truncation attacks.
* <P>
* This method is idempotent: if the inbound side has already
@@ -801,7 +801,7 @@
* called to flush any remaining handshake data.
*
* @throws SSLException
- * if this engine has not received the proper SSL/TLS close
+ * if this engine has not received the proper SSL/TLS/DTLS close
* notification message from the peer.
*
* @see #isInboundDone()
@@ -814,7 +814,7 @@
* Returns whether {@link #unwrap(ByteBuffer, ByteBuffer)} will
* accept any more inbound data messages.
*
- * @return true if the <code>SSLEngine</code> will not
+ * @return true if the {@code SSLEngine} will not
* consume anymore network data (and by implication,
* will not produce any more application data.)
* @see #closeInbound()
@@ -824,7 +824,7 @@
/**
* Signals that no more outbound application data will be sent
- * on this <code>SSLEngine</code>.
+ * on this {@code SSLEngine}.
* <P>
* This method is idempotent: if the outbound side has already
* been closed, this method does not do anything.
@@ -841,12 +841,12 @@
* Returns whether {@link #wrap(ByteBuffer, ByteBuffer)} will
* produce any more outbound data messages.
* <P>
- * Note that during the closure phase, a <code>SSLEngine</code> may
+ * Note that during the closure phase, a {@code SSLEngine} may
* generate handshake closure data that must be sent to the peer.
- * <code>wrap()</code> must be called to generate this data. When
+ * {@code wrap()} must be called to generate this data. When
* this method returns true, no more outbound data will be created.
*
- * @return true if the <code>SSLEngine</code> will not produce
+ * @return true if the {@code SSLEngine} will not produce
* any more network data
*
* @see #closeOutbound()
@@ -890,10 +890,10 @@
/**
* Sets the cipher suites enabled for use on this engine.
* <P>
- * Each cipher suite in the <code>suites</code> parameter must have
+ * Each cipher suite in the {@code suites} parameter must have
* been listed by getSupportedCipherSuites(), or the method will
* fail. Following a successful call to this method, only suites
- * listed in the <code>suites</code> parameter are enabled for use.
+ * listed in the {@code suites} parameter are enabled for use.
* <P>
* See {@link #getEnabledCipherSuites()} for more information
* on why a specific cipher suite may never be used on a engine.
@@ -910,7 +910,7 @@
/**
* Returns the names of the protocols which could be enabled for use
- * with this <code>SSLEngine</code>.
+ * with this {@code SSLEngine}.
*
* @return an array of protocols supported
*/
@@ -919,7 +919,7 @@
/**
* Returns the names of the protocol versions which are currently
- * enabled for use with this <code>SSLEngine</code>.
+ * enabled for use with this {@code SSLEngine}.
*
* @return an array of protocols
* @see #setEnabledProtocols(String [])
@@ -932,7 +932,7 @@
* <P>
* The protocols must have been listed by getSupportedProtocols()
* as being supported. Following a successful call to this method,
- * only protocols listed in the <code>protocols</code> parameter
+ * only protocols listed in the {@code protocols} parameter
* are enabled for use.
*
* @param protocols Names of all the protocols to enable.
@@ -945,8 +945,8 @@
/**
- * Returns the <code>SSLSession</code> in use in this
- * <code>SSLEngine</code>.
+ * Returns the {@code SSLSession} in use in this
+ * {@code SSLEngine}.
* <P>
* These can be long lived, and frequently correspond to an entire
* login session for some user. The session specifies a particular
@@ -961,22 +961,22 @@
* a session object which reports an invalid cipher suite of
* "SSL_NULL_WITH_NULL_NULL".
*
- * @return the <code>SSLSession</code> for this <code>SSLEngine</code>
+ * @return the {@code SSLSession} for this {@code SSLEngine}
* @see SSLSession
*/
public abstract SSLSession getSession();
/**
- * Returns the {@code SSLSession} being constructed during a SSL/TLS
+ * Returns the {@code SSLSession} being constructed during a SSL/TLS/DTLS
* handshake.
* <p>
- * TLS protocols may negotiate parameters that are needed when using
+ * TLS/DTLS protocols may negotiate parameters that are needed when using
* an instance of this class, but before the {@code SSLSession} has
* been completely initialized and made available via {@code getSession}.
* For example, the list of valid signature algorithms may restrict
* the type of certificates that can used during TrustManager
- * decisions, or the maximum TLS fragment packet sizes can be
+ * decisions, or the maximum TLS/DTLS fragment packet sizes can be
* resized to better support the network environment.
* <p>
* This method provides early access to the {@code SSLSession} being
@@ -1012,26 +1012,26 @@
* Initiates handshaking (initial or renegotiation) on this SSLEngine.
* <P>
* This method is not needed for the initial handshake, as the
- * <code>wrap()</code> and <code>unwrap()</code> methods will
+ * {@code wrap()} and {@code unwrap()} methods will
* implicitly call this method if handshaking has not already begun.
* <P>
* Note that the peer may also request a session renegotiation with
- * this <code>SSLEngine</code> by sending the appropriate
+ * this {@code SSLEngine} by sending the appropriate
* session renegotiate handshake message.
* <P>
* Unlike the {@link SSLSocket#startHandshake()
* SSLSocket#startHandshake()} method, this method does not block
* until handshaking is completed.
* <P>
- * To force a complete SSL/TLS session renegotiation, the current
+ * To force a complete SSL/TLS/DTLS session renegotiation, the current
* session should be invalidated prior to calling this method.
* <P>
* Some protocols may not support multiple handshakes on an existing
- * engine and may throw an <code>SSLException</code>.
+ * engine and may throw an {@code SSLException}.
*
* @throws SSLException
* if a problem was encountered while signaling the
- * <code>SSLEngine</code> to begin a new handshake.
+ * {@code SSLEngine} to begin a new handshake.
* See the class description for more information on
* engine closure.
* @throws IllegalStateException if the client/server mode
@@ -1042,9 +1042,9 @@
/**
- * Returns the current handshake status for this <code>SSLEngine</code>.
+ * Returns the current handshake status for this {@code SSLEngine}.
*
- * @return the current <code>SSLEngineResult.HandshakeStatus</code>.
+ * @return the current {@code SSLEngineResult.HandshakeStatus}.
*/
public abstract SSLEngineResult.HandshakeStatus getHandshakeStatus();
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngineResult.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLEngineResult.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,13 +27,13 @@
/**
* An encapsulation of the result state produced by
- * <code>SSLEngine</code> I/O calls.
+ * {@code SSLEngine} I/O calls.
*
- * <p> A <code>SSLEngine</code> provides a means for establishing
- * secure communication sessions between two peers. <code>SSLEngine</code>
+ * <p> A {@code SSLEngine} provides a means for establishing
+ * secure communication sessions between two peers. {@code SSLEngine}
* operations typically consume bytes from an input buffer and produce
* bytes in an output buffer. This class provides operational result
- * values describing the state of the <code>SSLEngine</code>, including
+ * values describing the state of the {@code SSLEngine}, including
* indications of what operations are needed to finish an
* ongoing handshake. Lastly, it reports the number of bytes consumed
* and produced as a result of this operation.
@@ -49,12 +49,12 @@
public class SSLEngineResult {
/**
- * An <code>SSLEngineResult</code> enum describing the overall result
- * of the <code>SSLEngine</code> operation.
+ * An {@code SSLEngineResult} enum describing the overall result
+ * of the {@code SSLEngine} operation.
*
- * The <code>Status</code> value does not reflect the
- * state of a <code>SSLEngine</code> handshake currently
- * in progress. The <code>SSLEngineResult's HandshakeStatus</code>
+ * The {@code Status} value does not reflect the
+ * state of a {@code SSLEngine} handshake currently
+ * in progress. The {@code SSLEngineResult's HandshakeStatus}
* should be consulted for that information.
*
* @author Brad R. Wetmore
@@ -63,7 +63,7 @@
public static enum Status {
/**
- * The <code>SSLEngine</code> was not able to unwrap the
+ * The {@code SSLEngine} was not able to unwrap the
* incoming data because there were not enough source bytes
* available to make a complete packet.
*
@@ -73,7 +73,7 @@
BUFFER_UNDERFLOW,
/**
- * The <code>SSLEngine</code> was not able to process the
+ * The {@code SSLEngine} was not able to process the
* operation because there are not enough bytes available in the
* destination buffer to hold the result.
* <P>
@@ -85,22 +85,22 @@
BUFFER_OVERFLOW,
/**
- * The <code>SSLEngine</code> completed the operation, and
+ * The {@code SSLEngine} completed the operation, and
* is available to process similar calls.
*/
OK,
/**
* The operation just closed this side of the
- * <code>SSLEngine</code>, or the operation
+ * {@code SSLEngine}, or the operation
* could not be completed because it was already closed.
*/
CLOSED;
}
/**
- * An <code>SSLEngineResult</code> enum describing the current
- * handshaking state of this <code>SSLEngine</code>.
+ * An {@code SSLEngineResult} enum describing the current
+ * handshaking state of this {@code SSLEngine}.
*
* @author Brad R. Wetmore
* @since 1.5
@@ -108,17 +108,17 @@
public static enum HandshakeStatus {
/**
- * The <code>SSLEngine</code> is not currently handshaking.
+ * The {@code SSLEngine} is not currently handshaking.
*/
NOT_HANDSHAKING,
/**
- * The <code>SSLEngine</code> has just finished handshaking.
+ * The {@code SSLEngine} has just finished handshaking.
* <P>
* This value is only generated by a call to
- * <code>SSLEngine.wrap()/unwrap()</code> when that call
+ * {@code SSLEngine.wrap()/unwrap()} when that call
* finishes a handshake. It is never generated by
- * <code>SSLEngine.getHandshakeStatus()</code>.
+ * {@code SSLEngine.getHandshakeStatus()}.
*
* @see SSLEngine#wrap(ByteBuffer, ByteBuffer)
* @see SSLEngine#unwrap(ByteBuffer, ByteBuffer)
@@ -127,7 +127,7 @@
FINISHED,
/**
- * The <code>SSLEngine</code> needs the results of one (or more)
+ * The {@code SSLEngine} needs the results of one (or more)
* delegated tasks before handshaking can continue.
*
* @see SSLEngine#getDelegatedTask()
@@ -135,8 +135,8 @@
NEED_TASK,
/**
- * The <code>SSLEngine</code> must send data to the remote side
- * before handshaking can continue, so <code>SSLEngine.wrap()</code>
+ * The {@code SSLEngine} must send data to the remote side
+ * before handshaking can continue, so {@code SSLEngine.wrap()}
* should be called.
*
* @see SSLEngine#wrap(ByteBuffer, ByteBuffer)
@@ -144,10 +144,22 @@
NEED_WRAP,
/**
- * The <code>SSLEngine</code> needs to receive data from the
+ * The {@code SSLEngine} needs to receive data from the
* remote side before handshaking can continue.
*/
- NEED_UNWRAP;
+ NEED_UNWRAP,
+
+ /**
+ * The {@code SSLEngine} needs to unwrap before handshaking can
+ * can continue.
+ * <P>
+ * This value is used to indicate that not-yet-interpreted data
+ * has been previously received from the remote side, and does
+ * not need to be received again.
+ *
+ * @since 1.9
+ */
+ NEED_UNWRAP_AGAIN;
}
@@ -155,6 +167,7 @@
private final HandshakeStatus handshakeStatus;
private final int bytesConsumed;
private final int bytesProduced;
+ private final long sequenceNumber;
/**
* Initializes a new instance of this class.
@@ -172,12 +185,44 @@
* the number of bytes placed into the destination ByteBuffer
*
* @throws IllegalArgumentException
- * if the <code>status</code> or <code>handshakeStatus</code>
- * arguments are null, or if <code>bytesConsumed</code> or
- * <code>bytesProduced</code> is negative.
+ * if the {@code status} or {@code handshakeStatus}
+ * arguments are null, or if {@code bytesConsumed} or
+ * {@code bytesProduced} is negative.
*/
public SSLEngineResult(Status status, HandshakeStatus handshakeStatus,
int bytesConsumed, int bytesProduced) {
+ this(status, handshakeStatus, bytesConsumed, bytesProduced, -1);
+ }
+
+ /**
+ * Initializes a new instance of this class.
+ *
+ * @param status
+ * the return value of the operation.
+ *
+ * @param handshakeStatus
+ * the current handshaking status.
+ *
+ * @param bytesConsumed
+ * the number of bytes consumed from the source ByteBuffer
+ *
+ * @param bytesProduced
+ * the number of bytes placed into the destination ByteBuffer
+ *
+ * @param sequenceNumber
+ * the sequence number (unsigned long) of the produced or
+ * consumed SSL/TLS/DTLS record, or ${@code -1L} if no record
+ * produced or consumed
+ *
+ * @throws IllegalArgumentException
+ * if the {@code status} or {@code handshakeStatus}
+ * arguments are null, or if {@code bytesConsumed} or
+ * {@code bytesProduced} is negative
+ *
+ * @since 1.9
+ */
+ public SSLEngineResult(Status status, HandshakeStatus handshakeStatus,
+ int bytesConsumed, int bytesProduced, long sequenceNumber) {
if ((status == null) || (handshakeStatus == null) ||
(bytesConsumed < 0) || (bytesProduced < 0)) {
@@ -188,10 +233,11 @@
this.handshakeStatus = handshakeStatus;
this.bytesConsumed = bytesConsumed;
this.bytesProduced = bytesProduced;
+ this.sequenceNumber = sequenceNumber;
}
/**
- * Gets the return value of this <code>SSLEngine</code> operation.
+ * Gets the return value of this {@code SSLEngine} operation.
*
* @return the return value
*/
@@ -200,7 +246,7 @@
}
/**
- * Gets the handshake status of this <code>SSLEngine</code>
+ * Gets the handshake status of this {@code SSLEngine}
* operation.
*
* @return the handshake status
@@ -228,6 +274,41 @@
}
/**
+ * Returns the sequence number of the produced or consumed SSL/TLS/DTLS
+ * record (optional operation).
+ *
+ * @apiNote Note that sequence number is an unsigned long and cannot
+ * exceed {@code -1L}. It is desired to use the unsigned
+ * long comparing mode for comparison of unsigned long values
+ * (see also {@link java.lang.Long#compareUnsigned()
+ * Long.compareUnsigned()}).
+ * <P>
+ * For DTLS protocols, the first 16 bits of the sequence
+ * number is a counter value (epoch) that is incremented on
+ * every cipher state change. The remaining 48 bits on the
+ * right side of the sequence number represents the sequence
+ * of the record, which is maintained separately for each epoch.
+ *
+ * @implNote It is recommended that providers should never allow the
+ * sequence number incremented to {@code -1L}. If the sequence
+ * number is close to wrapping, renegotiate should be requested,
+ * otherwise the connection should be closed immediately.
+ * This should be carried on automatically by the underlying
+ * implementation.
+ *
+ * @return the sequence number of the produced or consumed SSL/TLS/DTLS
+ * record; or ${@code -1L} if no record is produced or consumed,
+ * or this operation is not supported by the underlying provider
+ *
+ * @see java.lang.Long#compareUnsigned(boolean, boolean)
+ *
+ * @since 1.9
+ */
+ final public long sequenceNumber() {
+ return sequenceNumber;
+ }
+
+ /**
* Returns a String representation of this object.
*/
@Override
@@ -235,6 +316,8 @@
return ("Status = " + status +
" HandshakeStatus = " + handshakeStatus +
"\nbytesConsumed = " + bytesConsumed +
- " bytesProduced = " + bytesProduced);
+ " bytesProduced = " + bytesProduced +
+ (sequenceNumber == -1 ? "" :
+ " sequenceNumber = " + Long.toUnsignedString(sequenceNumber)));
}
}
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLParameters.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLParameters.java Tue Jun 02 09:15:47 2015 -0700
@@ -35,22 +35,22 @@
import java.util.LinkedHashMap;
/**
- * Encapsulates parameters for an SSL/TLS connection. The parameters
- * are the list of ciphersuites to be accepted in an SSL/TLS handshake,
+ * Encapsulates parameters for an SSL/TLS/DTLS connection. The parameters
+ * are the list of ciphersuites to be accepted in an SSL/TLS/DTLS handshake,
* the list of protocols to be allowed, the endpoint identification
- * algorithm during SSL/TLS handshaking, the Server Name Indication (SNI),
- * the algorithm constraints and whether SSL/TLS servers should request
- * or require client authentication, etc.
+ * algorithm during SSL/TLS/DTLS handshaking, the Server Name Indication (SNI),
+ * the maximum network packet size, the algorithm constraints and whether
+ * SSL/TLS/DTLS servers should request or require client authentication, etc.
* <p>
* SSLParameters can be created via the constructors in this class.
- * Objects can also be obtained using the <code>getSSLParameters()</code>
+ * Objects can also be obtained using the {@code getSSLParameters()}
* methods in
* {@link SSLSocket#getSSLParameters SSLSocket} and
* {@link SSLServerSocket#getSSLParameters SSLServerSocket} and
* {@link SSLEngine#getSSLParameters SSLEngine} or the
* {@link SSLContext#getDefaultSSLParameters getDefaultSSLParameters()} and
* {@link SSLContext#getSupportedSSLParameters getSupportedSSLParameters()}
- * methods in <code>SSLContext</code>.
+ * methods in {@code SSLContext}.
* <p>
* SSLParameters can be applied to a connection via the methods
* {@link SSLSocket#setSSLParameters SSLSocket.setSSLParameters()} and
@@ -74,14 +74,18 @@
private Map<Integer, SNIServerName> sniNames = null;
private Map<Integer, SNIMatcher> sniMatchers = null;
private boolean preferLocalCipherSuites;
+ private boolean enableRetransmissions = true;
+ private int maximumPacketSize = 0;
/**
* Constructs SSLParameters.
* <p>
* The values of cipherSuites, protocols, cryptographic algorithm
* constraints, endpoint identification algorithm, server names and
- * server name matchers are set to <code>null</code>, useCipherSuitesOrder,
- * wantClientAuth and needClientAuth are set to <code>false</code>.
+ * server name matchers are set to {@code null}; useCipherSuitesOrder,
+ * wantClientAuth and needClientAuth are set to {@code false};
+ * enableRetransmissions is set to {@code true}; maximum network packet
+ * size is set to {@code 0}.
*/
public SSLParameters() {
// empty
@@ -92,7 +96,7 @@
* <p>
* Calling this constructor is equivalent to calling the no-args
* constructor followed by
- * <code>setCipherSuites(cipherSuites);</code>.
+ * {@code setCipherSuites(cipherSuites);}.
*
* @param cipherSuites the array of ciphersuites (or null)
*/
@@ -106,7 +110,7 @@
* <p>
* Calling this constructor is equivalent to calling the no-args
* constructor followed by
- * <code>setCipherSuites(cipherSuites); setProtocols(protocols);</code>.
+ * {@code setCipherSuites(cipherSuites); setProtocols(protocols);}.
*
* @param cipherSuites the array of ciphersuites (or null)
* @param protocols the array of protocols (or null)
@@ -171,7 +175,7 @@
/**
* Sets whether client authentication should be requested. Calling
- * this method clears the <code>needClientAuth</code> flag.
+ * this method clears the {@code needClientAuth} flag.
*
* @param wantClientAuth whether client authentication should be requested
*/
@@ -191,7 +195,7 @@
/**
* Sets whether client authentication should be required. Calling
- * this method clears the <code>wantClientAuth</code> flag.
+ * this method clears the {@code wantClientAuth} flag.
*
* @param needClientAuth whether client authentication should be required
*/
@@ -218,9 +222,9 @@
* Sets the cryptographic algorithm constraints, which will be used
* in addition to any configured by the runtime environment.
* <p>
- * If the <code>constraints</code> parameter is non-null, every
+ * If the {@code constraints} parameter is non-null, every
* cryptographic algorithm, key and algorithm parameters used in the
- * SSL/TLS handshake must be permitted by the constraints.
+ * SSL/TLS/DTLS handshake must be permitted by the constraints.
*
* @param constraints the algorithm constraints (or null)
*
@@ -249,9 +253,9 @@
/**
* Sets the endpoint identification algorithm.
* <p>
- * If the <code>algorithm</code> parameter is non-null or non-empty, the
+ * If the {@code algorithm} parameter is non-null or non-empty, the
* endpoint identification/verification procedures must be handled during
- * SSL/TLS handshaking. This is to prevent man-in-the-middle attacks.
+ * SSL/TLS/DTLS handshaking. This is to prevent man-in-the-middle attacks.
*
* @param algorithm The standard string name of the endpoint
* identification algorithm (or null). See Appendix A in the <a href=
@@ -317,7 +321,7 @@
* This method is only useful to {@link SSLSocket}s or {@link SSLEngine}s
* operating in client mode.
* <P>
- * For SSL/TLS connections, the underlying SSL/TLS provider
+ * For SSL/TLS/DTLS connections, the underlying SSL/TLS/DTLS provider
* may specify a default value for a certain server name type. In
* client mode, it is recommended that, by default, providers should
* include the server name indication whenever the server can be located
@@ -440,7 +444,7 @@
*
* @param honorOrder whether local cipher suites order in
* {@code #getCipherSuites} should be honored during
- * SSL/TLS handshaking.
+ * SSL/TLS/DTLS handshaking.
*
* @see #getUseCipherSuitesOrder()
*
@@ -454,7 +458,7 @@
* Returns whether the local cipher suites preference should be honored.
*
* @return whether local cipher suites order in {@code #getCipherSuites}
- * should be honored during SSL/TLS handshaking.
+ * should be honored during SSL/TLS/DTLS handshaking.
*
* @see #setUseCipherSuitesOrder(boolean)
*
@@ -463,5 +467,107 @@
public final boolean getUseCipherSuitesOrder() {
return preferLocalCipherSuites;
}
+
+ /**
+ * Sets whether DTLS handshake retransmissions should be enabled.
+ *
+ * This method only applies to DTLS.
+ *
+ * @param enableRetransmissions
+ * {@code true} indicates that DTLS handshake retransmissions
+ * should be enabled; {@code false} indicates that DTLS handshake
+ * retransmissions should be disabled
+ *
+ * @see #getEnableRetransmissions()
+ *
+ * @since 1.9
+ */
+ public void setEnableRetransmissions(boolean enableRetransmissions) {
+ this.enableRetransmissions = enableRetransmissions;
+ }
+
+ /**
+ * Returns whether DTLS handshake retransmissions should be enabled.
+ *
+ * This method only applies to DTLS.
+ *
+ * @return true, if DTLS handshake retransmissions should be enabled
+ *
+ * @see #setEnableRetransmissions(boolean)
+ *
+ * @since 1.9
+ */
+ public boolean getEnableRetransmissions() {
+ return enableRetransmissions;
+ }
+
+ /**
+ * Sets the maximum expected network packet size in bytes for
+ * SSL/TLS/DTLS records.
+ *
+ * @apiNote It is recommended that if possible, the maximum packet size
+ * should not be less than 256 bytes so that small handshake
+ * messages, such as HelloVerifyRequests, are not fragmented.
+ *
+ * @implNote If the maximum packet size is too small to hold a minimal
+ * record, an implementation may attempt to generate as minimal
+ * records as possible. However, this may cause a generated
+ * packet to be larger than the maximum packet size.
+ *
+ * @param maximumPacketSize
+ * the maximum expected network packet size in bytes, or
+ * {@code 0} to use the implicit size that is automatically
+ * specified by the underlying implementation.
+ * @throws IllegalArgumentException
+ * if {@code maximumPacketSize} is negative.
+ *
+ * @see #getMaximumPacketSize()
+ *
+ * @since 1.9
+ */
+ public void setMaximumPacketSize(int maximumPacketSize) {
+ if (maximumPacketSize < 0) {
+ throw new IllegalArgumentException(
+ "The maximum packet size cannot be negative");
+ }
+
+ this.maximumPacketSize = maximumPacketSize;
+ }
+
+ /**
+ * Returns the maximum expected network packet size in bytes for
+ * SSL/TLS/DTLS records.
+ *
+ * @apiNote The implicit size may not be a fixed value, especially
+ * for a DTLS protocols implementation.
+ *
+ * @implNote For SSL/TLS/DTLS connections, the underlying provider
+ * should calculate and specify the implicit value of the
+ * maximum expected network packet size if it is not
+ * configured explicitly. For any connection populated
+ * object, this method should never return {@code 0} so
+ * that applications can retrieve the actual implicit size
+ * of the underlying implementation.
+ * <P>
+ * An implementation should attempt to comply with the maximum
+ * packet size configuration. However, if the maximum packet
+ * size is too small to hold a minimal record, an implementation
+ * may try to generate as minimal records as possible. This
+ * may cause a generated packet to be larger than the maximum
+ * packet size.
+ *
+ * @return the maximum expected network packet size, or {@code 0} if
+ * use the implicit size that is automatically specified by
+ * the underlying implementation and this object has not been
+ * populated by any connection.
+ *
+ * @see #setMaximumPacketSize(int)
+ *
+ * @since 1.9
+ */
+ public int getMaximumPacketSize() {
+ return maximumPacketSize;
+ }
+
}
--- a/jdk/src/java.base/share/classes/javax/net/ssl/SSLSession.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/SSLSession.java Tue Jun 02 09:15:47 2015 -0700
@@ -35,7 +35,7 @@
* also be replaced by a different session. Sessions are created, or
* rejoined, as part of the SSL handshaking protocol. Sessions may be
* invalidated due to policies affecting security or resource usage,
- * or by an application explicitly calling <code>invalidate</code>.
+ * or by an application explicitly calling {@code invalidate}.
* Session management policies are typically used to tune performance.
*
* <P> In addition to the standard session attributes, SSL sessions expose
@@ -82,8 +82,8 @@
* security manager installed, the caller may require
* permission to access it or a security exception may be thrown.
* In a Java environment, the security manager's
- * <code>checkPermission</code> method is called with a
- * <code>SSLPermission("getSSLSessionContext")</code> permission.
+ * {@code checkPermission} method is called with a
+ * {@code SSLPermission("getSSLSessionContext")} permission.
*
* @throws SecurityException if the calling thread does not have
* permission to get SSL session context.
@@ -148,14 +148,14 @@
/**
*
- * Binds the specified <code>value</code> object into the
+ * Binds the specified {@code value} object into the
* session's application layer data
- * with the given <code>name</code>.
+ * with the given {@code name}.
* <P>
- * Any existing binding using the same <code>name</code> is
- * replaced. If the new (or existing) <code>value</code> implements the
- * <code>SSLSessionBindingListener</code> interface, the object
- * represented by <code>value</code> is notified appropriately.
+ * Any existing binding using the same {@code name} is
+ * replaced. If the new (or existing) {@code value} implements the
+ * {@code SSLSessionBindingListener} interface, the object
+ * represented by {@code value} is notified appropriately.
* <p>
* For security reasons, the same named values may not be
* visible across different access control contexts.
@@ -187,7 +187,7 @@
* Removes the object bound to the given name in the session's
* application layer data. Does nothing if there is no object
* bound to the given name. If the bound existing object
- * implements the <code>SessionBindingListener</code> interface,
+ * implements the {@code SessionBindingListener} interface,
* it is notified appropriately.
* <p>
* For security reasons, the same named values may not be
@@ -349,7 +349,7 @@
* by this method.
* <P>
* This value is not authenticated and should not be relied upon.
- * It is mainly used as a hint for <code>SSLSession</code> caching
+ * It is mainly used as a hint for {@code SSLSession} caching
* strategies.
*
* @return the host name of the peer host, or null if no information
@@ -364,7 +364,7 @@
* the client, it is the server's port number.
* <P>
* This value is not authenticated and should not be relied upon.
- * It is mainly used as a hint for <code>SSLSession</code> caching
+ * It is mainly used as a hint for {@code SSLSession} caching
* strategies.
*
* @return the port number of the peer host, or -1 if no information
@@ -375,14 +375,14 @@
public int getPeerPort();
/**
- * Gets the current size of the largest SSL/TLS packet that is expected
- * when using this session.
+ * Gets the current size of the largest SSL/TLS/DTLS packet that is
+ * expected when using this session.
* <P>
- * A <code>SSLEngine</code> using this session may generate SSL/TLS
+ * An {@code SSLEngine} using this session may generate SSL/TLS/DTLS
* packets of any size up to and including the value returned by this
- * method. All <code>SSLEngine</code> network buffers should be sized
+ * method. All {@code SSLEngine} network buffers should be sized
* at least this large to avoid insufficient space problems when
- * performing <code>wrap</code> and <code>unwrap</code> calls.
+ * performing {@code wrap} and {@code unwrap} calls.
*
* @return the current maximum expected network packet size
*
@@ -398,7 +398,7 @@
* Gets the current size of the largest application data that is
* expected when using this session.
* <P>
- * <code>SSLEngine</code> application data buffers must be large
+ * {@code SSLEngine} application data buffers must be large
* enough to hold the application data from any inbound network
* application data packet received. Typically, outbound
* application data buffers can be of any size.
--- a/jdk/src/java.base/share/classes/javax/net/ssl/X509ExtendedTrustManager.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/javax/net/ssl/X509ExtendedTrustManager.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,16 +32,17 @@
import java.security.cert.CertificateException;
/**
- * Extensions to the <code>X509TrustManager</code> interface to support
- * SSL/TLS connection sensitive trust management.
+ * Extensions to the {@code X509TrustManager} interface to support
+ * SSL/TLS/DTLS connection sensitive trust management.
* <p>
* To prevent man-in-the-middle attacks, hostname checks can be done
* to verify that the hostname in an end-entity certificate matches the
- * targeted hostname. TLS does not require such checks, but some protocols
- * over TLS (such as HTTPS) do. In earlier versions of the JDK, the
- * certificate chain checks were done at the SSL/TLS layer, and the hostname
- * verification checks were done at the layer over TLS. This class allows
- * for the checking to be done during a single call to this class.
+ * targeted hostname. TLS/DTLS does not require such checks, but some
+ * protocols over TLS/DTLS (such as HTTPS) do. In earlier versions of the
+ * JDK, the certificate chain checks were done at the SSL/TLS/DTLS layer,
+ * and the hostname verification checks were done at the layer over TLS/DTLS.
+ * This class allows for the checking to be done during a single call to
+ * this class.
* <p>
* RFC 2830 defines the server identification specification for the "LDAPS"
* algorithm. RFC 2818 defines both the server identification and the
@@ -62,17 +63,17 @@
* used. For instance, if RSAPublicKey is used, the authType
* should be "RSA". Checking is case-sensitive.
* <p>
- * If the <code>socket</code> parameter is an instance of
+ * If the {@code socket} parameter is an instance of
* {@link javax.net.ssl.SSLSocket}, and the endpoint identification
- * algorithm of the <code>SSLParameters</code> is non-empty, to prevent
- * man-in-the-middle attacks, the address that the <code>socket</code>
+ * algorithm of the {@code SSLParameters} is non-empty, to prevent
+ * man-in-the-middle attacks, the address that the {@code socket}
* connected to should be checked against the peer's identity presented
* in the end-entity X509 certificate, as specified in the endpoint
* identification algorithm.
* <p>
- * If the <code>socket</code> parameter is an instance of
+ * If the {@code socket} parameter is an instance of
* {@link javax.net.ssl.SSLSocket}, and the algorithm constraints of the
- * <code>SSLParameters</code> is non-null, for every certificate in the
+ * {@code SSLParameters} is non-null, for every certificate in the
* certification path, fields such as subject public key, the signature
* algorithm, key usage, extended key usage, etc. need to conform to the
* algorithm constraints in place on this socket.
@@ -83,8 +84,8 @@
* can be null, which indicates that implementations need not check
* the ssl parameters
* @throws IllegalArgumentException if null or zero-length array is passed
- * in for the <code>chain</code> parameter or if null or zero-length
- * string is passed in for the <code>authType</code> parameter
+ * in for the {@code chain} parameter or if null or zero-length
+ * string is passed in for the {@code authType} parameter
* @throws CertificateException if the certificate chain is not trusted
* by this TrustManager
*
@@ -110,17 +111,17 @@
* used for the key exchange, and RSA when the key from the server
* certificate is used. Checking is case-sensitive.
* <p>
- * If the <code>socket</code> parameter is an instance of
+ * If the {@code socket} parameter is an instance of
* {@link javax.net.ssl.SSLSocket}, and the endpoint identification
- * algorithm of the <code>SSLParameters</code> is non-empty, to prevent
- * man-in-the-middle attacks, the address that the <code>socket</code>
+ * algorithm of the {@code SSLParameters} is non-empty, to prevent
+ * man-in-the-middle attacks, the address that the {@code socket}
* connected to should be checked against the peer's identity presented
* in the end-entity X509 certificate, as specified in the endpoint
* identification algorithm.
* <p>
- * If the <code>socket</code> parameter is an instance of
+ * If the {@code socket} parameter is an instance of
* {@link javax.net.ssl.SSLSocket}, and the algorithm constraints of the
- * <code>SSLParameters</code> is non-null, for every certificate in the
+ * {@code SSLParameters} is non-null, for every certificate in the
* certification path, fields such as subject public key, the signature
* algorithm, key usage, extended key usage, etc. need to conform to the
* algorithm constraints in place on this socket.
@@ -131,8 +132,8 @@
* can be null, which indicates that implementations need not check
* the ssl parameters
* @throws IllegalArgumentException if null or zero-length array is passed
- * in for the <code>chain</code> parameter or if null or zero-length
- * string is passed in for the <code>authType</code> parameter
+ * in for the {@code chain} parameter or if null or zero-length
+ * string is passed in for the {@code authType} parameter
* @throws CertificateException if the certificate chain is not trusted
* by this TrustManager
*
@@ -153,15 +154,15 @@
* used. For instance, if RSAPublicKey is used, the authType
* should be "RSA". Checking is case-sensitive.
* <p>
- * If the <code>engine</code> parameter is available, and the endpoint
- * identification algorithm of the <code>SSLParameters</code> is
+ * If the {@code engine} parameter is available, and the endpoint
+ * identification algorithm of the {@code SSLParameters} is
* non-empty, to prevent man-in-the-middle attacks, the address that
- * the <code>engine</code> connected to should be checked against
+ * the {@code engine} connected to should be checked against
* the peer's identity presented in the end-entity X509 certificate,
* as specified in the endpoint identification algorithm.
* <p>
- * If the <code>engine</code> parameter is available, and the algorithm
- * constraints of the <code>SSLParameters</code> is non-null, for every
+ * If the {@code engine} parameter is available, and the algorithm
+ * constraints of the {@code SSLParameters} is non-null, for every
* certificate in the certification path, fields such as subject public
* key, the signature algorithm, key usage, extended key usage, etc.
* need to conform to the algorithm constraints in place on this engine.
@@ -172,8 +173,8 @@
* can be null, which indicates that implementations need not check
* the ssl parameters
* @throws IllegalArgumentException if null or zero-length array is passed
- * in for the <code>chain</code> parameter or if null or zero-length
- * string is passed in for the <code>authType</code> parameter
+ * in for the {@code chain} parameter or if null or zero-length
+ * string is passed in for the {@code authType} parameter
* @throws CertificateException if the certificate chain is not trusted
* by this TrustManager
*
@@ -199,15 +200,15 @@
* used for the key exchange, and RSA when the key from the server
* certificate is used. Checking is case-sensitive.
* <p>
- * If the <code>engine</code> parameter is available, and the endpoint
- * identification algorithm of the <code>SSLParameters</code> is
+ * If the {@code engine} parameter is available, and the endpoint
+ * identification algorithm of the {@code SSLParameters} is
* non-empty, to prevent man-in-the-middle attacks, the address that
- * the <code>engine</code> connected to should be checked against
+ * the {@code engine} connected to should be checked against
* the peer's identity presented in the end-entity X509 certificate,
* as specified in the endpoint identification algorithm.
* <p>
- * If the <code>engine</code> parameter is available, and the algorithm
- * constraints of the <code>SSLParameters</code> is non-null, for every
+ * If the {@code engine} parameter is available, and the algorithm
+ * constraints of the {@code SSLParameters} is non-null, for every
* certificate in the certification path, fields such as subject public
* key, the signature algorithm, key usage, extended key usage, etc.
* need to conform to the algorithm constraints in place on this engine.
@@ -218,8 +219,8 @@
* can be null, which indicates that implementations need not check
* the ssl parameters
* @throws IllegalArgumentException if null or zero-length array is passed
- * in for the <code>chain</code> parameter or if null or zero-length
- * string is passed in for the <code>authType</code> parameter
+ * in for the {@code chain} parameter or if null or zero-length
+ * string is passed in for the {@code authType} parameter
* @throws CertificateException if the certificate chain is not trusted
* by this TrustManager
*
--- a/jdk/src/java.base/share/classes/sun/security/ssl/AppInputStream.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/AppInputStream.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,41 +26,54 @@
package sun.security.ssl;
-import java.io.*;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.net.ssl.SSLProtocolException;
/**
* InputStream for application data as returned by SSLSocket.getInputStream().
- * It uses an InputRecord as internal buffer that is refilled on demand
- * whenever it runs out of data.
*
* @author David Brownell
*/
-class AppInputStream extends InputStream {
+final class AppInputStream extends InputStream {
+ // the buffer size for each read of network data
+ private static final int READ_BUFFER_SIZE = 4096;
// static dummy array we use to implement skip()
- private final static byte[] SKIP_ARRAY = new byte[1024];
+ private static final byte[] SKIP_ARRAY = new byte[256];
+
+ // the related socket of the input stream
+ private final SSLSocketImpl socket;
- private SSLSocketImpl c;
- InputRecord r;
+ // the temporary buffer used to read network
+ private ByteBuffer buffer;
+
+ // Is application data available in the stream?
+ private boolean appDataIsAvailable;
// One element array used to implement the single byte read() method
private final byte[] oneByte = new byte[1];
AppInputStream(SSLSocketImpl conn) {
- r = new InputRecord();
- c = conn;
+ this.buffer = ByteBuffer.allocate(READ_BUFFER_SIZE);
+ this.socket = conn;
+ this.appDataIsAvailable = false;
}
/**
* Return the minimum number of bytes that can be read without blocking.
+ *
* Currently not synchronized.
*/
@Override
public int available() throws IOException {
- if (c.checkEOF() || (r.isAppDataValid() == false)) {
+ if ((!appDataIsAvailable) || socket.checkEOF()) {
return 0;
}
- return r.available();
+
+ return buffer.remaining();
}
/**
@@ -72,17 +85,21 @@
if (n <= 0) { // EOF
return -1;
}
- return oneByte[0] & 0xff;
+ return oneByte[0] & 0xFF;
}
/**
- * Read up to "len" bytes into this buffer, starting at "off".
+ * Reads up to {@code len} bytes of data from the input stream into an
+ * array of bytes. An attempt is made to read as many as {@code len} bytes,
+ * but a smaller number may be read. The number of bytes actually read
+ * is returned as an integer.
+ *
* If the layer above needs more data, it asks for more, so we
* are responsible only for blocking to fill at most one buffer,
* and returning "-1" on non-fault EOF status.
*/
@Override
- public synchronized int read(byte b[], int off, int len)
+ public synchronized int read(byte[] b, int off, int len)
throws IOException {
if (b == null) {
throw new NullPointerException();
@@ -92,28 +109,69 @@
return 0;
}
- if (c.checkEOF()) {
+ if (socket.checkEOF()) {
return -1;
}
+
+ // Read the available bytes at first.
+ int remains = available();
+ if (remains > 0) {
+ int howmany = Math.min(remains, len);
+ buffer.get(b, off, howmany);
+
+ return howmany;
+ }
+
+ appDataIsAvailable = false;
+ int volume = 0;
+
try {
/*
* Read data if needed ... notice that the connection guarantees
* that handshake, alert, and change cipher spec data streams are
* handled as they arrive, so we never see them here.
*/
- while (r.available() == 0) {
- c.readDataRecord(r);
- if (c.checkEOF()) {
+ while(volume == 0) {
+ // Clear the buffer for a new record reading.
+ buffer.clear();
+
+ //
+ // grow the buffer if needed
+ //
+
+ // Read the header of a record into the buffer, and return
+ // the packet size.
+ int packetLen = socket.bytesInCompletePacket();
+ if (packetLen < 0) { // EOF
return -1;
}
+
+ // Is this packet bigger than SSL/TLS normally allows?
+ if (packetLen > SSLRecord.maxLargeRecordSize) {
+ throw new SSLProtocolException(
+ "Illegal packet size: " + packetLen);
+ }
+
+ if (packetLen > buffer.remaining()) {
+ buffer = ByteBuffer.allocate(packetLen);
+ }
+
+ volume = socket.readRecord(buffer);
+ if (volume < 0) { // EOF
+ return -1;
+ } else if (volume > 0) {
+ appDataIsAvailable = true;
+ break;
+ }
}
- int howmany = Math.min(len, r.available());
- howmany = r.read(b, off, howmany);
+ int howmany = Math.min(len, volume);
+ buffer.get(b, off, howmany);
return howmany;
} catch (Exception e) {
// shutdown and rethrow (wrapped) exception as appropriate
- c.handleException(e);
+ socket.handleException(e);
+
// dummy for compiler
return -1;
}
@@ -147,9 +205,8 @@
*/
@Override
public void close() throws IOException {
- c.close();
+ socket.close();
}
// inherit default mark/reset behavior (throw Exceptions) from InputStream
-
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/AppOutputStream.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/AppOutputStream.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,41 +23,33 @@
* questions.
*/
-
package sun.security.ssl;
import java.io.OutputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
/*
- * Output stream for application data. This is the kind of stream
- * that's handed out via SSLSocket.getOutputStream(). It's all the application
- * ever sees.
- *
- * Once the initial handshake has completed, application data may be
- * interleaved with handshake data. That is handled internally and remains
- * transparent to the application.
+ * OutputStream for application data as returned by SSLSocket.getOutputStream().
*
* @author David Brownell
*/
class AppOutputStream extends OutputStream {
- private SSLSocketImpl c;
- OutputRecord r;
+ private SSLSocketImpl socket;
// One element array used to implement the write(byte) method
private final byte[] oneByte = new byte[1];
AppOutputStream(SSLSocketImpl conn) {
- r = new OutputRecord(Record.ct_application_data);
- c = conn;
+ this.socket = conn;
}
/**
* Write the data out, NOW.
*/
@Override
- synchronized public void write(byte b[], int off, int len)
+ synchronized public void write(byte[] b, int off, int len)
throws IOException {
if (b == null) {
throw new NullPointerException();
@@ -68,64 +60,15 @@
}
// check if the Socket is invalid (error or closed)
- c.checkWrite();
+ socket.checkWrite();
- /*
- * By default, we counter chosen plaintext issues on CBC mode
- * ciphersuites in SSLv3/TLS1.0 by sending one byte of application
- * data in the first record of every payload, and the rest in
- * subsequent record(s). Note that the issues have been solved in
- * TLS 1.1 or later.
- *
- * It is not necessary to split the very first application record of
- * a freshly negotiated TLS session, as there is no previous
- * application data to guess. To improve compatibility, we will not
- * split such records.
- *
- * This avoids issues in the outbound direction. For a full fix,
- * the peer must have similar protections.
- */
- boolean isFirstRecordOfThePayload = true;
-
- // Always flush at the end of each application level record.
- // This lets application synchronize read and write streams
- // however they like; if we buffered here, they couldn't.
+ // Delegate the writing to the underlying socket.
try {
- do {
- boolean holdRecord = false;
- int howmuch;
- if (isFirstRecordOfThePayload && c.needToSplitPayload()) {
- howmuch = Math.min(0x01, r.availableDataBytes());
- /*
- * Nagle's algorithm (TCP_NODELAY) was coming into
- * play here when writing short (split) packets.
- * Signal to the OutputRecord code to internally
- * buffer this small packet until the next outbound
- * packet (of any type) is written.
- */
- if ((len != 1) && (howmuch == 1)) {
- holdRecord = true;
- }
- } else {
- howmuch = Math.min(len, r.availableDataBytes());
- }
-
- if (isFirstRecordOfThePayload && howmuch != 0) {
- isFirstRecordOfThePayload = false;
- }
-
- // NOTE: *must* call c.writeRecord() even for howmuch == 0
- if (howmuch > 0) {
- r.write(b, off, howmuch);
- off += howmuch;
- len -= howmuch;
- }
- c.writeRecord(r, holdRecord);
- c.checkWrite();
- } while (len > 0);
+ socket.writeRecord(b, off, len);
+ socket.checkWrite();
} catch (Exception e) {
// shutdown and rethrow (wrapped) exception as appropriate
- c.handleException(e);
+ socket.handleException(e);
}
}
@@ -143,7 +86,7 @@
*/
@Override
public void close() throws IOException {
- c.close();
+ socket.close();
}
// inherit no-op flush()
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Authenticator.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Authenticator.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -28,19 +28,26 @@
import java.util.Arrays;
/**
- * This class represents an SSL/TLS message authentication token,
+ * This class represents an SSL/TLS/DTLS message authentication token,
* which encapsulates a sequence number and ensures that attempts to
* delete or reorder messages can be detected.
*
- * Each SSL/TLS connection state contains a sequence number, which
- * is maintained separately for read and write states. The sequence
- * number MUST be set to zero whenever a connection state is made the
- * active state. Sequence numbers are of type uint64 and may not
- * exceed 2^64-1. Sequence numbers do not wrap. If a SSL/TLS
- * implementation would need to wrap a sequence number, it must
- * renegotiate instead. A sequence number is incremented after each
- * record: specifically, the first record transmitted under a
- * particular connection state MUST use sequence number 0.
+ * Each connection state contains a sequence number, which is maintained
+ * separately for read and write states.
+ *
+ * For SSL/TLS protocols, the sequence number MUST be set to zero
+ * whenever a connection state is made the active state.
+ *
+ * DTLS uses an explicit sequence number, rather than an implicit one.
+ * Sequence numbers are maintained separately for each epoch, with
+ * each sequence number initially being 0 for each epoch. The sequence
+ * number used to compute the DTLS MAC is the 64-bit value formed by
+ * concatenating the epoch and the sequence number.
+ *
+ * Sequence numbers do not wrap. If an implementation would need to wrap
+ * a sequence number, it must renegotiate instead. A sequence number is
+ * incremented after each record: specifically, the first record transmitted
+ * under a particular connection state MUST use sequence number 0.
*/
class Authenticator {
@@ -56,13 +63,30 @@
// sequence number + record type + protocol version + record length
private static final int BLOCK_SIZE_TLS = 8 + 1 + 2 + 2;
+ // the block size of DTLS v1.0 and later:
+ // epoch + sequence number + record type + protocol version + record length
+ private static final int BLOCK_SIZE_DTLS = 2 + 6 + 1 + 2 + 2;
+
+ private final boolean isDTLS;
+
/**
* Default construct, no message authentication token is initialized.
*
* Note that this construct can only be called for null MAC
*/
- Authenticator() {
- block = new byte[0];
+ protected Authenticator(boolean isDTLS) {
+ if (isDTLS) {
+ // For DTLS protocols, plaintexts use explicit epoch and
+ // sequence number in each record. The first 8 byte of
+ // the block is initialized for null MAC so that the
+ // epoch and sequence number can be acquired to generate
+ // plaintext records.
+ block = new byte[8];
+ } else {
+ block = new byte[0];
+ }
+
+ this.isDTLS = isDTLS;
}
/**
@@ -70,12 +94,22 @@
* SSL/TLS protocol.
*/
Authenticator(ProtocolVersion protocolVersion) {
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.isDTLSProtocol()) {
+ block = new byte[BLOCK_SIZE_DTLS];
+ block[9] = protocolVersion.major;
+ block[10] = protocolVersion.minor;
+
+ this.isDTLS = true;
+ } else if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
block = new byte[BLOCK_SIZE_TLS];
block[9] = protocolVersion.major;
block[10] = protocolVersion.minor;
+
+ this.isDTLS = false;
} else {
block = new byte[BLOCK_SIZE_SSL];
+
+ this.isDTLS = false;
}
}
@@ -93,11 +127,19 @@
* Conservatively, we don't allow more records to be generated
* when there are only 2^8 sequence numbers left.
*/
- return (block.length != 0 &&
+ if (isDTLS) {
+ return (block.length != 0 &&
+ // no epoch bytes, block[0] and block[1]
+ block[2] == (byte)0xFF && block[3] == (byte)0xFF &&
+ block[4] == (byte)0xFF && block[5] == (byte)0xFF &&
+ block[6] == (byte)0xFF);
+ } else {
+ return (block.length != 0 &&
block[0] == (byte)0xFF && block[1] == (byte)0xFF &&
block[2] == (byte)0xFF && block[3] == (byte)0xFF &&
block[4] == (byte)0xFF && block[5] == (byte)0xFF &&
block[6] == (byte)0xFF);
+ }
}
/**
@@ -113,14 +155,22 @@
final boolean seqNumIsHuge() {
/*
* Conservatively, we should ask for renegotiation when there are
- * only 2^48 sequence numbers left.
+ * only 2^32 sequence numbers left.
*/
- return (block.length != 0 &&
- block[0] == (byte)0xFF && block[1] == (byte)0xFF);
+ if (isDTLS) {
+ return (block.length != 0 &&
+ // no epoch bytes, block[0] and block[1]
+ block[2] == (byte)0xFF && block[3] == (byte)0xFF);
+ } else {
+ return (block.length != 0 &&
+ block[0] == (byte)0xFF && block[1] == (byte)0xFF &&
+ block[2] == (byte)0xFF && block[3] == (byte)0xFF);
+ }
}
/**
- * Gets the current sequence number.
+ * Gets the current sequence number, including the epoch number for
+ * DTLS protocols.
*
* @return the byte array of the current sequence number
*/
@@ -129,33 +179,83 @@
}
/**
+ * Sets the epoch number (only apply to DTLS protocols).
+ */
+ final void setEpochNumber(int epoch) {
+ if (!isDTLS) {
+ throw new RuntimeException(
+ "Epoch numbers apply to DTLS protocols only");
+ }
+
+ block[0] = (byte)((epoch >> 8) & 0xFF);
+ block[1] = (byte)(epoch & 0xFF);
+ }
+
+ /**
+ * Increase the sequence number.
+ */
+ final void increaseSequenceNumber() {
+ /*
+ * The sequence number in the block array is a 64-bit
+ * number stored in big-endian format.
+ */
+ int k = 7;
+ while ((k >= 0) && (++block[k] == 0)) {
+ k--;
+ }
+ }
+
+ /**
* Acquires the current message authentication information with the
* specified record type and fragment length, and then increases the
* sequence number.
*
* @param type the record type
* @param length the fragment of the record
+ * @param sequence the explicit sequence number of the record
+ *
* @return the byte array of the current message authentication information
*/
- final byte[] acquireAuthenticationBytes(byte type, int length) {
+ final byte[] acquireAuthenticationBytes(
+ byte type, int length, byte[] sequence) {
+
byte[] copy = block.clone();
+ if (sequence != null) {
+ if (sequence.length != 8) {
+ throw new RuntimeException(
+ "Insufficient explicit sequence number bytes");
+ }
+
+ System.arraycopy(sequence, 0, copy, 0, sequence.length);
+ } // Otherwise, use the implicit sequence number.
if (block.length != 0) {
copy[8] = type;
+
copy[copy.length - 2] = (byte)(length >> 8);
copy[copy.length - 1] = (byte)(length);
- /*
- * Increase the sequence number in the block array
- * it is a 64-bit number stored in big-endian format
- */
- int k = 7;
- while ((k >= 0) && (++block[k] == 0)) {
- k--;
+ if (sequence == null || sequence.length != 0) {
+ // Increase the implicit sequence number in the block array.
+ increaseSequenceNumber();
}
}
return copy;
}
+ final static long toLong(byte[] recordEnS) {
+ if (recordEnS != null && recordEnS.length == 8) {
+ return ((recordEnS[0] & 0xFFL) << 56) |
+ ((recordEnS[1] & 0xFFL) << 48) |
+ ((recordEnS[2] & 0xFFL) << 40) |
+ ((recordEnS[3] & 0xFFL) << 32) |
+ ((recordEnS[4] & 0xFFL) << 24) |
+ ((recordEnS[5] & 0xFFL) << 16) |
+ ((recordEnS[6] & 0xFFL) << 8) |
+ (recordEnS[7] & 0xFFL);
+ }
+
+ return -1L;
+ }
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/CipherBox.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/CipherBox.java Tue Jun 02 09:15:47 2015 -0700
@@ -154,9 +154,9 @@
* NULL cipherbox. Identity operation, no encryption.
*/
private CipherBox() {
- this.protocolVersion = ProtocolVersion.DEFAULT;
+ this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
this.cipher = null;
- this.cipherType = STREAM_CIPHER;
+ this.cipherType = NULL_CIPHER;
this.fixedIv = new byte[0];
this.key = null;
this.mode = Cipher.ENCRYPT_MODE; // choose at random
@@ -197,7 +197,7 @@
*/
if (iv == null && bulkCipher.ivSize != 0 &&
mode == Cipher.DECRYPT_MODE &&
- protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ protocolVersion.useTLS11PlusSpec()) {
iv = getFixedMask(bulkCipher.ivSize);
}
@@ -491,7 +491,7 @@
newLen = removePadding(
buf, offset, newLen, tagLen, blockSize, protocolVersion);
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
if (newLen < blockSize) {
throw new BadPaddingException("invalid explicit IV");
}
@@ -573,7 +573,7 @@
newLen = removePadding(bb, tagLen, blockSize, protocolVersion);
// check the explicit IV of TLS v1.1 or later
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
if (newLen < blockSize) {
throw new BadPaddingException("invalid explicit IV");
}
@@ -746,7 +746,7 @@
// The padding data should be filled with the padding length value.
int[] results = checkPadding(buf, offset + newLen,
padLen + 1, (byte)(padLen & 0xFF));
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
if (results[0] != 0) { // padding data has invalid bytes
throw new BadPaddingException("Invalid TLS padding data");
}
@@ -792,7 +792,7 @@
int[] results = checkPadding(
bb.duplicate().position(offset + newLen),
(byte)(padLen & 0xFF));
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
if (results[0] != 0) { // padding data has invalid bytes
throw new BadPaddingException("Invalid TLS padding data");
}
@@ -873,7 +873,7 @@
// For block ciphers, the explicit IV length is of length
// SecurityParameters.record_iv_length, which is equal to
// the SecurityParameters.block_size.
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
return cipher.getBlockSize();
}
break;
@@ -902,7 +902,7 @@
* @return the explicit nonce size of the cipher.
*/
int applyExplicitNonce(Authenticator authenticator, byte contentType,
- ByteBuffer bb) throws BadPaddingException {
+ ByteBuffer bb, byte[] sequence) throws BadPaddingException {
switch (cipherType) {
case BLOCK_CIPHER:
// sanity check length of the ciphertext
@@ -918,7 +918,7 @@
// For block ciphers, the explicit IV length is of length
// SecurityParameters.record_iv_length, which is equal to
// the SecurityParameters.block_size.
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
return cipher.getBlockSize();
}
break;
@@ -945,7 +945,8 @@
// update the additional authentication data
byte[] aad = authenticator.acquireAuthenticationBytes(
- contentType, bb.remaining() - recordIvSize - tagSize);
+ contentType, bb.remaining() - recordIvSize - tagSize,
+ sequence);
cipher.updateAAD(aad);
return recordIvSize;
@@ -957,33 +958,6 @@
}
/*
- * Applies the explicit nonce/IV to this cipher. This method is used to
- * decrypt an SSL/TLS input record.
- *
- * The returned value is the SecurityParameters.record_iv_length in
- * RFC 4346/5246. It is the size of explicit IV for CBC mode, and the
- * size of explicit nonce for AEAD mode.
- *
- * @param authenticator the authenticator to get the additional
- * authentication data
- * @param contentType the content type of the input record
- * @param buf the byte array to get the explicit nonce from
- * @param offset the offset of the byte buffer
- * @param cipheredLength the ciphered fragment length of the output
- * record, it is the TLSCiphertext.length in RFC 4346/5246.
- *
- * @return the explicit nonce size of the cipher.
- */
- int applyExplicitNonce(Authenticator authenticator,
- byte contentType, byte[] buf, int offset,
- int cipheredLength) throws BadPaddingException {
-
- ByteBuffer bb = ByteBuffer.wrap(buf, offset, cipheredLength);
-
- return applyExplicitNonce(authenticator, contentType, bb);
- }
-
- /*
* Creates the explicit nonce/IV to this cipher. This method is used to
* encrypt an SSL/TLS output record.
*
@@ -1005,7 +979,7 @@
byte[] nonce = new byte[0];
switch (cipherType) {
case BLOCK_CIPHER:
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
// For block ciphers, the explicit IV length is of length
// SecurityParameters.record_iv_length, which is equal to
// the SecurityParameters.block_size.
@@ -1034,9 +1008,10 @@
"invalid key or spec in GCM mode", ikae);
}
- // update the additional authentication data
+ // Update the additional authentication data, using the
+ // implicit sequence number of the authenticator.
byte[] aad = authenticator.acquireAuthenticationBytes(
- contentType, fragmentLength);
+ contentType, fragmentLength, null);
cipher.updateAAD(aad);
break;
}
@@ -1044,6 +1019,93 @@
return nonce;
}
+ // See also CipherSuite.calculatePacketSize().
+ int calculatePacketSize(int fragmentSize, int macLen, int headerSize) {
+ int packetSize = fragmentSize;
+ if (cipher != null) {
+ int blockSize = cipher.getBlockSize();
+ switch (cipherType) {
+ case BLOCK_CIPHER:
+ packetSize += macLen;
+ packetSize += 1; // 1 byte padding length field
+ packetSize += // use the minimal padding
+ (blockSize - (packetSize % blockSize)) % blockSize;
+ if (protocolVersion.useTLS11PlusSpec()) {
+ packetSize += blockSize; // explicit IV
+ }
+
+ break;
+ case AEAD_CIPHER:
+ packetSize += recordIvSize;
+ packetSize += tagSize;
+
+ break;
+ default: // NULL_CIPHER or STREAM_CIPHER
+ packetSize += macLen;
+ }
+ }
+
+ return packetSize + headerSize;
+ }
+
+ // See also CipherSuite.calculateFragSize().
+ int calculateFragmentSize(int packetLimit, int macLen, int headerSize) {
+ int fragLen = packetLimit - headerSize;
+ if (cipher != null) {
+ int blockSize = cipher.getBlockSize();
+ switch (cipherType) {
+ case BLOCK_CIPHER:
+ if (protocolVersion.useTLS11PlusSpec()) {
+ fragLen -= blockSize; // explicit IV
+ }
+ fragLen -= (fragLen % blockSize); // cannot hold a block
+ // No padding for a maximum fragment.
+ fragLen -= 1; // 1 byte padding length field: 0x00
+ fragLen -= macLen;
+
+ break;
+ case AEAD_CIPHER:
+ fragLen -= recordIvSize;
+ fragLen -= tagSize;
+
+ break;
+ default: // NULL_CIPHER or STREAM_CIPHER
+ fragLen -= macLen;
+ }
+ }
+
+ return fragLen;
+ }
+
+ // Estimate the maximum fragment size of a received packet.
+ int estimateFragmentSize(int packetSize, int macLen, int headerSize) {
+ int fragLen = packetSize - headerSize;
+ if (cipher != null) {
+ int blockSize = cipher.getBlockSize();
+ switch (cipherType) {
+ case BLOCK_CIPHER:
+ if (protocolVersion.useTLS11PlusSpec()) {
+ fragLen -= blockSize; // explicit IV
+ }
+ // No padding for a maximum fragment.
+ fragLen -= 1; // 1 byte padding length field: 0x00
+ fragLen -= macLen;
+
+ break;
+ case AEAD_CIPHER:
+ fragLen -= recordIvSize;
+ fragLen -= tagSize;
+
+ break;
+ default: // NULL_CIPHER or STREAM_CIPHER
+ fragLen -= macLen;
+ }
+ }
+
+ return fragLen;
+ }
+
+
/*
* Is this cipher available?
*
@@ -1100,7 +1162,7 @@
if ((fragmentLen % blockSize) == 0) {
int minimal = tagLen + 1;
minimal = (minimal >= blockSize) ? minimal : blockSize;
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
minimal += blockSize; // plus the size of the explicit IV
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/CipherSuite.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/CipherSuite.java Tue Jun 02 09:15:47 2015 -0700
@@ -122,9 +122,15 @@
final boolean allowed;
// obsoleted since protocol version
+ //
+ // TLS version is used. If checking DTLS versions, please map to
+ // TLS version firstly. See ProtocolVersion.mapToTLSProtocol().
final int obsoleted;
- // supported since protocol version
+ // supported since protocol version (TLS version is used)
+ //
+ // TLS version is used. If checking DTLS versions, please map to
+ // TLS version firstly. See ProtocolVersion.mapToTLSProtocol().
final int supported;
/**
@@ -182,6 +188,70 @@
return this != C_SCSV && isAvailable();
}
+ // See also CipherBox.calculatePacketSize().
+ int calculatePacketSize(int fragmentSize,
+ ProtocolVersion protocolVersion, boolean isDTLS) {
+
+ int packetSize = fragmentSize;
+ if (cipher != B_NULL) {
+ int blockSize = cipher.ivSize;
+ switch (cipher.cipherType) {
+ case BLOCK_CIPHER:
+ packetSize += macAlg.size;
+ packetSize += 1; // 1 byte padding length field
+ packetSize += // use the minimal padding
+ (blockSize - (packetSize % blockSize)) % blockSize;
+ if (protocolVersion.useTLS11PlusSpec()) {
+ packetSize += blockSize; // explicit IV
+ }
+
+ break;
+ case AEAD_CIPHER:
+ packetSize += cipher.ivSize - cipher.fixedIvSize; // record IV
+ packetSize += cipher.tagSize;
+
+ break;
+ default: // NULL_CIPHER or STREAM_CIPHER
+ packetSize += macAlg.size;
+ }
+ }
+
+ return packetSize +
+ (isDTLS ? DTLSRecord.headerSize : SSLRecord.headerSize);
+ }
+
+ // See also CipherBox.calculateFragmentSize().
+ int calculateFragSize(int packetLimit,
+ ProtocolVersion protocolVersion, boolean isDTLS) {
+
+ int fragSize = packetLimit -
+ (isDTLS ? DTLSRecord.headerSize : SSLRecord.headerSize);
+ if (cipher != B_NULL) {
+ int blockSize = cipher.ivSize;
+ switch (cipher.cipherType) {
+ case BLOCK_CIPHER:
+ if (protocolVersion.useTLS11PlusSpec()) {
+ fragSize -= blockSize; // explicit IV
+ }
+ fragSize -= (fragSize % blockSize); // cannot hold a block
+ // No padding for a maximum fragment.
+ fragSize -= 1; // 1 byte padding length field: 0x00
+ fragSize -= macAlg.size;
+
+ break;
+ case AEAD_CIPHER:
+ fragSize -= cipher.tagSize;
+ fragSize -= cipher.ivSize - cipher.fixedIvSize; // record IV
+
+ break;
+ default: // NULL_CIPHER or STREAM_CIPHER
+ fragSize -= macAlg.size;
+ }
+ }
+
+ return fragSize;
+ }
+
/**
* Compares CipherSuites based on their priority. Has the effect of
* sorting CipherSuites when put in a sorted collection, which is
@@ -242,7 +312,7 @@
return c;
}
- // for use by CipherSuiteList only
+ // for use by SSLContextImpl only
static Collection<CipherSuite> allowedCipherSuites() {
return nameMap.values();
}
@@ -372,7 +442,8 @@
}
static enum CipherType {
- STREAM_CIPHER, // null or stream cipher
+ NULL_CIPHER, // null cipher
+ STREAM_CIPHER, // stream cipher
BLOCK_CIPHER, // block cipher in CBC mode
AEAD_CIPHER // AEAD cipher
}
@@ -387,7 +458,7 @@
static enum BulkCipher {
// export strength ciphers
- B_NULL("NULL", STREAM_CIPHER, 0, 0, 0, 0, true),
+ B_NULL("NULL", NULL_CIPHER, 0, 0, 0, 0, true),
B_RC4_40(CIPHER_RC4, STREAM_CIPHER, 5, 16, 0, 0, true),
B_RC2_40("RC2", BLOCK_CIPHER, 5, 16, 8, 0, false),
B_DES_40(CIPHER_DES, BLOCK_CIPHER, 5, 8, 8, 0, true),
@@ -568,7 +639,7 @@
iv = new IvParameterSpec(new byte[cipher.ivSize]);
}
temporary = cipher.newCipher(
- ProtocolVersion.DEFAULT,
+ ProtocolVersion.DEFAULT_TLS,
key, iv, secureRandom, true);
b = temporary.isAvailable();
} catch (NoSuchAlgorithmException e) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Ciphertext.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import static sun.security.ssl.HandshakeMessage.*;
+
+/*
+ * enumeration of record type
+ */
+final class Ciphertext {
+ final static Ciphertext CIPHERTEXT_NULL = new Ciphertext();
+
+ RecordType recordType;
+ long recordSN;
+
+ HandshakeStatus handshakeStatus; // null if not used or not handshaking
+
+ Ciphertext() {
+ this.recordType = null;
+ this.recordSN = -1L;
+ this.handshakeStatus = null;
+ }
+
+ Ciphertext(RecordType recordType, long recordSN) {
+ this.recordType = recordType;
+ this.recordSN = recordSN;
+ this.handshakeStatus = null;
+ }
+
+ static enum RecordType {
+ RECORD_CHANGE_CIPHER_SPEC (
+ Record.ct_change_cipher_spec, ht_not_applicable),
+ RECORD_ALERT (
+ Record.ct_alert, ht_not_applicable),
+ RECORD_HELLO_REQUEST (
+ Record.ct_handshake, ht_hello_request),
+ RECORD_CLIENT_HELLO (
+ Record.ct_handshake, ht_client_hello),
+ RECORD_SERVER_HELLO (
+ Record.ct_handshake, ht_server_hello),
+ RECORD_HELLO_VERIFY_REQUEST (
+ Record.ct_handshake, ht_hello_verify_request),
+ RECORD_NEW_SESSION_TICKET (
+ Record.ct_handshake, ht_new_session_ticket),
+ RECORD_CERTIFICATE (
+ Record.ct_handshake, ht_certificate),
+ RECORD_SERVER_KEY_EXCHANGE (
+ Record.ct_handshake, ht_server_key_exchange),
+ RECORD_CERTIFICATE_REQUEST (
+ Record.ct_handshake, ht_certificate_request),
+ RECORD_SERVER_HELLO_DONE (
+ Record.ct_handshake, ht_server_hello_done),
+ RECORD_CERTIFICATE_VERIFY (
+ Record.ct_handshake, ht_certificate_verify),
+ RECORD_CLIENT_KEY_EXCHANGE (
+ Record.ct_handshake, ht_client_key_exchange),
+ RECORD_FINISHED (
+ Record.ct_handshake, ht_finished),
+ RECORD_CERTIFICATE_URL (
+ Record.ct_handshake, ht_certificate_url),
+ RECORD_CERTIFICATE_STATUS (
+ Record.ct_handshake, ht_certificate_status),
+ RECORD_SUPPLIEMENTAL_DATA (
+ Record.ct_handshake, ht_supplemental_data),
+ RECORD_APPLICATION_DATA (
+ Record.ct_application_data, ht_not_applicable);
+
+ byte contentType;
+ byte handshakeType;
+
+ private RecordType(byte contentType, byte handshakeType) {
+ this.contentType = contentType;
+ this.handshakeType = handshakeType;
+ }
+
+ static RecordType valueOf(byte contentType, byte handshakeType) {
+ if (contentType == Record.ct_change_cipher_spec) {
+ return RECORD_CHANGE_CIPHER_SPEC;
+ } else if (contentType == Record.ct_alert) {
+ return RECORD_ALERT;
+ } else if (contentType == Record.ct_application_data) {
+ return RECORD_APPLICATION_DATA;
+ } else if (handshakeType == ht_hello_request) {
+ return RECORD_HELLO_REQUEST;
+ } else if (handshakeType == ht_client_hello) {
+ return RECORD_CLIENT_HELLO;
+ } else if (handshakeType == ht_server_hello) {
+ return RECORD_SERVER_HELLO;
+ } else if (handshakeType == ht_hello_verify_request) {
+ return RECORD_HELLO_VERIFY_REQUEST;
+ } else if (handshakeType == ht_new_session_ticket) {
+ return RECORD_NEW_SESSION_TICKET;
+ } else if (handshakeType == ht_certificate) {
+ return RECORD_CERTIFICATE;
+ } else if (handshakeType == ht_server_key_exchange) {
+ return RECORD_SERVER_KEY_EXCHANGE;
+ } else if (handshakeType == ht_certificate_request) {
+ return RECORD_CERTIFICATE_REQUEST;
+ } else if (handshakeType == ht_server_hello_done) {
+ return RECORD_SERVER_HELLO_DONE;
+ } else if (handshakeType == ht_certificate_verify) {
+ return RECORD_CERTIFICATE_VERIFY;
+ } else if (handshakeType == ht_client_key_exchange) {
+ return RECORD_CLIENT_KEY_EXCHANGE;
+ } else if (handshakeType == ht_finished) {
+ return RECORD_FINISHED;
+ } else if (handshakeType == ht_certificate_url) {
+ return RECORD_CERTIFICATE_URL;
+ } else if (handshakeType == ht_certificate_status) {
+ return RECORD_CERTIFICATE_STATUS;
+ } else if (handshakeType == ht_supplemental_data) {
+ return RECORD_SUPPLIEMENTAL_DATA;
+ }
+
+ // otherwise, invalid record type
+ throw new IllegalArgumentException(
+ "Invalid record type (ContentType:" + contentType +
+ ", HandshakeType:" + handshakeType + ")");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ClientAuthType.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+/**
+ * Client authentication type.
+ */
+enum ClientAuthType {
+ CLIENT_AUTH_NONE, // turn off client authentication
+ CLIENT_AUTH_REQUESTED, // need to request client authentication
+ CLIENT_AUTH_REQUIRED // require client authentication
+}
+
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java Tue Jun 02 09:15:47 2015 -0700
@@ -44,8 +44,6 @@
import javax.net.ssl.*;
-import javax.security.auth.Subject;
-
import sun.security.ssl.HandshakeMessage.*;
import static sun.security.ssl.CipherSuite.KeyExchange.*;
@@ -141,11 +139,20 @@
private final static boolean allowUnsafeServerCertChange =
Debug.getBooleanProperty("jdk.tls.allowUnsafeServerCertChange", false);
+ // To switch off the max_fragment_length extension.
+ private final static boolean enableMFLExtension =
+ Debug.getBooleanProperty("jsse.enableMFLExtension", false);
+
private List<SNIServerName> requestedServerNames =
Collections.<SNIServerName>emptyList();
+ // maximum fragment length
+ private int requestedMFLength = -1; // -1: no fragment length limit
+
private boolean serverNamesAccepted = false;
+ private ClientHello initialClientHelloMsg = null; // DTLS only
+
/*
* the reserved server certificate chain in previous handshaking
*
@@ -172,11 +179,12 @@
ProtocolList enabledProtocols,
ProtocolVersion activeProtocolVersion,
boolean isInitialHandshake, boolean secureRenegotiation,
- byte[] clientVerifyData, byte[] serverVerifyData) {
+ byte[] clientVerifyData, byte[] serverVerifyData,
+ boolean isDTLS) {
super(engine, context, enabledProtocols, true, true,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
- clientVerifyData, serverVerifyData);
+ clientVerifyData, serverVerifyData, isDTLS);
}
/*
@@ -191,29 +199,48 @@
*/
@Override
void processMessage(byte type, int messageLen) throws IOException {
- if (state >= type
- && (type != HandshakeMessage.ht_hello_request)) {
- throw new SSLProtocolException(
- "Handshake message sequence violation, " + type);
- }
+ // check the handshake state
+ handshakeState.check(type);
switch (type) {
case HandshakeMessage.ht_hello_request:
- this.serverHelloRequest(new HelloRequest(input));
+ HelloRequest helloRequest = new HelloRequest(input);
+ handshakeState.update(helloRequest, resumingSession);
+ this.serverHelloRequest(helloRequest);
+ break;
+
+ case HandshakeMessage.ht_hello_verify_request:
+ if (!isDTLS) {
+ throw new SSLProtocolException(
+ "hello_verify_request is not a SSL/TLS handshake message");
+ }
+
+ HelloVerifyRequest helloVerifyRequest =
+ new HelloVerifyRequest(input, messageLen);
+ handshakeState.update(helloVerifyRequest, resumingSession);
+ this.helloVerifyRequest(helloVerifyRequest);
break;
case HandshakeMessage.ht_server_hello:
- this.serverHello(new ServerHello(input, messageLen));
+ ServerHello serverHello = new ServerHello(input, messageLen);
+ this.serverHello(serverHello);
+
+ // This handshake state update needs the resumingSession value
+ // set by serverHello().
+ handshakeState.update(serverHello, resumingSession);
break;
case HandshakeMessage.ht_certificate:
if (keyExchange == K_DH_ANON || keyExchange == K_ECDH_ANON
- || keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) {
+ || ClientKeyExchangeService.find(keyExchange.name) != null) {
+ // No external key exchange provider needs a cert now.
fatalSE(Alerts.alert_unexpected_message,
"unexpected server cert chain");
// NOTREACHED
}
- this.serverCertificate(new CertificateMsg(input));
+ CertificateMsg certificateMsg = new CertificateMsg(input);
+ handshakeState.update(certificateMsg, resumingSession);
+ this.serverCertificate(certificateMsg);
serverKey =
session.getPeerCertificates()[0].getPublicKey();
break;
@@ -249,41 +276,52 @@
}
try {
- this.serverKeyExchange(new RSA_ServerKeyExchange(input));
+ RSA_ServerKeyExchange rsaSrvKeyExchange =
+ new RSA_ServerKeyExchange(input);
+ handshakeState.update(rsaSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(rsaSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_DH_ANON:
try {
- this.serverKeyExchange(new DH_ServerKeyExchange(
- input, protocolVersion));
+ DH_ServerKeyExchange dhSrvKeyExchange =
+ new DH_ServerKeyExchange(input, protocolVersion);
+ handshakeState.update(dhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(dhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_DHE_DSS:
case K_DHE_RSA:
try {
- this.serverKeyExchange(new DH_ServerKeyExchange(
- input, serverKey,
- clnt_random.random_bytes, svr_random.random_bytes,
- messageLen,
- localSupportedSignAlgs, protocolVersion));
+ DH_ServerKeyExchange dhSrvKeyExchange =
+ new DH_ServerKeyExchange(
+ input, serverKey,
+ clnt_random.random_bytes, svr_random.random_bytes,
+ messageLen,
+ localSupportedSignAlgs, protocolVersion);
+ handshakeState.update(dhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(dhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_ECDHE_ECDSA:
case K_ECDHE_RSA:
case K_ECDH_ANON:
try {
- this.serverKeyExchange(new ECDH_ServerKeyExchange
- (input, serverKey, clnt_random.random_bytes,
- svr_random.random_bytes,
- localSupportedSignAlgs, protocolVersion));
+ ECDH_ServerKeyExchange ecdhSrvKeyExchange =
+ new ECDH_ServerKeyExchange
+ (input, serverKey, clnt_random.random_bytes,
+ svr_random.random_bytes,
+ localSupportedSignAlgs, protocolVersion);
+ handshakeState.update(ecdhSrvKeyExchange, resumingSession);
+ this.serverKeyExchange(ecdhSrvKeyExchange);
} catch (GeneralSecurityException e) {
- throwSSLException("Server key", e);
+ throw new SSLException("Server key", e);
}
break;
case K_RSA:
@@ -294,13 +332,9 @@
throw new SSLProtocolException(
"Protocol violation: server sent a server key exchange"
+ " message for key exchange " + keyExchange);
- case K_KRB5:
- case K_KRB5_EXPORT:
- throw new SSLProtocolException(
- "unexpected receipt of server key exchange algorithm");
default:
throw new SSLProtocolException(
- "unsupported key exchange algorithm = "
+ "unsupported or unexpected key exchange algorithm = "
+ keyExchange);
}
break;
@@ -311,17 +345,19 @@
throw new SSLHandshakeException(
"Client authentication requested for "+
"anonymous cipher suite.");
- } else if (keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) {
+ } else if (ClientKeyExchangeService.find(keyExchange.name) != null) {
+ // No external key exchange provider needs a cert now.
throw new SSLHandshakeException(
"Client certificate requested for "+
- "kerberos cipher suite.");
+ "external cipher suite: " + keyExchange);
}
certRequest = new CertificateRequest(input, protocolVersion);
if (debug != null && Debug.isOn("handshake")) {
certRequest.print(System.out);
}
+ handshakeState.update(certRequest, resumingSession);
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
Collection<SignatureAndHashAlgorithm> peerSignAlgs =
certRequest.getSignAlgorithms();
if (peerSignAlgs == null || peerSignAlgs.isEmpty()) {
@@ -345,33 +381,24 @@
break;
case HandshakeMessage.ht_server_hello_done:
- this.serverHelloDone(new ServerHelloDone(input));
+ ServerHelloDone serverHelloDone = new ServerHelloDone(input);
+ handshakeState.update(serverHelloDone, resumingSession);
+ this.serverHelloDone(serverHelloDone);
+
break;
case HandshakeMessage.ht_finished:
- // A ChangeCipherSpec record must have been received prior to
- // reception of the Finished message (RFC 5246, 7.4.9).
- if (!receivedChangeCipherSpec()) {
- fatalSE(Alerts.alert_handshake_failure,
- "Received Finished message before ChangeCipherSpec");
- }
+ Finished serverFinished =
+ new Finished(protocolVersion, input, cipherSuite);
+ handshakeState.update(serverFinished, resumingSession);
+ this.serverFinished(serverFinished);
- this.serverFinished(
- new Finished(protocolVersion, input, cipherSuite));
break;
default:
throw new SSLProtocolException(
"Illegal client handshake msg, " + type);
}
-
- //
- // Move state machine forward if the message handling
- // code didn't already do so
- //
- if (state < type) {
- state = type;
- }
}
/*
@@ -389,10 +416,10 @@
// Could be (e.g. at connection setup) that we already
// sent the "client hello" but the server's not seen it.
//
- if (state < HandshakeMessage.ht_client_hello) {
+ if (!clientHelloDelivered) {
if (!secureRenegotiation && !allowUnsafeRenegotiation) {
// renegotiation is not allowed.
- if (activeProtocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (activeProtocolVersion.useTLS10PlusSpec()) {
// response with a no_renegotiation warning,
warningSE(Alerts.alert_no_renegotiation);
@@ -428,6 +455,29 @@
}
}
+ private void helloVerifyRequest(
+ HelloVerifyRequest mesg) throws IOException {
+
+ if (debug != null && Debug.isOn("handshake")) {
+ mesg.print(System.out);
+ }
+
+ //
+ // Note that HelloVerifyRequest.server_version is used solely to
+ // indicate packet formatting, and not as part of version negotiation.
+ // Need not to check version values match for HelloVerifyRequest
+ // message.
+ //
+ initialClientHelloMsg.cookie = mesg.cookie.clone();
+
+ if (debug != null && Debug.isOn("handshake")) {
+ initialClientHelloMsg.print(System.out);
+ }
+
+ // deliver the ClientHello message with cookie
+ initialClientHelloMsg.write(output);
+ handshakeState.update(initialClientHelloMsg, resumingSession);
+ }
/*
* Server chooses session parameters given options created by the
@@ -441,6 +491,9 @@
* probably authentication getting done.
*/
private void serverHello(ServerHello mesg) throws IOException {
+ // Dispose the reserved ClientHello message (if exists).
+ initialClientHelloMsg = null;
+
serverKeyExchangeReceived = false;
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
@@ -536,7 +589,7 @@
}
setCipherSuite(mesg.cipherSuite);
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
handshakeHash.setFinishedAlg(cipherSuite.prfAlg.getPRFHashAlg());
}
@@ -569,51 +622,22 @@
}
// validate subject identity
- if (sessionSuite.keyExchange == K_KRB5 ||
- sessionSuite.keyExchange == K_KRB5_EXPORT) {
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(sessionSuite.keyExchange.name);
+ if (p != null) {
Principal localPrincipal = session.getLocalPrincipal();
- Subject subject = null;
- try {
- subject = AccessController.doPrivileged(
- new PrivilegedExceptionAction<Subject>() {
- @Override
- public Subject run() throws Exception {
- return Krb5Helper.getClientSubject(getAccSE());
- }});
- } catch (PrivilegedActionException e) {
- subject = null;
- if (debug != null && Debug.isOn("session")) {
- System.out.println("Attempt to obtain" +
- " subject failed!");
- }
- }
-
- if (subject != null) {
- // Eliminate dependency on KerberosPrincipal
- Set<Principal> principals =
- subject.getPrincipals(Principal.class);
- if (!principals.contains(localPrincipal)) {
- throw new SSLProtocolException("Server resumed" +
- " session with wrong subject identity");
- } else {
- if (debug != null && Debug.isOn("session"))
- System.out.println("Subject identity is same");
- }
+ if (p.isRelated(true, getAccSE(), localPrincipal)) {
+ if (debug != null && Debug.isOn("session"))
+ System.out.println("Subject identity is same");
} else {
- if (debug != null && Debug.isOn("session"))
- System.out.println("Kerberos credentials are not" +
- " present in the current Subject; check if " +
- " javax.security.auth.useSubjectAsCreds" +
- " system property has been set to false");
- throw new SSLProtocolException
- ("Server resumed session with no subject");
+ throw new SSLProtocolException("Server resumed" +
+ " session with wrong subject identity or no subject");
}
}
- // looks fine; resume it, and update the state machine.
+ // looks fine; resume it.
resumingSession = true;
- state = HandshakeMessage.ht_finished - 1;
calculateConnectionKeys(session.getMasterSecret());
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Server resumed " + session);
@@ -627,6 +651,24 @@
}
}
+ // check the "max_fragment_length" extension
+ MaxFragmentLengthExtension maxFragLenExt = (MaxFragmentLengthExtension)
+ mesg.extensions.get(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+ if (maxFragLenExt != null) {
+ if ((requestedMFLength == -1) ||
+ maxFragLenExt.getMaxFragLen() != requestedMFLength) {
+ // If the client did not request this extension, or the
+ // response value is different from the length it requested,
+ // abort the handshake with a fatal illegal_parameter alert.
+ fatalSE(Alerts.alert_illegal_parameter,
+ "Failed to negotiate the max_fragment_length");
+ }
+ } else if (!resumingSession) {
+ // no "max_fragment_length" extension
+ requestedMFLength = -1;
+ } // Otherwise, using the value negotiated during the original
+ // session initiation
+
if (resumingSession && session != null) {
setHandshakeSessionSE(session);
// Reserve the handshake state if this is a session-resumption
@@ -657,6 +699,8 @@
getLocalSupportedSignAlgs(),
mesg.sessionId, getHostSE(), getPortSE());
session.setRequestedServerNames(requestedServerNames);
+ session.setNegotiatedMaxFragSize(requestedMFLength);
+ session.setMaximumPacketSize(maximumPacketSize);
setHandshakeSessionSE(session);
if (debug != null && Debug.isOn("handshake")) {
System.out.println("** " + cipherSuite);
@@ -681,7 +725,6 @@
ephemeralServerKey = mesg.getPublicKey();
}
-
/*
* Diffie-Hellman key exchange. We save the server public key and
* our own D-H algorithm object so we can defer key calculations
@@ -716,13 +759,6 @@
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
}
- /*
- * Always make sure the input has been digested before we
- * start emitting data, to ensure the hashes are correctly
- * computed for the Finished and CertificateVerify messages
- * which we send (here).
- */
- input.digestNow();
/*
* FIRST ... if requested, send an appropriate Certificate chain
@@ -817,7 +853,7 @@
// server. For SSLv3, send the no_certificate alert;
// TLS uses an empty cert chain instead.
//
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
m1 = new CertificateMsg(new X509Certificate [0]);
} else {
warningSE(Alerts.alert_no_certificate);
@@ -837,6 +873,7 @@
m1.print(System.out);
}
m1.write(output);
+ handshakeState.update(m1, resumingSession);
}
}
@@ -943,8 +980,14 @@
ecdh = new ECDHCrypt(params, sslContext.getSecureRandom());
m2 = new ECDHClientKeyExchange(ecdh.getPublicKey());
break;
- case K_KRB5:
- case K_KRB5_EXPORT:
+ default:
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(keyExchange.name);
+ if (p == null) {
+ // somethings very wrong
+ throw new RuntimeException
+ ("Unsupported key exchange: " + keyExchange);
+ }
String sniHostname = null;
for (SNIServerName serverName : requestedServerNames) {
if (serverName instanceof SNIHostName) {
@@ -953,13 +996,13 @@
}
}
- KerberosClientKeyExchange kerberosMsg = null;
+ ClientKeyExchange exMsg = null;
if (sniHostname != null) {
// use first requested SNI hostname
try {
- kerberosMsg = new KerberosClientKeyExchange(
- sniHostname, getAccSE(), protocolVersion,
- sslContext.getSecureRandom());
+ exMsg = p.createClientExchange(
+ sniHostname, getAccSE(), protocolVersion,
+ sslContext.getSecureRandom());
} catch(IOException e) {
if (serverNamesAccepted) {
// server accepted requested SNI hostname,
@@ -975,32 +1018,28 @@
}
}
- if (kerberosMsg == null) {
+ if (exMsg == null) {
String hostname = getHostSE();
if (hostname == null) {
throw new IOException("Hostname is required" +
- " to use Kerberos cipher suites");
+ " to use " + keyExchange + " key exchange");
}
- kerberosMsg = new KerberosClientKeyExchange(
- hostname, getAccSE(), protocolVersion,
- sslContext.getSecureRandom());
+ exMsg = p.createClientExchange(
+ hostname, getAccSE(), protocolVersion,
+ sslContext.getSecureRandom());
}
// Record the principals involved in exchange
- session.setPeerPrincipal(kerberosMsg.getPeerPrincipal());
- session.setLocalPrincipal(kerberosMsg.getLocalPrincipal());
- m2 = kerberosMsg;
+ session.setPeerPrincipal(exMsg.getPeerPrincipal());
+ session.setLocalPrincipal(exMsg.getLocalPrincipal());
+ m2 = exMsg;
break;
- default:
- // somethings very wrong
- throw new RuntimeException
- ("Unsupported key exchange: " + keyExchange);
}
if (debug != null && Debug.isOn("handshake")) {
m2.print(System.out);
}
m2.write(output);
-
+ handshakeState.update(m2, resumingSession);
/*
* THIRD, send a "change_cipher_spec" record followed by the
@@ -1010,8 +1049,6 @@
* to compute the "Finished" message, and to compute the keys used
* to protect all records following the change_cipher_spec.
*/
-
- output.doHashes();
output.flush();
/*
@@ -1027,13 +1064,6 @@
case K_RSA_EXPORT:
preMasterSecret = ((RSAClientKeyExchange)m2).preMaster;
break;
- case K_KRB5:
- case K_KRB5_EXPORT:
- byte[] secretBytes =
- ((KerberosClientKeyExchange)m2).getUnencryptedPreMasterSecret();
- preMasterSecret = new SecretKeySpec(secretBytes,
- "TlsPremasterSecret");
- break;
case K_DHE_RSA:
case K_DHE_DSS:
case K_DH_ANON:
@@ -1049,8 +1079,13 @@
preMasterSecret = ecdh.getAgreedSecret(serverKey);
break;
default:
- throw new IOException("Internal error: unknown key exchange "
- + keyExchange);
+ if (ClientKeyExchangeService.find(keyExchange.name) != null) {
+ preMasterSecret =
+ ((ClientKeyExchange) m2).clientKeyExchange();
+ } else {
+ throw new IOException("Internal error: unknown key exchange "
+ + keyExchange);
+ }
}
calculateKeys(preMasterSecret, null);
@@ -1069,7 +1104,7 @@
CertificateVerify m3;
try {
SignatureAndHashAlgorithm preferableSignatureAlgorithm = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
peerSupportedSignAlgs, signingKey.getAlgorithm(),
@@ -1103,13 +1138,17 @@
m3.print(System.out);
}
m3.write(output);
- output.doHashes();
+ handshakeState.update(m3, resumingSession);
+ output.flush();
}
/*
* OK, that's that!
*/
sendChangeCipherAndFinish(false);
+
+ // expecting the final ChangeCipherSpec and Finished messages
+ expectingFinishFlightSE();
}
@@ -1158,8 +1197,9 @@
* completed handshakes.
*/
if (resumingSession) {
- input.digestNow();
sendChangeCipherAndFinish(true);
+ } else {
+ handshakeFinished = true;
}
session.setLastAccessedTime(System.currentTimeMillis());
@@ -1188,6 +1228,10 @@
*/
private void sendChangeCipherAndFinish(boolean finishedTag)
throws IOException {
+
+ // Reload if this message has been reserved.
+ handshakeHash.reload();
+
Finished mesg = new Finished(protocolVersion, handshakeHash,
Finished.CLIENT, session.getMasterSecret(), cipherSuite);
@@ -1205,13 +1249,6 @@
if (secureRenegotiation) {
clientVerifyData = mesg.getVerifyData();
}
-
- /*
- * Update state machine so server MUST send 'finished' next.
- * (In "long" handshake case; in short case, we're responding
- * to its message.)
- */
- state = HandshakeMessage.ht_finished - 1;
}
@@ -1361,10 +1398,10 @@
// create the ClientHello message
ClientHello clientHelloMessage = new ClientHello(
sslContext.getSecureRandom(), maxProtocolVersion,
- sessionId, cipherSuites);
+ sessionId, cipherSuites, isDTLS);
// add signature_algorithm extension
- if (maxProtocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (maxProtocolVersion.useTLS12PlusSpec()) {
// we will always send the signature_algorithm extension
Collection<SignatureAndHashAlgorithm> localSignAlgs =
getLocalSupportedSignAlgs();
@@ -1389,6 +1426,37 @@
}
}
+ // add max_fragment_length extension
+ if (enableMFLExtension) {
+ if (session != null) {
+ // The same extension should be sent for resumption.
+ requestedMFLength = session.getNegotiatedMaxFragSize();
+ } else if (maximumPacketSize != 0) {
+ // Maybe we can calculate the fragment size more accurate
+ // by condering the enabled cipher suites in the future.
+ requestedMFLength = maximumPacketSize;
+ if (isDTLS) {
+ requestedMFLength -= DTLSRecord.maxPlaintextPlusSize;
+ } else {
+ requestedMFLength -= SSLRecord.maxPlaintextPlusSize;
+ }
+ } else {
+ // Need no max_fragment_length extension.
+ requestedMFLength = -1;
+ }
+
+ if ((requestedMFLength > 0) &&
+ MaxFragmentLengthExtension.needFragLenNego(requestedMFLength)) {
+
+ requestedMFLength =
+ MaxFragmentLengthExtension.getValidMaxFragLen(
+ requestedMFLength);
+ clientHelloMessage.addMFLExtension(requestedMFLength);
+ } else {
+ requestedMFLength = -1;
+ }
+ }
+
// reset the client random cookie
clnt_random = clientHelloMessage.clnt_random;
@@ -1403,6 +1471,11 @@
clientHelloMessage.addRenegotiationInfoExtension(clientVerifyData);
}
+ if (isDTLS) {
+ // Cookie exchange need to reserve the initial ClientHello message.
+ initialClientHelloMsg = clientHelloMessage;
+ }
+
return clientHelloMessage;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ClientKeyExchange.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.Principal;
+
+/**
+ * Models a non-certificate based ClientKeyExchange
+ */
+public abstract class ClientKeyExchange extends HandshakeMessage {
+
+ public ClientKeyExchange() {
+ }
+
+ @Override
+ int messageType() {
+ return ht_client_key_exchange;
+ }
+
+ @Override
+ abstract public int messageLength();
+
+ @Override
+ abstract public void send(HandshakeOutStream s) throws IOException;
+
+ @Override
+ abstract public void print(PrintStream s) throws IOException;
+
+ abstract public SecretKey clientKeyExchange();
+
+ abstract public Principal getPeerPrincipal();
+
+ abstract public Principal getLocalPrincipal();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ClientKeyExchangeService.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import sun.security.action.GetPropertyAction;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+import java.util.*;
+
+/**
+ * Models a service that provides support for a particular client key exchange
+ * mode. Currently used to implement Kerberos-related cipher suites.
+ *
+ * @since 1.9
+ */
+public interface ClientKeyExchangeService {
+
+ static class Loader {
+ private static final Map<String,ClientKeyExchangeService>
+ providers = new HashMap<>();
+
+ static {
+ final String key = "java.home";
+ String path = AccessController.doPrivileged(
+ new GetPropertyAction(key), null,
+ new PropertyPermission(key, "read"));
+ ServiceLoader<ClientKeyExchangeService> sc =
+ AccessController.doPrivileged(
+ (PrivilegedAction<ServiceLoader<ClientKeyExchangeService>>)
+ () -> ServiceLoader.loadInstalled(ClientKeyExchangeService.class),
+ null,
+ new FilePermission(new File(path, "-").toString(), "read"));
+ Iterator<ClientKeyExchangeService> iter = sc.iterator();
+ while (iter.hasNext()) {
+ ClientKeyExchangeService cs = iter.next();
+ for (String ex: cs.supported()) {
+ providers.put(ex, cs);
+ }
+ }
+ }
+
+ }
+
+ public static ClientKeyExchangeService find(String ex) {
+ return Loader.providers.get(ex);
+ }
+
+
+ /**
+ * Returns the supported key exchange modes by this provider.
+ * @return the supported key exchange modes
+ */
+ String[] supported();
+
+ /**
+ * Returns a generalized credential object on the server side. The server
+ * side can use the info to determine if a cipher suite can be enabled.
+ * @param acc the AccessControlContext of the SSL session
+ * @return the credential object
+ */
+ Object getServiceCreds(AccessControlContext acc);
+
+ /**
+ * Returns the host name for a service principal. The info can be used in
+ * SNI or host name verifier.
+ * @param principal the principal of a service
+ * @return the string formed host name
+ */
+ String getServiceHostName(Principal principal);
+
+ /**
+ * Returns whether the specified principal is related to the current
+ * SSLSession. The info can be used to verify a SSL resume.
+ * @param isClient if true called from client side, otherwise from server
+ * @param acc the AccessControlContext of the SSL session
+ * @param p the specified principal
+ * @return true if related
+ */
+ boolean isRelated(boolean isClient, AccessControlContext acc, Principal p);
+
+ /**
+ * Creates the ClientKeyExchange object on the client side.
+ * @param serverName the intented peer name
+ * @param acc the AccessControlContext of the SSL session
+ * @param protocolVersion the TLS protocol version
+ * @param rand the SecureRandom that will used to generate the premaster
+ * @return the new Exchanger object
+ * @throws IOException if there is an error
+ */
+ ClientKeyExchange createClientExchange(String serverName, AccessControlContext acc,
+ ProtocolVersion protocolVersion, SecureRandom rand) throws IOException;
+
+ /**
+ * Create the ClientKeyExchange on the server side.
+ * @param protocolVersion the protocol version
+ * @param clientVersion the input protocol version
+ * @param rand a SecureRandom object used to generate premaster
+ * (if the server has to create one)
+ * @param encodedTicket the ticket from client
+ * @param encrypted the encrypted premaster secret from client
+ * @param acc the AccessControlContext of the SSL session
+ * @param ServiceCreds the service side credentials object as retrived from
+ * {@link #getServiceCreds}
+ * @return the new Exchanger object
+ * @throws IOException if there is an error
+ */
+ ClientKeyExchange createServerExchange(
+ ProtocolVersion protocolVersion, ProtocolVersion clientVersion,
+ SecureRandom rand, byte[] encodedTicket, byte[] encrypted,
+ AccessControlContext acc, Object ServiceCreds) throws IOException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,1265 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+import javax.crypto.BadPaddingException;
+
+import javax.net.ssl.*;
+
+import sun.misc.HexDumpEncoder;
+import static sun.security.ssl.HandshakeMessage.*;
+
+/**
+ * DTLS {@code InputRecord} implementation for {@code SSLEngine}.
+ */
+final class DTLSInputRecord extends InputRecord implements DTLSRecord {
+
+ private DTLSReassembler reassembler = null;
+
+ // Cache the session identifier for the detection of session-resuming
+ // handshake.
+ byte[] prevSessionID = new byte[0];
+
+ int readEpoch;
+
+ int prevReadEpoch;
+ Authenticator prevReadAuthenticator;
+ CipherBox prevReadCipher;
+
+ DTLSInputRecord() {
+ this.readEpoch = 0;
+ this.readAuthenticator = new MAC(true);
+
+ this.prevReadEpoch = 0;
+ this.prevReadCipher = CipherBox.NULL;
+ this.prevReadAuthenticator = new MAC(true);
+ }
+
+ @Override
+ void changeReadCiphers(Authenticator readAuthenticator,
+ CipherBox readCipher) {
+
+ prevReadCipher.dispose();
+
+ this.prevReadAuthenticator = this.readAuthenticator;
+ this.prevReadCipher = this.readCipher;
+ this.prevReadEpoch = this.readEpoch;
+
+ this.readAuthenticator = readAuthenticator;
+ this.readCipher = readCipher;
+ this.readEpoch++;
+ }
+
+ @Override
+ synchronized public void close() throws IOException {
+ if (!isClosed) {
+ prevReadCipher.dispose();
+ super.close();
+ }
+ }
+
+ @Override
+ boolean isEmpty() {
+ return ((reassembler == null) || reassembler.isEmpty());
+ }
+
+ @Override
+ int estimateFragmentSize(int packetSize) {
+ int macLen = 0;
+ if (readAuthenticator instanceof MAC) {
+ macLen = ((MAC)readAuthenticator).MAClen();
+ }
+
+ if (packetSize > 0) {
+ return readCipher.estimateFragmentSize(
+ packetSize, macLen, headerSize);
+ } else {
+ return Record.maxDataSize;
+ }
+ }
+
+ @Override
+ void expectingFinishFlight() {
+ if (reassembler != null) {
+ reassembler.expectingFinishFlight();
+ }
+ }
+
+ @Override
+ Plaintext acquirePlaintext() {
+ if (reassembler != null) {
+ Plaintext plaintext = reassembler.acquirePlaintext();
+ if (reassembler.finished()) {
+ // discard all buffered unused message.
+ reassembler = null;
+ }
+
+ return plaintext;
+ }
+
+ return null;
+ }
+
+ @Override
+ Plaintext decode(ByteBuffer packet) {
+
+ if (isClosed) {
+ return null;
+ }
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw read]: length = " + packet.remaining(), packet);
+ }
+
+ // The caller should have validated the record.
+ int srcPos = packet.position();
+ int srcLim = packet.limit();
+
+ byte contentType = packet.get(); // pos: 0
+ byte majorVersion = packet.get(); // pos: 1
+ byte minorVersion = packet.get(); // pos: 2
+ byte[] recordEnS = new byte[8]; // epoch + seqence
+ packet.get(recordEnS);
+ int recordEpoch = ((recordEnS[0] & 0xFF) << 8) |
+ (recordEnS[1] & 0xFF); // pos: 3, 4
+ long recordSeq = Authenticator.toLong(recordEnS);
+ int contentLen = ((packet.get() & 0xFF) << 8) |
+ (packet.get() & 0xFF); // pos: 11, 12
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", READ: " +
+ ProtocolVersion.valueOf(majorVersion, minorVersion) +
+ " " + Record.contentName(contentType) + ", length = " +
+ contentLen);
+ }
+
+ int recLim = srcPos + DTLSRecord.headerSize + contentLen;
+ if (this.readEpoch > recordEpoch) {
+ // Discard old records delivered before this epoch.
+
+ // Reset the position of the packet buffer.
+ packet.position(recLim);
+ return null;
+ }
+
+ if (this.readEpoch < recordEpoch) {
+ if (contentType != Record.ct_handshake) {
+ // just discard it if not a handshake message
+ packet.position(recLim);
+ return null;
+ }
+
+ // Not ready to decrypt this record, may be encrypted Finished
+ // message, need to buffer it.
+ if (reassembler == null) {
+ reassembler = new DTLSReassembler();
+ }
+
+ byte[] fragment = new byte[contentLen];
+ packet.get(fragment); // copy the fragment
+ RecordFragment buffered = new RecordFragment(fragment, contentType,
+ majorVersion, minorVersion,
+ recordEnS, recordEpoch, recordSeq, true);
+
+ reassembler.queueUpFragment(buffered);
+
+ // consume the full record in the packet buffer.
+ packet.position(recLim);
+
+ Plaintext plaintext = reassembler.acquirePlaintext();
+ if (reassembler.finished()) {
+ // discard all buffered unused message.
+ reassembler = null;
+ }
+
+ return plaintext;
+ }
+
+ if (this.readEpoch == recordEpoch) {
+ // decrypt the fragment
+ packet.limit(recLim);
+ packet.position(srcPos + DTLSRecord.headerSize);
+
+ ByteBuffer plaintextFragment;
+ try {
+ plaintextFragment = decrypt(readAuthenticator,
+ readCipher, contentType, packet, recordEnS);
+ } catch (BadPaddingException bpe) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ " discard invalid record: " + bpe);
+ }
+
+ // invalid, discard this record [section 4.1.2.7, RFC 6347]
+ return null;
+ } finally {
+ // comsume a complete record
+ packet.limit(srcLim);
+ packet.position(recLim);
+ }
+
+ if (contentType != Record.ct_change_cipher_spec &&
+ contentType != Record.ct_handshake) { // app data or alert
+ // no retransmission
+ return new Plaintext(contentType, majorVersion, minorVersion,
+ recordEpoch, recordSeq, plaintextFragment);
+ }
+
+ if (contentType == Record.ct_change_cipher_spec) {
+ if (reassembler == null) {
+ // handshake has not started, should be an
+ // old handshake message, discard it.
+ return null;
+ }
+
+ reassembler.queueUpFragment(
+ new RecordFragment(plaintextFragment, contentType,
+ majorVersion, minorVersion,
+ recordEnS, recordEpoch, recordSeq, false));
+ } else { // handshake record
+ // One record may contain 1+ more handshake messages.
+ while (plaintextFragment.remaining() > 0) {
+
+ HandshakeFragment hsFrag = parseHandshakeMessage(
+ contentType, majorVersion, minorVersion,
+ recordEnS, recordEpoch, recordSeq, plaintextFragment);
+
+ if (hsFrag == null) {
+ // invalid, discard this record
+ return null;
+ }
+
+ if ((reassembler == null) &&
+ isKickstart(hsFrag.handshakeType)) {
+ reassembler = new DTLSReassembler();
+ }
+
+ if (reassembler != null) {
+ reassembler.queueUpHandshake(hsFrag);
+ } // else, just ignore the message.
+ }
+ }
+
+ // Completed the read of the full record. Acquire the reassembled
+ // messages.
+ if (reassembler != null) {
+ Plaintext plaintext = reassembler.acquirePlaintext();
+ if (reassembler.finished()) {
+ // discard all buffered unused message.
+ reassembler = null;
+ }
+
+ return plaintext;
+ }
+ }
+
+ return null; // make the complier happy
+ }
+
+ @Override
+ int bytesInCompletePacket(ByteBuffer packet) throws SSLException {
+
+ // DTLS length field is in bytes 11/12
+ if (packet.remaining() < headerSize) {
+ return -1;
+ }
+
+ // Last sanity check that it's not a wild record
+ int pos = packet.position();
+
+ // Check the content type of the record.
+ byte contentType = packet.get(pos);
+ if (!Record.isValidContentType(contentType)) {
+ throw new SSLException(
+ "Unrecognized SSL message, plaintext connection?");
+ }
+
+ // Check the protocol version of the record.
+ ProtocolVersion recordVersion =
+ ProtocolVersion.valueOf(packet.get(pos + 1), packet.get(pos + 2));
+ checkRecordVersion(recordVersion, false);
+
+ // Get the fragment length of the record.
+ int fragLen = ((packet.get(pos + 11) & 0xFF) << 8) +
+ (packet.get(pos + 12) & 0xFF) + headerSize;
+ if (fragLen > Record.maxFragmentSize) {
+ throw new SSLException(
+ "Record overflow, fragment length (" + fragLen +
+ ") MUST not exceed " + Record.maxFragmentSize);
+ }
+
+ return fragLen;
+ }
+
+ @Override
+ void checkRecordVersion(ProtocolVersion recordVersion,
+ boolean allowSSL20Hello) throws SSLException {
+
+ if (!recordVersion.maybeDTLSProtocol()) {
+ throw new SSLException(
+ "Unrecognized record version " + recordVersion +
+ " , plaintext connection?");
+ }
+ }
+
+ private static boolean isKickstart(byte handshakeType) {
+ return (handshakeType == HandshakeMessage.ht_client_hello) ||
+ (handshakeType == HandshakeMessage.ht_hello_request) ||
+ (handshakeType == HandshakeMessage.ht_hello_verify_request);
+ }
+
+ private static HandshakeFragment parseHandshakeMessage(
+ byte contentType, byte majorVersion, byte minorVersion,
+ byte[] recordEnS, int recordEpoch, long recordSeq,
+ ByteBuffer plaintextFragment) {
+
+ int remaining = plaintextFragment.remaining();
+ if (remaining < handshakeHeaderSize) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ " discard invalid record: " +
+ "too small record to hold a handshake fragment");
+ }
+
+ // invalid, discard this record [section 4.1.2.7, RFC 6347]
+ return null;
+ }
+
+ byte handshakeType = plaintextFragment.get(); // pos: 0
+ int messageLength =
+ ((plaintextFragment.get() & 0xFF) << 16) |
+ ((plaintextFragment.get() & 0xFF) << 8) |
+ (plaintextFragment.get() & 0xFF); // pos: 1-3
+ int messageSeq =
+ ((plaintextFragment.get() & 0xFF) << 8) |
+ (plaintextFragment.get() & 0xFF); // pos: 4/5
+ int fragmentOffset =
+ ((plaintextFragment.get() & 0xFF) << 16) |
+ ((plaintextFragment.get() & 0xFF) << 8) |
+ (plaintextFragment.get() & 0xFF); // pos: 6-8
+ int fragmentLength =
+ ((plaintextFragment.get() & 0xFF) << 16) |
+ ((plaintextFragment.get() & 0xFF) << 8) |
+ (plaintextFragment.get() & 0xFF); // pos: 9-11
+ if ((remaining - handshakeHeaderSize) < fragmentLength) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ " discard invalid record: " +
+ "not a complete handshake fragment in the record");
+ }
+
+ // invalid, discard this record [section 4.1.2.7, RFC 6347]
+ return null;
+ }
+
+ byte[] fragment = new byte[fragmentLength];
+ plaintextFragment.get(fragment);
+
+ return new HandshakeFragment(fragment, contentType,
+ majorVersion, minorVersion,
+ recordEnS, recordEpoch, recordSeq,
+ handshakeType, messageLength,
+ messageSeq, fragmentOffset, fragmentLength);
+ }
+
+ // buffered record fragment
+ private static class RecordFragment implements Comparable<RecordFragment> {
+ boolean isCiphertext;
+
+ byte contentType;
+ byte majorVersion;
+ byte minorVersion;
+ int recordEpoch;
+ long recordSeq;
+ byte[] recordEnS;
+ byte[] fragment;
+
+ RecordFragment(ByteBuffer fragBuf, byte contentType,
+ byte majorVersion, byte minorVersion, byte[] recordEnS,
+ int recordEpoch, long recordSeq, boolean isCiphertext) {
+ this((byte[])null, contentType, majorVersion, minorVersion,
+ recordEnS, recordEpoch, recordSeq, isCiphertext);
+
+ this.fragment = new byte[fragBuf.remaining()];
+ fragBuf.get(this.fragment);
+ }
+
+ RecordFragment(byte[] fragment, byte contentType,
+ byte majorVersion, byte minorVersion, byte[] recordEnS,
+ int recordEpoch, long recordSeq, boolean isCiphertext) {
+ this.isCiphertext = isCiphertext;
+
+ this.contentType = contentType;
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ this.recordEpoch = recordEpoch;
+ this.recordSeq = recordSeq;
+ this.recordEnS = recordEnS;
+ this.fragment = fragment; // The caller should have cloned
+ // the buffer if necessary.
+ }
+
+ @Override
+ public int compareTo(RecordFragment o) {
+ return Long.compareUnsigned(this.recordSeq, o.recordSeq);
+ }
+ }
+
+ // buffered handshake message
+ private static final class HandshakeFragment extends RecordFragment {
+
+ byte handshakeType; // handshake msg_type
+ int messageSeq; // message_seq
+ int messageLength; // Handshake body length
+ int fragmentOffset; // fragment_offset
+ int fragmentLength; // fragment_length
+
+ HandshakeFragment(byte[] fragment, byte contentType,
+ byte majorVersion, byte minorVersion, byte[] recordEnS,
+ int recordEpoch, long recordSeq,
+ byte handshakeType, int messageLength,
+ int messageSeq, int fragmentOffset, int fragmentLength) {
+
+ super(fragment, contentType, majorVersion, minorVersion,
+ recordEnS, recordEpoch , recordSeq, false);
+
+ this.handshakeType = handshakeType;
+ this.messageSeq = messageSeq;
+ this.messageLength = messageLength;
+ this.fragmentOffset = fragmentOffset;
+ this.fragmentLength = fragmentLength;
+ }
+
+ @Override
+ public int compareTo(RecordFragment o) {
+ if (o instanceof HandshakeFragment) {
+ HandshakeFragment other = (HandshakeFragment)o;
+ if (this.messageSeq != other.messageSeq) {
+ // keep the insertion order for the same message
+ return this.messageSeq - other.messageSeq;
+ }
+ }
+
+ return Long.compareUnsigned(this.recordSeq, o.recordSeq);
+ }
+ }
+
+ private static final class HoleDescriptor {
+ int offset; // fragment_offset
+ int limit; // fragment_offset + fragment_length
+
+ HoleDescriptor(int offset, int limit) {
+ this.offset = offset;
+ this.limit = limit;
+ }
+ }
+
+ final class DTLSReassembler {
+ TreeSet<RecordFragment> bufferedFragments = new TreeSet<>();
+
+ HashMap<Byte, List<HoleDescriptor>> holesMap = new HashMap<>(5);
+
+ // Epoch, sequence number and handshake message sequence of the
+ // beginning message of a flight.
+ byte flightType = (byte)0xFF;
+
+ int flightTopEpoch = 0;
+ long flightTopRecordSeq = -1;
+ int flightTopMessageSeq = 0;
+
+ // Epoch, sequence number and handshake message sequence of the
+ // next message acquisition of a flight.
+ int nextRecordEpoch = 0; // next record epoch
+ long nextRecordSeq = 0; // next record sequence number
+ int nextMessageSeq = 0; // next handshake message number
+
+ // Expect ChangeCipherSpec and Finished messages for the final flight.
+ boolean expectCCSFlight = false;
+
+ // Ready to process this flight if received all messages of the flight.
+ boolean flightIsReady = false;
+ boolean needToCheckFlight = false;
+
+ // Is it a session-resuming abbreviated handshake.?
+ boolean isAbbreviatedHandshake = false;
+
+ // The handshke fragment with the biggest record sequence number
+ // in a flight, not counting the Finished message.
+ HandshakeFragment lastHandshakeFragment = null;
+
+ // Is handshake (intput) finished?
+ boolean handshakeFinished = false;
+
+ DTLSReassembler() {
+ // blank
+ }
+
+ boolean finished() {
+ return handshakeFinished;
+ }
+
+ void expectingFinishFlight() {
+ expectCCSFlight = true;
+ }
+
+ void queueUpHandshake(HandshakeFragment hsf) {
+
+ if ((nextRecordEpoch > hsf.recordEpoch) ||
+ (nextRecordSeq > hsf.recordSeq) ||
+ (nextMessageSeq > hsf.messageSeq)) {
+ // too old, discard this record
+ return;
+ }
+
+ // Is it the first message of next flight?
+ if ((flightTopMessageSeq == hsf.messageSeq) &&
+ (hsf.fragmentOffset == 0) && (flightTopRecordSeq == -1)) {
+
+ flightType = hsf.handshakeType;
+ flightTopEpoch = hsf.recordEpoch;
+ flightTopRecordSeq = hsf.recordSeq;
+
+ if (hsf.handshakeType == HandshakeMessage.ht_server_hello) {
+ // Is it a session-resuming handshake?
+ try {
+ isAbbreviatedHandshake =
+ isSessionResuming(hsf.fragment, prevSessionID);
+ } catch (SSLException ssle) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ " discard invalid record: " + ssle);
+ }
+
+ // invalid, discard it [section 4.1.2.7, RFC 6347]
+ return;
+ }
+
+ if (!isAbbreviatedHandshake) {
+ prevSessionID = getSessionID(hsf.fragment);
+ }
+ }
+ }
+
+ boolean fragmented = false;
+ if ((hsf.fragmentOffset) != 0 ||
+ (hsf.fragmentLength != hsf.messageLength)) {
+
+ fragmented = true;
+ }
+
+ List<HoleDescriptor> holes = holesMap.get(hsf.handshakeType);
+ if (holes == null) {
+ if (!fragmented) {
+ holes = Collections.emptyList();
+ } else {
+ holes = new LinkedList<HoleDescriptor>();
+ holes.add(new HoleDescriptor(0, hsf.messageLength));
+ }
+ holesMap.put(hsf.handshakeType, holes);
+ } else if (holes.isEmpty()) {
+ // Have got the full handshake message. This record may be
+ // a handshake message retransmission. Discard this record.
+ //
+ // It's OK to discard retransmission as the handshake hash
+ // is computed as if each handshake message had been sent
+ // as a single fragment.
+ //
+ // Note that ClientHello messages are delivered twice in
+ // DTLS handshaking.
+ if ((hsf.handshakeType != HandshakeMessage.ht_client_hello &&
+ hsf.handshakeType != ht_hello_verify_request) ||
+ (nextMessageSeq != hsf.messageSeq)) {
+ return;
+ }
+
+ if (fragmented) {
+ holes = new LinkedList<HoleDescriptor>();
+ holes.add(new HoleDescriptor(0, hsf.messageLength));
+ }
+ holesMap.put(hsf.handshakeType, holes);
+ }
+
+ if (fragmented) {
+ int fragmentLimit = hsf.fragmentOffset + hsf.fragmentLength;
+ for (int i = 0; i < holes.size(); i++) {
+
+ HoleDescriptor hole = holes.get(i);
+ if ((hole.limit <= hsf.fragmentOffset) ||
+ (hole.offset >= fragmentLimit)) {
+ // Also discard overlapping handshake retransmissions.
+ continue;
+ }
+
+ // The ranges SHOULD NOT overlap.
+ if (((hole.offset > hsf.fragmentOffset) &&
+ (hole.offset < fragmentLimit)) ||
+ ((hole.limit > hsf.fragmentOffset) &&
+ (hole.limit < fragmentLimit))) {
+
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ " discard invalid record: " +
+ "handshake fragment ranges are overlapping");
+ }
+
+ // invalid, discard it [section 4.1.2.7, RFC 6347]
+ return;
+ }
+
+ // This record interacts with this hole, fill the hole.
+ holes.remove(i);
+ // i--;
+
+ if (hsf.fragmentOffset > hole.offset) {
+ holes.add(new HoleDescriptor(
+ hole.offset, hsf.fragmentOffset));
+ // i++;
+ }
+
+ if (fragmentLimit < hole.limit) {
+ holes.add(new HoleDescriptor(
+ fragmentLimit, hole.limit));
+ // i++;
+ }
+
+ // As no ranges overlap, no interact with other holes.
+ break;
+ }
+ }
+
+ // append this fragment
+ bufferedFragments.add(hsf);
+
+ if ((lastHandshakeFragment == null) ||
+ (lastHandshakeFragment.compareTo(hsf) < 0)) {
+
+ lastHandshakeFragment = hsf;
+ }
+
+ if (flightIsReady) {
+ flightIsReady = false;
+ }
+ needToCheckFlight = true;
+ }
+
+ // queue up change_cipher_spec or encrypted message
+ void queueUpFragment(RecordFragment rf) {
+ if ((nextRecordEpoch > rf.recordEpoch) ||
+ (nextRecordSeq > rf.recordSeq)) {
+ // too old, discard this record
+ return;
+ }
+
+ // Is it the first message of next flight?
+ if (expectCCSFlight &&
+ (rf.contentType == Record.ct_change_cipher_spec)) {
+
+ flightType = (byte)0xFE;
+ flightTopEpoch = rf.recordEpoch;
+ flightTopRecordSeq = rf.recordSeq;
+ }
+
+ // append this fragment
+ bufferedFragments.add(rf);
+
+ if (flightIsReady) {
+ flightIsReady = false;
+ }
+ needToCheckFlight = true;
+ }
+
+ boolean isEmpty() {
+ return (bufferedFragments.isEmpty() ||
+ (!flightIsReady && !needToCheckFlight) ||
+ (needToCheckFlight && !flightIsReady()));
+ }
+
+ Plaintext acquirePlaintext() {
+ if (bufferedFragments.isEmpty()) {
+ // reset the flight
+ if (flightIsReady) {
+ flightIsReady = false;
+ needToCheckFlight = false;
+ }
+
+ return null;
+ }
+
+ if (!flightIsReady && needToCheckFlight) {
+ // check the fligth status
+ flightIsReady = flightIsReady();
+
+ // set for next flight
+ if (flightIsReady) {
+ flightTopMessageSeq = lastHandshakeFragment.messageSeq + 1;
+ flightTopRecordSeq = -1;
+ }
+
+ needToCheckFlight = false;
+ }
+
+ if (!flightIsReady) {
+ return null;
+ }
+
+ RecordFragment rFrag = bufferedFragments.first();
+ if (!rFrag.isCiphertext) {
+ // handshake message, or ChangeCipherSpec message
+ return acquireHandshakeMessage();
+ } else {
+ // a Finished message or other ciphertexts
+ return acquireCachedMessage();
+ }
+ }
+
+ private Plaintext acquireCachedMessage() {
+
+ RecordFragment rFrag = bufferedFragments.first();
+ if (readEpoch != rFrag.recordEpoch) {
+ if (readEpoch > rFrag.recordEpoch) {
+ // discard old records
+ bufferedFragments.remove(rFrag); // popup the fragment
+ }
+
+ // reset the flight
+ if (flightIsReady) {
+ flightIsReady = false;
+ }
+ return null;
+ }
+
+ bufferedFragments.remove(rFrag); // popup the fragment
+
+ ByteBuffer fragment = ByteBuffer.wrap(rFrag.fragment);
+ ByteBuffer plaintextFragment = null;
+ try {
+ plaintextFragment = decrypt(readAuthenticator, readCipher,
+ rFrag.contentType, fragment, rFrag.recordEnS);
+ } catch (BadPaddingException bpe) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ " discard invalid record: " + bpe);
+ }
+
+ // invalid, discard this record [section 4.1.2.7, RFC 6347]
+ return null;
+ }
+
+ // The ciphtext handshake message can only be Finished (the
+ // end of this flight), ClinetHello or HelloRequest (the
+ // beginning of the next flight) message. Need not to check
+ // any ChangeCipherSpec message.
+ if (rFrag.contentType == Record.ct_handshake) {
+ HandshakeFragment finFrag = null;
+ while (plaintextFragment.remaining() > 0) {
+ HandshakeFragment hsFrag = parseHandshakeMessage(
+ rFrag.contentType,
+ rFrag.majorVersion, rFrag.minorVersion,
+ rFrag.recordEnS, rFrag.recordEpoch, rFrag.recordSeq,
+ plaintextFragment);
+
+ if (hsFrag == null) {
+ // invalid, discard this record
+ return null;
+ }
+
+ if (hsFrag.handshakeType == HandshakeMessage.ht_finished) {
+ finFrag = hsFrag;
+
+ // reset for the next flight
+ this.flightType = (byte)0xFF;
+ this.flightTopEpoch = rFrag.recordEpoch;
+ this.flightTopMessageSeq = hsFrag.messageSeq + 1;
+ this.flightTopRecordSeq = -1;
+ } else {
+ // reset the flight
+ if (flightIsReady) {
+ flightIsReady = false;
+ }
+ queueUpHandshake(hsFrag);
+ }
+ }
+
+ this.nextRecordSeq = rFrag.recordSeq + 1;
+ this.nextMessageSeq = 0;
+
+ if (finFrag != null) {
+ this.nextRecordEpoch = finFrag.recordEpoch;
+ this.nextRecordSeq = finFrag.recordSeq + 1;
+ this.nextMessageSeq = finFrag.messageSeq + 1;
+
+ // Finished message does not fragment.
+ byte[] recordFrag = new byte[finFrag.messageLength + 4];
+ Plaintext plaintext = new Plaintext(finFrag.contentType,
+ finFrag.majorVersion, finFrag.minorVersion,
+ finFrag.recordEpoch, finFrag.recordSeq,
+ ByteBuffer.wrap(recordFrag));
+
+ // fill the handshake fragment of the record
+ recordFrag[0] = finFrag.handshakeType;
+ recordFrag[1] =
+ (byte)((finFrag.messageLength >>> 16) & 0xFF);
+ recordFrag[2] =
+ (byte)((finFrag.messageLength >>> 8) & 0xFF);
+ recordFrag[3] = (byte)(finFrag.messageLength & 0xFF);
+
+ System.arraycopy(finFrag.fragment, 0,
+ recordFrag, 4, finFrag.fragmentLength);
+
+ // handshake hashing
+ handshakeHashing(finFrag, plaintext);
+
+ // input handshake finished
+ handshakeFinished = true;
+
+ return plaintext;
+ } else {
+ return acquirePlaintext();
+ }
+ } else {
+ return new Plaintext(rFrag.contentType,
+ rFrag.majorVersion, rFrag.minorVersion,
+ rFrag.recordEpoch, rFrag.recordSeq,
+ plaintextFragment);
+ }
+ }
+
+ private Plaintext acquireHandshakeMessage() {
+
+ RecordFragment rFrag = bufferedFragments.first();
+ if (rFrag.contentType == Record.ct_change_cipher_spec) {
+ this.nextRecordEpoch = rFrag.recordEpoch + 1;
+ this.nextRecordSeq = 0;
+ // no change on next handshake message sequence number
+
+ bufferedFragments.remove(rFrag); // popup the fragment
+
+ // Reload if this message has been reserved for handshake hash.
+ handshakeHash.reload();
+
+ return new Plaintext(rFrag.contentType,
+ rFrag.majorVersion, rFrag.minorVersion,
+ rFrag.recordEpoch, rFrag.recordSeq,
+ ByteBuffer.wrap(rFrag.fragment));
+ } else { // rFrag.contentType == Record.ct_handshake
+ HandshakeFragment hsFrag = (HandshakeFragment)rFrag;
+ if ((hsFrag.messageLength == hsFrag.fragmentLength) &&
+ (hsFrag.fragmentOffset == 0)) { // no fragmentation
+
+ bufferedFragments.remove(rFrag); // popup the fragment
+
+ // this.nextRecordEpoch = hsFrag.recordEpoch;
+ this.nextRecordSeq = hsFrag.recordSeq + 1;
+ this.nextMessageSeq = hsFrag.messageSeq + 1;
+
+ // Note: may try to avoid byte array copy in the future.
+ byte[] recordFrag = new byte[hsFrag.messageLength + 4];
+ Plaintext plaintext = new Plaintext(hsFrag.contentType,
+ hsFrag.majorVersion, hsFrag.minorVersion,
+ hsFrag.recordEpoch, hsFrag.recordSeq,
+ ByteBuffer.wrap(recordFrag));
+
+ // fill the handshake fragment of the record
+ recordFrag[0] = hsFrag.handshakeType;
+ recordFrag[1] =
+ (byte)((hsFrag.messageLength >>> 16) & 0xFF);
+ recordFrag[2] =
+ (byte)((hsFrag.messageLength >>> 8) & 0xFF);
+ recordFrag[3] = (byte)(hsFrag.messageLength & 0xFF);
+
+ System.arraycopy(hsFrag.fragment, 0,
+ recordFrag, 4, hsFrag.fragmentLength);
+
+ // handshake hashing
+ handshakeHashing(hsFrag, plaintext);
+
+ return plaintext;
+ } else { // fragmented handshake message
+ // the first record
+ //
+ // Note: may try to avoid byte array copy in the future.
+ byte[] recordFrag = new byte[hsFrag.messageLength + 4];
+ Plaintext plaintext = new Plaintext(hsFrag.contentType,
+ hsFrag.majorVersion, hsFrag.minorVersion,
+ hsFrag.recordEpoch, hsFrag.recordSeq,
+ ByteBuffer.wrap(recordFrag));
+
+ // fill the handshake fragment of the record
+ recordFrag[0] = hsFrag.handshakeType;
+ recordFrag[1] =
+ (byte)((hsFrag.messageLength >>> 16) & 0xFF);
+ recordFrag[2] =
+ (byte)((hsFrag.messageLength >>> 8) & 0xFF);
+ recordFrag[3] = (byte)(hsFrag.messageLength & 0xFF);
+
+ int msgSeq = hsFrag.messageSeq;
+ long maxRecodeSN = hsFrag.recordSeq;
+ HandshakeFragment hmFrag = hsFrag;
+ do {
+ System.arraycopy(hmFrag.fragment, 0,
+ recordFrag, hmFrag.fragmentOffset + 4,
+ hmFrag.fragmentLength);
+ // popup the fragment
+ bufferedFragments.remove(rFrag);
+
+ if (maxRecodeSN < hmFrag.recordSeq) {
+ maxRecodeSN = hmFrag.recordSeq;
+ }
+
+ // Note: may buffer retransmitted fragments in order to
+ // speed up the reassembly in the future.
+
+ // read the next buffered record
+ if (!bufferedFragments.isEmpty()) {
+ rFrag = bufferedFragments.first();
+ if (rFrag.contentType != Record.ct_handshake) {
+ break;
+ } else {
+ hmFrag = (HandshakeFragment)rFrag;
+ }
+ }
+ } while (!bufferedFragments.isEmpty() &&
+ (msgSeq == hmFrag.messageSeq));
+
+ // handshake hashing
+ handshakeHashing(hsFrag, plaintext);
+
+ this.nextRecordSeq = maxRecodeSN + 1;
+ this.nextMessageSeq = msgSeq + 1;
+
+ return plaintext;
+ }
+ }
+ }
+
+ boolean flightIsReady() {
+
+ //
+ // the ChangeCipherSpec/Finished flight
+ //
+ if (expectCCSFlight) {
+ // Have the ChangeCipherSpec/Finished messages been received?
+ return hasFinisedMessage(bufferedFragments);
+ }
+
+ if (flightType == (byte)0xFF) {
+ return false;
+ }
+
+ if ((flightType == HandshakeMessage.ht_client_hello) ||
+ (flightType == HandshakeMessage.ht_hello_request) ||
+ (flightType == HandshakeMessage.ht_hello_verify_request)) {
+
+ // single handshake message flight
+ return hasCompleted(holesMap.get(flightType));
+ }
+
+ //
+ // the ServerHello flight
+ //
+ if (flightType == HandshakeMessage.ht_server_hello) {
+ // Firstly, check the first flight handshake message.
+ if (!hasCompleted(holesMap.get(flightType))) {
+ return false;
+ }
+
+ //
+ // an abbreviated handshake
+ //
+ if (isAbbreviatedHandshake) {
+ // Ready to use the flight if received the
+ // ChangeCipherSpec and Finished messages.
+ return hasFinisedMessage(bufferedFragments);
+ }
+
+ //
+ // a full handshake
+ //
+ if (lastHandshakeFragment.handshakeType !=
+ HandshakeMessage.ht_server_hello_done) {
+ // Not yet got the final message of the flight.
+ return false;
+ }
+
+ // Have all handshake message been received?
+ return hasCompleted(bufferedFragments,
+ flightTopMessageSeq, lastHandshakeFragment.messageSeq);
+ }
+
+ //
+ // the ClientKeyExchange flight
+ //
+ // Note: need to consider more messages in this flight if
+ // ht_supplemental_data and ht_certificate_url are
+ // suppported in the future.
+ //
+ if ((flightType == HandshakeMessage.ht_certificate) ||
+ (flightType == HandshakeMessage.ht_client_key_exchange)) {
+
+ // Firstly, check the first flight handshake message.
+ if (!hasCompleted(holesMap.get(flightType))) {
+ return false;
+ }
+
+ if (!hasFinisedMessage(bufferedFragments)) {
+ // not yet got the ChangeCipherSpec/Finished messages
+ return false;
+ }
+
+ if (flightType == HandshakeMessage.ht_client_key_exchange) {
+ // single handshake message flight
+ return true;
+ }
+
+ //
+ // flightType == HandshakeMessage.ht_certificate
+ //
+ // We don't support certificates containing fixed
+ // Diffie-Hellman parameters. Therefore, CertificateVerify
+ // message is required if client Certificate message presents.
+ //
+ if (lastHandshakeFragment.handshakeType !=
+ HandshakeMessage.ht_certificate_verify) {
+ // Not yet got the final message of the flight.
+ return false;
+ }
+
+ // Have all handshake message been received?
+ return hasCompleted(bufferedFragments,
+ flightTopMessageSeq, lastHandshakeFragment.messageSeq);
+ }
+
+ //
+ // Otherwise, need to receive more handshake messages.
+ //
+ return false;
+ }
+
+ private boolean isSessionResuming(
+ byte[] fragment, byte[] prevSid) throws SSLException {
+
+ // As the first fragment of ServerHello should be big enough
+ // to hold the session_id field, need not to worry about the
+ // fragmentation here.
+ if ((fragment == null) || (fragment.length < 38)) {
+ // 38: the minimal ServerHello body length
+ throw new SSLException(
+ "Invalid ServerHello message: no sufficient data");
+ }
+
+ int sidLen = fragment[34]; // 34: the length field
+ if (sidLen > 32) { // opaque SessionID<0..32>
+ throw new SSLException(
+ "Invalid ServerHello message: invalid session id");
+ }
+
+ if (fragment.length < 38 + sidLen) {
+ throw new SSLException(
+ "Invalid ServerHello message: no sufficient data");
+ }
+
+ if (sidLen != 0 && (prevSid.length == sidLen)) {
+ // may be a session-resuming handshake
+ for (int i = 0; i < sidLen; i++) {
+ if (prevSid[i] != fragment[35 + i]) {
+ // 35: the session identifier
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private byte[] getSessionID(byte[] fragment) {
+ // The validity has been checked in the call to isSessionResuming().
+ int sidLen = fragment[34]; // 34: the sessionID length field
+
+ byte[] temporary = new byte[sidLen];
+ System.arraycopy(fragment, 35, temporary, 0, sidLen);
+
+ return temporary;
+ }
+
+ // Looking for the ChangeCipherSpec and Finished messages.
+ //
+ // As the cached Finished message should be a ciphertext, we don't
+ // exactly know a ciphertext is a Finished message or not. According
+ // to the spec of TLS/DTLS handshaking, a Finished message is always
+ // sent immediately after a ChangeCipherSpec message. The first
+ // ciphertext handshake message should be the expected Finished message.
+ private boolean hasFinisedMessage(
+ Set<RecordFragment> fragments) {
+
+ boolean hasCCS = false;
+ boolean hasFin = false;
+ for (RecordFragment fragment : fragments) {
+ if (fragment.contentType == Record.ct_change_cipher_spec) {
+ if (hasFin) {
+ return true;
+ }
+ hasCCS = true;
+ } else if (fragment.contentType == Record.ct_handshake) {
+ // Finished is the first expected message of a new epoch.
+ if (fragment.isCiphertext) {
+ if (hasCCS) {
+ return true;
+ }
+ hasFin = true;
+ }
+ }
+ }
+
+ return hasFin && hasCCS;
+ }
+
+ private boolean hasCompleted(List<HoleDescriptor> holes) {
+ if (holes == null) {
+ // not yet received this kind of handshake message
+ return false;
+ }
+
+ return holes.isEmpty(); // no fragment hole for complete message
+ }
+
+ private boolean hasCompleted(
+ Set<RecordFragment> fragments,
+ int presentMsgSeq, int endMsgSeq) {
+
+ // The caller should have checked the completion of the first
+ // present handshake message. Need not to check it again.
+ for (RecordFragment rFrag : fragments) {
+ if ((rFrag.contentType != Record.ct_handshake) ||
+ rFrag.isCiphertext) {
+ break;
+ }
+
+ HandshakeFragment hsFrag = (HandshakeFragment)rFrag;
+ if (hsFrag.messageSeq == presentMsgSeq) {
+ continue;
+ } else if (hsFrag.messageSeq == (presentMsgSeq + 1)) {
+ // check the completion of the handshake message
+ if (!hasCompleted(holesMap.get(hsFrag.handshakeType))) {
+ return false;
+ }
+
+ presentMsgSeq = hsFrag.messageSeq;
+ } else {
+ // not yet got handshake message next to presentMsgSeq
+ break;
+ }
+ }
+
+ return (presentMsgSeq >= endMsgSeq);
+ // false: if not yet got all messages of the flight.
+ }
+
+ private void handshakeHashing(
+ HandshakeFragment hsFrag, Plaintext plaintext) {
+
+ byte hsType = hsFrag.handshakeType;
+ if ((hsType == HandshakeMessage.ht_hello_request) ||
+ (hsType == HandshakeMessage.ht_hello_verify_request)) {
+
+ // omitted from handshake hash computation
+ return;
+ }
+
+ if ((hsFrag.messageSeq == 0) &&
+ (hsType == HandshakeMessage.ht_client_hello)) {
+
+ // omit initial ClientHello message
+ //
+ // 4: handshake header
+ // 2: ClientHello.client_version
+ // 32: ClientHello.random
+ int sidLen = plaintext.fragment.get(38);
+
+ if (sidLen == 0) { // empty session_id, initial handshake
+ return;
+ }
+ }
+
+ // calculate the DTLS header
+ byte[] temporary = new byte[12]; // 12: handshake header size
+
+ // Handshake.msg_type
+ temporary[0] = hsFrag.handshakeType;
+
+ // Handshake.length
+ temporary[1] = (byte)((hsFrag.messageLength >> 16) & 0xFF);
+ temporary[2] = (byte)((hsFrag.messageLength >> 8) & 0xFF);
+ temporary[3] = (byte)(hsFrag.messageLength & 0xFF);
+
+ // Handshake.message_seq
+ temporary[4] = (byte)((hsFrag.messageSeq >> 8) & 0xFF);
+ temporary[5] = (byte)(hsFrag.messageSeq & 0xFF);
+
+ // Handshake.fragment_offset
+ temporary[6] = 0;
+ temporary[7] = 0;
+ temporary[8] = 0;
+
+ // Handshake.fragment_length
+ temporary[9] = temporary[1];
+ temporary[10] = temporary[2];
+ temporary[11] = temporary[3];
+
+ plaintext.fragment.position(4); // ignore the TLS header
+ if ((hsType != HandshakeMessage.ht_finished) &&
+ (hsType != HandshakeMessage.ht_certificate_verify)) {
+
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.update(temporary, 0, 12);
+ handshakeHash.update(plaintext.fragment);
+ } else {
+ // Reserve until this handshake message has been processed.
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.reserve(temporary, 0, 12);
+ handshakeHash.reserve(plaintext.fragment);
+ }
+ plaintext.fragment.position(0); // restore the position
+ }
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+
+import javax.crypto.BadPaddingException;
+
+import javax.net.ssl.*;
+
+import sun.misc.HexDumpEncoder;
+import static sun.security.ssl.Ciphertext.RecordType;
+
+/**
+ * DTLS {@code OutputRecord} implementation for {@code SSLEngine}.
+ */
+final class DTLSOutputRecord extends OutputRecord implements DTLSRecord {
+
+ private DTLSFragmenter fragmenter = null;
+
+ int writeEpoch;
+
+ int prevWriteEpoch;
+ Authenticator prevWriteAuthenticator;
+ CipherBox prevWriteCipher;
+
+ private LinkedList<RecordMemo> alertMemos = new LinkedList<>();
+
+ DTLSOutputRecord() {
+ this.writeAuthenticator = new MAC(true);
+
+ this.writeEpoch = 0;
+ this.prevWriteEpoch = 0;
+ this.prevWriteCipher = CipherBox.NULL;
+ this.prevWriteAuthenticator = new MAC(true);
+
+ this.packetSize = DTLSRecord.maxRecordSize;
+ this.protocolVersion = ProtocolVersion.DEFAULT_DTLS;
+ }
+
+ @Override
+ void changeWriteCiphers(Authenticator writeAuthenticator,
+ CipherBox writeCipher) throws IOException {
+
+ encodeChangeCipherSpec();
+
+ prevWriteCipher.dispose();
+
+ this.prevWriteAuthenticator = this.writeAuthenticator;
+ this.prevWriteCipher = this.writeCipher;
+ this.prevWriteEpoch = this.writeEpoch;
+
+ this.writeAuthenticator = writeAuthenticator;
+ this.writeCipher = writeCipher;
+ this.writeEpoch++;
+
+ this.isFirstAppOutputRecord = true;
+
+ // set the epoch number
+ this.writeAuthenticator.setEpochNumber(this.writeEpoch);
+ }
+
+ @Override
+ void encodeAlert(byte level, byte description) throws IOException {
+ RecordMemo memo = new RecordMemo();
+
+ memo.contentType = Record.ct_alert;
+ memo.majorVersion = protocolVersion.major;
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeEpoch = writeEpoch;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.fragment = new byte[2];
+ memo.fragment[0] = level;
+ memo.fragment[1] = description;
+
+ alertMemos.add(memo);
+ }
+
+ @Override
+ void encodeChangeCipherSpec() throws IOException {
+ if (fragmenter == null) {
+ fragmenter = new DTLSFragmenter();
+ }
+ fragmenter.queueUpChangeCipherSpec();
+ }
+
+ @Override
+ void encodeHandshake(byte[] source,
+ int offset, int length) throws IOException {
+
+ if (firstMessage) {
+ firstMessage = false;
+ }
+
+ if (fragmenter == null) {
+ fragmenter = new DTLSFragmenter();
+ }
+
+ fragmenter.queueUpHandshake(source, offset, length);
+ }
+
+ @Override
+ Ciphertext encode(ByteBuffer[] sources, int offset, int length,
+ ByteBuffer destination) throws IOException {
+
+ if (writeAuthenticator.seqNumOverflow()) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", sequence number extremely close to overflow " +
+ "(2^64-1 packets). Closing connection.");
+ }
+
+ throw new SSLHandshakeException("sequence number overflow");
+ }
+
+ // not apply to handshake message
+ int macLen = 0;
+ if (writeAuthenticator instanceof MAC) {
+ macLen = ((MAC)writeAuthenticator).MAClen();
+ }
+
+ int fragLen;
+ if (packetSize > 0) {
+ fragLen = Math.min(maxRecordSize, packetSize);
+ fragLen = writeCipher.calculateFragmentSize(
+ fragLen, macLen, headerSize);
+
+ fragLen = Math.min(fragLen, Record.maxDataSize);
+ } else {
+ fragLen = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLen = Math.min(fragLen, fragmentSize);
+ }
+
+ int dstPos = destination.position();
+ int dstLim = destination.limit();
+ int dstContent = dstPos + headerSize +
+ writeCipher.getExplicitNonceSize();
+ destination.position(dstContent);
+
+ int remains = Math.min(fragLen, destination.remaining());
+ fragLen = 0;
+ int srcsLen = offset + length;
+ for (int i = offset; (i < srcsLen) && (remains > 0); i++) {
+ int amount = Math.min(sources[i].remaining(), remains);
+ int srcLimit = sources[i].limit();
+ sources[i].limit(sources[i].position() + amount);
+ destination.put(sources[i]);
+ sources[i].limit(srcLimit); // restore the limit
+ remains -= amount;
+ fragLen += amount;
+ }
+
+ destination.limit(destination.position());
+ destination.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(Record.ct_application_data) +
+ ", length = " + destination.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ long recordSN = encrypt(writeAuthenticator, writeCipher,
+ Record.ct_application_data, destination,
+ dstPos, dstLim, headerSize,
+ protocolVersion, true);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = destination.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ // remain the limit unchanged
+ destination.limit(dstLim);
+
+ return new Ciphertext(RecordType.RECORD_APPLICATION_DATA, recordSN);
+ }
+
+ @Override
+ Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
+ if (alertMemos != null && !alertMemos.isEmpty()) {
+ RecordMemo memo = alertMemos.pop();
+
+ int macLen = 0;
+ if (memo.encodeAuthenticator instanceof MAC) {
+ macLen = ((MAC)memo.encodeAuthenticator).MAClen();
+ }
+
+ int dstPos = destination.position();
+ int dstLim = destination.limit();
+ int dstContent = dstPos + headerSize +
+ writeCipher.getExplicitNonceSize();
+ destination.position(dstContent);
+
+ destination.put(memo.fragment);
+
+ destination.limit(destination.position());
+ destination.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(Record.ct_alert) +
+ ", length = " + destination.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
+ Record.ct_alert, destination, dstPos, dstLim, headerSize,
+ ProtocolVersion.valueOf(memo.majorVersion,
+ memo.minorVersion), true);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = destination.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ // remain the limit unchanged
+ destination.limit(dstLim);
+
+ return new Ciphertext(RecordType.RECORD_ALERT, recordSN);
+ }
+
+ if (fragmenter != null) {
+ return fragmenter.acquireCiphertext(destination);
+ }
+
+ return null;
+ }
+
+ @Override
+ boolean isEmpty() {
+ return ((fragmenter == null) || fragmenter.isEmpty()) &&
+ ((alertMemos == null) || alertMemos.isEmpty());
+ }
+
+ @Override
+ void initHandshaker() {
+ // clean up
+ fragmenter = null;
+ }
+
+ // buffered record fragment
+ private static class RecordMemo {
+ byte contentType;
+ byte majorVersion;
+ byte minorVersion;
+ int encodeEpoch;
+ CipherBox encodeCipher;
+ Authenticator encodeAuthenticator;
+
+ byte[] fragment;
+ }
+
+ private static class HandshakeMemo extends RecordMemo {
+ byte handshakeType;
+ int messageSequence;
+ int acquireOffset;
+ }
+
+ private final class DTLSFragmenter {
+ private LinkedList<RecordMemo> handshakeMemos = new LinkedList<>();
+ private int acquireIndex = 0;
+ private int messageSequence = 0;
+ private boolean flightIsReady = false;
+
+ // Per section 4.1.1, RFC 6347:
+ //
+ // If repeated retransmissions do not result in a response, and the
+ // PMTU is unknown, subsequent retransmissions SHOULD back off to a
+ // smaller record size, fragmenting the handshake message as
+ // appropriate.
+ //
+ // In this implementation, two times of retransmits would be attempted
+ // before backing off. The back off is supported only if the packet
+ // size is bigger than 256 bytes.
+ private int retransmits = 2; // attemps of retransmits
+
+ void queueUpChangeCipherSpec() {
+
+ // Cleanup if a new flight starts.
+ if (flightIsReady) {
+ handshakeMemos.clear();
+ acquireIndex = 0;
+ flightIsReady = false;
+ }
+
+ RecordMemo memo = new RecordMemo();
+
+ memo.contentType = Record.ct_change_cipher_spec;
+ memo.majorVersion = protocolVersion.major;
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeEpoch = writeEpoch;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.fragment = new byte[1];
+ memo.fragment[0] = 1;
+
+ handshakeMemos.add(memo);
+ }
+
+ void queueUpHandshake(byte[] buf,
+ int offset, int length) throws IOException {
+
+ // Cleanup if a new flight starts.
+ if (flightIsReady) {
+ handshakeMemos.clear();
+ acquireIndex = 0;
+ flightIsReady = false;
+ }
+
+ HandshakeMemo memo = new HandshakeMemo();
+
+ memo.contentType = Record.ct_handshake;
+ memo.majorVersion = protocolVersion.major;
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeEpoch = writeEpoch;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.handshakeType = buf[offset];
+ memo.messageSequence = messageSequence++;
+ memo.acquireOffset = 0;
+ memo.fragment = new byte[length - 4]; // 4: header size
+ // 1: HandshakeType
+ // 3: message length
+ System.arraycopy(buf, offset + 4, memo.fragment, 0, length - 4);
+
+ handshakeHashing(memo, memo.fragment);
+ handshakeMemos.add(memo);
+
+ if ((memo.handshakeType == HandshakeMessage.ht_client_hello) ||
+ (memo.handshakeType == HandshakeMessage.ht_hello_request) ||
+ (memo.handshakeType ==
+ HandshakeMessage.ht_hello_verify_request) ||
+ (memo.handshakeType == HandshakeMessage.ht_server_hello_done) ||
+ (memo.handshakeType == HandshakeMessage.ht_finished)) {
+
+ flightIsReady = true;
+ }
+ }
+
+ Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException {
+ if (isEmpty()) {
+ if (isRetransmittable()) {
+ setRetransmission(); // configure for retransmission
+ } else {
+ return null;
+ }
+ }
+
+ RecordMemo memo = handshakeMemos.get(acquireIndex);
+ HandshakeMemo hsMemo = null;
+ if (memo.contentType == Record.ct_handshake) {
+ hsMemo = (HandshakeMemo)memo;
+ }
+
+ int macLen = 0;
+ if (memo.encodeAuthenticator instanceof MAC) {
+ macLen = ((MAC)memo.encodeAuthenticator).MAClen();
+ }
+
+ // ChangeCipherSpec message is pretty small. Don't worry about
+ // the fragmentation of ChangeCipherSpec record.
+ int fragLen;
+ if (packetSize > 0) {
+ fragLen = Math.min(maxRecordSize, packetSize);
+ fragLen = memo.encodeCipher.calculateFragmentSize(
+ fragLen, macLen, 25); // 25: header size
+ // 13: DTLS record
+ // 12: DTLS handshake message
+ fragLen = Math.min(fragLen, Record.maxDataSize);
+ } else {
+ fragLen = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLen = Math.min(fragLen, fragmentSize);
+ }
+
+ int dstPos = dstBuf.position();
+ int dstLim = dstBuf.limit();
+ int dstContent = dstPos + headerSize +
+ memo.encodeCipher.getExplicitNonceSize();
+ dstBuf.position(dstContent);
+
+ if (hsMemo != null) {
+ fragLen = Math.min(fragLen,
+ (hsMemo.fragment.length - hsMemo.acquireOffset));
+
+ dstBuf.put(hsMemo.handshakeType);
+ dstBuf.put((byte)((hsMemo.fragment.length >> 16) & 0xFF));
+ dstBuf.put((byte)((hsMemo.fragment.length >> 8) & 0xFF));
+ dstBuf.put((byte)(hsMemo.fragment.length & 0xFF));
+ dstBuf.put((byte)((hsMemo.messageSequence >> 8) & 0xFF));
+ dstBuf.put((byte)(hsMemo.messageSequence & 0xFF));
+ dstBuf.put((byte)((hsMemo.acquireOffset >> 16) & 0xFF));
+ dstBuf.put((byte)((hsMemo.acquireOffset >> 8) & 0xFF));
+ dstBuf.put((byte)(hsMemo.acquireOffset & 0xFF));
+ dstBuf.put((byte)((fragLen >> 16) & 0xFF));
+ dstBuf.put((byte)((fragLen >> 8) & 0xFF));
+ dstBuf.put((byte)(fragLen & 0xFF));
+ dstBuf.put(hsMemo.fragment, hsMemo.acquireOffset, fragLen);
+ } else {
+ fragLen = Math.min(fragLen, memo.fragment.length);
+ dstBuf.put(memo.fragment, 0, fragLen);
+ }
+
+ dstBuf.limit(dstBuf.position());
+ dstBuf.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(memo.contentType) +
+ ", length = " + dstBuf.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
+ memo.contentType, dstBuf,
+ dstPos, dstLim, headerSize,
+ ProtocolVersion.valueOf(memo.majorVersion,
+ memo.minorVersion), true);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = dstBuf.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ // remain the limit unchanged
+ dstBuf.limit(dstLim);
+
+ // Reset the fragmentation offset.
+ if (hsMemo != null) {
+ hsMemo.acquireOffset += fragLen;
+ if (hsMemo.acquireOffset == hsMemo.fragment.length) {
+ acquireIndex++;
+ }
+
+ return new Ciphertext(RecordType.valueOf(
+ hsMemo.contentType, hsMemo.handshakeType), recordSN);
+ } else {
+ acquireIndex++;
+ return new Ciphertext(
+ RecordType.RECORD_CHANGE_CIPHER_SPEC, recordSN);
+ }
+ }
+
+ private void handshakeHashing(HandshakeMemo hsFrag, byte[] hsBody) {
+
+ byte hsType = hsFrag.handshakeType;
+ if ((hsType == HandshakeMessage.ht_hello_request) ||
+ (hsType == HandshakeMessage.ht_hello_verify_request)) {
+
+ // omitted from handshake hash computation
+ return;
+ }
+
+ if ((hsFrag.messageSequence == 0) &&
+ (hsType == HandshakeMessage.ht_client_hello)) {
+
+ // omit initial ClientHello message
+ //
+ // 2: ClientHello.client_version
+ // 32: ClientHello.random
+ int sidLen = hsBody[34];
+
+ if (sidLen == 0) { // empty session_id, initial handshake
+ return;
+ }
+ }
+
+ // calculate the DTLS header
+ byte[] temporary = new byte[12]; // 12: handshake header size
+
+ // Handshake.msg_type
+ temporary[0] = hsFrag.handshakeType;
+
+ // Handshake.length
+ temporary[1] = (byte)((hsBody.length >> 16) & 0xFF);
+ temporary[2] = (byte)((hsBody.length >> 8) & 0xFF);
+ temporary[3] = (byte)(hsBody.length & 0xFF);
+
+ // Handshake.message_seq
+ temporary[4] = (byte)((hsFrag.messageSequence >> 8) & 0xFF);
+ temporary[5] = (byte)(hsFrag.messageSequence & 0xFF);
+
+ // Handshake.fragment_offset
+ temporary[6] = 0;
+ temporary[7] = 0;
+ temporary[8] = 0;
+
+ // Handshake.fragment_length
+ temporary[9] = temporary[1];
+ temporary[10] = temporary[2];
+ temporary[11] = temporary[3];
+
+ if ((hsType != HandshakeMessage.ht_finished) &&
+ (hsType != HandshakeMessage.ht_certificate_verify)) {
+
+ handshakeHash.update(temporary, 0, 12);
+ handshakeHash.update(hsBody, 0, hsBody.length);
+ } else {
+ // Reserve until this handshake message has been processed.
+ handshakeHash.reserve(temporary, 0, 12);
+ handshakeHash.reserve(hsBody, 0, hsBody.length);
+ }
+
+ }
+
+ boolean isEmpty() {
+ if (!flightIsReady || handshakeMemos.isEmpty() ||
+ acquireIndex >= handshakeMemos.size()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ boolean isRetransmittable() {
+ return (flightIsReady && !handshakeMemos.isEmpty() &&
+ (acquireIndex >= handshakeMemos.size()));
+ }
+
+ private void setRetransmission() {
+ acquireIndex = 0;
+ for (RecordMemo memo : handshakeMemos) {
+ if (memo instanceof HandshakeMemo) {
+ HandshakeMemo hmemo = (HandshakeMemo)memo;
+ hmemo.acquireOffset = 0;
+ }
+ }
+
+ // Shrink packet size if:
+ // 1. maximum fragment size is allowed, in which case the packet
+ // size is configured bigger than maxRecordSize;
+ // 2. maximum packet is bigger than 256 bytes;
+ // 3. two times of retransmits have been attempted.
+ if ((packetSize <= maxRecordSize) &&
+ (packetSize > 256) && ((retransmits--) <= 0)) {
+
+ // shrink packet size
+ shrinkPacketSize();
+ retransmits = 2; // attemps of retransmits
+ }
+ }
+
+ private void shrinkPacketSize() {
+ packetSize = Math.max(256, packetSize / 2);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/DTLSRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+/**
+ * DTLS record
+ */
+interface DTLSRecord extends Record {
+
+ static final int headerSize = 13; // DTLS record header
+
+ static final int handshakeHeaderSize = 12; // DTLS handshake header
+
+ /*
+ * The size of the header plus the max IV length
+ */
+ static final int headerPlusMaxIVSize =
+ headerSize // header
+ + maxIVLength; // iv
+
+ /*
+ * The maximum size that may be increased when translating plaintext to
+ * ciphertext fragment.
+ */
+ static final int maxPlaintextPlusSize =
+ headerSize // header
+ + maxIVLength // iv
+ + maxMacSize // MAC or AEAD tag
+ + maxPadding; // block cipher padding
+
+ /*
+ * the maximum record size
+ */
+ static final int maxRecordSize =
+ headerPlusMaxIVSize // header + iv
+ + maxDataSize // data
+ + maxPadding // padding
+ + maxMacSize; // MAC or AEAD tag
+
+ /*
+ * For CBC protection in SSL3/TLS1, we break some plaintext into two
+ * packets. Max application data size for the second packet.
+ */
+ static final int maxDataSizeMinusOneByteRecord =
+ maxDataSize // max data size
+ - ( // max one byte record size
+ headerPlusMaxIVSize // header + iv
+ + 1 // one byte data
+ + maxPadding // padding
+ + maxMacSize // MAC
+ );
+
+ /*
+ * Maximum record size for alert and change cipher spec records.
+ * They only contain 2 and 1 bytes of data, respectively.
+ * Allocate a smaller array.
+ */
+ static final int maxAlertRecordSize =
+ headerPlusMaxIVSize // header + iv
+ + 2 // alert
+ + maxPadding // padding
+ + maxMacSize; // MAC
+
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Debug.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Debug.java Tue Jun 02 09:15:47 2015 -0700
@@ -29,6 +29,9 @@
import java.security.AccessController;
import java.util.Locale;
+import sun.misc.HexDumpEncoder;
+import java.nio.ByteBuffer;
+
import sun.security.action.GetPropertyAction;
/**
@@ -198,4 +201,47 @@
static String toString(byte[] b) {
return sun.security.util.Debug.toString(b);
}
+
+ static void printHex(String prefix, byte[] bytes) {
+ HexDumpEncoder dump = new HexDumpEncoder();
+
+ synchronized (System.out) {
+ System.out.println(prefix);
+ try {
+ dump.encodeBuffer(bytes, System.out);
+ } catch (Exception e) {
+ // ignore
+ }
+ System.out.flush();
+ }
+ }
+
+ static void printHex(String prefix, ByteBuffer bb) {
+ HexDumpEncoder dump = new HexDumpEncoder();
+
+ synchronized (System.out) {
+ System.out.println(prefix);
+ try {
+ dump.encodeBuffer(bb.slice(), System.out);
+ } catch (Exception e) {
+ // ignore
+ }
+ System.out.flush();
+ }
+ }
+
+ static void printHex(String prefix, byte[] bytes, int offset, int length) {
+ HexDumpEncoder dump = new HexDumpEncoder();
+
+ synchronized (System.out) {
+ System.out.println(prefix);
+ try {
+ ByteBuffer bb = ByteBuffer.wrap(bytes, offset, length);
+ dump.encodeBuffer(bb, System.out);
+ } catch (Exception e) {
+ // ignore
+ }
+ System.out.flush();
+ }
+ }
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/EngineArgs.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-/*
- * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl;
-
-import java.nio.*;
-
-/*
- * A multi-purpose class which handles all of the SSLEngine arguments.
- * It validates arguments, checks for RO conditions, does space
- * calculations, performs scatter/gather, etc.
- *
- * @author Brad R. Wetmore
- */
-class EngineArgs {
-
- /*
- * Keep track of the input parameters.
- */
- ByteBuffer netData;
- ByteBuffer [] appData;
-
- private int offset; // offset/len for the appData array.
- private int len;
-
- /*
- * The initial pos/limit conditions. This is useful because we can
- * quickly calculate the amount consumed/produced in successful
- * operations, or easily return the buffers to their pre-error
- * conditions.
- */
- private int netPos;
- private int netLim;
-
- private int [] appPoss;
- private int [] appLims;
-
- /*
- * Sum total of the space remaining in all of the appData buffers
- */
- private int appRemaining = 0;
-
- private boolean wrapMethod;
-
- /*
- * Called by the SSLEngine.wrap() method.
- */
- EngineArgs(ByteBuffer [] appData, int offset, int len,
- ByteBuffer netData) {
- this.wrapMethod = true;
- init(netData, appData, offset, len);
- }
-
- /*
- * Called by the SSLEngine.unwrap() method.
- */
- EngineArgs(ByteBuffer netData, ByteBuffer [] appData, int offset,
- int len) {
- this.wrapMethod = false;
- init(netData, appData, offset, len);
- }
-
- /*
- * The main initialization method for the arguments. Most
- * of them are pretty obvious as to what they do.
- *
- * Since we're already iterating over appData array for validity
- * checking, we also keep track of how much remainging space is
- * available. Info is used in both unwrap (to see if there is
- * enough space available in the destination), and in wrap (to
- * determine how much more we can copy into the outgoing data
- * buffer.
- */
- private void init(ByteBuffer netData, ByteBuffer [] appData,
- int offset, int len) {
-
- if ((netData == null) || (appData == null)) {
- throw new IllegalArgumentException("src/dst is null");
- }
-
- if ((offset < 0) || (len < 0) || (offset > appData.length - len)) {
- throw new IndexOutOfBoundsException();
- }
-
- if (wrapMethod && netData.isReadOnly()) {
- throw new ReadOnlyBufferException();
- }
-
- netPos = netData.position();
- netLim = netData.limit();
-
- appPoss = new int [appData.length];
- appLims = new int [appData.length];
-
- for (int i = offset; i < offset + len; i++) {
- if (appData[i] == null) {
- throw new IllegalArgumentException(
- "appData[" + i + "] == null");
- }
-
- /*
- * If we're unwrapping, then check to make sure our
- * destination bufffers are writable.
- */
- if (!wrapMethod && appData[i].isReadOnly()) {
- throw new ReadOnlyBufferException();
- }
-
- appRemaining += appData[i].remaining();
-
- appPoss[i] = appData[i].position();
- appLims[i] = appData[i].limit();
- }
-
- /*
- * Ok, looks like we have a good set of args, let's
- * store the rest of this stuff.
- */
- this.netData = netData;
- this.appData = appData;
- this.offset = offset;
- this.len = len;
- }
-
- /*
- * Given spaceLeft bytes to transfer, gather up that much data
- * from the appData buffers (starting at offset in the array),
- * and transfer it into the netData buffer.
- *
- * The user has already ensured there is enough room.
- */
- void gather(int spaceLeft) {
- for (int i = offset; (i < (offset + len)) && (spaceLeft > 0); i++) {
- int amount = Math.min(appData[i].remaining(), spaceLeft);
- appData[i].limit(appData[i].position() + amount);
- netData.put(appData[i]);
- appRemaining -= amount;
- spaceLeft -= amount;
- }
- }
-
- /*
- * Using the supplied buffer, scatter the data into the appData buffers
- * (starting at offset in the array).
- *
- * The user has already ensured there is enough room.
- */
- void scatter(ByteBuffer readyData) {
- int amountLeft = readyData.remaining();
-
- for (int i = offset; (i < (offset + len)) && (amountLeft > 0);
- i++) {
- int amount = Math.min(appData[i].remaining(), amountLeft);
- readyData.limit(readyData.position() + amount);
- appData[i].put(readyData);
- amountLeft -= amount;
- }
- assert(readyData.remaining() == 0);
- }
-
- int getAppRemaining() {
- return appRemaining;
- }
-
- /*
- * Calculate the bytesConsumed/byteProduced. Aren't you glad
- * we saved this off earlier?
- */
- int deltaNet() {
- return (netData.position() - netPos);
- }
-
- /*
- * Calculate the bytesConsumed/byteProduced. Aren't you glad
- * we saved this off earlier?
- */
- int deltaApp() {
- int sum = 0; // Only calculating 2^14 here, don't need a long.
-
- for (int i = offset; i < offset + len; i++) {
- sum += appData[i].position() - appPoss[i];
- }
-
- return sum;
- }
-
- /*
- * In the case of Exception, we want to reset the positions
- * to appear as though no data has been consumed or produced.
- *
- * Currently, this method is only called as we are preparing to
- * fail out, and thus we don't need to actually recalculate
- * appRemaining. If that assumption changes, that variable should
- * be updated here.
- */
- void resetPos() {
- netData.position(netPos);
- for (int i = offset; i < offset + len; i++) {
- // See comment above about recalculating appRemaining.
- appData[i].position(appPoss[i]);
- }
- }
-
- /*
- * We are doing lots of ByteBuffer manipulations, in which case
- * we need to make sure that the limits get set back correctly.
- * This is one of the last things to get done before returning to
- * the user.
- */
- void resetLim() {
- netData.limit(netLim);
- for (int i = offset; i < offset + len; i++) {
- appData[i].limit(appLims[i]);
- }
- }
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/EngineInputRecord.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,427 +0,0 @@
-/*
- * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-
-package sun.security.ssl;
-
-import java.io.*;
-import java.nio.*;
-import javax.net.ssl.*;
-import javax.crypto.BadPaddingException;
-import sun.misc.HexDumpEncoder;
-
-
-/**
- * Wrapper class around InputRecord.
- *
- * Application data is kept external to the InputRecord,
- * but handshake data (alert/change_cipher_spec/handshake) will
- * be kept internally in the ByteArrayInputStream.
- *
- * @author Brad Wetmore
- */
-final class EngineInputRecord extends InputRecord {
-
- private SSLEngineImpl engine;
-
- /*
- * A dummy ByteBuffer we'll pass back even when the data
- * is stored internally. It'll never actually be used.
- */
- static private ByteBuffer tmpBB = ByteBuffer.allocate(0);
-
- /*
- * Flag to tell whether the last read/parsed data resides
- * internal in the ByteArrayInputStream, or in the external
- * buffers.
- */
- private boolean internalData;
-
- EngineInputRecord(SSLEngineImpl engine) {
- super();
- this.engine = engine;
- }
-
- @Override
- byte contentType() {
- if (internalData) {
- return super.contentType();
- } else {
- return ct_application_data;
- }
- }
-
- /*
- * Check if there is enough inbound data in the ByteBuffer
- * to make a inbound packet. Look for both SSLv2 and SSLv3.
- *
- * @return -1 if there are not enough bytes to tell (small header),
- */
- int bytesInCompletePacket(ByteBuffer buf) throws SSLException {
-
- /*
- * SSLv2 length field is in bytes 0/1
- * SSLv3/TLS length field is in bytes 3/4
- */
- if (buf.remaining() < 5) {
- return -1;
- }
-
- int pos = buf.position();
- byte byteZero = buf.get(pos);
-
- int len = 0;
-
- /*
- * If we have already verified previous packets, we can
- * ignore the verifications steps, and jump right to the
- * determination. Otherwise, try one last hueristic to
- * see if it's SSL/TLS.
- */
- if (formatVerified ||
- (byteZero == ct_handshake) ||
- (byteZero == ct_alert)) {
- /*
- * Last sanity check that it's not a wild record
- */
- ProtocolVersion recordVersion =
- ProtocolVersion.valueOf(buf.get(pos + 1), buf.get(pos + 2));
-
- // check the record version
- checkRecordVersion(recordVersion, false);
-
- /*
- * Reasonably sure this is a V3, disable further checks.
- * We can't do the same in the v2 check below, because
- * read still needs to parse/handle the v2 clientHello.
- */
- formatVerified = true;
-
- /*
- * One of the SSLv3/TLS message types.
- */
- len = ((buf.get(pos + 3) & 0xff) << 8) +
- (buf.get(pos + 4) & 0xff) + headerSize;
-
- } else {
- /*
- * Must be SSLv2 or something unknown.
- * Check if it's short (2 bytes) or
- * long (3) header.
- *
- * Internals can warn about unsupported SSLv2
- */
- boolean isShort = ((byteZero & 0x80) != 0);
-
- if (isShort &&
- ((buf.get(pos + 2) == 1) || buf.get(pos + 2) == 4)) {
-
- ProtocolVersion recordVersion =
- ProtocolVersion.valueOf(buf.get(pos + 3), buf.get(pos + 4));
-
- // check the record version
- checkRecordVersion(recordVersion, true);
-
- /*
- * Client or Server Hello
- */
- int mask = (isShort ? 0x7f : 0x3f);
- len = ((byteZero & mask) << 8) + (buf.get(pos + 1) & 0xff) +
- (isShort ? 2 : 3);
-
- } else {
- // Gobblygook!
- throw new SSLException(
- "Unrecognized SSL message, plaintext connection?");
- }
- }
-
- return len;
- }
-
- /*
- * Pass the data down if it's internally cached, otherwise
- * do it here.
- *
- * If internal data, data is decrypted internally.
- *
- * If external data(app), return a new ByteBuffer with data to
- * process.
- */
- ByteBuffer decrypt(Authenticator authenticator,
- CipherBox box, ByteBuffer bb) throws BadPaddingException {
-
- if (internalData) {
- decrypt(authenticator, box); // MAC is checked during decryption
- return tmpBB;
- }
-
- BadPaddingException reservedBPE = null;
- int tagLen =
- (authenticator instanceof MAC) ? ((MAC)authenticator).MAClen() : 0;
- int cipheredLength = bb.remaining();
-
- if (!box.isNullCipher()) {
- try {
- // apply explicit nonce for AEAD/CBC cipher suites if needed
- int nonceSize =
- box.applyExplicitNonce(authenticator, contentType(), bb);
-
- // decrypt the content
- if (box.isAEADMode()) {
- // DON'T encrypt the nonce_explicit for AEAD mode
- bb.position(bb.position() + nonceSize);
- } // The explicit IV for CBC mode can be decrypted.
-
- // Note that the CipherBox.decrypt() does not change
- // the capacity of the buffer.
- box.decrypt(bb, tagLen);
- bb.position(nonceSize); // We don't actually remove the nonce.
- } catch (BadPaddingException bpe) {
- // RFC 2246 states that decryption_failed should be used
- // for this purpose. However, that allows certain attacks,
- // so we just send bad record MAC. We also need to make
- // sure to always check the MAC to avoid a timing attack
- // for the same issue. See paper by Vaudenay et al and the
- // update in RFC 4346/5246.
- //
- // Failover to message authentication code checking.
- reservedBPE = bpe;
- }
- }
-
- // Requires message authentication code for null, stream and block
- // cipher suites.
- if ((authenticator instanceof MAC) && (tagLen != 0)) {
- MAC signer = (MAC)authenticator;
- int macOffset = bb.limit() - tagLen;
-
- // Note that although it is not necessary, we run the same MAC
- // computation and comparison on the payload for both stream
- // cipher and CBC block cipher.
- if (bb.remaining() < tagLen) {
- // negative data length, something is wrong
- if (reservedBPE == null) {
- reservedBPE = new BadPaddingException("bad record");
- }
-
- // set offset of the dummy MAC
- macOffset = cipheredLength - tagLen;
- bb.limit(cipheredLength);
- }
-
- // Run MAC computation and comparison on the payload.
- if (checkMacTags(contentType(), bb, signer, false)) {
- if (reservedBPE == null) {
- reservedBPE = new BadPaddingException("bad record MAC");
- }
- }
-
- // Run MAC computation and comparison on the remainder.
- //
- // It is only necessary for CBC block cipher. It is used to get a
- // constant time of MAC computation and comparison on each record.
- if (box.isCBCMode()) {
- int remainingLen = calculateRemainingLen(
- signer, cipheredLength, macOffset);
-
- // NOTE: here we use the InputRecord.buf because I did not find
- // an effective way to work on ByteBuffer when its capacity is
- // less than remainingLen.
-
- // NOTE: remainingLen may be bigger (less than 1 block of the
- // hash algorithm of the MAC) than the cipheredLength. However,
- // We won't need to worry about it because we always use a
- // maximum buffer for every record. We need a change here if
- // we use small buffer size in the future.
- if (remainingLen > buf.length) {
- // unlikely to happen, just a placehold
- throw new RuntimeException(
- "Internal buffer capacity error");
- }
-
- // Won't need to worry about the result on the remainder. And
- // then we won't need to worry about what's actual data to
- // check MAC tag on. We start the check from the header of the
- // buffer so that we don't need to construct a new byte buffer.
- checkMacTags(contentType(), buf, 0, remainingLen, signer, true);
- }
-
- bb.limit(macOffset);
- }
-
- // Is it a failover?
- if (reservedBPE != null) {
- throw reservedBPE;
- }
-
- return bb.slice();
- }
-
- /*
- * Run MAC computation and comparison
- *
- * Please DON'T change the content of the ByteBuffer parameter!
- */
- private static boolean checkMacTags(byte contentType, ByteBuffer bb,
- MAC signer, boolean isSimulated) {
-
- int position = bb.position();
- int tagLen = signer.MAClen();
- int lim = bb.limit();
- int macData = lim - tagLen;
-
- bb.limit(macData);
- byte[] hash = signer.compute(contentType, bb, isSimulated);
- if (hash == null || tagLen != hash.length) {
- // Something is wrong with MAC implementation.
- throw new RuntimeException("Internal MAC error");
- }
-
- bb.position(macData);
- bb.limit(lim);
- try {
- int[] results = compareMacTags(bb, hash);
- return (results[0] != 0);
- } finally {
- // reset to the data
- bb.position(position);
- bb.limit(macData);
- }
- }
-
- /*
- * A constant-time comparison of the MAC tags.
- *
- * Please DON'T change the content of the ByteBuffer parameter!
- */
- private static int[] compareMacTags(ByteBuffer bb, byte[] tag) {
-
- // An array of hits is used to prevent Hotspot optimization for
- // the purpose of a constant-time check.
- int[] results = {0, 0}; // {missed #, matched #}
-
- // The caller ensures there are enough bytes available in the buffer.
- // So we won't need to check the remaining of the buffer.
- for (int i = 0; i < tag.length; i++) {
- if (bb.get() != tag[i]) {
- results[0]++; // mismatched bytes
- } else {
- results[1]++; // matched bytes
- }
- }
-
- return results;
- }
-
- /*
- * Override the actual write below. We do things this way to be
- * consistent with InputRecord. InputRecord may try to write out
- * data to the peer, and *then* throw an Exception. This forces
- * data to be generated/output before the exception is ever
- * generated.
- */
- @Override
- void writeBuffer(OutputStream s, byte [] buf, int off, int len)
- throws IOException {
- /*
- * Copy data out of buffer, it's ready to go.
- */
- ByteBuffer netBB = ByteBuffer.allocate(len).put(buf, 0, len).flip();
- engine.writer.putOutboundDataSync(netBB);
- }
-
- /*
- * Delineate or read a complete packet from src.
- *
- * If internal data (hs, alert, ccs), the data is read and
- * stored internally.
- *
- * If external data (app), return a new ByteBuffer which points
- * to the data to process.
- */
- ByteBuffer read(ByteBuffer srcBB) throws IOException {
- /*
- * Could have a src == null/dst == null check here,
- * but that was already checked by SSLEngine.unwrap before
- * ever attempting to read.
- */
-
- /*
- * If we have anything besides application data,
- * or if we haven't even done the initial v2 verification,
- * we send this down to be processed by the underlying
- * internal cache.
- */
- if (!formatVerified ||
- (srcBB.get(srcBB.position()) != ct_application_data)) {
- internalData = true;
- read(new ByteBufferInputStream(srcBB), (OutputStream) null);
- return tmpBB;
- }
-
- internalData = false;
-
- int srcPos = srcBB.position();
- int srcLim = srcBB.limit();
-
- ProtocolVersion recordVersion = ProtocolVersion.valueOf(
- srcBB.get(srcPos + 1), srcBB.get(srcPos + 2));
-
- // check the record version
- checkRecordVersion(recordVersion, false);
-
- /*
- * It's really application data. How much to consume?
- * Jump over the header.
- */
- int len = bytesInCompletePacket(srcBB);
- assert(len > 0);
-
- if (debug != null && Debug.isOn("packet")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
- ByteBuffer bb = srcBB.duplicate(); // Use copy of BB
- bb.limit(srcPos + len);
-
- System.out.println("[Raw read (bb)]: length = " + len);
- hd.encodeBuffer(bb, System.out);
- } catch (IOException e) { }
- }
-
- // Demarcate past header to end of packet.
- srcBB.position(srcPos + headerSize);
- srcBB.limit(srcPos + len);
-
- // Protect remainder of buffer, create slice to actually
- // operate on.
- ByteBuffer bb = srcBB.slice();
-
- srcBB.position(srcBB.limit());
- srcBB.limit(srcLim);
-
- return bb;
- }
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/EngineOutputRecord.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,329 +0,0 @@
-/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-
-package sun.security.ssl;
-
-import java.io.*;
-import java.nio.*;
-
-/**
- * A OutputRecord class extension which uses external ByteBuffers
- * or the internal ByteArrayOutputStream for data manipulations.
- * <P>
- * Instead of rewriting this entire class
- * to use ByteBuffers, we leave things intact, so handshake, CCS,
- * and alerts will continue to use the internal buffers, but application
- * data will use external buffers.
- *
- * @author Brad Wetmore
- */
-final class EngineOutputRecord extends OutputRecord {
-
- private SSLEngineImpl engine;
- private EngineWriter writer;
-
- private boolean finishedMsg = false;
-
- /*
- * All handshake hashing is done by the superclass
- */
-
- /*
- * Default constructor makes a record supporting the maximum
- * SSL record size. It allocates the header bytes directly.
- *
- * @param type the content type for the record
- */
- EngineOutputRecord(byte type, SSLEngineImpl engine) {
- super(type, recordSize(type));
- this.engine = engine;
- writer = engine.writer;
- }
-
- /**
- * Get the size of the buffer we need for records of the specified
- * type.
- * <P>
- * Application data buffers will provide their own byte buffers,
- * and will not use the internal byte caching.
- */
- private static int recordSize(byte type) {
- switch (type) {
-
- case ct_change_cipher_spec:
- case ct_alert:
- return maxAlertRecordSize;
-
- case ct_handshake:
- return maxRecordSize;
-
- case ct_application_data:
- return 0;
- }
-
- throw new RuntimeException("Unknown record type: " + type);
- }
-
- void setFinishedMsg() {
- finishedMsg = true;
- }
-
- @Override
- public void flush() throws IOException {
- finishedMsg = false;
- }
-
- boolean isFinishedMsg() {
- return finishedMsg;
- }
-
- /*
- * Override the actual write below. We do things this way to be
- * consistent with InputRecord. InputRecord may try to write out
- * data to the peer, and *then* throw an Exception. This forces
- * data to be generated/output before the exception is ever
- * generated.
- */
- @Override
- void writeBuffer(OutputStream s, byte [] buf, int off, int len,
- int debugOffset) throws IOException {
- /*
- * Copy data out of buffer, it's ready to go.
- */
- ByteBuffer netBB = ByteBuffer.allocate(len).put(buf, off, len).flip();
- writer.putOutboundData(netBB);
- }
-
- /*
- * Main method for writing non-application data.
- * We MAC/encrypt, then send down for processing.
- */
- void write(Authenticator authenticator, CipherBox writeCipher)
- throws IOException {
-
- /*
- * Sanity check.
- */
- switch (contentType()) {
- case ct_change_cipher_spec:
- case ct_alert:
- case ct_handshake:
- break;
- default:
- throw new RuntimeException("unexpected byte buffers");
- }
-
- /*
- * Don't bother to really write empty records. We went this
- * far to drive the handshake machinery, for correctness; not
- * writing empty records improves performance by cutting CPU
- * time and network resource usage. Also, some protocol
- * implementations are fragile and don't like to see empty
- * records, so this increases robustness.
- *
- * (Even change cipher spec messages have a byte of data!)
- */
- if (!isEmpty()) {
- // compress(); // eventually
- encrypt(authenticator, writeCipher);
-
- // send down for processing
- write((OutputStream)null, false, (ByteArrayOutputStream)null);
- }
- return;
- }
-
- /**
- * Main wrap/write driver.
- */
- void write(EngineArgs ea, Authenticator authenticator,
- CipherBox writeCipher) throws IOException {
- /*
- * sanity check to make sure someone didn't inadvertantly
- * send us an impossible combination we don't know how
- * to process.
- */
- assert(contentType() == ct_application_data);
-
- /*
- * Have we set the MAC's yet? If not, we're not ready
- * to process application data yet.
- */
- if (authenticator == MAC.NULL) {
- return;
- }
-
- /*
- * Don't bother to really write empty records. We went this
- * far to drive the handshake machinery, for correctness; not
- * writing empty records improves performance by cutting CPU
- * time and network resource usage. Also, some protocol
- * implementations are fragile and don't like to see empty
- * records, so this increases robustness.
- */
- if (ea.getAppRemaining() == 0) {
- return;
- }
-
- /*
- * By default, we counter chosen plaintext issues on CBC mode
- * ciphersuites in SSLv3/TLS1.0 by sending one byte of application
- * data in the first record of every payload, and the rest in
- * subsequent record(s). Note that the issues have been solved in
- * TLS 1.1 or later.
- *
- * It is not necessary to split the very first application record of
- * a freshly negotiated TLS session, as there is no previous
- * application data to guess. To improve compatibility, we will not
- * split such records.
- *
- * Because of the compatibility, we'd better produce no more than
- * SSLSession.getPacketBufferSize() net data for each wrap. As we
- * need a one-byte record at first, the 2nd record size should be
- * equal to or less than Record.maxDataSizeMinusOneByteRecord.
- *
- * This avoids issues in the outbound direction. For a full fix,
- * the peer must have similar protections.
- */
- int length;
- if (engine.needToSplitPayload(writeCipher, protocolVersion)) {
- write(ea, authenticator, writeCipher, 0x01);
- ea.resetLim(); // reset application data buffer limit
- length = Math.min(ea.getAppRemaining(),
- maxDataSizeMinusOneByteRecord);
- } else {
- length = Math.min(ea.getAppRemaining(), maxDataSize);
- }
-
- // Don't bother to really write empty records.
- if (length > 0) {
- write(ea, authenticator, writeCipher, length);
- }
-
- return;
- }
-
- void write(EngineArgs ea, Authenticator authenticator,
- CipherBox writeCipher, int length) throws IOException {
- /*
- * Copy out existing buffer values.
- */
- ByteBuffer dstBB = ea.netData;
- int dstPos = dstBB.position();
- int dstLim = dstBB.limit();
-
- /*
- * Where to put the data. Jump over the header.
- *
- * Don't need to worry about SSLv2 rewrites, if we're here,
- * that's long since done.
- */
- int dstData = dstPos + headerSize + writeCipher.getExplicitNonceSize();
- dstBB.position(dstData);
-
- /*
- * transfer application data into the network data buffer
- */
- ea.gather(length);
- dstBB.limit(dstBB.position());
- dstBB.position(dstData);
-
- /*
- * "flip" but skip over header again, add MAC & encrypt
- */
- if (authenticator instanceof MAC) {
- MAC signer = (MAC)authenticator;
- if (signer.MAClen() != 0) {
- byte[] hash = signer.compute(contentType(), dstBB, false);
-
- /*
- * position was advanced to limit in compute above.
- *
- * Mark next area as writable (above layers should have
- * established that we have plenty of room), then write
- * out the hash.
- */
- dstBB.limit(dstBB.limit() + hash.length);
- dstBB.put(hash);
-
- // reset the position and limit
- dstBB.limit(dstBB.position());
- dstBB.position(dstData);
- }
- }
-
- if (!writeCipher.isNullCipher()) {
- /*
- * Requires explicit IV/nonce for CBC/AEAD cipher suites for TLS 1.1
- * or later.
- */
- if (protocolVersion.v >= ProtocolVersion.TLS11.v &&
- (writeCipher.isCBCMode() || writeCipher.isAEADMode())) {
- byte[] nonce = writeCipher.createExplicitNonce(
- authenticator, contentType(), dstBB.remaining());
- dstBB.position(dstPos + headerSize);
- dstBB.put(nonce);
- if (!writeCipher.isAEADMode()) {
- // The explicit IV in TLS 1.1 and later can be encrypted.
- dstBB.position(dstPos + headerSize);
- } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode
- }
-
- /*
- * Encrypt may pad, so again the limit may have changed.
- */
- writeCipher.encrypt(dstBB, dstLim);
-
- if ((debug != null) && (Debug.isOn("record") ||
- (Debug.isOn("handshake") &&
- (contentType() == ct_change_cipher_spec)))) {
- System.out.println(Thread.currentThread().getName()
- // v3.0/v3.1 ...
- + ", WRITE: " + protocolVersion
- + " " + InputRecord.contentName(contentType())
- + ", length = " + length);
- }
- } else {
- dstBB.position(dstBB.limit());
- }
-
- int packetLength = dstBB.limit() - dstPos - headerSize;
-
- /*
- * Finish out the record header.
- */
- dstBB.put(dstPos, contentType());
- dstBB.put(dstPos + 1, protocolVersion.major);
- dstBB.put(dstPos + 2, protocolVersion.minor);
- dstBB.put(dstPos + 3, (byte)(packetLength >> 8));
- dstBB.put(dstPos + 4, (byte)packetLength);
-
- /*
- * Position was already set by encrypt() above.
- */
- dstBB.limit(dstLim);
- }
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/EngineWriter.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,244 +0,0 @@
-/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import sun.misc.HexDumpEncoder;
-
-/**
- * A class to help abstract away SSLEngine writing synchronization.
- */
-final class EngineWriter {
-
- /*
- * Outgoing handshake Data waiting for a ride is stored here.
- * Normal application data is written directly into the outbound
- * buffer, but handshake data can be written out at any time,
- * so we have buffer it somewhere.
- *
- * When wrap is called, we first check to see if there is
- * any data waiting, then if we're in a data transfer state,
- * we try to write app data.
- *
- * This will contain either ByteBuffers, or the marker
- * HandshakeStatus.FINISHED to signify that a handshake just completed.
- */
- private LinkedList<Object> outboundList;
-
- private boolean outboundClosed = false;
-
- /* Class and subclass dynamic debugging support */
- private static final Debug debug = Debug.getInstance("ssl");
-
- EngineWriter() {
- outboundList = new LinkedList<Object>();
- }
-
- /*
- * Upper levels assured us we had room for at least one packet of data.
- * As per the SSLEngine spec, we only return one SSL packets worth of
- * data.
- */
- private HandshakeStatus getOutboundData(ByteBuffer dstBB) {
-
- Object msg = outboundList.removeFirst();
- assert(msg instanceof ByteBuffer);
-
- ByteBuffer bbIn = (ByteBuffer) msg;
- assert(dstBB.remaining() >= bbIn.remaining());
-
- dstBB.put(bbIn);
-
- /*
- * If we have more data in the queue, it's either
- * a finished message, or an indication that we need
- * to call wrap again.
- */
- if (hasOutboundDataInternal()) {
- msg = outboundList.getFirst();
- if (msg == HandshakeStatus.FINISHED) {
- outboundList.removeFirst(); // consume the message
- return HandshakeStatus.FINISHED;
- } else {
- return HandshakeStatus.NEED_WRAP;
- }
- } else {
- return null;
- }
- }
-
- /*
- * Properly orders the output of the data written to the wrap call.
- * This is only handshake data, application data goes through the
- * other writeRecord.
- */
- synchronized void writeRecord(EngineOutputRecord outputRecord,
- Authenticator authenticator,
- CipherBox writeCipher) throws IOException {
-
- /*
- * Only output if we're still open.
- */
- if (outboundClosed) {
- throw new IOException("writer side was already closed.");
- }
-
- outputRecord.write(authenticator, writeCipher);
-
- /*
- * Did our handshakers notify that we just sent the
- * Finished message?
- *
- * Add an "I'm finished" message to the queue.
- */
- if (outputRecord.isFinishedMsg()) {
- outboundList.addLast(HandshakeStatus.FINISHED);
- }
- }
-
- /*
- * Output the packet info.
- */
- private void dumpPacket(EngineArgs ea, boolean hsData) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
-
- ByteBuffer bb = ea.netData.duplicate();
-
- int pos = bb.position();
- bb.position(pos - ea.deltaNet());
- bb.limit(pos);
-
- System.out.println("[Raw write" +
- (hsData ? "" : " (bb)") + "]: length = " +
- bb.remaining());
- hd.encodeBuffer(bb, System.out);
- } catch (IOException e) { }
- }
-
- /*
- * Properly orders the output of the data written to the wrap call.
- * Only app data goes through here, handshake data goes through
- * the other writeRecord.
- *
- * Shouldn't expect to have an IOException here.
- *
- * Return any determined status.
- */
- synchronized HandshakeStatus writeRecord(
- EngineOutputRecord outputRecord, EngineArgs ea,
- Authenticator authenticator,
- CipherBox writeCipher) throws IOException {
-
- /*
- * If we have data ready to go, output this first before
- * trying to consume app data.
- */
- if (hasOutboundDataInternal()) {
- HandshakeStatus hss = getOutboundData(ea.netData);
-
- if (debug != null && Debug.isOn("packet")) {
- /*
- * We could have put the dump in
- * OutputRecord.write(OutputStream), but let's actually
- * output when it's actually output by the SSLEngine.
- */
- dumpPacket(ea, true);
- }
-
- return hss;
- }
-
- /*
- * If we are closed, no more app data can be output.
- * Only existing handshake data (above) can be obtained.
- */
- if (outboundClosed) {
- throw new IOException("The write side was already closed");
- }
-
- outputRecord.write(ea, authenticator, writeCipher);
-
- if (debug != null && Debug.isOn("packet")) {
- dumpPacket(ea, false);
- }
-
- /*
- * No way new outbound handshake data got here if we're
- * locked properly.
- *
- * We don't have any status we can return.
- */
- return null;
- }
-
- /*
- * We already hold "this" lock, this is the callback from the
- * outputRecord.write() above. We already know this
- * writer can accept more data (outboundClosed == false),
- * and the closure is sync'd.
- */
- void putOutboundData(ByteBuffer bytes) {
- outboundList.addLast(bytes);
- }
-
- /*
- * This is for the really rare case that someone is writing from
- * the *InputRecord* before we know what to do with it.
- */
- synchronized void putOutboundDataSync(ByteBuffer bytes)
- throws IOException {
-
- if (outboundClosed) {
- throw new IOException("Write side already closed");
- }
-
- outboundList.addLast(bytes);
- }
-
- /*
- * Non-synch'd version of this method, called by internals
- */
- private boolean hasOutboundDataInternal() {
- return (outboundList.size() != 0);
- }
-
- synchronized boolean hasOutboundData() {
- return hasOutboundDataInternal();
- }
-
- synchronized boolean isOutboundDone() {
- return outboundClosed && !hasOutboundDataInternal();
- }
-
- synchronized void closeOutbound() {
- outboundClosed = true;
- }
-
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeHash.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeHash.java Tue Jun 02 09:15:47 2015 -0700
@@ -29,6 +29,7 @@
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.util.Locale;
+import java.nio.ByteBuffer;
/**
* Abstraction for the SSL/TLS hash of all handshake messages that is
@@ -99,6 +100,9 @@
// For TLS 1.2
private MessageDigest finMD;
+ // Cache for input record handshake hash computation
+ private ByteArrayOutputStream reserve = new ByteArrayOutputStream();
+
/**
* Create a new HandshakeHash. needCertificateVerify indicates whether
* a hash for the certificate verify message is required.
@@ -107,7 +111,106 @@
clonesNeeded = needCertificateVerify ? 3 : 2;
}
+ void reserve(ByteBuffer input) {
+ if (input.hasArray()) {
+ reserve.write(input.array(),
+ input.position() + input.arrayOffset(), input.remaining());
+ } else {
+ int inPos = input.position();
+ byte[] holder = new byte[input.remaining()];
+ input.get(holder);
+ input.position(inPos);
+ reserve.write(holder, 0, holder.length);
+ }
+ }
+
+ void reserve(byte[] b, int offset, int len) {
+ reserve.write(b, offset, len);
+ }
+
+ void reload() {
+ if (reserve.size() != 0) {
+ byte[] bytes = reserve.toByteArray();
+ reserve.reset();
+ update(bytes, 0, bytes.length);
+ }
+ }
+
+ void update(ByteBuffer input) {
+
+ // reload if there are reserved messages.
+ reload();
+
+ int inPos = input.position();
+ switch (version) {
+ case 1:
+ md5.update(input);
+ input.position(inPos);
+
+ sha.update(input);
+ input.position(inPos);
+
+ break;
+ default:
+ if (finMD != null) {
+ finMD.update(input);
+ input.position(inPos);
+ }
+ if (input.hasArray()) {
+ data.write(input.array(),
+ inPos + input.arrayOffset(), input.remaining());
+ } else {
+ byte[] holder = new byte[input.remaining()];
+ input.get(holder);
+ input.position(inPos);
+ data.write(holder, 0, holder.length);
+ }
+ break;
+ }
+ }
+
+ void update(byte handshakeType, byte[] handshakeBody) {
+
+ // reload if there are reserved messages.
+ reload();
+
+ switch (version) {
+ case 1:
+ md5.update(handshakeType);
+ sha.update(handshakeType);
+
+ md5.update((byte)((handshakeBody.length >> 16) & 0xFF));
+ sha.update((byte)((handshakeBody.length >> 16) & 0xFF));
+ md5.update((byte)((handshakeBody.length >> 8) & 0xFF));
+ sha.update((byte)((handshakeBody.length >> 8) & 0xFF));
+ md5.update((byte)(handshakeBody.length & 0xFF));
+ sha.update((byte)(handshakeBody.length & 0xFF));
+
+ md5.update(handshakeBody);
+ sha.update(handshakeBody);
+ break;
+ default:
+ if (finMD != null) {
+ finMD.update(handshakeType);
+ finMD.update((byte)((handshakeBody.length >> 16) & 0xFF));
+ finMD.update((byte)((handshakeBody.length >> 8) & 0xFF));
+ finMD.update((byte)(handshakeBody.length & 0xFF));
+ finMD.update(handshakeBody);
+ }
+ data.write(handshakeType);
+ data.write((byte)((handshakeBody.length >> 16) & 0xFF));
+ data.write((byte)((handshakeBody.length >> 8) & 0xFF));
+ data.write((byte)(handshakeBody.length & 0xFF));
+ data.write(handshakeBody, 0, handshakeBody.length);
+ break;
+ }
+ }
+
void update(byte[] b, int offset, int len) {
+
+ // reload if there are reserved messages.
+ reload();
+
switch (version) {
case 1:
md5.update(b, offset, len);
@@ -139,9 +242,15 @@
void protocolDetermined(ProtocolVersion pv) {
// Do not set again, will ignore
- if (version != -1) return;
+ if (version != -1) {
+ return;
+ }
- version = pv.compareTo(ProtocolVersion.TLS12) >= 0 ? 2 : 1;
+ if (pv.maybeDTLSProtocol()) {
+ version = pv.compareTo(ProtocolVersion.DTLS12) >= 0 ? 2 : 1;
+ } else {
+ version = pv.compareTo(ProtocolVersion.TLS12) >= 0 ? 2 : 1;
+ }
switch (version) {
case 1:
// initiate md5, sha and call update on saved array
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeInStream.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeInStream.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,11 +23,11 @@
* questions.
*/
-
package sun.security.ssl;
-import java.io.InputStream;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
import javax.net.ssl.SSLException;
@@ -38,154 +38,104 @@
* Once a new handshake record arrives, it is buffered in this class until
* processed by the Handshaker. The buffer may also contain incomplete
* handshake messages in case the message is split across multiple records.
- * Handshaker.process_record deals with all that. It may also contain
+ * Handshaker.processRecord deals with all that. It may also contain
* handshake messages larger than the default buffer size (e.g. large
- * certificate messages). The buffer is grown dynamically to handle that
- * (see InputRecord.queueHandshake()).
+ * certificate messages). The buffer is grown dynamically to handle that.
*
- * Note that the InputRecord used as a buffer here is separate from the
- * AppInStream.r, which is where data from the socket is initially read
- * into. This is because once the initial handshake has been completed,
- * handshake and application data messages may be interleaved arbitrarily
- * and must be processed independently.
+ * Note that this class only handles Handshake messages in TLS format.
+ * DTLS Handshake messages should be converted into TLS format before
+ * calling into this method.
*
* @author David Brownell
*/
-public class HandshakeInStream extends InputStream {
- InputRecord r;
+// This class is used to handle plain text handshake messages.
+//
+public final class HandshakeInStream extends ByteArrayInputStream {
/*
* Construct the stream; we'll be accumulating hashes of the
* input records using two sets of digests.
*/
- HandshakeInStream(HandshakeHash handshakeHash) {
- r = new InputRecord();
- r.setHandshakeHash(handshakeHash);
- }
-
-
- // overridden InputStream methods
-
- /*
- * Return the number of bytes available for read().
- *
- * Note that this returns the bytes remaining in the buffer, not
- * the bytes remaining in the current handshake message.
- */
- @Override
- public int available() {
- return r.available();
- }
-
- /*
- * Get a byte of handshake data.
- */
- @Override
- public int read() throws IOException {
- int n = r.read();
- if (n == -1) {
- throw new SSLException("Unexpected end of handshake data");
- }
- return n;
+ HandshakeInStream() {
+ super(new byte[0]); // lazy to alloacte the internal buffer
}
- /*
- * Get a bunch of bytes of handshake data.
- */
+ //
+ // overridden ByteArrayInputStream methods
+ //
+
@Override
- public int read(byte b [], int off, int len) throws IOException {
- // we read from a ByteArrayInputStream, it always returns the
- // data in a single read if enough is available
- int n = r.read(b, off, len);
- if (n != len) {
+ public int read(byte[] b) throws IOException {
+ if (super.read(b) != b.length) {
throw new SSLException("Unexpected end of handshake data");
}
- return n;
- }
- /*
- * Skip some handshake data.
- */
- @Override
- public long skip(long n) throws IOException {
- return r.skip(n);
+ return b.length;
}
- /*
- * Mark/ reset code, implemented using InputRecord mark/ reset.
- *
- * Note that it currently provides only a limited mark functionality
- * and should be used with care (once a new handshake record has been
- * read, data that has already been consumed is lost even if marked).
- */
-
- @Override
- public void mark(int readlimit) {
- r.mark(readlimit);
- }
-
- @Override
- public void reset() throws IOException {
- r.reset();
- }
-
- @Override
- public boolean markSupported() {
- return true;
- }
-
-
- // handshake management functions
+ //
+ // handshake input stream management functions
+ //
/*
* Here's an incoming record with handshake data. Queue the contents;
* it might be one or more entire messages, complete a message that's
* partly queued, or both.
*/
- void incomingRecord(InputRecord in) throws IOException {
- r.queueHandshake(in);
+ void incomingRecord(ByteBuffer in) throws IOException {
+ int len;
+
+ // Move any unread data to the front of the buffer.
+ if (pos != 0) {
+ len = count - pos;
+ if (len != 0) {
+ System.arraycopy(buf, pos, buf, 0, len);
+ }
+ pos = 0;
+ count = len;
+ }
+
+ // Grow buffer if needed.
+ len = in.remaining() + count;
+ if (buf.length < len) {
+ byte[] newbuf = new byte[len];
+ if (count != 0) {
+ System.arraycopy(buf, 0, newbuf, 0, count);
+ }
+ buf = newbuf;
+ }
+
+ // Append the incoming record to the buffer
+ in.get(buf, count, in.remaining());
+ count = len;
}
- /*
- * Hash any data we've consumed but not yet hashed. Useful mostly
- * for processing client certificate messages (so we can check the
- * immediately following cert verify message) and finished messages
- * (so we can compute our own finished message).
- */
- void digestNow() {
- r.doHashes();
- }
-
- /*
- * Do more than skip that handshake data ... totally ignore it.
- * The difference is that the data does not get hashed.
- */
- void ignore(int n) {
- r.ignore(n);
- }
-
-
+ //
// Message parsing methods
+ //
/*
* Read 8, 16, 24, and 32 bit SSL integer data types, encoded
* in standard big-endian form.
*/
-
int getInt8() throws IOException {
+ verifyLength(1);
return read();
}
int getInt16() throws IOException {
+ verifyLength(2);
return (getInt8() << 8) | getInt8();
}
int getInt24() throws IOException {
+ verifyLength(3);
return (getInt8() << 16) | (getInt8() << 8) | getInt8();
}
int getInt32() throws IOException {
+ verifyLength(4);
return (getInt8() << 24) | (getInt8() << 16)
| (getInt8() << 8) | getInt8();
}
@@ -193,13 +143,12 @@
/*
* Read byte vectors with 8, 16, and 24 bit length encodings.
*/
-
byte[] getBytes8() throws IOException {
int len = getInt8();
verifyLength(len);
byte b[] = new byte[len];
- read(b, 0, len);
+ read(b);
return b;
}
@@ -208,7 +157,7 @@
verifyLength(len);
byte b[] = new byte[len];
- read(b, 0, len);
+ read(b);
return b;
}
@@ -217,16 +166,14 @@
verifyLength(len);
byte b[] = new byte[len];
- read(b, 0, len);
+ read(b);
return b;
}
// Is a length greater than available bytes in the record?
private void verifyLength(int len) throws SSLException {
if (len > available()) {
- throw new SSLException(
- "Not enough data to fill declared vector size");
+ throw new SSLException("Unexpected end of handshake data");
}
}
-
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java Tue Jun 02 09:15:47 2015 -0700
@@ -73,24 +73,43 @@
*/
public abstract class HandshakeMessage {
- HandshakeMessage() { }
+ /* Class and subclass dynamic debugging support */
+ public static final Debug debug = Debug.getInstance("ssl");
// enum HandshakeType:
- static final byte ht_hello_request = 0;
- static final byte ht_client_hello = 1;
- static final byte ht_server_hello = 2;
+ static final byte ht_hello_request = 0; // RFC 5246
+ static final byte ht_client_hello = 1; // RFC 5246
+ static final byte ht_server_hello = 2; // RFC 5246
+ static final byte ht_hello_verify_request = 3; // RFC 6347
+ static final byte ht_new_session_ticket = 4; // RFC 4507
+
+ static final byte ht_certificate = 11; // RFC 5246
+ static final byte ht_server_key_exchange = 12; // RFC 5246
+ static final byte ht_certificate_request = 13; // RFC 5246
+ static final byte ht_server_hello_done = 14; // RFC 5246
+ static final byte ht_certificate_verify = 15; // RFC 5246
+ static final byte ht_client_key_exchange = 16; // RFC 5246
- static final byte ht_certificate = 11;
- static final byte ht_server_key_exchange = 12;
- static final byte ht_certificate_request = 13;
- static final byte ht_server_hello_done = 14;
- static final byte ht_certificate_verify = 15;
- static final byte ht_client_key_exchange = 16;
+ static final byte ht_finished = 20; // RFC 5246
+ static final byte ht_certificate_url = 21; // RFC 6066
+ static final byte ht_certificate_status = 22; // RFC 6066
+ static final byte ht_supplemental_data = 23; // RFC 4680
+
+ static final byte ht_not_applicable = -1; // N/A
- static final byte ht_finished = 20;
+ /*
+ * SSL 3.0 MAC padding constants.
+ * Also used by CertificateVerify and Finished during the handshake.
+ */
+ static final byte[] MD5_pad1 = genPad(0x36, 48);
+ static final byte[] MD5_pad2 = genPad(0x5c, 48);
- /* Class and subclass dynamic debugging support */
- public static final Debug debug = Debug.getInstance("ssl");
+ static final byte[] SHA_pad1 = genPad(0x36, 40);
+ static final byte[] SHA_pad2 = genPad(0x5c, 40);
+
+ // default constructor
+ HandshakeMessage() {
+ }
/**
* Utility method to convert a BigInteger to a byte array in unsigned
@@ -109,16 +128,6 @@
return b;
}
- /*
- * SSL 3.0 MAC padding constants.
- * Also used by CertificateVerify and Finished during the handshake.
- */
- static final byte[] MD5_pad1 = genPad(0x36, 48);
- static final byte[] MD5_pad2 = genPad(0x5c, 48);
-
- static final byte[] SHA_pad1 = genPad(0x36, 40);
- static final byte[] SHA_pad2 = genPad(0x5c, 40);
-
private static byte[] genPad(int b, int count) {
byte[] padding = new byte[count];
Arrays.fill(padding, (byte)b);
@@ -141,6 +150,7 @@
s.write(messageType());
s.putInt24(len);
send(s);
+ s.complete();
}
/*
@@ -199,6 +209,69 @@
}
+/*
+ * HelloVerifyRequest ... SERVER --> CLIENT [DTLS only]
+ *
+ * The definition of HelloVerifyRequest is as follows:
+ *
+ * struct {
+ * ProtocolVersion server_version;
+ * opaque cookie<0..2^8-1>;
+ * } HelloVerifyRequest;
+ *
+ * For DTLS protocols, once the client has transmitted the ClientHello message,
+ * it expects to see a HelloVerifyRequest from the server. However, if the
+ * server's message is lost, the client knows that either the ClientHello or
+ * the HelloVerifyRequest has been lost and retransmits. [RFC 6347]
+ */
+static final class HelloVerifyRequest extends HandshakeMessage {
+ ProtocolVersion protocolVersion;
+ byte[] cookie; // 1 to 2^8 - 1 bytes
+
+ HelloVerifyRequest(HelloCookieManager helloCookieManager,
+ ClientHello clientHelloMsg) {
+
+ this.protocolVersion = clientHelloMsg.protocolVersion;
+ this.cookie = helloCookieManager.getCookie(clientHelloMsg);
+ }
+
+ HelloVerifyRequest(
+ HandshakeInStream input, int messageLength) throws IOException {
+
+ this.protocolVersion =
+ ProtocolVersion.valueOf(input.getInt8(), input.getInt8());
+ this.cookie = input.getBytes8();
+
+ // Is it a valid cookie?
+ HelloCookieManager.checkCookie(protocolVersion, cookie);
+ }
+
+ @Override
+ int messageType() {
+ return ht_hello_verify_request;
+ }
+
+ @Override
+ int messageLength() {
+ return 2 + cookie.length; // 2: the length of protocolVersion
+ }
+
+ @Override
+ void send(HandshakeOutStream hos) throws IOException {
+ hos.putInt8(protocolVersion.major);
+ hos.putInt8(protocolVersion.minor);
+ hos.putBytes8(cookie);
+ }
+
+ @Override
+ void print(PrintStream out) throws IOException {
+ out.println("*** HelloVerifyRequest");
+ if (debug != null && Debug.isOn("verbose")) {
+ out.println("server_version: " + protocolVersion);
+ Debug.println(out, "cookie", cookie);
+ }
+ }
+}
/*
* ClientHello ... CLIENT --> SERVER
@@ -213,22 +286,31 @@
*/
static final class ClientHello extends HandshakeMessage {
- ProtocolVersion protocolVersion;
- RandomCookie clnt_random;
- SessionId sessionId;
- private CipherSuiteList cipherSuites;
- byte[] compression_methods;
+ ProtocolVersion protocolVersion;
+ RandomCookie clnt_random;
+ SessionId sessionId;
+ byte[] cookie; // DTLS only
+ private CipherSuiteList cipherSuites;
+ private final boolean isDTLS;
+ byte[] compression_methods;
HelloExtensions extensions = new HelloExtensions();
private final static byte[] NULL_COMPRESSION = new byte[] {0};
ClientHello(SecureRandom generator, ProtocolVersion protocolVersion,
- SessionId sessionId, CipherSuiteList cipherSuites) {
+ SessionId sessionId, CipherSuiteList cipherSuites,
+ boolean isDTLS) {
+ this.isDTLS = isDTLS;
this.protocolVersion = protocolVersion;
this.sessionId = sessionId;
this.cipherSuites = cipherSuites;
+ if (isDTLS) {
+ this.cookie = new byte[0];
+ } else {
+ this.cookie = null;
+ }
if (cipherSuites.containsEC()) {
extensions.add(SupportedEllipticCurvesExtension.DEFAULT);
@@ -239,11 +321,21 @@
compression_methods = NULL_COMPRESSION;
}
- ClientHello(HandshakeInStream s, int messageLength) throws IOException {
+ ClientHello(HandshakeInStream s,
+ int messageLength, boolean isDTLS) throws IOException {
+
+ this.isDTLS = isDTLS;
+
protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());
clnt_random = new RandomCookie(s);
sessionId = new SessionId(s.getBytes8());
sessionId.checkLength(protocolVersion);
+ if (isDTLS) {
+ cookie = s.getBytes8();
+ } else {
+ cookie = null;
+ }
+
cipherSuites = new CipherSuiteList(s);
compression_methods = s.getBytes8();
if (messageLength() != messageLength) {
@@ -279,6 +371,28 @@
extensions.add(signatureAlgorithm);
}
+ void addMFLExtension(int maximumPacketSize) {
+ HelloExtension maxFragmentLength =
+ new MaxFragmentLengthExtension(maximumPacketSize);
+ extensions.add(maxFragmentLength);
+ }
+
+ void updateHelloCookie(MessageDigest cookieDigest) {
+ //
+ // Just use HandshakeOutStream to compute the hello verify cookie.
+ // Not actually used to output handshake message records.
+ //
+ HandshakeOutStream hos = new HandshakeOutStream(null);
+
+ try {
+ send(hos, false); // Do not count hello verify cookie.
+ } catch (IOException ioe) {
+ // unlikely to happen
+ }
+
+ cookieDigest.update(hos.toByteArray());
+ }
+
@Override
int messageType() { return ht_client_hello; }
@@ -290,6 +404,7 @@
*/
return (2 + 32 + 1 + 2 + 1
+ sessionId.length() /* ... + variable parts */
+ + (isDTLS ? (1 + cookie.length) : 0)
+ (cipherSuites.size() * 2)
+ compression_methods.length)
+ extensions.length();
@@ -297,13 +412,7 @@
@Override
void send(HandshakeOutStream s) throws IOException {
- s.putInt8(protocolVersion.major);
- s.putInt8(protocolVersion.minor);
- clnt_random.send(s);
- s.putBytes8(sessionId.getId());
- cipherSuites.send(s);
- s.putBytes8(compression_methods);
- extensions.send(s);
+ send(s, true); // Count hello verify cookie.
}
@Override
@@ -317,6 +426,10 @@
s.print("Session ID: ");
s.println(sessionId);
+ if (isDTLS) {
+ Debug.println(s, "cookie", cookie);
+ }
+
s.println("Cipher Suites: " + cipherSuites);
Debug.println(s, "Compression Methods", compression_methods);
@@ -324,6 +437,21 @@
s.println("***");
}
}
+
+ private void send(HandshakeOutStream s,
+ boolean computeCookie) throws IOException {
+ s.putInt8(protocolVersion.major);
+ s.putInt8(protocolVersion.minor);
+ clnt_random.send(s);
+ s.putBytes8(sessionId.getId());
+ if (isDTLS && computeCookie) {
+ s.putBytes8(cookie);
+ }
+ cipherSuites.send(s);
+ s.putBytes8(compression_methods);
+ extensions.send(s);
+ }
+
}
/*
@@ -740,7 +868,7 @@
setValues(obj);
Signature sig;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
this.preferableSignatureAlgorithm = signAlgorithm;
sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
} else {
@@ -801,7 +929,7 @@
new BigInteger(1, dh_g)));
// read the signature and hash algorithm
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
int hash = input.getInt8(); // hash algorithm
int signature = input.getInt8(); // signature algorithm
@@ -834,7 +962,7 @@
Signature sig;
String algorithm = publicKey.getAlgorithm();
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
sig = JsseJce.getSignature(
preferableSignatureAlgorithm.getAlgorithmName());
} else {
@@ -914,7 +1042,7 @@
temp += dh_Ys.length;
if (signature != null) {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
temp += SignatureAndHashAlgorithm.sizeInRecord();
}
@@ -934,7 +1062,7 @@
s.putBytes16(dh_Ys);
if (signature != null) {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.putInt8(preferableSignatureAlgorithm.getHashValue());
s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
}
@@ -959,7 +1087,7 @@
if (signature == null) {
s.println("Anonymous");
} else {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.println("Signature Algorithm " +
preferableSignatureAlgorithm.getAlgorithmName());
}
@@ -1021,7 +1149,7 @@
}
Signature sig;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
this.preferableSignatureAlgorithm = signAlgorithm;
sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
} else {
@@ -1084,7 +1212,7 @@
}
// read the signature and hash algorithm
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
int hash = input.getInt8(); // hash algorithm
int signature = input.getInt8(); // signature algorithm
@@ -1105,7 +1233,7 @@
// verify the signature
Signature sig;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
sig = JsseJce.getSignature(
preferableSignatureAlgorithm.getAlgorithmName());
} else {
@@ -1157,7 +1285,7 @@
int sigLen = 0;
if (signatureBytes != null) {
sigLen = 2 + signatureBytes.length;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
sigLen += SignatureAndHashAlgorithm.sizeInRecord();
}
}
@@ -1172,7 +1300,7 @@
s.putBytes8(pointBytes);
if (signatureBytes != null) {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.putInt8(preferableSignatureAlgorithm.getHashValue());
s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
}
@@ -1189,7 +1317,7 @@
if (signatureBytes == null) {
s.println("Anonymous");
} else {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.println("Signature Algorithm " +
preferableSignatureAlgorithm.getAlgorithmName());
}
@@ -1315,7 +1443,7 @@
this.types = JsseJce.isEcAvailable() ? TYPES_ECC : TYPES_NO_ECC;
// Use supported_signature_algorithms for TLS 1.2 or later.
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (signAlgs == null || signAlgs.isEmpty()) {
throw new SSLProtocolException(
"No supported signature algorithms");
@@ -1339,7 +1467,7 @@
types = input.getBytes8();
// Read the supported_signature_algorithms for TLS 1.2 or later.
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
algorithmsLen = input.getInt16();
if (algorithmsLen < 2) {
throw new SSLProtocolException(
@@ -1406,7 +1534,7 @@
int messageLength() {
int len = 1 + types.length + 2;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
len += algorithmsLen + 2;
}
@@ -1423,7 +1551,7 @@
output.putBytes8(types);
// put supported_signature_algorithms
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
output.putInt16(algorithmsLen);
for (SignatureAndHashAlgorithm algorithm : algorithms) {
output.putInt8(algorithm.getHashValue()); // hash
@@ -1478,7 +1606,7 @@
}
s.println();
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
StringBuilder sb = new StringBuilder();
boolean opened = false;
for (SignatureAndHashAlgorithm signAlg : algorithms) {
@@ -1576,7 +1704,7 @@
String algorithm = privateKey.getAlgorithm();
Signature sig = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
this.preferableSignatureAlgorithm = signAlgorithm;
sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
} else {
@@ -1598,7 +1726,7 @@
this.protocolVersion = protocolVersion;
// read the signature and hash algorithm
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
int hashAlg = input.getInt8(); // hash algorithm
int signAlg = input.getInt8(); // signature algorithm
@@ -1634,7 +1762,7 @@
SecretKey masterSecret) throws GeneralSecurityException {
String algorithm = publicKey.getAlgorithm();
Signature sig = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
sig = JsseJce.getSignature(
preferableSignatureAlgorithm.getAlgorithmName());
} else {
@@ -1676,11 +1804,11 @@
throws SignatureException {
if (algorithm.equals("RSA")) {
- if (protocolVersion.v < ProtocolVersion.TLS12.v) { // TLS1.1-
+ if (!protocolVersion.useTLS12PlusSpec()) { // TLS1.1-
MessageDigest md5Clone = handshakeHash.getMD5Clone();
MessageDigest shaClone = handshakeHash.getSHAClone();
- if (protocolVersion.v < ProtocolVersion.TLS10.v) { // SSLv3
+ if (!protocolVersion.useTLS10PlusSpec()) { // SSLv3
updateDigest(md5Clone, MD5_pad1, MD5_pad2, masterKey);
updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
}
@@ -1692,10 +1820,10 @@
sig.update(handshakeHash.getAllHandshakeMessages());
}
} else { // DSA, ECDSA
- if (protocolVersion.v < ProtocolVersion.TLS12.v) { // TLS1.1-
+ if (!protocolVersion.useTLS12PlusSpec()) { // TLS1.1-
MessageDigest shaClone = handshakeHash.getSHAClone();
- if (protocolVersion.v < ProtocolVersion.TLS10.v) { // SSLv3
+ if (!protocolVersion.useTLS10PlusSpec()) { // SSLv3
updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
}
@@ -1811,7 +1939,7 @@
int messageLength() {
int temp = 2;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
temp += SignatureAndHashAlgorithm.sizeInRecord();
}
@@ -1820,7 +1948,7 @@
@Override
void send(HandshakeOutStream s) throws IOException {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.putInt8(preferableSignatureAlgorithm.getHashValue());
s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
}
@@ -1833,7 +1961,7 @@
s.println("*** CertificateVerify");
if (debug != null && Debug.isOn("verbose")) {
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
s.println("Signature Algorithm " +
preferableSignatureAlgorithm.getAlgorithmName());
}
@@ -1899,7 +2027,7 @@
CipherSuite cipherSuite) throws IOException {
this.protocolVersion = protocolVersion;
this.cipherSuite = cipherSuite;
- int msgLen = (protocolVersion.v >= ProtocolVersion.TLS10.v) ? 12 : 36;
+ int msgLen = protocolVersion.useTLS10PlusSpec() ? 12 : 36;
verifyData = new byte[msgLen];
input.read(verifyData);
}
@@ -1932,7 +2060,7 @@
throw new RuntimeException("Invalid sender: " + sender);
}
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
// TLS 1.0+
try {
byte [] seed;
@@ -1940,14 +2068,14 @@
PRF prf;
// Get the KeyGenerator alg and calculate the seed.
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
- // TLS 1.2
+ if (protocolVersion.useTLS12PlusSpec()) {
+ // TLS 1.2+ or DTLS 1.2+
seed = handshakeHash.getFinishedHash();
prfAlg = "SunTls12Prf";
prf = cipherSuite.prfAlg;
} else {
- // TLS 1.0/1.1
+ // TLS 1.0/1.1, DTLS 1.0
MessageDigest md5Clone = handshakeHash.getMD5Clone();
MessageDigest shaClone = handshakeHash.getSHAClone();
seed = new byte[36];
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,10 +23,9 @@
* questions.
*/
-
package sun.security.ssl;
-import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
@@ -40,197 +39,113 @@
*
* @author David Brownell
*/
-public class HandshakeOutStream extends OutputStream {
-
- private SSLSocketImpl socket;
- private SSLEngineImpl engine;
-
- OutputRecord r;
+public class HandshakeOutStream extends ByteArrayOutputStream {
- HandshakeOutStream(ProtocolVersion protocolVersion,
- ProtocolVersion helloVersion, HandshakeHash handshakeHash,
- SSLSocketImpl socket) {
- this.socket = socket;
- r = new OutputRecord(Record.ct_handshake);
- init(protocolVersion, helloVersion, handshakeHash);
- }
+ OutputRecord outputRecord; // May be null if not actually used to
+ // output handshake message records.
- HandshakeOutStream(ProtocolVersion protocolVersion,
- ProtocolVersion helloVersion, HandshakeHash handshakeHash,
- SSLEngineImpl engine) {
- this.engine = engine;
- r = new EngineOutputRecord(Record.ct_handshake, engine);
- init(protocolVersion, helloVersion, handshakeHash);
- }
-
- private void init(ProtocolVersion protocolVersion,
- ProtocolVersion helloVersion, HandshakeHash handshakeHash) {
- r.setVersion(protocolVersion);
- r.setHelloVersion(helloVersion);
- r.setHandshakeHash(handshakeHash);
+ HandshakeOutStream(OutputRecord outputRecord) {
+ super();
+ this.outputRecord = outputRecord;
}
+ // Complete a handshakin message writing. Called by HandshakeMessage.
+ void complete() throws IOException {
+ if (size() < 4) { // 4: handshake message header size
+ // internal_error alert will be triggered
+ throw new RuntimeException("handshake message is not available");
+ }
- /*
- * Update the handshake data hashes ... mostly for use after a
- * client cert has been sent, so the cert verify message can be
- * constructed correctly yet without forcing extra I/O. In all
- * other cases, automatic hash calculation suffices.
- */
- void doHashes() {
- r.doHashes();
+ // outputRecord cannot be null
+ outputRecord.encodeHandshake(buf, 0, count);
+
+ // reset the byte array output stream
+ reset();
}
- /*
- * Write some data out onto the stream ... buffers as much as possible.
- * Hashes are updated automatically if something gets flushed to the
- * network (e.g. a big cert message etc).
- */
- @Override
- public void write(byte buf[], int off, int len) throws IOException {
- while (len > 0) {
- int howmuch = Math.min(len, r.availableDataBytes());
+ //
+ // overridden ByteArrayOutputStream methods
+ //
- if (howmuch == 0) {
- flush();
- } else {
- r.write(buf, off, howmuch);
- off += howmuch;
- len -= howmuch;
- }
- }
- }
-
- /*
- * write-a-byte
- */
@Override
- public void write(int i) throws IOException {
- if (r.availableDataBytes() < 1) {
- flush();
- }
- r.write(i);
+ public void write(byte[] b, int off, int len) {
+ // The maximum fragment size is 24 bytes.
+ checkOverflow(len, Record.OVERFLOW_OF_INT24);
+ super.write(b, off, len);
}
@Override
public void flush() throws IOException {
- if (socket != null) {
- try {
- socket.writeRecord(r);
- } catch (IOException e) {
- // Had problems writing; check if there was an
- // alert from peer. If alert received, waitForClose
- // will throw an exception for the alert
- socket.waitForClose(true);
-
- // No alert was received, just rethrow exception
- throw e;
- }
- } else { // engine != null
- /*
- * Even if record might be empty, flush anyway in case
- * there is a finished handshake message that we need
- * to queue.
- */
- engine.writeRecord((EngineOutputRecord)r);
- }
+ outputRecord.flush();
}
- /*
- * Tell the OutputRecord that a finished message was
- * contained either in this record or the one immeiately
- * preceding it. We need to reliably pass back notifications
- * that a finish message occurred.
- */
- void setFinishedMsg() {
- assert(socket == null);
-
- ((EngineOutputRecord)r).setFinishedMsg();
- }
+ //
+ // handshake output stream management functions
+ //
/*
* Put integers encoded in standard 8, 16, 24, and 32 bit
* big endian formats. Note that OutputStream.write(int) only
* writes the least significant 8 bits and ignores the rest.
*/
-
void putInt8(int i) throws IOException {
checkOverflow(i, Record.OVERFLOW_OF_INT08);
- r.write(i);
+ super.write(i);
}
void putInt16(int i) throws IOException {
checkOverflow(i, Record.OVERFLOW_OF_INT16);
- if (r.availableDataBytes() < 2) {
- flush();
- }
- r.write(i >> 8);
- r.write(i);
+ super.write(i >> 8);
+ super.write(i);
}
void putInt24(int i) throws IOException {
checkOverflow(i, Record.OVERFLOW_OF_INT24);
- if (r.availableDataBytes() < 3) {
- flush();
- }
- r.write(i >> 16);
- r.write(i >> 8);
- r.write(i);
- }
-
- void putInt32(int i) throws IOException {
- if (r.availableDataBytes() < 4) {
- flush();
- }
- r.write(i >> 24);
- r.write(i >> 16);
- r.write(i >> 8);
- r.write(i);
+ super.write(i >> 16);
+ super.write(i >> 8);
+ super.write(i);
}
/*
* Put byte arrays with length encoded as 8, 16, 24 bit
* integers in big-endian format.
*/
- void putBytes8(byte b[]) throws IOException {
+ void putBytes8(byte[] b) throws IOException {
if (b == null) {
putInt8(0);
- return;
} else {
- checkOverflow(b.length, Record.OVERFLOW_OF_INT08);
+ putInt8(b.length);
+ super.write(b, 0, b.length);
}
- putInt8(b.length);
- write(b, 0, b.length);
}
public void putBytes16(byte b[]) throws IOException {
if (b == null) {
putInt16(0);
- return;
} else {
- checkOverflow(b.length, Record.OVERFLOW_OF_INT16);
+ putInt16(b.length);
+ super.write(b, 0, b.length);
}
- putInt16(b.length);
- write(b, 0, b.length);
}
void putBytes24(byte b[]) throws IOException {
if (b == null) {
putInt24(0);
- return;
} else {
- checkOverflow(b.length, Record.OVERFLOW_OF_INT24);
+ putInt24(b.length);
+ super.write(b, 0, b.length);
}
- putInt24(b.length);
- write(b, 0, b.length);
}
- private void checkOverflow(int length, int overflow) {
- if (length >= overflow) {
+ /*
+ * Does the specified length overflow the limitation?
+ */
+ private static void checkOverflow(int length, int limit) {
+ if (length >= limit) {
// internal_error alert will be triggered
throw new RuntimeException(
"Field length overflow, the field length (" +
- length + ") should be less than " + overflow);
+ length + ") should be less than " + limit);
}
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HandshakeStateManager.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,925 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.util.LinkedList;
+import java.util.HashMap;
+import javax.net.ssl.SSLProtocolException;
+
+import sun.security.ssl.HandshakeMessage.*;
+
+import static sun.security.ssl.CipherSuite.KeyExchange;
+import static sun.security.ssl.CipherSuite.KeyExchange.*;
+import static sun.security.ssl.HandshakeStateManager.HandshakeState.*;
+import static sun.security.ssl.HandshakeMessage.*;
+
+/*
+ * Handshake state manager.
+ *
+ * Messages flow for a full handshake:
+ *
+ * - -
+ * | HelloRequest (No.0, RFC 5246) [*] |
+ * | <-------------------------------------------- |
+ * | |
+ * | ClientHello (No.1, RFC 5246) |
+ * | --------------------------------------------> |
+ * | |
+ * | - HelloVerifyRequest (No.3, RFC 6347) - |
+ * | D | <-------------------------------------------- | D |
+ * | T | | T |
+ * | L | ClientHello (No.1, RFC 5246) | L |
+ * | S | --------------------------------------------> | S |
+ * | - - |
+ * | |
+ * C | ServerHello (No.2, RFC 5246) | S
+ * L | SupplementalData (No.23, RFC4680) [*] | E
+ * I | Certificate (No.11, RFC 5246) [*] | R
+ * E | CertificateStatus (No.22, RFC 6066) [*] | V
+ * N | ServerKeyExchange (No.12, RFC 5246) [*] | E
+ * T | CertificateRequest (No.13, RFC 5246) [*] | R
+ * | ServerHelloDone (No.14, RFC 5246) |
+ * | <-------------------------------------------- |
+ * | |
+ * | SupplementalData (No.23, RFC4680) [*] |
+ * | Certificate (No.11, RFC 5246) [*] Or |
+ * | CertificateURL (No.21, RFC6066) [*] |
+ * | ClientKeyExchange (No.16, RFC 5246) |
+ * | CertificateVerify (No.15, RFC 5246) [*] |
+ * | [ChangeCipherSpec] (RFC 5246) |
+ * | Finished (No.20, RFC 5246) |
+ * | --------------------------------------------> |
+ * | |
+ * | NewSessionTicket (No.4, RFC4507) [*] |
+ * | [ChangeCipherSpec] (RFC 5246) |
+ * | Finished (No.20, RFC 5246) |
+ * | <-------------------------------------------- |
+ * - -
+ * [*] Indicates optional or situation-dependent messages that are not
+ * always sent.
+ *
+ * Message flow for an abbreviated handshake:
+ * - -
+ * | ClientHello (No.1, RFC 5246) |
+ * | --------------------------------------------> |
+ * | |
+ * C | ServerHello (No.2, RFC 5246) | S
+ * L | NewSessionTicket (No.4, RFC4507) [*] | E
+ * I | [ChangeCipherSpec] (RFC 5246) | R
+ * E | Finished (No.20, RFC 5246) | V
+ * N | <-------------------------------------------- | E
+ * T | | R
+ * | [ChangeCipherSpec] (RFC 5246) |
+ * | Finished (No.20, RFC 5246) |
+ * | --------------------------------------------> |
+ * - -
+ *
+ *
+ * State machine of handshake states:
+ *
+ * +--------------+
+ * START -----> | HelloRequest |
+ * | +--------------+
+ * | |
+ * v v
+ * +---------------------+ --> +---------------------+
+ * | ClientHello | | HelloVerifyRequest |
+ * +---------------------+ <-- +---------------------+
+ * |
+ * |
+ * =========================================================================
+ * |
+ * v
+ * +---------------------+
+ * | ServerHello | ----------------------------------+------+
+ * +---------------------+ --> +-------------------------+ | |
+ * | | Server SupplementalData | | |
+ * | +-------------------------+ | |
+ * | | | |
+ * v v | |
+ * +---------------------+ | |
+ * +---- | Server Certificate | | |
+ * | +---------------------+ | |
+ * | | | |
+ * | | +--------------------+ | |
+ * | +-> | CertificateStatus | | |
+ * | | +--------------------+ v |
+ * | | | | +--------------------+ |
+ * | v v +--> | ServerKeyExchange | |
+ * | +---------------------+ | +--------------------+ |
+ * | | CertificateRequest | | | |
+ * | +---------------------+ <-+---------+ |
+ * | | | | |
+ * v v | | |
+ * +---------------------+ <-------+ | |
+ * | ServerHelloDone | <-----------------+ |
+ * +---------------------+ |
+ * | | |
+ * | | |
+ * | | |
+ * =========================================================================
+ * | | |
+ * | v |
+ * | +-------------------------+ |
+ * | | Client SupplementalData | --------------+ |
+ * | +-------------------------+ | |
+ * | | | |
+ * | v | |
+ * | +--------------------+ | |
+ * +-> | Client Certificate | ALT. | |
+ * | +--------------------+----------------+ | |
+ * | | CertificateURL | | |
+ * | +----------------+ | |
+ * v | |
+ * +-------------------+ <------------------------+ |
+ * | ClientKeyExchange | |
+ * +-------------------+ |
+ * | | |
+ * | v |
+ * | +-------------------+ |
+ * | | CertificateVerify | |
+ * | +-------------------+ |
+ * | | |
+ * v v |
+ * +-------------------------+ |
+ * | Client ChangeCipherSpec | <---------------+ |
+ * +-------------------------+ | |
+ * | | |
+ * v | |
+ * +-----------------+ (abbreviated) | |
+ * | Client Finished | -------------> END | |
+ * +-----------------+ (Abbreviated handshake) | |
+ * | | |
+ * | (full) | |
+ * | | |
+ * ================================ | |
+ * | | |
+ * | ================================
+ * | | |
+ * v | |
+ * +------------------+ | (abbreviated) |
+ * | NewSessionTicket | <--------------------------------+
+ * +------------------+ | |
+ * | | |
+ * v | |
+ * +-------------------------+ | (abbreviated) |
+ * | Server ChangeCipherSpec | <-------------------------------------+
+ * +-------------------------+ |
+ * | |
+ * v |
+ * +-----------------+ (abbreviated) |
+ * | Server Finished | -------------------------+
+ * +-----------------+
+ * | (full)
+ * v
+ * END (Full handshake)
+ *
+ *
+ * The scenarios of the use of this class:
+ * 1. Create an instance of HandshakeStateManager during the initializtion
+ * handshake.
+ * 2. If receiving a handshake message, call HandshakeStateManager.check()
+ * to make sure that the message is of the expected handshake type. And
+ * then call HandshakeStateManager.update() in case handshake states may
+ * be impacted by this new incoming handshake message.
+ * 3. On delivering a handshake message, call HandshakeStateManager.update()
+ * in case handshake states may by thie new outgoing handshake message.
+ * 4. On receiving and delivering ChangeCipherSpec message, call
+ * HandshakeStateManager.changeCipherSpec() to check the present sequence
+ * of this message, and update the states if necessary.
+ */
+final class HandshakeStateManager {
+ // upcoming handshake states.
+ private LinkedList<HandshakeState> upcomingStates;
+ private LinkedList<HandshakeState> alternatives;
+
+ private boolean isDTLS;
+
+ private final static boolean debugIsOn;
+
+ private final static HashMap<Byte, String> handshakeTypes;
+
+ static {
+ debugIsOn = (Handshaker.debug != null) &&
+ Debug.isOn("handshake") && Debug.isOn("verbose");
+ handshakeTypes = new HashMap<>(15);
+
+ handshakeTypes.put(ht_hello_request, "hello_request");
+ handshakeTypes.put(ht_client_hello, "client_hello");
+ handshakeTypes.put(ht_server_hello, "server_hello");
+ handshakeTypes.put(ht_hello_verify_request, "hello_verify_request");
+ handshakeTypes.put(ht_new_session_ticket, "session_ticket");
+ handshakeTypes.put(ht_certificate, "certificate");
+ handshakeTypes.put(ht_server_key_exchange, "server_key_exchange");
+ handshakeTypes.put(ht_certificate_request, "certificate_request");
+ handshakeTypes.put(ht_server_hello_done, "server_hello_done");
+ handshakeTypes.put(ht_certificate_verify, "certificate_verify");
+ handshakeTypes.put(ht_client_key_exchange, "client_key_exchange");
+ handshakeTypes.put(ht_finished, "finished");
+ handshakeTypes.put(ht_certificate_url, "certificate_url");
+ handshakeTypes.put(ht_certificate_status, "certificate_status");
+ handshakeTypes.put(ht_supplemental_data, "supplemental_data");
+ }
+
+ HandshakeStateManager(boolean isDTLS) {
+ this.upcomingStates = new LinkedList<>();
+ this.alternatives = new LinkedList<>();
+ this.isDTLS = isDTLS;
+ }
+
+ //
+ // enumation of handshake type
+ //
+ static enum HandshakeState {
+ HS_HELLO_REQUEST(
+ "hello_request",
+ HandshakeMessage.ht_hello_request),
+ HS_CLIENT_HELLO(
+ "client_hello",
+ HandshakeMessage.ht_client_hello),
+ HS_HELLO_VERIFY_REQUEST(
+ "hello_verify_request",
+ HandshakeMessage.ht_hello_verify_request),
+ HS_SERVER_HELLO(
+ "server_hello",
+ HandshakeMessage.ht_server_hello),
+ HS_SERVER_SUPPLEMENTAL_DATA(
+ "server supplemental_data",
+ HandshakeMessage.ht_supplemental_data, true),
+ HS_SERVER_CERTIFICATE(
+ "server certificate",
+ HandshakeMessage.ht_certificate),
+ HS_CERTIFICATE_STATUS(
+ "certificate_status",
+ HandshakeMessage.ht_certificate_status, true),
+ HS_SERVER_KEY_EXCHANGE(
+ "server_key_exchange",
+ HandshakeMessage.ht_server_key_exchange, true),
+ HS_CERTIFICATE_REQUEST(
+ "certificate_request",
+ HandshakeMessage.ht_certificate_request, true),
+ HS_SERVER_HELLO_DONE(
+ "server_hello_done",
+ HandshakeMessage.ht_server_hello_done),
+ HS_CLIENT_SUPPLEMENTAL_DATA(
+ "client supplemental_data",
+ HandshakeMessage.ht_supplemental_data, true),
+ HS_CLIENT_CERTIFICATE(
+ "client certificate",
+ HandshakeMessage.ht_certificate, true),
+ HS_CERTIFICATE_URL(
+ "certificate_url",
+ HandshakeMessage.ht_certificate_url, true),
+ HS_CLIENT_KEY_EXCHANGE(
+ "client_key_exchange",
+ HandshakeMessage.ht_client_key_exchange),
+ HS_CERTIFICATE_VERIFY(
+ "certificate_verify",
+ HandshakeMessage.ht_certificate_verify, true),
+ HS_CLIENT_CHANGE_CIPHER_SPEC(
+ "client change_cipher_spec",
+ HandshakeMessage.ht_not_applicable),
+ HS_CLEINT_FINISHED(
+ "client finished",
+ HandshakeMessage.ht_finished),
+ HS_NEW_SESSION_TICKET(
+ "session_ticket",
+ HandshakeMessage.ht_new_session_ticket),
+ HS_SERVER_CHANGE_CIPHER_SPEC(
+ "server change_cipher_spec",
+ HandshakeMessage.ht_not_applicable),
+ HS_SERVER_FINISHDE(
+ "server finished",
+ HandshakeMessage.ht_finished);
+
+ final String description;
+ final byte handshakeType;
+ final boolean isOptional;
+
+ HandshakeState(String description, byte handshakeType) {
+ this.description = description;
+ this.handshakeType = handshakeType;
+ this.isOptional = false;
+ }
+
+ HandshakeState(String description,
+ byte handshakeType, boolean isOptional) {
+
+ this.description = description;
+ this.handshakeType = handshakeType;
+ this.isOptional = isOptional;
+ }
+
+ public String toString() {
+ return description + "[" + handshakeType + "]" +
+ (isOptional ? "(optional)" : "");
+ }
+ }
+
+ boolean isEmpty() {
+ return upcomingStates.isEmpty();
+ }
+
+ void check(byte handshakeType) throws SSLProtocolException {
+ String exceptionMsg =
+ "Handshake message sequence violation, " + handshakeType;
+
+ if (debugIsOn) {
+ System.out.println(
+ "check handshake state: " + toString(handshakeType));
+ }
+
+ if (upcomingStates.isEmpty()) {
+ // Is it a kickstart message?
+ if ((handshakeType != HandshakeMessage.ht_hello_request) &&
+ (handshakeType != HandshakeMessage.ht_client_hello)) {
+
+ throw new SSLProtocolException(
+ "Handshake message sequence violation, " + handshakeType);
+ }
+
+ // It is a kickstart message.
+ return;
+ }
+
+ // Ignore the checking for HelloRequest messages as they are
+ // may be sent by the server at any time.
+ if (handshakeType == HandshakeMessage.ht_hello_request) {
+ return;
+ }
+
+ for (HandshakeState handshakeState : upcomingStates) {
+ if (handshakeState.handshakeType == handshakeType) {
+ // It's the expected next handshake type.
+ return;
+ }
+
+ if (handshakeState.isOptional) {
+ continue;
+ } else {
+ for (HandshakeState alternative : alternatives) {
+ if (alternative.handshakeType == handshakeType) {
+ return;
+ }
+
+ if (alternative.isOptional) {
+ continue;
+ } else {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ }
+ }
+
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // Not an expected Handshake message.
+ throw new SSLProtocolException(
+ "Handshake message sequence violation, " + handshakeType);
+ }
+
+ void update(HandshakeMessage handshakeMessage,
+ boolean isAbbreviated) throws SSLProtocolException {
+
+ byte handshakeType = (byte)handshakeMessage.messageType();
+ String exceptionMsg =
+ "Handshake message sequence violation, " + handshakeType;
+
+ if (debugIsOn) {
+ System.out.println(
+ "update handshake state: " + toString(handshakeType));
+ }
+
+ boolean hasPresentState = false;
+ switch (handshakeType) {
+ case HandshakeMessage.ht_hello_request:
+ //
+ // State machine:
+ // PRESENT: START
+ // TO : ClientHello
+ //
+
+ // No old state to update.
+
+ // Add the upcoming states.
+ if (!upcomingStates.isEmpty()) {
+ // A ClientHello message should be followed.
+ upcomingStates.add(HS_CLIENT_HELLO);
+
+ } // Otherwise, ignore this HelloRequest message.
+
+ break;
+
+ case HandshakeMessage.ht_client_hello:
+ //
+ // State machine:
+ // PRESENT: START
+ // HS_CLIENT_HELLO
+ // TO : HS_HELLO_VERIFY_REQUEST (DTLS)
+ // HS_SERVER_HELLO
+ //
+
+ // Check and update the present state.
+ if (!upcomingStates.isEmpty()) {
+ // The current state should be HS_CLIENT_HELLO.
+ HandshakeState handshakeState = upcomingStates.pop();
+ if (handshakeState != HS_CLIENT_HELLO) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ }
+
+ // Add the upcoming states.
+ ClientHello clientHello = (ClientHello)handshakeMessage;
+ if (isDTLS) {
+ // Is it an initial ClientHello message?
+ if (clientHello.cookie == null ||
+ clientHello.cookie.length == 0) {
+ // Is it an abbreviated handshake?
+ if (clientHello.sessionId.length() != 0) {
+ // A HelloVerifyRequest message or a ServerHello
+ // message may follow the abbreviated session
+ // resuming handshake request.
+ upcomingStates.add(HS_HELLO_VERIFY_REQUEST);
+ alternatives.add(HS_SERVER_HELLO);
+ } else {
+ // A HelloVerifyRequest message should follow
+ // the initial ClientHello message.
+ upcomingStates.add(HS_HELLO_VERIFY_REQUEST);
+ }
+ } else {
+ // A HelloVerifyRequest may be followed if the cookie
+ // cannot be verified.
+ upcomingStates.add(HS_SERVER_HELLO);
+ alternatives.add(HS_HELLO_VERIFY_REQUEST);
+ }
+ } else {
+ upcomingStates.add(HS_SERVER_HELLO);
+ }
+
+ break;
+
+ case HandshakeMessage.ht_hello_verify_request:
+ //
+ // State machine:
+ // PRESENT: HS_HELLO_VERIFY_REQUEST
+ // TO : HS_CLIENT_HELLO
+ //
+ // Note that this state may have an alternative option.
+
+ // Check and update the present state.
+ if (!upcomingStates.isEmpty()) {
+ // The current state should be HS_HELLO_VERIFY_REQUEST.
+ HandshakeState handshakeState = upcomingStates.pop();
+ HandshakeState alternative = null;
+ if (!alternatives.isEmpty()) {
+ alternative = alternatives.pop();
+ }
+
+ if ((handshakeState != HS_HELLO_VERIFY_REQUEST) &&
+ (alternative != HS_HELLO_VERIFY_REQUEST)) {
+
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ } else {
+ // No present state.
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // Add the upcoming states.
+ upcomingStates.add(HS_CLIENT_HELLO);
+
+ break;
+
+ case HandshakeMessage.ht_server_hello:
+ //
+ // State machine:
+ // PRESENT: HS_SERVER_HELLO
+ // TO :
+ // Full handshake state stacks
+ // (ServerHello Flight)
+ // HS_SERVER_SUPPLEMENTAL_DATA [optional]
+ // --> HS_SERVER_CERTIFICATE [optional]
+ // --> HS_CERTIFICATE_STATUS [optional]
+ // --> HS_SERVER_KEY_EXCHANGE [optional]
+ // --> HS_CERTIFICATE_REQUEST [optional]
+ // --> HS_SERVER_HELLO_DONE
+ // (Client ClientKeyExchange Flight)
+ // --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
+ // --> HS_CLIENT_CERTIFICATE or
+ // HS_CERTIFICATE_URL
+ // --> HS_CLIENT_KEY_EXCHANGE
+ // --> HS_CERTIFICATE_VERIFY [optional]
+ // --> HS_CLIENT_CHANGE_CIPHER_SPEC
+ // --> HS_CLEINT_FINISHED
+ // (Server Finished Flight)
+ // --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
+ //
+ // Abbreviated handshake state stacks
+ // (Server Finished Flight)
+ // HS_NEW_SESSION_TICKET
+ // --> HS_SERVER_CHANGE_CIPHER_SPEC
+ // --> HS_SERVER_FINISHDE
+ // (Client Finished Flight)
+ // --> HS_CLIENT_CHANGE_CIPHER_SPEC
+ // --> HS_CLEINT_FINISHED
+ //
+ // Note that this state may have an alternative option.
+
+ // Check and update the present state.
+ if (!upcomingStates.isEmpty()) {
+ // The current state should be HS_SERVER_HELLO
+ HandshakeState handshakeState = upcomingStates.pop();
+ HandshakeState alternative = null;
+ if (!alternatives.isEmpty()) {
+ alternative = alternatives.pop();
+ }
+
+ if ((handshakeState != HS_SERVER_HELLO) &&
+ (alternative != HS_SERVER_HELLO)) {
+
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ } else {
+ // No present state.
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // Add the upcoming states.
+ ServerHello serverHello = (ServerHello)handshakeMessage;
+ HelloExtensions hes = serverHello.extensions;
+
+
+ // Not support SessionTicket extension yet.
+ //
+ // boolean hasSessionTicketExt =
+ // (hes.get(HandshakeMessage.ht_new_session_ticket) != null);
+
+ if (isAbbreviated) {
+ // Not support SessionTicket extension yet.
+ //
+ // // Mandatory NewSessionTicket message
+ // if (hasSessionTicketExt) {
+ // upcomingStates.add(HS_NEW_SESSION_TICKET);
+ // }
+
+ // Mandatory server ChangeCipherSpec and Finished messages
+ upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
+ upcomingStates.add(HS_SERVER_FINISHDE);
+
+ // Mandatory client ChangeCipherSpec and Finished messages
+ upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
+ upcomingStates.add(HS_CLEINT_FINISHED);
+ } else {
+ // Not support SupplementalData extension yet.
+ //
+ // boolean hasSupplementalDataExt =
+ // (hes.get(HandshakeMessage.ht_supplemental_data) != null);
+
+ // Not support CertificateStatus extension yet.
+ //
+ // boolean hasCertificateStatusExt =
+ // (hes.get(HandshakeMessage.ht_certificate_status) != null);
+
+ // Not support CertificateURL extension yet.
+ //
+ // boolean hasCertificateUrlExt =
+ // (hes.get(HandshakeMessage.ht_certificate_url) != null);
+
+ // Not support SupplementalData extension yet.
+ //
+ // // Optional SupplementalData message
+ // if (hasSupplementalDataExt) {
+ // upcomingStates.add(HS_SERVER_SUPPLEMENTAL_DATA);
+ // }
+
+ // Need server Certificate message or not?
+ KeyExchange keyExchange = serverHello.cipherSuite.keyExchange;
+ if ((keyExchange != K_KRB5) &&
+ (keyExchange != K_KRB5_EXPORT) &&
+ (keyExchange != K_DH_ANON) &&
+ (keyExchange != K_ECDH_ANON)) {
+ // Mandatory Certificate message
+ upcomingStates.add(HS_SERVER_CERTIFICATE);
+ }
+
+ // Not support CertificateStatus extension yet.
+ //
+ // // Optional CertificateStatus message
+ // if (hasCertificateStatusExt) {
+ // upcomingStates.add(HS_CERTIFICATE_STATUS);
+ // }
+
+ // Need ServerKeyExchange message or not?
+ if ((keyExchange == K_RSA_EXPORT) ||
+ (keyExchange == K_DHE_RSA) ||
+ (keyExchange == K_DHE_DSS) ||
+ (keyExchange == K_DH_ANON) ||
+ (keyExchange == K_ECDHE_RSA) ||
+ (keyExchange == K_ECDHE_ECDSA) ||
+ (keyExchange == K_ECDH_ANON)) {
+ // Optional ServerKeyExchange message
+ upcomingStates.add(HS_SERVER_KEY_EXCHANGE);
+ }
+
+ // Optional CertificateRequest message
+ upcomingStates.add(HS_CERTIFICATE_REQUEST);
+
+ // Mandatory ServerHelloDone message
+ upcomingStates.add(HS_SERVER_HELLO_DONE);
+
+ // Not support SupplementalData extension yet.
+ //
+ // // Optional SupplementalData message
+ // if (hasSupplementalDataExt) {
+ // upcomingStates.add(HS_CLIENT_SUPPLEMENTAL_DATA);
+ // }
+
+ // Optional client Certificate message
+ upcomingStates.add(HS_CLIENT_CERTIFICATE);
+
+ // Not support CertificateURL extension yet.
+ //
+ // // Alternative CertificateURL message, optional too.
+ // //
+ // // Please put CertificateURL rather than Certificate
+ // // message in the alternatives list. So that we can
+ // // simplify the process of this alternative pair later.
+ // if (hasCertificateUrlExt) {
+ // alternatives.add(HS_CERTIFICATE_URL);
+ // }
+
+ // Mandatory ClientKeyExchange message
+ upcomingStates.add(HS_CLIENT_KEY_EXCHANGE);
+
+ // Optional CertificateVerify message
+ upcomingStates.add(HS_CERTIFICATE_VERIFY);
+
+ // Mandatory client ChangeCipherSpec and Finished messages
+ upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
+ upcomingStates.add(HS_CLEINT_FINISHED);
+
+ // Not support SessionTicket extension yet.
+ //
+ // // Mandatory NewSessionTicket message
+ // if (hasSessionTicketExt) {
+ // upcomingStates.add(HS_NEW_SESSION_TICKET);
+ // }
+
+ // Mandatory server ChangeCipherSpec and Finished messages
+ upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
+ upcomingStates.add(HS_SERVER_FINISHDE);
+ }
+
+ break;
+
+ case HandshakeMessage.ht_certificate:
+ //
+ // State machine:
+ // PRESENT: HS_CERTIFICATE_URL or
+ // HS_CLIENT_CERTIFICATE
+ // TO : HS_CLIENT_KEY_EXCHANGE
+ //
+ // Or
+ //
+ // PRESENT: HS_SERVER_CERTIFICATE
+ // TO : HS_CERTIFICATE_STATUS [optional]
+ // HS_SERVER_KEY_EXCHANGE [optional]
+ // HS_CERTIFICATE_REQUEST [optional]
+ // HS_SERVER_HELLO_DONE
+ //
+ // Note that this state may have an alternative option.
+
+ // Check and update the present state.
+ while (!upcomingStates.isEmpty()) {
+ HandshakeState handshakeState = upcomingStates.pop();
+ if (handshakeState.handshakeType == handshakeType) {
+ hasPresentState = true;
+
+ // The current state should be HS_CLIENT_CERTIFICATE or
+ // HS_SERVER_CERTIFICATE.
+ //
+ // Note that we won't put HS_CLIENT_CERTIFICATE into
+ // the alternative list.
+ if ((handshakeState != HS_CLIENT_CERTIFICATE) &&
+ (handshakeState != HS_SERVER_CERTIFICATE)) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // Is it an expected client Certificate message?
+ boolean isClientMessage = false;
+ if (!upcomingStates.isEmpty()) {
+ // If the next expected message is ClientKeyExchange,
+ // this one should be an expected client Certificate
+ // message.
+ HandshakeState nextState = upcomingStates.getFirst();
+ if (nextState == HS_CLIENT_KEY_EXCHANGE) {
+ isClientMessage = true;
+ }
+ }
+
+ if (isClientMessage) {
+ if (handshakeState != HS_CLIENT_CERTIFICATE) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // Not support CertificateURL extension yet.
+ /*******************************************
+ // clear up the alternatives list
+ if (!alternatives.isEmpty()) {
+ HandshakeState alternative = alternatives.pop();
+
+ if (alternative != HS_CERTIFICATE_URL) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ }
+ ********************************************/
+ } else {
+ if ((handshakeState != HS_SERVER_CERTIFICATE)) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ }
+
+ break;
+ } else if (!handshakeState.isOptional) {
+ throw new SSLProtocolException(exceptionMsg);
+ } // Otherwise, looking for next state track.
+ }
+
+ // No present state.
+ if (!hasPresentState) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // no new upcoming states.
+
+ break;
+
+ // Not support CertificateURL extension yet.
+ /*************************************************/
+ case HandshakeMessage.ht_certificate_url:
+ //
+ // State machine:
+ // PRESENT: HS_CERTIFICATE_URL or
+ // HS_CLIENT_CERTIFICATE
+ // TO : HS_CLIENT_KEY_EXCHANGE
+ //
+ // Note that this state may have an alternative option.
+
+ // Check and update the present state.
+ while (!upcomingStates.isEmpty()) {
+ // The current state should be HS_CLIENT_CERTIFICATE.
+ //
+ // Note that we won't put HS_CLIENT_CERTIFICATE into
+ // the alternative list.
+ HandshakeState handshakeState = upcomingStates.pop();
+ if (handshakeState.handshakeType ==
+ HS_CLIENT_CERTIFICATE.handshakeType) {
+ hasPresentState = true;
+
+ // Look for HS_CERTIFICATE_URL state track.
+ if (!alternatives.isEmpty()) {
+ HandshakeState alternative = alternatives.pop();
+
+ if (alternative != HS_CERTIFICATE_URL) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+ } else {
+ // No alternative CertificateUR state track.
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ if ((handshakeState != HS_CLIENT_CERTIFICATE)) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ break;
+ } else if (!handshakeState.isOptional) {
+ throw new SSLProtocolException(exceptionMsg);
+ } // Otherwise, looking for next state track.
+
+ }
+
+ // No present state.
+ if (!hasPresentState) {
+ // No present state.
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // no new upcoming states.
+
+ break;
+ /*************************************************/
+
+ default:
+ // Check and update the present state.
+ while (!upcomingStates.isEmpty()) {
+ HandshakeState handshakeState = upcomingStates.pop();
+ if (handshakeState.handshakeType == handshakeType) {
+ hasPresentState = true;
+ break;
+ } else if (!handshakeState.isOptional) {
+ throw new SSLProtocolException(exceptionMsg);
+ } // Otherwise, looking for next state track.
+ }
+
+ // No present state.
+ if (!hasPresentState) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // no new upcoming states.
+ }
+
+ if (debugIsOn) {
+ for (HandshakeState handshakeState : upcomingStates) {
+ System.out.println(
+ "upcoming handshake states: " + handshakeState);
+ }
+ for (HandshakeState handshakeState : alternatives) {
+ System.out.println(
+ "upcoming handshake alternative state: " + handshakeState);
+ }
+ }
+ }
+
+ void changeCipherSpec(boolean isInput,
+ boolean isClient) throws SSLProtocolException {
+
+ if (debugIsOn) {
+ System.out.println(
+ "update handshake state: change_cipher_spec");
+ }
+
+ String exceptionMsg = "ChangeCipherSpec message sequence violation";
+
+ HandshakeState expectedState;
+ if ((isClient && isInput) || (!isClient && !isInput)) {
+ expectedState = HS_SERVER_CHANGE_CIPHER_SPEC;
+ } else {
+ expectedState = HS_CLIENT_CHANGE_CIPHER_SPEC;
+ }
+
+ boolean hasPresentState = false;
+
+ // Check and update the present state.
+ while (!upcomingStates.isEmpty()) {
+ HandshakeState handshakeState = upcomingStates.pop();
+ if (handshakeState == expectedState) {
+ hasPresentState = true;
+ break;
+ } else if (!handshakeState.isOptional) {
+ throw new SSLProtocolException(exceptionMsg);
+ } // Otherwise, looking for next state track.
+ }
+
+ // No present state.
+ if (!hasPresentState) {
+ throw new SSLProtocolException(exceptionMsg);
+ }
+
+ // no new upcoming states.
+
+ if (debugIsOn) {
+ for (HandshakeState handshakeState : upcomingStates) {
+ System.out.println(
+ "upcoming handshake states: " + handshakeState);
+ }
+ for (HandshakeState handshakeState : alternatives) {
+ System.out.println(
+ "upcoming handshake alternative state: " + handshakeState);
+ }
+ }
+ }
+
+ private static String toString(byte handshakeType) {
+ String s = handshakeTypes.get(handshakeType);
+ if (s == null) {
+ s = "unknown";
+ }
+ return (s + "[" + handshakeType + "]");
+ }
+}
+
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Handshaker.java Tue Jun 02 09:15:47 2015 -0700
@@ -29,6 +29,7 @@
import java.io.*;
import java.util.*;
import java.security.*;
+import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.AccessController;
import java.security.AlgorithmConstraints;
@@ -83,7 +84,7 @@
private CipherSuiteList enabledCipherSuites;
// The endpoint identification protocol
- String identificationProtocol;
+ String identificationProtocol;
// The cryptographic algorithm constraints
private AlgorithmConstraints algorithmConstraints = null;
@@ -109,12 +110,15 @@
* Active cipher suites is a subset of enabled cipher suites, and will
* contain only those cipher suites available for the active protocols.
*/
- private CipherSuiteList activeCipherSuites;
+ private CipherSuiteList activeCipherSuites;
// The server name indication and matchers
List<SNIServerName> serverNames = Collections.<SNIServerName>emptyList();
Collection<SNIMatcher> sniMatchers = Collections.<SNIMatcher>emptyList();
+ // The maximum expected network packet size for SSL/TLS/DTLS records.
+ int maximumPacketSize = 0;
+
private boolean isClient;
private boolean needCertVerify;
@@ -124,11 +128,16 @@
HandshakeHash handshakeHash;
HandshakeInStream input;
HandshakeOutStream output;
- int state;
SSLContextImpl sslContext;
RandomCookie clnt_random, svr_random;
SSLSessionImpl session;
+ HandshakeStateManager handshakeState;
+ boolean clientHelloDelivered;
+ boolean serverHelloRequested;
+ boolean handshakeActivated;
+ boolean handshakeFinished;
+
// current CipherSuite. Never null, initially SSL_NULL_WITH_NULL_NULL
CipherSuite cipherSuite;
@@ -141,10 +150,6 @@
// True if it's OK to start a new SSL session
boolean enableNewSession;
- // True if session keys have been calculated and the caller may receive
- // and process a ChangeCipherSpec message
- private boolean sessKeysCalculated;
-
// Whether local cipher suites preference should be honored during
// handshaking?
//
@@ -207,12 +212,18 @@
// need to dispose the object when it is invalidated
boolean invalidated;
+ /*
+ * Is this an instance for Datagram Transport Layer Security (DTLS)?
+ */
+ final boolean isDTLS;
+
Handshaker(SSLSocketImpl c, SSLContextImpl context,
ProtocolList enabledProtocols, boolean needCertVerify,
boolean isClient, ProtocolVersion activeProtocolVersion,
boolean isInitialHandshake, boolean secureRenegotiation,
byte[] clientVerifyData, byte[] serverVerifyData) {
this.conn = c;
+ this.isDTLS = false;
init(context, enabledProtocols, needCertVerify, isClient,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData);
@@ -222,8 +233,10 @@
ProtocolList enabledProtocols, boolean needCertVerify,
boolean isClient, ProtocolVersion activeProtocolVersion,
boolean isInitialHandshake, boolean secureRenegotiation,
- byte[] clientVerifyData, byte[] serverVerifyData) {
+ byte[] clientVerifyData, byte[] serverVerifyData,
+ boolean isDTLS) {
this.engine = engine;
+ this.isDTLS = isDTLS;
init(context, enabledProtocols, needCertVerify, isClient,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData);
@@ -251,9 +264,13 @@
this.secureRenegotiation = secureRenegotiation;
this.clientVerifyData = clientVerifyData;
this.serverVerifyData = serverVerifyData;
- enableNewSession = true;
- invalidated = false;
- sessKeysCalculated = false;
+ this.enableNewSession = true;
+ this.invalidated = false;
+ this.handshakeState = new HandshakeStateManager(isDTLS);
+ this.clientHelloDelivered = false;
+ this.serverHelloRequested = false;
+ this.handshakeActivated = false;
+ this.handshakeFinished = false;
setCipherSuite(CipherSuite.C_NULL);
setEnabledProtocols(enabledProtocols);
@@ -263,22 +280,6 @@
} else { // engine != null
algorithmConstraints = new SSLAlgorithmConstraints(engine, true);
}
-
-
- //
- // In addition to the connection state machine, controlling
- // how the connection deals with the different sorts of records
- // that get sent (notably handshake transitions!), there's
- // also a handshaking state machine that controls message
- // sequencing.
- //
- // It's a convenient artifact of the protocol that this can,
- // with only a couple of minor exceptions, be driven by the
- // type constant for the last message seen: except for the
- // client's cert verify, those constants are in a convenient
- // order to drastically simplify state machine checking.
- //
- state = -2; // initialized but not activated
}
/*
@@ -360,14 +361,6 @@
}
}
- final boolean receivedChangeCipherSpec() {
- if (conn != null) {
- return conn.receivedChangeCipherSpec();
- } else {
- return engine.receivedChangeCipherSpec();
- }
- }
-
String getEndpointIdentificationAlgorithmSE() {
SSLParameters paras;
if (conn != null) {
@@ -395,8 +388,6 @@
void setVersion(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
setVersionSE(protocolVersion);
-
- output.r.setVersion(protocolVersion);
}
/**
@@ -483,6 +474,13 @@
}
/**
+ * Sets the maximum packet size of the handshaking.
+ */
+ void setMaximumPacketSize(int maximumPacketSize) {
+ this.maximumPacketSize = maximumPacketSize;
+ }
+
+ /**
* Sets the cipher suites preference.
*/
void setUseCipherSuitesOrder(boolean on) {
@@ -532,23 +530,29 @@
handshakeHash = new HandshakeHash(needCertVerify);
// Generate handshake input/output stream.
- input = new HandshakeInStream(handshakeHash);
if (conn != null) {
- output = new HandshakeOutStream(protocolVersion, helloVersion,
- handshakeHash, conn);
- conn.getAppInputStream().r.setHandshakeHash(handshakeHash);
- conn.getAppInputStream().r.setHelloVersion(helloVersion);
- conn.getAppOutputStream().r.setHelloVersion(helloVersion);
- } else {
- output = new HandshakeOutStream(protocolVersion, helloVersion,
- handshakeHash, engine);
+ input = new HandshakeInStream();
+ output = new HandshakeOutStream(conn.outputRecord);
+
+ conn.inputRecord.setHandshakeHash(handshakeHash);
+ conn.inputRecord.setHelloVersion(helloVersion);
+
+ conn.outputRecord.setHandshakeHash(handshakeHash);
+ conn.outputRecord.setHelloVersion(helloVersion);
+ conn.outputRecord.setVersion(protocolVersion);
+ } else if (engine != null) {
+ input = new HandshakeInStream();
+ output = new HandshakeOutStream(engine.outputRecord);
+
engine.inputRecord.setHandshakeHash(handshakeHash);
engine.inputRecord.setHelloVersion(helloVersion);
+
+ engine.outputRecord.setHandshakeHash(handshakeHash);
engine.outputRecord.setHelloVersion(helloVersion);
+ engine.outputRecord.setVersion(protocolVersion);
}
- // move state to activated
- state = -1;
+ handshakeActivated = true;
}
/**
@@ -637,15 +641,15 @@
if (!(activeProtocols.collection().isEmpty()) &&
activeProtocols.min.v != ProtocolVersion.NONE.v) {
for (CipherSuite suite : enabledCipherSuites.collection()) {
- if (suite.obsoleted > activeProtocols.min.v &&
- suite.supported <= activeProtocols.max.v) {
+ if (!activeProtocols.min.obsoletes(suite) &&
+ activeProtocols.max.supports(suite)) {
if (algorithmConstraints.permits(
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
suite.name, null)) {
suites.add(suite);
}
} else if (debug != null && Debug.isOn("verbose")) {
- if (suite.obsoleted <= activeProtocols.min.v) {
+ if (activeProtocols.min.obsoletes(suite)) {
System.out.println(
"Ignoring obsoleted cipher suite: " + suite);
} else {
@@ -700,8 +704,8 @@
boolean found = false;
for (CipherSuite suite : enabledCipherSuites.collection()) {
- if (suite.isAvailable() && suite.obsoleted > protocol.v &&
- suite.supported <= protocol.v) {
+ if (suite.isAvailable() && (!protocol.obsoletes(suite)) &&
+ protocol.supports(suite)) {
if (algorithmConstraints.permits(
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
suite.name, null)) {
@@ -837,7 +841,7 @@
* this freshly created session can become the current one.
*/
boolean isDone() {
- return state == HandshakeMessage.ht_finished;
+ return started() && handshakeState.isEmpty() && handshakeFinished;
}
@@ -861,6 +865,14 @@
}
}
+ void expectingFinishFlightSE() {
+ if (conn != null) {
+ conn.expectingFinishFlight();
+ } else {
+ engine.expectingFinishFlight();
+ }
+ }
+
/*
* Returns true if renegotiation is in use for this connection.
*/
@@ -886,8 +898,8 @@
* This routine is fed SSL handshake records when they become available,
* and processes messages found therein.
*/
- void process_record(InputRecord r, boolean expectingFinished)
- throws IOException {
+ void processRecord(ByteBuffer record,
+ boolean expectingFinished) throws IOException {
checkThrown();
@@ -895,7 +907,7 @@
* Store the incoming handshake data, then see if we can
* now process any completed handshake messages
*/
- input.incomingRecord(r);
+ input.incomingRecord(record);
/*
* We don't need to create a separate delegatable task
@@ -946,6 +958,13 @@
return;
}
+ // Set the flags in the message receiving side.
+ if (messageType == HandshakeMessage.ht_client_hello) {
+ clientHelloDelivered = true;
+ } else if (messageType == HandshakeMessage.ht_hello_request) {
+ serverHelloRequested = true;
+ }
+
/*
* Process the message. We require
* that processMessage() consumes the entire message. In
@@ -961,14 +980,16 @@
* Also, note that hello request messages are never hashed;
* that includes the hello request header, too.
*/
- if (messageType == HandshakeMessage.ht_hello_request) {
- input.reset();
- processMessage(messageType, messageLen);
- input.ignore(4 + messageLen);
- } else {
- input.mark(messageLen);
- processMessage(messageType, messageLen);
- input.digestNow();
+ processMessage(messageType, messageLen);
+
+ // Reload if this message has been reserved.
+ //
+ // Note: in the implementation, only certificate_verify and
+ // finished messages are reserved.
+ if ((messageType == HandshakeMessage.ht_finished) ||
+ (messageType == HandshakeMessage.ht_certificate_verify)) {
+
+ handshakeHash.reload();
}
}
}
@@ -980,29 +1001,29 @@
* In activated state, the handshaker may not send any messages out.
*/
boolean activated() {
- return state >= -1;
+ return handshakeActivated;
}
/**
* Returns true iff the handshaker has sent any messages.
*/
boolean started() {
- return state >= 0; // 0: HandshakeMessage.ht_hello_request
- // 1: HandshakeMessage.ht_client_hello
+ return (serverHelloRequested || clientHelloDelivered);
}
-
/*
* Used to kickstart the negotiation ... either writing a
* ClientHello or a HelloRequest as appropriate, whichever
* the subclass returns. NOP if handshaking's already started.
*/
void kickstart() throws IOException {
- if (state >= 0) {
+ if ((isClient && clientHelloDelivered) ||
+ (!isClient && serverHelloRequested)) {
return;
}
HandshakeMessage m = getKickstartMessage();
+ handshakeState.update(m, resumingSession);
if (debug != null && Debug.isOn("handshake")) {
m.print(System.out);
@@ -1010,7 +1031,13 @@
m.write(output);
output.flush();
- state = m.messageType();
+ // Set the flags in the message delivering side.
+ int handshakeType = m.messageType();
+ if (handshakeType == HandshakeMessage.ht_hello_request) {
+ serverHelloRequested = true;
+ } else { // HandshakeMessage.ht_client_hello
+ clientHelloDelivered = true;
+ }
}
/**
@@ -1052,24 +1079,16 @@
* We already hold SSLEngine/SSLSocket "this" by virtue
* of this being called from the readRecord code.
*/
- OutputRecord r;
- if (conn != null) {
- r = new OutputRecord(Record.ct_change_cipher_spec);
- } else {
- r = new EngineOutputRecord(Record.ct_change_cipher_spec, engine);
- }
-
- r.setVersion(protocolVersion);
- r.write(1); // single byte of data
-
if (conn != null) {
conn.writeLock.lock();
try {
- conn.writeRecord(r);
+ handshakeState.changeCipherSpec(false, isClient);
conn.changeWriteCiphers();
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
}
+
+ handshakeState.update(mesg, resumingSession);
mesg.write(output);
output.flush();
} finally {
@@ -1077,19 +1096,25 @@
}
} else {
synchronized (engine.writeLock) {
- engine.writeRecord((EngineOutputRecord)r);
+ handshakeState.changeCipherSpec(false, isClient);
engine.changeWriteCiphers();
if (debug != null && Debug.isOn("handshake")) {
mesg.print(System.out);
}
+
+ handshakeState.update(mesg, resumingSession);
mesg.write(output);
-
- if (lastMessage) {
- output.setFinishedMsg();
- }
output.flush();
}
}
+
+ if (lastMessage) {
+ handshakeFinished = true;
+ }
+ }
+
+ void receiveChangeCipherSpec() throws IOException {
+ handshakeState.changeCipherSpec(true, isClient);
}
/*
@@ -1131,12 +1156,31 @@
String masterAlg;
PRF prf;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
- masterAlg = "SunTls12MasterSecret";
- prf = cipherSuite.prfAlg;
+ byte majorVersion = protocolVersion.major;
+ byte minorVersion = protocolVersion.minor;
+ if (protocolVersion.isDTLSProtocol()) {
+ // Use TLS version number for DTLS key calculation
+ if (protocolVersion.v == ProtocolVersion.DTLS10.v) {
+ majorVersion = ProtocolVersion.TLS11.major;
+ minorVersion = ProtocolVersion.TLS11.minor;
+
+ masterAlg = "SunTlsMasterSecret";
+ prf = P_NONE;
+ } else { // DTLS 1.2
+ majorVersion = ProtocolVersion.TLS12.major;
+ minorVersion = ProtocolVersion.TLS12.minor;
+
+ masterAlg = "SunTls12MasterSecret";
+ prf = cipherSuite.prfAlg;
+ }
} else {
- masterAlg = "SunTlsMasterSecret";
- prf = P_NONE;
+ if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ masterAlg = "SunTls12MasterSecret";
+ prf = cipherSuite.prfAlg;
+ } else {
+ masterAlg = "SunTlsMasterSecret";
+ prf = P_NONE;
+ }
}
String prfHashAlg = prf.getPRFHashAlg();
@@ -1145,7 +1189,7 @@
@SuppressWarnings("deprecation")
TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec(
- preMasterSecret, protocolVersion.major, protocolVersion.minor,
+ preMasterSecret, (majorVersion & 0xFF), (minorVersion & 0xFF),
clnt_random.random_bytes, svr_random.random_bytes,
prfHashAlg, prfHashLength, prfBlockSize);
@@ -1196,36 +1240,55 @@
String keyMaterialAlg;
PRF prf;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
- keyMaterialAlg = "SunTls12KeyMaterial";
- prf = cipherSuite.prfAlg;
+ byte majorVersion = protocolVersion.major;
+ byte minorVersion = protocolVersion.minor;
+ if (protocolVersion.isDTLSProtocol()) {
+ // Use TLS version number for DTLS key calculation
+ if (protocolVersion.v == ProtocolVersion.DTLS10.v) {
+ majorVersion = ProtocolVersion.TLS11.major;
+ minorVersion = ProtocolVersion.TLS11.minor;
+
+ keyMaterialAlg = "SunTlsKeyMaterial";
+ prf = P_NONE;
+ } else { // DTLS 1.2+
+ majorVersion = ProtocolVersion.TLS12.major;
+ minorVersion = ProtocolVersion.TLS12.minor;
+
+ keyMaterialAlg = "SunTls12KeyMaterial";
+ prf = cipherSuite.prfAlg;
+ }
} else {
- keyMaterialAlg = "SunTlsKeyMaterial";
- prf = P_NONE;
+ if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ keyMaterialAlg = "SunTls12KeyMaterial";
+ prf = cipherSuite.prfAlg;
+ } else {
+ keyMaterialAlg = "SunTlsKeyMaterial";
+ prf = P_NONE;
+ }
}
String prfHashAlg = prf.getPRFHashAlg();
int prfHashLength = prf.getPRFHashLength();
int prfBlockSize = prf.getPRFBlockSize();
- // TLS v1.1 or later uses an explicit IV in CBC cipher suites to
+ // TLS v1.1+ and DTLS use an explicit IV in CBC cipher suites to
// protect against the CBC attacks. AEAD/GCM cipher suites in TLS
// v1.2 or later use a fixed IV as the implicit part of the partially
// implicit nonce technique described in RFC 5116.
int ivSize = cipher.ivSize;
if (cipher.cipherType == AEAD_CIPHER) {
ivSize = cipher.fixedIvSize;
- } else if (protocolVersion.v >= ProtocolVersion.TLS11.v &&
- cipher.cipherType == BLOCK_CIPHER) {
+ } else if ((cipher.cipherType == BLOCK_CIPHER) &&
+ protocolVersion.useTLS11PlusSpec()) {
ivSize = 0;
}
TlsKeyMaterialParameterSpec spec = new TlsKeyMaterialParameterSpec(
- masterKey, protocolVersion.major, protocolVersion.minor,
- clnt_random.random_bytes, svr_random.random_bytes,
- cipher.algorithm, cipher.keySize, expandedKeySize,
- ivSize, hashSize,
- prfHashAlg, prfHashLength, prfBlockSize);
+ masterKey, (majorVersion & 0xFF), (minorVersion & 0xFF),
+ clnt_random.random_bytes, svr_random.random_bytes,
+ cipher.algorithm, cipher.keySize, expandedKeySize,
+ ivSize, hashSize,
+ prfHashAlg, prfHashLength, prfBlockSize);
try {
KeyGenerator kg = JsseJce.getKeyGenerator(keyMaterialAlg);
@@ -1247,10 +1310,6 @@
throw new ProviderException(e);
}
- // Mark a flag that allows outside entities (like SSLSocket/SSLEngine)
- // determine if a ChangeCipherSpec message could be processed.
- sessKeysCalculated = true;
-
//
// Dump the connection keys as they're generated.
//
@@ -1293,7 +1352,7 @@
System.out.println("Server write IV:");
printHex(dump, svrWriteIV.getIV());
} else {
- if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+ if (protocolVersion.useTLS11PlusSpec()) {
System.out.println(
"... no IV derived for this protocol");
} else {
@@ -1305,15 +1364,6 @@
}
}
- /**
- * Return whether or not the Handshaker has derived session keys for
- * this handshake. This is used for determining readiness to process
- * an incoming ChangeCipherSpec message.
- */
- boolean sessionKeysCalculated() {
- return sessKeysCalculated;
- }
-
private static void printHex(HexDumpEncoder dump, byte[] bytes) {
if (bytes == null) {
System.out.println("(key bytes not available)");
@@ -1326,19 +1376,6 @@
}
}
- /**
- * Throw an SSLException with the specified message and cause.
- * Shorthand until a new SSLException constructor is added.
- * This method never returns.
- */
- static void throwSSLException(String msg, Throwable cause)
- throws SSLException {
- SSLException e = new SSLException(msg);
- e.initCause(cause);
- throw e;
- }
-
-
/*
* Implement a simple task delegator.
*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.IOException;
+import javax.net.ssl.SSLProtocolException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+import sun.security.ssl.HandshakeMessage.ClientHello;
+
+/*
+ * HelloVerifyRequest cookie manager
+ */
+final class HelloCookieManager {
+ // the cookie secret life time
+ private static long COOKIE_TIMING_WINDOW = 3600000; // in milliseconds
+ private static int COOKIE_MAX_LENGTH_DTLS10 = 32; // 32 bytes
+ private static int COOKIE_MAX_LENGTH_DTLS12 = 0xFF; // 2^8 -1 bytes
+
+ private final SecureRandom secureRandom;
+ private final MessageDigest cookieDigest;
+
+ private int cookieVersion; // allow to wrap
+ private long secretLifetime;
+ private byte[] cookieSecret;
+
+ private int prevCookieVersion;
+ private byte[] prevCookieSecret;
+
+ HelloCookieManager(SecureRandom secureRandom) {
+ this.secureRandom = secureRandom;
+ this.cookieDigest = JsseJce.getMessageDigest("SHA-256");
+
+ this.cookieVersion = secureRandom.nextInt();
+ this.secretLifetime = 0;
+ this.cookieSecret = null;
+
+ this.prevCookieVersion = 0;
+ this.prevCookieSecret = null;
+ }
+
+ // Used by server side to generate cookies in HelloVerifyRequest message.
+ synchronized byte[] getCookie(ClientHello clientHelloMsg) {
+ if (secretLifetime < System.currentTimeMillis()) {
+ if (cookieSecret != null) {
+ prevCookieVersion = cookieVersion;
+ prevCookieSecret = cookieSecret.clone();
+ } else {
+ cookieSecret = new byte[32];
+ }
+
+ cookieVersion++;
+ secureRandom.nextBytes(cookieSecret);
+ secretLifetime = System.currentTimeMillis() + COOKIE_TIMING_WINDOW;
+ }
+
+ clientHelloMsg.updateHelloCookie(cookieDigest);
+ byte[] cookie = cookieDigest.digest(cookieSecret); // 32 bytes
+ cookie[0] = (byte)((cookieVersion >> 24) & 0xFF);
+ cookie[1] = (byte)((cookieVersion >> 16) & 0xFF);
+ cookie[2] = (byte)((cookieVersion >> 8) & 0xFF);
+ cookie[3] = (byte)(cookieVersion & 0xFF);
+
+ return cookie;
+ }
+
+ // Used by server side to check the cookie in ClientHello message.
+ synchronized boolean isValid(ClientHello clientHelloMsg) {
+ byte[] cookie = clientHelloMsg.cookie;
+
+ // no cookie exchange or not a valid cookie length
+ if ((cookie == null) || (cookie.length != 32)) {
+ return false;
+ }
+
+ int version = ((cookie[0] & 0xFF) << 24) |
+ ((cookie[1] & 0xFF) << 16) |
+ ((cookie[2] & 0xFF) << 8) |
+ (cookie[3] & 0xFF);
+
+ byte[] secret;
+ if (version == cookieVersion) {
+ secret = cookieSecret;
+ } else if (version == prevCookieVersion) {
+ secret = prevCookieSecret;
+ } else {
+ return false; // may be out of the timing window
+ }
+
+ clientHelloMsg.updateHelloCookie(cookieDigest);
+ byte[] target = cookieDigest.digest(secret); // 32 bytes
+
+ for (int i = 4; i < 32; i++) {
+ if (cookie[i] != target[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Used by client side to check the cookie in HelloVerifyRequest message.
+ static void checkCookie(ProtocolVersion protocolVersion,
+ byte[] cookie) throws IOException {
+ if (cookie != null && cookie.length != 0) {
+ int limit = COOKIE_MAX_LENGTH_DTLS12;
+ if (protocolVersion.v == ProtocolVersion.DTLS10.v) {
+ limit = COOKIE_MAX_LENGTH_DTLS10;
+ }
+
+ if (cookie.length > COOKIE_MAX_LENGTH_DTLS10) {
+ throw new SSLProtocolException(
+ "Invalid HelloVerifyRequest.cookie (length = " +
+ cookie.length + " bytes)");
+ }
+ }
+
+ // Otherwise, no cookie exchange.
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java Tue Jun 02 09:15:47 2015 -0700
@@ -85,6 +85,8 @@
new SupportedEllipticPointFormatsExtension(s, extlen);
} else if (extType == ExtensionType.EXT_RENEGOTIATION_INFO) {
extension = new RenegotiationInfoExtension(s, extlen);
+ } else if (extType == ExtensionType.EXT_MAX_FRAGMENT_LENGTH) {
+ extension = new MaxFragmentLengthExtension(s, extlen);
} else {
extension = new UnknownExtension(s, extlen, extType);
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/InputRecord.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/InputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,11 +23,11 @@
* questions.
*/
-
package sun.security.ssl;
import java.io.*;
import java.nio.*;
+import java.util.*;
import javax.crypto.BadPaddingException;
@@ -37,66 +37,35 @@
/**
- * SSL 3.0 records, as pulled off a TCP stream. Input records are
- * basically buffers tied to a particular input stream ... a layer
- * above this must map these records into the model of a continuous
- * stream of data.
- *
- * Since this returns SSL 3.0 records, it's the layer that needs to
- * map SSL 2.0 style handshake records into SSL 3.0 ones for those
- * "old" clients that interop with both V2 and V3 servers. Not as
- * pretty as might be desired.
- *
- * NOTE: During handshaking, each message must be hashed to support
- * verification that the handshake process wasn't compromised.
+ * {@code InputRecord} takes care of the management of SSL/TLS/DTLS input
+ * records, including buffering, decryption, handshake messages marshal, etc.
*
* @author David Brownell
*/
-class InputRecord extends ByteArrayInputStream implements Record {
+class InputRecord implements Record, Closeable {
+
+ /* Class and subclass dynamic debugging support */
+ static final Debug debug = Debug.getInstance("ssl");
- private HandshakeHash handshakeHash;
- private int lastHashed;
- boolean formatVerified = true; // SSLv2 ruled out?
- private boolean isClosed;
- private boolean appDataValid;
+ Authenticator readAuthenticator;
+ CipherBox readCipher;
+
+ HandshakeHash handshakeHash;
+ boolean isClosed;
// The ClientHello version to accept. If set to ProtocolVersion.SSL20Hello
// and the first message we read is a ClientHello in V2 format, we convert
// it to V3. Otherwise we throw an exception when encountering a V2 hello.
- private ProtocolVersion helloVersion;
-
- /* Class and subclass dynamic debugging support */
- static final Debug debug = Debug.getInstance("ssl");
+ ProtocolVersion helloVersion;
- /* The existing record length */
- private int exlen;
-
- /* V2 handshake message */
- private byte v2Buf[];
+ // fragment size
+ int fragmentSize;
- /*
- * Construct the record to hold the maximum sized input record.
- * Data will be filled in separately.
- *
- * The structure of the byte buffer looks like:
- *
- * |--------+---------+---------------------------------|
- * | header | IV | content, MAC/TAG, padding, etc. |
- * | headerPlusIVSize |
- *
- * header: the header of an SSL records
- * IV: the optional IV/nonce field, it is only required for block
- * (TLS 1.1 or later) and AEAD cipher suites.
- *
- */
InputRecord() {
- super(new byte[maxRecordSize]);
- setHelloVersion(ProtocolVersion.DEFAULT_HELLO);
- pos = headerSize;
- count = headerSize;
- lastHashed = count;
- exlen = 0;
- v2Buf = null;
+ this.readCipher = CipherBox.NULL;
+ this.readAuthenticator = null; // Please override this assignment.
+ this.helloVersion = ProtocolVersion.DEFAULT_HELLO;
+ this.fragmentSize = Record.maxDataSize;
}
void setHelloVersion(ProtocolVersion helloVersion) {
@@ -108,70 +77,332 @@
}
/*
- * Enable format checks if initial handshaking hasn't completed
- */
- void enableFormatChecks() {
- formatVerified = false;
- }
-
- // return whether the data in this record is valid, decrypted data
- boolean isAppDataValid() {
- return appDataValid;
- }
-
- void setAppDataValid(boolean value) {
- appDataValid = value;
- }
-
- /*
- * Return the content type of the record.
- */
- byte contentType() {
- return buf[0];
- }
-
- /*
+ * Set instance for the computation of handshake hashes.
+ *
* For handshaking, we need to be able to hash every byte above the
* record marking layer. This is where we're guaranteed to see those
* bytes, so this is where we can hash them ... especially in the
* case of hashing the initial V2 message!
*/
void setHandshakeHash(HandshakeHash handshakeHash) {
+ if (handshakeHash != null) {
+ byte[] reserved = null;
+ if (this.handshakeHash != null) {
+ reserved = this.handshakeHash.getAllHandshakeMessages();
+ }
+ if ((reserved != null) && (reserved.length != 0)) {
+ handshakeHash.update(reserved, 0, reserved.length);
+
+ if (debug != null && Debug.isOn("data")) {
+ Debug.printHex(
+ "[reserved] handshake hash: len = " + reserved.length,
+ reserved);
+ }
+ }
+ }
+
this.handshakeHash = handshakeHash;
}
- HandshakeHash getHandshakeHash() {
- return handshakeHash;
+ boolean seqNumIsHuge() {
+ return (readAuthenticator != null) &&
+ readAuthenticator.seqNumIsHuge();
+ }
+
+ boolean isEmpty() {
+ return false;
+ }
+
+ // apply to DTLS SSLEngine
+ void expectingFinishFlight() {
+ // blank
+ }
+
+ /**
+ * Prevent any more data from being read into this record,
+ * and flag the record as holding no data.
+ */
+ @Override
+ synchronized public void close() throws IOException {
+ if (!isClosed) {
+ isClosed = true;
+ readCipher.dispose();
+ }
+ }
+
+ // apply to SSLSocket and SSLEngine
+ void changeReadCiphers(
+ Authenticator readAuthenticator, CipherBox readCipher) {
+
+ /*
+ * Dispose of any intermediate state in the underlying cipher.
+ * For PKCS11 ciphers, this will release any attached sessions,
+ * and thus make finalization faster.
+ *
+ * Since MAC's doFinal() is called for every SSL/TLS packet, it's
+ * not necessary to do the same with MAC's.
+ */
+ readCipher.dispose();
+
+ this.readAuthenticator = readAuthenticator;
+ this.readCipher = readCipher;
+ }
+
+ // change fragment size
+ void changeFragmentSize(int fragmentSize) {
+ this.fragmentSize = fragmentSize;
+ }
+
+ /*
+ * Check if there is enough inbound data in the ByteBuffer to make
+ * a inbound packet.
+ *
+ * @return -1 if there are not enough bytes to tell (small header),
+ */
+ // apply to SSLEngine only
+ int bytesInCompletePacket(ByteBuffer buf) throws SSLException {
+ throw new UnsupportedOperationException();
+ }
+
+ // apply to SSLSocket only
+ int bytesInCompletePacket(InputStream is) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Return true if the specified record protocol version is out of the
+ * range of the possible supported versions.
+ */
+ void checkRecordVersion(ProtocolVersion version,
+ boolean allowSSL20Hello) throws SSLException {
+ // blank
+ }
+
+ // apply to DTLS SSLEngine only
+ Plaintext acquirePlaintext()
+ throws IOException, BadPaddingException {
+ throw new UnsupportedOperationException();
+ }
+
+ // read, decrypt and decompress the network record.
+ //
+ // apply to SSLEngine only
+ Plaintext decode(ByteBuffer netData)
+ throws IOException, BadPaddingException {
+ throw new UnsupportedOperationException();
+ }
+
+ // apply to SSLSocket only
+ Plaintext decode(InputStream is, ByteBuffer destination)
+ throws IOException, BadPaddingException {
+ throw new UnsupportedOperationException();
+ }
+
+ // apply to SSLSocket only
+ void setDeliverStream(OutputStream outputStream) {
+ throw new UnsupportedOperationException();
+ }
+
+ // calculate plaintext fragment size
+ //
+ // apply to SSLEngine only
+ int estimateFragmentSize(int packetSize) {
+ throw new UnsupportedOperationException();
}
- void decrypt(Authenticator authenticator,
- CipherBox box) throws BadPaddingException {
+ //
+ // shared helpers
+ //
+
+ // Not apply to DTLS
+ static ByteBuffer convertToClientHello(ByteBuffer packet) {
+
+ int srcPos = packet.position();
+ int srcLim = packet.limit();
+
+ byte firstByte = packet.get();
+ byte secondByte = packet.get();
+ int recordLen = (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2;
+
+ packet.position(srcPos + 3); // the V2ClientHello record header
+
+ byte majorVersion = packet.get();
+ byte minorVersion = packet.get();
+
+ int cipherSpecLen = ((packet.get() & 0xFF) << 8) +
+ (packet.get() & 0xFF);
+ int sessionIdLen = ((packet.get() & 0xFF) << 8) +
+ (packet.get() & 0xFF);
+ int nonceLen = ((packet.get() & 0xFF) << 8) +
+ (packet.get() & 0xFF);
+
+ // Required space for the target SSLv3 ClientHello message.
+ // 5: record header size
+ // 4: handshake header size
+ // 2: ClientHello.client_version
+ // 32: ClientHello.random
+ // 1: length byte of ClientHello.session_id
+ // 2: empty ClientHello.compression_methods
+ int requiredSize = 46 + sessionIdLen + ((cipherSpecLen * 2 ) / 3 );
+ byte[] converted = new byte[requiredSize];
+
+ /*
+ * Build the first part of the V3 record header from the V2 one
+ * that's now buffered up. (Lengths are fixed up later).
+ */
+ // Note: need not to set the header actually.
+ converted[0] = ct_handshake;
+ converted[1] = majorVersion;
+ converted[2] = minorVersion;
+ // header [3..4] for handshake message length
+ // required size is 5;
+
+ /*
+ * Store the generic V3 handshake header: 4 bytes
+ */
+ converted[5] = 1; // HandshakeMessage.ht_client_hello
+ // buf [6..8] for length of ClientHello (int24)
+ // required size += 4;
+
+ /*
+ * ClientHello header starts with SSL version
+ */
+ converted[9] = majorVersion;
+ converted[10] = minorVersion;
+ // required size += 2;
+ int pointer = 11;
+
+ /*
+ * Copy Random value/nonce ... if less than the 32 bytes of
+ * a V3 "Random", right justify and zero pad to the left. Else
+ * just take the last 32 bytes.
+ */
+ int offset = srcPos + 11 + cipherSpecLen + sessionIdLen;
+
+ if (nonceLen < 32) {
+ for (int i = 0; i < (32 - nonceLen); i++) {
+ converted[pointer++] = 0;
+ }
+ packet.position(offset);
+ packet.get(converted, pointer, nonceLen);
+
+ pointer += nonceLen;
+ } else {
+ packet.position(offset + nonceLen - 32);
+ packet.get(converted, pointer, 32);
+
+ pointer += 32;
+ }
+
+ /*
+ * Copy session ID (only one byte length!)
+ */
+ offset -= sessionIdLen;
+ converted[pointer++] = (byte)(sessionIdLen & 0xFF);
+ packet.position(offset);
+ packet.get(converted, pointer, sessionIdLen);
+
+ /*
+ * Copy and translate cipher suites ... V2 specs with first byte zero
+ * are really V3 specs (in the last 2 bytes), just copy those and drop
+ * the other ones. Preference order remains unchanged.
+ *
+ * Example: Netscape Navigator 3.0 (exportable) says:
+ *
+ * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5
+ * 0/6, SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ *
+ * Microsoft Internet Explorer 3.0 (exportable) supports only
+ *
+ * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5
+ */
+ int j;
+
+ offset -= cipherSpecLen;
+ packet.position(offset);
+
+ j = pointer + 2;
+ for (int i = 0; i < cipherSpecLen; i += 3) {
+ if (packet.get() != 0) {
+ // Ignore version 2.0 specifix cipher suite. Clients
+ // should also include the version 3.0 equivalent in
+ // the V2ClientHello message.
+ packet.get(); // ignore the 2nd byte
+ packet.get(); // ignore the 3rd byte
+ continue;
+ }
+
+ converted[j++] = packet.get();
+ converted[j++] = packet.get();
+ }
+
+ j -= pointer + 2;
+ converted[pointer++] = (byte)((j >>> 8) & 0xFF);
+ converted[pointer++] = (byte)(j & 0xFF);
+ pointer += j;
+
+ /*
+ * Append compression methods (default/null only)
+ */
+ converted[pointer++] = 1;
+ converted[pointer++] = 0; // Session.compression_null
+
+ /*
+ * Fill in lengths of the messages we synthesized (nested:
+ * V3 handshake message within V3 record).
+ */
+ // Note: need not to set the header actually.
+ int fragLen = pointer - 5; // TLSPlaintext.length
+ converted[3] = (byte)((fragLen >>> 8) & 0xFF);
+ converted[4] = (byte)(fragLen & 0xFF);
+
+ /*
+ * Handshake.length, length of ClientHello message
+ */
+ fragLen = pointer - 9; // Handshake.length
+ converted[6] = (byte)((fragLen >>> 16) & 0xFF);
+ converted[7] = (byte)((fragLen >>> 8) & 0xFF);
+ converted[8] = (byte)(fragLen & 0xFF);
+
+ // consume the full record
+ packet.position(srcPos + recordLen);
+
+ // Need no header bytes.
+ return ByteBuffer.wrap(converted, 5, pointer - 5); // 5: header size
+ }
+
+ static ByteBuffer decrypt(Authenticator authenticator, CipherBox box,
+ byte contentType, ByteBuffer bb) throws BadPaddingException {
+
+ return decrypt(authenticator, box, contentType, bb, null);
+ }
+
+ static ByteBuffer decrypt(Authenticator authenticator,
+ CipherBox box, byte contentType, ByteBuffer bb,
+ byte[] sequence) throws BadPaddingException {
+
BadPaddingException reservedBPE = null;
int tagLen =
(authenticator instanceof MAC) ? ((MAC)authenticator).MAClen() : 0;
- int cipheredLength = count - headerSize;
-
+ int cipheredLength = bb.remaining();
+ int srcPos = bb.position();
if (!box.isNullCipher()) {
try {
// apply explicit nonce for AEAD/CBC cipher suites if needed
- int nonceSize = box.applyExplicitNonce(authenticator,
- contentType(), buf, headerSize, cipheredLength);
- pos = headerSize + nonceSize;
- lastHashed = pos; // don't digest the explicit nonce
+ int nonceSize = box.applyExplicitNonce(
+ authenticator, contentType, bb, sequence);
// decrypt the content
- int offset = headerSize;
if (box.isAEADMode()) {
- // DON'T encrypt the nonce_explicit for AEAD mode
- offset += nonceSize;
+ // DON'T decrypt the nonce_explicit for AEAD mode
+ bb.position(srcPos + nonceSize);
} // The explicit IV for CBC mode can be decrypted.
// Note that the CipherBox.decrypt() does not change
// the capacity of the buffer.
- count = offset +
- box.decrypt(buf, offset, count - offset, tagLen);
-
- // Note that we don't remove the nonce from the buffer.
+ box.decrypt(bb, tagLen);
+ // We don't actually remove the nonce.
+ bb.position(srcPos + nonceSize);
} catch (BadPaddingException bpe) {
// RFC 2246 states that decryption_failed should be used
// for this purpose. However, that allows certain attacks,
@@ -187,10 +418,9 @@
// Requires message authentication code for null, stream and block
// cipher suites.
- if (authenticator instanceof MAC && tagLen != 0) {
+ if ((authenticator instanceof MAC) && (tagLen != 0)) {
MAC signer = (MAC)authenticator;
- int macOffset = count - tagLen;
- int contentLen = macOffset - pos;
+ int contentLen = bb.remaining() - tagLen;
// Note that although it is not necessary, we run the same MAC
// computation and comparison on the payload for both stream
@@ -202,19 +432,14 @@
}
// set offset of the dummy MAC
- macOffset = headerSize + cipheredLength - tagLen;
- contentLen = macOffset - headerSize;
+ contentLen = cipheredLength - tagLen;
+ bb.limit(srcPos + cipheredLength);
}
- count -= tagLen; // Set the count before any MAC checking
- // exception occurs, so that the following
- // process can read the actual decrypted
- // content (minus the MAC) in the fragment
- // if necessary.
-
// Run MAC computation and comparison on the payload.
- if (checkMacTags(contentType(),
- buf, pos, contentLen, signer, false)) {
+ //
+ // MAC data would be stripped off during the check.
+ if (checkMacTags(contentType, bb, signer, sequence, false)) {
if (reservedBPE == null) {
reservedBPE = new BadPaddingException("bad record MAC");
}
@@ -229,21 +454,18 @@
signer, cipheredLength, contentLen);
// NOTE: remainingLen may be bigger (less than 1 block of the
- // hash algorithm of the MAC) than the cipheredLength. However,
- // We won't need to worry about it because we always use a
- // maximum buffer for every record. We need a change here if
- // we use small buffer size in the future.
- if (remainingLen > buf.length) {
- // unlikely to happen, just a placehold
- throw new RuntimeException(
- "Internal buffer capacity error");
- }
+ // hash algorithm of the MAC) than the cipheredLength.
+ //
+ // Is it possible to use a static buffer, rather than allocate
+ // it dynamically?
+ remainingLen += signer.MAClen();
+ ByteBuffer temporary = ByteBuffer.allocate(remainingLen);
// Won't need to worry about the result on the remainder. And
// then we won't need to worry about what's actual data to
// check MAC tag on. We start the check from the header of the
// buffer so that we don't need to construct a new byte buffer.
- checkMacTags(contentType(), buf, 0, remainingLen, signer, true);
+ checkMacTags(contentType, temporary, signer, sequence, true);
}
}
@@ -251,6 +473,63 @@
if (reservedBPE != null) {
throw reservedBPE;
}
+
+ return bb.slice();
+ }
+
+ /*
+ * Run MAC computation and comparison
+ *
+ */
+ private static boolean checkMacTags(byte contentType, ByteBuffer bb,
+ MAC signer, byte[] sequence, boolean isSimulated) {
+
+ int tagLen = signer.MAClen();
+ int position = bb.position();
+ int lim = bb.limit();
+ int macOffset = lim - tagLen;
+
+ bb.limit(macOffset);
+ byte[] hash = signer.compute(contentType, bb, sequence, isSimulated);
+ if (hash == null || tagLen != hash.length) {
+ // Something is wrong with MAC implementation.
+ throw new RuntimeException("Internal MAC error");
+ }
+
+ bb.position(macOffset);
+ bb.limit(lim);
+ try {
+ int[] results = compareMacTags(bb, hash);
+ return (results[0] != 0);
+ } finally {
+ // reset to the data
+ bb.position(position);
+ bb.limit(macOffset);
+ }
+ }
+
+ /*
+ * A constant-time comparison of the MAC tags.
+ *
+ * Please DON'T change the content of the ByteBuffer parameter!
+ */
+ private static int[] compareMacTags(ByteBuffer bb, byte[] tag) {
+
+ // An array of hits is used to prevent Hotspot optimization for
+ // the purpose of a constant-time check.
+ int[] results = {0, 0}; // {missed #, matched #}
+
+ // The caller ensures there are enough bytes available in the buffer.
+ // So we won't need to check the remaining of the buffer.
+ for (int i = 0; i < tag.length; i++) {
+ if (bb.get() != tag[i]) {
+ results[0]++; // mismatched bytes
+ } else {
+ results[1]++; // matched bytes
+ }
+ }
+
+ return results;
}
/*
@@ -258,7 +537,7 @@
*
* Please DON'T change the content of the byte buffer parameter!
*/
- static boolean checkMacTags(byte contentType, byte[] buffer,
+ private static boolean checkMacTags(byte contentType, byte[] buffer,
int offset, int contentLen, MAC signer, boolean isSimulated) {
int tagLen = signer.MAClen();
@@ -304,7 +583,7 @@
*
* The caller MUST ensure that the fullLen is not less than usedLen.
*/
- static int calculateRemainingLen(
+ private static int calculateRemainingLen(
MAC signer, int fullLen, int usedLen) {
int blockLen = signer.hashBlockLen();
@@ -322,551 +601,7 @@
// that the return value is positive. The extra one byte does
// not impact the overall MAC compression function evaluations.
return 0x01 + (int)(Math.ceil(fullLen/(1.0d * blockLen)) -
- Math.ceil(usedLen/(1.0d * blockLen))) * signer.hashBlockLen();
- }
-
- /*
- * Well ... hello_request messages are _never_ hashed since we can't
- * know when they'd appear in the sequence.
- */
- void ignore(int bytes) {
- if (bytes > 0) {
- pos += bytes;
- lastHashed = pos;
- }
- }
-
- /*
- * We hash the (plaintext) we've processed, but only on demand.
- *
- * There is one place where we want to access the hash in the middle
- * of a record: client cert message gets hashed, and part of the
- * same record is the client cert verify message which uses that hash.
- * So we track how much we've read and hashed.
- */
- void doHashes() {
- int len = pos - lastHashed;
-
- if (len > 0) {
- hashInternal(buf, lastHashed, len);
- lastHashed = pos;
- }
- }
-
- /*
- * Need a helper function so we can hash the V2 hello correctly
- */
- private void hashInternal(byte databuf [], int offset, int len) {
- if (debug != null && Debug.isOn("data")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
-
- System.out.println("[read] MD5 and SHA1 hashes: len = "
- + len);
- hd.encodeBuffer(new ByteArrayInputStream(databuf, offset, len),
- System.out);
- } catch (IOException e) { }
- }
- handshakeHash.update(databuf, offset, len);
- }
-
-
- /*
- * Handshake messages may cross record boundaries. We "queue"
- * these in big buffers if we need to cope with this problem.
- * This is not anticipated to be a common case; if this turns
- * out to be wrong, this can readily be sped up.
- */
- void queueHandshake(InputRecord r) throws IOException {
- int len;
-
- /*
- * Hash any data that's read but unhashed.
- */
- doHashes();
-
- /*
- * Move any unread data to the front of the buffer,
- * flagging it all as unhashed.
- */
- if (pos > headerSize) {
- len = count - pos;
- if (len != 0) {
- System.arraycopy(buf, pos, buf, headerSize, len);
- }
- pos = headerSize;
- lastHashed = pos;
- count = headerSize + len;
- }
-
- /*
- * Grow "buf" if needed
- */
- len = r.available() + count;
- if (buf.length < len) {
- byte newbuf [];
-
- newbuf = new byte [len];
- System.arraycopy(buf, 0, newbuf, 0, count);
- buf = newbuf;
- }
-
- /*
- * Append the new buffer to this one.
- */
- System.arraycopy(r.buf, r.pos, buf, count, len - count);
- count = len;
-
- /*
- * Adjust lastHashed; important for now with clients which
- * send SSL V2 client hellos. This will go away eventually,
- * by buffer code cleanup.
- */
- len = r.lastHashed - r.pos;
- if (pos == headerSize) {
- lastHashed += len;
- } else {
- throw new SSLProtocolException("?? confused buffer hashing ??");
- }
- // we've read the record, advance the pointers
- r.pos = r.count;
- }
-
-
- /**
- * Prevent any more data from being read into this record,
- * and flag the record as holding no data.
- */
- @Override
- public void close() {
- appDataValid = false;
- isClosed = true;
- mark = 0;
- pos = 0;
- count = 0;
+ Math.ceil(usedLen/(1.0d * blockLen))) * blockLen;
}
-
-
- /*
- * We may need to send this SSL v2 "No Cipher" message back, if we
- * are faced with an SSLv2 "hello" that's not saying "I talk v3".
- * It's the only one documented in the V2 spec as a fatal error.
- */
- private static final byte[] v2NoCipher = {
- (byte)0x80, (byte)0x03, // unpadded 3 byte record
- (byte)0x00, // ... error message
- (byte)0x00, (byte)0x01 // ... NO_CIPHER error
- };
-
- private int readFully(InputStream s, byte b[], int off, int len)
- throws IOException {
- int n = 0;
- while (n < len) {
- int readLen = s.read(b, off + n, len - n);
- if (readLen < 0) {
- return readLen;
- }
-
- if (debug != null && Debug.isOn("packet")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
- ByteBuffer bb = ByteBuffer.wrap(b, off + n, readLen);
-
- System.out.println("[Raw read]: length = " +
- bb.remaining());
- hd.encodeBuffer(bb, System.out);
- } catch (IOException e) { }
- }
-
- n += readLen;
- exlen += readLen;
- }
-
- return n;
- }
-
- /*
- * Read the SSL V3 record ... first time around, check to see if it
- * really IS a V3 record. Handle SSL V2 clients which can talk V3.0,
- * as well as real V3 record format; otherwise report an error.
- */
- void read(InputStream s, OutputStream o) throws IOException {
- if (isClosed) {
- return;
- }
-
- /*
- * For SSL it really _is_ an error if the other end went away
- * so ungracefully as to not shut down cleanly.
- */
- if(exlen < headerSize) {
- int really = readFully(s, buf, exlen, headerSize - exlen);
- if (really < 0) {
- throw new EOFException("SSL peer shut down incorrectly");
- }
-
- pos = headerSize;
- count = headerSize;
- lastHashed = pos;
- }
-
- /*
- * The first record might use some other record marking convention,
- * typically SSL v2 header. (PCT could also be detected here.)
- * This case is currently common -- Navigator 3.0 usually works
- * this way, as do IE 3.0 and other products.
- */
- if (!formatVerified) {
- formatVerified = true;
- /*
- * The first record must either be a handshake record or an
- * alert message. If it's not, it is either invalid or an
- * SSLv2 message.
- */
- if (buf[0] != ct_handshake && buf[0] != ct_alert) {
- handleUnknownRecord(s, o);
- } else {
- readV3Record(s, o);
- }
- } else { // formatVerified == true
- readV3Record(s, o);
- }
- }
-
- /**
- * Return true if the specified record protocol version is out of the
- * range of the possible supported versions.
- */
- static void checkRecordVersion(ProtocolVersion version,
- boolean allowSSL20Hello) throws SSLException {
- // Check if the record version is too old (currently not possible)
- // or if the major version does not match.
- //
- // The actual version negotiation is in the handshaker classes
- if ((version.v < ProtocolVersion.MIN.v) ||
- ((version.major & 0xFF) > (ProtocolVersion.MAX.major & 0xFF))) {
-
- // if it's not SSLv2, we're out of here.
- if (!allowSSL20Hello ||
- (version.v != ProtocolVersion.SSL20Hello.v)) {
- throw new SSLException("Unsupported record version " + version);
- }
- }
- }
-
- /**
- * Read a SSL/TLS record. Throw an IOException if the format is invalid.
- */
- private void readV3Record(InputStream s, OutputStream o)
- throws IOException {
- ProtocolVersion recordVersion = ProtocolVersion.valueOf(buf[1], buf[2]);
-
- // check the record version
- checkRecordVersion(recordVersion, false);
-
- /*
- * Get and check length, then the data.
- */
- int contentLen = ((buf[3] & 0x0ff) << 8) + (buf[4] & 0xff);
-
- /*
- * Check for upper bound.
- */
- if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) {
- throw new SSLProtocolException("Bad InputRecord size"
- + ", count = " + contentLen
- + ", buf.length = " + buf.length);
- }
-
- /*
- * Grow "buf" if needed. Since buf is maxRecordSize by default,
- * this only occurs when we receive records which violate the
- * SSL specification. This is a workaround for a Microsoft SSL bug.
- */
- if (contentLen > buf.length - headerSize) {
- byte[] newbuf = new byte[contentLen + headerSize];
- System.arraycopy(buf, 0, newbuf, 0, headerSize);
- buf = newbuf;
- }
+}
- if (exlen < contentLen + headerSize) {
- int really = readFully(
- s, buf, exlen, contentLen + headerSize - exlen);
- if (really < 0) {
- throw new SSLException("SSL peer shut down incorrectly");
- }
- }
-
- // now we've got a complete record.
- count = contentLen + headerSize;
- exlen = 0;
-
- if (debug != null && Debug.isOn("record")) {
- if (count < 0 || count > (maxRecordSize - headerSize)) {
- System.out.println(Thread.currentThread().getName()
- + ", Bad InputRecord size" + ", count = " + count);
- }
- System.out.println(Thread.currentThread().getName()
- + ", READ: " + recordVersion + " "
- + contentName(contentType()) + ", length = " + available());
- }
- /*
- * then caller decrypts, verifies, and uncompresses
- */
- }
-
- /**
- * Deal with unknown records. Called if the first data we read on this
- * connection does not look like an SSL/TLS record. It could a SSLv2
- * message, or just garbage.
- */
- private void handleUnknownRecord(InputStream s, OutputStream o)
- throws IOException {
- /*
- * No? Oh well; does it look like a V2 "ClientHello"?
- * That'd be an unpadded handshake message; we don't
- * bother checking length just now.
- */
- if (((buf[0] & 0x080) != 0) && buf[2] == 1) {
- /*
- * if the user has disabled SSLv2Hello (using
- * setEnabledProtocol) then throw an
- * exception
- */
- if (helloVersion != ProtocolVersion.SSL20Hello) {
- throw new SSLHandshakeException("SSLv2Hello is disabled");
- }
-
- ProtocolVersion recordVersion =
- ProtocolVersion.valueOf(buf[3], buf[4]);
-
- if (recordVersion == ProtocolVersion.SSL20Hello) {
- /*
- * Looks like a V2 client hello, but not one saying
- * "let's talk SSLv3". So we send an SSLv2 error
- * message, one that's treated as fatal by clients.
- * (Otherwise we'll hang.)
- */
- try {
- writeBuffer(o, v2NoCipher, 0, v2NoCipher.length);
- } catch (Exception e) {
- /* NOTHING */
- }
- throw new SSLException("Unsupported SSL v2.0 ClientHello");
- }
-
- /*
- * If we can map this into a V3 ClientHello, read and
- * hash the rest of the V2 handshake, turn it into a
- * V3 ClientHello message, and pass it up.
- */
- int len = ((buf[0] & 0x7f) << 8) +
- (buf[1] & 0xff) - 3;
- if (v2Buf == null) {
- v2Buf = new byte[len];
- }
- if (exlen < len + headerSize) {
- int really = readFully(
- s, v2Buf, exlen - headerSize, len + headerSize - exlen);
- if (really < 0) {
- throw new EOFException("SSL peer shut down incorrectly");
- }
- }
-
- // now we've got a complete record.
- exlen = 0;
-
- hashInternal(buf, 2, 3);
- hashInternal(v2Buf, 0, len);
- V2toV3ClientHello(v2Buf);
- v2Buf = null;
- lastHashed = count;
-
- if (debug != null && Debug.isOn("record")) {
- System.out.println(
- Thread.currentThread().getName()
- + ", READ: SSL v2, contentType = "
- + contentName(contentType())
- + ", translated length = " + available());
- }
- return;
-
- } else {
- /*
- * Does it look like a V2 "ServerHello"?
- */
- if (((buf [0] & 0x080) != 0) && buf [2] == 4) {
- throw new SSLException(
- "SSL V2.0 servers are not supported.");
- }
-
- /*
- * If this is a V2 NoCipher message then this means
- * the other server doesn't support V3. Otherwise, we just
- * don't understand what it's saying.
- */
- for (int i = 0; i < v2NoCipher.length; i++) {
- if (buf[i] != v2NoCipher[i]) {
- throw new SSLException(
- "Unrecognized SSL message, plaintext connection?");
- }
- }
-
- throw new SSLException("SSL V2.0 servers are not supported.");
- }
- }
-
- /*
- * Actually do the write here. For SSLEngine's HS data,
- * we'll override this method and let it take the appropriate
- * action.
- */
- void writeBuffer(OutputStream s, byte [] buf, int off, int len)
- throws IOException {
- s.write(buf, 0, len);
- s.flush();
- }
-
- /*
- * Support "old" clients which are capable of SSL V3.0 protocol ... for
- * example, Navigator 3.0 clients. The V2 message is in the header and
- * the bytes passed as parameter. This routine translates the V2 message
- * into an equivalent V3 one.
- */
- private void V2toV3ClientHello(byte v2Msg []) throws SSLException
- {
- int i;
-
- /*
- * Build the first part of the V3 record header from the V2 one
- * that's now buffered up. (Lengths are fixed up later).
- */
- buf [0] = ct_handshake;
- buf [1] = buf [3]; // V3.x
- buf[2] = buf[4];
- // header [3..4] for handshake message length
- // count = 5;
-
- /*
- * Store the generic V3 handshake header: 4 bytes
- */
- buf [5] = 1; // HandshakeMessage.ht_client_hello
- // buf [6..8] for length of ClientHello (int24)
- // count += 4;
-
- /*
- * ClientHello header starts with SSL version
- */
- buf [9] = buf [1];
- buf [10] = buf [2];
- // count += 2;
- count = 11;
-
- /*
- * Start parsing the V2 message ...
- */
- int cipherSpecLen, sessionIdLen, nonceLen;
-
- cipherSpecLen = ((v2Msg [0] & 0xff) << 8) + (v2Msg [1] & 0xff);
- sessionIdLen = ((v2Msg [2] & 0xff) << 8) + (v2Msg [3] & 0xff);
- nonceLen = ((v2Msg [4] & 0xff) << 8) + (v2Msg [5] & 0xff);
-
- /*
- * Copy Random value/nonce ... if less than the 32 bytes of
- * a V3 "Random", right justify and zero pad to the left. Else
- * just take the last 32 bytes.
- */
- int offset = 6 + cipherSpecLen + sessionIdLen;
-
- if (nonceLen < 32) {
- for (i = 0; i < (32 - nonceLen); i++)
- buf [count++] = 0;
- System.arraycopy(v2Msg, offset, buf, count, nonceLen);
- count += nonceLen;
- } else {
- System.arraycopy(v2Msg, offset + (nonceLen - 32),
- buf, count, 32);
- count += 32;
- }
-
- /*
- * Copy Session ID (only one byte length!)
- */
- offset -= sessionIdLen;
- buf [count++] = (byte) sessionIdLen;
-
- System.arraycopy(v2Msg, offset, buf, count, sessionIdLen);
- count += sessionIdLen;
-
- /*
- * Copy and translate cipher suites ... V2 specs with first byte zero
- * are really V3 specs (in the last 2 bytes), just copy those and drop
- * the other ones. Preference order remains unchanged.
- *
- * Example: Netscape Navigator 3.0 (exportable) says:
- *
- * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5
- * 0/6, SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
- *
- * Microsoft Internet Explorer 3.0 (exportable) supports only
- *
- * 0/3, SSL_RSA_EXPORT_WITH_RC4_40_MD5
- */
- int j;
-
- offset -= cipherSpecLen;
- j = count + 2;
-
- for (i = 0; i < cipherSpecLen; i += 3) {
- if (v2Msg [offset + i] != 0)
- continue;
- buf [j++] = v2Msg [offset + i + 1];
- buf [j++] = v2Msg [offset + i + 2];
- }
-
- j -= count + 2;
- buf [count++] = (byte) (j >>> 8);
- buf [count++] = (byte) j;
- count += j;
-
- /*
- * Append compression methods (default/null only)
- */
- buf [count++] = 1;
- buf [count++] = 0; // Session.compression_null
-
- /*
- * Fill in lengths of the messages we synthesized (nested:
- * V3 handshake message within V3 record) and then return
- */
- buf [3] = (byte) (count - headerSize);
- buf [4] = (byte) ((count - headerSize) >>> 8);
-
- buf [headerSize + 1] = 0;
- buf [headerSize + 2] = (byte) (((count - headerSize) - 4) >>> 8);
- buf [headerSize + 3] = (byte) ((count - headerSize) - 4);
-
- pos = headerSize;
- }
-
- /**
- * Return a description for the given content type. This method should be
- * in Record, but since that is an interface this is not possible.
- * Called from InputRecord and OutputRecord.
- */
- static String contentName(int contentType) {
- switch (contentType) {
- case ct_change_cipher_spec:
- return "Change Cipher Spec";
- case ct_alert:
- return "Alert";
- case ct_handshake:
- return "Handshake";
- case ct_application_data:
- return "Application Data";
- default:
- return "contentType = " + contentType;
- }
- }
-
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/JsseJce.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/JsseJce.java Tue Jun 02 09:15:47 2015 -0700
@@ -64,24 +64,9 @@
// If true, then all the Kerberos-based crypto we need is available.
private final static boolean kerberosAvailable;
static {
- boolean temp;
- try {
- AccessController.doPrivileged(
- new PrivilegedExceptionAction<Void>() {
- @Override
- public Void run() throws Exception {
- // Test for Kerberos using the bootstrap class loader
- Class.forName("sun.security.krb5.PrincipalName", true,
- null);
- return null;
- }
- });
- temp = true;
-
- } catch (Exception e) {
- temp = false;
- }
- kerberosAvailable = temp;
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find("KRB5");
+ kerberosAvailable = (p != null);
}
static {
--- a/jdk/src/java.base/share/classes/sun/security/ssl/KerberosClientKeyExchange.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.security.AccessController;
-import java.security.AccessControlContext;
-import java.security.Principal;
-import java.security.PrivilegedAction;
-import java.security.SecureRandom;
-import javax.crypto.SecretKey;
-
-/**
- * A helper class that calls the KerberosClientKeyExchange implementation.
- */
-public class KerberosClientKeyExchange extends HandshakeMessage {
-
- private static final String IMPL_CLASS =
- "sun.security.ssl.krb5.KerberosClientKeyExchangeImpl";
-
- private static final Class<?> implClass = AccessController.doPrivileged(
- new PrivilegedAction<Class<?>>() {
- @Override
- public Class<?> run() {
- try {
- return Class.forName(IMPL_CLASS, true, null);
- } catch (ClassNotFoundException cnf) {
- return null;
- }
- }
- }
- );
- private final KerberosClientKeyExchange impl = createImpl();
-
- private KerberosClientKeyExchange createImpl() {
- if (implClass != null &&
- getClass() == KerberosClientKeyExchange.class) {
- try {
- return (KerberosClientKeyExchange)implClass.newInstance();
- } catch (InstantiationException e) {
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }
- return null;
- }
-
- // This constructor will be called when constructing an instance of its
- // subclass -- KerberosClientKeyExchangeImpl. Please won't check the
- // value of impl variable in this constructor.
- protected KerberosClientKeyExchange() {
- // please won't check the value of impl variable
- }
-
- public KerberosClientKeyExchange(String serverName,
- AccessControlContext acc, ProtocolVersion protocolVersion,
- SecureRandom rand) throws IOException {
-
- if (impl != null) {
- init(serverName, acc, protocolVersion, rand);
- } else {
- throw new IllegalStateException("Kerberos is unavailable");
- }
- }
-
- public KerberosClientKeyExchange(ProtocolVersion protocolVersion,
- ProtocolVersion clientVersion, SecureRandom rand,
- HandshakeInStream input, AccessControlContext acc,
- Object serverKeys) throws IOException {
-
- if (impl != null) {
- init(protocolVersion, clientVersion, rand, input, acc, serverKeys);
- } else {
- throw new IllegalStateException("Kerberos is unavailable");
- }
- }
-
- @Override
- int messageType() {
- return ht_client_key_exchange;
- }
-
- @Override
- public int messageLength() {
- return impl.messageLength();
- }
-
- @Override
- public void send(HandshakeOutStream s) throws IOException {
- impl.send(s);
- }
-
- @Override
- public void print(PrintStream p) throws IOException {
- impl.print(p);
- }
-
- public void init(String serverName,
- AccessControlContext acc, ProtocolVersion protocolVersion,
- SecureRandom rand) throws IOException {
-
- if (impl != null) {
- impl.init(serverName, acc, protocolVersion, rand);
- }
- }
-
- public void init(ProtocolVersion protocolVersion,
- ProtocolVersion clientVersion, SecureRandom rand,
- HandshakeInStream input, AccessControlContext acc,
- Object ServiceCreds) throws IOException {
-
- if (impl != null) {
- impl.init(protocolVersion, clientVersion,
- rand, input, acc, ServiceCreds);
- }
- }
-
- public byte[] getUnencryptedPreMasterSecret() {
- return impl.getUnencryptedPreMasterSecret();
- }
-
- public Principal getPeerPrincipal(){
- return impl.getPeerPrincipal();
- }
-
- public Principal getLocalPrincipal(){
- return impl.getLocalPrincipal();
- }
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Krb5Helper.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.Permission;
-import java.security.Principal;
-import java.security.PrivilegedAction;
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginException;
-
-/**
- * A helper class for Kerberos APIs.
- */
-public final class Krb5Helper {
-
- private Krb5Helper() { }
-
- // loads Krb5Proxy implementation class if available
- private static final String IMPL_CLASS =
- "sun.security.ssl.krb5.Krb5ProxyImpl";
-
- private static final Krb5Proxy proxy =
- AccessController.doPrivileged(new PrivilegedAction<Krb5Proxy>() {
- @Override
- public Krb5Proxy run() {
- try {
- Class<?> c = Class.forName(IMPL_CLASS, true, null);
- return (Krb5Proxy)c.newInstance();
- } catch (ClassNotFoundException cnf) {
- return null;
- } catch (InstantiationException e) {
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }});
-
- /**
- * Returns true if Kerberos is available.
- */
- public static boolean isAvailable() {
- return proxy != null;
- }
-
- private static void ensureAvailable() {
- if (proxy == null)
- throw new AssertionError("Kerberos should have been available");
- }
-
- /**
- * Returns the Subject associated with client-side of the SSL socket.
- */
- public static Subject getClientSubject(AccessControlContext acc)
- throws LoginException {
- ensureAvailable();
- return proxy.getClientSubject(acc);
- }
-
- /**
- * Returns the Subject associated with server-side of the SSL socket.
- */
- public static Subject getServerSubject(AccessControlContext acc)
- throws LoginException {
- ensureAvailable();
- return proxy.getServerSubject(acc);
- }
-
- /**
- * Returns the KerberosKeys for the default server-side principal.
- */
- public static Object getServiceCreds(AccessControlContext acc)
- throws LoginException {
- ensureAvailable();
- return proxy.getServiceCreds(acc);
- }
-
- /**
- * Returns the server-side principal name associated with the KerberosKey.
- */
- public static String getServerPrincipalName(Object serviceCreds) {
- ensureAvailable();
- return proxy.getServerPrincipalName(serviceCreds);
- }
-
- /**
- * Returns the hostname embedded in the principal name.
- */
- public static String getPrincipalHostName(Principal principal) {
- ensureAvailable();
- return proxy.getPrincipalHostName(principal);
- }
-
- /**
- * Returns a ServicePermission for the principal name and action.
- */
- public static Permission getServicePermission(String principalName,
- String action) {
- ensureAvailable();
- return proxy.getServicePermission(principalName, action);
- }
-
- /**
- * Determines if the Subject might contain creds for princ.
- */
- public static boolean isRelated(Subject subject, Principal princ) {
- ensureAvailable();
- return proxy.isRelated(subject, princ);
- }
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Krb5Proxy.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl;
-
-import java.security.AccessControlContext;
-import java.security.Permission;
-import java.security.Principal;
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginException;
-
-/**
- * An interface to a subset of the Kerberos APIs to avoid a static dependency
- * on the types defined by these APIs.
- */
-public interface Krb5Proxy {
-
- /**
- * Returns the Subject associated with the client-side of the SSL socket.
- */
- Subject getClientSubject(AccessControlContext acc) throws LoginException;
-
- /**
- * Returns the Subject associated with the server-side of the SSL socket.
- */
- Subject getServerSubject(AccessControlContext acc) throws LoginException;
-
-
- /**
- * Returns the Kerberos ServiceCreds for the default server-side principal.
- */
- Object getServiceCreds(AccessControlContext acc) throws LoginException;
-
- /**
- * Returns the server-side principal name associated with the KerberosKey.
- */
- String getServerPrincipalName(Object serviceCreds);
-
- /**
- * Returns the hostname embedded in the principal name.
- */
- String getPrincipalHostName(Principal principal);
-
- /**
- * Returns a ServicePermission for the principal name and action.
- */
- Permission getServicePermission(String principalName, String action);
-
- /**
- * Determines if the Subject might contain creds for princ.
- */
- boolean isRelated(Subject subject, Principal princ);
-}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/MAC.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/MAC.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,7 +23,6 @@
* questions.
*/
-
package sun.security.ssl;
import java.security.InvalidKeyException;
@@ -50,24 +49,26 @@
*/
final class MAC extends Authenticator {
- final static MAC NULL = new MAC();
+ final static MAC TLS_NULL = new MAC(false);
// Value of the null MAC is fixed
private static final byte nullMAC[] = new byte[0];
// internal identifier for the MAC algorithm
- private final MacAlg macAlg;
+ private final MacAlg macAlg;
// JCE Mac object
private final Mac mac;
- private MAC() {
+ MAC(boolean isDTLS) {
+ super(isDTLS);
+
macAlg = M_NULL;
mac = null;
}
/**
- * Set up, configured for the given SSL/TLS MAC type and version.
+ * Set up, configured for the given MAC type and version.
*/
MAC(MacAlg macAlg, ProtocolVersion protocolVersion, SecretKey key)
throws NoSuchAlgorithmException, InvalidKeyException {
@@ -75,12 +76,14 @@
this.macAlg = macAlg;
String algorithm;
- boolean tls = (protocolVersion.v >= ProtocolVersion.TLS10.v);
+
+ // using SSL MAC computation?
+ boolean useSSLMac = (protocolVersion.v < ProtocolVersion.TLS10.v);
if (macAlg == M_MD5) {
- algorithm = tls ? "HmacMD5" : "SslMacMD5";
+ algorithm = useSSLMac ? "SslMacMD5" : "HmacMD5";
} else if (macAlg == M_SHA) {
- algorithm = tls ? "HmacSHA1" : "SslMacSHA1";
+ algorithm = useSSLMac ? "SslMacSHA1" : "HmacSHA1";
} else if (macAlg == M_SHA256) {
algorithm = "HmacSHA256"; // TLS 1.2+
} else if (macAlg == M_SHA384) {
@@ -122,6 +125,8 @@
* @param offset start of compressed record data
* @param len the size of the compressed record
* @param isSimulated if true, simulate the MAC computation
+ *
+ * @return the MAC result
*/
final byte[] compute(byte type, byte buf[],
int offset, int len, boolean isSimulated) {
@@ -130,7 +135,8 @@
}
if (!isSimulated) {
- byte[] additional = acquireAuthenticationBytes(type, len);
+ // Uses the implicit sequence number for the computation.
+ byte[] additional = acquireAuthenticationBytes(type, len, null);
mac.update(additional);
}
mac.update(buf, offset, len);
@@ -149,15 +155,22 @@
* @param bb a ByteBuffer in which the position and limit
* demarcate the data to be MAC'd.
* @param isSimulated if true, simulate the MAC computation
+ * @param sequence the explicit sequence number, or null if using
+ * the implicit sequence number for the computation
+ *
+ * @return the MAC result
*/
- final byte[] compute(byte type, ByteBuffer bb, boolean isSimulated) {
+ final byte[] compute(byte type, ByteBuffer bb,
+ byte[] sequence, boolean isSimulated) {
+
if (macAlg.size == 0) {
return nullMAC;
}
if (!isSimulated) {
+ // Uses the explicit sequence number for the computation.
byte[] additional =
- acquireAuthenticationBytes(type, bb.remaining());
+ acquireAuthenticationBytes(type, bb.remaining(), sequence);
mac.update(additional);
}
mac.update(bb);
@@ -165,5 +178,22 @@
return mac.doFinal();
}
+ /**
+ * Compute and returns the MAC for the remaining data
+ * in this ByteBuffer.
+ *
+ * On return, the bb position == limit, and limit will
+ * have not changed.
+ *
+ * @param type record type
+ * @param bb a ByteBuffer in which the position and limit
+ * demarcate the data to be MAC'd.
+ * @param isSimulated if true, simulate the the MAC computation
+ *
+ * @return the MAC result
+ */
+ final byte[] compute(byte type, ByteBuffer bb, boolean isSimulated) {
+ // Uses the implicit sequence number for the computation.
+ return compute(type, bb, null, isSimulated);
+ }
}
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/MaxFragmentLengthExtension.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.IOException;
+import javax.net.ssl.SSLProtocolException;
+
+/*
+ * [RFC6066] TLS specifies a fixed maximum plaintext fragment length of
+ * 2^14 bytes. It may be desirable for constrained clients to negotiate
+ * a smaller maximum fragment length due to memory limitations or bandwidth
+ * limitations.
+ *
+ * In order to negotiate smaller maximum fragment lengths, clients MAY
+ * include an extension of type "max_fragment_length" in the (extended)
+ * client hello. The "extension_data" field of this extension SHALL
+ * contain:
+ *
+ * enum{
+ * 2^9(1), 2^10(2), 2^11(3), 2^12(4), (255)
+ * } MaxFragmentLength;
+ *
+ * whose value is the desired maximum fragment length.
+ */
+final class MaxFragmentLengthExtension extends HelloExtension {
+
+ private static final int MAX_FRAGMENT_LENGTH_512 = 1; // 2^9
+ private static final int MAX_FRAGMENT_LENGTH_1024 = 2; // 2^10
+ private static final int MAX_FRAGMENT_LENGTH_2048 = 3; // 2^11
+ private static final int MAX_FRAGMENT_LENGTH_4096 = 4; // 2^12
+
+ final int maxFragmentLength;
+
+ MaxFragmentLengthExtension(int fragmentSize) {
+ super(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+
+ if (fragmentSize < 1024) {
+ maxFragmentLength = MAX_FRAGMENT_LENGTH_512;
+ } else if (fragmentSize < 2048) {
+ maxFragmentLength = MAX_FRAGMENT_LENGTH_1024;
+ } else if (fragmentSize < 4096) {
+ maxFragmentLength = MAX_FRAGMENT_LENGTH_2048;
+ } else {
+ maxFragmentLength = MAX_FRAGMENT_LENGTH_4096;
+ }
+ }
+
+ MaxFragmentLengthExtension(HandshakeInStream s, int len)
+ throws IOException {
+ super(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+
+ // check the extension length
+ if (len != 1) {
+ throw new SSLProtocolException("Invalid " + type + " extension");
+ }
+
+ maxFragmentLength = s.getInt8();
+ if ((maxFragmentLength > 4) || (maxFragmentLength < 1)) {
+ throw new SSLProtocolException("Invalid " + type + " extension");
+ }
+ }
+
+ // Length of the encoded extension, including the type and length fields
+ @Override
+ int length() {
+ return 5; // 4: extension type and length fields
+ // 1: MaxFragmentLength field
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putInt16(type.id);
+ s.putInt16(1);
+ s.putInt8(maxFragmentLength);
+ }
+
+ int getMaxFragLen() {
+ switch (maxFragmentLength) {
+ case MAX_FRAGMENT_LENGTH_512:
+ return 512;
+ case MAX_FRAGMENT_LENGTH_1024:
+ return 1024;
+ case MAX_FRAGMENT_LENGTH_2048:
+ return 2048;
+ case MAX_FRAGMENT_LENGTH_4096:
+ return 4096;
+ }
+
+ // unlikely to happen
+ return -1;
+ }
+
+ static boolean needFragLenNego(int fragmentSize) {
+ return (fragmentSize > 0) && (fragmentSize <= 4096);
+ }
+
+ static int getValidMaxFragLen(int fragmentSize) {
+ if (fragmentSize < 1024) {
+ return 512;
+ } else if (fragmentSize < 2048) {
+ return 1024;
+ } else if (fragmentSize < 4096) {
+ return 2048;
+ } else if (fragmentSize == 4096) {
+ return 4096;
+ } else {
+ return 16384;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Extension " + type + ", max_fragment_length: " +
+ "(2^" + (maxFragmentLength + 8) + ")";
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/OutputRecord.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/OutputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,7 +23,6 @@
* questions.
*/
-
package sun.security.ssl;
import java.io.*;
@@ -35,92 +34,61 @@
/**
- * SSL 3.0 records, as written to a TCP stream.
- *
- * Each record has a message area that starts out with data supplied by the
- * application. It may grow/shrink due to compression and will be modified
- * in place for mac-ing and encryption.
- *
- * Handshake records have additional needs, notably accumulation of a set
- * of hashes which are used to establish that handshaking was done right.
- * Handshake records usually have several handshake messages each, and we
- * need message-level control over what's hashed.
+ * {@code OutputRecord} takes care of the management of SSL/TLS/DTLS output
+ * records, including buffering, encryption, handshake messages marshal, etc.
*
* @author David Brownell
*/
-class OutputRecord extends ByteArrayOutputStream implements Record {
+abstract class OutputRecord extends ByteArrayOutputStream
+ implements Record, Closeable {
+
+ /* Class and subclass dynamic debugging support */
+ static final Debug debug = Debug.getInstance("ssl");
- private HandshakeHash handshakeHash;
- private int lastHashed;
- private boolean firstMessage;
- final private byte contentType;
- private int headerOffset;
+ Authenticator writeAuthenticator;
+ CipherBox writeCipher;
+
+ HandshakeHash handshakeHash;
+ boolean firstMessage;
// current protocol version, sent as record version
- ProtocolVersion protocolVersion;
+ ProtocolVersion protocolVersion;
// version for the ClientHello message. Only relevant if this is a
// client handshake record. If set to ProtocolVersion.SSL20Hello,
// the V3 client hello is converted to V2 format.
- private ProtocolVersion helloVersion;
+ ProtocolVersion helloVersion;
+
+ // Is it the first application record to write?
+ boolean isFirstAppOutputRecord = true;
- /* Class and subclass dynamic debugging support */
- static final Debug debug = Debug.getInstance("ssl");
+ // packet size
+ int packetSize;
+
+ // fragment size
+ int fragmentSize;
+
+ // closed or not?
+ boolean isClosed;
/*
- * Default constructor makes a record supporting the maximum
- * SSL record size. It allocates the header bytes directly.
- *
- * The structure of the byte buffer looks like:
- *
- * |---------+--------+-------+---------------------------------|
- * | unused | header | IV | content, MAC/TAG, padding, etc. |
- * | headerPlusMaxIVSize |
- *
- * unused: unused part of the buffer of size
- *
- * headerPlusMaxIVSize - header size - IV size
- *
- * When this object is created, we don't know the protocol
- * version number, IV length, etc., so reserve space in front
- * to avoid extra data movement (copies).
- * header: the header of an SSL record
- * IV: the optional IV/nonce field, it is only required for block
- * (TLS 1.1 or later) and AEAD cipher suites.
- *
- * @param type the content type for the record
+ * Mappings from V3 cipher suite encodings to their pure V2 equivalents.
+ * This is taken from the SSL V3 specification, Appendix E.
*/
- OutputRecord(byte type, int size) {
- super(size);
- this.protocolVersion = ProtocolVersion.DEFAULT;
- this.helloVersion = ProtocolVersion.DEFAULT_HELLO;
- firstMessage = true;
- count = headerPlusMaxIVSize;
- contentType = type;
- lastHashed = count;
- headerOffset = headerPlusMaxIVSize - headerSize;
+ private static int[] V3toV2CipherMap1 =
+ {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07};
+ private static int[] V3toV2CipherMap3 =
+ {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0};
+
+ OutputRecord() {
+ this.writeCipher = CipherBox.NULL;
+ this.firstMessage = true;
+ this.fragmentSize = Record.maxDataSize;
+
+ // Please set packetSize and protocolVersion in the implementation.
}
- OutputRecord(byte type) {
- this(type, recordSize(type));
- }
-
- /**
- * Get the size of the buffer we need for records of the specified
- * type.
- */
- private static int recordSize(byte type) {
- if ((type == ct_change_cipher_spec) || (type == ct_alert)) {
- return maxAlertRecordSize;
- } else {
- return maxRecordSize;
- }
- }
-
- /*
- * Updates the SSL version of this record.
- */
- synchronized void setVersion(ProtocolVersion protocolVersion) {
+ void setVersion(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
}
@@ -132,441 +100,364 @@
}
/*
- * Reset the record so that it can be refilled, starting
- * immediately after the header.
- */
- @Override
- public synchronized void reset() {
- super.reset();
- count = headerPlusMaxIVSize;
- lastHashed = count;
- headerOffset = headerPlusMaxIVSize - headerSize;
- }
-
- /*
* For handshaking, we need to be able to hash every byte above the
* record marking layer. This is where we're guaranteed to see those
* bytes, so this is where we can hash them.
*/
void setHandshakeHash(HandshakeHash handshakeHash) {
- assert(contentType == ct_handshake);
this.handshakeHash = handshakeHash;
}
/*
- * We hash (the plaintext) on demand. There is one place where
- * we want to access the hash in the middle of a record: client
- * cert message gets hashed, and part of the same record is the
- * client cert verify message which uses that hash. So we track
- * how much of each record we've hashed so far.
- */
- void doHashes() {
- int len = count - lastHashed;
-
- if (len > 0) {
- hashInternal(buf, lastHashed, len);
- lastHashed = count;
- }
- }
-
- /*
- * Need a helper function so we can hash the V2 hello correctly
- */
- private void hashInternal(byte buf [], int offset, int len) {
- if (debug != null && Debug.isOn("data")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
-
- System.out.println("[write] MD5 and SHA1 hashes: len = "
- + len);
- hd.encodeBuffer(new ByteArrayInputStream(buf,
- lastHashed, len), System.out);
- } catch (IOException e) { }
- }
-
- handshakeHash.update(buf, lastHashed, len);
- lastHashed = count;
- }
-
- /*
* Return true iff the record is empty -- to avoid doing the work
* of sending empty records over the network.
*/
boolean isEmpty() {
- return count == headerPlusMaxIVSize;
- }
-
- /*
- * Return true if the record is of an alert of the given description.
- *
- * Per SSL/TLS specifications, alert messages convey the severity of the
- * message (warning or fatal) and a description of the alert. An alert
- * is defined with a two bytes struct, {byte level, byte description},
- * following after the header bytes.
- */
- boolean isAlert(byte description) {
- if ((count > (headerPlusMaxIVSize + 1)) && (contentType == ct_alert)) {
- return buf[headerPlusMaxIVSize + 1] == description;
- }
-
return false;
}
- /*
- * Encrypt ... length may grow due to block cipher padding, or
- * message authentication code or tag.
- */
- void encrypt(Authenticator authenticator, CipherBox box)
- throws IOException {
+ boolean seqNumIsHuge() {
+ return (writeAuthenticator != null) &&
+ writeAuthenticator.seqNumIsHuge();
+ }
- // In case we are automatically flushing a handshake stream, make
- // sure we have hashed the message first.
- //
- // when we support compression, hashing can't go here
- // since it'll need to be done on the uncompressed data,
- // and the MAC applies to the compressed data.
- if (contentType == ct_handshake) {
- doHashes();
- }
+ // SSLEngine and SSLSocket
+ abstract void encodeAlert(byte level, byte description) throws IOException;
+
+ // SSLEngine and SSLSocket
+ abstract void encodeHandshake(byte[] buffer,
+ int offset, int length) throws IOException;
+
+ // SSLEngine and SSLSocket
+ abstract void encodeChangeCipherSpec() throws IOException;
- // Requires message authentication code for stream and block
- // cipher suites.
- if (authenticator instanceof MAC) {
- MAC signer = (MAC)authenticator;
- if (signer.MAClen() != 0) {
- byte[] hash = signer.compute(contentType, buf,
- headerPlusMaxIVSize, count - headerPlusMaxIVSize, false);
- write(hash);
- }
- }
+ // apply to SSLEngine only
+ Ciphertext encode(ByteBuffer[] sources, int offset, int length,
+ ByteBuffer destination) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ // apply to SSLEngine only
+ void encodeV2NoCipher() throws IOException {
+ throw new UnsupportedOperationException();
+ }
- if (!box.isNullCipher()) {
- // Requires explicit IV/nonce for CBC/AEAD cipher suites for
- // TLS 1.1 or later.
- if ((protocolVersion.v >= ProtocolVersion.TLS11.v) &&
- (box.isCBCMode() || box.isAEADMode())) {
- byte[] nonce = box.createExplicitNonce(authenticator,
- contentType, count - headerPlusMaxIVSize);
- int offset = headerPlusMaxIVSize - nonce.length;
- System.arraycopy(nonce, 0, buf, offset, nonce.length);
- headerOffset = offset - headerSize;
- } else {
- headerOffset = headerPlusMaxIVSize - headerSize;
- }
+ // apply to SSLSocket only
+ void deliver(byte[] source, int offset, int length) throws IOException {
+ throw new UnsupportedOperationException();
+ }
- // encrypt the content
- int offset = headerPlusMaxIVSize;
- if (!box.isAEADMode()) {
- // The explicit IV can be encrypted.
- offset = headerOffset + headerSize;
- } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode
+ // apply to SSLSocket only
+ void setDeliverStream(OutputStream outputStream) {
+ throw new UnsupportedOperationException();
+ }
- count = offset + box.encrypt(buf, offset, count - offset);
- }
+ // apply to SSLEngine only
+ Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
+ throw new UnsupportedOperationException();
}
- /*
- * Tell how full the buffer is ... for filling it with application or
- * handshake data.
- */
- final int availableDataBytes() {
- int dataSize = count - headerPlusMaxIVSize;
- return maxDataSize - dataSize;
+ void changeWriteCiphers(Authenticator writeAuthenticator,
+ CipherBox writeCipher) throws IOException {
+
+ encodeChangeCipherSpec();
+
+ /*
+ * Dispose of any intermediate state in the underlying cipher.
+ * For PKCS11 ciphers, this will release any attached sessions,
+ * and thus make finalization faster.
+ *
+ * Since MAC's doFinal() is called for every SSL/TLS packet, it's
+ * not necessary to do the same with MAC's.
+ */
+ writeCipher.dispose();
+
+ this.writeAuthenticator = writeAuthenticator;
+ this.writeCipher = writeCipher;
+ this.isFirstAppOutputRecord = true;
}
- /*
- * Increases the capacity if necessary to ensure that it can hold
- * at least the number of elements specified by the minimum
- * capacity argument.
- *
- * Note that the increased capacity is only can be used for held
- * record buffer. Please DO NOT update the availableDataBytes()
- * according to the expended buffer capacity.
- *
- * @see availableDataBytes()
- */
- private void ensureCapacity(int minCapacity) {
- // overflow-conscious code
- if (minCapacity > buf.length) {
- buf = Arrays.copyOf(buf, minCapacity);
+ void changePacketSize(int packetSize) {
+ this.packetSize = packetSize;
+ }
+
+ void changeFragmentSize(int fragmentSize) {
+ this.fragmentSize = fragmentSize;
+ }
+
+ int getMaxPacketSize() {
+ return packetSize;
+ }
+
+ // apply to DTLS SSLEngine
+ void initHandshaker() {
+ // blank
+ }
+
+ @Override
+ synchronized public void close() throws IOException {
+ if (!isClosed) {
+ isClosed = true;
+ writeCipher.dispose();
}
}
- /*
- * Return the type of SSL record that's buffered here.
- */
- final byte contentType() {
- return contentType;
- }
+ //
+ // shared helpers
+ //
+
+ // Encrypt a fragment and wrap up a record.
+ //
+ // To be consistent with the spec of SSLEngine.wrap() methods, the
+ // destination ByteBuffer's position is updated to reflect the amount
+ // of data produced. The limit remains the same.
+ static long encrypt(Authenticator authenticator,
+ CipherBox encCipher, byte contentType, ByteBuffer destination,
+ int headerOffset, int dstLim, int headerSize,
+ ProtocolVersion protocolVersion, boolean isDTLS) {
+
+ byte[] sequenceNumber = null;
+ int dstContent = destination.position();
- /*
- * Write the record out on the stream. Note that you must have (in
- * order) compressed the data, appended the MAC, and encrypted it in
- * order for the record to be understood by the other end. (Some of
- * those steps will be null early in handshaking.)
- *
- * Note that this does no locking for the connection, it's required
- * that synchronization be done elsewhere. Also, this does its work
- * in a single low level write, for efficiency.
- */
- void write(OutputStream s, boolean holdRecord,
- ByteArrayOutputStream heldRecordBuffer) throws IOException {
+ // Acquire the current sequence number before using.
+ if (isDTLS) {
+ sequenceNumber = authenticator.sequenceNumber();
+ }
+
+ // "flip" but skip over header again, add MAC & encrypt
+ if (authenticator instanceof MAC) {
+ MAC signer = (MAC)authenticator;
+ if (signer.MAClen() != 0) {
+ byte[] hash = signer.compute(contentType, destination, false);
- /*
- * Don't emit content-free records. (Even change cipher spec
- * messages have a byte of data!)
- */
- if (count == headerPlusMaxIVSize) {
- return;
+ /*
+ * position was advanced to limit in MAC compute above.
+ *
+ * Mark next area as writable (above layers should have
+ * established that we have plenty of room), then write
+ * out the hash.
+ */
+ destination.limit(destination.limit() + hash.length);
+ destination.put(hash);
+
+ // reset the position and limit
+ destination.limit(destination.position());
+ destination.position(dstContent);
+ }
}
- int length = count - headerOffset - headerSize;
- // "should" really never write more than about 14 Kb...
- if (length < 0) {
- throw new SSLException("output record size too small: "
- + length);
+ if (!encCipher.isNullCipher()) {
+ if (protocolVersion.useTLS11PlusSpec() &&
+ (encCipher.isCBCMode() || encCipher.isAEADMode())) {
+ byte[] nonce = encCipher.createExplicitNonce(
+ authenticator, contentType, destination.remaining());
+ destination.position(headerOffset + headerSize);
+ destination.put(nonce);
+ }
+ if (!encCipher.isAEADMode()) {
+ // The explicit IV in TLS 1.1 and later can be encrypted.
+ destination.position(headerOffset + headerSize);
+ } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode
+
+ // Encrypt may pad, so again the limit may be changed.
+ encCipher.encrypt(destination, dstLim);
+ } else {
+ destination.position(destination.limit());
+ }
+
+ // Finish out the record header.
+ int fragLen = destination.limit() - headerOffset - headerSize;
+
+ destination.put(headerOffset, contentType); // content type
+ destination.put(headerOffset + 1, protocolVersion.major);
+ destination.put(headerOffset + 2, protocolVersion.minor);
+ if (!isDTLS) {
+ // fragment length
+ destination.put(headerOffset + 3, (byte)(fragLen >> 8));
+ destination.put(headerOffset + 4, (byte)fragLen);
+ } else {
+ // epoch and sequence_number
+ destination.put(headerOffset + 3, sequenceNumber[0]);
+ destination.put(headerOffset + 4, sequenceNumber[1]);
+ destination.put(headerOffset + 5, sequenceNumber[2]);
+ destination.put(headerOffset + 6, sequenceNumber[3]);
+ destination.put(headerOffset + 7, sequenceNumber[4]);
+ destination.put(headerOffset + 8, sequenceNumber[5]);
+ destination.put(headerOffset + 9, sequenceNumber[6]);
+ destination.put(headerOffset + 10, sequenceNumber[7]);
+
+ // fragment length
+ destination.put(headerOffset + 11, (byte)(fragLen >> 8));
+ destination.put(headerOffset + 12, (byte)fragLen);
+
+ // Increase the sequence number for next use.
+ authenticator.increaseSequenceNumber();
}
- if (debug != null
- && (Debug.isOn("record") || Debug.isOn("handshake"))) {
- if ((debug != null && Debug.isOn("record"))
- || contentType() == ct_change_cipher_spec)
- System.out.println(Thread.currentThread().getName()
- // v3.0/v3.1 ...
- + ", WRITE: " + protocolVersion
- + " " + InputRecord.contentName(contentType())
- + ", length = " + length);
+ // Update destination position to reflect the amount of data produced.
+ destination.position(destination.limit());
+
+ return Authenticator.toLong(sequenceNumber);
+ }
+
+ // Encrypt a fragment and wrap up a record.
+ //
+ // Uses the internal expandable buf variable and the current
+ // protocolVersion variable.
+ void encrypt(Authenticator authenticator,
+ CipherBox encCipher, byte contentType, int headerSize) {
+
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+
+ // "flip" but skip over header again, add MAC & encrypt
+ int macLen = 0;
+ if (authenticator instanceof MAC) {
+ MAC signer = (MAC)authenticator;
+ macLen = signer.MAClen();
+ if (macLen != 0) {
+ byte[] hash = signer.compute(contentType,
+ buf, position, (count - position), false);
+
+ write(hash, 0, hash.length);
+ }
}
- /*
- * If this is the initial ClientHello on this connection and
- * we're not trying to resume a (V3) session then send a V2
- * ClientHello instead so we can detect V2 servers cleanly.
- */
- if (firstMessage && useV2Hello()) {
- byte[] v3Msg = new byte[length - 4];
- System.arraycopy(buf, headerPlusMaxIVSize + 4,
- v3Msg, 0, v3Msg.length);
- headerOffset = 0; // reset the header offset
- V3toV2ClientHello(v3Msg);
- handshakeHash.reset();
- lastHashed = 2;
- doHashes();
- if (debug != null && Debug.isOn("record")) {
- System.out.println(
- Thread.currentThread().getName()
- + ", WRITE: SSLv2 client hello message"
- + ", length = " + (count - 2)); // 2 byte SSLv2 header
+ if (!encCipher.isNullCipher()) {
+ // Requires explicit IV/nonce for CBC/AEAD cipher suites for
+ // TLS 1.1 or later.
+ if (protocolVersion.useTLS11PlusSpec() &&
+ (encCipher.isCBCMode() || encCipher.isAEADMode())) {
+
+ byte[] nonce = encCipher.createExplicitNonce(
+ authenticator, contentType, (count - position));
+ int noncePos = position - nonce.length;
+ System.arraycopy(nonce, 0, buf, noncePos, nonce.length);
+ }
+
+ if (!encCipher.isAEADMode()) {
+ // The explicit IV in TLS 1.1 and later can be encrypted.
+ position = headerSize;
+ } // Otherwise, DON'T encrypt the nonce_explicit for AEAD mode
+
+ // increase buf capacity if necessary
+ int fragSize = count - position;
+ int packetSize =
+ encCipher.calculatePacketSize(fragSize, macLen, headerSize);
+ if (packetSize > (buf.length - position)) {
+ byte[] newBuf = new byte[position + packetSize];
+ System.arraycopy(buf, 0, newBuf, 0, count);
+ buf = newBuf;
}
- } else {
- /*
- * Fill out the header, write it and the message.
- */
- buf[headerOffset + 0] = contentType;
- buf[headerOffset + 1] = protocolVersion.major;
- buf[headerOffset + 2] = protocolVersion.minor;
- buf[headerOffset + 3] = (byte)(length >> 8);
- buf[headerOffset + 4] = (byte)(length);
+
+ // Encrypt may pad, so again the count may be changed.
+ count = position +
+ encCipher.encrypt(buf, position, (count - position));
}
- firstMessage = false;
+
+ // Fill out the header, write it and the message.
+ int fragLen = count - headerSize;
+ buf[0] = contentType;
+ buf[1] = protocolVersion.major;
+ buf[2] = protocolVersion.minor;
+ buf[3] = (byte)((fragLen >> 8) & 0xFF);
+ buf[4] = (byte)(fragLen & 0xFF);
+ }
+
+ static ByteBuffer encodeV2ClientHello(
+ byte[] fragment, int offset, int length) throws IOException {
+
+ int v3SessIdLenOffset = offset + 34; // 2: client_version
+ // 32: random
+
+ int v3SessIdLen = fragment[v3SessIdLenOffset];
+ int v3CSLenOffset = v3SessIdLenOffset + 1 + v3SessIdLen;
+ int v3CSLen = ((fragment[v3CSLenOffset] & 0xff) << 8) +
+ (fragment[v3CSLenOffset + 1] & 0xff);
+ int cipherSpecs = v3CSLen / 2; // 2: cipher spec size
+
+ // Estimate the max V2ClientHello message length
+ //
+ // 11: header size
+ // (cipherSpecs * 6): cipher_specs
+ // 6: one cipher suite may need 6 bytes, see V3toV2CipherSuite.
+ // 3: placeholder for the TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ // signaling cipher suite
+ // 32: challenge size
+ int v2MaxMsgLen = 11 + (cipherSpecs * 6) + 3 + 32;
+
+ // Create a ByteBuffer backed by an accessible byte array.
+ byte[] dstBytes = new byte[v2MaxMsgLen];
+ ByteBuffer dstBuf = ByteBuffer.wrap(dstBytes);
/*
- * The upper levels may want us to delay sending this packet so
- * multiple TLS Records can be sent in one (or more) TCP packets.
- * If so, add this packet to the heldRecordBuffer.
- *
- * NOTE: all writes have been synchronized by upper levels.
+ * Copy over the cipher specs. We don't care about actually
+ * translating them for use with an actual V2 server since
+ * we only talk V3. Therefore, just copy over the V3 cipher
+ * spec values with a leading 0.
*/
- int debugOffset = 0;
- if (holdRecord) {
- /*
- * If holdRecord is true, we must have a heldRecordBuffer.
- *
- * Don't worry about the override of writeBuffer(), because
- * when holdRecord is true, the implementation in this class
- * will be used.
- */
- writeBuffer(heldRecordBuffer,
- buf, headerOffset, count - headerOffset, debugOffset);
- } else {
- // It's time to send, do we have buffered data?
- // May or may not have a heldRecordBuffer.
- if (heldRecordBuffer != null && heldRecordBuffer.size() > 0) {
- int heldLen = heldRecordBuffer.size();
-
- // Ensure the capacity of this buffer.
- int newCount = count + heldLen - headerOffset;
- ensureCapacity(newCount);
-
- // Slide everything in the buffer to the right.
- System.arraycopy(buf, headerOffset,
- buf, heldLen, count - headerOffset);
-
- // Prepend the held record to the buffer.
- System.arraycopy(
- heldRecordBuffer.toByteArray(), 0, buf, 0, heldLen);
- count = newCount;
- headerOffset = 0;
-
- // Clear the held buffer.
- heldRecordBuffer.reset();
-
- // The held buffer has been dumped, set the debug dump offset.
- debugOffset = heldLen;
- }
- writeBuffer(s, buf, headerOffset,
- count - headerOffset, debugOffset);
- }
-
- reset();
- }
+ int v3CSOffset = v3CSLenOffset + 2; // skip length field
+ int v2CSLen = 0;
- /*
- * Actually do the write here. For SSLEngine's HS data,
- * we'll override this method and let it take the appropriate
- * action.
- */
- void writeBuffer(OutputStream s, byte [] buf, int off, int len,
- int debugOffset) throws IOException {
- s.write(buf, off, len);
- s.flush();
-
- // Output only the record from the specified debug offset.
- if (debug != null && Debug.isOn("packet")) {
- try {
- HexDumpEncoder hd = new HexDumpEncoder();
-
- System.out.println("[Raw write]: length = " +
- (len - debugOffset));
- hd.encodeBuffer(new ByteArrayInputStream(buf,
- off + debugOffset, len - debugOffset), System.out);
- } catch (IOException e) { }
- }
- }
-
- /*
- * Return whether the buffer contains a ClientHello message that should
- * be converted to V2 format.
- */
- private boolean useV2Hello() {
- return firstMessage
- && (helloVersion == ProtocolVersion.SSL20Hello)
- && (contentType == ct_handshake)
- && (buf[headerOffset + 5] == HandshakeMessage.ht_client_hello)
- // 5: recode header size
- && (buf[headerPlusMaxIVSize + 4 + 2 + 32] == 0);
- // V3 session ID is empty
- // 4: handshake header size
- // 2: client_version in ClientHello
- // 32: random in ClientHello
- }
-
- /*
- * Detect "old" servers which are capable of SSL V2.0 protocol ... for
- * example, Netscape Commerce 1.0 servers. The V3 message is in the
- * header and the bytes passed as parameter. This routine translates
- * the V3 message into an equivalent V2 one.
- *
- * Note that the translation will strip off all hello extensions as
- * SSL V2.0 does not support hello extension.
- */
- private void V3toV2ClientHello(byte v3Msg []) throws SSLException {
- int v3SessionIdLenOffset = 2 + 32; // version + nonce
- int v3SessionIdLen = v3Msg[v3SessionIdLenOffset];
- int v3CipherSpecLenOffset = v3SessionIdLenOffset + 1 + v3SessionIdLen;
- int v3CipherSpecLen = ((v3Msg[v3CipherSpecLenOffset] & 0xff) << 8) +
- (v3Msg[v3CipherSpecLenOffset + 1] & 0xff);
- int cipherSpecs = v3CipherSpecLen / 2; // 2 bytes each in V3
-
- /*
- * Copy over the cipher specs. We don't care about actually translating
- * them for use with an actual V2 server since we only talk V3.
- * Therefore, just copy over the V3 cipher spec values with a leading
- * 0.
- */
- int v3CipherSpecOffset = v3CipherSpecLenOffset + 2; // skip length
- int v2CipherSpecLen = 0;
- count = 11;
+ dstBuf.position(11);
boolean containsRenegoInfoSCSV = false;
for (int i = 0; i < cipherSpecs; i++) {
byte byte1, byte2;
- byte1 = v3Msg[v3CipherSpecOffset++];
- byte2 = v3Msg[v3CipherSpecOffset++];
- v2CipherSpecLen += V3toV2CipherSuite(byte1, byte2);
+ byte1 = fragment[v3CSOffset++];
+ byte2 = fragment[v3CSOffset++];
+ v2CSLen += V3toV2CipherSuite(dstBuf, byte1, byte2);
if (!containsRenegoInfoSCSV &&
- byte1 == (byte)0x00 && byte2 == (byte)0xFF) {
+ byte1 == (byte)0x00 && byte2 == (byte)0xFF) {
containsRenegoInfoSCSV = true;
}
}
if (!containsRenegoInfoSCSV) {
- v2CipherSpecLen += V3toV2CipherSuite((byte)0x00, (byte)0xFF);
+ v2CSLen += V3toV2CipherSuite(dstBuf, (byte)0x00, (byte)0xFF);
}
/*
+ * Copy in the nonce.
+ */
+ dstBuf.put(fragment, (offset + 2), 32);
+
+ /*
* Build the first part of the V3 record header from the V2 one
* that's now buffered up. (Lengths are fixed up later).
*/
- buf[2] = HandshakeMessage.ht_client_hello;
- buf[3] = v3Msg[0]; // major version
- buf[4] = v3Msg[1]; // minor version
- buf[5] = (byte)(v2CipherSpecLen >>> 8);
- buf[6] = (byte)v2CipherSpecLen;
- buf[7] = 0;
- buf[8] = 0; // always no session
- buf[9] = 0;
- buf[10] = 32; // nonce length (always 32 in V3)
+ int msgLen = dstBuf.position() - 2; // Exclude the legth field itself
+ dstBuf.position(0);
+ dstBuf.put((byte)(0x80 | ((msgLen >>> 8) & 0xFF))); // pos: 0
+ dstBuf.put((byte)(msgLen & 0xFF)); // pos: 1
+ dstBuf.put(HandshakeMessage.ht_client_hello); // pos: 2
+ dstBuf.put(fragment[offset]); // major version, pos: 3
+ dstBuf.put(fragment[offset + 1]); // minor version, pos: 4
+ dstBuf.put((byte)(v2CSLen >>> 8)); // pos: 5
+ dstBuf.put((byte)(v2CSLen & 0xFF)); // pos: 6
+ dstBuf.put((byte)0x00); // session_id_length, pos: 7
+ dstBuf.put((byte)0x00); // pos: 8
+ dstBuf.put((byte)0x00); // challenge_length, pos: 9
+ dstBuf.put((byte)32); // pos: 10
- /*
- * Copy in the nonce.
- */
- System.arraycopy(v3Msg, 2, buf, count, 32);
- count += 32;
+ dstBuf.position(0);
+ dstBuf.limit(msgLen + 2);
- /*
- * Set the length of the message.
- */
- count -= 2; // don't include length field itself
- buf[0] = (byte)(count >>> 8);
- buf[0] |= 0x80;
- buf[1] = (byte)(count);
- count += 2;
+ return dstBuf;
}
- /*
- * Mappings from V3 cipher suite encodings to their pure V2 equivalents.
- * This is taken from the SSL V3 specification, Appendix E.
- */
- private static int[] V3toV2CipherMap1 =
- {-1, -1, -1, 0x02, 0x01, -1, 0x04, 0x05, -1, 0x06, 0x07};
- private static int[] V3toV2CipherMap3 =
- {-1, -1, -1, 0x80, 0x80, -1, 0x80, 0x80, -1, 0x40, 0xC0};
+ private static int V3toV2CipherSuite(ByteBuffer dstBuf,
+ byte byte1, byte byte2) {
+ dstBuf.put((byte)0);
+ dstBuf.put(byte1);
+ dstBuf.put(byte2);
- /*
- * See which matching pure-V2 cipher specs we need to include.
- * We are including these not because we are actually prepared
- * to talk V2 but because the Oracle Web Server insists on receiving
- * at least 1 "pure V2" cipher suite that it supports and returns an
- * illegal_parameter alert unless one is present. Rather than mindlessly
- * claiming to implement all documented pure V2 cipher suites the code below
- * just claims to implement the V2 cipher suite that is "equivalent"
- * in terms of cipher algorithm & exportability with the actual V3 cipher
- * suite that we do support.
- */
- private int V3toV2CipherSuite(byte byte1, byte byte2) {
- buf[count++] = 0;
- buf[count++] = byte1;
- buf[count++] = byte2;
-
- if (((byte2 & 0xff) > 0xA) ||
- (V3toV2CipherMap1[byte2] == -1)) {
+ if (((byte2 & 0xff) > 0xA) || (V3toV2CipherMap1[byte2] == -1)) {
return 3;
}
- buf[count++] = (byte)V3toV2CipherMap1[byte2];
- buf[count++] = 0;
- buf[count++] = (byte)V3toV2CipherMap3[byte2];
+ dstBuf.put((byte)V3toV2CipherMap1[byte2]);
+ dstBuf.put((byte)0);
+ dstBuf.put((byte)V3toV2CipherMap3[byte2]);
return 6;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Plaintext.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.nio.ByteBuffer;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+
+/*
+ * Plaintext
+ */
+final class Plaintext {
+ final static Plaintext PLAINTEXT_NULL = new Plaintext();
+
+ byte contentType;
+ byte majorVersion;
+ byte minorVersion;
+ int recordEpoch; // incremented on every cipher state change
+ long recordSN;
+ ByteBuffer fragment; // null if need to be reassembled
+
+ HandshakeStatus handshakeStatus; // null if not used or not handshaking
+
+ Plaintext() {
+ this.contentType = 0;
+ this.majorVersion = 0;
+ this.minorVersion = 0;
+ this.recordEpoch = -1;
+ this.recordSN = -1;
+ this.fragment = null;
+ this.handshakeStatus = null;
+ }
+
+ Plaintext(byte contentType, byte majorVersion, byte minorVersion,
+ int recordEpoch, long recordSN, ByteBuffer fragment) {
+
+ this.contentType = contentType;
+ this.majorVersion = majorVersion;
+ this.minorVersion = minorVersion;
+ this.recordEpoch = recordEpoch;
+ this.recordSN = recordSN;
+ this.fragment = fragment;
+
+ this.handshakeStatus = null;
+ }
+
+ public String toString() {
+ return "contentType: " + contentType + "/" +
+ "majorVersion: " + majorVersion + "/" +
+ "minorVersion: " + minorVersion + "/" +
+ "recordEpoch: " + recordEpoch + "/" +
+ "recordSN: 0x" + Long.toHexString(recordSN) + "/" +
+ "fragment: " + fragment;
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ProtocolList.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ProtocolList.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -124,8 +124,9 @@
ProtocolVersion selectProtocolVersion(ProtocolVersion protocolVersion) {
ProtocolVersion selectedVersion = null;
for (ProtocolVersion pv : protocols) {
- if (pv.v > protocolVersion.v) {
- break; // Safe to break here as this.protocols is sorted
+ if (pv.compareTo(protocolVersion) > 0) {
+ break; // Safe to break here as this.protocols is sorted,
+ // and DTLS and SSL/TLS protocols are not mixed.
}
selectedVersion = pv;
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ProtocolVersion.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ProtocolVersion.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
import java.util.*;
import java.security.CryptoPrimitive;
+import sun.security.ssl.CipherSuite.*;
/**
* Type safe enum for an SSL/TLS protocol version. Instances are obtained
@@ -61,9 +62,9 @@
// Dummy protocol version value for invalid SSLSession
final static ProtocolVersion NONE = new ProtocolVersion(-1, "NONE");
- // If enabled, send/ accept SSLv2 hello messages
- final static ProtocolVersion SSL20Hello = new ProtocolVersion(0x0002,
- "SSLv2Hello");
+ // If enabled, send/accept SSLv2 hello messages
+ final static ProtocolVersion SSL20Hello =
+ new ProtocolVersion(0x0002, "SSLv2Hello");
// SSL 3.0
final static ProtocolVersion SSL30 = new ProtocolVersion(0x0300, "SSLv3");
@@ -77,6 +78,19 @@
// TLS 1.2
final static ProtocolVersion TLS12 = new ProtocolVersion(0x0303, "TLSv1.2");
+ // DTLS 1.0
+ // {254, 255}, the version value of DTLS 1.0.
+ final static ProtocolVersion DTLS10 =
+ new ProtocolVersion(0xFEFF, "DTLSv1.0");
+
+ // No DTLS 1.1, that version number was skipped in order to harmonize
+ // version numbers with TLS.
+
+ // DTLS 1.2
+ // {254, 253}, the version value of DTLS 1.2.
+ final static ProtocolVersion DTLS12 =
+ new ProtocolVersion(0xFEFD, "DTLSv1.2");
+
private static final boolean FIPS = SunJSSE.isFIPS();
// minimum version we implement (SSL 3.0)
@@ -85,8 +99,11 @@
// maximum version we implement (TLS 1.2)
final static ProtocolVersion MAX = TLS12;
- // ProtocolVersion to use by default (TLS 1.2)
- final static ProtocolVersion DEFAULT = TLS12;
+ // SSL/TLS ProtocolVersion to use by default (TLS 1.2)
+ final static ProtocolVersion DEFAULT_TLS = TLS12;
+
+ // DTLS ProtocolVersion to use by default (TLS 1.2)
+ final static ProtocolVersion DEFAULT_DTLS = DTLS12;
// Default version for hello messages (SSLv2Hello)
final static ProtocolVersion DEFAULT_HELLO = FIPS ? TLS10 : SSL30;
@@ -108,10 +125,10 @@
// Initialize the available protocols.
static {
- Set<ProtocolVersion> protocols = new HashSet<>(5);
+ Set<ProtocolVersion> protocols = new HashSet<>(7);
ProtocolVersion[] pvs = new ProtocolVersion[] {
- SSL20Hello, SSL30, TLS10, TLS11, TLS12};
+ SSL20Hello, SSL30, TLS10, TLS11, TLS12, DTLS10, DTLS12};
EnumSet<CryptoPrimitive> cryptoPrimitives =
EnumSet.<CryptoPrimitive>of(CryptoPrimitive.KEY_AGREEMENT);
for (ProtocolVersion p : pvs) {
@@ -145,6 +162,10 @@
return TLS12;
} else if (v == SSL20Hello.v) {
return SSL20Hello;
+ } else if (v == DTLS10.v) {
+ return DTLS10;
+ } else if (v == DTLS12.v) {
+ return DTLS12;
} else {
int major = (v >>> 8) & 0xFF;
int minor = v & 0xFF;
@@ -172,8 +193,8 @@
}
if (FIPS && (name.equals(SSL30.name) || name.equals(SSL20Hello.name))) {
- throw new IllegalArgumentException
- ("Only TLS 1.0 or later allowed in FIPS mode");
+ throw new IllegalArgumentException(
+ "Only TLS 1.0 or later allowed in FIPS mode");
}
if (name.equals(SSL30.name)) {
@@ -186,6 +207,10 @@
return TLS12;
} else if (name.equals(SSL20Hello.name)) {
return SSL20Hello;
+ } else if (name.equals(DTLS10.name)) {
+ return DTLS10;
+ } else if (name.equals(DTLS12.name)) {
+ return DTLS12;
} else {
throw new IllegalArgumentException(name);
}
@@ -201,6 +226,90 @@
*/
@Override
public int compareTo(ProtocolVersion protocolVersion) {
- return this.v - protocolVersion.v;
+ if (maybeDTLSProtocol()) {
+ if (!protocolVersion.maybeDTLSProtocol()) {
+ throw new IllegalArgumentException("Not DTLS protocol");
+ }
+
+ return protocolVersion.v - this.v;
+ } else {
+ if (protocolVersion.maybeDTLSProtocol()) {
+ throw new IllegalArgumentException("Not TLS protocol");
+ }
+
+ return this.v - protocolVersion.v;
+ }
+ }
+
+ /**
+ * Returns true if a ProtocolVersion represents a DTLS protocol.
+ */
+ boolean isDTLSProtocol() {
+ return this.v == DTLS12.v || this.v == DTLS10.v;
+ }
+
+ /**
+ * Returns true if a ProtocolVersion may represent a DTLS protocol.
+ */
+ boolean maybeDTLSProtocol() {
+ return (this.major & 0x80) != 0;
+ }
+
+ boolean useTLS12PlusSpec() {
+ return maybeDTLSProtocol() ? (this.v <= DTLS12.v) : (this.v >= TLS12.v);
+ }
+
+ boolean useTLS11PlusSpec() {
+ return maybeDTLSProtocol() ? true : (this.v >= TLS11.v);
+ }
+
+ boolean useTLS10PlusSpec() {
+ return maybeDTLSProtocol() ? true : (this.v >= TLS10.v);
+ }
+
+ boolean obsoletes(CipherSuite suite) {
+ ProtocolVersion proto = this;
+ if (proto.isDTLSProtocol()) {
+ // DTLS bans stream ciphers.
+ if (suite.cipher.cipherType == CipherType.STREAM_CIPHER) {
+ return true;
+ }
+
+ proto = mapToTLSProtocol(this);
+ }
+
+ return (proto.v >= suite.obsoleted);
+ }
+
+ boolean supports(CipherSuite suite) {
+ ProtocolVersion proto = this;
+ if (proto.isDTLSProtocol()) {
+ // DTLS bans stream ciphers.
+ if (suite.cipher.cipherType == CipherType.STREAM_CIPHER) {
+ return false;
+ }
+
+ proto = mapToTLSProtocol(this);
+ }
+
+ return (proto.v >= suite.supported);
+ }
+
+ // Map a specified protocol to the corresponding TLS version.
+ //
+ // DTLS 1.2 -> TLS 1.2
+ // DTLS 1.0 -> TLS 1.1
+ private static ProtocolVersion mapToTLSProtocol(
+ ProtocolVersion protocolVersion) {
+
+ if (protocolVersion.isDTLSProtocol()) {
+ if (protocolVersion.v == DTLS10.v) {
+ protocolVersion = TLS11;
+ } else { // DTLS12
+ protocolVersion = TLS12;
+ }
+ }
+
+ return protocolVersion;
}
}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java Tue Jun 02 09:15:47 2015 -0700
@@ -73,8 +73,8 @@
this.protocolVersion = protocolVersion;
try {
- String s = ((protocolVersion.v >= ProtocolVersion.TLS12.v) ?
- "SunTls12RsaPremasterSecret" : "SunTlsRsaPremasterSecret");
+ String s = protocolVersion.useTLS12PlusSpec() ?
+ "SunTls12RsaPremasterSecret" : "SunTlsRsaPremasterSecret";
KeyGenerator kg = JsseJce.getKeyGenerator(s);
kg.init(new TlsRsaPremasterSecretParameterSpec(
maxVersion.v, protocolVersion.v), generator);
@@ -103,7 +103,7 @@
throw new SSLKeyException("Private key not of type RSA");
}
- if (currentVersion.v >= ProtocolVersion.TLS10.v) {
+ if (currentVersion.useTLS10PlusSpec()) {
encrypted = input.getBytes16();
} else {
encrypted = new byte [messageSize];
@@ -142,7 +142,7 @@
@Override
int messageLength() {
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
return encrypted.length + 2;
} else {
return encrypted.length;
@@ -151,7 +151,7 @@
@Override
void send(HandshakeOutStream s) throws IOException {
- if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (protocolVersion.useTLS10PlusSpec()) {
s.putBytes16(encrypted);
} else {
s.write(encrypted);
--- a/jdk/src/java.base/share/classes/sun/security/ssl/RandomCookie.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/RandomCookie.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -70,10 +70,10 @@
void print(PrintStream s) {
int i, gmt_unix_time;
- gmt_unix_time = random_bytes[0] << 24;
- gmt_unix_time += random_bytes[1] << 16;
- gmt_unix_time += random_bytes[2] << 8;
- gmt_unix_time += random_bytes[3];
+ gmt_unix_time = ((random_bytes[0] & 0xFF) << 24) |
+ ((random_bytes[1] & 0xFF) << 16) |
+ ((random_bytes[2] & 0xFF) << 8) |
+ (random_bytes[3] & 0xFF);
s.print("GMT: " + gmt_unix_time + " ");
s.print("bytes = { ");
--- a/jdk/src/java.base/share/classes/sun/security/ssl/Record.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/Record.java Tue Jun 02 09:15:47 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,21 +23,20 @@
* questions.
*/
-
package sun.security.ssl;
-
/**
- * SSL/TLS records, as pulled off (and put onto) a TCP stream. This is
+ * SSL/TLS/DTLS records, as pulled off (and put onto) a TCP stream. This is
* the base interface, which defines common information and interfaces
* used by both Input and Output records.
*
* @author David Brownell
*/
interface Record {
+
/*
- * There are four SSL record types, which are part of the interface
- * to this level (along with the maximum record size)
+ * There are four record types, which are part of the interface
+ * to this level (along with the maximum record size).
*
* enum { change_cipher_spec(20), alert(21), handshake(22),
* application_data(23), (255) } ContentType;
@@ -47,75 +46,20 @@
static final byte ct_handshake = 22;
static final byte ct_application_data = 23;
- static final int headerSize = 5; // SSLv3 record header
- static final int maxExpansion = 1024; // for bad compression
- static final int trailerSize = 20; // SHA1 hash size
+ static final int maxMacSize = 48; // the max supported MAC or
+ // AEAD tag size
static final int maxDataSize = 16384; // 2^14 bytes of data
static final int maxPadding = 256; // block cipher padding
- static final int maxIVLength = 256; // IV length
-
- /*
- * The size of the header plus the max IV length
- */
- static final int headerPlusMaxIVSize =
- headerSize // header
- + maxIVLength; // iv
+ static final int maxIVLength = 16; // the max supported IV length
- /*
- * SSL has a maximum record size. It's header, (compressed) data,
- * padding, and a trailer for the message authentication information (MAC
- * for block and stream ciphers, and message authentication tag for AEAD
- * ciphers).
- *
- * Some compression algorithms have rare cases where they expand the data.
- * As we don't support compression at this time, leave that out.
- */
- static final int maxRecordSize =
- headerPlusMaxIVSize // header + iv
- + maxDataSize // data
- + maxPadding // padding
- + trailerSize; // MAC or AEAD tag
-
- static final boolean enableCBCProtection =
- Debug.getBooleanProperty("jsse.enableCBCProtection", true);
+ static final int maxFragmentSize = 18432; // the max fragment size
+ // 2^14 + 2048
/*
- * For CBC protection in SSL3/TLS1, we break some plaintext into two
- * packets. Max application data size for the second packet.
+ * System property to enable/disable CBC protection in SSL3/TLS1.
*/
- static final int maxDataSizeMinusOneByteRecord =
- maxDataSize // max data size
- - ( // max one byte record size
- headerPlusMaxIVSize // header + iv
- + 1 // one byte data
- + maxPadding // padding
- + trailerSize // MAC
- );
-
- /*
- * The maximum large record size.
- *
- * Some SSL/TLS implementations support large fragment upto 2^15 bytes,
- * such as Microsoft. We support large incoming fragments.
- *
- * The maximum large record size is defined as maxRecordSize plus 2^14,
- * this is the amount OpenSSL is using.
- */
- static final int maxLargeRecordSize =
- maxRecordSize // Max size with a conforming implementation
- + maxDataSize; // extra 2^14 bytes for large data packets.
-
-
- /*
- * Maximum record size for alert and change cipher spec records.
- * They only contain 2 and 1 bytes of data, respectively.
- * Allocate a smaller array.
- */
- static final int maxAlertRecordSize =
- headerPlusMaxIVSize // header + iv
- + 2 // alert
- + maxPadding // padding
- + trailerSize; // MAC
+ static final boolean enableCBCProtection =
+ Debug.getBooleanProperty("jsse.enableCBCProtection", true);
/*
* The overflow values of integers of 8, 16 and 24 bits.
@@ -123,4 +67,27 @@
static final int OVERFLOW_OF_INT08 = (1 << 8);
static final int OVERFLOW_OF_INT16 = (1 << 16);
static final int OVERFLOW_OF_INT24 = (1 << 24);
+
+ /**
+ * Return a description for the given content type.
+ */
+ static String contentName(byte contentType) {
+ switch (contentType) {
+ case ct_change_cipher_spec:
+ return "Change Cipher Spec";
+ case ct_alert:
+ return "Alert";
+ case ct_handshake:
+ return "Handshake";
+ case ct_application_data:
+ return "Application Data";
+ default:
+ return "contentType = " + contentType;
+ }
+ }
+
+ static boolean isValidContentType(byte contentType) {
+ return (contentType == 20) || (contentType == 21) ||
+ (contentType == 22) || (contentType == 23);
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/RecordType.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+/*
+ * enumation of record type
+ */
+enum RecordType {
+
+ RECORD_CHANGE_CIPHER_SPEC (Record.ct_change_cipher_spec,
+ HandshakeMessage.ht_not_applicable),
+ RECORD_ALERT (Record.ct_alert,
+ HandshakeMessage.ht_not_applicable),
+ RECORD_HELLO_REQUEST (Record.ct_handshake,
+ HandshakeMessage.ht_hello_request),
+ RECORD_CLIENT_HELLO (Record.ct_handshake,
+ HandshakeMessage.ht_client_hello),
+ RECORD_SERVER_HELLO (Record.ct_handshake,
+ HandshakeMessage.ht_server_hello),
+ RECORD_HELLO_VERIFY_REQUEST (Record.ct_handshake,
+ HandshakeMessage.ht_hello_verify_request),
+ RECORD_NEW_SESSION_TICKET (Record.ct_handshake,
+ HandshakeMessage.ht_new_session_ticket),
+ RECORD_CERTIFICATE (Record.ct_handshake,
+ HandshakeMessage.ht_certificate),
+ RECORD_SERVER_KEY_EXCHANGE (Record.ct_handshake,
+ HandshakeMessage.ht_server_key_exchange),
+ RECORD_CERTIFICATE_REQUEST (Record.ct_handshake,
+ HandshakeMessage.ht_certificate_request),
+ RECORD_SERVER_HELLO_DONE (Record.ct_handshake,
+ HandshakeMessage.ht_server_hello_done),
+ RECORD_CERTIFICATE_VERIFY (Record.ct_handshake,
+ HandshakeMessage.ht_certificate_verify),
+ RECORD_CLIENT_KEY_EXCHANGE (Record.ct_handshake,
+ HandshakeMessage.ht_client_key_exchange),
+ RECORD_FINISHED (Record.ct_handshake,
+ HandshakeMessage.ht_finished),
+ RECORD_CERTIFICATE_URL (Record.ct_handshake,
+ HandshakeMessage.ht_certificate_url),
+ RECORD_CERTIFICATE_STATUS (Record.ct_handshake,
+ HandshakeMessage.ht_certificate_status),
+ RECORD_SUPPLIEMENTAL_DATA (Record.ct_handshake,
+ HandshakeMessage.ht_supplemental_data),
+ RECORD_APPLICATION_DATA (Record.ct_application_data,
+ HandshakeMessage.ht_not_applicable);
+
+ byte contentType;
+ byte handshakeType;
+
+ private RecordType(byte contentType, byte handshakeType) {
+ this.contentType = contentType;
+ this.handshakeType = handshakeType;
+ }
+
+ static RecordType valueOf(byte contentType, byte handshakeType) {
+ if (contentType == Record.ct_change_cipher_spec) {
+ return RECORD_CHANGE_CIPHER_SPEC;
+ } else if (contentType == Record.ct_alert) {
+ return RECORD_ALERT;
+ } else if (contentType == Record.ct_application_data) {
+ return RECORD_APPLICATION_DATA;
+ } else if (handshakeType == HandshakeMessage.ht_hello_request) {
+ return RECORD_HELLO_REQUEST;
+ } else if (handshakeType == HandshakeMessage.ht_client_hello) {
+ return RECORD_CLIENT_HELLO;
+ } else if (handshakeType == HandshakeMessage.ht_server_hello) {
+ return RECORD_SERVER_HELLO;
+ } else if (handshakeType == HandshakeMessage.ht_hello_verify_request) {
+ return RECORD_HELLO_VERIFY_REQUEST;
+ } else if (handshakeType == HandshakeMessage.ht_new_session_ticket) {
+ return RECORD_NEW_SESSION_TICKET;
+ } else if (handshakeType == HandshakeMessage.ht_certificate) {
+ return RECORD_CERTIFICATE;
+ } else if (handshakeType == HandshakeMessage.ht_server_key_exchange) {
+ return RECORD_SERVER_KEY_EXCHANGE;
+ } else if (handshakeType == HandshakeMessage.ht_certificate_request) {
+ return RECORD_CERTIFICATE_REQUEST;
+ } else if (handshakeType == HandshakeMessage.ht_server_hello_done) {
+ return RECORD_SERVER_HELLO_DONE;
+ } else if (handshakeType == HandshakeMessage.ht_certificate_verify) {
+ return RECORD_CERTIFICATE_VERIFY;
+ } else if (handshakeType == HandshakeMessage.ht_client_key_exchange) {
+ return RECORD_CLIENT_KEY_EXCHANGE;
+ } else if (handshakeType == HandshakeMessage.ht_finished) {
+ return RECORD_FINISHED;
+ } else if (handshakeType == HandshakeMessage.ht_certificate_url) {
+ return RECORD_CERTIFICATE_URL;
+ } else if (handshakeType == HandshakeMessage.ht_certificate_status) {
+ return RECORD_CERTIFICATE_STATUS;
+ } else if (handshakeType == HandshakeMessage.ht_supplemental_data) {
+ return RECORD_SUPPLIEMENTAL_DATA;
+ }
+
+ // otherwise, invalid record type
+ throw new IllegalArgumentException(
+ "Invalid record type (ContentType:" + contentType +
+ ", HandshakeType:" + handshakeType + ")");
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java Tue Jun 02 09:15:47 2015 -0700
@@ -352,18 +352,13 @@
components.add("ECDH_ANON");
}
break;
- case K_KRB5:
- if (!forCertPathOnly) {
- components.add("KRB5");
+ default:
+ if (ClientKeyExchangeService.find(keyExchange.name) != null) {
+ if (!forCertPathOnly) {
+ components.add(keyExchange.name);
+ }
}
- break;
- case K_KRB5_EXPORT:
- if (!forCertPathOnly) {
- components.add("KRB5_EXPORT");
- }
- break;
- default:
- // ignore
+ // otherwise ignore
}
return components;
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -62,6 +62,9 @@
private CipherSuiteList defaultClientCipherSuiteList;
private CipherSuiteList supportedCipherSuiteList;
+ // DTLS cookie exchange manager
+ private HelloCookieManager helloCookieManager;
+
SSLContextImpl() {
ephemeralKeyManager = new EphemeralKeyManager();
clientCache = new SSLSessionContextImpl();
@@ -175,11 +178,29 @@
return DummyX509KeyManager.INSTANCE;
}
+ abstract SSLEngine createSSLEngineImpl();
+ abstract SSLEngine createSSLEngineImpl(String host, int port);
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine() {
+ if (!isInitialized) {
+ throw new IllegalStateException("SSLContext is not initialized");
+ }
+ return createSSLEngineImpl();
+ }
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine(String host, int port) {
+ if (!isInitialized) {
+ throw new IllegalStateException("SSLContext is not initialized");
+ }
+ return createSSLEngineImpl(host, port);
+ }
+
@Override
protected SSLSocketFactory engineGetSocketFactory() {
if (!isInitialized) {
- throw new IllegalStateException(
- "SSLContextImpl is not initialized");
+ throw new IllegalStateException("SSLContext is not initialized");
}
return new SSLSocketFactoryImpl(this);
}
@@ -193,24 +214,6 @@
}
@Override
- protected SSLEngine engineCreateSSLEngine() {
- if (!isInitialized) {
- throw new IllegalStateException(
- "SSLContextImpl is not initialized");
- }
- return new SSLEngineImpl(this);
- }
-
- @Override
- protected SSLEngine engineCreateSSLEngine(String host, int port) {
- if (!isInitialized) {
- throw new IllegalStateException(
- "SSLContextImpl is not initialized");
- }
- return new SSLEngineImpl(this, host, port);
- }
-
- @Override
protected SSLSessionContext engineGetClientSessionContext() {
return clientCache;
}
@@ -236,6 +239,23 @@
return ephemeralKeyManager;
}
+ HelloCookieManager getHelloCookieManager() {
+ if (!isInitialized) {
+ throw new IllegalStateException("SSLContext is not initialized");
+ }
+
+ if (helloCookieManager == null) {
+ helloCookieManager = getHelloCookieManager(secureRandom);
+ }
+
+ return helloCookieManager;
+ }
+
+ HelloCookieManager getHelloCookieManager(SecureRandom secureRandom) {
+ throw new UnsupportedOperationException(
+ "Cookie exchange applies to DTLS only");
+ }
+
abstract SSLParameters getDefaultServerSSLParams();
abstract SSLParameters getDefaultClientSSLParams();
abstract SSLParameters getSupportedSSLParams();
@@ -319,12 +339,20 @@
(protocols == defaultClientProtocolList);
}
+ /**
+ * Return whether a protocol list is the original default enabled
+ * protocols. See: SSLSocket/SSLEngine.setEnabledProtocols()
+ */
+ boolean isDefaultCipherSuiteList(CipherSuiteList cipherSuites) {
+ return (cipherSuites == defaultClientCipherSuiteList) ||
+ (cipherSuites == defaultServerCipherSuiteList);
+ }
/*
* Return the list of all available CipherSuites with a priority of
* minPriority or above.
*/
- private CipherSuiteList getApplicableCipherSuiteList(
+ private static CipherSuiteList getApplicableCipherSuiteList(
ProtocolList protocols, boolean onlyEnabled) {
int minPriority = CipherSuite.SUPPORTED_SUITES_PRIORITY;
@@ -344,8 +372,8 @@
}
if (suite.isAvailable() &&
- suite.obsoleted > protocols.min.v &&
- suite.supported <= protocols.max.v) {
+ !protocols.min.obsoletes(suite) &&
+ protocols.max.supports(suite)) {
if (SSLAlgorithmConstraints.DEFAULT.permits(
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
suite.name, null)) {
@@ -353,10 +381,10 @@
}
} else if (debug != null &&
Debug.isOn("sslctx") && Debug.isOn("verbose")) {
- if (suite.obsoleted <= protocols.min.v) {
+ if (protocols.min.obsoletes(suite)) {
System.out.println(
"Ignoring obsoleted cipher suite: " + suite);
- } else if (suite.supported > protocols.max.v) {
+ } else if (!protocols.max.supports(suite)) {
System.out.println(
"Ignoring unsupported cipher suite: " + suite);
} else {
@@ -388,8 +416,25 @@
}
}
+ private static String[] getAvailableProtocols(
+ ProtocolVersion[] protocolCandidates) {
+
+ List<String> availableProtocols = Collections.<String>emptyList();
+ if (protocolCandidates != null && protocolCandidates.length != 0) {
+ availableProtocols = new ArrayList<>(protocolCandidates.length);
+ for (ProtocolVersion p : protocolCandidates) {
+ if (ProtocolVersion.availableProtocols.contains(p)) {
+ availableProtocols.add(p.name);
+ }
+ }
+ }
+
+ return availableProtocols.toArray(new String[0]);
+ }
+
+
/*
- * The SSLContext implementation for TLS/SSL algorithm
+ * The SSLContext implementation for SSL/(D)TLS algorithm
*
* SSL/TLS protocols specify the forward compatibility and version
* roll-back attack protections, however, a number of SSL/TLS server
@@ -418,14 +463,15 @@
*/
/*
- * The base abstract SSLContext implementation.
+ * The base abstract SSLContext implementation for the Transport Layer
+ * Security (TLS) protocols.
*
* This abstract class encapsulates supported and the default server
- * SSL parameters.
+ * SSL/TLS parameters.
*
* @see SSLContext
*/
- private abstract static class AbstractSSLContext extends SSLContextImpl {
+ private abstract static class AbstractTLSContext extends SSLContextImpl {
// parameters
private static final SSLParameters defaultServerSSLParams;
private static final SSLParameters supportedSSLParams;
@@ -482,20 +528,14 @@
return supportedSSLParams;
}
- static String[] getAvailableProtocols(
- ProtocolVersion[] protocolCandidates) {
+ @Override
+ SSLEngine createSSLEngineImpl() {
+ return new SSLEngineImpl(this, false);
+ }
- List<String> availableProtocols = Collections.<String>emptyList();
- if (protocolCandidates != null && protocolCandidates.length != 0) {
- availableProtocols = new ArrayList<>(protocolCandidates.length);
- for (ProtocolVersion p : protocolCandidates) {
- if (ProtocolVersion.availableProtocols.contains(p)) {
- availableProtocols.add(p.name);
- }
- }
- }
-
- return availableProtocols.toArray(new String[0]);
+ @Override
+ SSLEngine createSSLEngineImpl(String host, int port) {
+ return new SSLEngineImpl(this, host, port, false);
}
}
@@ -504,7 +544,7 @@
*
* @see SSLContext
*/
- public static final class TLS10Context extends AbstractSSLContext {
+ public static final class TLS10Context extends AbstractTLSContext {
private static final SSLParameters defaultClientSSLParams;
static {
@@ -537,7 +577,7 @@
*
* @see SSLContext
*/
- public static final class TLS11Context extends AbstractSSLContext {
+ public static final class TLS11Context extends AbstractTLSContext {
private static final SSLParameters defaultClientSSLParams;
static {
@@ -572,7 +612,7 @@
*
* @see SSLContext
*/
- public static final class TLS12Context extends AbstractSSLContext {
+ public static final class TLS12Context extends AbstractTLSContext {
private static final SSLParameters defaultClientSSLParams;
static {
@@ -605,12 +645,73 @@
}
/*
+ * The interface for the customized SSL/(D)TLS SSLContext.
+ *
+ * @see SSLContext
+ */
+ private static class CustomizedSSLProtocols {
+ private final static String PROPERTY_NAME = "jdk.tls.client.protocols";
+ static IllegalArgumentException reservedException = null;
+ static ArrayList<ProtocolVersion>
+ customizedProtocols = new ArrayList<>();
+
+ // Don't want a java.lang.LinkageError for illegal system property.
+ //
+ // Please don't throw exception in this static block. Otherwise,
+ // java.lang.LinkageError may be thrown during the instantiation of
+ // the provider service. Instead, please handle the initialization
+ // exception in the caller's constructor.
+ static {
+ String property = AccessController.doPrivileged(
+ new GetPropertyAction(PROPERTY_NAME));
+ if (property != null && property.length() != 0) {
+ // remove double quote marks from beginning/end of the property
+ if (property.length() > 1 && property.charAt(0) == '"' &&
+ property.charAt(property.length() - 1) == '"') {
+ property = property.substring(1, property.length() - 1);
+ }
+ }
+
+ if (property != null && property.length() != 0) {
+ String[] protocols = property.split(",");
+ for (int i = 0; i < protocols.length; i++) {
+ protocols[i] = protocols[i].trim();
+ // Is it a supported protocol name?
+ try {
+ ProtocolVersion pro =
+ ProtocolVersion.valueOf(protocols[i]);
+
+ if (SunJSSE.isFIPS() &&
+ ((pro.v == ProtocolVersion.SSL30.v) ||
+ (pro.v == ProtocolVersion.SSL20Hello.v))) {
+ reservedException = new IllegalArgumentException(
+ PROPERTY_NAME + ": " + pro +
+ " is not FIPS compliant");
+
+ break;
+ }
+
+ // ignore duplicated protocols
+ if (!customizedProtocols.contains(pro)) {
+ customizedProtocols.add(pro);
+ }
+ } catch (IllegalArgumentException iae) {
+ reservedException = new IllegalArgumentException(
+ PROPERTY_NAME + ": " + protocols[i] +
+ " is not a standard SSL protocol name", iae);
+ }
+ }
+ }
+ }
+ }
+
+ /*
* The SSLContext implementation for customized TLS protocols
*
* @see SSLContext
*/
- private static class CustomizedSSLContext extends AbstractSSLContext {
- private static final String PROPERTY_NAME = "jdk.tls.client.protocols";
+ private static class CustomizedTLSContext extends AbstractTLSContext {
+
private static final SSLParameters defaultClientSSLParams;
private static IllegalArgumentException reservedException = null;
@@ -621,78 +722,52 @@
// the provider service. Instead, let's handle the initialization
// exception in constructor.
static {
- // candidates for available protocols
- ProtocolVersion[] candidates;
-
- String property = AccessController.doPrivileged(
- new GetPropertyAction(PROPERTY_NAME));
- if (property == null || property.length() == 0) {
- // the default enabled client TLS protocols
- if (SunJSSE.isFIPS()) {
- candidates = new ProtocolVersion[] {
- ProtocolVersion.TLS10,
- ProtocolVersion.TLS11,
- ProtocolVersion.TLS12
- };
- } else {
- candidates = new ProtocolVersion[] {
- ProtocolVersion.SSL30,
- ProtocolVersion.TLS10,
- ProtocolVersion.TLS11,
- ProtocolVersion.TLS12
- };
- }
- } else {
- // remove double quote marks from beginning/end of the property
- if (property.length() > 1 && property.charAt(0) == '"' &&
- property.charAt(property.length() - 1) == '"') {
- property = property.substring(1, property.length() - 1);
- }
-
- String[] protocols = null;
- if (property != null && property.length() != 0) {
- protocols = property.split(",");
- } else {
- reservedException = new IllegalArgumentException(
- "No protocol specified in " +
- PROPERTY_NAME + " system property");
- protocols = new String[0];
- }
-
- candidates = new ProtocolVersion[protocols.length];
- for (int i = 0; i < protocols.length; i++) {
- protocols[i] = protocols[i].trim();
- // Is it a supported protocol name?
- try {
- candidates[i] = ProtocolVersion.valueOf(protocols[i]);
- } catch (IllegalArgumentException iae) {
- reservedException = new IllegalArgumentException(
- PROPERTY_NAME + ": " + protocols[i] +
- " is not a standard SSL/TLS protocol name", iae);
- break;
+ reservedException = CustomizedSSLProtocols.reservedException;
+ if (reservedException == null) {
+ ArrayList<ProtocolVersion>
+ customizedTLSProtocols = new ArrayList<>();
+ for (ProtocolVersion protocol :
+ CustomizedSSLProtocols.customizedProtocols) {
+ if (!protocol.isDTLSProtocol()) {
+ customizedTLSProtocols.add(protocol);
}
}
- if ((reservedException == null) && SunJSSE.isFIPS()) {
- for (ProtocolVersion protocolVersion : candidates) {
- if (ProtocolVersion.SSL20Hello.v == protocolVersion.v ||
- ProtocolVersion.SSL30.v == protocolVersion.v) {
- reservedException = new IllegalArgumentException(
- PROPERTY_NAME + ": " + protocolVersion +
- " is not FIPS compliant");
- }
+ // candidates for available protocols
+ ProtocolVersion[] candidates;
+ if (customizedTLSProtocols.isEmpty()) {
+ // Use the default enabled client protocols if no
+ // customized TLS protocols.
+ if (SunJSSE.isFIPS()) {
+ candidates = new ProtocolVersion[] {
+ ProtocolVersion.TLS10,
+ ProtocolVersion.TLS11,
+ ProtocolVersion.TLS12
+ };
+ } else {
+ candidates = new ProtocolVersion[] {
+ ProtocolVersion.SSL30,
+ ProtocolVersion.TLS10,
+ ProtocolVersion.TLS11,
+ ProtocolVersion.TLS12
+ };
}
+ } else {
+ // Use the customized TLS protocols.
+ candidates =
+ new ProtocolVersion[customizedTLSProtocols.size()];
+ candidates = customizedTLSProtocols.toArray(candidates);
}
- }
- defaultClientSSLParams = new SSLParameters();
- if (reservedException == null) {
+ defaultClientSSLParams = new SSLParameters();
defaultClientSSLParams.setProtocols(
getAvailableProtocols(candidates));
+ } else {
+ defaultClientSSLParams = null; // unlikely to be used
}
}
- protected CustomizedSSLContext() {
+ protected CustomizedTLSContext() {
if (reservedException != null) {
throw reservedException;
}
@@ -709,7 +784,7 @@
*
* @see SSLContext
*/
- public static final class TLSContext extends CustomizedSSLContext {
+ public static final class TLSContext extends CustomizedTLSContext {
// use the default constructor and methods
}
@@ -718,7 +793,7 @@
*
* @see SSLContext
*/
- public static final class DefaultSSLContext extends CustomizedSSLContext {
+ public static final class DefaultSSLContext extends CustomizedTLSContext {
private static final String NONE = "NONE";
private static final String P11KEYSTORE = "PKCS11";
@@ -879,6 +954,193 @@
}
}
+ /*
+ * The base abstract SSLContext implementation for the Datagram Transport
+ * Layer Security (DTLS) protocols.
+ *
+ * This abstract class encapsulates supported and the default server DTLS
+ * parameters.
+ *
+ * @see SSLContext
+ */
+ private abstract static class AbstractDTLSContext extends SSLContextImpl {
+ // parameters
+ private static final SSLParameters defaultServerSSLParams;
+ private static final SSLParameters supportedSSLParams;
+
+ static {
+ // supported SSL parameters
+ supportedSSLParams = new SSLParameters();
+
+ // Both DTLSv1.0 and DTLSv1.2 can be used in FIPS mode.
+ supportedSSLParams.setProtocols(new String[] {
+ ProtocolVersion.DTLS10.name,
+ ProtocolVersion.DTLS12.name
+ });
+
+ // candidates for available protocols
+ ProtocolVersion[] candidates = new ProtocolVersion[] {
+ ProtocolVersion.DTLS10,
+ ProtocolVersion.DTLS12
+ };
+
+ defaultServerSSLParams = new SSLParameters();
+ defaultServerSSLParams.setProtocols(
+ getAvailableProtocols(candidates));
+ }
+
+ @Override
+ SSLParameters getDefaultServerSSLParams() {
+ return defaultServerSSLParams;
+ }
+
+ @Override
+ SSLParameters getSupportedSSLParams() {
+ return supportedSSLParams;
+ }
+
+ @Override
+ SSLEngine createSSLEngineImpl() {
+ return new SSLEngineImpl(this, true);
+ }
+
+ @Override
+ SSLEngine createSSLEngineImpl(String host, int port) {
+ return new SSLEngineImpl(this, host, port, true);
+ }
+
+ @Override
+ HelloCookieManager getHelloCookieManager(SecureRandom secureRandom) {
+ return new HelloCookieManager(secureRandom);
+ }
+ }
+
+ /*
+ * The SSLContext implementation for DTLSv1.0 algorithm.
+ *
+ * @see SSLContext
+ */
+ public static final class DTLS10Context extends AbstractDTLSContext {
+ private final static SSLParameters defaultClientSSLParams;
+
+ static {
+ // candidates for available protocols
+ ProtocolVersion[] candidates = new ProtocolVersion[] {
+ ProtocolVersion.DTLS10
+ };
+
+ defaultClientSSLParams = new SSLParameters();
+ defaultClientSSLParams.setProtocols(
+ getAvailableProtocols(candidates));
+ }
+
+ @Override
+ SSLParameters getDefaultClientSSLParams() {
+ return defaultClientSSLParams;
+ }
+ }
+
+ /*
+ * The SSLContext implementation for DTLSv1.2 algorithm.
+ *
+ * @see SSLContext
+ */
+ public static final class DTLS12Context extends AbstractDTLSContext {
+ private final static SSLParameters defaultClientSSLParams;
+
+ static {
+ // candidates for available protocols
+ ProtocolVersion[] candidates = new ProtocolVersion[] {
+ ProtocolVersion.DTLS10,
+ ProtocolVersion.DTLS12
+ };
+
+ defaultClientSSLParams = new SSLParameters();
+ defaultClientSSLParams.setProtocols(
+ getAvailableProtocols(candidates));
+ }
+
+ @Override
+ SSLParameters getDefaultClientSSLParams() {
+ return defaultClientSSLParams;
+ }
+ }
+
+ /*
+ * The SSLContext implementation for customized TLS protocols
+ *
+ * @see SSLContext
+ */
+ private static class CustomizedDTLSContext extends AbstractDTLSContext {
+ private final static SSLParameters defaultClientSSLParams;
+ private static IllegalArgumentException reservedException = null;
+
+ // Don't want a java.lang.LinkageError for illegal system property.
+ //
+ // Please don't throw exception in this static block. Otherwise,
+ // java.lang.LinkageError may be thrown during the instantiation of
+ // the provider service. Instead, let's handle the initialization
+ // exception in constructor.
+ static {
+ reservedException = CustomizedSSLProtocols.reservedException;
+ if (reservedException == null) {
+ ArrayList<ProtocolVersion>
+ customizedDTLSProtocols = new ArrayList<>();
+ for (ProtocolVersion protocol :
+ CustomizedSSLProtocols.customizedProtocols) {
+ if (protocol.isDTLSProtocol()) {
+ customizedDTLSProtocols.add(protocol);
+ }
+ }
+
+ // candidates for available protocols
+ ProtocolVersion[] candidates;
+ if (customizedDTLSProtocols.isEmpty()) {
+ // Use the default enabled client protocols if no
+ // customized TLS protocols.
+ //
+ // Both DTLSv1.0 and DTLSv1.2 can be used in FIPS mode.
+ candidates = new ProtocolVersion[] {
+ ProtocolVersion.DTLS10,
+ ProtocolVersion.DTLS12
+ };
+
+ } else {
+ // Use the customized TLS protocols.
+ candidates =
+ new ProtocolVersion[customizedDTLSProtocols.size()];
+ candidates = customizedDTLSProtocols.toArray(candidates);
+ }
+
+ defaultClientSSLParams = new SSLParameters();
+ defaultClientSSLParams.setProtocols(
+ getAvailableProtocols(candidates));
+ } else {
+ defaultClientSSLParams = null; // unlikely to be used
+ }
+ }
+
+ protected CustomizedDTLSContext() {
+ if (reservedException != null) {
+ throw reservedException;
+ }
+ }
+
+ @Override
+ SSLParameters getDefaultClientSSLParams() {
+ return defaultClientSSLParams;
+ }
+ }
+
+ /*
+ * The SSLContext implementation for default "DTLS" algorithm
+ *
+ * @see SSLContext
+ */
+ public static final class DTLSContext extends CustomizedDTLSContext {
+ // use the default constructor and methods
+ }
+
}
@@ -961,7 +1223,7 @@
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
AlgorithmConstraints constraints = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
@@ -1003,7 +1265,7 @@
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
AlgorithmConstraints constraints = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -54,57 +54,6 @@
* before thread1, and sends the data. The receiving side would see an
* out-of-order error.
*
- * Handshaking is still done the same way as SSLSocket using the normal
- * InputStream/OutputStream abstactions. We create
- * ClientHandshakers/ServerHandshakers, which produce/consume the
- * handshaking data. The transfer of the data is largely handled by the
- * HandshakeInStream/HandshakeOutStreams. Lastly, the
- * InputRecord/OutputRecords still have the same functionality, except
- * that they are overridden with EngineInputRecord/EngineOutputRecord,
- * which provide SSLEngine-specific functionality.
- *
- * Some of the major differences are:
- *
- * EngineInputRecord/EngineOutputRecord/EngineWriter:
- *
- * In order to avoid writing whole new control flows for
- * handshaking, and to reuse most of the same code, we kept most
- * of the actual handshake code the same. As usual, reading
- * handshake data may trigger output of more handshake data, so
- * what we do is write this data to internal buffers, and wait for
- * wrap() to be called to give that data a ride.
- *
- * All data is routed through
- * EngineInputRecord/EngineOutputRecord. However, all handshake
- * data (ct_alert/ct_change_cipher_spec/ct_handshake) are passed
- * through to the underlying InputRecord/OutputRecord, and
- * the data uses the internal buffers.
- *
- * Application data is handled slightly different, we copy the data
- * directly from the src to the dst buffers, and do all operations
- * on those buffers, saving the overhead of multiple copies.
- *
- * In the case of an inbound record, unwrap passes the inbound
- * ByteBuffer to the InputRecord. If the data is handshake data,
- * the data is read into the InputRecord's internal buffer. If
- * the data is application data, the data is decoded directly into
- * the dst buffer.
- *
- * In the case of an outbound record, when the write to the
- * "real" OutputStream's would normally take place, instead we
- * call back up to the EngineOutputRecord's version of
- * writeBuffer, at which time we capture the resulting output in a
- * ByteBuffer, and send that back to the EngineWriter for internal
- * storage.
- *
- * EngineWriter is responsible for "handling" all outbound
- * data, be it handshake or app data, and for returning the data
- * to wrap() in the proper order.
- *
- * ClientHandshaker/ServerHandshaker/Handshaker:
- * Methods which relied on SSLSocket now have work on either
- * SSLSockets or SSLEngines.
- *
* @author Brad Wetmore
*/
final public class SSLEngineImpl extends SSLEngine {
@@ -175,11 +124,10 @@
/*
* Once we're in state cs_CLOSED, we can continue to
* wrap/unwrap until we finish sending/receiving the messages
- * for close_notify. EngineWriter handles outboundDone.
+ * for close_notify.
*/
private boolean inboundDone = false;
-
- EngineWriter writer;
+ private boolean outboundDone = false;
/*
* The authentication context holds all information used to establish
@@ -201,21 +149,6 @@
private SSLSessionImpl sess;
private volatile SSLSessionImpl handshakeSession;
-
- /*
- * Client authentication be off, requested, or required.
- *
- * This will be used by both this class and SSLSocket's variants.
- */
- static final byte clauth_none = 0;
- static final byte clauth_requested = 1;
- static final byte clauth_required = 2;
-
- /*
- * Flag indicating that the engine has received a ChangeCipherSpec message.
- */
- private boolean receivedCCS;
-
/*
* Flag indicating if the next record we receive MUST be a Finished
* message. Temporarily set during the handshake to ensure that
@@ -243,11 +176,12 @@
* Per-connection private state that doesn't change when the
* session is changed.
*/
- private byte doClientAuth;
- private boolean enableSessionCreation = true;
- EngineInputRecord inputRecord;
- EngineOutputRecord outputRecord;
- private AccessControlContext acc;
+ private ClientAuthType doClientAuth =
+ ClientAuthType.CLIENT_AUTH_NONE;
+ private boolean enableSessionCreation = true;
+ InputRecord inputRecord;
+ OutputRecord outputRecord;
+ private AccessControlContext acc;
// The cipher suites enabled for use on this connection.
private CipherSuiteList enabledCipherSuites;
@@ -280,14 +214,7 @@
/*
* The SSL version associated with this connection.
*/
- private ProtocolVersion protocolVersion = ProtocolVersion.DEFAULT;
-
- /*
- * Crypto state that's reinitialized when the session changes.
- */
- private Authenticator readAuthenticator, writeAuthenticator;
- private CipherBox readCipher, writeCipher;
- // NOTE: compression state would be saved here
+ private ProtocolVersion protocolVersion;
/*
* security parameters for secure renegotiation.
@@ -320,17 +247,27 @@
Object writeLock;
/*
- * Is it the first application record to write?
- */
- private boolean isFirstAppOutputRecord = true;
-
- /*
* Whether local cipher suites preference in server side should be
* honored during handshaking?
*/
private boolean preferLocalCipherSuites = false;
/*
+ * whether DTLS handshake retransmissions should be enabled?
+ */
+ private boolean enableRetransmissions = false;
+
+ /*
+ * The maximum expected network packet size for SSL/TLS/DTLS records.
+ */
+ private int maximumPacketSize = 0;
+
+ /*
+ * Is this an instance for Datagram Transport Layer Security (DTLS)?
+ */
+ private final boolean isDTLS;
+
+ /*
* Class and subclass dynamic debugging support
*/
private static final Debug debug = Debug.getInstance("ssl");
@@ -344,23 +281,25 @@
* host/port hints. This Engine will not be able to cache
* sessions, but must renegotiate everything by hand.
*/
- SSLEngineImpl(SSLContextImpl ctx) {
+ SSLEngineImpl(SSLContextImpl ctx, boolean isDTLS) {
super();
- init(ctx);
+ this.isDTLS = isDTLS;
+ init(ctx, isDTLS);
}
/**
* Constructor for an SSLEngine from SSLContext.
*/
- SSLEngineImpl(SSLContextImpl ctx, String host, int port) {
+ SSLEngineImpl(SSLContextImpl ctx, String host, int port, boolean isDTLS) {
super(host, port);
- init(ctx);
+ this.isDTLS = isDTLS;
+ init(ctx, isDTLS);
}
/**
* Initializes the Engine
*/
- private void init(SSLContextImpl ctx) {
+ private void init(SSLContextImpl ctx, boolean isDTLS) {
if (debug != null && Debug.isOn("ssl")) {
System.out.println("Using SSLEngineImpl.");
}
@@ -368,6 +307,8 @@
sslContext = ctx;
sess = SSLSessionImpl.nullSession;
handshakeSession = null;
+ protocolVersion = isDTLS ?
+ ProtocolVersion.DEFAULT_DTLS : ProtocolVersion.DEFAULT_TLS;
/*
* State is cs_START until we initialize the handshaker.
@@ -377,22 +318,11 @@
*/
roleIsServer = true;
connectionState = cs_START;
- receivedCCS = false;
// default server name indication
serverNames =
Utilities.addToSNIServerNameList(serverNames, getPeerHost());
- /*
- * default read and write side cipher and MAC support
- *
- * Note: compression support would go here too
- */
- readCipher = CipherBox.NULL;
- readAuthenticator = MAC.NULL;
- writeCipher = CipherBox.NULL;
- writeAuthenticator = MAC.NULL;
-
// default security parameters for secure renegotiation
secureRenegotiation = false;
clientVerifyData = new byte[0];
@@ -421,12 +351,19 @@
* elsewhere. All inbound data goes through this one
* input record.
*/
- outputRecord =
- new EngineOutputRecord(Record.ct_application_data, this);
- inputRecord = new EngineInputRecord(this);
- inputRecord.enableFormatChecks();
+ if (isDTLS) {
+ enableRetransmissions = true;
+
+ // SSLEngine needs no record local buffer
+ outputRecord = new DTLSOutputRecord();
+ inputRecord = new DTLSInputRecord();
- writer = new EngineWriter();
+ } else {
+ outputRecord = new SSLEngineOutputRecord();
+ inputRecord = new SSLEngineInputRecord();
+ }
+
+ maximumPacketSize = outputRecord.getMaxPacketSize();
}
/**
@@ -480,18 +417,23 @@
handshaker = new ServerHandshaker(this, sslContext,
enabledProtocols, doClientAuth,
protocolVersion, connectionState == cs_HANDSHAKE,
- secureRenegotiation, clientVerifyData, serverVerifyData);
+ secureRenegotiation, clientVerifyData, serverVerifyData,
+ isDTLS);
handshaker.setSNIMatchers(sniMatchers);
handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
} else {
handshaker = new ClientHandshaker(this, sslContext,
enabledProtocols,
protocolVersion, connectionState == cs_HANDSHAKE,
- secureRenegotiation, clientVerifyData, serverVerifyData);
+ secureRenegotiation, clientVerifyData, serverVerifyData,
+ isDTLS);
handshaker.setSNIServerNames(serverNames);
}
+ handshaker.setMaximumPacketSize(maximumPacketSize);
handshaker.setEnabledCipherSuites(enabledCipherSuites);
handshaker.setEnableSessionCreation(enableSessionCreation);
+
+ outputRecord.initHandshaker();
}
/*
@@ -504,11 +446,14 @@
}
synchronized (this) {
- if (writer.hasOutboundData()) {
+ if (!outputRecord.isEmpty()) {
+ // If no handshaking, special case to wrap alters.
return HandshakeStatus.NEED_WRAP;
} else if (handshaker != null) {
if (handshaker.taskOutstanding()) {
return HandshakeStatus.NEED_TASK;
+ } else if (isDTLS && !inputRecord.isEmpty()) {
+ return HandshakeStatus.NEED_UNWRAP_AGAIN;
} else {
return HandshakeStatus.NEED_UNWRAP;
}
@@ -572,67 +517,15 @@
}
/*
- * When a connection finishes handshaking by enabling use of a newly
- * negotiated session, each end learns about it in two halves (read,
- * and write). When both read and write ciphers have changed, and the
- * last handshake message has been read, the connection has joined
- * (rejoined) the new session.
- *
- * NOTE: The SSLv3 spec is rather unclear on the concepts here.
- * Sessions don't change once they're established (including cipher
- * suite and master secret) but connections can join them (and leave
- * them). They're created by handshaking, though sometime handshaking
- * causes connections to join up with pre-established sessions.
- *
- * Synchronized on "this" from readRecord.
- */
- private void changeReadCiphers() throws SSLException {
- if (connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE) {
- throw new SSLProtocolException(
- "State error, change cipher specs");
- }
-
- // ... create decompressor
-
- CipherBox oldCipher = readCipher;
-
- try {
- readCipher = handshaker.newReadCipher();
- readAuthenticator = handshaker.newReadAuthenticator();
- } catch (GeneralSecurityException e) {
- // "can't happen"
- throw new SSLException("Algorithm missing: ", e);
- }
-
- /*
- * Dispose of any intermediate state in the underlying cipher.
- * For PKCS11 ciphers, this will release any attached sessions,
- * and thus make finalization faster.
- *
- * Since MAC's doFinal() is called for every SSL/TLS packet, it's
- * not necessary to do the same with MAC's.
- */
- oldCipher.dispose();
- }
-
- /*
* used by Handshaker to change the active write cipher, follows
* the output of the CCS message.
*
* Also synchronized on "this" from readRecord/delegatedTask.
*/
- void changeWriteCiphers() throws SSLException {
- if (connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE) {
- throw new SSLProtocolException(
- "State error, change cipher specs");
- }
+ void changeWriteCiphers() throws IOException {
- // ... create compressor
-
- CipherBox oldCipher = writeCipher;
-
+ Authenticator writeAuthenticator;
+ CipherBox writeCipher;
try {
writeCipher = handshaker.newWriteCipher();
writeAuthenticator = handshaker.newWriteAuthenticator();
@@ -641,11 +534,7 @@
throw new SSLException("Algorithm missing: ", e);
}
- // See comment above.
- oldCipher.dispose();
-
- // reset the flag of the first application record
- isFirstAppOutputRecord = true;
+ outputRecord.changeWriteCiphers(writeAuthenticator, writeCipher);
}
/*
@@ -716,10 +605,6 @@
//
// Kickstart handshake state machine if we need to ...
//
- // Note that handshaker.kickstart() writes the message
- // to its HandshakeOutStream, which calls back into
- // SSLSocketImpl.writeRecord() to send it.
- //
if (!handshaker.activated()) {
// prior to handshaking, activate the handshake
if (connectionState == cs_RENEGOTIATE) {
@@ -738,10 +623,6 @@
} else {
// we want to renegotiate, send hello request
handshaker.kickstart();
-
- // hello request is not included in the handshake
- // hashes, reset them
- handshaker.handshakeHash.reset();
}
}
}
@@ -771,15 +652,20 @@
* the unwrapLock, which blocks multiple unwraps from occurring.
*/
@Override
- public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer [] appData,
+ public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer[] appData,
int offset, int length) throws SSLException {
- EngineArgs ea = new EngineArgs(netData, appData, offset, length);
+ // check engine parameters
+ checkEngineParas(netData, appData, offset, length, false);
try {
synchronized (unwrapLock) {
- return readNetRecord(ea);
+ return readNetRecord(netData, appData, offset, length);
}
+ } catch (SSLProtocolException spe) {
+ // may be an unexpected handshake message
+ fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe);
+ return null; // make compiler happy
} catch (Exception e) {
/*
* Don't reset position so it looks like we didn't
@@ -790,11 +676,39 @@
fatal(Alerts.alert_internal_error,
"problem unwrapping net record", e);
return null; // make compiler happy
- } finally {
+ }
+ }
+
+ private static void checkEngineParas(ByteBuffer netData,
+ ByteBuffer[] appData, int offset, int len, boolean isForWrap) {
+
+ if ((netData == null) || (appData == null)) {
+ throw new IllegalArgumentException("src/dst is null");
+ }
+
+ if ((offset < 0) || (len < 0) || (offset > appData.length - len)) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ /*
+ * If wrapping, make sure the destination bufffer is writable.
+ */
+ if (isForWrap && netData.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+
+ for (int i = offset; i < offset + len; i++) {
+ if (appData[i] == null) {
+ throw new IllegalArgumentException(
+ "appData[" + i + "] == null");
+ }
+
/*
- * Just in case something failed to reset limits properly.
+ * If unwrapping, make sure the destination bufffers are writable.
*/
- ea.resetLim();
+ if (!isForWrap && appData[i].isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
}
}
@@ -802,7 +716,8 @@
* Makes additional checks for unwrap, but this time more
* specific to this packet and the current state of the machine.
*/
- private SSLEngineResult readNetRecord(EngineArgs ea) throws IOException {
+ private SSLEngineResult readNetRecord(ByteBuffer netData,
+ ByteBuffer[] appData, int offset, int length) throws IOException {
Status status = null;
HandshakeStatus hsStatus = null;
@@ -857,54 +772,117 @@
* message which would change the ciphers.
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
+ return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
+ }
+
+ if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
+ Plaintext plainText = null;
+ try {
+ plainText = readRecord(null, null, 0, 0);
+ } catch (SSLException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new SSLException("readRecord", e);
+ }
+
+ status = (isInboundDone() ? Status.CLOSED : Status.OK);
+ hsStatus = getHSStatus(plainText.handshakeStatus);
+
return new SSLEngineResult(
- Status.OK, hsStatus, 0, 0);
+ status, hsStatus, 0, 0, plainText.recordSN);
}
/*
* Check the packet to make sure enough is here.
* This will also indirectly check for 0 len packets.
*/
- int packetLen = inputRecord.bytesInCompletePacket(ea.netData);
+ int packetLen = 0;
+ try {
+ packetLen = inputRecord.bytesInCompletePacket(netData);
+ } catch (SSLException ssle) {
+ // Need to discard invalid records for DTLS protocols.
+ if (isDTLS) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ " discard invalid record: " + ssle);
+ }
+
+ // invalid, discard the entire data [section 4.1.2.7, RFC 6347]
+ int deltaNet = netData.remaining();
+ netData.position(netData.limit());
+
+ status = (isInboundDone() ? Status.CLOSED : Status.OK);
+ hsStatus = getHSStatus(hsStatus);
+
+ return new SSLEngineResult(status, hsStatus, deltaNet, 0, -1L);
+ } else {
+ throw ssle;
+ }
+ }
// Is this packet bigger than SSL/TLS normally allows?
if (packetLen > sess.getPacketBufferSize()) {
- if (packetLen > Record.maxLargeRecordSize) {
- throw new SSLProtocolException(
- "Input SSL/TLS record too big: max = " +
- Record.maxLargeRecordSize +
- " len = " + packetLen);
- } else {
+ int largestRecordSize = isDTLS ?
+ DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize;
+ if ((packetLen <= largestRecordSize) && !isDTLS) {
// Expand the expected maximum packet/application buffer
// sizes.
+ //
+ // Only apply to SSL/TLS protocols.
+
+ // Old behavior: shall we honor the System Property
+ // "jsse.SSLEngine.acceptLargeFragments" if it is "false"?
sess.expandBufferSizes();
}
+
+ // check the packet again
+ largestRecordSize = sess.getPacketBufferSize();
+ if (packetLen > largestRecordSize) {
+ throw new SSLProtocolException(
+ "Input record too big: max = " +
+ largestRecordSize + " len = " + packetLen);
+ }
+ }
+
+ int netPos = netData.position();
+ int appRemains = 0;
+ for (int i = offset; i < offset + length; i++) {
+ if (appData[i] == null) {
+ throw new IllegalArgumentException(
+ "appData[" + i + "] == null");
+ }
+ appRemains += appData[i].remaining();
}
/*
* Check for OVERFLOW.
*
- * To be considered: We could delay enforcing the application buffer
- * free space requirement until after the initial handshaking.
+ * Delay enforcing the application buffer free space requirement
+ * until after the initial handshaking.
*/
- if ((packetLen - Record.headerSize) > ea.getAppRemaining()) {
- return new SSLEngineResult(Status.BUFFER_OVERFLOW, hsStatus, 0, 0);
+ // synchronize connectionState?
+ if ((connectionState == cs_DATA) ||
+ (connectionState == cs_RENEGOTIATE)) {
+
+ int FragLen = inputRecord.estimateFragmentSize(packetLen);
+ if (FragLen > appRemains) {
+ return new SSLEngineResult(
+ Status.BUFFER_OVERFLOW, hsStatus, 0, 0);
+ }
}
// check for UNDERFLOW.
- if ((packetLen == -1) || (ea.netData.remaining() < packetLen)) {
- return new SSLEngineResult(
- Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
+ if ((packetLen == -1) || (netData.remaining() < packetLen)) {
+ return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0);
}
/*
* We're now ready to actually do the read.
- * The only result code we really need to be exactly
- * right is the HS finished, for signaling to
- * HandshakeCompletedListeners.
*/
+ Plaintext plainText = null;
try {
- hsStatus = readRecord(ea);
+ plainText = readRecord(netData, appData, offset, length);
} catch (SSLException e) {
throw e;
} catch (IOException e) {
@@ -922,10 +900,21 @@
* status above should cover: FINISHED, NEED_TASK
*/
status = (isInboundDone() ? Status.CLOSED : Status.OK);
- hsStatus = getHSStatus(hsStatus);
+ hsStatus = getHSStatus(plainText.handshakeStatus);
+
+ int deltaNet = netData.position() - netPos;
+ int deltaApp = appRemains;
+ for (int i = offset; i < offset + length; i++) {
+ deltaApp -= appData[i].remaining();
+ }
- return new SSLEngineResult(status, hsStatus,
- ea.deltaNet(), ea.deltaApp());
+ return new SSLEngineResult(
+ status, hsStatus, deltaNet, deltaApp, plainText.recordSN);
+ }
+
+ // the caller have synchronized readLock
+ void expectingFinishFlight() {
+ inputRecord.expectingFinishFlight();
}
/*
@@ -936,220 +925,266 @@
* that a handshake just completed.
*
* It would be nice to be symmetrical with the write side and move
- * the majority of this to EngineInputRecord, but there's too much
+ * the majority of this to SSLInputRecord, but there's too much
* SSLEngine state to do that cleanly. It must still live here.
*/
- private HandshakeStatus readRecord(EngineArgs ea) throws IOException {
-
- HandshakeStatus hsStatus = null;
+ private Plaintext readRecord(ByteBuffer netData,
+ ByteBuffer[] appData, int offset, int length) throws IOException {
/*
* The various operations will return new sliced BB's,
* this will avoid having to worry about positions and
* limits in the netBB.
*/
- ByteBuffer readBB = null;
- ByteBuffer decryptedBB = null;
+ Plaintext plainText = null;
- if (getConnectionState() != cs_ERROR) {
+ if (getConnectionState() == cs_ERROR) {
+ return Plaintext.PLAINTEXT_NULL;
+ }
- /*
- * Read a record ... maybe emitting an alert if we get a
- * comprehensible but unsupported "hello" message during
- * format checking (e.g. V2).
- */
- try {
- readBB = inputRecord.read(ea.netData);
- } catch (IOException e) {
- fatal(Alerts.alert_unexpected_message, e);
+ /*
+ * Read a record ... maybe emitting an alert if we get a
+ * comprehensible but unsupported "hello" message during
+ * format checking (e.g. V2).
+ */
+ try {
+ if (isDTLS) {
+ // Don't process the incoming record until all of the
+ // buffered records get handled.
+ plainText = inputRecord.acquirePlaintext();
}
+ if ((!isDTLS || plainText == null) && netData != null) {
+ plainText = inputRecord.decode(netData);
+ }
+ } catch (UnsupportedOperationException unsoe) { // SSLv2Hello
+ // Hack code to deliver SSLv2 error message for SSL/TLS connections.
+ if (!isDTLS) {
+ outputRecord.encodeV2NoCipher();
+ }
+
+ fatal(Alerts.alert_unexpected_message, unsoe);
+ } catch (BadPaddingException e) {
/*
* The basic SSLv3 record protection involves (optional)
* encryption for privacy, and an integrity check ensuring
* data origin authentication. We do them both here, and
* throw a fatal alert if the integrity check fails.
*/
- try {
- decryptedBB = inputRecord.decrypt(
- readAuthenticator, readCipher, readBB);
- } catch (BadPaddingException e) {
- byte alertType = (inputRecord.contentType() ==
- Record.ct_handshake) ?
- Alerts.alert_handshake_failure :
- Alerts.alert_bad_record_mac;
- fatal(alertType, e.getMessage(), e);
- }
+ byte alertType = (connectionState != cs_DATA) ?
+ Alerts.alert_handshake_failure :
+ Alerts.alert_bad_record_mac;
+ fatal(alertType, e.getMessage(), e);
+ } catch (SSLHandshakeException she) {
+ // may be record sequence number overflow
+ fatal(Alerts.alert_handshake_failure, she);
+ } catch (IOException ioe) {
+ fatal(Alerts.alert_unexpected_message, ioe);
+ }
+
+ // plainText should never be null for TLS protocols
+ HandshakeStatus hsStatus = null;
+ if (!isDTLS || plainText != null) {
+ hsStatus = processInputRecord(plainText, appData, offset, length);
+ }
- // if (!inputRecord.decompress(c))
- // fatal(Alerts.alert_decompression_failure,
- // "decompression failure");
+ if (hsStatus == null) {
+ hsStatus = getHSStatus(null);
+ }
+
+ if (plainText == null) {
+ plainText = new Plaintext();
+ }
+ plainText.handshakeStatus = hsStatus;
+ return plainText;
+ }
- /*
- * Process the record.
- */
+ /*
+ * Process the record.
+ */
+ private synchronized HandshakeStatus processInputRecord(
+ Plaintext plainText,
+ ByteBuffer[] appData, int offset, int length) throws IOException {
- synchronized (this) {
- switch (inputRecord.contentType()) {
- case Record.ct_handshake:
- /*
- * Handshake messages always go to a pending session
- * handshaker ... if there isn't one, create one. This
- * must work asynchronously, for renegotiation.
- *
- * NOTE that handshaking will either resume a session
- * which was in the cache (and which might have other
- * connections in it already), or else will start a new
- * session (new keys exchanged) with just this connection
- * in it.
- */
- initHandshaker();
- if (!handshaker.activated()) {
- // prior to handshaking, activate the handshake
- if (connectionState == cs_RENEGOTIATE) {
- // don't use SSLv2Hello when renegotiating
- handshaker.activate(protocolVersion);
- } else {
- handshaker.activate(null);
- }
+ HandshakeStatus hsStatus = null;
+ switch (plainText.contentType) {
+ case Record.ct_handshake:
+ /*
+ * Handshake messages always go to a pending session
+ * handshaker ... if there isn't one, create one. This
+ * must work asynchronously, for renegotiation.
+ *
+ * NOTE that handshaking will either resume a session
+ * which was in the cache (and which might have other
+ * connections in it already), or else will start a new
+ * session (new keys exchanged) with just this connection
+ * in it.
+ */
+ initHandshaker();
+ if (!handshaker.activated()) {
+ // prior to handshaking, activate the handshake
+ if (connectionState == cs_RENEGOTIATE) {
+ // don't use SSLv2Hello when renegotiating
+ handshaker.activate(protocolVersion);
+ } else {
+ handshaker.activate(null);
+ }
+ }
+
+ /*
+ * process the handshake record ... may contain just
+ * a partial handshake message or multiple messages.
+ *
+ * The handshaker state machine will ensure that it's
+ * a finished message.
+ */
+ handshaker.processRecord(plainText.fragment, expectingFinished);
+ expectingFinished = false;
+
+ if (handshaker.invalidated) {
+ finishHandshake();
+
+ // if state is cs_RENEGOTIATE, revert it to cs_DATA
+ if (connectionState == cs_RENEGOTIATE) {
+ connectionState = cs_DATA;
+ }
+ } else if (handshaker.isDone()) {
+ // reset the parameters for secure renegotiation.
+ secureRenegotiation =
+ handshaker.isSecureRenegotiation();
+ clientVerifyData = handshaker.getClientVerifyData();
+ serverVerifyData = handshaker.getServerVerifyData();
+
+ sess = handshaker.getSession();
+ handshakeSession = null;
+ if (outputRecord.isEmpty()) {
+ hsStatus = finishHandshake();
+ connectionState = cs_DATA;
}
- /*
- * process the handshake record ... may contain just
- * a partial handshake message or multiple messages.
- *
- * The handshaker state machine will ensure that it's
- * a finished message.
- */
- handshaker.process_record(inputRecord, expectingFinished);
- expectingFinished = false;
+ // No handshakeListeners here. That's a
+ // SSLSocket thing.
+ } else if (handshaker.taskOutstanding()) {
+ hsStatus = HandshakeStatus.NEED_TASK;
+ }
+ break;
- if (handshaker.invalidated) {
- handshaker = null;
- receivedCCS = false;
- // if state is cs_RENEGOTIATE, revert it to cs_DATA
- if (connectionState == cs_RENEGOTIATE) {
- connectionState = cs_DATA;
- }
- } else if (handshaker.isDone()) {
- // reset the parameters for secure renegotiation.
- secureRenegotiation =
- handshaker.isSecureRenegotiation();
- clientVerifyData = handshaker.getClientVerifyData();
- serverVerifyData = handshaker.getServerVerifyData();
-
- sess = handshaker.getSession();
- handshakeSession = null;
- if (!writer.hasOutboundData()) {
- hsStatus = HandshakeStatus.FINISHED;
- }
- handshaker = null;
- connectionState = cs_DATA;
- receivedCCS = false;
-
- // No handshakeListeners here. That's a
- // SSLSocket thing.
- } else if (handshaker.taskOutstanding()) {
- hsStatus = HandshakeStatus.NEED_TASK;
- }
- break;
-
- case Record.ct_application_data:
- // Pass this right back up to the application.
- if ((connectionState != cs_DATA)
- && (connectionState != cs_RENEGOTIATE)
- && (connectionState != cs_CLOSED)) {
- throw new SSLProtocolException(
+ case Record.ct_application_data:
+ // Pass this right back up to the application.
+ if ((connectionState != cs_DATA)
+ && (connectionState != cs_RENEGOTIATE)
+ && (connectionState != cs_CLOSED)) {
+ throw new SSLProtocolException(
"Data received in non-data state: " +
connectionState);
- }
-
- if (expectingFinished) {
- throw new SSLProtocolException
- ("Expecting finished message, received data");
- }
-
- /*
- * Don't return data once the inbound side is
- * closed.
- */
- if (!inboundDone) {
- ea.scatter(decryptedBB.slice());
- }
- break;
-
- case Record.ct_alert:
- recvAlert();
- break;
+ }
- case Record.ct_change_cipher_spec:
- if ((connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE)
- || !handshaker.sessionKeysCalculated()
- || receivedCCS) {
- // For the CCS message arriving in the wrong state
- fatal(Alerts.alert_unexpected_message,
- "illegal change cipher spec msg, conn state = "
- + connectionState + ", handshake state = "
- + handshaker.state);
- } else if (inputRecord.available() != 1
- || inputRecord.read() != 1) {
- // For structural/content issues with the CCS
- fatal(Alerts.alert_unexpected_message,
- "Malformed change cipher spec msg");
- }
-
- // Once we've received CCS, update the flag.
- // If the remote endpoint sends it again in this handshake
- // we won't process it.
- receivedCCS = true;
+ if (expectingFinished) {
+ throw new SSLProtocolException
+ ("Expecting finished message, received data");
+ }
- //
- // The first message after a change_cipher_spec
- // record MUST be a "Finished" handshake record,
- // else it's a protocol violation. We force this
- // to be checked by a minor tweak to the state
- // machine.
- //
- changeReadCiphers();
- // next message MUST be a finished message
- expectingFinished = true;
- break;
+ if (!inboundDone) {
+ ByteBuffer fragment = plainText.fragment;
+ int remains = fragment.remaining();
- default:
- //
- // TLS requires that unrecognized records be ignored.
- //
- if (debug != null && Debug.isOn("ssl")) {
- System.out.println(Thread.currentThread().getName() +
- ", Received record type: "
- + inputRecord.contentType());
- }
- break;
- } // switch
-
- /*
- * We only need to check the sequence number state for
- * non-handshaking record.
- *
- * Note that in order to maintain the handshake status
- * properly, we check the sequence number after the last
- * record reading process. As we request renegotiation
- * or close the connection for wrapped sequence number
- * when there is enough sequence number space left to
- * handle a few more records, so the sequence number
- * of the last record cannot be wrapped.
- */
- hsStatus = getHSStatus(hsStatus);
- if (connectionState < cs_ERROR && !isInboundDone() &&
- (hsStatus == HandshakeStatus.NOT_HANDSHAKING)) {
- if (checkSequenceNumber(readAuthenticator,
- inputRecord.contentType())) {
- hsStatus = getHSStatus(null);
+ // Should have enough room in appData.
+ for (int i = offset;
+ ((i < (offset + length)) && (remains > 0)); i++) {
+ int amount = Math.min(appData[i].remaining(), remains);
+ fragment.limit(fragment.position() + amount);
+ appData[i].put(fragment);
+ remains -= amount;
}
}
- } // synchronized (this)
+
+ break;
+
+ case Record.ct_alert:
+ recvAlert(plainText.fragment);
+ break;
+
+ case Record.ct_change_cipher_spec:
+ if ((connectionState != cs_HANDSHAKE
+ && connectionState != cs_RENEGOTIATE)) {
+ // For the CCS message arriving in the wrong state
+ fatal(Alerts.alert_unexpected_message,
+ "illegal change cipher spec msg, conn state = "
+ + connectionState);
+ } else if (plainText.fragment.remaining() != 1
+ || plainText.fragment.get() != 1) {
+ // For structural/content issues with the CCS
+ fatal(Alerts.alert_unexpected_message,
+ "Malformed change cipher spec msg");
+ }
+
+ //
+ // The first message after a change_cipher_spec
+ // record MUST be a "Finished" handshake record,
+ // else it's a protocol violation. We force this
+ // to be checked by a minor tweak to the state
+ // machine.
+ //
+ handshaker.receiveChangeCipherSpec();
+
+ CipherBox readCipher;
+ Authenticator readAuthenticator;
+ try {
+ readCipher = handshaker.newReadCipher();
+ readAuthenticator = handshaker.newReadAuthenticator();
+ } catch (GeneralSecurityException e) {
+ // can't happen
+ throw new SSLException("Algorithm missing: ", e);
+ }
+ inputRecord.changeReadCiphers(readAuthenticator, readCipher);
+
+ // next message MUST be a finished message
+ expectingFinished = true;
+ break;
+
+ default:
+ //
+ // TLS requires that unrecognized records be ignored.
+ //
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", Received record type: " + plainText.contentType);
+ }
+ break;
+ } // switch
+
+ /*
+ * We only need to check the sequence number state for
+ * non-handshaking record.
+ *
+ * Note that in order to maintain the handshake status
+ * properly, we check the sequence number after the last
+ * record reading process. As we request renegotiation
+ * or close the connection for wrapped sequence number
+ * when there is enough sequence number space left to
+ * handle a few more records, so the sequence number
+ * of the last record cannot be wrapped.
+ */
+ hsStatus = getHSStatus(hsStatus);
+ if (connectionState < cs_ERROR && !isInboundDone() &&
+ (hsStatus == HandshakeStatus.NOT_HANDSHAKING) &&
+ (inputRecord.seqNumIsHuge())) {
+ /*
+ * Ask for renegotiation when need to renew sequence number.
+ *
+ * Don't bother to kickstart the renegotiation when the local is
+ * asking for it.
+ */
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", request renegotiation " +
+ "to avoid sequence number overflow");
+ }
+
+ beginHandshake();
+
+ hsStatus = getHSStatus(null);
}
return hsStatus;
@@ -1166,36 +1201,33 @@
* the wrapLock, which blocks multiple wraps from occurring.
*/
@Override
- public SSLEngineResult wrap(ByteBuffer [] appData,
+ public SSLEngineResult wrap(ByteBuffer[] appData,
int offset, int length, ByteBuffer netData) throws SSLException {
- EngineArgs ea = new EngineArgs(appData, offset, length, netData);
+ // check engine parameters
+ checkEngineParas(netData, appData, offset, length, true);
/*
* We can be smarter about using smaller buffer sizes later.
- * For now, force it to be large enough to handle any
- * valid SSL/TLS record.
+ * For now, force it to be large enough to handle any valid record.
*/
- if (netData.remaining() < EngineOutputRecord.maxRecordSize) {
+ if (netData.remaining() < sess.getPacketBufferSize()) {
return new SSLEngineResult(
Status.BUFFER_OVERFLOW, getHSStatus(null), 0, 0);
}
try {
synchronized (wrapLock) {
- return writeAppRecord(ea);
+ return writeAppRecord(appData, offset, length, netData);
}
+ } catch (SSLProtocolException spe) {
+ // may be an unexpected handshake message
+ fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe);
+ return null; // make compiler happy
} catch (Exception e) {
- ea.resetPos();
-
fatal(Alerts.alert_internal_error,
"problem wrapping app data", e);
return null; // make compiler happy
- } finally {
- /*
- * Just in case something didn't reset limits properly.
- */
- ea.resetLim();
}
}
@@ -1203,7 +1235,8 @@
* Makes additional checks for unwrap, but this time more
* specific to this packet and the current state of the machine.
*/
- private SSLEngineResult writeAppRecord(EngineArgs ea) throws IOException {
+ private SSLEngineResult writeAppRecord(ByteBuffer[] appData,
+ int offset, int length, ByteBuffer netData) throws IOException {
Status status = null;
HandshakeStatus hsStatus = null;
@@ -1216,7 +1249,7 @@
/*
* short circuit if we're closed/closing.
*/
- if (writer.isOutboundDone()) {
+ if (isOutboundDone()) {
return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0);
}
@@ -1226,7 +1259,8 @@
*/
synchronized (this) {
if ((connectionState == cs_HANDSHAKE) ||
- (connectionState == cs_START)) {
+ (connectionState == cs_START)) {
+
kickstartHandshake();
/*
@@ -1234,9 +1268,18 @@
* without trying to wrap anything.
*/
hsStatus = getHSStatus(null);
+ if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
+ /*
+ * For DTLS, if the handshake state is
+ * HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap()
+ * means that the previous handshake packets (if delivered)
+ * get lost, and need retransmit the handshake messages.
+ */
+ if (!isDTLS || !enableRetransmissions ||
+ (handshaker == null) || outputRecord.firstMessage) {
- if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
- return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
+ return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
+ } // otherwise, need retransmission
}
}
}
@@ -1258,17 +1301,33 @@
* message which would change the ciphers.
*/
if (hsStatus == HandshakeStatus.NEED_TASK) {
- return new SSLEngineResult(
- Status.OK, hsStatus, 0, 0);
+ return new SSLEngineResult(Status.OK, hsStatus, 0, 0);
}
/*
* This will obtain any waiting outbound data, or will
* process the outbound appData.
*/
+ int netPos = netData.position();
+ int appRemains = 0;
+ for (int i = offset; i < offset + length; i++) {
+ if (appData[i] == null) {
+ throw new IllegalArgumentException(
+ "appData[" + i + "] == null");
+ }
+ appRemains += appData[i].remaining();
+ }
+
+ Ciphertext ciphertext = null;
try {
- synchronized (writeLock) {
- hsStatus = writeRecord(outputRecord, ea);
+ if (appRemains != 0) {
+ synchronized (writeLock) {
+ ciphertext = writeRecord(appData, offset, length, netData);
+ }
+ } else {
+ synchronized (writeLock) {
+ ciphertext = writeRecord(null, 0, 0, netData);
+ }
}
} catch (SSLException e) {
throw e;
@@ -1283,21 +1342,62 @@
* status above should cover: NEED_WRAP/FINISHED
*/
status = (isOutboundDone() ? Status.CLOSED : Status.OK);
- hsStatus = getHSStatus(hsStatus);
+ hsStatus = getHSStatus(ciphertext.handshakeStatus);
- return new SSLEngineResult(status, hsStatus,
- ea.deltaApp(), ea.deltaNet());
+ int deltaNet = netData.position() - netPos;
+ int deltaApp = appRemains;
+ for (int i = offset; i < offset + length; i++) {
+ deltaApp -= appData[i].remaining();
+ }
+
+ return new SSLEngineResult(
+ status, hsStatus, deltaApp, deltaNet, ciphertext.recordSN);
}
/*
* Central point to write/get all of the outgoing data.
*/
- private HandshakeStatus writeRecord(EngineOutputRecord eor,
- EngineArgs ea) throws IOException {
+ private Ciphertext writeRecord(ByteBuffer[] appData,
+ int offset, int length, ByteBuffer netData) throws IOException {
+
+ Ciphertext ciphertext = null;
+ try {
+ // Acquire the buffered to-be-delivered records or retransmissions.
+ //
+ // May have buffered records, or need retransmission if handshaking.
+ if (!outputRecord.isEmpty() || (handshaker != null)) {
+ ciphertext = outputRecord.acquireCiphertext(netData);
+ }
+
+ if ((ciphertext == null) && (appData != null)) {
+ ciphertext = outputRecord.encode(
+ appData, offset, length, netData);
+ }
+ } catch (SSLHandshakeException she) {
+ // may be record sequence number overflow
+ fatal(Alerts.alert_handshake_failure, she);
- // eventually compress as well.
- HandshakeStatus hsStatus =
- writer.writeRecord(eor, ea, writeAuthenticator, writeCipher);
+ return Ciphertext.CIPHERTEXT_NULL; // make the complier happy
+ } catch (IOException e) {
+ fatal(Alerts.alert_unexpected_message, e);
+
+ return Ciphertext.CIPHERTEXT_NULL; // make the complier happy
+ }
+
+ if (ciphertext == null) {
+ return Ciphertext.CIPHERTEXT_NULL;
+ }
+
+ HandshakeStatus hsStatus = null;
+ Ciphertext.RecordType recordType = ciphertext.recordType;
+ if ((handshaker != null) &&
+ (recordType.contentType == Record.ct_handshake) &&
+ (recordType.handshakeType == HandshakeMessage.ht_finished) &&
+ handshaker.isDone() && outputRecord.isEmpty()) {
+
+ hsStatus = finishHandshake();
+ connectionState = cs_DATA;
+ } // Otherwise, the followed call to getHSStatus() will help.
/*
* We only need to check the sequence number state for
@@ -1313,129 +1413,41 @@
*/
hsStatus = getHSStatus(hsStatus);
if (connectionState < cs_ERROR && !isOutboundDone() &&
- (hsStatus == HandshakeStatus.NOT_HANDSHAKING)) {
- if (checkSequenceNumber(writeAuthenticator, eor.contentType())) {
- hsStatus = getHSStatus(null);
- }
- }
-
- /*
- * turn off the flag of the first application record if we really
- * consumed at least byte.
- */
- if (isFirstAppOutputRecord && ea.deltaApp() > 0) {
- isFirstAppOutputRecord = false;
- }
-
- return hsStatus;
- }
-
- /*
- * Need to split the payload except the following cases:
- *
- * 1. protocol version is TLS 1.1 or later;
- * 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
- * 3. the payload is the first application record of a freshly
- * negotiated TLS session.
- * 4. the CBC protection is disabled;
- *
- * More details, please refer to
- * EngineOutputRecord.write(EngineArgs, MAC, CipherBox).
- */
- boolean needToSplitPayload(CipherBox cipher, ProtocolVersion protocol) {
- return (protocol.v <= ProtocolVersion.TLS10.v) &&
- cipher.isCBCMode() && !isFirstAppOutputRecord &&
- Record.enableCBCProtection;
- }
-
- /*
- * Non-application OutputRecords go through here.
- */
- void writeRecord(EngineOutputRecord eor) throws IOException {
- // eventually compress as well.
- writer.writeRecord(eor, writeAuthenticator, writeCipher);
-
- /*
- * Check the sequence number state
- *
- * Note that in order to maintain the connection I/O
- * properly, we check the sequence number after the last
- * record writing process. As we request renegotiation
- * or close the connection for wrapped sequence number
- * when there is enough sequence number space left to
- * handle a few more records, so the sequence number
- * of the last record cannot be wrapped.
- */
- if ((connectionState < cs_ERROR) && !isOutboundDone()) {
- checkSequenceNumber(writeAuthenticator, eor.contentType());
- }
- }
-
- //
- // Close code
- //
-
- /**
- * Check the sequence number state
- *
- * RFC 4346 states that, "Sequence numbers are of type uint64 and
- * may not exceed 2^64-1. Sequence numbers do not wrap. If a TLS
- * implementation would need to wrap a sequence number, it must
- * renegotiate instead."
- *
- * Return true if the handshake status may be changed.
- */
- private boolean checkSequenceNumber(Authenticator authenticator, byte type)
- throws IOException {
-
- /*
- * Don't bother to check the sequence number for error or
- * closed connections, or NULL MAC
- */
- if (connectionState >= cs_ERROR || authenticator == MAC.NULL) {
- return false;
- }
-
- /*
- * Conservatively, close the connection immediately when the
- * sequence number is close to overflow
- */
- if (authenticator.seqNumOverflow()) {
+ (hsStatus == HandshakeStatus.NOT_HANDSHAKING) &&
+ (outputRecord.seqNumIsHuge())) {
/*
- * TLS protocols do not define a error alert for sequence
- * number overflow. We use handshake_failure error alert
- * for handshaking and bad_record_mac for other records.
+ * Ask for renegotiation when need to renew sequence number.
+ *
+ * Don't bother to kickstart the renegotiation when the local is
+ * asking for it.
*/
if (debug != null && Debug.isOn("ssl")) {
System.out.println(Thread.currentThread().getName() +
- ", sequence number extremely close to overflow " +
- "(2^64-1 packets). Closing connection.");
- }
-
- fatal(Alerts.alert_handshake_failure, "sequence number overflow");
-
- return true; // make the compiler happy
- }
-
- /*
- * Ask for renegotiation when need to renew sequence number.
- *
- * Don't bother to kickstart the renegotiation when the local is
- * asking for it.
- */
- if ((type != Record.ct_handshake) && authenticator.seqNumIsHuge()) {
- if (debug != null && Debug.isOn("ssl")) {
- System.out.println(Thread.currentThread().getName() +
", request renegotiation " +
"to avoid sequence number overflow");
}
beginHandshake();
- return true;
+
+ hsStatus = getHSStatus(null);
}
+ ciphertext.handshakeStatus = hsStatus;
+
+ return ciphertext;
+ }
- return false;
- }
+ private HandshakeStatus finishHandshake() {
+ handshaker = null;
+ inputRecord.setHandshakeHash(null);
+ outputRecord.setHandshakeHash(null);
+ connectionState = cs_DATA;
+
+ return HandshakeStatus.FINISHED;
+ }
+
+ //
+ // Close code
+ //
/**
* Signals that no more outbound application data will be sent
@@ -1451,7 +1463,7 @@
/*
* Already closed, ignore
*/
- if (writer.isOutboundDone()) {
+ if (outboundDone) {
return;
}
@@ -1461,7 +1473,18 @@
* If we haven't even started yet, don't bother reading inbound.
*/
case cs_START:
- writer.closeOutbound();
+ try {
+ outputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ outboundDone = true;
+
+ try {
+ inputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
inboundDone = true;
break;
@@ -1477,13 +1500,15 @@
// case cs_RENEGOTIATE:
default:
warning(Alerts.alert_close_notify);
- writer.closeOutbound();
+ try {
+ outputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ outboundDone = true;
break;
}
- // See comment in changeReadCiphers()
- writeCipher.dispose();
-
connectionState = cs_CLOSED;
}
@@ -1505,7 +1530,7 @@
*/
@Override
public boolean isOutboundDone() {
- return writer.isOutboundDone();
+ return outboundDone && outputRecord.isEmpty();
}
/**
@@ -1527,11 +1552,14 @@
}
closeOutboundInternal();
+
+ try {
+ inputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
inboundDone = true;
- // See comment in changeReadCiphers()
- readCipher.dispose();
-
connectionState = cs_CLOSED;
}
@@ -1602,6 +1630,10 @@
}
synchronized void setHandshakeSession(SSLSessionImpl session) {
+ // update the fragment size, which may be negotiated during handshaking
+ inputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
+ outputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
+
handshakeSession = session;
}
@@ -1701,6 +1733,11 @@
int oldState = connectionState;
connectionState = cs_ERROR;
+ try {
+ inputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
inboundDone = true;
sess.invalidate();
@@ -1728,14 +1765,15 @@
Alerts.getSSLException(description, cause, diagnostic);
}
- writer.closeOutbound();
+ try {
+ outputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ outboundDone = true;
connectionState = cs_CLOSED;
- // See comment in changeReadCiphers()
- readCipher.dispose();
- writeCipher.dispose();
-
if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else {
@@ -1747,9 +1785,10 @@
* Process an incoming alert ... caller must already have synchronized
* access to "this".
*/
- private void recvAlert() throws IOException {
- byte level = (byte)inputRecord.read();
- byte description = (byte)inputRecord.read();
+ private void recvAlert(ByteBuffer fragment) throws IOException {
+ byte level = fragment.get();
+ byte description = fragment.get();
+
if (description == -1) { // check for short message
fatal(Alerts.alert_illegal_parameter, "Short alert message");
}
@@ -1813,40 +1852,18 @@
// For initial handshaking, don't send alert message to peer if
// handshaker has not started.
- if (connectionState == cs_HANDSHAKE &&
- (handshaker == null || !handshaker.started())) {
+ //
+ // Shall we send an fatal alter to terminate the connection gracefully?
+ if (connectionState <= cs_HANDSHAKE &&
+ (handshaker == null || !handshaker.started() ||
+ !handshaker.activated())) {
return;
}
- EngineOutputRecord r = new EngineOutputRecord(Record.ct_alert, this);
- r.setVersion(protocolVersion);
-
- boolean useDebug = debug != null && Debug.isOn("ssl");
- if (useDebug) {
- synchronized (System.out) {
- System.out.print(Thread.currentThread().getName());
- System.out.print(", SEND " + protocolVersion + " ALERT: ");
- if (level == Alerts.alert_fatal) {
- System.out.print("fatal, ");
- } else if (level == Alerts.alert_warning) {
- System.out.print("warning, ");
- } else {
- System.out.print("<level = " + (0x0ff & level) + ">, ");
- }
- System.out.println("description = "
- + Alerts.alertDescription(description));
- }
- }
-
- r.write(level);
- r.write(description);
try {
- writeRecord(r);
- } catch (IOException e) {
- if (useDebug) {
- System.out.println(Thread.currentThread().getName() +
- ", Exception sending alert: " + e);
- }
+ outputRecord.encodeAlert(level, description);
+ } catch (IOException ioe) {
+ // ignore
}
}
@@ -1894,7 +1911,8 @@
@Override
synchronized public void setNeedClientAuth(boolean flag) {
doClientAuth = (flag ?
- SSLEngineImpl.clauth_required : SSLEngineImpl.clauth_none);
+ ClientAuthType.CLIENT_AUTH_REQUIRED :
+ ClientAuthType.CLIENT_AUTH_NONE);
if ((handshaker != null) &&
(handshaker instanceof ServerHandshaker) &&
@@ -1905,7 +1923,7 @@
@Override
synchronized public boolean getNeedClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_required);
+ return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED);
}
/**
@@ -1919,7 +1937,8 @@
@Override
synchronized public void setWantClientAuth(boolean flag) {
doClientAuth = (flag ?
- SSLEngineImpl.clauth_requested : SSLEngineImpl.clauth_none);
+ ClientAuthType.CLIENT_AUTH_REQUESTED :
+ ClientAuthType.CLIENT_AUTH_NONE);
if ((handshaker != null) &&
(handshaker instanceof ServerHandshaker) &&
@@ -1930,7 +1949,7 @@
@Override
synchronized public boolean getWantClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_requested);
+ return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED);
}
@@ -1946,13 +1965,21 @@
case cs_START:
/*
- * If we need to change the engine mode and the enabled
- * protocols haven't specifically been set by the user,
- * change them to the corresponding default ones.
+ * If we need to change the socket mode and the enabled
+ * protocols and cipher suites haven't specifically been
+ * set by the user, change them to the corresponding
+ * default ones.
*/
- if (roleIsServer != (!flag) &&
- sslContext.isDefaultProtocolList(enabledProtocols)) {
- enabledProtocols = sslContext.getDefaultProtocolList(!flag);
+ if (roleIsServer != (!flag)) {
+ if (sslContext.isDefaultProtocolList(enabledProtocols)) {
+ enabledProtocols =
+ sslContext.getDefaultProtocolList(!flag);
+ }
+
+ if (sslContext.isDefaultCipherSuiteList(enabledCipherSuites)) {
+ enabledCipherSuites =
+ sslContext.getDefaultCipherSuiteList(!flag);
+ }
}
roleIsServer = !flag;
@@ -1970,13 +1997,22 @@
assert(handshaker != null);
if (!handshaker.activated()) {
/*
- * If we need to change the engine mode and the enabled
- * protocols haven't specifically been set by the user,
- * change them to the corresponding default ones.
+ * If we need to change the socket mode and the enabled
+ * protocols and cipher suites haven't specifically been
+ * set by the user, change them to the corresponding
+ * default ones.
*/
- if (roleIsServer != (!flag) &&
- sslContext.isDefaultProtocolList(enabledProtocols)) {
- enabledProtocols = sslContext.getDefaultProtocolList(!flag);
+ if (roleIsServer != (!flag)) {
+ if (sslContext.isDefaultProtocolList(enabledProtocols)) {
+ enabledProtocols =
+ sslContext.getDefaultProtocolList(!flag);
+ }
+
+ if (sslContext.isDefaultCipherSuiteList(
+ enabledCipherSuites)) {
+ enabledCipherSuites =
+ sslContext.getDefaultCipherSuiteList(!flag);
+ }
}
roleIsServer = !flag;
@@ -2102,6 +2138,8 @@
params.setSNIMatchers(sniMatchers);
params.setServerNames(serverNames);
params.setUseCipherSuitesOrder(preferLocalCipherSuites);
+ params.setEnableRetransmissions(enableRetransmissions);
+ params.setMaximumPacketSize(maximumPacketSize);
return params;
}
@@ -2117,6 +2155,15 @@
identificationProtocol = params.getEndpointIdentificationAlgorithm();
algorithmConstraints = params.getAlgorithmConstraints();
preferLocalCipherSuites = params.getUseCipherSuitesOrder();
+ enableRetransmissions = params.getEnableRetransmissions();
+ maximumPacketSize = params.getMaximumPacketSize();
+
+ if (maximumPacketSize != 0) {
+ outputRecord.changePacketSize(maximumPacketSize);
+ } else {
+ // use the implicit maximum packet size.
+ maximumPacketSize = outputRecord.getMaxPacketSize();
+ }
List<SNIServerName> sniNames = params.getServerNames();
if (sniNames != null) {
@@ -2131,6 +2178,7 @@
if ((handshaker != null) && !handshaker.started()) {
handshaker.setIdentificationProtocol(identificationProtocol);
handshaker.setAlgorithmConstraints(algorithmConstraints);
+ handshaker.setMaximumPacketSize(maximumPacketSize);
if (roleIsServer) {
handshaker.setSNIMatchers(sniMatchers);
handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
@@ -2141,14 +2189,6 @@
}
/**
- * Returns a boolean indicating whether the ChangeCipherSpec message
- * has been received for this handshake.
- */
- boolean receivedChangeCipherSpec() {
- return receivedCCS;
- }
-
- /**
* Returns a printable representation of this end of the connection.
*/
@Override
@@ -2162,6 +2202,7 @@
retval.append((host == null) ? "null" : host);
retval.append(" port=");
retval.append(Integer.toString(getPeerPort()));
+ retval.append(" role=" + (roleIsServer ? "Server" : "Client"));
retval.append("] ");
retval.append(getSession().getCipherSuite());
retval.append("]");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+
+import javax.crypto.BadPaddingException;
+
+import javax.net.ssl.*;
+
+import sun.misc.HexDumpEncoder;
+
+
+/**
+ * {@code InputRecord} implementation for {@code SSLEngine}.
+ */
+final class SSLEngineInputRecord extends InputRecord implements SSLRecord {
+ // used by handshake hash computation for handshake fragment
+ private byte prevType = -1;
+ private int hsMsgOff = 0;
+ private int hsMsgLen = 0;
+
+ private boolean formatVerified = false; // SSLv2 ruled out?
+
+ SSLEngineInputRecord() {
+ this.readAuthenticator = MAC.TLS_NULL;
+ }
+
+ @Override
+ int estimateFragmentSize(int packetSize) {
+ int macLen = 0;
+ if (readAuthenticator instanceof MAC) {
+ macLen = ((MAC)readAuthenticator).MAClen();
+ }
+
+ if (packetSize > 0) {
+ return readCipher.estimateFragmentSize(
+ packetSize, macLen, headerSize);
+ } else {
+ return Record.maxDataSize;
+ }
+ }
+
+ @Override
+ int bytesInCompletePacket(ByteBuffer packet) throws SSLException {
+ /*
+ * SSLv2 length field is in bytes 0/1
+ * SSLv3/TLS length field is in bytes 3/4
+ */
+ if (packet.remaining() < 5) {
+ return -1;
+ }
+
+ int pos = packet.position();
+ byte byteZero = packet.get(pos);
+
+ int len = 0;
+
+ /*
+ * If we have already verified previous packets, we can
+ * ignore the verifications steps, and jump right to the
+ * determination. Otherwise, try one last hueristic to
+ * see if it's SSL/TLS.
+ */
+ if (formatVerified ||
+ (byteZero == ct_handshake) || (byteZero == ct_alert)) {
+ /*
+ * Last sanity check that it's not a wild record
+ */
+ ProtocolVersion recordVersion = ProtocolVersion.valueOf(
+ packet.get(pos + 1), packet.get(pos + 2));
+
+ // check the record version
+ checkRecordVersion(recordVersion, false);
+
+ /*
+ * Reasonably sure this is a V3, disable further checks.
+ * We can't do the same in the v2 check below, because
+ * read still needs to parse/handle the v2 clientHello.
+ */
+ formatVerified = true;
+
+ /*
+ * One of the SSLv3/TLS message types.
+ */
+ len = ((packet.get(pos + 3) & 0xFF) << 8) +
+ (packet.get(pos + 4) & 0xFF) + headerSize;
+
+ } else {
+ /*
+ * Must be SSLv2 or something unknown.
+ * Check if it's short (2 bytes) or
+ * long (3) header.
+ *
+ * Internals can warn about unsupported SSLv2
+ */
+ boolean isShort = ((byteZero & 0x80) != 0);
+
+ if (isShort &&
+ ((packet.get(pos + 2) == 1) || packet.get(pos + 2) == 4)) {
+
+ ProtocolVersion recordVersion = ProtocolVersion.valueOf(
+ packet.get(pos + 3), packet.get(pos + 4));
+
+ // check the record version
+ checkRecordVersion(recordVersion, true);
+
+ /*
+ * Client or Server Hello
+ */
+ int mask = (isShort ? 0x7F : 0x3F);
+ len = ((byteZero & mask) << 8) +
+ (packet.get(pos + 1) & 0xFF) + (isShort ? 2 : 3);
+
+ } else {
+ // Gobblygook!
+ throw new SSLException(
+ "Unrecognized SSL message, plaintext connection?");
+ }
+ }
+
+ return len;
+ }
+
+ @Override
+ void checkRecordVersion(ProtocolVersion recordVersion,
+ boolean allowSSL20Hello) throws SSLException {
+
+ if (recordVersion.maybeDTLSProtocol()) {
+ throw new SSLException(
+ "Unrecognized record version " + recordVersion +
+ " , DTLS packet?");
+ }
+
+ // Check if the record version is too old.
+ if ((recordVersion.v < ProtocolVersion.MIN.v)) {
+ // if it's not SSLv2, we're out of here.
+ if (!allowSSL20Hello ||
+ (recordVersion.v != ProtocolVersion.SSL20Hello.v)) {
+ throw new SSLException(
+ "Unsupported record version " + recordVersion);
+ }
+ }
+ }
+
+ @Override
+ Plaintext decode(ByteBuffer packet)
+ throws IOException, BadPaddingException {
+
+ if (isClosed) {
+ return null;
+ }
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw read]: length = " + packet.remaining(), packet);
+ }
+
+ // The caller should have validated the record.
+ if (!formatVerified) {
+ formatVerified = true;
+
+ /*
+ * The first record must either be a handshake record or an
+ * alert message. If it's not, it is either invalid or an
+ * SSLv2 message.
+ */
+ int pos = packet.position();
+ byte byteZero = packet.get(pos);
+ if (byteZero != ct_handshake && byteZero != ct_alert) {
+ return handleUnknownRecord(packet);
+ }
+ }
+
+ return decodeInputRecord(packet);
+ }
+
+ private Plaintext decodeInputRecord(ByteBuffer packet)
+ throws IOException, BadPaddingException {
+
+ //
+ // The packet should be a complete record, or more.
+ //
+
+ int srcPos = packet.position();
+ int srcLim = packet.limit();
+
+ byte contentType = packet.get(); // pos: 0
+ byte majorVersion = packet.get(); // pos: 1
+ byte minorVersion = packet.get(); // pos: 2
+ int contentLen = ((packet.get() & 0xFF) << 8) +
+ (packet.get() & 0xFF); // pos: 3, 4
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", READ: " +
+ ProtocolVersion.valueOf(majorVersion, minorVersion) +
+ " " + Record.contentName(contentType) + ", length = " +
+ contentLen);
+ }
+
+ //
+ // Check for upper bound.
+ //
+ // Note: May check packetSize limit in the future.
+ if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) {
+ throw new SSLProtocolException(
+ "Bad input record size, TLSCiphertext.length = " + contentLen);
+ }
+
+ //
+ // check for handshake fragment
+ //
+ if ((contentType != ct_handshake) && (hsMsgOff != hsMsgLen)) {
+ throw new SSLProtocolException(
+ "Expected to get a handshake fragment");
+ }
+
+ //
+ // Decrypt the fragment
+ //
+ int recLim = srcPos + SSLRecord.headerSize + contentLen;
+ packet.limit(recLim);
+ packet.position(srcPos + SSLRecord.headerSize);
+
+ ByteBuffer plaintext;
+ try {
+ plaintext =
+ decrypt(readAuthenticator, readCipher, contentType, packet);
+ } finally {
+ // comsume a complete record
+ packet.limit(srcLim);
+ packet.position(recLim);
+ }
+
+ //
+ // handshake hashing
+ //
+ if (contentType == ct_handshake) {
+ int pltPos = plaintext.position();
+ int pltLim = plaintext.limit();
+ int frgPos = pltPos;
+ for (int remains = plaintext.remaining(); remains > 0;) {
+ int howmuch;
+ byte handshakeType;
+ if (hsMsgOff < hsMsgLen) {
+ // a fragment of the handshake message
+ howmuch = Math.min((hsMsgLen - hsMsgOff), remains);
+ handshakeType = prevType;
+
+ hsMsgOff += howmuch;
+ if (hsMsgOff == hsMsgLen) {
+ // Now is a complete handshake message.
+ hsMsgOff = 0;
+ hsMsgLen = 0;
+ }
+ } else { // hsMsgOff == hsMsgLen, a new handshake message
+ handshakeType = plaintext.get();
+ int handshakeLen = ((plaintext.get() & 0xFF) << 16) |
+ ((plaintext.get() & 0xFF) << 8) |
+ (plaintext.get() & 0xFF);
+ plaintext.position(frgPos);
+ if (remains < (handshakeLen + 1)) { // 1: handshake type
+ // This handshake message is fragmented.
+ prevType = handshakeType;
+ hsMsgOff = remains - 4; // 4: handshake header
+ hsMsgLen = handshakeLen;
+ }
+
+ howmuch = Math.min(handshakeLen + 4, remains);
+ }
+
+ plaintext.limit(frgPos + howmuch);
+
+ if (handshakeType == HandshakeMessage.ht_hello_request) {
+ // omitted from handshake hash computation
+ } else if ((handshakeType != HandshakeMessage.ht_finished) &&
+ (handshakeType != HandshakeMessage.ht_certificate_verify)) {
+
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.update(plaintext);
+ } else {
+ // Reserve until this handshake message has been processed.
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.reserve(plaintext);
+ }
+
+ plaintext.position(frgPos + howmuch);
+ plaintext.limit(pltLim);
+
+ frgPos += howmuch;
+ remains -= howmuch;
+ }
+
+ plaintext.position(pltPos);
+ }
+
+ return new Plaintext(contentType,
+ majorVersion, minorVersion, -1, -1L, plaintext);
+ // recordEpoch, recordSeq, plaintext);
+ }
+
+ private Plaintext handleUnknownRecord(ByteBuffer packet)
+ throws IOException, BadPaddingException {
+
+ //
+ // The packet should be a complete record.
+ //
+ int srcPos = packet.position();
+ int srcLim = packet.limit();
+
+ byte firstByte = packet.get(srcPos);
+ byte thirdByte = packet.get(srcPos + 2);
+
+ // Does it look like a Version 2 client hello (V2ClientHello)?
+ if (((firstByte & 0x80) != 0) && (thirdByte == 1)) {
+ /*
+ * If SSLv2Hello is not enabled, throw an exception.
+ */
+ if (helloVersion != ProtocolVersion.SSL20Hello) {
+ throw new SSLHandshakeException("SSLv2Hello is not enabled");
+ }
+
+ byte majorVersion = packet.get(srcPos + 3);
+ byte minorVersion = packet.get(srcPos + 4);
+
+ if ((majorVersion == ProtocolVersion.SSL20Hello.major) &&
+ (minorVersion == ProtocolVersion.SSL20Hello.minor)) {
+
+ /*
+ * Looks like a V2 client hello, but not one saying
+ * "let's talk SSLv3". So we need to send an SSLv2
+ * error message, one that's treated as fatal by
+ * clients (Otherwise we'll hang.)
+ */
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ "Requested to negotiate unsupported SSLv2!");
+ }
+
+ // hack code, the exception is caught in SSLEngineImpl
+ // so that SSLv2 error message can be delivered properly.
+ throw new UnsupportedOperationException( // SSLv2Hello
+ "Unsupported SSL v2.0 ClientHello");
+ }
+
+ /*
+ * If we can map this into a V3 ClientHello, read and
+ * hash the rest of the V2 handshake, turn it into a
+ * V3 ClientHello message, and pass it up.
+ */
+ packet.position(srcPos + 2); // exclude the header
+
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.update(packet);
+ packet.position(srcPos);
+
+ ByteBuffer converted = convertToClientHello(packet);
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Converted] ClientHello", converted);
+ }
+
+ return new Plaintext(ct_handshake,
+ majorVersion, minorVersion, -1, -1L, converted);
+ } else {
+ if (((firstByte & 0x80) != 0) && (thirdByte == 4)) {
+ throw new SSLException("SSL V2.0 servers are not supported.");
+ }
+
+ throw new SSLException("Unsupported or unrecognized SSL message");
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import sun.misc.HexDumpEncoder;
+import static sun.security.ssl.Ciphertext.RecordType;
+
+/**
+ * {@code OutputRecord} implementation for {@code SSLEngine}.
+ */
+final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord {
+
+ private HandshakeFragment fragmenter = null;
+ private LinkedList<RecordMemo> alertMemos = new LinkedList<>();
+ private boolean isTalkingToV2 = false; // SSLv2Hello
+ private ByteBuffer v2ClientHello = null; // SSLv2Hello
+
+ private boolean isCloseWaiting = false;
+
+ SSLEngineOutputRecord() {
+ this.writeAuthenticator = MAC.TLS_NULL;
+
+ this.packetSize = SSLRecord.maxRecordSize;
+ this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
+ }
+
+ @Override
+ synchronized public void close() throws IOException {
+ if (!isClosed) {
+ if (alertMemos != null && !alertMemos.isEmpty()) {
+ isCloseWaiting = true;
+ } else {
+ super.close();
+ }
+ }
+ }
+
+ @Override
+ void encodeAlert(byte level, byte description) throws IOException {
+ RecordMemo memo = new RecordMemo();
+
+ memo.contentType = Record.ct_alert;
+ memo.majorVersion = protocolVersion.major;
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.fragment = new byte[2];
+ memo.fragment[0] = level;
+ memo.fragment[1] = description;
+
+ alertMemos.add(memo);
+ }
+
+ @Override
+ void encodeHandshake(byte[] source,
+ int offset, int length) throws IOException {
+
+ if (fragmenter == null) {
+ fragmenter = new HandshakeFragment();
+ }
+
+ if (firstMessage) {
+ firstMessage = false;
+
+ if ((helloVersion == ProtocolVersion.SSL20Hello) &&
+ (source[offset] == HandshakeMessage.ht_client_hello) &&
+ // 5: recode header size
+ (source[offset + 4 + 2 + 32] == 0)) {
+ // V3 session ID is empty
+ // 4: handshake header size
+ // 2: client_version in ClientHello
+ // 32: random in ClientHello
+
+ // Double space should be big enough for the converted message.
+ v2ClientHello = encodeV2ClientHello(
+ source, (offset + 4), (length - 4));
+
+ v2ClientHello.position(2); // exclude the header
+ handshakeHash.update(v2ClientHello);
+ v2ClientHello.position(0);
+
+ return;
+ }
+ }
+
+ byte handshakeType = source[offset];
+ if (handshakeType != HandshakeMessage.ht_hello_request) {
+ handshakeHash.update(source, offset, length);
+ }
+
+ fragmenter.queueUpFragment(source, offset, length);
+ }
+
+ @Override
+ void encodeChangeCipherSpec() throws IOException {
+ if (fragmenter == null) {
+ fragmenter = new HandshakeFragment();
+ }
+ fragmenter.queueUpChangeCipherSpec();
+ }
+
+ @Override
+ void encodeV2NoCipher() throws IOException {
+ isTalkingToV2 = true;
+ }
+
+ @Override
+ Ciphertext encode(ByteBuffer[] sources, int offset, int length,
+ ByteBuffer destination) throws IOException {
+
+ if (writeAuthenticator.seqNumOverflow()) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", sequence number extremely close to overflow " +
+ "(2^64-1 packets). Closing connection.");
+ }
+
+ throw new SSLHandshakeException("sequence number overflow");
+ }
+
+ int macLen = 0;
+ if (writeAuthenticator instanceof MAC) {
+ macLen = ((MAC)writeAuthenticator).MAClen();
+ }
+
+ int dstLim = destination.limit();
+ boolean isFirstRecordOfThePayload = true;
+ int packetLeftSize = Math.min(maxRecordSize, packetSize);
+ boolean needMorePayload = true;
+ long recordSN = 0L;
+ while (needMorePayload) {
+ int fragLen;
+ if (isFirstRecordOfThePayload && needToSplitPayload()) {
+ needMorePayload = true;
+
+ fragLen = 1;
+ isFirstRecordOfThePayload = false;
+ } else {
+ needMorePayload = false;
+
+ if (packetLeftSize > 0) {
+ fragLen = writeCipher.calculateFragmentSize(
+ packetLeftSize, macLen, headerSize);
+
+ fragLen = Math.min(fragLen, Record.maxDataSize);
+ } else {
+ fragLen = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLen = Math.min(fragLen, fragmentSize);
+ }
+ }
+
+ int dstPos = destination.position();
+ int dstContent = dstPos + headerSize +
+ writeCipher.getExplicitNonceSize();
+ destination.position(dstContent);
+
+ int remains = Math.min(fragLen, destination.remaining());
+ fragLen = 0;
+ int srcsLen = offset + length;
+ for (int i = offset; (i < srcsLen) && (remains > 0); i++) {
+ int amount = Math.min(sources[i].remaining(), remains);
+ int srcLimit = sources[i].limit();
+ sources[i].limit(sources[i].position() + amount);
+ destination.put(sources[i]);
+ sources[i].limit(srcLimit); // restore the limit
+ remains -= amount;
+ fragLen += amount;
+
+ if (remains > 0) {
+ offset++;
+ length--;
+ }
+ }
+
+ destination.limit(destination.position());
+ destination.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(Record.ct_application_data) +
+ ", length = " + destination.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ recordSN = encrypt(writeAuthenticator, writeCipher,
+ Record.ct_application_data, destination,
+ dstPos, dstLim, headerSize,
+ protocolVersion, false);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = destination.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ packetLeftSize -= destination.position() - dstPos;
+
+ // remain the limit unchanged
+ destination.limit(dstLim);
+
+ if (isFirstAppOutputRecord) {
+ isFirstAppOutputRecord = false;
+ }
+ }
+
+ return new Ciphertext(RecordType.RECORD_APPLICATION_DATA, recordSN);
+ }
+
+ @Override
+ Ciphertext acquireCiphertext(ByteBuffer destination) throws IOException {
+ if (isTalkingToV2) { // SSLv2Hello
+ // We don't support SSLv2. Send an SSLv2 error message
+ // so that the connection can be closed gracefully.
+ //
+ // Please don't change the limit of the destination buffer.
+ destination.put(SSLRecord.v2NoCipher);
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + SSLRecord.v2NoCipher.length,
+ SSLRecord.v2NoCipher);
+ }
+
+ isTalkingToV2 = false;
+
+ return new Ciphertext(RecordType.RECORD_ALERT, -1L);
+ }
+
+ if (v2ClientHello != null) {
+ // deliver the SSLv2 format ClientHello message
+ //
+ // Please don't change the limit of the destination buffer.
+ if (debug != null) {
+ if (Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: SSLv2 ClientHello message" +
+ ", length = " + v2ClientHello.remaining());
+ }
+
+ if (Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + v2ClientHello.remaining(),
+ v2ClientHello);
+ }
+ }
+
+ destination.put(v2ClientHello);
+ v2ClientHello = null;
+
+ return new Ciphertext(RecordType.RECORD_CLIENT_HELLO, -1L);
+ }
+
+ if (alertMemos != null && !alertMemos.isEmpty()) {
+ RecordMemo memo = alertMemos.pop();
+
+ int macLen = 0;
+ if (memo.encodeAuthenticator instanceof MAC) {
+ macLen = ((MAC)memo.encodeAuthenticator).MAClen();
+ }
+
+ int dstPos = destination.position();
+ int dstLim = destination.limit();
+ int dstContent = dstPos + headerSize +
+ writeCipher.getExplicitNonceSize();
+ destination.position(dstContent);
+
+ destination.put(memo.fragment);
+
+ destination.limit(destination.position());
+ destination.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(Record.ct_alert) +
+ ", length = " + destination.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
+ Record.ct_alert, destination, dstPos, dstLim, headerSize,
+ ProtocolVersion.valueOf(memo.majorVersion,
+ memo.minorVersion), false);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = destination.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ // remain the limit unchanged
+ destination.limit(dstLim);
+
+ if (isCloseWaiting && (memo.contentType == Record.ct_alert)) {
+ isCloseWaiting = true;
+ close();
+ }
+ return new Ciphertext(RecordType.RECORD_ALERT, recordSN);
+ }
+
+ if (fragmenter != null) {
+ return fragmenter.acquireCiphertext(destination);
+ }
+
+ return null;
+ }
+
+ @Override
+ boolean isEmpty() {
+ return (!isTalkingToV2) && (v2ClientHello == null) &&
+ ((fragmenter == null) || fragmenter.isEmpty()) &&
+ ((alertMemos == null) || alertMemos.isEmpty());
+ }
+
+ // buffered record fragment
+ private static class RecordMemo {
+ byte contentType;
+ byte majorVersion;
+ byte minorVersion;
+ CipherBox encodeCipher;
+ Authenticator encodeAuthenticator;
+
+ byte[] fragment;
+ }
+
+ private static class HandshakeMemo extends RecordMemo {
+ byte handshakeType;
+ int acquireOffset;
+ }
+
+ final class HandshakeFragment {
+ private LinkedList<RecordMemo> handshakeMemos = new LinkedList<>();
+
+ void queueUpFragment(byte[] source,
+ int offset, int length) throws IOException {
+
+ HandshakeMemo memo = new HandshakeMemo();
+
+ memo.contentType = Record.ct_handshake;
+ memo.majorVersion = protocolVersion.major; // kick start version?
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.handshakeType = source[offset];
+ memo.acquireOffset = 0;
+ memo.fragment = new byte[length - 4]; // 4: header size
+ // 1: HandshakeType
+ // 3: message length
+ System.arraycopy(source, offset + 4, memo.fragment, 0, length - 4);
+
+ handshakeMemos.add(memo);
+ }
+
+ void queueUpChangeCipherSpec() {
+ RecordMemo memo = new RecordMemo();
+
+ memo.contentType = Record.ct_change_cipher_spec;
+ memo.majorVersion = protocolVersion.major;
+ memo.minorVersion = protocolVersion.minor;
+ memo.encodeCipher = writeCipher;
+ memo.encodeAuthenticator = writeAuthenticator;
+
+ memo.fragment = new byte[1];
+ memo.fragment[0] = 1;
+
+ handshakeMemos.add(memo);
+ }
+
+ Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException {
+ if (isEmpty()) {
+ return null;
+ }
+
+ RecordMemo memo = handshakeMemos.getFirst();
+ HandshakeMemo hsMemo = null;
+ if (memo.contentType == Record.ct_handshake) {
+ hsMemo = (HandshakeMemo)memo;
+ }
+
+ int macLen = 0;
+ if (memo.encodeAuthenticator instanceof MAC) {
+ macLen = ((MAC)memo.encodeAuthenticator).MAClen();
+ }
+
+ // ChangeCipherSpec message is pretty small. Don't worry about
+ // the fragmentation of ChangeCipherSpec record.
+ int fragLen;
+ if (packetSize > 0) {
+ fragLen = Math.min(maxRecordSize, packetSize);
+ fragLen = memo.encodeCipher.calculateFragmentSize(
+ fragLen, macLen, headerSize);
+ } else {
+ fragLen = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLen = Math.min(fragLen, fragmentSize);
+ }
+
+ int dstPos = dstBuf.position();
+ int dstLim = dstBuf.limit();
+ int dstContent = dstPos + headerSize +
+ memo.encodeCipher.getExplicitNonceSize();
+ dstBuf.position(dstContent);
+
+ if (hsMemo != null) {
+ int remainingFragLen = fragLen;
+ while ((remainingFragLen > 0) && !handshakeMemos.isEmpty()) {
+ int memoFragLen = hsMemo.fragment.length;
+ if (hsMemo.acquireOffset == 0) {
+ // Don't fragment handshake message header
+ if (remainingFragLen <= 4) {
+ break;
+ }
+
+ dstBuf.put(hsMemo.handshakeType);
+ dstBuf.put((byte)((memoFragLen >> 16) & 0xFF));
+ dstBuf.put((byte)((memoFragLen >> 8) & 0xFF));
+ dstBuf.put((byte)(memoFragLen & 0xFF));
+
+ remainingFragLen -= 4;
+ } // Otherwise, handshake message is fragmented.
+
+ int chipLen = Math.min(remainingFragLen,
+ (memoFragLen - hsMemo.acquireOffset));
+ dstBuf.put(hsMemo.fragment, hsMemo.acquireOffset, chipLen);
+
+ hsMemo.acquireOffset += chipLen;
+ if (hsMemo.acquireOffset == memoFragLen) {
+ handshakeMemos.removeFirst();
+
+ // still have space for more records?
+ if ((remainingFragLen > chipLen) &&
+ !handshakeMemos.isEmpty()) {
+
+ // look for the next buffered record fragment
+ RecordMemo reMemo = handshakeMemos.getFirst();
+ if (reMemo.contentType == Record.ct_handshake) {
+ hsMemo = (HandshakeMemo)reMemo;
+ } else {
+ // not handshake message, break the loop
+ break;
+ }
+ }
+ }
+
+ remainingFragLen -= chipLen;
+ }
+
+ fragLen -= remainingFragLen;
+ } else {
+ fragLen = Math.min(fragLen, memo.fragment.length);
+ dstBuf.put(memo.fragment, 0, fragLen);
+
+ handshakeMemos.removeFirst();
+ }
+
+ dstBuf.limit(dstBuf.position());
+ dstBuf.position(dstContent);
+
+ if ((debug != null) && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion + " " +
+ Record.contentName(memo.contentType) +
+ ", length = " + dstBuf.remaining());
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ long recordSN = encrypt(memo.encodeAuthenticator, memo.encodeCipher,
+ memo.contentType, dstBuf,
+ dstPos, dstLim, headerSize,
+ ProtocolVersion.valueOf(memo.majorVersion,
+ memo.minorVersion), false);
+
+ if ((debug != null) && Debug.isOn("packet")) {
+ ByteBuffer temporary = dstBuf.duplicate();
+ temporary.limit(temporary.position());
+ temporary.position(dstPos);
+ Debug.printHex(
+ "[Raw write]: length = " + temporary.remaining(),
+ temporary);
+ }
+
+ // remain the limit unchanged
+ dstBuf.limit(dstLim);
+
+ // Reset the fragmentation offset.
+ if (hsMemo != null) {
+ return new Ciphertext(RecordType.valueOf(
+ hsMemo.contentType, hsMemo.handshakeType), recordSN);
+ } else {
+ return new Ciphertext(
+ RecordType.RECORD_CHANGE_CIPHER_SPEC, recordSN);
+ }
+ }
+
+ boolean isEmpty() {
+ return handshakeMemos.isEmpty();
+ }
+ }
+
+ /*
+ * Need to split the payload except the following cases:
+ *
+ * 1. protocol version is TLS 1.1 or later;
+ * 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
+ * 3. the payload is the first application record of a freshly
+ * negotiated TLS session.
+ * 4. the CBC protection is disabled;
+ *
+ * By default, we counter chosen plaintext issues on CBC mode
+ * ciphersuites in SSLv3/TLS1.0 by sending one byte of application
+ * data in the first record of every payload, and the rest in
+ * subsequent record(s). Note that the issues have been solved in
+ * TLS 1.1 or later.
+ *
+ * It is not necessary to split the very first application record of
+ * a freshly negotiated TLS session, as there is no previous
+ * application data to guess. To improve compatibility, we will not
+ * split such records.
+ *
+ * This avoids issues in the outbound direction. For a full fix,
+ * the peer must have similar protections.
+ */
+ boolean needToSplitPayload() {
+ return (!protocolVersion.useTLS11PlusSpec()) &&
+ writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
+ Record.enableCBCProtection;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+/**
+ * SSL/TLS record
+ *
+ * @author David Brownell
+ */
+interface SSLRecord extends Record {
+
+ static final int headerSize = 5; // SSLv3 record header
+
+ /*
+ * The size of the header plus the max IV length
+ */
+ static final int headerPlusMaxIVSize =
+ headerSize // header
+ + maxIVLength; // iv
+
+ /*
+ * The maximum size that may be increased when translating plaintext to
+ * ciphertext fragment.
+ */
+ static final int maxPlaintextPlusSize =
+ headerSize // header
+ + maxIVLength // iv
+ + maxMacSize // MAC or AEAD tag
+ + maxPadding; // block cipher padding
+
+ /*
+ * SSL has a maximum record size. It's header, (compressed) data,
+ * padding, and a trailer for the message authentication information (MAC
+ * for block and stream ciphers, and message authentication tag for AEAD
+ * ciphers).
+ *
+ * Some compression algorithms have rare cases where they expand the data.
+ * As we don't support compression at this time, leave that out.
+ */
+ static final int maxRecordSize =
+ headerPlusMaxIVSize // header + iv
+ + maxDataSize // data
+ + maxPadding // padding
+ + maxMacSize; // MAC or AEAD tag
+
+ /*
+ * For CBC protection in SSL3/TLS1, we break some plaintext into two
+ * packets. Max application data size for the second packet.
+ */
+ static final int maxDataSizeMinusOneByteRecord =
+ maxDataSize // max data size
+ - ( // max one byte record size
+ headerPlusMaxIVSize // header + iv
+ + 1 // one byte data
+ + maxPadding // padding
+ + maxMacSize // MAC
+ );
+
+ /*
+ * The maximum large record size.
+ *
+ * Some SSL/TLS implementations support large fragment upto 2^15 bytes,
+ * such as Microsoft. We support large incoming fragments.
+ *
+ * The maximum large record size is defined as maxRecordSize plus 2^14,
+ * this is the amount OpenSSL is using.
+ */
+ static final int maxLargeRecordSize =
+ maxRecordSize // Max size with a conforming implementation
+ + maxDataSize; // extra 2^14 bytes for large data packets.
+
+
+ /*
+ * Maximum record size for alert and change cipher spec records.
+ * They only contain 2 and 1 bytes of data, respectively.
+ * Allocate a smaller array.
+ */
+ static final int maxAlertRecordSize =
+ headerPlusMaxIVSize // header + iv
+ + 2 // alert
+ + maxPadding // padding
+ + maxMacSize; // MAC
+
+ /*
+ * We may need to send this SSL v2 "No Cipher" message back, if we
+ * are faced with an SSLv2 "hello" that's not saying "I talk v3".
+ * It's the only one documented in the V2 spec as a fatal error.
+ */
+ static final byte[] v2NoCipher = {
+ (byte)0x80, (byte)0x03, // unpadded 3 byte record
+ (byte)0x00, // ... error message
+ (byte)0x00, (byte)0x01 // ... NO_CIPHER error
+ };
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -68,7 +68,7 @@
private SSLContextImpl sslContext;
/* Do newly accepted connections require clients to authenticate? */
- private byte doClientAuth = SSLEngineImpl.clauth_none;
+ private ClientAuthType clientAuthType = ClientAuthType.CLIENT_AUTH_NONE;
/* Do new connections created here use the "server" mode of SSL? */
private boolean useServerMode = true;
@@ -230,13 +230,13 @@
*/
@Override
public void setNeedClientAuth(boolean flag) {
- doClientAuth = (flag ?
- SSLEngineImpl.clauth_required : SSLEngineImpl.clauth_none);
+ clientAuthType = (flag ? ClientAuthType.CLIENT_AUTH_REQUIRED :
+ ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public boolean getNeedClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_required);
+ return (clientAuthType == ClientAuthType.CLIENT_AUTH_REQUIRED);
}
/**
@@ -245,13 +245,13 @@
*/
@Override
public void setWantClientAuth(boolean flag) {
- doClientAuth = (flag ?
- SSLEngineImpl.clauth_requested : SSLEngineImpl.clauth_none);
+ clientAuthType = (flag ? ClientAuthType.CLIENT_AUTH_REQUESTED :
+ ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public boolean getWantClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_requested);
+ return (clientAuthType == ClientAuthType.CLIENT_AUTH_REQUESTED);
}
/**
@@ -341,7 +341,7 @@
@Override
public Socket accept() throws IOException {
SSLSocketImpl s = new SSLSocketImpl(sslContext, useServerMode,
- enabledCipherSuites, doClientAuth, enableSessionCreation,
+ enabledCipherSuites, clientAuthType, enableSessionCreation,
enabledProtocols, identificationProtocol, algorithmConstraints,
sniMatchers, preferLocalCipherSuites);
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -109,6 +109,8 @@
private String[] peerSupportedSignAlgs;
private List<SNIServerName> requestedServerNames;
+ private int negotiatedMaxFragLen;
+ private int maximumPacketSize;
// Principals for non-certificate based cipher suites
private Principal peerPrincipal;
@@ -177,6 +179,7 @@
sessionCount = ++counter;
localSupportedSignAlgs =
SignatureAndHashAlgorithm.getAlgorithmNames(algorithms);
+ negotiatedMaxFragLen = -1;
if (debug != null && Debug.isOn("session")) {
System.out.println("%% Initialized: " + this);
@@ -422,10 +425,9 @@
// change record of peer identity even by accident, much
// less do it intentionally.
//
- if ((cipherSuite.keyExchange == K_KRB5) ||
- (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
throw new SSLPeerUnverifiedException("no certificates expected"
- + " for Kerberos cipher suites");
+ + " for " + cipherSuite.keyExchange + " cipher suites");
}
if (peerCerts == null) {
throw new SSLPeerUnverifiedException("peer not authenticated");
@@ -478,10 +480,9 @@
// change record of peer identity even by accident, much
// less do it intentionally.
//
- if ((cipherSuite.keyExchange == K_KRB5) ||
- (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
throw new SSLPeerUnverifiedException("no certificates expected"
- + " for Kerberos cipher suites");
+ + " for " + cipherSuite.keyExchange + " cipher suites");
}
if (peerCerts == null) {
throw new SSLPeerUnverifiedException("peer not authenticated");
@@ -519,10 +520,9 @@
* change record of peer identity even by accident, much
* less do it intentionally.
*/
- if ((cipherSuite.keyExchange == K_KRB5) ||
- (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
throw new SSLPeerUnverifiedException("no certificates expected"
- + " for Kerberos cipher suites");
+ + " for " + cipherSuite.keyExchange + " cipher suites");
}
if (peerCerts != null) {
return peerCerts.clone();
@@ -537,7 +537,7 @@
*
* @return the peer's principal. Returns an X500Principal of the
* end-entity certificate for X509-based cipher suites, and
- * Principal for Kerberos cipher suites.
+ * Principal for Kerberos cipher suites, etc.
*
* @throws SSLPeerUnverifiedException if the peer's identity has not
* been verified
@@ -546,12 +546,10 @@
public Principal getPeerPrincipal()
throws SSLPeerUnverifiedException
{
- if ((cipherSuite.keyExchange == K_KRB5) ||
- (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
if (peerPrincipal == null) {
throw new SSLPeerUnverifiedException("peer not authenticated");
} else {
- // Eliminate dependency on KerberosPrincipal
return peerPrincipal;
}
}
@@ -566,15 +564,13 @@
*
* @return the principal sent to the peer. Returns an X500Principal
* of the end-entity certificate for X509-based cipher suites, and
- * Principal for Kerberos cipher suites. If no principal was
+ * Principal for Kerberos cipher suites, etc. If no principal was
* sent, then null is returned.
*/
@Override
public Principal getLocalPrincipal() {
- if ((cipherSuite.keyExchange == K_KRB5) ||
- (cipherSuite.keyExchange == K_KRB5_EXPORT)) {
- // Eliminate dependency on KerberosPrincipal
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
return (localPrincipal == null ? null : localPrincipal);
}
return (localCerts == null ? null :
@@ -785,8 +781,30 @@
*/
@Override
public synchronized int getPacketBufferSize() {
- return acceptLargeFragments ?
- Record.maxLargeRecordSize : Record.maxRecordSize;
+ // Use the bigger packet size calculated from maximumPacketSize
+ // and negotiatedMaxFragLen.
+ int packetSize = 0;
+ if (negotiatedMaxFragLen > 0) {
+ packetSize = cipherSuite.calculatePacketSize(
+ negotiatedMaxFragLen, protocolVersion,
+ protocolVersion.isDTLSProtocol());
+ }
+
+ if (maximumPacketSize > 0) {
+ return (maximumPacketSize > packetSize) ?
+ maximumPacketSize : packetSize;
+ }
+
+ if (packetSize != 0) {
+ return packetSize;
+ }
+
+ if (protocolVersion.isDTLSProtocol()) {
+ return DTLSRecord.maxRecordSize;
+ } else {
+ return acceptLargeFragments ?
+ SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize;
+ }
}
/**
@@ -795,7 +813,64 @@
*/
@Override
public synchronized int getApplicationBufferSize() {
- return getPacketBufferSize() - Record.headerSize;
+ // Use the bigger fragment size calculated from maximumPacketSize
+ // and negotiatedMaxFragLen.
+ int fragmentSize = 0;
+ if (maximumPacketSize > 0) {
+ fragmentSize = cipherSuite.calculateFragSize(
+ maximumPacketSize, protocolVersion,
+ protocolVersion.isDTLSProtocol());
+ }
+
+ if (negotiatedMaxFragLen > 0) {
+ return (negotiatedMaxFragLen > fragmentSize) ?
+ negotiatedMaxFragLen : fragmentSize;
+ }
+
+ if (fragmentSize != 0) {
+ return fragmentSize;
+ }
+
+ if (protocolVersion.isDTLSProtocol()) {
+ return Record.maxDataSize;
+ } else {
+ int maxPacketSize = acceptLargeFragments ?
+ SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize;
+ return (maxPacketSize - SSLRecord.headerSize);
+ }
+ }
+
+ /**
+ * Sets the negotiated maximum fragment length, as specified by the
+ * max_fragment_length ClientHello extension in RFC 6066.
+ *
+ * @param negotiatedMaxFragLen
+ * the negotiated maximum fragment length, or {@code -1} if
+ * no such length has been negotiated.
+ */
+ synchronized void setNegotiatedMaxFragSize(
+ int negotiatedMaxFragLen) {
+
+ this.negotiatedMaxFragLen = negotiatedMaxFragLen;
+ }
+
+ /**
+ * Get the negotiated maximum fragment length, as specified by the
+ * max_fragment_length ClientHello extension in RFC 6066.
+ *
+ * @return the negotiated maximum fragment length, or {@code -1} if
+ * no such length has been negotiated.
+ */
+ synchronized int getNegotiatedMaxFragSize() {
+ return negotiatedMaxFragLen;
+ }
+
+ synchronized void setMaximumPacketSize(int maximumPacketSize) {
+ this.maximumPacketSize = maximumPacketSize;
+ }
+
+ synchronized int getMaximumPacketSize() {
+ return maximumPacketSize;
}
/**
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -27,6 +27,7 @@
package sun.security.ssl;
import java.io.*;
+import java.nio.*;
import java.net.*;
import java.security.GeneralSecurityException;
import java.security.AccessController;
@@ -155,30 +156,16 @@
private static final int cs_DATA = 2;
private static final int cs_RENEGOTIATE = 3;
private static final int cs_ERROR = 4;
- private static final int cs_SENT_CLOSE = 5;
+ private static final int cs_SENT_CLOSE = 5;
private static final int cs_CLOSED = 6;
private static final int cs_APP_CLOSED = 7;
-
- /*
- * Client authentication be off, requested, or required.
- *
- * Migrated to SSLEngineImpl:
- * clauth_none/cl_auth_requested/clauth_required
- */
-
/*
* Drives the protocol state machine.
*/
private volatile int connectionState;
/*
- * Flag indicating that the engine's handshaker has done the necessary
- * steps so the engine may process a ChangeCipherSpec message.
- */
- private boolean receivedCCS;
-
- /*
* Flag indicating if the next record we receive MUST be a Finished
* message. Temporarily set during the handshake to ensure that
* a change cipher spec message is followed by a finished message.
@@ -197,7 +184,8 @@
* Per-connection private state that doesn't change when the
* session is changed.
*/
- private byte doClientAuth;
+ private ClientAuthType doClientAuth =
+ ClientAuthType.CLIENT_AUTH_NONE;
private boolean roleIsServer;
private boolean enableSessionCreation = true;
private String host;
@@ -284,9 +272,9 @@
* is created, at which time the new handshaker's state is set.
*
* The readLock is held during readRecord(), which is responsible
- * for reading an InputRecord, decrypting it, and processing it.
+ * for reading an SSLInputRecord, decrypting it, and processing it.
* The readLock ensures that these three steps are done atomically
- * and that once started, no other thread can block on InputRecord.read.
+ * and that once started, no other thread can block on SSLInputRecord.read.
* This is necessary so that processing of close_notify alerts
* from the peer are handled properly.
*/
@@ -294,14 +282,8 @@
final ReentrantLock writeLock = new ReentrantLock();
final private Object readLock = new Object();
- private InputRecord inrec;
-
- /*
- * Crypto state that's reinitialized when the session changes.
- */
- private Authenticator readAuthenticator, writeAuthenticator;
- private CipherBox readCipher, writeCipher;
- // NOTE: compression state would be saved here
+ InputRecord inputRecord;
+ OutputRecord outputRecord;
/*
* security parameters for secure renegotiation.
@@ -368,7 +350,7 @@
/*
* The SSL version associated with this connection.
*/
- private ProtocolVersion protocolVersion = ProtocolVersion.DEFAULT;
+ private ProtocolVersion protocolVersion = ProtocolVersion.DEFAULT_TLS;
/* Class and subclass dynamic debugging support */
private static final Debug debug = Debug.getInstance("ssl");
@@ -390,6 +372,11 @@
*/
private boolean preferLocalCipherSuites = false;
+ /*
+ * The maximum expected network packet size for SSL/TLS/DTLS records.
+ */
+ private int maximumPacketSize = 0;
+
//
// CONSTRUCTORS AND INITIALIZATION CODE
//
@@ -491,7 +478,7 @@
* giving control over the use of SSL client authentication.
*/
SSLSocketImpl(SSLContextImpl context, boolean serverMode,
- CipherSuiteList suites, byte clientAuth,
+ CipherSuiteList suites, ClientAuthType clientAuth,
boolean sessionCreation, ProtocolList protocols,
String identificationProtocol,
AlgorithmConstraints algorithmConstraints,
@@ -594,17 +581,6 @@
*/
roleIsServer = isServer;
connectionState = cs_START;
- receivedCCS = false;
-
- /*
- * default read and write side cipher and MAC support
- *
- * Note: compression support would go here too
- */
- readCipher = CipherBox.NULL;
- readAuthenticator = MAC.NULL;
- writeCipher = CipherBox.NULL;
- writeAuthenticator = MAC.NULL;
// initial security parameters for secure renegotiation
secureRenegotiation = false;
@@ -616,7 +592,10 @@
enabledProtocols =
sslContext.getDefaultProtocolList(roleIsServer);
- inrec = null;
+ inputRecord = new SSLSocketInputRecord();;
+ outputRecord = new SSLSocketOutputRecord();
+
+ maximumPacketSize = outputRecord.getMaxPacketSize();
// save the acc
acc = AccessController.getContext();
@@ -672,6 +651,9 @@
sockInput = super.getInputStream();
sockOutput = super.getOutputStream();
+ inputRecord.setDeliverStream(sockOutput);
+ outputRecord.setDeliverStream(sockOutput);
+
/*
* Move to handshaking state, with pending session initialized
* to defaults and the appropriate kind of handshaker set up.
@@ -696,29 +678,18 @@
//
/*
- * AppOutputStream calls may need to buffer multiple outbound
- * application packets.
+ * Application data record output.
*
- * All other writeRecord() calls will not buffer, so do not hold
- * these records.
+ * Application data can't be sent until the first handshake establishes
+ * a session.
*/
- void writeRecord(OutputRecord r) throws IOException {
- writeRecord(r, false);
- }
-
- /*
- * Record Output. Application data can't be sent until the first
- * handshake establishes a session.
- *
- * NOTE: we let empty records be written as a hook to force some
- * TCP-level activity, notably handshaking, to occur.
- */
- void writeRecord(OutputRecord r, boolean holdRecord) throws IOException {
+ void writeRecord(byte[] source, int offset, int length) throws IOException {
/*
* The loop is in case of HANDSHAKE --> ERROR transitions, etc
*/
- loop:
- while (r.contentType() == Record.ct_application_data) {
+ // Don't bother to check the emptiness of source applicatoin data
+ // before the security connection established.
+ for (boolean readyForApp = false; !readyForApp;) {
/*
* Not all states support passing application data. We
* synchronize access to the connection state, so that
@@ -726,41 +697,43 @@
*/
switch (getConnectionState()) {
- /*
- * We've deferred the initial handshaking till just now,
- * when presumably a thread's decided it's OK to block for
- * longish periods of time for I/O purposes (as well as
- * configured the cipher suites it wants to use).
- */
- case cs_HANDSHAKE:
- performInitialHandshake();
- break;
+ /*
+ * We've deferred the initial handshaking till just now,
+ * when presumably a thread's decided it's OK to block for
+ * longish periods of time for I/O purposes (as well as
+ * configured the cipher suites it wants to use).
+ */
+ case cs_HANDSHAKE:
+ performInitialHandshake();
+ break;
- case cs_DATA:
- case cs_RENEGOTIATE:
- break loop;
+ case cs_DATA:
+ case cs_RENEGOTIATE:
+ readyForApp = true;
+ break;
- case cs_ERROR:
- fatal(Alerts.alert_close_notify,
- "error while writing to socket");
- break; // dummy
+ case cs_ERROR:
+ fatal(Alerts.alert_close_notify,
+ "error while writing to socket");
+ break; // dummy
- case cs_SENT_CLOSE:
- case cs_CLOSED:
- case cs_APP_CLOSED:
- // we should never get here (check in AppOutputStream)
- // this is just a fallback
- if (closeReason != null) {
- throw closeReason;
- } else {
- throw new SocketException("Socket closed");
- }
+ case cs_SENT_CLOSE:
+ case cs_CLOSED:
+ case cs_APP_CLOSED:
+ // we should never get here (check in AppOutputStream)
+ // this is just a fallback
+ if (closeReason != null) {
+ throw closeReason;
+ } else {
+ throw new SocketException("Socket closed");
+ }
- /*
- * Else something's goofy in this state machine's use.
- */
- default:
- throw new SSLProtocolException("State error, send app data");
+ /*
+ * Else something's goofy in this state machine's use.
+ */
+ default:
+ throw new SSLProtocolException(
+ "State error, send app data");
}
}
@@ -772,97 +745,19 @@
// implementations are fragile and don't like to see empty
// records, so this also increases robustness.
//
- if (!r.isEmpty()) {
-
- // If the record is a close notify alert, we need to honor
- // socket option SO_LINGER. Note that we will try to send
- // the close notify even if the SO_LINGER set to zero.
- if (r.isAlert(Alerts.alert_close_notify) && getSoLinger() >= 0) {
-
- // keep and clear the current thread interruption status.
- boolean interrupted = Thread.interrupted();
- try {
- if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) {
- try {
- writeRecordInternal(r, holdRecord);
- } finally {
- writeLock.unlock();
- }
- } else {
- SSLException ssle = new SSLException(
- "SO_LINGER timeout," +
- " close_notify message cannot be sent.");
-
-
- // For layered, non-autoclose sockets, we are not
- // able to bring them into a usable state, so we
- // treat it as fatal error.
- if (isLayered() && !autoClose) {
- // Note that the alert description is
- // specified as -1, so no message will be send
- // to peer anymore.
- fatal((byte)(-1), ssle);
- } else if ((debug != null) && Debug.isOn("ssl")) {
- System.out.println(
- Thread.currentThread().getName() +
- ", received Exception: " + ssle);
- }
-
- // RFC2246 requires that the session becomes
- // unresumable if any connection is terminated
- // without proper close_notify messages with
- // level equal to warning.
- //
- // RFC4346 no longer requires that a session not be
- // resumed if failure to properly close a connection.
- //
- // We choose to make the session unresumable if
- // failed to send the close_notify message.
- //
- sess.invalidate();
- }
- } catch (InterruptedException ie) {
- // keep interrupted status
- interrupted = true;
- }
-
- // restore the interrupted status
- if (interrupted) {
- Thread.currentThread().interrupt();
- }
- } else {
- writeLock.lock();
- try {
- writeRecordInternal(r, holdRecord);
- } finally {
- writeLock.unlock();
- }
+ if (length > 0) {
+ writeLock.lock();
+ try {
+ outputRecord.deliver(source, offset, length);
+ } catch (SSLHandshakeException she) {
+ // may be record sequence number overflow
+ fatal(Alerts.alert_handshake_failure, she);
+ } catch (IOException e) {
+ fatal(Alerts.alert_unexpected_message, e);
+ } finally {
+ writeLock.unlock();
}
}
- }
-
- private void writeRecordInternal(OutputRecord r,
- boolean holdRecord) throws IOException {
-
- // r.compress(c);
- r.encrypt(writeAuthenticator, writeCipher);
-
- if (holdRecord) {
- // If we were requested to delay the record due to possibility
- // of Nagle's being active when finally got to writing, and
- // it's actually not, we don't really need to delay it.
- if (getTcpNoDelay()) {
- holdRecord = false;
- } else {
- // We need to hold the record, so let's provide
- // a per-socket place to do it.
- if (heldRecordBuffer == null) {
- // Likely only need 37 bytes.
- heldRecordBuffer = new ByteArrayOutputStream(40);
- }
- }
- }
- r.write(sockOutput, holdRecord, heldRecordBuffer);
/*
* Check the sequence number state
@@ -874,51 +769,173 @@
* when there is enough sequence number space left to
* handle a few more records, so the sequence number
* of the last record cannot be wrapped.
+ *
+ * Don't bother to kickstart the renegotiation when the
+ * local is asking for it.
*/
- if (connectionState < cs_ERROR) {
- checkSequenceNumber(writeAuthenticator, r.contentType());
- }
+ if ((connectionState == cs_DATA) && outputRecord.seqNumIsHuge()) {
+ /*
+ * Ask for renegotiation when need to renew sequence number.
+ *
+ * Don't bother to kickstart the renegotiation when the local is
+ * asking for it.
+ */
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", request renegotiation " +
+ "to avoid sequence number overflow");
+ }
- // turn off the flag of the first application record
- if (isFirstAppOutputRecord &&
- r.contentType() == Record.ct_application_data) {
- isFirstAppOutputRecord = false;
+ startHandshake();
}
}
/*
- * Need to split the payload except the following cases:
- *
- * 1. protocol version is TLS 1.1 or later;
- * 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
- * 3. the payload is the first application record of a freshly
- * negotiated TLS session.
- * 4. the CBC protection is disabled;
- *
- * More details, please refer to AppOutputStream.write(byte[], int, int).
+ * Alert record output.
*/
- boolean needToSplitPayload() {
- writeLock.lock();
- try {
- return (protocolVersion.v <= ProtocolVersion.TLS10.v) &&
- writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
- Record.enableCBCProtection;
- } finally {
- writeLock.unlock();
+ void writeAlert(byte level, byte description) throws IOException {
+
+ // If the record is a close notify alert, we need to honor
+ // socket option SO_LINGER. Note that we will try to send
+ // the close notify even if the SO_LINGER set to zero.
+ if ((description == Alerts.alert_close_notify) && getSoLinger() >= 0) {
+
+ // keep and clear the current thread interruption status.
+ boolean interrupted = Thread.interrupted();
+ try {
+ if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) {
+ try {
+ outputRecord.encodeAlert(level, description);
+ } finally {
+ writeLock.unlock();
+ }
+ } else {
+ SSLException ssle = new SSLException(
+ "SO_LINGER timeout," +
+ " close_notify message cannot be sent.");
+
+
+ // For layered, non-autoclose sockets, we are not
+ // able to bring them into a usable state, so we
+ // treat it as fatal error.
+ if (isLayered() && !autoClose) {
+ // Note that the alert description is
+ // specified as -1, so no message will be send
+ // to peer anymore.
+ fatal((byte)(-1), ssle);
+ } else if ((debug != null) && Debug.isOn("ssl")) {
+ System.out.println(
+ Thread.currentThread().getName() +
+ ", received Exception: " + ssle);
+ }
+
+ // RFC2246 requires that the session becomes
+ // unresumable if any connection is terminated
+ // without proper close_notify messages with
+ // level equal to warning.
+ //
+ // RFC4346 no longer requires that a session not be
+ // resumed if failure to properly close a connection.
+ //
+ // We choose to make the session unresumable if
+ // failed to send the close_notify message.
+ //
+ sess.invalidate();
+ }
+ } catch (InterruptedException ie) {
+ // keep interrupted status
+ interrupted = true;
+ }
+
+ // restore the interrupted status
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ } else {
+ writeLock.lock();
+ try {
+ outputRecord.encodeAlert(level, description);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ // Don't bother to check sequence number overlap here. If sequence
+ // number is huge, there should be enough sequence number space to
+ // request renegotiation in next application data read and write.
+ }
+
+
+ int bytesInCompletePacket() throws IOException {
+ if (getConnectionState() == cs_HANDSHAKE) {
+ performInitialHandshake();
+ }
+
+ synchronized (readLock) {
+ int state = getConnectionState();
+ if ((state == cs_CLOSED) ||
+ (state == cs_ERROR) || (state == cs_APP_CLOSED)) {
+ return -1;
+ }
+
+ try {
+ return inputRecord.bytesInCompletePacket(sockInput);
+ } catch (EOFException eofe) {
+ boolean handshaking = (connectionState <= cs_HANDSHAKE);
+ boolean rethrow = requireCloseNotify || handshaking;
+ if ((debug != null) && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", received EOFException: "
+ + (rethrow ? "error" : "ignored"));
+ }
+
+ if (!rethrow) {
+ // treat as if we had received a close_notify
+ closeInternal(false);
+ } else {
+ SSLException e;
+ if (handshaking) {
+ e = new SSLHandshakeException(
+ "Remote host terminated the handshake");
+ } else {
+ e = new SSLProtocolException(
+ "Remote host terminated the handshake");
+ }
+ e.initCause(eofe);
+ throw e;
+ }
+ }
+
+ return -1;
}
}
+ // the caller have synchronized readLock
+ void expectingFinishFlight() {
+ inputRecord.expectingFinishFlight();
+ }
+
/*
- * Read an application data record. Alerts and handshake
- * messages are handled directly.
+ * Read an application data record.
+ *
+ * Alerts and handshake messages are internally handled directly.
*/
- void readDataRecord(InputRecord r) throws IOException {
+ int readRecord(ByteBuffer buffer) throws IOException {
if (getConnectionState() == cs_HANDSHAKE) {
performInitialHandshake();
}
- readRecord(r, true);
+
+ return readRecord(buffer, true);
}
+ /*
+ * Read a record, no application data input required.
+ *
+ * Alerts and handshake messages are internally handled directly.
+ */
+ int readRecord(boolean needAppData) throws IOException {
+ return readRecord(null, needAppData);
+ }
/*
* Clear the pipeline of records from the peer, optionally returning
@@ -929,11 +946,11 @@
* Don't synchronize (this) during a blocking read() since it
* protects data which is accessed on the write side as well.
*/
- private void readRecord(InputRecord r, boolean needAppData)
+ private int readRecord(ByteBuffer buffer, boolean needAppData)
throws IOException {
int state;
- // readLock protects reading and processing of an InputRecord.
+ // readLock protects reading and processing of an SSLInputRecord.
// It keeps the reading from sockInput and processing of the record
// atomic so that no two threads can be blocked on the
// read from the same input stream at the same time.
@@ -944,306 +961,282 @@
//
// Use readLock instead of 'this' for locking because
// 'this' also protects data accessed during writing.
- synchronized (readLock) {
- /*
- * Read and handle records ... return application data
- * ONLY if it's needed.
- */
-
- while (((state = getConnectionState()) != cs_CLOSED) &&
- (state != cs_ERROR) && (state != cs_APP_CLOSED)) {
+ synchronized (readLock) {
/*
- * Read a record ... maybe emitting an alert if we get a
- * comprehensible but unsupported "hello" message during
- * format checking (e.g. V2).
- */
- try {
- r.setAppDataValid(false);
- r.read(sockInput, sockOutput);
- } catch (SSLProtocolException e) {
- try {
- fatal(Alerts.alert_unexpected_message, e);
- } catch (IOException x) {
- // discard this exception
- }
- throw e;
- } catch (EOFException eof) {
- boolean handshaking = (getConnectionState() <= cs_HANDSHAKE);
- boolean rethrow = requireCloseNotify || handshaking;
- if ((debug != null) && Debug.isOn("ssl")) {
- System.out.println(Thread.currentThread().getName() +
- ", received EOFException: "
- + (rethrow ? "error" : "ignored"));
- }
- if (rethrow) {
- SSLException e;
- if (handshaking) {
- e = new SSLHandshakeException
- ("Remote host closed connection during handshake");
- } else {
- e = new SSLProtocolException
- ("Remote host closed connection incorrectly");
- }
- e.initCause(eof);
- throw e;
- } else {
- // treat as if we had received a close_notify
- closeInternal(false);
- continue;
- }
- }
-
-
- /*
- * The basic SSLv3 record protection involves (optional)
- * encryption for privacy, and an integrity check ensuring
- * data origin authentication. We do them both here, and
- * throw a fatal alert if the integrity check fails.
- */
- try {
- r.decrypt(readAuthenticator, readCipher);
- } catch (BadPaddingException e) {
- byte alertType = (r.contentType() == Record.ct_handshake)
- ? Alerts.alert_handshake_failure
- : Alerts.alert_bad_record_mac;
- fatal(alertType, e.getMessage(), e);
- }
-
- // if (!r.decompress(c))
- // fatal(Alerts.alert_decompression_failure,
- // "decompression failure");
-
- /*
- * Process the record.
+ * Read and handle records ... return application data
+ * ONLY if it's needed.
*/
- synchronized (this) {
- switch (r.contentType()) {
- case Record.ct_handshake:
- /*
- * Handshake messages always go to a pending session
- * handshaker ... if there isn't one, create one. This
- * must work asynchronously, for renegotiation.
- *
- * NOTE that handshaking will either resume a session
- * which was in the cache (and which might have other
- * connections in it already), or else will start a new
- * session (new keys exchanged) with just this connection
- * in it.
- */
- initHandshaker();
- if (!handshaker.activated()) {
- // prior to handshaking, activate the handshake
- if (connectionState == cs_RENEGOTIATE) {
- // don't use SSLv2Hello when renegotiating
- handshaker.activate(protocolVersion);
- } else {
- handshaker.activate(null);
- }
- }
-
- /*
- * process the handshake record ... may contain just
- * a partial handshake message or multiple messages.
- *
- * The handshaker state machine will ensure that it's
- * a finished message.
- */
- handshaker.process_record(r, expectingFinished);
- expectingFinished = false;
+ Plaintext plainText = null;
+ while (((state = getConnectionState()) != cs_CLOSED) &&
+ (state != cs_ERROR) && (state != cs_APP_CLOSED)) {
+ // clean the buffer
+ if (buffer != null) {
+ buffer.clear();
+ }
- if (handshaker.invalidated) {
- handshaker = null;
- receivedCCS = false;
- // if state is cs_RENEGOTIATE, revert it to cs_DATA
- if (connectionState == cs_RENEGOTIATE) {
- connectionState = cs_DATA;
+ /*
+ * Read a record ... maybe emitting an alert if we get a
+ * comprehensible but unsupported "hello" message during
+ * format checking (e.g. V2).
+ */
+ try {
+ plainText = inputRecord.decode(sockInput, buffer);
+ } catch (BadPaddingException bpe) {
+ byte alertType = (state != cs_DATA) ?
+ Alerts.alert_handshake_failure :
+ Alerts.alert_bad_record_mac;
+ fatal(alertType, bpe.getMessage(), bpe);
+ } catch (SSLProtocolException spe) {
+ try {
+ fatal(Alerts.alert_unexpected_message, spe);
+ } catch (IOException x) {
+ // discard this exception, throw the original exception
+ }
+ throw spe;
+ } catch (SSLHandshakeException she) {
+ // may be record sequence number overflow
+ fatal(Alerts.alert_handshake_failure, she);
+ } catch (EOFException eof) {
+ boolean handshaking = (connectionState <= cs_HANDSHAKE);
+ boolean rethrow = requireCloseNotify || handshaking;
+ if ((debug != null) && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", received EOFException: "
+ + (rethrow ? "error" : "ignored"));
+ }
+ if (rethrow) {
+ SSLException e;
+ if (handshaking) {
+ e = new SSLHandshakeException(
+ "Remote host terminated the handshake");
+ } else {
+ e = new SSLProtocolException(
+ "Remote host terminated the connection");
}
- } else if (handshaker.isDone()) {
- // reset the parameters for secure renegotiation.
- secureRenegotiation =
- handshaker.isSecureRenegotiation();
- clientVerifyData = handshaker.getClientVerifyData();
- serverVerifyData = handshaker.getServerVerifyData();
-
- sess = handshaker.getSession();
- handshakeSession = null;
- handshaker = null;
- connectionState = cs_DATA;
- receivedCCS = false;
-
- //
- // Tell folk about handshake completion, but do
- // it in a separate thread.
- //
- if (handshakeListeners != null) {
- HandshakeCompletedEvent event =
- new HandshakeCompletedEvent(this, sess);
-
- Thread t = new ManagedLocalsThread(
- new NotifyHandshake(
- handshakeListeners.entrySet(), event),
- "HandshakeCompletedNotify-Thread");
- t.start();
- }
- }
-
- if (needAppData || connectionState != cs_DATA) {
+ e.initCause(eof);
+ throw e;
+ } else {
+ // treat as if we had received a close_notify
+ closeInternal(false);
continue;
}
- break;
+ }
+
+ // PlainText should never be null. Process input record.
+ int volume = processInputRecord(plainText, needAppData);
+
+ if (plainText.contentType == Record.ct_application_data) {
+ return volume;
+ }
+
+ if (plainText.contentType == Record.ct_handshake) {
+ if (!needAppData && connectionState == cs_DATA) {
+ return volume;
+ } // otherwise, need to read more for app data.
+ }
- case Record.ct_application_data:
- // Pass this right back up to the application.
- if (connectionState != cs_DATA
- && connectionState != cs_RENEGOTIATE
- && connectionState != cs_SENT_CLOSE) {
- throw new SSLProtocolException(
- "Data received in non-data state: " +
- connectionState);
- }
- if (expectingFinished) {
- throw new SSLProtocolException
- ("Expecting finished message, received data");
- }
- if (!needAppData) {
- throw new SSLException("Discarding app data");
- }
+ // continue to read more net data
+ } // while
- r.setAppDataValid(true);
- break;
+ //
+ // couldn't read, due to some kind of error
+ //
+ return -1;
+ } // readLock synchronization
+ }
+
+ /*
+ * Process the plainText input record.
+ */
+ private synchronized int processInputRecord(
+ Plaintext plainText, boolean needAppData) throws IOException {
- case Record.ct_alert:
- recvAlert(r);
- continue;
+ /*
+ * Process the record.
+ */
+ int volume = 0; // no application data
+ switch (plainText.contentType) {
+ case Record.ct_handshake:
+ /*
+ * Handshake messages always go to a pending session
+ * handshaker ... if there isn't one, create one. This
+ * must work asynchronously, for renegotiation.
+ *
+ * NOTE that handshaking will either resume a session
+ * which was in the cache (and which might have other
+ * connections in it already), or else will start a new
+ * session (new keys exchanged) with just this connection
+ * in it.
+ */
+ initHandshaker();
+ if (!handshaker.activated()) {
+ // prior to handshaking, activate the handshake
+ if (connectionState == cs_RENEGOTIATE) {
+ // don't use SSLv2Hello when renegotiating
+ handshaker.activate(protocolVersion);
+ } else {
+ handshaker.activate(null);
+ }
+ }
- case Record.ct_change_cipher_spec:
- if ((connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE)
- || !handshaker.sessionKeysCalculated()
- || receivedCCS) {
- // For the CCS message arriving in the wrong state
- fatal(Alerts.alert_unexpected_message,
- "illegal change cipher spec msg, conn state = "
- + connectionState + ", handshake state = "
- + handshaker.state);
- } else if (r.available() != 1 || r.read() != 1) {
- // For structural/content issues with the CCS
- fatal(Alerts.alert_unexpected_message,
- "Malformed change cipher spec msg");
+ /*
+ * process the handshake record ... may contain just
+ * a partial handshake message or multiple messages.
+ *
+ * The handshaker state machine will ensure that it's
+ * a finished message.
+ */
+ handshaker.processRecord(plainText.fragment, expectingFinished);
+ expectingFinished = false;
+
+ if (handshaker.invalidated) {
+ handshaker = null;
+ inputRecord.setHandshakeHash(null);
+ outputRecord.setHandshakeHash(null);
+
+ // if state is cs_RENEGOTIATE, revert it to cs_DATA
+ if (connectionState == cs_RENEGOTIATE) {
+ connectionState = cs_DATA;
}
+ } else if (handshaker.isDone()) {
+ // reset the parameters for secure renegotiation.
+ secureRenegotiation =
+ handshaker.isSecureRenegotiation();
+ clientVerifyData = handshaker.getClientVerifyData();
+ serverVerifyData = handshaker.getServerVerifyData();
- // Once we've received CCS, update the flag.
- // If the remote endpoint sends it again in this handshake
- // we won't process it.
- receivedCCS = true;
+ sess = handshaker.getSession();
+ handshakeSession = null;
+ handshaker = null;
+ inputRecord.setHandshakeHash(null);
+ outputRecord.setHandshakeHash(null);
+ connectionState = cs_DATA;
//
- // The first message after a change_cipher_spec
- // record MUST be a "Finished" handshake record,
- // else it's a protocol violation. We force this
- // to be checked by a minor tweak to the state
- // machine.
+ // Tell folk about handshake completion, but do
+ // it in a separate thread.
//
- changeReadCiphers();
- // next message MUST be a finished message
- expectingFinished = true;
- continue;
+ if (handshakeListeners != null) {
+ HandshakeCompletedEvent event =
+ new HandshakeCompletedEvent(this, sess);
+
+ Thread thread = new ManagedLocalsThread(
+ new NotifyHandshake(
+ handshakeListeners.entrySet(), event),
+ "HandshakeCompletedNotify-Thread");
+ thread.start();
+ }
+ }
+
+ break;
- default:
- //
- // TLS requires that unrecognized records be ignored.
- //
- if (debug != null && Debug.isOn("ssl")) {
- System.out.println(Thread.currentThread().getName() +
- ", Received record type: "
- + r.contentType());
- }
- continue;
- } // switch
+ case Record.ct_application_data:
+ if (connectionState != cs_DATA
+ && connectionState != cs_RENEGOTIATE
+ && connectionState != cs_SENT_CLOSE) {
+ throw new SSLProtocolException(
+ "Data received in non-data state: " +
+ connectionState);
+ }
+ if (expectingFinished) {
+ throw new SSLProtocolException
+ ("Expecting finished message, received data");
+ }
+ if (!needAppData) {
+ throw new SSLException("Discarding app data");
+ }
+
+ volume = plainText.fragment.remaining();
+ break;
+
+ case Record.ct_alert:
+ recvAlert(plainText.fragment);
+ break;
- /*
- * Check the sequence number state
- *
- * Note that in order to maintain the connection I/O
- * properly, we check the sequence number after the last
- * record reading process. As we request renegotiation
- * or close the connection for wrapped sequence number
- * when there is enough sequence number space left to
- * handle a few more records, so the sequence number
- * of the last record cannot be wrapped.
- */
- if (connectionState < cs_ERROR) {
- checkSequenceNumber(readAuthenticator, r.contentType());
- }
+ case Record.ct_change_cipher_spec:
+ if ((connectionState != cs_HANDSHAKE
+ && connectionState != cs_RENEGOTIATE)) {
+ // For the CCS message arriving in the wrong state
+ fatal(Alerts.alert_unexpected_message,
+ "illegal change cipher spec msg, conn state = "
+ + connectionState);
+ } else if (plainText.fragment.remaining() != 1
+ || plainText.fragment.get() != 1) {
+ // For structural/content issues with the CCS
+ fatal(Alerts.alert_unexpected_message,
+ "Malformed change cipher spec msg");
+ }
- return;
- } // synchronized (this)
- }
+ //
+ // The first message after a change_cipher_spec
+ // record MUST be a "Finished" handshake record,
+ // else it's a protocol violation. We force this
+ // to be checked by a minor tweak to the state
+ // machine.
+ //
+ handshaker.receiveChangeCipherSpec();
- //
- // couldn't read, due to some kind of error
- //
- r.close();
- return;
- } // synchronized (readLock)
- }
+ CipherBox readCipher;
+ Authenticator readAuthenticator;
+ try {
+ readCipher = handshaker.newReadCipher();
+ readAuthenticator = handshaker.newReadAuthenticator();
+ } catch (GeneralSecurityException e) {
+ // can't happen
+ throw new SSLException("Algorithm missing: ", e);
+ }
+ inputRecord.changeReadCiphers(readAuthenticator, readCipher);
- /**
- * Check the sequence number state
- *
- * RFC 4346 states that, "Sequence numbers are of type uint64 and
- * may not exceed 2^64-1. Sequence numbers do not wrap. If a TLS
- * implementation would need to wrap a sequence number, it must
- * renegotiate instead."
- */
- private void checkSequenceNumber(Authenticator authenticator, byte type)
- throws IOException {
+ // next message MUST be a finished message
+ expectingFinished = true;
+
+ break;
- /*
- * Don't bother to check the sequence number for error or
- * closed connections, or NULL MAC.
- */
- if (connectionState >= cs_ERROR || authenticator == MAC.NULL) {
- return;
+ default:
+ //
+ // TLS requires that unrecognized records be ignored.
+ //
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", Received record type: " + plainText.contentType);
+ }
+ break;
}
/*
- * Conservatively, close the connection immediately when the
- * sequence number is close to overflow
+ * Check the sequence number state
+ *
+ * Note that in order to maintain the connection I/O
+ * properly, we check the sequence number after the last
+ * record reading process. As we request renegotiation
+ * or close the connection for wrapped sequence number
+ * when there is enough sequence number space left to
+ * handle a few more records, so the sequence number
+ * of the last record cannot be wrapped.
+ *
+ * Don't bother to kickstart the renegotiation when the
+ * local is asking for it.
*/
- if (authenticator.seqNumOverflow()) {
+ if ((connectionState == cs_DATA) && inputRecord.seqNumIsHuge()) {
/*
- * TLS protocols do not define a error alert for sequence
- * number overflow. We use handshake_failure error alert
- * for handshaking and bad_record_mac for other records.
+ * Ask for renegotiation when need to renew sequence number.
+ *
+ * Don't bother to kickstart the renegotiation when the local is
+ * asking for it.
*/
if (debug != null && Debug.isOn("ssl")) {
System.out.println(Thread.currentThread().getName() +
- ", sequence number extremely close to overflow " +
- "(2^64-1 packets). Closing connection.");
-
- }
-
- fatal(Alerts.alert_handshake_failure, "sequence number overflow");
- }
-
- /*
- * Ask for renegotiation when need to renew sequence number.
- *
- * Don't bother to kickstart the renegotiation when the local is
- * asking for it.
- */
- if ((type != Record.ct_handshake) && authenticator.seqNumIsHuge()) {
- if (debug != null && Debug.isOn("ssl")) {
- System.out.println(Thread.currentThread().getName() +
", request renegotiation " +
"to avoid sequence number overflow");
}
startHandshake();
}
+
+ return volume;
}
+
//
// HANDSHAKE RELATED CODE
//
@@ -1323,6 +1316,7 @@
secureRenegotiation, clientVerifyData, serverVerifyData);
handshaker.setSNIServerNames(serverNames);
}
+ handshaker.setMaximumPacketSize(maximumPacketSize);
handshaker.setEnabledCipherSuites(enabledCipherSuites);
handshaker.setEnableSessionCreation(enableSessionCreation);
}
@@ -1342,29 +1336,12 @@
kickstartHandshake();
/*
- * All initial handshaking goes through this
- * InputRecord until we have a valid SSL connection.
- * Once initial handshaking is finished, AppInputStream's
- * InputRecord can handle any future renegotiation.
+ * All initial handshaking goes through this operation
+ * until we have a valid SSL connection.
*
- * Keep this local so that it goes out of scope and is
- * eventually GC'd.
+ * Handle handshake messages only, need no application data.
*/
- if (inrec == null) {
- inrec = new InputRecord();
-
- /*
- * Grab the characteristics already assigned to
- * AppInputStream's InputRecord. Enable checking for
- * SSLv2 hellos on this first handshake.
- */
- inrec.setHandshakeHash(input.r.getHandshakeHash());
- inrec.setHelloVersion(input.r.getHelloVersion());
- inrec.enableFormatChecks();
- }
-
- readRecord(inrec, false);
- inrec = null;
+ readRecord(false);
}
}
}
@@ -1482,9 +1459,6 @@
} else {
// we want to renegotiate, send hello request
handshaker.kickstart();
- // hello request is not included in the handshake
- // hashes, reset them
- handshaker.handshakeHash.reset();
}
}
}
@@ -1547,7 +1521,7 @@
}
}
- protected void closeSocket() throws IOException {
+ private void closeSocket() throws IOException {
if ((debug != null) && Debug.isOn("ssl")) {
System.out.println(Thread.currentThread().getName() +
@@ -1591,6 +1565,23 @@
", called close()");
}
closeInternal(true); // caller is initiating close
+
+ // Clearup the resources.
+ try {
+ synchronized (readLock) {
+ inputRecord.close();
+ }
+
+ writeLock.lock();
+ try {
+ outputRecord.close();
+ } finally {
+ writeLock.unlock();
+ }
+ } catch (IOException ioe) {
+ // ignore
+ }
+
setConnectionState(cs_APP_CLOSED);
}
@@ -1714,19 +1705,17 @@
// notify any threads waiting for the closing to finish
this.notifyAll();
}
- if (closeSocketCalled) {
- // Dispose of ciphers since we've closed socket
- disposeCiphers();
- }
+
if (cachedThrowable != null) {
/*
* Rethrow the error to the calling method
* The Throwable caught can only be an Error or RuntimeException
*/
- if (cachedThrowable instanceof Error)
- throw (Error) cachedThrowable;
- if (cachedThrowable instanceof RuntimeException)
- throw (RuntimeException) cachedThrowable;
+ if (cachedThrowable instanceof Error) {
+ throw (Error)cachedThrowable;
+ } else if (cachedThrowable instanceof RuntimeException) {
+ throw (RuntimeException)cachedThrowable;
+ } // Otherwise, unlikely
}
}
}
@@ -1750,19 +1739,14 @@
while (((state = getConnectionState()) != cs_CLOSED) &&
(state != cs_ERROR) && (state != cs_APP_CLOSED)) {
- // create the InputRecord if it isn't initialized.
- if (inrec == null) {
- inrec = new InputRecord();
- }
// Ask for app data and then throw it away
try {
- readRecord(inrec, true);
+ readRecord(true);
} catch (SocketTimeoutException e) {
// if time out, ignore the exception and continue
}
}
- inrec = null;
} catch (IOException e) {
if (debug != null && Debug.isOn("ssl")) {
System.out.println(Thread.currentThread().getName() +
@@ -1774,24 +1758,6 @@
}
}
- /**
- * Called by closeInternal() only. Be sure to consider the
- * synchronization locks carefully before calling it elsewhere.
- */
- private void disposeCiphers() {
- // See comment in changeReadCiphers()
- synchronized (readLock) {
- readCipher.dispose();
- }
- // See comment in changeReadCiphers()
- writeLock.lock();
- try {
- writeCipher.dispose();
- } finally {
- writeLock.unlock();
- }
- }
-
//
// EXCEPTION AND ALERT HANDLING
//
@@ -1853,7 +1819,7 @@
// need to perform error shutdown
boolean isSSLException = (e instanceof SSLException);
- if ((isSSLException == false) && (e instanceof IOException)) {
+ if ((!isSSLException) && (e instanceof IOException)) {
// IOException from the socket
// this means the TCP connection is already dead
// we call fatal just to set the error status
@@ -1903,9 +1869,14 @@
*/
synchronized void fatal(byte description, String diagnostic,
Throwable cause) throws IOException {
- if ((input != null) && (input.r != null)) {
- input.r.close();
+
+ // Be care of deadlock. Please don't synchronize readLock.
+ try {
+ inputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
}
+
sess.invalidate();
if (handshakeSession != null) {
handshakeSession.invalidate();
@@ -1945,15 +1916,12 @@
* Clean up our side.
*/
closeSocket();
- // Another thread may have disposed the ciphers during closing
- if (connectionState < cs_CLOSED) {
- connectionState = (oldState == cs_APP_CLOSED) ? cs_APP_CLOSED
- : cs_CLOSED;
- // We should lock readLock and writeLock if no deadlock risks.
- // See comment in changeReadCiphers()
- readCipher.dispose();
- writeCipher.dispose();
+ // Be care of deadlock. Please don't synchronize writeLock.
+ try {
+ outputRecord.close();
+ } catch (IOException ioe) {
+ // ignore
}
throw closeReason;
@@ -1964,9 +1932,10 @@
* Process an incoming alert ... caller must already have synchronized
* access to "this".
*/
- private void recvAlert(InputRecord r) throws IOException {
- byte level = (byte)r.read();
- byte description = (byte)r.read();
+ private void recvAlert(ByteBuffer fragment) throws IOException {
+ byte level = fragment.get();
+ byte description = fragment.get();
+
if (description == -1) { // check for short message
fatal(Alerts.alert_illegal_parameter, "Short alert message");
}
@@ -2029,14 +1998,14 @@
// For initial handshaking, don't send alert message to peer if
// handshaker has not started.
- if (connectionState == cs_HANDSHAKE &&
- (handshaker == null || !handshaker.started())) {
+ //
+ // Shall we send an fatal alter to terminate the connection gracefully?
+ if (connectionState <= cs_HANDSHAKE &&
+ (handshaker == null || !handshaker.started() ||
+ !handshaker.activated())) {
return;
}
- OutputRecord r = new OutputRecord(Record.ct_alert);
- r.setVersion(protocolVersion);
-
boolean useDebug = debug != null && Debug.isOn("ssl");
if (useDebug) {
synchronized (System.out) {
@@ -2054,10 +2023,8 @@
}
}
- r.write(level);
- r.write(description);
try {
- writeRecord(r);
+ writeAlert(level, description);
} catch (IOException e) {
if (useDebug) {
System.out.println(Thread.currentThread().getName() +
@@ -2070,61 +2037,10 @@
// VARIOUS OTHER METHODS
//
- /*
- * When a connection finishes handshaking by enabling use of a newly
- * negotiated session, each end learns about it in two halves (read,
- * and write). When both read and write ciphers have changed, and the
- * last handshake message has been read, the connection has joined
- * (rejoined) the new session.
- *
- * NOTE: The SSLv3 spec is rather unclear on the concepts here.
- * Sessions don't change once they're established (including cipher
- * suite and master secret) but connections can join them (and leave
- * them). They're created by handshaking, though sometime handshaking
- * causes connections to join up with pre-established sessions.
- */
- private void changeReadCiphers() throws SSLException {
- if (connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE) {
- throw new SSLProtocolException(
- "State error, change cipher specs");
- }
-
- // ... create decompressor
-
- CipherBox oldCipher = readCipher;
-
- try {
- readCipher = handshaker.newReadCipher();
- readAuthenticator = handshaker.newReadAuthenticator();
- } catch (GeneralSecurityException e) {
- // "can't happen"
- throw new SSLException("Algorithm missing: ", e);
- }
-
- /*
- * Dispose of any intermediate state in the underlying cipher.
- * For PKCS11 ciphers, this will release any attached sessions,
- * and thus make finalization faster.
- *
- * Since MAC's doFinal() is called for every SSL/TLS packet, it's
- * not necessary to do the same with MAC's.
- */
- oldCipher.dispose();
- }
-
// used by Handshaker
- void changeWriteCiphers() throws SSLException {
- if (connectionState != cs_HANDSHAKE
- && connectionState != cs_RENEGOTIATE) {
- throw new SSLProtocolException(
- "State error, change cipher specs");
- }
-
- // ... create compressor
-
- CipherBox oldCipher = writeCipher;
-
+ void changeWriteCiphers() throws IOException {
+ Authenticator writeAuthenticator;
+ CipherBox writeCipher;
try {
writeCipher = handshaker.newWriteCipher();
writeAuthenticator = handshaker.newWriteAuthenticator();
@@ -2132,12 +2048,7 @@
// "can't happen"
throw new SSLException("Algorithm missing: ", e);
}
-
- // See comment above.
- oldCipher.dispose();
-
- // reset the flag of the first application record
- isFirstAppOutputRecord = true;
+ outputRecord.changeWriteCiphers(writeAuthenticator, writeCipher);
}
/*
@@ -2146,7 +2057,7 @@
*/
synchronized void setVersion(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
- output.r.setVersion(protocolVersion);
+ outputRecord.setVersion(protocolVersion);
}
synchronized String getHost() {
@@ -2245,6 +2156,10 @@
}
synchronized void setHandshakeSession(SSLSessionImpl session) {
+ // update the fragment size, which may be negotiated during handshaking
+ inputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
+ outputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
+
handshakeSession = session;
}
@@ -2285,8 +2200,8 @@
*/
@Override
synchronized public void setNeedClientAuth(boolean flag) {
- doClientAuth = (flag ?
- SSLEngineImpl.clauth_required : SSLEngineImpl.clauth_none);
+ doClientAuth = (flag ? ClientAuthType.CLIENT_AUTH_REQUIRED :
+ ClientAuthType.CLIENT_AUTH_NONE);
if ((handshaker != null) &&
(handshaker instanceof ServerHandshaker) &&
@@ -2297,7 +2212,7 @@
@Override
synchronized public boolean getNeedClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_required);
+ return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED);
}
/**
@@ -2310,8 +2225,8 @@
*/
@Override
synchronized public void setWantClientAuth(boolean flag) {
- doClientAuth = (flag ?
- SSLEngineImpl.clauth_requested : SSLEngineImpl.clauth_none);
+ doClientAuth = (flag ? ClientAuthType.CLIENT_AUTH_REQUESTED :
+ ClientAuthType.CLIENT_AUTH_NONE);
if ((handshaker != null) &&
(handshaker instanceof ServerHandshaker) &&
@@ -2322,7 +2237,7 @@
@Override
synchronized public boolean getWantClientAuth() {
- return (doClientAuth == SSLEngineImpl.clauth_requested);
+ return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED);
}
@@ -2339,13 +2254,22 @@
case cs_START:
/*
* If we need to change the socket mode and the enabled
- * protocols haven't specifically been set by the user,
- * change them to the corresponding default ones.
+ * protocols and cipher suites haven't specifically been
+ * set by the user, change them to the corresponding
+ * default ones.
*/
- if (roleIsServer != (!flag) &&
- sslContext.isDefaultProtocolList(enabledProtocols)) {
- enabledProtocols = sslContext.getDefaultProtocolList(!flag);
+ if (roleIsServer != (!flag)) {
+ if (sslContext.isDefaultProtocolList(enabledProtocols)) {
+ enabledProtocols =
+ sslContext.getDefaultProtocolList(!flag);
+ }
+
+ if (sslContext.isDefaultCipherSuiteList(enabledCipherSuites)) {
+ enabledCipherSuites =
+ sslContext.getDefaultCipherSuiteList(!flag);
+ }
}
+
roleIsServer = !flag;
break;
@@ -2361,13 +2285,23 @@
if (!handshaker.activated()) {
/*
* If we need to change the socket mode and the enabled
- * protocols haven't specifically been set by the user,
- * change them to the corresponding default ones.
+ * protocols and cipher suites haven't specifically been
+ * set by the user, change them to the corresponding
+ * default ones.
*/
- if (roleIsServer != (!flag) &&
- sslContext.isDefaultProtocolList(enabledProtocols)) {
- enabledProtocols = sslContext.getDefaultProtocolList(!flag);
+ if (roleIsServer != (!flag)) {
+ if (sslContext.isDefaultProtocolList(enabledProtocols)) {
+ enabledProtocols =
+ sslContext.getDefaultProtocolList(!flag);
+ }
+
+ if (sslContext.isDefaultCipherSuiteList(
+ enabledCipherSuites)) {
+ enabledCipherSuites =
+ sslContext.getDefaultCipherSuiteList(!flag);
+ }
}
+
roleIsServer = !flag;
connectionState = cs_START;
initHandshaker();
@@ -2535,6 +2469,9 @@
params.setSNIMatchers(sniMatchers);
params.setServerNames(serverNames);
params.setUseCipherSuitesOrder(preferLocalCipherSuites);
+ params.setMaximumPacketSize(maximumPacketSize);
+
+ // DTLS handshake retransmissions parameter does not apply here.
return params;
}
@@ -2550,6 +2487,16 @@
identificationProtocol = params.getEndpointIdentificationAlgorithm();
algorithmConstraints = params.getAlgorithmConstraints();
preferLocalCipherSuites = params.getUseCipherSuitesOrder();
+ maximumPacketSize = params.getMaximumPacketSize();
+
+ // DTLS handshake retransmissions parameter does not apply here.
+
+ if (maximumPacketSize != 0) {
+ outputRecord.changePacketSize(maximumPacketSize);
+ } else {
+ // use the implicit maximum packet size.
+ maximumPacketSize = outputRecord.getMaxPacketSize();
+ }
List<SNIServerName> sniNames = params.getServerNames();
if (sniNames != null) {
@@ -2564,6 +2511,7 @@
if ((handshaker != null) && !handshaker.started()) {
handshaker.setIdentificationProtocol(identificationProtocol);
handshaker.setAlgorithmConstraints(algorithmConstraints);
+ handshaker.setMaximumPacketSize(maximumPacketSize);
if (roleIsServer) {
handshaker.setSNIMatchers(sniMatchers);
handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
@@ -2612,14 +2560,6 @@
}
/**
- * Returns a boolean indicating whether the ChangeCipherSpec message
- * has been received for this handshake.
- */
- boolean receivedChangeCipherSpec() {
- return receivedCCS;
- }
-
- /**
* Returns a printable representation of this end of the connection.
*/
@Override
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+
+import javax.crypto.BadPaddingException;
+
+import javax.net.ssl.*;
+
+import sun.misc.HexDumpEncoder;
+
+
+/**
+ * {@code InputRecord} implementation for {@code SSLSocket}.
+ *
+ * @author David Brownell
+ */
+final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
+ private OutputStream deliverStream = null;
+ private byte[] temporary = new byte[1024];
+
+ // used by handshake hash computation for handshake fragment
+ private byte prevType = -1;
+ private int hsMsgOff = 0;
+ private int hsMsgLen = 0;
+
+ private boolean formatVerified = false; // SSLv2 ruled out?
+
+ private boolean hasHeader = false; // Had read the record header
+
+ SSLSocketInputRecord() {
+ this.readAuthenticator = MAC.TLS_NULL;
+ }
+
+ @Override
+ int bytesInCompletePacket(InputStream is) throws IOException {
+
+ if (!hasHeader) {
+ // read exactly one record
+ int really = read(is, temporary, 0, headerSize);
+ if (really < 0) {
+ throw new EOFException("SSL peer shut down incorrectly");
+ }
+ hasHeader = true;
+ }
+
+ byte byteZero = temporary[0];
+ int len = 0;
+
+ /*
+ * If we have already verified previous packets, we can
+ * ignore the verifications steps, and jump right to the
+ * determination. Otherwise, try one last hueristic to
+ * see if it's SSL/TLS.
+ */
+ if (formatVerified ||
+ (byteZero == ct_handshake) || (byteZero == ct_alert)) {
+ /*
+ * Last sanity check that it's not a wild record
+ */
+ ProtocolVersion recordVersion =
+ ProtocolVersion.valueOf(temporary[1], temporary[2]);
+
+ // check the record version
+ checkRecordVersion(recordVersion, false);
+
+ /*
+ * Reasonably sure this is a V3, disable further checks.
+ * We can't do the same in the v2 check below, because
+ * read still needs to parse/handle the v2 clientHello.
+ */
+ formatVerified = true;
+
+ /*
+ * One of the SSLv3/TLS message types.
+ */
+ len = ((temporary[3] & 0xFF) << 8) +
+ (temporary[4] & 0xFF) + headerSize;
+ } else {
+ /*
+ * Must be SSLv2 or something unknown.
+ * Check if it's short (2 bytes) or
+ * long (3) header.
+ *
+ * Internals can warn about unsupported SSLv2
+ */
+ boolean isShort = ((byteZero & 0x80) != 0);
+
+ if (isShort && ((temporary[2] == 1) || (temporary[2] == 4))) {
+ ProtocolVersion recordVersion =
+ ProtocolVersion.valueOf(temporary[3], temporary[4]);
+
+ // check the record version
+ checkRecordVersion(recordVersion, true);
+
+ /*
+ * Client or Server Hello
+ */
+ //
+ // Short header is using here. We reverse the code here
+ // in case it it used in the future.
+ //
+ // int mask = (isShort ? 0x7F : 0x3F);
+ // len = ((byteZero & mask) << 8) +
+ // (temporary[1] & 0xFF) + (isShort ? 2 : 3);
+ //
+ len = ((byteZero & 0x7F) << 8) + (temporary[1] & 0xFF) + 2;
+ } else {
+ // Gobblygook!
+ throw new SSLException(
+ "Unrecognized SSL message, plaintext connection?");
+ }
+ }
+
+ return len;
+ }
+
+ // destination.position() is zero.
+ @Override
+ Plaintext decode(InputStream is, ByteBuffer destination)
+ throws IOException, BadPaddingException {
+
+ if (isClosed) {
+ return null;
+ }
+
+ if (!hasHeader) {
+ // read exactly one record
+ int really = read(is, temporary, 0, headerSize);
+ if (really < 0) {
+ throw new EOFException("SSL peer shut down incorrectly");
+ }
+ hasHeader = true;
+ }
+
+ Plaintext plaintext = null;
+ if (!formatVerified) {
+ formatVerified = true;
+
+ /*
+ * The first record must either be a handshake record or an
+ * alert message. If it's not, it is either invalid or an
+ * SSLv2 message.
+ */
+ if ((temporary[0] != ct_handshake) &&
+ (temporary[0] != ct_alert)) {
+
+ plaintext = handleUnknownRecord(is, temporary, destination);
+ }
+ }
+
+ if (plaintext == null) {
+ plaintext = decodeInputRecord(is, temporary, destination);
+ }
+
+ // The record header should has comsumed.
+ hasHeader = false;
+
+ return plaintext;
+ }
+
+ @Override
+ void setDeliverStream(OutputStream outputStream) {
+ this.deliverStream = outputStream;
+ }
+
+ // Note that destination may be null
+ private Plaintext decodeInputRecord(InputStream is, byte[] header,
+ ByteBuffer destination) throws IOException, BadPaddingException {
+
+ byte contentType = header[0];
+ byte majorVersion = header[1];
+ byte minorVersion = header[2];
+ int contentLen = ((header[3] & 0xFF) << 8) + (header[4] & 0xFF);
+
+ //
+ // Check for upper bound.
+ //
+ // Note: May check packetSize limit in the future.
+ if (contentLen < 0 || contentLen > maxLargeRecordSize - headerSize) {
+ throw new SSLProtocolException(
+ "Bad input record size, TLSCiphertext.length = " + contentLen);
+ }
+
+ //
+ // Read a complete record.
+ //
+ if (destination == null) {
+ destination = ByteBuffer.allocate(headerSize + contentLen);
+ } // Otherwise, the destination buffer should have enough room.
+
+ int dstPos = destination.position();
+ destination.put(temporary, 0, headerSize);
+ while (contentLen > 0) {
+ int howmuch = Math.min(temporary.length, contentLen);
+ int really = read(is, temporary, 0, howmuch);
+ if (really < 0) {
+ throw new EOFException("SSL peer shut down incorrectly");
+ }
+
+ destination.put(temporary, 0, howmuch);
+ contentLen -= howmuch;
+ }
+ destination.flip();
+ destination.position(dstPos + headerSize);
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", READ: " +
+ ProtocolVersion.valueOf(majorVersion, minorVersion) +
+ " " + Record.contentName(contentType) + ", length = " +
+ destination.remaining());
+ }
+
+ //
+ // Decrypt the fragment
+ //
+ ByteBuffer plaintext =
+ decrypt(readAuthenticator, readCipher, contentType, destination);
+
+ if ((contentType != ct_handshake) && (hsMsgOff != hsMsgLen)) {
+ throw new SSLProtocolException(
+ "Expected to get a handshake fragment");
+ }
+
+ //
+ // handshake hashing
+ //
+ if (contentType == ct_handshake) {
+ int pltPos = plaintext.position();
+ int pltLim = plaintext.limit();
+ int frgPos = pltPos;
+ for (int remains = plaintext.remaining(); remains > 0;) {
+ int howmuch;
+ byte handshakeType;
+ if (hsMsgOff < hsMsgLen) {
+ // a fragment of the handshake message
+ howmuch = Math.min((hsMsgLen - hsMsgOff), remains);
+ handshakeType = prevType;
+
+ hsMsgOff += howmuch;
+ if (hsMsgOff == hsMsgLen) {
+ // Now is a complete handshake message.
+ hsMsgOff = 0;
+ hsMsgLen = 0;
+ }
+ } else { // hsMsgOff == hsMsgLen, a new handshake message
+ handshakeType = plaintext.get();
+ int handshakeLen = ((plaintext.get() & 0xFF) << 16) |
+ ((plaintext.get() & 0xFF) << 8) |
+ (plaintext.get() & 0xFF);
+ plaintext.position(frgPos);
+ if (remains < (handshakeLen + 1)) { // 1: handshake type
+ // This handshake message is fragmented.
+ prevType = handshakeType;
+ hsMsgOff = remains - 4; // 4: handshake header
+ hsMsgLen = handshakeLen;
+ }
+
+ howmuch = Math.min(handshakeLen + 4, remains);
+ }
+
+ plaintext.limit(frgPos + howmuch);
+
+ if (handshakeType == HandshakeMessage.ht_hello_request) {
+ // omitted from handshake hash computation
+ } else if ((handshakeType != HandshakeMessage.ht_finished) &&
+ (handshakeType != HandshakeMessage.ht_certificate_verify)) {
+
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.update(plaintext);
+ } else {
+ // Reserve until this handshake message has been processed.
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.reserve(plaintext);
+ }
+
+ plaintext.position(frgPos + howmuch);
+ plaintext.limit(pltLim);
+
+ frgPos += howmuch;
+ remains -= howmuch;
+ }
+ plaintext.position(pltPos);
+ }
+
+ return new Plaintext(contentType,
+ majorVersion, minorVersion, -1, -1L, plaintext);
+ // recordEpoch, recordSeq, plaintext);
+ }
+
+ private Plaintext handleUnknownRecord(InputStream is, byte[] header,
+ ByteBuffer destination) throws IOException, BadPaddingException {
+
+ byte firstByte = header[0];
+ byte thirdByte = header[2];
+
+ // Does it look like a Version 2 client hello (V2ClientHello)?
+ if (((firstByte & 0x80) != 0) && (thirdByte == 1)) {
+ /*
+ * If SSLv2Hello is not enabled, throw an exception.
+ */
+ if (helloVersion != ProtocolVersion.SSL20Hello) {
+ throw new SSLHandshakeException("SSLv2Hello is not enabled");
+ }
+
+ byte majorVersion = header[3];
+ byte minorVersion = header[4];
+
+ if ((majorVersion == ProtocolVersion.SSL20Hello.major) &&
+ (minorVersion == ProtocolVersion.SSL20Hello.minor)) {
+
+ /*
+ * Looks like a V2 client hello, but not one saying
+ * "let's talk SSLv3". So we need to send an SSLv2
+ * error message, one that's treated as fatal by
+ * clients (Otherwise we'll hang.)
+ */
+ deliverStream.write(SSLRecord.v2NoCipher); // SSLv2Hello
+
+ if (debug != null) {
+ if (Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ "Requested to negotiate unsupported SSLv2!");
+ }
+
+ if (Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " +
+ SSLRecord.v2NoCipher.length,
+ SSLRecord.v2NoCipher);
+ }
+ }
+
+ throw new SSLException("Unsupported SSL v2.0 ClientHello");
+ }
+
+ int msgLen = ((header[0] & 0x7F) << 8) | (header[1] & 0xFF);
+
+ if (destination == null) {
+ destination = ByteBuffer.allocate(headerSize + msgLen);
+ }
+ destination.put(temporary, 0, headerSize);
+ msgLen -= 3; // had read 3 bytes of content as header
+ while (msgLen > 0) {
+ int howmuch = Math.min(temporary.length, msgLen);
+ int really = read(is, temporary, 0, howmuch);
+ if (really < 0) {
+ throw new EOFException("SSL peer shut down incorrectly");
+ }
+
+ destination.put(temporary, 0, howmuch);
+ msgLen -= howmuch;
+ }
+ destination.flip();
+
+ /*
+ * If we can map this into a V3 ClientHello, read and
+ * hash the rest of the V2 handshake, turn it into a
+ * V3 ClientHello message, and pass it up.
+ */
+ destination.position(2); // exclude the header
+
+ if (handshakeHash == null) {
+ // used for cache only
+ handshakeHash = new HandshakeHash(false);
+ }
+ handshakeHash.update(destination);
+ destination.position(0);
+
+ ByteBuffer converted = convertToClientHello(destination);
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Converted] ClientHello", converted);
+ }
+
+ return new Plaintext(ct_handshake,
+ majorVersion, minorVersion, -1, -1L, converted);
+ } else {
+ if (((firstByte & 0x80) != 0) && (thirdByte == 4)) {
+ throw new SSLException("SSL V2.0 servers are not supported.");
+ }
+
+ throw new SSLException("Unsupported or unrecognized SSL message");
+ }
+ }
+
+ // Read the exact bytes of data, otherwise, return -1.
+ private static int read(InputStream is,
+ byte[] buffer, int offset, int len) throws IOException {
+ int n = 0;
+ while (n < len) {
+ int readLen = is.read(buffer, offset + n, len - n);
+ if (readLen < 0) {
+ return -1;
+ }
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw read]: length = " + readLen,
+ buffer, offset + n, readLen);
+ }
+
+ n += readLen;
+ }
+
+ return n;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+import java.nio.*;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import sun.misc.HexDumpEncoder;
+
+
+/**
+ * {@code OutputRecord} implementation for {@code SSLSocket}.
+ */
+final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord {
+ private OutputStream deliverStream = null;
+
+ SSLSocketOutputRecord() {
+ this.writeAuthenticator = MAC.TLS_NULL;
+
+ this.packetSize = SSLRecord.maxRecordSize;
+ this.protocolVersion = ProtocolVersion.DEFAULT_TLS;
+ }
+
+ @Override
+ void encodeAlert(byte level, byte description) throws IOException {
+ // use the buf of ByteArrayOutputStream
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+ count = position;
+
+ write(level);
+ write(description);
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion +
+ " " + Record.contentName(Record.ct_alert) +
+ ", length = " + (count - headerSize));
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ encrypt(writeAuthenticator, writeCipher,
+ Record.ct_alert, headerSize);
+
+ // deliver this message
+ deliverStream.write(buf, 0, count); // may throw IOException
+ deliverStream.flush(); // may throw IOException
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, buf, 0, count);
+ }
+
+ // reset the internal buffer
+ count = 0;
+ }
+
+ @Override
+ void encodeHandshake(byte[] source,
+ int offset, int length) throws IOException {
+
+ if (firstMessage) {
+ firstMessage = false;
+
+ if ((helloVersion == ProtocolVersion.SSL20Hello) &&
+ (source[offset] == HandshakeMessage.ht_client_hello) &&
+ // 5: recode header size
+ (source[offset + 4 + 2 + 32] == 0)) {
+ // V3 session ID is empty
+ // 4: handshake header size
+ // 2: client_version in ClientHello
+ // 32: random in ClientHello
+
+ ByteBuffer v2ClientHello = encodeV2ClientHello(
+ source, (offset + 4), (length - 4));
+
+ byte[] record = v2ClientHello.array(); // array offset is zero
+ int limit = v2ClientHello.limit();
+ handshakeHash.update(record, 2, (limit - 2));
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: SSLv2 ClientHello message" +
+ ", length = " + limit);
+ }
+
+ // deliver this message
+ //
+ // Version 2 ClientHello message should be plaintext.
+ //
+ // No max fragment length negotiation.
+ deliverStream.write(record, 0, limit);
+ deliverStream.flush();
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, record, 0, limit);
+ }
+
+ return;
+ }
+ }
+
+ byte handshakeType = source[0];
+ if (handshakeType != HandshakeMessage.ht_hello_request) {
+ handshakeHash.update(source, offset, length);
+ }
+
+ int fragLimit = getFragLimit();
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+ if (count == 0) {
+ count = position;
+ }
+
+ if ((count - position) < (fragLimit - length)) {
+ write(source, offset, length);
+ return;
+ }
+
+ for (int limit = (offset + length); offset < limit;) {
+
+ int remains = (limit - offset) + (count - position);
+ int fragLen = Math.min(fragLimit, remains);
+
+ // use the buf of ByteArrayOutputStream
+ write(source, offset, fragLen);
+ if (remains < fragLimit) {
+ return;
+ }
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion +
+ " " + Record.contentName(Record.ct_handshake) +
+ ", length = " + (count - headerSize));
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ encrypt(writeAuthenticator, writeCipher,
+ Record.ct_handshake, headerSize);
+
+ // deliver this message
+ deliverStream.write(buf, 0, count); // may throw IOException
+ deliverStream.flush(); // may throw IOException
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, buf, 0, count);
+ }
+
+ // reset the offset
+ offset += fragLen;
+
+ // reset the internal buffer
+ count = position;
+ }
+ }
+
+ @Override
+ void encodeChangeCipherSpec() throws IOException {
+
+ // use the buf of ByteArrayOutputStream
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+ count = position;
+
+ write((byte)1); // byte 1: change_cipher_spec(
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion +
+ " " + Record.contentName(Record.ct_change_cipher_spec) +
+ ", length = " + (count - headerSize));
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ encrypt(writeAuthenticator, writeCipher,
+ Record.ct_change_cipher_spec, headerSize);
+
+ // deliver this message
+ deliverStream.write(buf, 0, count); // may throw IOException
+ // deliverStream.flush(); // flush in Finished
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, buf, 0, count);
+ }
+
+ // reset the internal buffer
+ count = 0;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+ if (count <= position) {
+ return;
+ }
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion +
+ " " + Record.contentName(Record.ct_handshake) +
+ ", length = " + (count - headerSize));
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ encrypt(writeAuthenticator, writeCipher,
+ Record.ct_handshake, headerSize);
+
+ // deliver this message
+ deliverStream.write(buf, 0, count); // may throw IOException
+ deliverStream.flush(); // may throw IOException
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, buf, 0, count);
+ }
+
+ // reset the internal buffer
+ count = 0; // DON'T use position
+ }
+
+ @Override
+ void deliver(byte[] source, int offset, int length) throws IOException {
+
+ if (writeAuthenticator.seqNumOverflow()) {
+ if (debug != null && Debug.isOn("ssl")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", sequence number extremely close to overflow " +
+ "(2^64-1 packets). Closing connection.");
+ }
+
+ throw new SSLHandshakeException("sequence number overflow");
+ }
+
+ boolean isFirstRecordOfThePayload = true;
+ for (int limit = (offset + length); offset < limit;) {
+ int macLen = 0;
+ if (writeAuthenticator instanceof MAC) {
+ macLen = ((MAC)writeAuthenticator).MAClen();
+ }
+
+ int fragLen;
+ if (packetSize > 0) {
+ fragLen = Math.min(maxRecordSize, packetSize);
+ fragLen = writeCipher.calculateFragmentSize(
+ fragLen, macLen, headerSize);
+
+ fragLen = Math.min(fragLen, Record.maxDataSize);
+ } else {
+ fragLen = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLen = Math.min(fragLen, fragmentSize);
+ }
+
+ if (isFirstRecordOfThePayload && needToSplitPayload()) {
+ fragLen = 1;
+ isFirstRecordOfThePayload = false;
+ } else {
+ fragLen = Math.min(fragLen, (limit - offset));
+ }
+
+ // use the buf of ByteArrayOutputStream
+ int position = headerSize + writeCipher.getExplicitNonceSize();
+ count = position;
+ write(source, offset, fragLen);
+
+ if (debug != null && Debug.isOn("record")) {
+ System.out.println(Thread.currentThread().getName() +
+ ", WRITE: " + protocolVersion +
+ " " + Record.contentName(Record.ct_application_data) +
+ ", length = " + (count - headerSize));
+ }
+
+ // Encrypt the fragment and wrap up a record.
+ encrypt(writeAuthenticator, writeCipher,
+ Record.ct_application_data, headerSize);
+
+ // deliver this message
+ deliverStream.write(buf, 0, count); // may throw IOException
+ deliverStream.flush(); // may throw IOException
+
+ if (debug != null && Debug.isOn("packet")) {
+ Debug.printHex(
+ "[Raw write]: length = " + count, buf, 0, count);
+ }
+
+ // reset the internal buffer
+ count = 0;
+
+ if (isFirstAppOutputRecord) {
+ isFirstAppOutputRecord = false;
+ }
+
+ offset += fragLen;
+ }
+ }
+
+ @Override
+ void setDeliverStream(OutputStream outputStream) {
+ this.deliverStream = outputStream;
+ }
+
+ /*
+ * Need to split the payload except the following cases:
+ *
+ * 1. protocol version is TLS 1.1 or later;
+ * 2. bulk cipher does not use CBC mode, including null bulk cipher suites.
+ * 3. the payload is the first application record of a freshly
+ * negotiated TLS session.
+ * 4. the CBC protection is disabled;
+ *
+ * By default, we counter chosen plaintext issues on CBC mode
+ * ciphersuites in SSLv3/TLS1.0 by sending one byte of application
+ * data in the first record of every payload, and the rest in
+ * subsequent record(s). Note that the issues have been solved in
+ * TLS 1.1 or later.
+ *
+ * It is not necessary to split the very first application record of
+ * a freshly negotiated TLS session, as there is no previous
+ * application data to guess. To improve compatibility, we will not
+ * split such records.
+ *
+ * This avoids issues in the outbound direction. For a full fix,
+ * the peer must have similar protections.
+ */
+ boolean needToSplitPayload() {
+ return (!protocolVersion.useTLS11PlusSpec()) &&
+ writeCipher.isCBCMode() && !isFirstAppOutputRecord &&
+ Record.enableCBCProtection;
+ }
+
+ private int getFragLimit() {
+ int macLen = 0;
+ if (writeAuthenticator instanceof MAC) {
+ macLen = ((MAC)writeAuthenticator).MAClen();
+ }
+
+ int fragLimit;
+ if (packetSize > 0) {
+ fragLimit = Math.min(maxRecordSize, packetSize);
+ fragLimit = writeCipher.calculateFragmentSize(
+ fragLimit, macLen, headerSize);
+
+ fragLimit = Math.min(fragLimit, Record.maxDataSize);
+ } else {
+ fragLimit = Record.maxDataSize;
+ }
+
+ if (fragmentSize > 0) {
+ fragLimit = Math.min(fragLimit, fragmentSize);
+ }
+
+ return fragLimit;
+ }
+}
--- a/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java Tue Jun 02 09:15:47 2015 -0700
@@ -38,8 +38,6 @@
import javax.net.ssl.*;
-import javax.security.auth.Subject;
-
import sun.security.util.KeyUtil;
import sun.security.action.GetPropertyAction;
import sun.security.ssl.HandshakeMessage.*;
@@ -58,7 +56,7 @@
final class ServerHandshaker extends Handshaker {
// is the server going to require the client to authenticate?
- private byte doClientAuth;
+ private ClientAuthType doClientAuth;
// our authentication info
private X509Certificate[] certs;
@@ -143,13 +141,13 @@
* Constructor ... use the keys found in the auth context.
*/
ServerHandshaker(SSLSocketImpl socket, SSLContextImpl context,
- ProtocolList enabledProtocols, byte clientAuth,
+ ProtocolList enabledProtocols, ClientAuthType clientAuth,
ProtocolVersion activeProtocolVersion, boolean isInitialHandshake,
boolean secureRenegotiation,
byte[] clientVerifyData, byte[] serverVerifyData) {
super(socket, context, enabledProtocols,
- (clientAuth != SSLEngineImpl.clauth_none), false,
+ (clientAuth != ClientAuthType.CLIENT_AUTH_NONE), false,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
clientVerifyData, serverVerifyData);
doClientAuth = clientAuth;
@@ -159,15 +157,16 @@
* Constructor ... use the keys found in the auth context.
*/
ServerHandshaker(SSLEngineImpl engine, SSLContextImpl context,
- ProtocolList enabledProtocols, byte clientAuth,
+ ProtocolList enabledProtocols, ClientAuthType clientAuth,
ProtocolVersion activeProtocolVersion,
boolean isInitialHandshake, boolean secureRenegotiation,
- byte[] clientVerifyData, byte[] serverVerifyData) {
+ byte[] clientVerifyData, byte[] serverVerifyData,
+ boolean isDTLS) {
super(engine, context, enabledProtocols,
- (clientAuth != SSLEngineImpl.clauth_none), false,
+ (clientAuth != ClientAuthType.CLIENT_AUTH_NONE), false,
activeProtocolVersion, isInitialHandshake, secureRenegotiation,
- clientVerifyData, serverVerifyData);
+ clientVerifyData, serverVerifyData, isDTLS);
doClientAuth = clientAuth;
}
@@ -176,7 +175,7 @@
* whether client authentication is required. Otherwise,
* we will need to wait for the next handshake.
*/
- void setClientAuth(byte clientAuth) {
+ void setClientAuth(ClientAuthType clientAuth) {
doClientAuth = clientAuth;
}
@@ -192,21 +191,15 @@
@Override
void processMessage(byte type, int message_len)
throws IOException {
- //
- // In SSLv3 and TLS, messages follow strictly increasing
- // numerical order _except_ for one annoying special case.
- //
- if ((state >= type)
- && (state != HandshakeMessage.ht_client_key_exchange
- && type != HandshakeMessage.ht_certificate_verify)) {
- throw new SSLProtocolException(
- "Handshake message sequence violation, state = " + state
- + ", type = " + type);
- }
+
+ // check the handshake state
+ handshakeState.check(type);
switch (type) {
case HandshakeMessage.ht_client_hello:
- ClientHello ch = new ClientHello(input, message_len);
+ ClientHello ch = new ClientHello(input, message_len, isDTLS);
+ handshakeState.update(ch, resumingSession);
+
/*
* send it off for processing.
*/
@@ -214,12 +207,14 @@
break;
case HandshakeMessage.ht_certificate:
- if (doClientAuth == SSLEngineImpl.clauth_none) {
+ if (doClientAuth == ClientAuthType.CLIENT_AUTH_NONE) {
fatalSE(Alerts.alert_unexpected_message,
"client sent unsolicited cert chain");
// NOTREACHED
}
- this.clientCertificate(new CertificateMsg(input));
+ CertificateMsg certificateMsg = new CertificateMsg(input);
+ handshakeState.update(certificateMsg, resumingSession);
+ this.clientCertificate(certificateMsg);
break;
case HandshakeMessage.ht_client_key_exchange:
@@ -237,18 +232,9 @@
protocolVersion, clientRequestedVersion,
sslContext.getSecureRandom(), input,
message_len, privateKey);
+ handshakeState.update(pms, resumingSession);
preMasterSecret = this.clientKeyExchange(pms);
break;
- case K_KRB5:
- case K_KRB5_EXPORT:
- preMasterSecret = this.clientKeyExchange(
- new KerberosClientKeyExchange(protocolVersion,
- clientRequestedVersion,
- sslContext.getSecureRandom(),
- input,
- this.getAccSE(),
- serviceCreds));
- break;
case K_DHE_RSA:
case K_DHE_DSS:
case K_DH_ANON:
@@ -258,20 +244,39 @@
* protocol difference in these five flavors is in how
* the ServerKeyExchange message was constructed!
*/
- preMasterSecret = this.clientKeyExchange(
- new DHClientKeyExchange(input));
+ DHClientKeyExchange dhcke = new DHClientKeyExchange(input);
+ handshakeState.update(dhcke, resumingSession);
+ preMasterSecret = this.clientKeyExchange(dhcke);
break;
case K_ECDH_RSA:
case K_ECDH_ECDSA:
case K_ECDHE_RSA:
case K_ECDHE_ECDSA:
case K_ECDH_ANON:
- preMasterSecret = this.clientKeyExchange
- (new ECDHClientKeyExchange(input));
+ ECDHClientKeyExchange ecdhcke =
+ new ECDHClientKeyExchange(input);
+ handshakeState.update(ecdhcke, resumingSession);
+ preMasterSecret = this.clientKeyExchange(ecdhcke);
break;
default:
- throw new SSLProtocolException
- ("Unrecognized key exchange: " + keyExchange);
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(keyExchange.name);
+ if (p == null) {
+ throw new SSLProtocolException
+ ("Unrecognized key exchange: " + keyExchange);
+ }
+ byte[] encodedTicket = input.getBytes16();
+ input.getBytes16();
+ byte[] secret = input.getBytes16();
+ ClientKeyExchange cke = p.createServerExchange(protocolVersion,
+ clientRequestedVersion,
+ sslContext.getSecureRandom(),
+ encodedTicket,
+ secret,
+ this.getAccSE(), serviceCreds);
+ handshakeState.update(cke, resumingSession);
+ preMasterSecret = this.clientKeyExchange(cke);
+ break;
}
//
@@ -282,20 +287,20 @@
break;
case HandshakeMessage.ht_certificate_verify:
- this.clientCertificateVerify(new CertificateVerify(input,
- localSupportedSignAlgs, protocolVersion));
+ CertificateVerify cvm =
+ new CertificateVerify(input,
+ localSupportedSignAlgs, protocolVersion);
+ handshakeState.update(cvm, resumingSession);
+ this.clientCertificateVerify(cvm);
+
break;
case HandshakeMessage.ht_finished:
- // A ChangeCipherSpec record must have been received prior to
- // reception of the Finished message (RFC 5246, 7.4.9).
- if (!receivedChangeCipherSpec()) {
- fatalSE(Alerts.alert_handshake_failure,
- "Received Finished message before ChangeCipherSpec");
- }
+ Finished cfm =
+ new Finished(protocolVersion, input, cipherSuite);
+ handshakeState.update(cfm, resumingSession);
+ this.clientFinished(cfm);
- this.clientFinished(
- new Finished(protocolVersion, input, cipherSuite));
break;
default:
@@ -303,17 +308,6 @@
"Illegal server handshake msg, " + type);
}
- //
- // Move state machine forward if the message handling
- // code didn't already do so
- //
- if (state < type) {
- if(type == HandshakeMessage.ht_certificate_verify) {
- state = type + 2; // an annoying special case
- } else {
- state = type;
- }
- }
}
@@ -344,7 +338,7 @@
//
// This will not have any impact on server initiated renegotiation.
if (rejectClientInitiatedRenego && !isInitialHandshake &&
- state != HandshakeMessage.ht_hello_request) {
+ !serverHelloRequested) {
fatalSE(Alerts.alert_handshake_failure,
"Client initiated renegotiation is not allowed");
}
@@ -438,7 +432,7 @@
}
} else if (!allowUnsafeRenegotiation) {
// abort the handshake
- if (activeProtocolVersion.v >= ProtocolVersion.TLS10.v) {
+ if (activeProtocolVersion.useTLS10PlusSpec()) {
// respond with a no_renegotiation warning
warningSE(Alerts.alert_no_renegotiation);
@@ -480,11 +474,52 @@
}
}
- /*
- * Always make sure this entire record has been digested before we
- * start emitting output, to ensure correct digesting order.
- */
- input.digestNow();
+ // check the "max_fragment_length" extension
+ MaxFragmentLengthExtension maxFragLenExt = (MaxFragmentLengthExtension)
+ mesg.extensions.get(ExtensionType.EXT_MAX_FRAGMENT_LENGTH);
+ if ((maxFragLenExt != null) && (maximumPacketSize != 0)) {
+ // Not yet consider the impact of IV/MAC/padding.
+ int estimatedMaxFragSize = maximumPacketSize;
+ if (isDTLS) {
+ estimatedMaxFragSize -= DTLSRecord.headerSize;
+ } else {
+ estimatedMaxFragSize -= SSLRecord.headerSize;
+ }
+
+ if (maxFragLenExt.getMaxFragLen() > estimatedMaxFragSize) {
+ // For better interoperability, abort the maximum fragment
+ // length negotiation, rather than terminate the connection
+ // with a fatal alert.
+ maxFragLenExt = null;
+
+ // fatalSE(Alerts.alert_illegal_parameter,
+ // "Not an allowed max_fragment_length value");
+ }
+ }
+
+ // cookie exchange
+ if (isDTLS) {
+ HelloCookieManager hcMgr = sslContext.getHelloCookieManager();
+ if ((mesg.cookie == null) || (mesg.cookie.length == 0) ||
+ (!hcMgr.isValid(mesg))) {
+
+ //
+ // Perform cookie exchange for DTLS handshaking if no cookie
+ // or the cookie is invalid in the ClientHello message.
+ //
+ HelloVerifyRequest m0 = new HelloVerifyRequest(hcMgr, mesg);
+
+ if (debug != null && Debug.isOn("handshake")) {
+ m0.print(System.out);
+ }
+
+ m0.write(output);
+ handshakeState.update(m0, resumingSession);
+ output.flush();
+
+ return;
+ }
+ }
/*
* FIRST, construct the ServerHello using the options and priorities
@@ -580,7 +615,7 @@
}
if (resumingSession &&
- (doClientAuth == SSLEngineImpl.clauth_required)) {
+ (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED)) {
try {
previous.getPeerPrincipal();
} catch (SSLPeerUnverifiedException e) {
@@ -591,47 +626,21 @@
// validate subject identity
if (resumingSession) {
CipherSuite suite = previous.getSuite();
- if (suite.keyExchange == K_KRB5 ||
- suite.keyExchange == K_KRB5_EXPORT) {
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(suite.keyExchange.name);
+ if (p != null) {
Principal localPrincipal = previous.getLocalPrincipal();
- Subject subject = null;
- try {
- subject = AccessController.doPrivileged(
- new PrivilegedExceptionAction<Subject>() {
- @Override
- public Subject run() throws Exception {
- return
- Krb5Helper.getServerSubject(getAccSE());
- }});
- } catch (PrivilegedActionException e) {
- subject = null;
- if (debug != null && Debug.isOn("session")) {
- System.out.println("Attempt to obtain" +
- " subject failed!");
- }
- }
-
- if (subject != null) {
- // Eliminate dependency on KerberosPrincipal
- if (Krb5Helper.isRelated(subject, localPrincipal)) {
- if (debug != null && Debug.isOn("session"))
- System.out.println("Subject can" +
- " provide creds for princ");
- } else {
- resumingSession = false;
- if (debug != null && Debug.isOn("session"))
- System.out.println("Subject cannot" +
- " provide creds for princ");
- }
+ if (p.isRelated(
+ false, getAccSE(), localPrincipal)) {
+ if (debug != null && Debug.isOn("session"))
+ System.out.println("Subject can" +
+ " provide creds for princ");
} else {
resumingSession = false;
if (debug != null && Debug.isOn("session"))
- System.out.println("Kerberos credentials are" +
- " not present in the current Subject;" +
- " check if " +
- " javax.security.auth.useSubjectAsCreds" +
- " system property has been set to false");
+ System.out.println("Subject cannot" +
+ " provide creds for princ");
}
}
}
@@ -660,7 +669,7 @@
}
}
}
- } // else client did not try to resume
+ } // else client did not try to resume
//
// If client hasn't specified a session we can resume, start a
@@ -677,7 +686,7 @@
// We only need to handle the "signature_algorithm" extension
// for full handshakes and TLS 1.2 or later.
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
SignatureAlgorithmsExtension signAlgs =
(SignatureAlgorithmsExtension)mesg.extensions.get(
ExtensionType.EXT_SIGNATURE_ALGORITHMS);
@@ -708,7 +717,7 @@
sslContext.getSecureRandom(),
getHostAddressSE(), getPortSE());
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (peerSupportedSignAlgs != null) {
session.setPeerSupportedSignatureAlgorithms(
peerSupportedSignAlgs);
@@ -734,12 +743,41 @@
session.setLocalPrivateKey(privateKey);
// chooseCompression(mesg);
+
+ // set the negotiated maximum fragment in the session
+ //
+ // The protocol version and cipher suite have been negotiated
+ // in previous processes.
+ if (maxFragLenExt != null) {
+ int maxFragLen = maxFragLenExt.getMaxFragLen();
+
+ // More check of the requested "max_fragment_length" extension.
+ if (maximumPacketSize != 0) {
+ int estimatedMaxFragSize = cipherSuite.calculatePacketSize(
+ maxFragLen, protocolVersion, isDTLS);
+ if (estimatedMaxFragSize > maximumPacketSize) {
+ // For better interoperability, abort the maximum
+ // fragment length negotiation, rather than terminate
+ // the connection with a fatal alert.
+ maxFragLenExt = null;
+
+ // fatalSE(Alerts.alert_illegal_parameter,
+ // "Not an allowed max_fragment_length value");
+ }
+ }
+
+ if (maxFragLenExt != null) {
+ session.setNegotiatedMaxFragSize(maxFragLen);
+ }
+ }
+
+ session.setMaximumPacketSize(maximumPacketSize);
} else {
// set the handshake session
setHandshakeSessionSE(session);
}
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
handshakeHash.setFinishedAlg(cipherSuite.prfAlg.getPRFHashAlg());
}
@@ -771,11 +809,20 @@
}
}
+ if ((maxFragLenExt != null) && !resumingSession) {
+ // When resuming a session, the server MUST NOT include a
+ // max_fragment_length extension in the server hello.
+ //
+ // Otherwise, use the same value as the requested extension.
+ m1.extensions.add(maxFragLenExt);
+ }
+
if (debug != null && Debug.isOn("handshake")) {
m1.print(System.out);
System.out.println("Cipher suite: " + session.getSuite());
}
m1.write(output);
+ handshakeState.update(m1, resumingSession);
//
// If we are resuming a session, we finish writing handshake
@@ -784,6 +831,10 @@
if (resumingSession) {
calculateConnectionKeys(session.getMasterSecret());
sendChangeCipherAndFinish(false);
+
+ // expecting the final ChangeCipherSpec and Finished messages
+ expectingFinishFlightSE();
+
return;
}
@@ -796,9 +847,8 @@
* defined in the protocol spec are explicitly stated to require
* using RSA certificates.
*/
- if (keyExchange == K_KRB5 || keyExchange == K_KRB5_EXPORT) {
- // Server certificates are omitted for Kerberos ciphers
-
+ if (ClientKeyExchangeService.find(cipherSuite.keyExchange.name) != null) {
+ // No external key exchange provider needs a cert now.
} else if ((keyExchange != K_DH_ANON) && (keyExchange != K_ECDH_ANON)) {
if (certs == null) {
throw new RuntimeException("no certificates");
@@ -815,6 +865,7 @@
m2.print(System.out);
}
m2.write(output);
+ handshakeState.update(m2, resumingSession);
// XXX has some side effects with OS TCP buffering,
// leave it out for now
@@ -839,9 +890,7 @@
ServerKeyExchange m3;
switch (keyExchange) {
case K_RSA:
- case K_KRB5:
- case K_KRB5_EXPORT:
- // no server key exchange for RSA or KRB5 ciphersuites
+ // no server key exchange for RSA ciphersuites
m3 = null;
break;
case K_RSA_EXPORT:
@@ -853,9 +902,9 @@
sslContext.getSecureRandom());
privateKey = tempPrivateKey;
} catch (GeneralSecurityException e) {
- throwSSLException
- ("Error generating RSA server key exchange", e);
m3 = null; // make compiler happy
+ throw new SSLException(
+ "Error generating RSA server key exchange", e);
}
} else {
// RSA_EXPORT with short key, don't need ServerKeyExchange
@@ -873,8 +922,9 @@
preferableSignatureAlgorithm,
protocolVersion);
} catch (GeneralSecurityException e) {
- throwSSLException("Error generating DH server key exchange", e);
m3 = null; // make compiler happy
+ throw new SSLException(
+ "Error generating DH server key exchange", e);
}
break;
case K_DH_ANON:
@@ -892,9 +942,9 @@
preferableSignatureAlgorithm,
protocolVersion);
} catch (GeneralSecurityException e) {
- throwSSLException(
- "Error generating ECDH server key exchange", e);
m3 = null; // make compiler happy
+ throw new SSLException(
+ "Error generating ECDH server key exchange", e);
}
break;
case K_ECDH_RSA:
@@ -903,6 +953,13 @@
m3 = null;
break;
default:
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(keyExchange.name);
+ if (p != null) {
+ // No external key exchange provider needs a cert now.
+ m3 = null;
+ break;
+ }
throw new RuntimeException("internal error: " + keyExchange);
}
if (m3 != null) {
@@ -910,6 +967,7 @@
m3.print(System.out);
}
m3.write(output);
+ handshakeState.update(m3, resumingSession);
}
//
@@ -922,16 +980,16 @@
// Needed only if server requires client to authenticate self.
// Illegal for anonymous flavors, so we need to check that.
//
- // CertificateRequest is omitted for Kerberos ciphers
- if (doClientAuth != SSLEngineImpl.clauth_none &&
+ // No external key exchange provider needs a cert now.
+ if (doClientAuth != ClientAuthType.CLIENT_AUTH_NONE &&
keyExchange != K_DH_ANON && keyExchange != K_ECDH_ANON &&
- keyExchange != K_KRB5 && keyExchange != K_KRB5_EXPORT) {
+ ClientKeyExchangeService.find(keyExchange.name) == null) {
CertificateRequest m4;
X509Certificate caCerts[];
Collection<SignatureAndHashAlgorithm> localSignAlgs = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
// We currently use all local upported signature and hash
// algorithms. However, to minimize the computation cost
// of requested hash algorithms, we may use a restricted
@@ -959,6 +1017,7 @@
m4.print(System.out);
}
m4.write(output);
+ handshakeState.update(m4, resumingSession);
}
/*
@@ -970,6 +1029,7 @@
m5.print(System.out);
}
m5.write(output);
+ handshakeState.update(m5, resumingSession);
/*
* Flush any buffered messages so the client will see them.
@@ -1000,7 +1060,7 @@
continue;
}
- if (doClientAuth == SSLEngineImpl.clauth_required) {
+ if (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED) {
if ((suite.keyExchange == K_DH_ANON) ||
(suite.keyExchange == K_ECDH_ANON)) {
continue;
@@ -1043,12 +1103,12 @@
}
// must not negotiate the obsoleted weak cipher suites.
- if (protocolVersion.v >= suite.obsoleted) {
+ if (protocolVersion.obsoletes(suite)) {
return false;
}
// must not negotiate unsupported cipher suites.
- if (protocolVersion.v < suite.supported) {
+ if (!protocolVersion.supports(suite)) {
return false;
}
@@ -1062,7 +1122,7 @@
tempPublicKey = null;
Collection<SignatureAndHashAlgorithm> supportedSignAlgs = null;
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (peerSupportedSignAlgs != null) {
supportedSignAlgs = peerSupportedSignAlgs;
} else {
@@ -1151,7 +1211,7 @@
}
// get preferable peer signature algorithm for server key exchange
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
supportedSignAlgs, "RSA", privateKey);
@@ -1169,7 +1229,7 @@
}
// get preferable peer signature algorithm for server key exchange
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
supportedSignAlgs, "RSA", privateKey);
@@ -1184,7 +1244,7 @@
break;
case K_DHE_DSS:
// get preferable peer signature algorithm for server key exchange
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
supportedSignAlgs, "DSA");
@@ -1202,7 +1262,7 @@
break;
case K_ECDHE_ECDSA:
// get preferable peer signature algorithm for server key exchange
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
preferableSignatureAlgorithm =
SignatureAndHashAlgorithm.getPreferableAlgorithm(
supportedSignAlgs, "ECDSA");
@@ -1233,13 +1293,6 @@
}
setupStaticECDHKeys();
break;
- case K_KRB5:
- case K_KRB5_EXPORT:
- // need Kerberos Key
- if (!setupKerberosKeys()) {
- return false;
- }
- break;
case K_DH_ANON:
// no certs needed for anonymous
setupEphemeralDHKeys(suite.exportable, null);
@@ -1251,13 +1304,31 @@
}
break;
default:
- // internal error, unknown key exchange
- throw new RuntimeException("Unrecognized cipherSuite: " + suite);
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find(keyExchange.name);
+ if (p == null) {
+ // internal error, unknown key exchange
+ throw new RuntimeException("Unrecognized cipherSuite: " + suite);
+ }
+ // need service creds
+ if (serviceCreds == null) {
+ AccessControlContext acc = getAccSE();
+ serviceCreds = p.getServiceCreds(acc);
+ if (serviceCreds != null) {
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Using serviceCreds");
+ }
+ }
+ if (serviceCreds == null) {
+ return false;
+ }
+ }
+ break;
}
setCipherSuite(suite);
// set the peer implicit supported signature algorithms
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (peerSupportedSignAlgs == null) {
setPeerSupportedSignAlgs(supportedSignAlgs);
// we had alreay update the session
@@ -1442,73 +1513,10 @@
return true;
}
- /**
- * Retrieve the Kerberos key for the specified server principal
- * from the JAAS configuration file.
- *
- * @return true if successful, false if not available or invalid
+ /*
+ * Returns premaster secret for external key exchange services.
*/
- private boolean setupKerberosKeys() {
- if (serviceCreds != null) {
- return true;
- }
- try {
- final AccessControlContext acc = getAccSE();
- serviceCreds = AccessController.doPrivileged(
- // Eliminate dependency on KerberosKey
- new PrivilegedExceptionAction<Object>() {
- @Override
- public Object run() throws Exception {
- // get kerberos key for the default principal
- return Krb5Helper.getServiceCreds(acc);
- }});
-
- // check permission to access and use the secret key of the
- // Kerberized "host" service
- if (serviceCreds != null) {
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Using Kerberos creds");
- }
- String serverPrincipal =
- Krb5Helper.getServerPrincipalName(serviceCreds);
- if (serverPrincipal != null) {
- // When service is bound, we check ASAP. Otherwise,
- // will check after client request is received
- // in Kerberos ClientKeyExchange
- SecurityManager sm = System.getSecurityManager();
- try {
- if (sm != null) {
- // Eliminate dependency on ServicePermission
- sm.checkPermission(Krb5Helper.getServicePermission(
- serverPrincipal, "accept"), acc);
- }
- } catch (SecurityException se) {
- serviceCreds = null;
- // Do not destroy keys. Will affect Subject
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Permission to access Kerberos"
- + " secret key denied");
- }
- return false;
- }
- }
- }
- return serviceCreds != null;
- } catch (PrivilegedActionException e) {
- // Likely exception here is LoginExceptin
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Attempt to obtain Kerberos key failed: "
- + e.toString());
- }
- return false;
- }
- }
-
- /*
- * For Kerberos ciphers, the premaster secret is encrypted using
- * the session key. See RFC 2712.
- */
- private SecretKey clientKeyExchange(KerberosClientKeyExchange mesg)
+ private SecretKey clientKeyExchange(ClientKeyExchange mesg)
throws IOException {
if (debug != null && Debug.isOn("handshake")) {
@@ -1519,8 +1527,7 @@
session.setPeerPrincipal(mesg.getPeerPrincipal());
session.setLocalPrincipal(mesg.getLocalPrincipal());
- byte[] b = mesg.getUnencryptedPreMasterSecret();
- return new SecretKeySpec(b, "TlsPremasterSecret");
+ return mesg.clientKeyExchange();
}
/*
@@ -1571,7 +1578,7 @@
mesg.print(System.out);
}
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
SignatureAndHashAlgorithm signAlg =
mesg.getPreferableSignatureAlgorithm();
if (signAlg == null) {
@@ -1623,9 +1630,9 @@
* Verify if client did send the certificate when client
* authentication was required, otherwise server should not proceed
*/
- if (doClientAuth == SSLEngineImpl.clauth_required) {
+ if (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED) {
// get X500Principal of the end-entity certificate for X509-based
- // ciphersuites, or Kerberos principal for Kerberos ciphersuites
+ // ciphersuites, or Kerberos principal for Kerberos ciphersuites, etc
session.getPeerPrincipal();
}
@@ -1664,8 +1671,9 @@
* the change_cipher_spec and Finished message.
*/
if (!resumingSession) {
- input.digestNow();
sendChangeCipherAndFinish(true);
+ } else {
+ handshakeFinished = true;
}
/*
@@ -1695,7 +1703,8 @@
private void sendChangeCipherAndFinish(boolean finishedTag)
throws IOException {
- output.flush();
+ // Reload if this message has been reserved.
+ handshakeHash.reload();
Finished mesg = new Finished(protocolVersion, handshakeHash,
Finished.SERVER, session.getMasterSecret(), cipherSuite);
@@ -1713,16 +1722,6 @@
if (secureRenegotiation) {
serverVerifyData = mesg.getVerifyData();
}
-
- /*
- * Update state machine so client MUST send 'finished' next
- * The update should only take place if it is not in the fast
- * handshake mode since the server has to wait for a finished
- * message from the client.
- */
- if (finishedTag) {
- state = HandshakeMessage.ht_finished;
- }
}
@@ -1757,7 +1756,7 @@
* session will get an SSLPeerUnverifiedException.
*/
if ((description == Alerts.alert_no_certificate) &&
- (doClientAuth == SSLEngineImpl.clauth_requested)) {
+ (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED)) {
return;
}
@@ -1798,7 +1797,7 @@
* If the client authentication is only *REQUESTED* (e.g.
* not *REQUIRED*, this is an acceptable condition.)
*/
- if (doClientAuth == SSLEngineImpl.clauth_requested) {
+ if (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED) {
return;
} else {
fatalSE(Alerts.alert_bad_certificate,
--- a/jdk/src/java.base/share/classes/sun/security/ssl/SunJSSE.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/SunJSSE.java Tue Jun 02 09:15:47 2015 -0700
@@ -61,7 +61,7 @@
private static String info = "Sun JSSE provider" +
"(PKCS12, SunX509/PKIX key/trust factories, " +
- "SSLv3/TLSv1/TLSv1.1/TLSv1.2)";
+ "SSLv3/TLSv1/TLSv1.1/TLSv1.2/DTLSv1.0/DTLSv1.2)";
private static String fipsInfo =
"Sun JSSE provider (FIPS mode, crypto provider ";
@@ -220,6 +220,13 @@
put("Alg.Alias.SSLContext.SSLv3", "TLSv1");
}
+ put("SSLContext.DTLSv1.0",
+ "sun.security.ssl.SSLContextImpl$DTLS10Context");
+ put("SSLContext.DTLSv1.2",
+ "sun.security.ssl.SSLContextImpl$DTLS12Context");
+ put("SSLContext.DTLS",
+ "sun.security.ssl.SSLContextImpl$DTLSContext");
+
put("SSLContext.Default",
"sun.security.ssl.SSLContextImpl$DefaultSSLContext");
--- a/jdk/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -190,7 +190,7 @@
if (session != null) {
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
String[] peerSupportedSignAlgs = null;
if (session instanceof ExtendedSSLSession) {
@@ -218,7 +218,7 @@
if (session != null) {
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
String[] peerSupportedSignAlgs = null;
if (session instanceof ExtendedSSLSession) {
--- a/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java Tue Jun 02 09:15:47 2015 -0700
@@ -204,7 +204,7 @@
// create the algorithm constraints
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
@@ -256,7 +256,7 @@
// create the algorithm constraints
ProtocolVersion protocolVersion =
ProtocolVersion.valueOf(session.getProtocol());
- if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+ if (protocolVersion.useTLS12PlusSpec()) {
if (session instanceof ExtendedSSLSession) {
ExtendedSSLSession extSession =
(ExtendedSSLSession)session;
--- a/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/util/HostnameChecker.java Tue Jun 02 09:15:47 2015 -0700
@@ -35,7 +35,7 @@
import javax.security.auth.x500.X500Principal;
-import sun.security.ssl.Krb5Helper;
+import sun.security.ssl.ClientKeyExchangeService;
import sun.security.x509.X500Name;
import sun.net.util.IPAddressUtil;
@@ -108,7 +108,12 @@
* Return the Server name from Kerberos principal.
*/
public static String getServerName(Principal principal) {
- return Krb5Helper.getPrincipalHostName(principal);
+ ClientKeyExchangeService p =
+ ClientKeyExchangeService.find("KRB5");
+ if (p == null) {
+ throw new AssertionError("Kerberos should have been available");
+ }
+ return p.getServiceHostName(principal);
}
/**
--- a/jdk/src/java.base/share/conf/security/java.security Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/src/java.base/share/conf/security/java.security Tue Jun 02 09:15:47 2015 -0700
@@ -511,11 +511,11 @@
jdk.certpath.disabledAlgorithms=MD2, MD5, RSA keySize < 1024
# Algorithm restrictions for Secure Socket Layer/Transport Layer Security
-# (SSL/TLS) processing
+# (SSL/TLS/DTLS) processing
#
# In some environments, certain algorithms or key lengths may be undesirable
-# when using SSL/TLS. This section describes the mechanism for disabling
-# algorithms during SSL/TLS security parameters negotiation, including
+# when using SSL/TLS/DTLS. This section describes the mechanism for disabling
+# algorithms during SSL/TLS/DTLS security parameters negotiation, including
# protocol version negotiation, cipher suites selection, peer authentication
# and key exchange mechanisms.
#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.security.jgss/share/classes/META-INF/services/sun.security.ssl.ClientKeyExchangeService Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,2 @@
+sun.security.krb5.internal.ssl.Krb5KeyExchangeService
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/ssl/KerberosPreMasterSecret.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.krb5.internal.ssl;
+
+import java.io.*;
+import java.security.*;
+import java.util.Arrays;
+
+import javax.net.ssl.*;
+
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.EncryptedData;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.internal.crypto.KeyUsage;
+
+import sun.security.ssl.Debug;
+import sun.security.ssl.HandshakeInStream;
+import sun.security.ssl.HandshakeMessage;
+import sun.security.ssl.ProtocolVersion;
+
+/**
+ * This is the Kerberos premaster secret in the Kerberos client key
+ * exchange message (CLIENT --> SERVER); it holds the
+ * Kerberos-encrypted pre-master secret. The secret is encrypted using the
+ * Kerberos session key. The padding and size of the resulting message
+ * depends on the session key type, but the pre-master secret is
+ * always exactly 48 bytes.
+ *
+ */
+final class KerberosPreMasterSecret {
+
+ private ProtocolVersion protocolVersion; // preMaster [0,1]
+ private byte preMaster[]; // 48 bytes
+ private byte encrypted[];
+
+ /**
+ * Constructor used by client to generate premaster secret.
+ *
+ * Client randomly creates a pre-master secret and encrypts it
+ * using the Kerberos session key; only the server can decrypt
+ * it, using the session key available in the service ticket.
+ *
+ * @param protocolVersion used to set preMaster[0,1]
+ * @param generator random number generator for generating premaster secret
+ * @param sessionKey Kerberos session key for encrypting premaster secret
+ */
+ KerberosPreMasterSecret(ProtocolVersion protocolVersion,
+ SecureRandom generator, EncryptionKey sessionKey) throws IOException {
+
+ if (sessionKey.getEType() ==
+ EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
+ throw new IOException(
+ "session keys with des3-cbc-hmac-sha1-kd encryption type " +
+ "are not supported for TLS Kerberos cipher suites");
+ }
+
+ this.protocolVersion = protocolVersion;
+ preMaster = generatePreMaster(generator, protocolVersion);
+
+ // Encrypt premaster secret
+ try {
+ EncryptedData eData = new EncryptedData(sessionKey, preMaster,
+ KeyUsage.KU_UNKNOWN);
+ encrypted = eData.getBytes(); // not ASN.1 encoded.
+
+ } catch (KrbException e) {
+ throw (SSLKeyException)new SSLKeyException
+ ("Kerberos premaster secret error").initCause(e);
+ }
+ }
+
+ /*
+ * Constructor used by server to decrypt encrypted premaster secret.
+ * The protocol version in preMaster[0,1] must match either currentVersion
+ * or clientVersion, otherwise, the premaster secret is set to
+ * a random one to foil possible attack.
+ *
+ * @param currentVersion version of protocol being used
+ * @param clientVersion version requested by client
+ * @param generator random number generator used to generate
+ * bogus premaster secret if premaster secret verification fails
+ * @param input input stream from which to read the encrypted
+ * premaster secret
+ * @param sessionKey Kerberos session key to be used for decryption
+ */
+ KerberosPreMasterSecret(ProtocolVersion currentVersion,
+ ProtocolVersion clientVersion,
+ SecureRandom generator, byte[] encrypted,
+ EncryptionKey sessionKey) throws IOException {
+
+ if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
+ if (encrypted != null) {
+ Debug.println(System.out,
+ "encrypted premaster secret", encrypted);
+ }
+ }
+
+ if (sessionKey.getEType() ==
+ EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
+ throw new IOException(
+ "session keys with des3-cbc-hmac-sha1-kd encryption type " +
+ "are not supported for TLS Kerberos cipher suites");
+ }
+
+ // Decrypt premaster secret
+ try {
+ EncryptedData data = new EncryptedData(sessionKey.getEType(),
+ null /* optional kvno */, encrypted);
+
+ byte[] temp = data.decrypt(sessionKey, KeyUsage.KU_UNKNOWN);
+ if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
+ if (encrypted != null) {
+ Debug.println(System.out,
+ "decrypted premaster secret", temp);
+ }
+ }
+
+ // Remove padding bytes after decryption. Only DES and DES3 have
+ // paddings and we don't support DES3 in TLS (see above)
+
+ if (temp.length == 52 &&
+ data.getEType() == EncryptedData.ETYPE_DES_CBC_CRC) {
+ // For des-cbc-crc, 4 paddings. Value can be 0x04 or 0x00.
+ if (paddingByteIs(temp, 52, (byte)4) ||
+ paddingByteIs(temp, 52, (byte)0)) {
+ temp = Arrays.copyOf(temp, 48);
+ }
+ } else if (temp.length == 56 &&
+ data.getEType() == EncryptedData.ETYPE_DES_CBC_MD5) {
+ // For des-cbc-md5, 8 paddings with 0x08, or no padding
+ if (paddingByteIs(temp, 56, (byte)8)) {
+ temp = Arrays.copyOf(temp, 48);
+ }
+ }
+
+ preMaster = temp;
+
+ protocolVersion = ProtocolVersion.valueOf(preMaster[0],
+ preMaster[1]);
+ if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
+ System.out.println("Kerberos PreMasterSecret version: "
+ + protocolVersion);
+ }
+ } catch (Exception e) {
+ // catch exception & process below
+ preMaster = null;
+ protocolVersion = currentVersion;
+ }
+
+ // check if the premaster secret version is ok
+ // the specification says that it must be the maximum version supported
+ // by the client from its ClientHello message. However, many
+ // old implementations send the negotiated version, so accept both
+ // for SSL v3.0 and TLS v1.0.
+ // NOTE that we may be comparing two unsupported version numbers in
+ // the second case, which is why we cannot use object references
+ // equality in this special case
+ boolean versionMismatch = (protocolVersion.v != clientVersion.v);
+
+ /*
+ * we never checked the client_version in server side
+ * for TLS v1.0 and SSL v3.0. For compatibility, we
+ * maintain this behavior.
+ */
+ if (versionMismatch && (clientVersion.v <= 0x0301)) {
+ versionMismatch = (protocolVersion.v != currentVersion.v);
+ }
+
+ /*
+ * Bogus decrypted ClientKeyExchange? If so, conjure a
+ * a random preMaster secret that will fail later during
+ * Finished message processing. This is a countermeasure against
+ * the "interactive RSA PKCS#1 encryption envelop attack" reported
+ * in June 1998. Preserving the executation path will
+ * mitigate timing attacks and force consistent error handling
+ * that will prevent an attacking client from differentiating
+ * different kinds of decrypted ClientKeyExchange bogosities.
+ */
+ if ((preMaster == null) || (preMaster.length != 48)
+ || versionMismatch) {
+ if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
+ System.out.println("Kerberos PreMasterSecret error, "
+ + "generating random secret");
+ if (preMaster != null) {
+ Debug.println(System.out, "Invalid secret", preMaster);
+ }
+ }
+
+ /*
+ * Randomize the preMaster secret with the
+ * ClientHello.client_version, as will produce invalid master
+ * secret to prevent the attacks.
+ */
+ preMaster = generatePreMaster(generator, clientVersion);
+ protocolVersion = clientVersion;
+ }
+ }
+
+ /**
+ * Checks if all paddings of data are b
+ * @param data the block with padding
+ * @param len length of data, >= 48
+ * @param b expected padding byte
+ */
+ private static boolean paddingByteIs(byte[] data, int len, byte b) {
+ for (int i=48; i<len; i++) {
+ if (data[i] != b) return false;
+ }
+ return true;
+ }
+
+ /*
+ * Used by server to generate premaster secret in case of
+ * problem decoding ticket.
+ *
+ * @param protocolVersion used for preMaster[0,1]
+ * @param generator random number generator to use for generating secret.
+ */
+ KerberosPreMasterSecret(ProtocolVersion protocolVersion,
+ SecureRandom generator) {
+
+ this.protocolVersion = protocolVersion;
+ preMaster = generatePreMaster(generator, protocolVersion);
+ }
+
+ private static byte[] generatePreMaster(SecureRandom rand,
+ ProtocolVersion ver) {
+
+ byte[] pm = new byte[48];
+ rand.nextBytes(pm);
+ pm[0] = ver.major;
+ pm[1] = ver.minor;
+
+ return pm;
+ }
+
+ // Clone not needed; internal use only
+ byte[] getUnencrypted() {
+ return preMaster;
+ }
+
+ // Clone not needed; internal use only
+ byte[] getEncrypted() {
+ return encrypted;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.security.jgss/share/classes/sun/security/krb5/internal/ssl/Krb5KeyExchangeService.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.krb5.internal.ssl;
+
+import sun.security.ssl.ClientKeyExchange;
+import sun.security.ssl.Debug;
+import sun.security.ssl.ClientKeyExchangeService;
+import sun.security.ssl.HandshakeOutStream;
+
+import sun.security.jgss.GSSCaller;
+import sun.security.jgss.krb5.Krb5Util;
+import sun.security.jgss.krb5.ServiceCreds;
+import sun.security.krb5.EncryptedData;
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.PrincipalName;
+import sun.security.krb5.internal.EncTicketPart;
+import sun.security.krb5.internal.Ticket;
+import sun.security.krb5.internal.crypto.KeyUsage;
+import sun.security.ssl.ProtocolVersion;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.kerberos.KeyTab;
+import javax.security.auth.kerberos.ServicePermission;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureRandom;
+import java.util.Set;
+
+/**
+ * The provider for TLS_KRB_ cipher suites.
+ *
+ * @since 1.9
+ */
+public class Krb5KeyExchangeService implements ClientKeyExchangeService {
+
+ public static final Debug debug = Debug.getInstance("ssl");
+
+ @Override
+ public String[] supported() {
+ return new String[] { "KRB5", "KRB5_EXPORT" };
+ }
+
+ @Override
+ public Object getServiceCreds(AccessControlContext acc) {
+ try {
+ ServiceCreds serviceCreds = AccessController.doPrivileged(
+ (PrivilegedExceptionAction<ServiceCreds>)
+ () -> Krb5Util.getServiceCreds(
+ GSSCaller.CALLER_SSL_SERVER, null, acc));
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Using Kerberos creds");
+ }
+ String serverPrincipal = serviceCreds.getName();
+ if (serverPrincipal != null) {
+ // When service is bound, we check ASAP. Otherwise,
+ // will check after client request is received
+ // in in Kerberos ClientKeyExchange
+ SecurityManager sm = System.getSecurityManager();
+ try {
+ if (sm != null) {
+ // Eliminate dependency on ServicePermission
+ sm.checkPermission(new ServicePermission(
+ serverPrincipal, "accept"), acc);
+ }
+ } catch (SecurityException se) {
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Permission to access Kerberos"
+ + " secret key denied");
+ }
+ return null;
+ }
+ }
+ return serviceCreds;
+ } catch (PrivilegedActionException e) {
+ // Likely exception here is LoginException
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Attempt to obtain Kerberos key failed: "
+ + e.toString());
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public String getServiceHostName(Principal principal) {
+ if (principal == null) {
+ return null;
+ }
+ String hostName = null;
+ try {
+ PrincipalName princName =
+ new PrincipalName(principal.getName(),
+ PrincipalName.KRB_NT_SRV_HST);
+ String[] nameParts = princName.getNameStrings();
+ if (nameParts.length >= 2) {
+ hostName = nameParts[1];
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return hostName;
+ }
+
+
+ @Override
+ public boolean isRelated(boolean isClient,
+ AccessControlContext acc, Principal p) {
+
+ if (p == null) return false;
+ try {
+ Subject subject = AccessController.doPrivileged(
+ (PrivilegedExceptionAction<Subject>)
+ () -> Krb5Util.getSubject(
+ isClient ? GSSCaller.CALLER_SSL_CLIENT
+ : GSSCaller.CALLER_SSL_SERVER,
+ acc));
+ if (subject == null) {
+ if (debug != null && Debug.isOn("session")) {
+ System.out.println("Kerberos credentials are" +
+ " not present in the current Subject;" +
+ " check if " +
+ " javax.security.auth.useSubjectAsCreds" +
+ " system property has been set to false");
+ }
+ return false;
+ }
+ Set<Principal> principals =
+ subject.getPrincipals(Principal.class);
+ if (principals.contains(p)) {
+ // bound to this principal
+ return true;
+ } else {
+ if (isClient) {
+ return false;
+ } else {
+ for (KeyTab pc : subject.getPrivateCredentials(KeyTab.class)) {
+ if (!pc.isBound()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ } catch (PrivilegedActionException pae) {
+ if (debug != null && Debug.isOn("session")) {
+ System.out.println("Attempt to obtain" +
+ " subject failed! " + pae);
+ }
+ return false;
+ }
+
+ }
+
+ public ClientKeyExchange createClientExchange(
+ String serverName, AccessControlContext acc,
+ ProtocolVersion protocolVerson, SecureRandom rand) throws IOException {
+ return new ExchangerImpl(serverName, acc, protocolVerson, rand);
+ }
+
+ public ClientKeyExchange createServerExchange(
+ ProtocolVersion protocolVersion, ProtocolVersion clientVersion,
+ SecureRandom rand, byte[] encodedTicket, byte[] encrypted,
+ AccessControlContext acc, Object serviceCreds) throws IOException {
+ return new ExchangerImpl(protocolVersion, clientVersion, rand,
+ encodedTicket, encrypted, acc, serviceCreds);
+ }
+
+ static class ExchangerImpl extends ClientKeyExchange {
+
+ final private KerberosPreMasterSecret preMaster;
+ final private byte[] encodedTicket;
+ final private KerberosPrincipal peerPrincipal;
+ final private KerberosPrincipal localPrincipal;
+
+ @Override
+ public int messageLength() {
+ return encodedTicket.length + preMaster.getEncrypted().length + 6;
+ }
+
+ @Override
+ public void send(HandshakeOutStream s) throws IOException {
+ s.putBytes16(encodedTicket);
+ s.putBytes16(null);
+ s.putBytes16(preMaster.getEncrypted());
+ }
+
+ @Override
+ public void print(PrintStream s) throws IOException {
+ s.println("*** ClientKeyExchange, Kerberos");
+
+ if (debug != null && Debug.isOn("verbose")) {
+ Debug.println(s, "Kerberos service ticket", encodedTicket);
+ Debug.println(s, "Random Secret", preMaster.getUnencrypted());
+ Debug.println(s, "Encrypted random Secret", preMaster.getEncrypted());
+ }
+ }
+
+ ExchangerImpl(String serverName, AccessControlContext acc,
+ ProtocolVersion protocolVersion, SecureRandom rand) throws IOException {
+
+ // Get service ticket
+ KerberosTicket ticket = getServiceTicket(serverName, acc);
+ encodedTicket = ticket.getEncoded();
+
+ // Record the Kerberos principals
+ peerPrincipal = ticket.getServer();
+ localPrincipal = ticket.getClient();
+
+ // Optional authenticator, encrypted using session key,
+ // currently ignored
+
+ // Generate premaster secret and encrypt it using session key
+ EncryptionKey sessionKey = new EncryptionKey(
+ ticket.getSessionKeyType(),
+ ticket.getSessionKey().getEncoded());
+
+ preMaster = new KerberosPreMasterSecret(protocolVersion,
+ rand, sessionKey);
+ }
+
+ ExchangerImpl(
+ ProtocolVersion protocolVersion, ProtocolVersion clientVersion, SecureRandom rand,
+ byte[] encodedTicket, byte[] encrypted,
+ AccessControlContext acc, Object serviceCreds) throws IOException {
+
+ // Read ticket
+ this.encodedTicket = encodedTicket;
+
+ if (debug != null && Debug.isOn("verbose")) {
+ Debug.println(System.out,
+ "encoded Kerberos service ticket", encodedTicket);
+ }
+
+ EncryptionKey sessionKey = null;
+ KerberosPrincipal tmpPeer = null;
+ KerberosPrincipal tmpLocal = null;
+
+ try {
+ Ticket t = new Ticket(encodedTicket);
+
+ EncryptedData encPart = t.encPart;
+ PrincipalName ticketSname = t.sname;
+
+ final ServiceCreds creds = (ServiceCreds)serviceCreds;
+ final KerberosPrincipal princ =
+ new KerberosPrincipal(ticketSname.toString());
+
+ // For bound service, permission already checked at setup
+ if (creds.getName() == null) {
+ SecurityManager sm = System.getSecurityManager();
+ try {
+ if (sm != null) {
+ // Eliminate dependency on ServicePermission
+ sm.checkPermission(new ServicePermission(
+ ticketSname.toString(), "accept"), acc);
+ }
+ } catch (SecurityException se) {
+ serviceCreds = null;
+ // Do not destroy keys. Will affect Subject
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Permission to access Kerberos"
+ + " secret key denied");
+ se.printStackTrace(System.out);
+ }
+ throw new IOException("Kerberos service not allowedy");
+ }
+ }
+ KerberosKey[] serverKeys = AccessController.doPrivileged(
+ new PrivilegedAction<KerberosKey[]>() {
+ @Override
+ public KerberosKey[] run() {
+ return creds.getKKeys(princ);
+ }
+ });
+ if (serverKeys.length == 0) {
+ throw new IOException("Found no key for " + princ +
+ (creds.getName() == null ? "" :
+ (", this keytab is for " + creds.getName() + " only")));
+ }
+
+ /*
+ * permission to access and use the secret key of the Kerberized
+ * "host" service is done in ServerHandshaker.getKerberosKeys()
+ * to ensure server has the permission to use the secret key
+ * before promising the client
+ */
+
+ // See if we have the right key to decrypt the ticket to get
+ // the session key.
+ int encPartKeyType = encPart.getEType();
+ Integer encPartKeyVersion = encPart.getKeyVersionNumber();
+ KerberosKey dkey = null;
+ try {
+ dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
+ } catch (KrbException ke) { // a kvno mismatch
+ throw new IOException(
+ "Cannot find key matching version number", ke);
+ }
+ if (dkey == null) {
+ // %%% Should print string repr of etype
+ throw new IOException("Cannot find key of appropriate type" +
+ " to decrypt ticket - need etype " + encPartKeyType);
+ }
+
+ EncryptionKey secretKey = new EncryptionKey(
+ encPartKeyType,
+ dkey.getEncoded());
+
+ // Decrypt encPart using server's secret key
+ byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
+
+ // Reset data stream after decryption, remove redundant bytes
+ byte[] temp = encPart.reset(bytes);
+ EncTicketPart encTicketPart = new EncTicketPart(temp);
+
+ // Record the Kerberos Principals
+ tmpPeer = new KerberosPrincipal(encTicketPart.cname.getName());
+ tmpLocal = new KerberosPrincipal(ticketSname.getName());
+
+ sessionKey = encTicketPart.key;
+
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("server principal: " + ticketSname);
+ System.out.println("cname: " + encTicketPart.cname.toString());
+ }
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("KerberosWrapper error getting session key,"
+ + " generating random secret (" + e.getMessage() + ")");
+ }
+ sessionKey = null;
+ }
+
+ //input.getBytes16(); // XXX Read and ignore authenticator
+
+ if (sessionKey != null) {
+ preMaster = new KerberosPreMasterSecret(protocolVersion,
+ clientVersion, rand, encrypted, sessionKey);
+ } else {
+ // Generate bogus premaster secret
+ preMaster = new KerberosPreMasterSecret(clientVersion, rand);
+ }
+
+ peerPrincipal = tmpPeer;
+ localPrincipal = tmpLocal;
+ }
+
+ // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
+ private static KerberosTicket getServiceTicket(String serverName,
+ final AccessControlContext acc) throws IOException {
+
+ if ("localhost".equals(serverName) ||
+ "localhost.localdomain".equals(serverName)) {
+
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Get the local hostname");
+ }
+ String localHost = java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<String>() {
+ public String run() {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (java.net.UnknownHostException e) {
+ if (debug != null && Debug.isOn("handshake")) {
+ System.out.println("Warning,"
+ + " cannot get the local hostname: "
+ + e.getMessage());
+ }
+ return null;
+ }
+ }
+ });
+ if (localHost != null) {
+ serverName = localHost;
+ }
+ }
+
+ // Resolve serverName (possibly in IP addr form) to Kerberos principal
+ // name for service with hostname
+ String serviceName = "host/" + serverName;
+ PrincipalName principal;
+ try {
+ principal = new PrincipalName(serviceName,
+ PrincipalName.KRB_NT_SRV_HST);
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ IOException ioe = new IOException("Invalid service principal" +
+ " name: " + serviceName);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ String realm = principal.getRealmAsString();
+
+ final String serverPrincipal = principal.toString();
+ final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
+ final String clientPrincipal = null; // use default
+
+
+ // check permission to obtain a service ticket to initiate a
+ // context with the "host" service
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new ServicePermission(serverPrincipal,
+ "initiate"), acc);
+ }
+
+ try {
+ KerberosTicket ticket = AccessController.doPrivileged(
+ new PrivilegedExceptionAction<KerberosTicket>() {
+ public KerberosTicket run() throws Exception {
+ return Krb5Util.getTicketFromSubjectAndTgs(
+ GSSCaller.CALLER_SSL_CLIENT,
+ clientPrincipal, serverPrincipal,
+ tgsPrincipal, acc);
+ }});
+
+ if (ticket == null) {
+ throw new IOException("Failed to find any kerberos service" +
+ " ticket for " + serverPrincipal);
+ }
+ return ticket;
+ } catch (PrivilegedActionException e) {
+ IOException ioe = new IOException(
+ "Attempt to obtain kerberos service ticket for " +
+ serverPrincipal + " failed!");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+
+ @Override
+ public SecretKey clientKeyExchange() {
+ byte[] secretBytes = preMaster.getUnencrypted();
+ return new SecretKeySpec(secretBytes, "TlsPremasterSecret");
+ }
+
+ @Override
+ public Principal getPeerPrincipal() {
+ return peerPrincipal;
+ }
+
+ @Override
+ public Principal getLocalPrincipal() {
+ return localPrincipal;
+ }
+
+ /**
+ * Determines if a kvno matches another kvno. Used in the method
+ * findKey(etype, version, keys). Always returns true if either input
+ * is null or zero, in case any side does not have kvno info available.
+ *
+ * Note: zero is included because N/A is not a legal value for kvno
+ * in javax.security.auth.kerberos.KerberosKey. Therefore, the info
+ * that the kvno is N/A might be lost when converting between
+ * EncryptionKey and KerberosKey.
+ */
+ private static boolean versionMatches(Integer v1, int v2) {
+ if (v1 == null || v1 == 0 || v2 == 0) {
+ return true;
+ }
+ return v1.equals(v2);
+ }
+
+ private static KerberosKey findKey(int etype, Integer version,
+ KerberosKey[] keys) throws KrbException {
+ int ktype;
+ boolean etypeFound = false;
+
+ // When no matched kvno is found, returns tke key of the same
+ // etype with the highest kvno
+ int kvno_found = 0;
+ KerberosKey key_found = null;
+
+ for (int i = 0; i < keys.length; i++) {
+ ktype = keys[i].getKeyType();
+ if (etype == ktype) {
+ int kv = keys[i].getVersionNumber();
+ etypeFound = true;
+ if (versionMatches(version, kv)) {
+ return keys[i];
+ } else if (kv > kvno_found) {
+ key_found = keys[i];
+ kvno_found = kv;
+ }
+ }
+ }
+ // Key not found.
+ // %%% kludge to allow DES keys to be used for diff etypes
+ if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
+ etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
+ for (int i = 0; i < keys.length; i++) {
+ ktype = keys[i].getKeyType();
+ if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
+ ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
+ int kv = keys[i].getVersionNumber();
+ etypeFound = true;
+ if (versionMatches(version, kv)) {
+ return new KerberosKey(keys[i].getPrincipal(),
+ keys[i].getEncoded(),
+ etype,
+ kv);
+ } else if (kv > kvno_found) {
+ key_found = new KerberosKey(keys[i].getPrincipal(),
+ keys[i].getEncoded(),
+ etype,
+ kv);
+ kvno_found = kv;
+ }
+ }
+ }
+ }
+ if (etypeFound) {
+ return key_found;
+ }
+ return null;
+ }
+ }
+}
--- a/jdk/src/java.security.jgss/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,463 +0,0 @@
-/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl.krb5;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.security.AccessController;
-import java.security.AccessControlContext;
-import java.security.PrivilegedExceptionAction;
-import java.security.PrivilegedActionException;
-import java.security.SecureRandom;
-import java.net.InetAddress;
-import java.security.PrivilegedAction;
-
-import javax.security.auth.kerberos.KerberosTicket;
-import javax.security.auth.kerberos.KerberosKey;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.kerberos.ServicePermission;
-import sun.security.jgss.GSSCaller;
-
-import sun.security.krb5.EncryptionKey;
-import sun.security.krb5.EncryptedData;
-import sun.security.krb5.PrincipalName;
-import sun.security.krb5.internal.Ticket;
-import sun.security.krb5.internal.EncTicketPart;
-import sun.security.krb5.internal.crypto.KeyUsage;
-
-import sun.security.jgss.krb5.Krb5Util;
-import sun.security.jgss.krb5.ServiceCreds;
-import sun.security.krb5.KrbException;
-import sun.security.krb5.internal.Krb5;
-
-import sun.security.ssl.Debug;
-import sun.security.ssl.HandshakeInStream;
-import sun.security.ssl.HandshakeOutStream;
-import sun.security.ssl.Krb5Helper;
-import sun.security.ssl.ProtocolVersion;
-
-/**
- * This is Kerberos option in the client key exchange message
- * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted
- * premaster secret encrypted with the session key sealed in the ticket.
- * From RFC 2712:
- * struct
- * {
- * opaque Ticket;
- * opaque authenticator; // optional
- * opaque EncryptedPreMasterSecret; // encrypted with the session key
- * // which is sealed in the ticket
- * } KerberosWrapper;
- *
- *
- * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1)
- * Encrypted pre-master secret has the same structure as it does for RSA
- * except for Kerberos, the encryption key is the session key instead of
- * the RSA public key.
- *
- * XXX authenticator currently ignored
- *
- */
-public final class KerberosClientKeyExchangeImpl
- extends sun.security.ssl.KerberosClientKeyExchange {
-
- private KerberosPreMasterSecret preMaster;
- private byte[] encodedTicket;
- private KerberosPrincipal peerPrincipal;
- private KerberosPrincipal localPrincipal;
-
- public KerberosClientKeyExchangeImpl() {
- }
-
- /**
- * Creates an instance of KerberosClientKeyExchange consisting of the
- * Kerberos service ticket, authenticator and encrypted premaster secret.
- * Called by client handshaker.
- *
- * @param serverName name of server with which to do handshake;
- * this is used to get the Kerberos service ticket
- * @param protocolVersion Maximum version supported by client (i.e,
- * version it requested in client hello)
- * @param rand random number generator to use for generating pre-master
- * secret
- */
- @Override
- public void init(String serverName,
- AccessControlContext acc, ProtocolVersion protocolVersion,
- SecureRandom rand) throws IOException {
-
- // Get service ticket
- KerberosTicket ticket = getServiceTicket(serverName, acc);
- encodedTicket = ticket.getEncoded();
-
- // Record the Kerberos principals
- peerPrincipal = ticket.getServer();
- localPrincipal = ticket.getClient();
-
- // Optional authenticator, encrypted using session key,
- // currently ignored
-
- // Generate premaster secret and encrypt it using session key
- EncryptionKey sessionKey = new EncryptionKey(
- ticket.getSessionKeyType(),
- ticket.getSessionKey().getEncoded());
-
- preMaster = new KerberosPreMasterSecret(protocolVersion,
- rand, sessionKey);
- }
-
- /**
- * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding.
- * Used by ServerHandshaker to verify and obtain premaster secret.
- *
- * @param protocolVersion current protocol version
- * @param clientVersion version requested by client in its ClientHello;
- * used by premaster secret version check
- * @param rand random number generator used for generating random
- * premaster secret if ticket and/or premaster verification fails
- * @param input inputstream from which to get ASN.1-encoded KerberosWrapper
- * @param acc the AccessControlContext of the handshaker
- * @param serviceCreds server's creds
- */
- @Override
- public void init(ProtocolVersion protocolVersion,
- ProtocolVersion clientVersion,
- SecureRandom rand, HandshakeInStream input, AccessControlContext acc, Object serviceCreds)
- throws IOException {
-
- // Read ticket
- encodedTicket = input.getBytes16();
-
- if (debug != null && Debug.isOn("verbose")) {
- Debug.println(System.out,
- "encoded Kerberos service ticket", encodedTicket);
- }
-
- EncryptionKey sessionKey = null;
-
- try {
- Ticket t = new Ticket(encodedTicket);
-
- EncryptedData encPart = t.encPart;
- PrincipalName ticketSname = t.sname;
-
- final ServiceCreds creds = (ServiceCreds)serviceCreds;
- final KerberosPrincipal princ =
- new KerberosPrincipal(ticketSname.toString());
-
- // For bound service, permission already checked at setup
- if (creds.getName() == null) {
- SecurityManager sm = System.getSecurityManager();
- try {
- if (sm != null) {
- // Eliminate dependency on ServicePermission
- sm.checkPermission(Krb5Helper.getServicePermission(
- ticketSname.toString(), "accept"), acc);
- }
- } catch (SecurityException se) {
- serviceCreds = null;
- // Do not destroy keys. Will affect Subject
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Permission to access Kerberos"
- + " secret key denied");
- }
- throw new IOException("Kerberos service not allowedy");
- }
- }
- KerberosKey[] serverKeys = AccessController.doPrivileged(
- new PrivilegedAction<KerberosKey[]>() {
- @Override
- public KerberosKey[] run() {
- return creds.getKKeys(princ);
- }
- });
- if (serverKeys.length == 0) {
- throw new IOException("Found no key for " + princ +
- (creds.getName() == null ? "" :
- (", this keytab is for " + creds.getName() + " only")));
- }
-
- /*
- * permission to access and use the secret key of the Kerberized
- * "host" service is done in ServerHandshaker.getKerberosKeys()
- * to ensure server has the permission to use the secret key
- * before promising the client
- */
-
- // See if we have the right key to decrypt the ticket to get
- // the session key.
- int encPartKeyType = encPart.getEType();
- Integer encPartKeyVersion = encPart.getKeyVersionNumber();
- KerberosKey dkey = null;
- try {
- dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
- } catch (KrbException ke) { // a kvno mismatch
- throw new IOException(
- "Cannot find key matching version number", ke);
- }
- if (dkey == null) {
- // %%% Should print string repr of etype
- throw new IOException("Cannot find key of appropriate type" +
- " to decrypt ticket - need etype " + encPartKeyType);
- }
-
- EncryptionKey secretKey = new EncryptionKey(
- encPartKeyType,
- dkey.getEncoded());
-
- // Decrypt encPart using server's secret key
- byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
-
- // Reset data stream after decryption, remove redundant bytes
- byte[] temp = encPart.reset(bytes);
- EncTicketPart encTicketPart = new EncTicketPart(temp);
-
- // Record the Kerberos Principals
- peerPrincipal =
- new KerberosPrincipal(encTicketPart.cname.getName());
- localPrincipal = new KerberosPrincipal(ticketSname.getName());
-
- sessionKey = encTicketPart.key;
-
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("server principal: " + ticketSname);
- System.out.println("cname: " + encTicketPart.cname.toString());
- }
- } catch (IOException e) {
- throw e;
- } catch (Exception e) {
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("KerberosWrapper error getting session key,"
- + " generating random secret (" + e.getMessage() + ")");
- }
- sessionKey = null;
- }
-
- input.getBytes16(); // XXX Read and ignore authenticator
-
- if (sessionKey != null) {
- preMaster = new KerberosPreMasterSecret(protocolVersion,
- clientVersion, rand, input, sessionKey);
- } else {
- // Generate bogus premaster secret
- preMaster = new KerberosPreMasterSecret(clientVersion, rand);
- }
- }
-
- @Override
- public int messageLength() {
- return (6 + encodedTicket.length + preMaster.getEncrypted().length);
- }
-
- @Override
- public void send(HandshakeOutStream s) throws IOException {
- s.putBytes16(encodedTicket);
- s.putBytes16(null); // XXX no authenticator
- s.putBytes16(preMaster.getEncrypted());
- }
-
- @Override
- public void print(PrintStream s) throws IOException {
- s.println("*** ClientKeyExchange, Kerberos");
-
- if (debug != null && Debug.isOn("verbose")) {
- Debug.println(s, "Kerberos service ticket", encodedTicket);
- Debug.println(s, "Random Secret", preMaster.getUnencrypted());
- Debug.println(s, "Encrypted random Secret",
- preMaster.getEncrypted());
- }
- }
-
- // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
- private static KerberosTicket getServiceTicket(String serverName,
- final AccessControlContext acc) throws IOException {
-
- if ("localhost".equals(serverName) ||
- "localhost.localdomain".equals(serverName)) {
-
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Get the local hostname");
- }
- String localHost = java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction<String>() {
- public String run() {
- try {
- return InetAddress.getLocalHost().getHostName();
- } catch (java.net.UnknownHostException e) {
- if (debug != null && Debug.isOn("handshake")) {
- System.out.println("Warning,"
- + " cannot get the local hostname: "
- + e.getMessage());
- }
- return null;
- }
- }
- });
- if (localHost != null) {
- serverName = localHost;
- }
- }
-
- // Resolve serverName (possibly in IP addr form) to Kerberos principal
- // name for service with hostname
- String serviceName = "host/" + serverName;
- PrincipalName principal;
- try {
- principal = new PrincipalName(serviceName,
- PrincipalName.KRB_NT_SRV_HST);
- } catch (SecurityException se) {
- throw se;
- } catch (Exception e) {
- IOException ioe = new IOException("Invalid service principal" +
- " name: " + serviceName);
- ioe.initCause(e);
- throw ioe;
- }
- String realm = principal.getRealmAsString();
-
- final String serverPrincipal = principal.toString();
- final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
- final String clientPrincipal = null; // use default
-
-
- // check permission to obtain a service ticket to initiate a
- // context with the "host" service
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- sm.checkPermission(new ServicePermission(serverPrincipal,
- "initiate"), acc);
- }
-
- try {
- KerberosTicket ticket = AccessController.doPrivileged(
- new PrivilegedExceptionAction<KerberosTicket>() {
- public KerberosTicket run() throws Exception {
- return Krb5Util.getTicketFromSubjectAndTgs(
- GSSCaller.CALLER_SSL_CLIENT,
- clientPrincipal, serverPrincipal,
- tgsPrincipal, acc);
- }});
-
- if (ticket == null) {
- throw new IOException("Failed to find any kerberos service" +
- " ticket for " + serverPrincipal);
- }
- return ticket;
- } catch (PrivilegedActionException e) {
- IOException ioe = new IOException(
- "Attempt to obtain kerberos service ticket for " +
- serverPrincipal + " failed!");
- ioe.initCause(e);
- throw ioe;
- }
- }
-
- @Override
- public byte[] getUnencryptedPreMasterSecret() {
- return preMaster.getUnencrypted();
- }
-
- @Override
- public KerberosPrincipal getPeerPrincipal() {
- return peerPrincipal;
- }
-
- @Override
- public KerberosPrincipal getLocalPrincipal() {
- return localPrincipal;
- }
-
- /**
- * Determines if a kvno matches another kvno. Used in the method
- * findKey(etype, version, keys). Always returns true if either input
- * is null or zero, in case any side does not have kvno info available.
- *
- * Note: zero is included because N/A is not a legal value for kvno
- * in javax.security.auth.kerberos.KerberosKey. Therefore, the info
- * that the kvno is N/A might be lost when converting between
- * EncryptionKey and KerberosKey.
- */
- private static boolean versionMatches(Integer v1, int v2) {
- if (v1 == null || v1 == 0 || v2 == 0) {
- return true;
- }
- return v1.equals(v2);
- }
-
- private static KerberosKey findKey(int etype, Integer version,
- KerberosKey[] keys) throws KrbException {
- int ktype;
- boolean etypeFound = false;
-
- // When no matched kvno is found, returns tke key of the same
- // etype with the highest kvno
- int kvno_found = 0;
- KerberosKey key_found = null;
-
- for (int i = 0; i < keys.length; i++) {
- ktype = keys[i].getKeyType();
- if (etype == ktype) {
- int kv = keys[i].getVersionNumber();
- etypeFound = true;
- if (versionMatches(version, kv)) {
- return keys[i];
- } else if (kv > kvno_found) {
- key_found = keys[i];
- kvno_found = kv;
- }
- }
- }
- // Key not found.
- // %%% kludge to allow DES keys to be used for diff etypes
- if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
- etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
- for (int i = 0; i < keys.length; i++) {
- ktype = keys[i].getKeyType();
- if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
- ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
- int kv = keys[i].getVersionNumber();
- etypeFound = true;
- if (versionMatches(version, kv)) {
- return new KerberosKey(keys[i].getPrincipal(),
- keys[i].getEncoded(),
- etype,
- kv);
- } else if (kv > kvno_found) {
- key_found = new KerberosKey(keys[i].getPrincipal(),
- keys[i].getEncoded(),
- etype,
- kv);
- kvno_found = kv;
- }
- }
- }
- }
- if (etypeFound) {
- return key_found;
- }
- return null;
- }
-}
--- a/jdk/src/java.security.jgss/share/classes/sun/security/ssl/krb5/KerberosPreMasterSecret.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,272 +0,0 @@
-/*
- * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl.krb5;
-
-import java.io.*;
-import java.security.*;
-import java.util.Arrays;
-
-import javax.net.ssl.*;
-
-import sun.security.krb5.EncryptionKey;
-import sun.security.krb5.EncryptedData;
-import sun.security.krb5.KrbException;
-import sun.security.krb5.internal.crypto.KeyUsage;
-
-import sun.security.ssl.Debug;
-import sun.security.ssl.HandshakeInStream;
-import sun.security.ssl.HandshakeMessage;
-import sun.security.ssl.ProtocolVersion;
-
-/**
- * This is the Kerberos premaster secret in the Kerberos client key
- * exchange message (CLIENT --> SERVER); it holds the
- * Kerberos-encrypted pre-master secret. The secret is encrypted using the
- * Kerberos session key. The padding and size of the resulting message
- * depends on the session key type, but the pre-master secret is
- * always exactly 48 bytes.
- *
- */
-final class KerberosPreMasterSecret {
-
- private ProtocolVersion protocolVersion; // preMaster [0,1]
- private byte preMaster[]; // 48 bytes
- private byte encrypted[];
-
- /**
- * Constructor used by client to generate premaster secret.
- *
- * Client randomly creates a pre-master secret and encrypts it
- * using the Kerberos session key; only the server can decrypt
- * it, using the session key available in the service ticket.
- *
- * @param protocolVersion used to set preMaster[0,1]
- * @param generator random number generator for generating premaster secret
- * @param sessionKey Kerberos session key for encrypting premaster secret
- */
- KerberosPreMasterSecret(ProtocolVersion protocolVersion,
- SecureRandom generator, EncryptionKey sessionKey) throws IOException {
-
- if (sessionKey.getEType() ==
- EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
- throw new IOException(
- "session keys with des3-cbc-hmac-sha1-kd encryption type " +
- "are not supported for TLS Kerberos cipher suites");
- }
-
- this.protocolVersion = protocolVersion;
- preMaster = generatePreMaster(generator, protocolVersion);
-
- // Encrypt premaster secret
- try {
- EncryptedData eData = new EncryptedData(sessionKey, preMaster,
- KeyUsage.KU_UNKNOWN);
- encrypted = eData.getBytes(); // not ASN.1 encoded.
-
- } catch (KrbException e) {
- throw (SSLKeyException)new SSLKeyException
- ("Kerberos premaster secret error").initCause(e);
- }
- }
-
- /*
- * Constructor used by server to decrypt encrypted premaster secret.
- * The protocol version in preMaster[0,1] must match either currentVersion
- * or clientVersion, otherwise, the premaster secret is set to
- * a random one to foil possible attack.
- *
- * @param currentVersion version of protocol being used
- * @param clientVersion version requested by client
- * @param generator random number generator used to generate
- * bogus premaster secret if premaster secret verification fails
- * @param input input stream from which to read the encrypted
- * premaster secret
- * @param sessionKey Kerberos session key to be used for decryption
- */
- KerberosPreMasterSecret(ProtocolVersion currentVersion,
- ProtocolVersion clientVersion,
- SecureRandom generator, HandshakeInStream input,
- EncryptionKey sessionKey) throws IOException {
-
- // Extract encrypted premaster secret from message
- encrypted = input.getBytes16();
-
- if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
- if (encrypted != null) {
- Debug.println(System.out,
- "encrypted premaster secret", encrypted);
- }
- }
-
- if (sessionKey.getEType() ==
- EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) {
- throw new IOException(
- "session keys with des3-cbc-hmac-sha1-kd encryption type " +
- "are not supported for TLS Kerberos cipher suites");
- }
-
- // Decrypt premaster secret
- try {
- EncryptedData data = new EncryptedData(sessionKey.getEType(),
- null /* optional kvno */, encrypted);
-
- byte[] temp = data.decrypt(sessionKey, KeyUsage.KU_UNKNOWN);
- if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
- if (encrypted != null) {
- Debug.println(System.out,
- "decrypted premaster secret", temp);
- }
- }
-
- // Remove padding bytes after decryption. Only DES and DES3 have
- // paddings and we don't support DES3 in TLS (see above)
-
- if (temp.length == 52 &&
- data.getEType() == EncryptedData.ETYPE_DES_CBC_CRC) {
- // For des-cbc-crc, 4 paddings. Value can be 0x04 or 0x00.
- if (paddingByteIs(temp, 52, (byte)4) ||
- paddingByteIs(temp, 52, (byte)0)) {
- temp = Arrays.copyOf(temp, 48);
- }
- } else if (temp.length == 56 &&
- data.getEType() == EncryptedData.ETYPE_DES_CBC_MD5) {
- // For des-cbc-md5, 8 paddings with 0x08, or no padding
- if (paddingByteIs(temp, 56, (byte)8)) {
- temp = Arrays.copyOf(temp, 48);
- }
- }
-
- preMaster = temp;
-
- protocolVersion = ProtocolVersion.valueOf(preMaster[0],
- preMaster[1]);
- if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
- System.out.println("Kerberos PreMasterSecret version: "
- + protocolVersion);
- }
- } catch (Exception e) {
- // catch exception & process below
- preMaster = null;
- protocolVersion = currentVersion;
- }
-
- // check if the premaster secret version is ok
- // the specification says that it must be the maximum version supported
- // by the client from its ClientHello message. However, many
- // old implementations send the negotiated version, so accept both
- // for SSL v3.0 and TLS v1.0.
- // NOTE that we may be comparing two unsupported version numbers in
- // the second case, which is why we cannot use object references
- // equality in this special case
- boolean versionMismatch = (protocolVersion.v != clientVersion.v);
-
- /*
- * we never checked the client_version in server side
- * for TLS v1.0 and SSL v3.0. For compatibility, we
- * maintain this behavior.
- */
- if (versionMismatch && (clientVersion.v <= 0x0301)) {
- versionMismatch = (protocolVersion.v != currentVersion.v);
- }
-
- /*
- * Bogus decrypted ClientKeyExchange? If so, conjure a
- * a random preMaster secret that will fail later during
- * Finished message processing. This is a countermeasure against
- * the "interactive RSA PKCS#1 encryption envelop attack" reported
- * in June 1998. Preserving the executation path will
- * mitigate timing attacks and force consistent error handling
- * that will prevent an attacking client from differentiating
- * different kinds of decrypted ClientKeyExchange bogosities.
- */
- if ((preMaster == null) || (preMaster.length != 48)
- || versionMismatch) {
- if (HandshakeMessage.debug != null && Debug.isOn("handshake")) {
- System.out.println("Kerberos PreMasterSecret error, "
- + "generating random secret");
- if (preMaster != null) {
- Debug.println(System.out, "Invalid secret", preMaster);
- }
- }
-
- /*
- * Randomize the preMaster secret with the
- * ClientHello.client_version, as will produce invalid master
- * secret to prevent the attacks.
- */
- preMaster = generatePreMaster(generator, clientVersion);
- protocolVersion = clientVersion;
- }
- }
-
- /**
- * Checks if all paddings of data are b
- * @param data the block with padding
- * @param len length of data, >= 48
- * @param b expected padding byte
- */
- private static boolean paddingByteIs(byte[] data, int len, byte b) {
- for (int i=48; i<len; i++) {
- if (data[i] != b) return false;
- }
- return true;
- }
-
- /*
- * Used by server to generate premaster secret in case of
- * problem decoding ticket.
- *
- * @param protocolVersion used for preMaster[0,1]
- * @param generator random number generator to use for generating secret.
- */
- KerberosPreMasterSecret(ProtocolVersion protocolVersion,
- SecureRandom generator) {
-
- this.protocolVersion = protocolVersion;
- preMaster = generatePreMaster(generator, protocolVersion);
- }
-
- private static byte[] generatePreMaster(SecureRandom rand,
- ProtocolVersion ver) {
-
- byte[] pm = new byte[48];
- rand.nextBytes(pm);
- pm[0] = ver.major;
- pm[1] = ver.minor;
-
- return pm;
- }
-
- // Clone not needed; internal use only
- byte[] getUnencrypted() {
- return preMaster;
- }
-
- // Clone not needed; internal use only
- byte[] getEncrypted() {
- return encrypted;
- }
-}
--- a/jdk/src/java.security.jgss/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java Mon Jun 01 10:15:21 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package sun.security.ssl.krb5;
-
-import java.security.AccessControlContext;
-import java.security.Permission;
-import java.security.Principal;
-import java.util.Set;
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.kerberos.KerberosKey;
-import javax.security.auth.kerberos.KeyTab;
-import javax.security.auth.kerberos.ServicePermission;
-import javax.security.auth.login.LoginException;
-
-import sun.security.jgss.GSSCaller;
-import sun.security.jgss.krb5.Krb5Util;
-import sun.security.jgss.krb5.ServiceCreds;
-import sun.security.krb5.PrincipalName;
-import sun.security.ssl.Krb5Proxy;
-
-/**
- * An implementation of Krb5Proxy that simply delegates to the appropriate
- * Kerberos APIs.
- */
-public class Krb5ProxyImpl implements Krb5Proxy {
-
- public Krb5ProxyImpl() { }
-
- @Override
- public Subject getClientSubject(AccessControlContext acc)
- throws LoginException {
- return Krb5Util.getSubject(GSSCaller.CALLER_SSL_CLIENT, acc);
- }
-
- @Override
- public Subject getServerSubject(AccessControlContext acc)
- throws LoginException {
- return Krb5Util.getSubject(GSSCaller.CALLER_SSL_SERVER, acc);
- }
-
- @Override
- public Object getServiceCreds(AccessControlContext acc)
- throws LoginException {
- ServiceCreds serviceCreds =
- Krb5Util.getServiceCreds(GSSCaller.CALLER_SSL_SERVER, null, acc);
- return serviceCreds;
- }
-
- @Override
- public String getServerPrincipalName(Object serviceCreds) {
- return ((ServiceCreds)serviceCreds).getName();
- }
-
- @Override
- public String getPrincipalHostName(Principal principal) {
- if (principal == null) {
- return null;
- }
- String hostName = null;
- try {
- PrincipalName princName =
- new PrincipalName(principal.getName(),
- PrincipalName.KRB_NT_SRV_HST);
- String[] nameParts = princName.getNameStrings();
- if (nameParts.length >= 2) {
- hostName = nameParts[1];
- }
- } catch (Exception e) {
- // ignore
- }
- return hostName;
- }
-
-
- @Override
- public Permission getServicePermission(String principalName,
- String action) {
- return new ServicePermission(principalName, action);
- }
-
- @Override
- public boolean isRelated(Subject subject, Principal princ) {
- if (princ == null) return false;
- Set<Principal> principals =
- subject.getPrincipals(Principal.class);
- if (principals.contains(princ)) {
- // bound to this principal
- return true;
- }
- for (KeyTab pc: subject.getPrivateCredentials(KeyTab.class)) {
- if (!pc.isBound()) {
- return true;
- }
- }
- return false;
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/CipherSuite.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA
+ * @run main/othervm CipherSuite TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+ * @run main/othervm CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA256
+ * @run main/othervm CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
+ * @run main/othervm CipherSuite TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
+ * @run main/othervm CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA
+ * @run main/othervm CipherSuite TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
+ * @run main/othervm CipherSuite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_RSA_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
+ * @run main/othervm CipherSuite TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
+ */
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test common DTLS cipher suites.
+ */
+public class CipherSuite extends DTLSOverDatagram {
+
+ // use the specific cipher suite
+ volatile static String cipherSuite;
+
+ public static void main(String[] args) throws Exception {
+ cipherSuite = args[0];
+
+ CipherSuite testCase = new CipherSuite();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ SSLEngine createSSLEngine(boolean isClient) throws Exception {
+ SSLEngine engine = super.createSSLEngine(isClient);
+
+ if (isClient) {
+ engine.setEnabledCipherSuites(new String[]{cipherSuite});
+ }
+
+ return engine;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/ClientAuth.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm ClientAuth
+ */
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test DTLS client authentication.
+ */
+public class ClientAuth extends DTLSOverDatagram {
+
+ public static void main(String[] args) throws Exception {
+ ClientAuth testCase = new ClientAuth();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ SSLEngine createSSLEngine(boolean isClient) throws Exception {
+ SSLEngine engine = super.createSSLEngine(isClient);
+
+ if (!isClient) {
+ engine.setNeedClientAuth(true);
+ }
+
+ return engine;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/DTLSOverDatagram.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @run main/othervm DTLSOverDatagram
+ */
+
+import java.io.*;
+import java.nio.*;
+import java.net.*;
+import java.util.*;
+import java.security.*;
+import java.security.cert.*;
+import javax.net.ssl.*;
+import java.util.concurrent.*;
+
+import sun.misc.HexDumpEncoder;
+
+/**
+ * An example to show the way to use SSLEngine in datagram connections.
+ */
+public class DTLSOverDatagram {
+ private static int MAX_HANDSHAKE_LOOPS = 60;
+ private static int MAX_APP_READ_LOOPS = 10;
+
+ /*
+ * The following is to set up the keystores.
+ */
+ private static String pathToStores = "../etc";
+ private static String keyStoreFile = "keystore";
+ private static String trustStoreFile = "truststore";
+ private static String passwd = "passphrase";
+
+ private static String keyFilename =
+ System.getProperty("test.src", ".") + "/" + pathToStores +
+ "/" + keyStoreFile;
+ private static String trustFilename =
+ System.getProperty("test.src", ".") + "/" + pathToStores +
+ "/" + trustStoreFile;
+ private static Exception clientException = null;
+ private static Exception serverException = null;
+
+ private static ByteBuffer serverApp =
+ ByteBuffer.wrap("Hi Client, I'm Server".getBytes());
+ private static ByteBuffer clientApp =
+ ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
+
+ /*
+ * =============================================================
+ * The test case
+ */
+ public static void main(String[] args) throws Exception {
+ DTLSOverDatagram testCase = new DTLSOverDatagram();
+ testCase.runTest(testCase);
+ }
+
+ /*
+ * Define the server side of the test.
+ */
+ void doServerSide() throws Exception {
+ DatagramSocket socket = serverDatagramSocket;
+ socket.setSoTimeout(10000); // 10 second
+
+ // create SSLEngine
+ SSLEngine engine = createSSLEngine(false);
+
+ // handshaking
+ handshake(engine, socket, clientSocketAddr);
+
+ // read client application data
+ receiveAppData(engine, socket, clientApp);
+
+ // write server application data
+ deliverAppData(engine, socket, serverApp, clientSocketAddr);
+
+ socket.close();
+ }
+
+ /*
+ * Define the client side of the test.
+ */
+ void doClientSide() throws Exception {
+ DatagramSocket socket = clientDatagramSocket;
+ socket.setSoTimeout(1000); // 1 second read timeout
+
+ // create SSLEngine
+ SSLEngine engine = createSSLEngine(true);
+
+ // handshaking
+ handshake(engine, socket, serverSocketAddr);
+
+ // write client application data
+ deliverAppData(engine, socket, clientApp, serverSocketAddr);
+
+ // read server application data
+ receiveAppData(engine, socket, serverApp);
+ }
+
+ /*
+ * =============================================================
+ * The remainder is support stuff for DTLS operations.
+ */
+ SSLEngine createSSLEngine(boolean isClient) throws Exception {
+ SSLContext context = getDTLSContext();
+ SSLEngine engine = context.createSSLEngine();
+
+ SSLParameters paras = engine.getSSLParameters();
+ paras.setMaximumPacketSize(1024);
+
+ engine.setUseClientMode(isClient);
+ engine.setSSLParameters(paras);
+
+ return engine;
+ }
+
+ // handshake
+ void handshake(SSLEngine engine, DatagramSocket socket,
+ SocketAddress peerAddr) throws Exception {
+
+ boolean endLoops = false;
+ int loops = MAX_HANDSHAKE_LOOPS;
+ engine.beginHandshake();
+ while (!endLoops &&
+ (serverException == null) && (clientException == null)) {
+
+ if (--loops < 0) {
+ throw new RuntimeException(
+ "Too much loops to produce handshake packets");
+ }
+
+ SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+ if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
+ hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
+
+ ByteBuffer iNet;
+ ByteBuffer iApp;
+ if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
+ // receive ClientHello request and other SSL/TLS records
+ byte[] buf = new byte[1024];
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ try {
+ socket.receive(packet);
+ } catch (SocketTimeoutException ste) {
+ List<DatagramPacket> packets =
+ onReceiveTimeout(engine, peerAddr);
+ for (DatagramPacket p : packets) {
+ socket.send(p);
+ }
+
+ continue;
+ }
+
+ iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
+ iApp = ByteBuffer.allocate(1024);
+ } else {
+ iNet = ByteBuffer.allocate(0);
+ iApp = ByteBuffer.allocate(1024);
+ }
+
+ SSLEngineResult r = engine.unwrap(iNet, iApp);
+ SSLEngineResult.Status rs = r.getStatus();
+ hs = r.getHandshakeStatus();
+ if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+ // the client maximum fragment size config does not work?
+ throw new Exception("Buffer overflow: " +
+ "incorrect client maximum fragment size");
+ } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+ // bad packet, or the client maximum fragment size
+ // config does not work?
+ if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ throw new Exception("Buffer underflow: " +
+ "incorrect client maximum fragment size");
+ } // otherwise, ignore this packet
+ } else if (rs == SSLEngineResult.Status.CLOSED) {
+ endLoops = true;
+ } // otherwise, SSLEngineResult.Status.OK:
+
+ if (rs != SSLEngineResult.Status.OK) {
+ continue;
+ }
+ } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
+ List<DatagramPacket> packets =
+ produceHandshakePackets(engine, peerAddr);
+ for (DatagramPacket p : packets) {
+ socket.send(p);
+ }
+ } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+ runDelegatedTasks(engine);
+ } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ // OK, time to do application data exchange.
+ endLoops = true;
+ } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
+ endLoops = true;
+ }
+ }
+
+ SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+ if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ throw new Exception("Not ready for application data yet");
+ }
+ }
+
+ // deliver application data
+ void deliverAppData(SSLEngine engine, DatagramSocket socket,
+ ByteBuffer appData, SocketAddress peerAddr) throws Exception {
+
+ // Note: have not consider the packet loses
+ List<DatagramPacket> packets =
+ produceApplicationPackets(engine, appData, peerAddr);
+ appData.flip();
+ for (DatagramPacket p : packets) {
+ socket.send(p);
+ }
+ }
+
+ // receive application data
+ void receiveAppData(SSLEngine engine,
+ DatagramSocket socket, ByteBuffer expectedApp) throws Exception {
+
+ int loops = MAX_APP_READ_LOOPS;
+ while ((serverException == null) && (clientException == null)) {
+ if (--loops < 0) {
+ throw new RuntimeException(
+ "Too much loops to receive application data");
+ }
+
+ byte[] buf = new byte[1024];
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ socket.receive(packet);
+ ByteBuffer netBuffer = ByteBuffer.wrap(buf, 0, packet.getLength());
+ ByteBuffer recBuffer = ByteBuffer.allocate(1024);
+ SSLEngineResult rs = engine.unwrap(netBuffer, recBuffer);
+ recBuffer.flip();
+ if (recBuffer.remaining() != 0) {
+ printHex("Received application data", recBuffer);
+ if (!recBuffer.equals(expectedApp)) {
+ System.out.println("Engine status is " + rs);
+ throw new Exception("Not the right application data");
+ }
+ break;
+ }
+ }
+ }
+
+ // produce handshake packets
+ List<DatagramPacket> produceHandshakePackets(
+ SSLEngine engine, SocketAddress socketAddr) throws Exception {
+
+ List<DatagramPacket> packets = new ArrayList<>();
+ boolean endLoops = false;
+ int loops = MAX_HANDSHAKE_LOOPS;
+ while (!endLoops &&
+ (serverException == null) && (clientException == null)) {
+
+ if (--loops < 0) {
+ throw new RuntimeException(
+ "Too much loops to produce handshake packets");
+ }
+
+ ByteBuffer oNet = ByteBuffer.allocate(32768);
+ ByteBuffer oApp = ByteBuffer.allocate(0);
+ SSLEngineResult r = engine.wrap(oApp, oNet);
+ oNet.flip();
+
+ SSLEngineResult.Status rs = r.getStatus();
+ SSLEngineResult.HandshakeStatus hs = r.getHandshakeStatus();
+ if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+ // the client maximum fragment size config does not work?
+ throw new Exception("Buffer overflow: " +
+ "incorrect server maximum fragment size");
+ } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+ // bad packet, or the client maximum fragment size
+ // config does not work?
+ if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ throw new Exception("Buffer underflow: " +
+ "incorrect server maximum fragment size");
+ } // otherwise, ignore this packet
+ } else if (rs == SSLEngineResult.Status.CLOSED) {
+ throw new Exception("SSLEngine has closed");
+ } // otherwise, SSLEngineResult.Status.OK
+
+ // SSLEngineResult.Status.OK:
+ if (oNet.hasRemaining()) {
+ byte[] ba = new byte[oNet.remaining()];
+ oNet.get(ba);
+ DatagramPacket packet = createHandshakePacket(ba, socketAddr);
+ packets.add(packet);
+ }
+ boolean endInnerLoop = false;
+ SSLEngineResult.HandshakeStatus nhs = hs;
+ while (!endInnerLoop) {
+ if (nhs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+ runDelegatedTasks(engine);
+ nhs = engine.getHandshakeStatus();
+ } else if ((nhs == SSLEngineResult.HandshakeStatus.FINISHED) ||
+ (nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) ||
+ (nhs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)) {
+
+ endInnerLoop = true;
+ endLoops = true;
+ } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
+ endInnerLoop = true;
+ }
+ }
+ }
+
+ return packets;
+ }
+
+ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
+ return new DatagramPacket(ba, ba.length, socketAddr);
+ }
+
+ // produce application packets
+ List<DatagramPacket> produceApplicationPackets(
+ SSLEngine engine, ByteBuffer source,
+ SocketAddress socketAddr) throws Exception {
+
+ List<DatagramPacket> packets = new ArrayList<>();
+ ByteBuffer appNet = ByteBuffer.allocate(32768);
+ SSLEngineResult r = engine.wrap(source, appNet);
+ appNet.flip();
+
+ SSLEngineResult.Status rs = r.getStatus();
+ if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+ // the client maximum fragment size config does not work?
+ throw new Exception("Buffer overflow: " +
+ "incorrect server maximum fragment size");
+ } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+ // unlikely
+ throw new Exception("Buffer underflow during wraping");
+ } else if (rs == SSLEngineResult.Status.CLOSED) {
+ throw new Exception("SSLEngine has closed");
+ } // otherwise, SSLEngineResult.Status.OK
+
+ // SSLEngineResult.Status.OK:
+ if (appNet.hasRemaining()) {
+ byte[] ba = new byte[appNet.remaining()];
+ appNet.get(ba);
+ DatagramPacket packet =
+ new DatagramPacket(ba, ba.length, socketAddr);
+ packets.add(packet);
+ }
+
+ return packets;
+ }
+
+ // run delegated tasks
+ void runDelegatedTasks(SSLEngine engine) throws Exception {
+ Runnable runnable;
+ while ((runnable = engine.getDelegatedTask()) != null) {
+ runnable.run();
+ }
+
+ SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+ if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+ throw new Exception("handshake shouldn't need additional tasks");
+ }
+ }
+
+ // retransmission if timeout
+ List<DatagramPacket> onReceiveTimeout(
+ SSLEngine engine, SocketAddress socketAddr) throws Exception {
+
+ SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+ if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
+ return new ArrayList<DatagramPacket>();
+ } else {
+ // retransmission of handshake messages
+ return produceHandshakePackets(engine, socketAddr);
+ }
+ }
+
+ // get DTSL context
+ SSLContext getDTLSContext() throws Exception {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ KeyStore ts = KeyStore.getInstance("JKS");
+
+ char[] passphrase = "passphrase".toCharArray();
+
+ ks.load(new FileInputStream(keyFilename), passphrase);
+ ts.load(new FileInputStream(trustFilename), passphrase);
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, passphrase);
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ts);
+
+ SSLContext sslCtx = SSLContext.getInstance("DTLS");
+
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ return sslCtx;
+ }
+
+
+ /*
+ * =============================================================
+ * The remainder is support stuff to kickstart the testing.
+ */
+
+ // the server side SocketAddress
+ volatile static SocketAddress serverSocketAddr = null;
+
+ // the client side SocketAddress
+ volatile static SocketAddress clientSocketAddr = null;
+
+ // the server side DatagramSocket instance
+ volatile static DatagramSocket serverDatagramSocket = null;
+
+ // the server side DatagramSocket instance
+ volatile static DatagramSocket clientDatagramSocket = null;
+
+ // get server side SocketAddress object
+ public SocketAddress getServerSocketAddress() {
+ return serverSocketAddr;
+ }
+
+ // get client side SocketAddress object
+ public SocketAddress getClientSocketAddress() {
+ return clientSocketAddr;
+ }
+
+ // get server side DatagramSocket object
+ public DatagramSocket getServerDatagramSocket() {
+ return serverDatagramSocket;
+ }
+
+ // get client side DatagramSocket object
+ public DatagramSocket getClientDatagramSocket() {
+ return clientDatagramSocket;
+ }
+
+ // Will the handshaking and application data exchange succeed?
+ public boolean isGoodJob() {
+ return true;
+ }
+
+ public final void runTest(DTLSOverDatagram testCase) throws Exception {
+ serverDatagramSocket = new DatagramSocket();
+ serverSocketAddr = new InetSocketAddress(
+ InetAddress.getLocalHost(), serverDatagramSocket.getLocalPort());
+
+ clientDatagramSocket = new DatagramSocket();
+ clientSocketAddr = new InetSocketAddress(
+ InetAddress.getLocalHost(), clientDatagramSocket.getLocalPort());
+
+ ExecutorService pool = Executors.newFixedThreadPool(2);
+ List<Future<String>> list = new ArrayList<Future<String>>();
+
+ try {
+ list.add(pool.submit(new ServerCallable(testCase))); // server task
+ list.add(pool.submit(new ClientCallable(testCase))); // client task
+ } finally {
+ pool.shutdown();
+ }
+
+ Exception reserved = null;
+ for (Future<String> fut : list) {
+ try {
+ System.out.println(fut.get());
+ } catch (CancellationException |
+ InterruptedException | ExecutionException cie) {
+ if (reserved != null) {
+ cie.addSuppressed(reserved);
+ reserved = cie;
+ } else {
+ reserved = cie;
+ }
+ }
+ }
+
+ if (reserved != null) {
+ throw reserved;
+ }
+ }
+
+ final static class ServerCallable implements Callable<String> {
+ DTLSOverDatagram testCase;
+
+ ServerCallable(DTLSOverDatagram testCase) {
+ this.testCase = testCase;
+ }
+
+ @Override
+ public String call() throws Exception {
+ try {
+ testCase.doServerSide();
+ } catch (Exception e) {
+ e.printStackTrace(System.out);
+ serverException = e;
+
+ if (testCase.isGoodJob()) {
+ throw e;
+ } else {
+ return "Well done, server!";
+ }
+ } finally {
+ if (serverDatagramSocket != null) {
+ serverDatagramSocket.close();
+ }
+ }
+
+ if (testCase.isGoodJob()) {
+ return "Well done, server!";
+ } else {
+ throw new Exception("No expected exception");
+ }
+ }
+ }
+
+ final static class ClientCallable implements Callable<String> {
+ DTLSOverDatagram testCase;
+
+ ClientCallable(DTLSOverDatagram testCase) {
+ this.testCase = testCase;
+ }
+
+ @Override
+ public String call() throws Exception {
+ try {
+ testCase.doClientSide();
+ } catch (Exception e) {
+ e.printStackTrace(System.out);
+ clientException = e;
+ if (testCase.isGoodJob()) {
+ throw e;
+ } else {
+ return "Well done, client!";
+ }
+ } finally {
+ if (clientDatagramSocket != null) {
+ clientDatagramSocket.close();
+ }
+ }
+
+ if (testCase.isGoodJob()) {
+ return "Well done, client!";
+ } else {
+ throw new Exception("No expected exception");
+ }
+ }
+ }
+
+ final static void printHex(String prefix, ByteBuffer bb) {
+ HexDumpEncoder dump = new HexDumpEncoder();
+
+ synchronized (System.out) {
+ System.out.println(prefix);
+ try {
+ dump.encodeBuffer(bb.slice(), System.out);
+ } catch (Exception e) {
+ // ignore
+ }
+ System.out.flush();
+ }
+ }
+
+ final static void printHex(String prefix,
+ byte[] bytes, int offset, int length) {
+
+ HexDumpEncoder dump = new HexDumpEncoder();
+
+ synchronized (System.out) {
+ System.out.println(prefix);
+ try {
+ ByteBuffer bb = ByteBuffer.wrap(bytes, offset, length);
+ dump.encodeBuffer(bb, System.out);
+ } catch (Exception e) {
+ // ignore
+ }
+ System.out.flush();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/InvalidCookie.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm InvalidCookie
+ */
+
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+
+/**
+ * Test that if the handshake cookie in client side is incorrect, the handshake
+ * process can continue as if the client does not use cookie.
+ */
+public class InvalidCookie extends DTLSOverDatagram {
+ boolean needInvalidCookie = true;
+
+ public static void main(String[] args) throws Exception {
+ InvalidCookie testCase = new InvalidCookie();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
+ if (needInvalidCookie && (ba.length >= 60) &&
+ (ba[0] == (byte)0x16) && (ba[13] == (byte)0x03)) {
+ // HelloVerifyRequest
+ needInvalidCookie = false;
+ System.out.println("invalidate handshake verify cookie");
+ if (ba[ba.length - 1] == (byte)0xFF) {
+ ba[ba.length - 1] = (byte)0xFE;
+ } else {
+ ba[ba.length - 1] = (byte)0xFF;
+ }
+ }
+
+ return super.createHandshakePacket(ba, socketAddr);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/InvalidRecords.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm InvalidRecords
+ */
+
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+
+/**
+ * Test that if handshake messages are crasged, the handshake would fail
+ * because of handshaking hash verification.
+ */
+public class InvalidRecords extends DTLSOverDatagram {
+ boolean needInvalidRecords = true;
+
+ public static void main(String[] args) throws Exception {
+ InvalidRecords testCase = new InvalidRecords();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ public boolean isGoodJob() {
+ return false;
+ }
+
+ @Override
+ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
+ if (needInvalidRecords && (ba.length >= 60) &&
+ (ba[0x00] == (byte)0x16) && (ba[0x0D] == (byte)0x01) &&
+ (ba[0x3B] == (byte)0x00) && (ba[0x3C] > 0)) {
+
+ // ba[0x00]: record type
+ // ba[0x0D]: handshake type
+ // ba[0x3B]: length of session ID
+ // ba[0x3C]: length of cookie
+
+ // ClientHello with cookie
+ needInvalidRecords = false;
+ System.out.println("invalidate ClientHello message");
+ if (ba[ba.length - 1] == (byte)0xFF) {
+ ba[ba.length - 1] = (byte)0xFE;
+ } else {
+ ba[ba.length - 1] = (byte)0xFF;
+ }
+ }
+
+ return super.createHandshakePacket(ba, socketAddr);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/NoMacInitialClientHello.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm NoMacInitialClientHello
+ */
+
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+
+/**
+ * Test that a server is able to discard invalid initial ClientHello silently.
+ */
+public class NoMacInitialClientHello extends DTLSOverDatagram {
+ boolean needInvalidRecords = true;
+
+ public static void main(String[] args) throws Exception {
+ NoMacInitialClientHello testCase = new NoMacInitialClientHello();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
+ if (needInvalidRecords && (ba.length >= 60) &&
+ (ba[0] == (byte)0x16) && (ba[13] == (byte)0x01)) { // ClientHello
+
+ needInvalidRecords = false;
+ System.out.println("invalidate ClientHello message");
+ if (ba[ba.length - 1] == (byte)0xFF) {
+ ba[ba.length - 1] = (byte)0xFE;
+ } else {
+ ba[ba.length - 1] = (byte)0xFF;
+ }
+ }
+
+ return super.createHandshakePacket(ba, socketAddr);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/Reordered.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm Reordered
+ */
+
+import java.util.List;
+import java.util.Collections;
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test that DTLS implementation is able to reorder data traffic.
+ */
+public class Reordered extends DTLSOverDatagram {
+ boolean needPacketReorder = true;
+
+ public static void main(String[] args) throws Exception {
+ Reordered testCase = new Reordered();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ List<DatagramPacket> produceHandshakePackets(
+ SSLEngine engine, SocketAddress socketAddr) throws Exception {
+ List<DatagramPacket> packets =
+ super.produceHandshakePackets(engine, socketAddr);
+
+ if (needPacketReorder && (!engine.getUseClientMode())) {
+ needPacketReorder = false;
+ Collections.reverse(packets);
+ }
+
+ return packets;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/Retransmission.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm Retransmission
+ */
+
+import java.util.List;
+import java.util.ArrayList;
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test that DTLS implementation is able to do retransmission internally
+ * automatically if packet get lost.
+ */
+public class Retransmission extends DTLSOverDatagram {
+ boolean needPacketLoss = true;
+
+ public static void main(String[] args) throws Exception {
+ Retransmission testCase = new Retransmission();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ List<DatagramPacket> produceHandshakePackets(
+ SSLEngine engine, SocketAddress socketAddr) throws Exception {
+ List<DatagramPacket> packets =
+ super.produceHandshakePackets(engine, socketAddr);
+
+ if (!needPacketLoss || (!engine.getUseClientMode())) {
+ return packets;
+ }
+
+ List<DatagramPacket> parts = new ArrayList<>();
+ int lostSeq = 2;
+ for (DatagramPacket packet : packets) {
+ lostSeq--;
+ if (lostSeq == 0) {
+ needPacketLoss = false;
+ // loss this packet
+ System.out.println("Loss a packet");
+ continue;
+ }
+
+ parts.add(packet);
+ }
+
+ return parts;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/net/ssl/DTLS/WeakCipherSuite.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8043758
+ * @summary Datagram Transport Layer Security (DTLS)
+ * @compile DTLSOverDatagram.java
+ * @run main/othervm WeakCipherSuite TLS_DH_anon_WITH_AES_128_GCM_SHA256
+ * @run main/othervm WeakCipherSuite SSL_DH_anon_WITH_DES_CBC_SHA
+ * @run main/othervm WeakCipherSuite SSL_RSA_WITH_DES_CBC_SHA
+ * @run main/othervm WeakCipherSuite SSL_DHE_RSA_WITH_DES_CBC_SHA
+ * @run main/othervm WeakCipherSuite SSL_DHE_DSS_WITH_DES_CBC_SHA
+ */
+
+import javax.net.ssl.SSLEngine;
+import java.security.Security;
+
+/**
+ * Test common DTLS weak cipher suites.
+ */
+public class WeakCipherSuite extends DTLSOverDatagram {
+
+ // use the specific cipher suite
+ volatile static String cipherSuite;
+
+ public static void main(String[] args) throws Exception {
+ // reset security properties to make sure that the algorithms
+ // and keys used in this test are not disabled.
+ Security.setProperty("jdk.tls.disabledAlgorithms", "");
+ Security.setProperty("jdk.certpath.disabledAlgorithms", "");
+
+ cipherSuite = args[0];
+
+ WeakCipherSuite testCase = new WeakCipherSuite();
+ testCase.runTest(testCase);
+ }
+
+ @Override
+ SSLEngine createSSLEngine(boolean isClient) throws Exception {
+ SSLEngine engine = super.createSSLEngine(isClient);
+ engine.setEnabledCipherSuites(new String[]{cipherSuite});
+
+ return engine;
+ }
+}
--- a/jdk/test/javax/net/ssl/SSLEngine/CheckStatus.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/SSLEngine/CheckStatus.java Tue Jun 02 09:15:47 2015 -0700
@@ -391,7 +391,7 @@
appOut2.rewind();
log("======================================");
- log("Client Cert");
+ log("Client Cert and Key Exchange");
result1 = ssle1.wrap(appOut1, oneToTwo);
checkResult(appOut1, oneToTwo, result1,
Status.OK, HandshakeStatus.NEED_WRAP, 0, -1);
@@ -406,22 +406,6 @@
oneToTwo.compact();
log("======================================");
- log("Key Exchange");
- result1 = ssle1.wrap(appOut1, oneToTwo);
- checkResult(appOut1, oneToTwo, result1,
- Status.OK, HandshakeStatus.NEED_WRAP, 0, -1);
-
- oneToTwo.flip();
- result2 = ssle2.unwrap(oneToTwo, appIn2);
-
- checkResult(oneToTwo, appIn2, result2,
- Status.OK, HandshakeStatus.NEED_TASK,
- result1.bytesProduced(), 0);
- runDelegatedTasks(ssle2);
-
- oneToTwo.compact();
-
- log("======================================");
log("CCS");
result1 = ssle1.wrap(appOut1, oneToTwo);
checkResult(appOut1, oneToTwo, result1,
--- a/jdk/test/javax/net/ssl/SSLEngine/LargeBufs.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/SSLEngine/LargeBufs.java Tue Jun 02 09:15:47 2015 -0700
@@ -116,7 +116,7 @@
if ((result2.bytesConsumed() != 0) &&
(result2.bytesConsumed() != appBufferMax) &&
(result2.bytesConsumed() != 2 * OFFSET)) {
- throw new Exception("result1: " + result1);
+ throw new Exception("result2: " + result2);
}
log("wrap1: " + result1);
--- a/jdk/test/javax/net/ssl/TLS/CipherTestUtils.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/TLS/CipherTestUtils.java Tue Jun 02 09:15:47 2015 -0700
@@ -406,6 +406,7 @@
return params;
}).forEach((params) -> {
try {
+ System.out.println("Testing " + params);
runTest(params);
System.out.println("Passed " + params);
} catch (Exception e) {
--- a/jdk/test/javax/net/ssl/TLSv11/ExportableBlockCipher.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/TLSv11/ExportableBlockCipher.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,15 +23,16 @@
* questions.
*/
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
/*
* @test
* @bug 4873188
* @summary Support TLS 1.1
* @run main/othervm ExportableBlockCipher
- *
- * SunJSSE does not support dynamic system properties, no way to re-use
- * system properties in samevm/agentvm mode.
- *
* @author Xuelei Fan
*/
@@ -109,7 +110,7 @@
sslIS.read();
sslOS.write('A');
sslOS.flush();
- } catch (SSLException ssle) {
+ } catch (IOException ioe) {
// get the expected exception
interrupted = true;
} finally {
--- a/jdk/test/javax/net/ssl/TLSv11/ExportableStreamCipher.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/TLSv11/ExportableStreamCipher.java Tue Jun 02 09:15:47 2015 -0700
@@ -23,15 +23,16 @@
* questions.
*/
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
/*
* @test
* @bug 4873188
* @summary Support TLS 1.1
* @run main/othervm ExportableStreamCipher
- *
- * SunJSSE does not support dynamic system properties, no way to re-use
- * system properties in samevm/agentvm mode.
- *
* @author Xuelei Fan
*/
@@ -109,7 +110,7 @@
sslIS.read();
sslOS.write('A');
sslOS.flush();
- } catch (SSLException ssle) {
+ } catch (IOException ioe) {
// get the expected exception
interrupted = true;
} finally {
--- a/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java Tue Jun 02 09:15:47 2015 -0700
@@ -139,6 +139,11 @@
* Main entry point for this test.
*/
public static void main(String args[]) throws Exception {
+ // reset security properties to make sure that the algorithms
+ // and keys used in this test are not disabled.
+ Security.setProperty("jdk.tls.disabledAlgorithms", "");
+ Security.setProperty("jdk.certpath.disabledAlgorithms", "");
+
if (debug) {
System.setProperty("javax.net.debug", "all");
}
@@ -153,7 +158,12 @@
*/
SSLSocketSSLEngineTemplate test =
new SSLSocketSSLEngineTemplate(protocol);
+ log("-------------------------------------");
+ log("Testing " + protocol + " for direct buffers ...");
test.runTest(true);
+
+ log("---------------------------------------");
+ log("Testing " + protocol + " for indirect buffers ...");
test.runTest(false);
}
@@ -329,6 +339,10 @@
thread.join();
}
+ if (sslSocket != null) {
+ sslSocket.close();
+ }
+
if (serverException != null) {
if (clientException != null) {
serverException.initCause(clientException);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/SSLwithPerms.java Tue Jun 02 09:15:47 2015 -0700
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8038089
+ * @summary TLS optional support for Kerberos cipher suites needs to be re-examined
+ * @library ../../../../java/security/testlibrary/
+ * @run main/othervm SSLwithPerms
+ */
+import java.io.*;
+import javax.net.ssl.*;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.kerberos.ServicePermission;
+import java.net.SocketPermission;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.Principal;
+import java.security.Security;
+import java.security.SecurityPermission;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.PropertyPermission;
+
+import sun.security.jgss.GSSUtil;
+
+public class SSLwithPerms {
+
+ static String KRB5_CONF = "krb5.conf";
+ static String JAAS_CONF = "jaas.conf";
+ static String REALM = "REALM";
+ static String KTAB = "ktab";
+ static String HOST = "host." + REALM.toLowerCase(Locale.US);
+ static String SERVER = "host/" + HOST;
+ static String USER = "user";
+ static char[] PASS = "password".toCharArray();
+
+ public static void main(String[] args) throws Exception {
+
+ Security.setProperty("jdk.tls.disabledAlgorithms", "");
+ if (args.length == 0) {
+ KDC kdc = KDC.create(REALM, HOST, 0, true);
+
+ kdc.addPrincipal(USER, PASS);
+ kdc.addPrincipalRandKey("krbtgt/" + REALM);
+ kdc.addPrincipalRandKey(SERVER);
+ KDC.saveConfig(KRB5_CONF, kdc);
+ kdc.writeKtab(KTAB);
+
+ File f = new File(JAAS_CONF);
+ FileOutputStream fos = new FileOutputStream(f);
+ fos.write((
+ "ssl {\n" +
+ " com.sun.security.auth.module.Krb5LoginModule required\n" +
+ " principal=\"" + SERVER + "\"\n" +
+ " useKeyTab=true\n" +
+ " keyTab=" + KTAB + "\n" +
+ " isInitiator=false\n" +
+ " storeKey=true;\n};\n"
+ ).getBytes());
+ fos.close();
+
+ Proc pc = Proc.create("SSLwithPerms")
+ .args("client")
+ .inheritIO()
+ .prop("java.security.manager", "")
+ .prop("java.security.krb5.conf", KRB5_CONF)
+ .prop("sun.net.spi.nameservice.provider.1", "ns,mock")
+ .prop("javax.net.ssl", "handshake")
+ .prop("sun.security.krb5.debug", "true")
+ .perm(new SecurityPermission("setProperty.jdk.tls.disabledAlgorithms"))
+ .perm(new PropertyPermission("sun.security.krb5.principal", "read"))
+ .perm(new FilePermission("port", "read"))
+ .perm(new FilePermission(KTAB, "read"))
+ .perm(new RuntimePermission("accessClassInPackage.sun.net.spi.nameservice"))
+ .perm(new AuthPermission("modifyPrincipals"))
+ .perm(new AuthPermission("modifyPrivateCredentials"))
+ .perm(new AuthPermission("doAs"))
+ .perm(new SocketPermission("127.0.0.1", "connect"))
+ .perm(new ServicePermission("host/host.realm@REALM", "initiate"))
+ .start();
+
+ Proc ps = Proc.create("SSLwithPerms")
+ .args("server")
+ .inheritIO()
+ .prop("java.security.manager", "")
+ .prop("java.security.krb5.conf", KRB5_CONF)
+ .prop("java.security.auth.login.config", JAAS_CONF)
+ .prop("javax.net.ssl", "handshake")
+ .prop("sun.security.krb5.debug", "true")
+ .perm(new SecurityPermission("setProperty.jdk.tls.disabledAlgorithms"))
+ .perm(new AuthPermission("createLoginContext.ssl"))
+ .perm(new AuthPermission("doAs"))
+ .perm(new FilePermission("port", "write"))
+ .perm(new SocketPermission("127.0.0.1", "accept"))
+ .perm(new ServicePermission("host/host.realm@REALM", "accept"))
+ .start();
+
+ if (pc.waitFor() != 0) {
+ throw new Exception();
+ }
+ if (ps.waitFor() != 0) {
+ throw new Exception();
+ }
+ } else if (args[0].equals("client")) {
+ Context c;
+ c = Context.fromUserPass(USER, PASS, false);
+ c.doAs(new JsseClientAction(), null);
+ } else if (args[0].equals("server")) {
+ final Context s = Context.fromJAAS("ssl");
+ s.doAs(new JsseServerAction(), null);
+ }
+ }
+
+ private static class JsseClientAction implements Action {
+ public byte[] run(Context s, byte[] input) throws Exception {
+ SSLSocketFactory sslsf =
+ (SSLSocketFactory) SSLSocketFactory.getDefault();
+ while (!Files.exists(Paths.get("port"))) {
+ Thread.sleep(100);
+ }
+ int port = ByteBuffer.allocate(4)
+ .put(Files.readAllBytes(Paths.get("port"))).getInt(0);
+ System.out.println("Connecting " + SERVER + ":" + port);
+ SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(HOST, port);
+
+ // Enable only a KRB5 cipher suite.
+ String enabledSuites[] = {"TLS_KRB5_WITH_RC4_128_SHA"};
+ sslSocket.setEnabledCipherSuites(enabledSuites);
+
+ SSLParameters params = sslSocket.getSSLParameters();
+ params.setServerNames(Collections.singletonList(new SNIHostName(HOST)));
+ sslSocket.setSSLParameters(params);
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(
+ sslSocket.getInputStream()));
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
+ sslSocket.getOutputStream()));
+
+ String outStr = "Hello There!\n";
+ out.write(outStr);
+ out.flush();
+ System.out.print("Sending " + outStr);
+
+ String inStr = in.readLine();
+ System.out.println("Received " + inStr);
+
+ String cipherSuiteChosen = sslSocket.getSession().getCipherSuite();
+ System.out.println("Cipher suite in use: " + cipherSuiteChosen);
+ Principal self = sslSocket.getSession().getLocalPrincipal();
+ System.out.println("I am: " + self.toString());
+ Principal peer = sslSocket.getSession().getPeerPrincipal();
+ System.out.println("Server is: " + peer.toString());
+
+ sslSocket.close();
+ return null;
+ }
+ }
+
+ private static class JsseServerAction implements Action {
+ public byte[] run(Context s, byte[] input) throws Exception {
+ SSLServerSocketFactory sslssf =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ SSLServerSocket sslServerSocket =
+ (SSLServerSocket) sslssf.createServerSocket(0); // any port
+ int port = sslServerSocket.getLocalPort();
+ System.out.println("Listening on " + port);
+
+ String enabledSuites[] = {"TLS_KRB5_WITH_RC4_128_SHA"};
+ sslServerSocket.setEnabledCipherSuites(enabledSuites);
+
+ Files.write(Paths.get("port"), ByteBuffer.allocate(4).putInt(port).array());
+ System.out.println("Waiting for incoming connection...");
+
+ SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
+
+ System.out.println("Got connection from client "
+ + sslSocket.getInetAddress());
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(
+ sslSocket.getInputStream()));
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
+ sslSocket.getOutputStream()));
+
+ String inStr = in.readLine();
+ System.out.println("Received " + inStr);
+
+ String outStr = inStr + " " + new Date().toString() + "\n";
+ out.write(outStr);
+ System.out.println("Sending " + outStr);
+ out.flush();
+
+ String cipherSuiteChosen =
+ sslSocket.getSession().getCipherSuite();
+ System.out.println("Cipher suite in use: " + cipherSuiteChosen);
+ Principal self = sslSocket.getSession().getLocalPrincipal();
+ System.out.println("I am: " + self.toString());
+ Principal peer = sslSocket.getSession().getPeerPrincipal();
+ System.out.println("Client is: " + peer.toString());
+
+ sslSocket.close();
+ return null;
+ }
+ }
+}
--- a/jdk/test/sun/security/ssl/AppInputStream/ReadHandshake.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/sun/security/ssl/AppInputStream/ReadHandshake.java Tue Jun 02 09:15:47 2015 -0700
@@ -21,19 +21,22 @@
* questions.
*/
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
/*
* @test
* @bug 4514971
* @summary Verify applications do not read handshake data after failure
* @run main/othervm ReadHandshake
- *
- * SunJSSE does not support dynamic system properties, no way to re-use
- * system properties in samevm/agentvm mode.
*/
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
+import java.security.Security;
public class ReadHandshake {
@@ -219,6 +222,10 @@
volatile Exception clientException = null;
public static void main(String[] args) throws Exception {
+ // reset security properties to make sure that the algorithms
+ // and keys used in this test are not disabled.
+ Security.setProperty("jdk.tls.disabledAlgorithms", "");
+ Security.setProperty("jdk.certpath.disabledAlgorithms", "");
if (debug)
System.setProperty("javax.net.debug", "all");
--- a/jdk/test/sun/security/ssl/ClientHandshaker/LengthCheckTest.java Mon Jun 01 10:15:21 2015 -0700
+++ b/jdk/test/sun/security/ssl/ClientHandshaker/LengthCheckTest.java Tue Jun 02 09:15:47 2015 -0700
@@ -233,7 +233,7 @@
// sent back to the server.
if (gotException == false ||
!isTlsMessage(cTOs, TLS_RECTYPE_ALERT, TLS_ALERT_LVL_FATAL,
- TLS_ALERT_INTERNAL_ERROR)) {
+ TLS_ALERT_UNEXPECTED_MSG)) {
throw new SSLException(
"Client failed to throw Alert:fatal:internal_error");
}
@@ -285,7 +285,7 @@
// sent back to the client.
if (gotException == false ||
!isTlsMessage(sTOc, TLS_RECTYPE_ALERT, TLS_ALERT_LVL_FATAL,
- TLS_ALERT_INTERNAL_ERROR)) {
+ TLS_ALERT_UNEXPECTED_MSG)) {
throw new SSLException(
"Server failed to throw Alert:fatal:internal_error");
}