diff options
Diffstat (limited to 'gnu')
28 files changed, 1361 insertions, 477 deletions
diff --git a/gnu/javax/net/ssl/AbstractSessionContext.java b/gnu/javax/net/ssl/AbstractSessionContext.java index 590d49a7f..916fec089 100644 --- a/gnu/javax/net/ssl/AbstractSessionContext.java +++ b/gnu/javax/net/ssl/AbstractSessionContext.java @@ -105,7 +105,7 @@ public abstract class AbstractSessionContext implements SSLSessionContext * @return The new session context. * @throws SSLException If an error occurs in creating the instance. */ - public static SSLSessionContext newInstance () throws SSLException + public static AbstractSessionContext newInstance () throws SSLException { try { @@ -173,7 +173,8 @@ public abstract class AbstractSessionContext implements SSLSessionContext public final SSLSession getSession (byte[] sessionId) { Session s = implGet (sessionId); - if (System.currentTimeMillis () - s.getLastAccessedTime () > timeout) + if (s != null + && System.currentTimeMillis () - s.getLastAccessedTime () > timeout) { remove (sessionId); return null; diff --git a/gnu/javax/net/ssl/Session.java b/gnu/javax/net/ssl/Session.java index e5e2700f6..e2b21aa1e 100644 --- a/gnu/javax/net/ssl/Session.java +++ b/gnu/javax/net/ssl/Session.java @@ -68,8 +68,6 @@ public abstract class Session implements SSLSession, Serializable protected long lastAccessedTime; protected int applicationBufferSize; - // Default to 2^14 + 5 -- the maximum size for a record. - protected int packetBufferSize = 16389; protected ID sessionId; protected Certificate[] localCerts; protected Certificate[] peerCerts; @@ -87,6 +85,7 @@ public abstract class Session implements SSLSession, Serializable { creationTime = System.currentTimeMillis(); values = new HashMap<String, Object>(); + applicationBufferSize = (1 << 14); } public void access() @@ -143,7 +142,7 @@ public abstract class Session implements SSLSession, Serializable public int getPacketBufferSize() { - return packetBufferSize; + return applicationBufferSize + 2048; } public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException diff --git a/gnu/javax/net/ssl/provider/AbstractHandshake.java b/gnu/javax/net/ssl/provider/AbstractHandshake.java index 336d26160..6166bbde2 100644 --- a/gnu/javax/net/ssl/provider/AbstractHandshake.java +++ b/gnu/javax/net/ssl/provider/AbstractHandshake.java @@ -51,6 +51,7 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Arrays; import java.util.HashMap; import java.util.logging.Logger; @@ -104,18 +105,36 @@ public abstract class AbstractHandshake }; /** - * SSL 3.0 + * SSL 3.0; the string "CLNT" */ private static final byte[] SENDER_CLIENT = new byte[] { 0x43, 0x4C, 0x4E, 0x54 }; /** - * SSL 3.0 + * SSL 3.0; the string "SRVR" */ private static final byte[] SENDER_SERVER = new byte[] { 0x53, 0x52, 0x56, 0x52 }; /** + * SSL 3.0; the value 0x36 40 (for SHA-1 hashes) or 48 (for MD5 hashes) + * times. + */ + private static final byte[] PAD1 = new byte[48]; + + /** + * SSL 3.0; the value 0x5c 40 (for SHA-1 hashes) or 48 (for MD5 hashes) + * times. + */ + private static final byte[] PAD2 = new byte[48]; + + static + { + Arrays.fill(PAD1, SSLHMac.PAD1); + Arrays.fill(PAD2, SSLHMac.PAD2); + } + + /** * The currently-read handshake messages. There may be zero, or * multiple, handshake messages in this buffer. */ @@ -149,10 +168,55 @@ public abstract class AbstractHandshake * output or temporary storage. * @return An {@link SSLEngineResult} describing the result. */ - public abstract SSLEngineResult.HandshakeStatus handleInput (ByteBuffer fragment) - throws SSLException; + public final HandshakeStatus handleInput (ByteBuffer fragment) + throws SSLException + { + HandshakeStatus status = status(); + if (status != HandshakeStatus.NEED_UNWRAP) + return status; + + // Try to read another... + if (!pollHandshake(fragment)) + return HandshakeStatus.NEED_UNWRAP; + + while (hasMessage() && status == HandshakeStatus.NEED_UNWRAP) + { + int pos = handshakeOffset; + status = implHandleInput(); + int len = handshakeOffset - pos; + if (len == 0) + { + // Don't bother; the impl is just telling us to go around + // again. + continue; + } + if (doHash()) + { + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "hashing output\n{0}", + Util.hexDump((ByteBuffer) handshakeBuffer + .duplicate().position(pos) + .limit(pos+len), " >> ")); + sha.update((ByteBuffer) handshakeBuffer.duplicate() + .position(pos).limit(pos+len)); + md5.update((ByteBuffer) handshakeBuffer.duplicate() + .position(pos).limit(pos+len)); + } + } + return status; + } /** + * Called to process more handshake data. This method will be called + * repeatedly while there is remaining handshake data, and while the + * status is + * @return + * @throws SSLException + */ + protected abstract HandshakeStatus implHandleInput() + throws SSLException; + + /** * Produce more handshake output. This is called in response to a * call to {@link javax.net.ssl.SSLEngine#wrap}, when the handshake * is still in progress. @@ -166,20 +230,27 @@ public abstract class AbstractHandshake public final SSLEngineResult.HandshakeStatus handleOutput (ByteBuffer fragment) throws SSLException { + int orig = fragment.position(); SSLEngineResult.HandshakeStatus status = implHandleOutput(fragment); if (doHash()) { - sha.update((ByteBuffer) fragment.duplicate().flip()); - md5.update((ByteBuffer) fragment.duplicate().flip()); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "hashing output:\n{0}", + Util.hexDump((ByteBuffer) fragment.duplicate().flip().position(orig), " >> ")); + sha.update((ByteBuffer) fragment.duplicate().flip().position(orig)); + md5.update((ByteBuffer) fragment.duplicate().flip().position(orig)); } return status; } /** - * Called to implement the underlying - * @param record - * @return - * @throws SSLException + * Called to implement the underlying output handling. The callee should + * attempt to fill the given buffer as much as it can; this can include + * multiple, and even partial, handshake messages. + * + * @param fragment The buffer the callee should write handshake messages to. + * @return The new status of the handshake. + * @throws SSLException If an error occurs processing the output message. */ protected abstract SSLEngineResult.HandshakeStatus implHandleOutput (ByteBuffer fragment) throws SSLException; @@ -208,6 +279,23 @@ public abstract class AbstractHandshake abstract OutputSecurityParameters getOutputParams() throws SSLException; /** + * Used by the skeletal code to query the current status of the handshake. + * This <em>should</em> be the same value as returned by the previous call + * to {@link #implHandleOutput(ByteBuffer)} or {@link + * #implHandleInput(ByteBuffer)}. + * + * @return The current handshake status. + */ + abstract SSLEngineResult.HandshakeStatus status(); + + /** + * Handle an SSLv2 client hello. This is only used by SSL servers. + * + * @param hello The hello message. + */ + abstract void handleV2Hello(ByteBuffer hello) throws SSLException; + + /** * Attempt to read the next handshake message from the given * record. If only a partial handshake message is available, then * this method saves the incoming bytes and returns false. If a @@ -221,29 +309,28 @@ public abstract class AbstractHandshake */ protected boolean pollHandshake (final ByteBuffer fragment) { - Record record = new Record(fragment); // Allocate space for the new fragment. - if (handshakeBuffer == null || handshakeBuffer.remaining () < record.length ()) + if (handshakeBuffer == null + || handshakeBuffer.remaining() < fragment.remaining()) { // We need space for anything still unread in the handshake // buffer... int len = ((handshakeBuffer == null) ? 0 - : handshakeBuffer.position () - handshakeOffset); + : handshakeBuffer.position() - handshakeOffset); // Plus room for the incoming record. len += fragment.remaining(); - reallocateBuffer (len); + reallocateBuffer(len); } + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "inserting {0} into {1}", + fragment, handshakeBuffer); + // Put the fragment into the buffer. - if (doHash()) - { - sha.update(fragment); - md5.update(fragment); - } - handshakeBuffer.put (fragment); + handshakeBuffer.put(fragment); - return hasMessage (); + return hasMessage(); } protected boolean doHash() @@ -255,15 +342,23 @@ public abstract class AbstractHandshake * Tell if the handshake buffer currently has a full handshake * message. */ - protected boolean hasMessage () + protected boolean hasMessage() { if (handshakeBuffer == null) return false; - ByteBuffer tmp = handshakeBuffer.duplicate (); - tmp.flip (); - tmp.position (handshakeOffset); - Handshake handshake = new Handshake (tmp); - return (handshake.length () >= tmp.remaining ()); + ByteBuffer tmp = handshakeBuffer.duplicate(); + tmp.flip(); + tmp.position(handshakeOffset); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "current buffer: {0}; test buffer {1}", + handshakeBuffer, tmp); + if (tmp.remaining() < 4) + return false; + Handshake handshake = new Handshake(tmp.slice()); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "handshake len:{0} remaining:{1}", + handshake.length(), tmp.remaining()); + return (handshake.length() <= tmp.remaining() - 4); } /** @@ -273,9 +368,23 @@ public abstract class AbstractHandshake */ private void reallocateBuffer (final int totalLen) { - int len = handshakeBuffer == null ? 0 : handshakeBuffer.capacity (); + int len = handshakeBuffer == null ? -1 + : handshakeBuffer.capacity() - (handshakeBuffer.limit() - handshakeOffset); if (len >= totalLen) - return; // Big enough; no need to reallocate. + { + // Big enough; no need to reallocate; but maybe shift the contents + // down. + if (handshakeOffset > 0) + { + ByteBuffer tmp = handshakeBuffer.duplicate(); + tmp.flip(); + tmp.position(handshakeOffset); + handshakeBuffer.position(0); + handshakeBuffer.put(tmp); + handshakeOffset = 0; + } + return; + } // Start at 1K (probably the system's page size). Double the size // from there. @@ -288,8 +397,8 @@ public abstract class AbstractHandshake if (handshakeBuffer != null) { handshakeBuffer.flip (); - handshakeBuffer.position (handshakeOffset); - newBuf.put (handshakeBuffer); + handshakeBuffer.position(handshakeOffset); + newBuf.put(handshakeBuffer); } handshakeBuffer = newBuf; @@ -319,7 +428,7 @@ Certificate.signature.sha_hash * @param session The current session being negotiated. * @return The computed to-be-signed value. */ - protected byte[] genV2CertificateVerify(MessageDigest md5, + protected byte[] genV3CertificateVerify(MessageDigest md5, MessageDigest sha, SessionImpl session) { @@ -386,9 +495,9 @@ Certificate.signature.sha_hash { byte[] seed = new byte[clientRandom.length() + serverRandom.length()]; - clientRandom.buffer().get(seed, 0, clientRandom.length()); - serverRandom.buffer().get(seed, clientRandom.length(), - serverRandom.length()); + serverRandom.buffer().get(seed, 0, serverRandom.length()); + clientRandom.buffer().get(seed, serverRandom.length(), + clientRandom.length()); prf = new SSLRandom(); HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2); attr.put(SSLRandom.SECRET, session.privateData.masterSecret); @@ -401,11 +510,11 @@ Certificate.signature.sha_hash + clientRandom.length() + serverRandom.length()]; System.arraycopy(KEY_EXPANSION, 0, seed, 0, KEY_EXPANSION.length); - clientRandom.buffer().get(seed, KEY_EXPANSION.length, - clientRandom.length()); - serverRandom.buffer().get(seed, (KEY_EXPANSION.length - + clientRandom.length()), + serverRandom.buffer().get(seed, KEY_EXPANSION.length, serverRandom.length()); + clientRandom.buffer().get(seed, (KEY_EXPANSION.length + + serverRandom.length()), + clientRandom.length()); prf = new TLSRandom(); HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2); @@ -420,11 +529,8 @@ Certificate.signature.sha_hash prf.nextBytes(keys[1], 0, keys[1].length); prf.nextBytes(keys[2], 0, keys[2].length); prf.nextBytes(keys[3], 0, keys[3].length); - if (session.version.compareTo(ProtocolVersion.TLS_1_1) < 0) - { - prf.nextBytes(keys[4], 0, keys[4].length); - prf.nextBytes(keys[5], 0, keys[5].length); - } + prf.nextBytes(keys[4], 0, keys[4].length); + prf.nextBytes(keys[5], 0, keys[5].length); } catch (LimitReachedException lre) { @@ -432,6 +538,16 @@ Certificate.signature.sha_hash throw new Error(lre); } + if (Debug.DEBUG_KEY_EXCHANGE) + logger.logv(Component.SSL_KEY_EXCHANGE, + "keys generated;\n [0]: {0}\n [1]: {1}\n [2]: {2}\n" + + " [3]: {3}\n [4]: {4}\n [5]: {5}", + Util.toHexString(keys[0], ':'), + Util.toHexString(keys[1], ':'), + Util.toHexString(keys[2], ':'), + Util.toHexString(keys[3], ':'), + Util.toHexString(keys[4], ':'), + Util.toHexString(keys[5], ':')); return keys; } @@ -459,6 +575,10 @@ Certificate.signature.sha_hash TLSRandom prf = new TLSRandom(); byte[] md5val = md5.digest(); byte[] shaval = sha.digest(); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "finished md5:{0} sha:{1}", + Util.toHexString(md5val, ':'), + Util.toHexString(shaval, ':')); byte[] seed = new byte[CLIENT_FINISHED.length + md5val.length + shaval.length]; @@ -503,23 +623,23 @@ Certificate.signature.sha_hash md5.update(isClient ? SENDER_CLIENT : SENDER_SERVER); md5.update(session.privateData.masterSecret); - md5.update(SSLHMac.PAD1); + md5.update(PAD1); byte[] tmp = md5.digest(); md5.reset(); md5.update(session.privateData.masterSecret); - md5.update(SSLHMac.PAD2); + md5.update(PAD2); md5.update(tmp); finishedBuffer.put(md5.digest()); sha.update(isClient ? SENDER_CLIENT : SENDER_SERVER); sha.update(session.privateData.masterSecret); - sha.update(SSLHMac.PAD1); + sha.update(PAD1, 0, 40); tmp = sha.digest(); sha.reset(); sha.update(session.privateData.masterSecret); - sha.update(SSLHMac.PAD2); + sha.update(PAD2, 0, 40); sha.update(tmp); finishedBuffer.put(sha.digest()).position(0); } @@ -574,6 +694,10 @@ Certificate.signature.sha_hash SessionImpl session) throws SSLException { + if (Debug.DEBUG_KEY_EXCHANGE) + logger.logv(Component.SSL_KEY_EXCHANGE, "preMasterSecret:\n{0}", + new ByteArray(preMasterSecret)); + if (session.version == ProtocolVersion.SSL_3) { try @@ -623,12 +747,12 @@ Certificate.signature.sha_hash byte[] seed = new byte[clientRandom.length() + serverRandom.length() + MASTER_SECRET.length]; - clientRandom.buffer().get(seed, 0, clientRandom.length()); - serverRandom.buffer().get(seed, clientRandom.length(), + System.arraycopy(MASTER_SECRET, 0, seed, 0, MASTER_SECRET.length); + clientRandom.buffer().get(seed, MASTER_SECRET.length, + clientRandom.length()); + serverRandom.buffer().get(seed, + MASTER_SECRET.length + clientRandom.length(), serverRandom.length()); - System.arraycopy(MASTER_SECRET, 0, seed, - clientRandom.length() + serverRandom.length(), - MASTER_SECRET.length); TLSRandom prf = new TLSRandom(); HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2); attr.put(TLSRandom.SECRET, preMasterSecret); @@ -639,7 +763,7 @@ Certificate.signature.sha_hash prf.nextBytes(session.privateData.masterSecret, 0, 48); } - if (Configuration.DEBUG) + if (Debug.DEBUG_KEY_EXCHANGE) logger.log(Component.SSL_KEY_EXCHANGE, "master_secret: {0}", new ByteArray(session.privateData.masterSecret)); diff --git a/gnu/javax/net/ssl/provider/CipherSuite.java b/gnu/javax/net/ssl/provider/CipherSuite.java index d3e89bd50..f3fbdcbc2 100644 --- a/gnu/javax/net/ssl/provider/CipherSuite.java +++ b/gnu/javax/net/ssl/provider/CipherSuite.java @@ -560,7 +560,7 @@ public final class CipherSuite implements Constructed if (macAlgorithm == MacAlgorithm.MD5) macAlg = "HMac-MD5"; if (macAlgorithm == MacAlgorithm.SHA) - macAlg = "HMac-SHA-1"; + macAlg = "HMac-SHA1"; } GetSecurityPropertyAction gspa = diff --git a/gnu/javax/net/ssl/provider/ClientHello.java b/gnu/javax/net/ssl/provider/ClientHello.java index 2b05f8ea5..9592bb8ff 100644 --- a/gnu/javax/net/ssl/provider/ClientHello.java +++ b/gnu/javax/net/ssl/provider/ClientHello.java @@ -48,6 +48,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Iterator; @@ -71,7 +72,7 @@ struct } ClientHello; </pre> */ -public final class ClientHello implements Handshake.Body +public class ClientHello implements Handshake.Body { // Fields. @@ -79,22 +80,20 @@ public final class ClientHello implements Handshake.Body // To help track offsets into the message: // The location of the 'random' field. - private static final int RANDOM_OFFSET = 2; + protected static final int RANDOM_OFFSET = 2; // The location of the sesion_id length. - private static final int SESSID_OFFSET = 32 + RANDOM_OFFSET; + protected static final int SESSID_OFFSET = 32 + RANDOM_OFFSET; // The location of the session_id bytes (if any). - private static final int SESSID_OFFSET2 = SESSID_OFFSET + 1; + protected static final int SESSID_OFFSET2 = SESSID_OFFSET + 1; - private final ByteBuffer buffer; - private int totalLength; + protected ByteBuffer buffer; // Constructor. // ------------------------------------------------------------------------- public ClientHello (final ByteBuffer buffer) { - this.buffer = buffer; - totalLength = buffer.limit (); + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } // Instance methods. @@ -102,7 +101,12 @@ public final class ClientHello implements Handshake.Body public int length () { - return totalLength; + int len = SESSID_OFFSET2 + buffer.get(SESSID_OFFSET); + len += (buffer.getShort(len) & 0xFFFF) + 2; + len += (buffer.get(len) & 0xFF) + 1; + if (hasExtensions()) + len += (buffer.get(len) & 0xFFFF) + 2; + return len; } /** @@ -177,45 +181,27 @@ public final class ClientHello implements Handshake.Body .limit (offset + len + 2)).slice (); return new ExtensionList (ebuf); } - - public void setVersion (final ProtocolVersion version) - { - buffer.putShort (0, (short) version.rawValue ()); - } - - public void setSessionId (final byte[] buffer) - { - setSessionId (buffer, 0, buffer.length); - } - - public void setSessionId (final byte[] buffer, final int offset, final int length) - { - int len = Math.min (32, length); - this.buffer.put (SESSID_OFFSET, (byte) len); - this.buffer.position (SESSID_OFFSET2); - this.buffer.put (buffer, offset, len); - } - - public void setExtensionsLength (final int length) + + public int extensionsLength() { - int offset = getExtensionsOffset(); - this.totalLength = offset + length + 4; - buffer.putShort(offset, (short) length); + if (hasExtensions()) + return 0; + return buffer.getShort(getExtensionsOffset()) & 0xFFFF; } - private int getCipherSuitesOffset () + protected int getCipherSuitesOffset () { return (SESSID_OFFSET2 + (buffer.get (SESSID_OFFSET) & 0xFF)); } - private int getCompressionMethodsOffset () + protected int getCompressionMethodsOffset () { int csOffset = getCipherSuitesOffset (); int csLen = buffer.getShort (csOffset) & 0xFFFF; return csOffset + csLen + 2; } - private int getExtensionsOffset () + protected int getExtensionsOffset () { int cmOffset = getCompressionMethodsOffset (); return (buffer.get (cmOffset) & 0xFF) + cmOffset + 1; diff --git a/gnu/javax/net/ssl/provider/ClientHelloBuilder.java b/gnu/javax/net/ssl/provider/ClientHelloBuilder.java new file mode 100644 index 000000000..e82ef48b1 --- /dev/null +++ b/gnu/javax/net/ssl/provider/ClientHelloBuilder.java @@ -0,0 +1,132 @@ +/* ClientHelloBuilder.java -- + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is a part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +USA + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.javax.net.ssl.provider; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Builder for {@link ClientHello} objects. + * + * @author Casey Marshall (csm@gnu.org) + */ +public class ClientHelloBuilder extends ClientHello implements Builder +{ + public ClientHelloBuilder() + { + super(ByteBuffer.allocate(256)); + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.Builder#buffer() + */ + public ByteBuffer buffer() + { + return (ByteBuffer) buffer.duplicate().position(0).limit(length()); + } + + public void setVersion(final ProtocolVersion version) + { + ensureCapacity(2); + buffer.putShort(0, (short) version.rawValue ()); + } + + public void setSessionId (final byte[] buffer) + { + setSessionId(buffer, 0, buffer.length); + } + + public void setSessionId (final byte[] buffer, final int offset, final int length) + { + ensureCapacity(SESSID_OFFSET2 + length); + int len = Math.min (32, length); + this.buffer.put (SESSID_OFFSET, (byte) len); + this.buffer.position (SESSID_OFFSET2); + this.buffer.put (buffer, offset, len); + } + + public void setCipherSuites(List<CipherSuite> suites) + { + int off = getCipherSuitesOffset(); + ensureCapacity(off + (2 * suites.size()) + 2); + buffer.putShort(off, (short) (suites.size() * 2)); + int i = 2; + for (CipherSuite suite : suites) + { + ((ByteBuffer) buffer.duplicate().position(off+i)).put(suite.id()); + i += 2; + } + } + + public void setCompressionMethods(List<CompressionMethod> methods) + { + int off = getCompressionMethodsOffset(); + ensureCapacity(off + methods.size() + 1); + buffer.put(off, (byte) methods.size()); + for (CompressionMethod method : methods) + buffer.put(++off, (byte) method.getValue()); + } + + public void setExtensionsLength (final int length) + { + if (length < 0 || length > 16384) + throw new IllegalArgumentException("length must be nonnegative and not exceed 16384"); + int needed = getExtensionsOffset() + 2 + length; + if (buffer.capacity() < needed) + ensureCapacity(needed); + buffer.putShort(getExtensionsOffset(), (short) length); + } + + public void setExtensions(ByteBuffer extensions) + { + extensions = (ByteBuffer) + extensions.duplicate().limit(extensions.position() + extensionsLength()); + ((ByteBuffer) buffer.duplicate().position(getExtensionsOffset() + 2)).put(extensions); + } + + public void ensureCapacity(final int length) + { + if (buffer.capacity() >= length) + return; + ByteBuffer newBuf = ByteBuffer.allocate(length); + newBuf.put((ByteBuffer) buffer.position(0)); + newBuf.position(0); + this.buffer = newBuf; + } +} diff --git a/gnu/javax/net/ssl/provider/ClientHelloV2.java b/gnu/javax/net/ssl/provider/ClientHelloV2.java index 11c46bf40..a514d9ad3 100644 --- a/gnu/javax/net/ssl/provider/ClientHelloV2.java +++ b/gnu/javax/net/ssl/provider/ClientHelloV2.java @@ -41,6 +41,7 @@ package gnu.javax.net.ssl.provider; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; @@ -71,7 +72,7 @@ class ClientHelloV2 implements Constructed ClientHelloV2 (final ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } public int length () @@ -99,7 +100,7 @@ class ClientHelloV2 implements Constructed return buffer.getShort (7) & 0xFFFF; } - List cipherSpecs () + public List<CipherSuite> cipherSpecs () { int n = cipherSpecsLength (); List<CipherSuite> l = new ArrayList<CipherSuite>(n / 3); diff --git a/gnu/javax/net/ssl/provider/Debug.java b/gnu/javax/net/ssl/provider/Debug.java new file mode 100644 index 000000000..8b56f2046 --- /dev/null +++ b/gnu/javax/net/ssl/provider/Debug.java @@ -0,0 +1,66 @@ +/* Debug.java -- Jessie debug constants. + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is a part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +USA + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.javax.net.ssl.provider; + +/** + * Debug constants for Jessie. + * + * @author Casey Marshall (csm@gnu.org) + */ +public final class Debug +{ + /** + * Set to true to dump out traces of SSL connections to the system + * logger. + */ + public static final boolean DEBUG = true; + + /** + * Set to true to dump out info about the SSL key exchange. Since this + * MAY contain sensitive data, it is a separate value. + */ + public static final boolean DEBUG_KEY_EXCHANGE = true; + + /** + * Set to true to turn on dumping of decrypted packets. Since this will + * log potentially-sensitive information (i.e., decrypted messages), only + * enable this in debug scenarios. + */ + public static final boolean DEBUG_DECRYPTION = true; +} diff --git a/gnu/javax/net/ssl/provider/Extension.java b/gnu/javax/net/ssl/provider/Extension.java index cd0285ed3..5442daa02 100644 --- a/gnu/javax/net/ssl/provider/Extension.java +++ b/gnu/javax/net/ssl/provider/Extension.java @@ -42,6 +42,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * An SSL hello extension. @@ -67,7 +68,7 @@ public final class Extension implements Constructed Extension(final ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } // Instance methods. @@ -75,7 +76,7 @@ public final class Extension implements Constructed public int length () { - return (buffer.getShort (2) & 0xFFFF); + return (buffer.getShort (2) & 0xFFFF) + 2; } public Type type() @@ -171,9 +172,10 @@ public final class Extension implements Constructed if (prefix != null) out.print (prefix); out.println(" type = " + type () + ";"); if (prefix != null) out.print (prefix); + String subprefix = " "; + if (prefix != null) subprefix = prefix + subprefix; out.println(" value ="); -// out.println(Util.hexDump(value (), (prefix != null) ? prefix + " " : " ")); - out.println(value()); + out.println(value().toString(subprefix)); if (prefix != null) out.print (prefix); out.print("} Extension;"); return str.toString(); diff --git a/gnu/javax/net/ssl/provider/ExtensionList.java b/gnu/javax/net/ssl/provider/ExtensionList.java index 686eae000..07c689ae8 100644 --- a/gnu/javax/net/ssl/provider/ExtensionList.java +++ b/gnu/javax/net/ssl/provider/ExtensionList.java @@ -3,6 +3,7 @@ package gnu.javax.net.ssl.provider; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.ListIterator; @@ -23,7 +24,7 @@ public class ExtensionList implements Iterable<Extension> public ExtensionList (ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); modCount = 0; } diff --git a/gnu/javax/net/ssl/provider/InputSecurityParameters.java b/gnu/javax/net/ssl/provider/InputSecurityParameters.java index 94f58760d..e1f56f052 100644 --- a/gnu/javax/net/ssl/provider/InputSecurityParameters.java +++ b/gnu/javax/net/ssl/provider/InputSecurityParameters.java @@ -38,12 +38,16 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; +import gnu.classpath.ByteArray; +import gnu.classpath.debug.Component; +import gnu.classpath.debug.SystemLogger; import gnu.java.io.ByteBufferOutputStream; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.logging.Level; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -57,20 +61,24 @@ import javax.net.ssl.SSLException; public class InputSecurityParameters { + private static final SystemLogger logger = SystemLogger.SYSTEM; private final Cipher cipher; private final Mac mac; private final Inflater inflater; private SessionImpl session; + private final CipherSuite suite; private long sequence; public InputSecurityParameters (final Cipher cipher, final Mac mac, final Inflater inflater, - final SessionImpl session) + final SessionImpl session, + final CipherSuite suite) { this.cipher = cipher; this.mac = mac; this.inflater = inflater; this.session = session; + this.suite = suite; sequence = 0; } @@ -127,29 +135,28 @@ public class InputSecurityParameters if (cipher != null) { ByteBuffer input = record.fragment(); - fragment = ByteBuffer.allocate (input.remaining()); - try - { - cipher.doFinal(input, fragment); - } - catch (BadPaddingException bpe) - { - // Should not happen, because we're doing the padding ourselves - // (the cipher itself should use NoPadding. - badPadding = true; - } + fragment = ByteBuffer.allocate(input.remaining()); + cipher.update(input, fragment); } else fragment = record.fragment(); + if (Debug.DEBUG_DECRYPTION) + logger.logv(Component.SSL_RECORD_LAYER, "decrypted fragment:\n{0}", + Util.hexDump((ByteBuffer) fragment.duplicate().position(0), " >> ")); + + int fragmentLength = record.length(); int maclen = 0; if (mac != null) maclen = mac.getMacLength(); + fragmentLength -= maclen; int padlen = 0; - if (!session.suite.isStreamCipher ()) + if (!suite.isStreamCipher ()) { padlen = fragment.get(record.length() - 1) & 0xFF; + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "padlen:{0}", padlen); if (record.version() == ProtocolVersion.SSL_3) { @@ -167,8 +174,22 @@ public class InputSecurityParameters for (int i = 0; i < pad.length; i++) if ((pad[i] & 0xFF) != padlen) badPadding = true; + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "TLSv1.x padding\n{0}", + new ByteArray(pad)); } + + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "padding bad? {0}", + badPadding); + if (!badPadding) + fragmentLength = fragmentLength - padlen - 1; } + + int ivlen = 0; + if (session.version.compareTo(ProtocolVersion.TLS_1_1) >= 0 + && !suite.isStreamCipher()) + ivlen = cipher.getBlockSize(); // Compute and check the MAC. if (mac != null) @@ -188,14 +209,18 @@ public class InputSecurityParameters mac.update((byte) version.major()); mac.update((byte) version.minor()); } - mac.update((byte) ((record.length() - maclen) >>> 8)); - mac.update((byte) (record.length() - maclen)); + mac.update((byte) ((fragmentLength - ivlen) >>> 8)); + mac.update((byte) (fragmentLength - ivlen)); ByteBuffer content = - (ByteBuffer) fragment.duplicate().limit(record.length() - maclen - padlen - 1); + (ByteBuffer) fragment.duplicate().position(ivlen).limit(fragmentLength); mac.update(content); byte[] mac1 = mac.doFinal (); byte[] mac2 = new byte[maclen]; - ((ByteBuffer) fragment.duplicate().position(record.length() - maclen - padlen - 1)).get(mac2); + mac.reset(); + ((ByteBuffer) fragment.duplicate().position(fragmentLength)).get(mac2); + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "mac1:{0} mac2:{1}", + Util.toHexString(mac1, ':'), Util.toHexString(mac2, ':')); if (!Arrays.equals (mac1, mac2)) badPadding = true; } @@ -210,11 +235,11 @@ public class InputSecurityParameters if (inflater != null) { ByteBufferOutputStream out = new ByteBufferOutputStream(record.length()); - byte[] inbuffer = new byte[4096]; - byte[] outbuffer = new byte[4096]; + byte[] inbuffer = new byte[1024]; + byte[] outbuffer = new byte[1024]; boolean done = false; if (record.version().compareTo(ProtocolVersion.TLS_1_1) >= 0 - && !session.suite.isStreamCipher()) + && !suite.isStreamCipher()) fragment.position (cipher.getBlockSize()); else fragment.position(0); @@ -237,7 +262,7 @@ public class InputSecurityParameters ByteBuffer outbuf = out.buffer(); if (outputStream != null) { - byte[] buf = new byte[4096]; + byte[] buf = new byte[1024]; while (outbuf.hasRemaining()) { int l = Math.min(outbuf.remaining(), buf.length); @@ -265,13 +290,13 @@ public class InputSecurityParameters else { ByteBuffer outbuf = (ByteBuffer) - fragment.duplicate().limit(record.length() - maclen - padlen - 1); + fragment.duplicate().position(0).limit(record.length() - maclen - padlen - 1); if (record.version().compareTo(ProtocolVersion.TLS_1_1) >= 0 - && !session.suite.isStreamCipher()) + && !suite.isStreamCipher()) outbuf.position(cipher.getBlockSize()); if (outputStream != null) { - byte[] buf = new byte[4096]; + byte[] buf = new byte[1024]; while (outbuf.hasRemaining()) { int l = Math.min(outbuf.remaining(), buf.length); @@ -303,6 +328,6 @@ public class InputSecurityParameters CipherSuite cipherSuite () { - return session.suite; + return suite; } } diff --git a/gnu/javax/net/ssl/provider/Jessie.java b/gnu/javax/net/ssl/provider/Jessie.java index fa7a44d92..5886b6c36 100644 --- a/gnu/javax/net/ssl/provider/Jessie.java +++ b/gnu/javax/net/ssl/provider/Jessie.java @@ -88,6 +88,10 @@ public class Jessie extends Provider put("Mac.SSLv3HMac-MD5", SSLv3HMacMD5Impl.class.getName()); put("Mac.SSLv3HMac-SHA", SSLv3HMacSHAImpl.class.getName()); + put("Signature.TLSv1.1-RSA", SSLRSASignatureImpl.class.getName()); + put("Alg.Alias.Signature.TLSv1-RSA", "TLSv1.1-RSA"); + put("Alg.Alias.Signature.SSLv3-RSA", "TLSv1.1-RSA"); + return null; } }); diff --git a/gnu/javax/net/ssl/provider/OutputSecurityParameters.java b/gnu/javax/net/ssl/provider/OutputSecurityParameters.java index d0fd13316..f940ec727 100644 --- a/gnu/javax/net/ssl/provider/OutputSecurityParameters.java +++ b/gnu/javax/net/ssl/provider/OutputSecurityParameters.java @@ -38,10 +38,14 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; +import gnu.classpath.ByteArray; +import gnu.classpath.debug.Component; +import gnu.classpath.debug.SystemLogger; import gnu.java.io.ByteBufferOutputStream; import java.nio.ByteBuffer; +import java.util.logging.Level; import java.util.zip.DataFormatException; import java.util.zip.Deflater; @@ -53,19 +57,23 @@ import javax.crypto.ShortBufferException; public class OutputSecurityParameters { + private static final SystemLogger logger = SystemLogger.SYSTEM; private final Cipher cipher; private final Mac mac; private final Deflater deflater; private final SessionImpl session; + private final CipherSuite suite; private long sequence; public OutputSecurityParameters (final Cipher cipher, final Mac mac, - final Deflater deflater, SessionImpl session) + final Deflater deflater, SessionImpl session, + CipherSuite suite) { this.cipher = cipher; this.mac = mac; this.deflater = deflater; this.session = session; + this.suite = suite; sequence = 0; } @@ -84,6 +92,11 @@ public class OutputSecurityParameters || length <= 0 || offset + length > input.length) throw new IndexOutOfBoundsException(); + if (Debug.DEBUG) + for (int i = offset; i < offset+length; i++) + logger.logv(Component.SSL_RECORD_LAYER, "encrypting record [{0}]: {1}", + i-offset, input[i]); + int maclen = 0; if (mac != null) maclen = session.isTruncatedMac() ? 10 : mac.getMacLength (); @@ -91,7 +104,7 @@ public class OutputSecurityParameters int ivlen = 0; byte[] iv = null; if (session.version.compareTo(ProtocolVersion.TLS_1_1) >= 0 - && !session.suite.isStreamCipher()) + && !suite.isStreamCipher()) { ivlen = cipher.getBlockSize(); iv = new byte[ivlen]; @@ -99,13 +112,13 @@ public class OutputSecurityParameters } int padaddlen = 0; - if (!session.suite.isStreamCipher() + if (!suite.isStreamCipher() && session.version.compareTo(ProtocolVersion.TLS_1) >= 0) { padaddlen = (session.random().nextInt(255 / cipher.getBlockSize()) * cipher.getBlockSize()); } - + int fragmentLength = 0; ByteBuffer[] fragments = null; // Compress the content, if needed. @@ -113,17 +126,22 @@ public class OutputSecurityParameters { ByteBufferOutputStream deflated = new ByteBufferOutputStream(); - byte[] inbuf = new byte[4096]; - byte[] outbuf = new byte[4096]; + byte[] inbuf = new byte[1024]; + byte[] outbuf = new byte[1024]; int written = 0; + + // Here we use the guarantee that the deflater won't increase the + // output size by more than 1K -- we resign ourselves to only deflate + // as much data as we have space for *uncompressed*, int limit = output.remaining() - (maclen + ivlen + padaddlen) - 1024; - for (int i = offset; i < length; i++) + for (int i = offset; i < length && written < limit; i++) { ByteBuffer in = input[i]; while (in.hasRemaining() && written < limit) { - int l = Math.min(in.remaining (), inbuf.length); + int l = Math.min(in.remaining(), inbuf.length); + l = Math.min(limit - written, l); in.get(inbuf, 0, l); deflater.setInput(inbuf, 0, l); l = deflater.deflate(outbuf); @@ -140,6 +158,7 @@ public class OutputSecurityParameters } fragments = new ByteBuffer[] { deflated.buffer() }; fragmentLength = ((int) deflater.getBytesWritten()) + maclen + ivlen; + deflater.reset(); offset = 0; length = 1; } @@ -154,20 +173,24 @@ public class OutputSecurityParameters } fragmentLength += maclen + ivlen; } - - // XXX Compute padding... + + // Compute padding... int padlen = 0; byte[] pad = null; - if (!session.suite.isStreamCipher()) + if (!suite.isStreamCipher()) { int bs = cipher.getBlockSize(); - padlen = bs - ((fragmentLength + bs - 1) % bs); + padlen = bs - (fragmentLength % bs); + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, + "framentLen:{0} padlen:{1} blocksize:{2}", + fragmentLength, padlen, bs); if (session.version.compareTo(ProtocolVersion.TLS_1) >= 0) { // TLS 1.0 and later uses a random amount of padding, up to // 255 bytes. Each byte of the pad is equal to the padding // length, minus one. - padlen += session.random().nextInt(256 / bs) * bs; + padlen += padaddlen; while (padlen > 255) padlen -= bs; pad = new byte[padlen]; @@ -182,6 +205,7 @@ public class OutputSecurityParameters session.random().nextBytes(pad); pad[padlen - 1] = (byte) (padlen - 1); } + fragmentLength += pad.length; } // If there is a MAC, compute it. @@ -202,25 +226,17 @@ public class OutputSecurityParameters mac.update((byte) session.version.major ()); mac.update((byte) session.version.minor ()); } - mac.update((byte) (fragmentLength >>> 8)); - mac.update((byte) fragmentLength); - if (iv != null) - { - mac.update(iv); - } int toWrite = fragmentLength - maclen - ivlen - padlen; + mac.update((byte) (toWrite >>> 8)); + mac.update((byte) toWrite); int written = 0; for (int i = offset; i < length && written < toWrite; i++) { - ByteBuffer fragment = fragments[i].slice(); + ByteBuffer fragment = fragments[i].duplicate(); int l = Math.min(fragment.remaining(), toWrite - written); fragment.limit(fragment.position() + l); mac.update(fragment); } - if (pad != null) - { - mac.update(pad); - } macValue = mac.doFinal(); } @@ -266,19 +282,25 @@ public class OutputSecurityParameters int toWrite = fragmentLength - maclen; for (int i = offset; i < offset + length && consumed < toWrite; i++) { - ByteBuffer fragment = fragments[i].slice(); + ByteBuffer fragment = fragments[i]; int l = Math.min(fragment.remaining(), toWrite - consumed); fragment.limit(fragment.position() + l); outfragment.put(fragment); - fragments[i].position(fragments[i].position() + l); consumed += l; } if (macValue != null) outfragment.put(macValue); } + // Advance the output buffer's position. + output.position(output.position() + outrecord.length() + 5); sequence++; return new int[] { consumed, fragmentLength + 5 }; } + + CipherSuite suite() + { + return suite; + } }
\ No newline at end of file diff --git a/gnu/javax/net/ssl/provider/ProtocolVersion.java b/gnu/javax/net/ssl/provider/ProtocolVersion.java index e96367acc..ca62054a8 100644 --- a/gnu/javax/net/ssl/provider/ProtocolVersion.java +++ b/gnu/javax/net/ssl/provider/ProtocolVersion.java @@ -81,7 +81,8 @@ public final class ProtocolVersion return SSL_3; if (name.equalsIgnoreCase ("TLSv1")) return TLS_1; - // TLSv1.1 not really supported yet. + if (name.equalsIgnoreCase("TLSv1.1")) + return TLS_1_1; throw new IllegalArgumentException ("unknown protocol name: " + name); } diff --git a/gnu/javax/net/ssl/provider/Random.java b/gnu/javax/net/ssl/provider/Random.java index 516ebbd29..e68159309 100644 --- a/gnu/javax/net/ssl/provider/Random.java +++ b/gnu/javax/net/ssl/provider/Random.java @@ -79,7 +79,7 @@ public class Random implements Builder, Constructed public Random copy() { ByteBuffer buffer = ByteBuffer.allocate(32); - buffer.put(this.buffer); + buffer.put((ByteBuffer) this.buffer.duplicate().position(0)); return new Random(buffer); } diff --git a/gnu/javax/net/ssl/provider/Record.java b/gnu/javax/net/ssl/provider/Record.java index a6307d20e..6f5a23ef4 100644 --- a/gnu/javax/net/ssl/provider/Record.java +++ b/gnu/javax/net/ssl/provider/Record.java @@ -41,6 +41,7 @@ package gnu.javax.net.ssl.provider; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * A SSL/TLS record structure. An SSL record is defined to be: @@ -61,7 +62,7 @@ public class Record public Record (final ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } // XXX remove @@ -114,6 +115,8 @@ public class Record */ public int length () { + // XXX this is different behavior than we usually want: we return the + // length field, not the total length. We should consider changing this. return buffer.getShort (3) & 0xFFFF; } @@ -183,6 +186,9 @@ public class Record out.print (" version: "); out.print (version ()); out.println (";"); + out.print(" length: "); + out.print(length()); + out.println(";"); out.println (" fragment {"); out.print (Util.hexDump (fragment (), " ")); out.println (" };"); diff --git a/gnu/javax/net/ssl/provider/SSLContextImpl.java b/gnu/javax/net/ssl/provider/SSLContextImpl.java index be75cb028..22fcbd7d8 100644 --- a/gnu/javax/net/ssl/provider/SSLContextImpl.java +++ b/gnu/javax/net/ssl/provider/SSLContextImpl.java @@ -74,8 +74,8 @@ import javax.net.ssl.X509TrustManager; public final class SSLContextImpl extends SSLContextSpi { - private SSLSessionContext serverContext; - private SSLSessionContext clientContext; + AbstractSessionContext serverContext; + AbstractSessionContext clientContext; X509ExtendedKeyManager keyManager; X509TrustManager trustManager; diff --git a/gnu/javax/net/ssl/provider/SSLEngineImpl.java b/gnu/javax/net/ssl/provider/SSLEngineImpl.java index 8fc9520cd..b7f19946a 100644 --- a/gnu/javax/net/ssl/provider/SSLEngineImpl.java +++ b/gnu/javax/net/ssl/provider/SSLEngineImpl.java @@ -49,8 +49,10 @@ import gnu.javax.net.ssl.provider.Alert.Level; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import java.util.zip.DataFormatException; @@ -71,7 +73,7 @@ public final class SSLEngineImpl extends SSLEngine { final SSLContextImpl contextImpl; private SSLRecordHandler[] handlers; - private static final Logger logger = SystemLogger.SYSTEM; + private static final SystemLogger logger = SystemLogger.SYSTEM; private SessionImpl session; private InputSecurityParameters insec; private OutputSecurityParameters outsec; @@ -101,12 +103,15 @@ public final class SSLEngineImpl extends SSLEngine */ private final ByteBuffer alertBuffer; - private int mode; - private static final int MODE_NONE = 0, MODE_SERVER = 1, MODE_CLIENT = 2; + private Mode mode; + private enum Mode { SERVER, CLIENT }; + SSLEngineImpl (SSLContextImpl contextImpl, String host, int port) { super(host, port); + logger.logv(java.util.logging.Level.INFO, "creating SSLEngine {0} {1}:{2}", + this, host, port); this.contextImpl = contextImpl; handlers = new SSLRecordHandler[256]; session = new SessionImpl(); @@ -117,9 +122,15 @@ public final class SSLEngineImpl extends SSLEngine session.setId(new Session.ID(sid)); session.setRandom(contextImpl.random); + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "generated session ID {0} with random {1}", + session.id(), contextImpl.random); + // Begin with no encryption. - insec = new InputSecurityParameters (null, null, null, session); - outsec = new OutputSecurityParameters (null, null, null, session); + insec = new InputSecurityParameters (null, null, null, session, + CipherSuite.TLS_NULL_WITH_NULL_NULL); + outsec = new OutputSecurityParameters (null, null, null, session, + CipherSuite.TLS_NULL_WITH_NULL_NULL); inClosed = false; outClosed = false; needClientAuth = false; @@ -127,10 +138,49 @@ public final class SSLEngineImpl extends SSLEngine createSessions = true; initialHandshakeDone = false; alertBuffer = ByteBuffer.wrap (new byte[2]); - mode = MODE_NONE; + mode = null; lastAlert = null; handshakeStatus = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; changeCipherSpec = false; + + // Set up default protocols and suites. + enabledProtocols = new String[] { + ProtocolVersion.TLS_1_1.toString(), + ProtocolVersion.TLS_1.toString(), + ProtocolVersion.SSL_3.toString() + }; + enabledSuites = new String[] { + CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA.toString(), + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA.toString(), + CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA.toString(), + CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA.toString(), + CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA.toString(), + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA.toString(), + CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA.toString(), + CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA.toString(), + CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA.toString(), + CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA.toString(), + CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA.toString(), + CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_RC4_128_MD5.toString(), + CipherSuite.TLS_RSA_WITH_RC4_128_SHA.toString(), + CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA.toString(), + CipherSuite.TLS_DHE_RSA_WITH_DES_CBC_SHA.toString(), + CipherSuite.TLS_DH_DSS_WITH_DES_CBC_SHA.toString(), + CipherSuite.TLS_DH_RSA_WITH_DES_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_DES_CBC_SHA.toString(), + CipherSuite.TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA.toString(), + CipherSuite.TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), + CipherSuite.TLS_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), + CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5.toString(), + CipherSuite.TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA.toString(), + CipherSuite.TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), + CipherSuite.TLS_RSA_WITH_NULL_MD5.toString(), + CipherSuite.TLS_RSA_WITH_NULL_SHA.toString() + }; } // XXX implement? @@ -152,9 +202,15 @@ public final class SSLEngineImpl extends SSLEngine @Override public void beginHandshake () throws SSLException { + if (Debug.DEBUG) + logger.log(Component.SSL_HANDSHAKE, "{0} handshake begins", mode); + + if (mode == null) + throw new IllegalStateException("setUseClientMode was never used"); + switch (mode) { - case MODE_SERVER: + case SERVER: if (getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) throw new SSLException("handshake already in progress"); try @@ -167,12 +223,9 @@ public final class SSLEngineImpl extends SSLEngine } break; - case MODE_CLIENT: + case CLIENT: throw new UnsupportedOperationException("client handshake not yet implemented"); //break; - - case MODE_NONE: - throw new IllegalStateException ("setUseClientMode was never called"); } } @@ -185,7 +238,7 @@ public final class SSLEngineImpl extends SSLEngine @Override public void closeOutbound() { - outClosed = true; + lastAlert = new Alert(Alert.Level.WARNING, Alert.Description.CLOSE_NOTIFY); } @Override @@ -233,7 +286,7 @@ public final class SSLEngineImpl extends SSLEngine @Override public boolean getUseClientMode () { - return (mode == MODE_CLIENT); + return (mode == Mode.CLIENT); } @Override @@ -245,13 +298,13 @@ public final class SSLEngineImpl extends SSLEngine @Override public boolean isInboundDone() { - return false; // XXX + return inClosed; } @Override public boolean isOutboundDone() { - return false; // XXX + return outClosed; } @Override @@ -303,9 +356,9 @@ public final class SSLEngineImpl extends SSLEngine public void setUseClientMode (final boolean clientMode) { if (clientMode) - mode = MODE_CLIENT; + mode = Mode.CLIENT; else - mode = MODE_SERVER; + mode = Mode.SERVER; } public @Override void setWantClientAuth(final boolean wantClientAuth) @@ -318,49 +371,51 @@ public final class SSLEngineImpl extends SSLEngine final int offset, final int length) throws SSLException { - if (mode == MODE_NONE) + if (mode == null) throw new IllegalStateException ("setUseClientMode was never called"); - Record record = new Record (source.slice ()); - ContentType type = record.contentType (); + if (inClosed) + return new SSLEngineResult(SSLEngineResult.Status.CLOSED, + handshakeStatus, 0, 0); + + if (source.remaining() < 5) + { + return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, + handshakeStatus, 0, 0); + } + + Record record = null; + boolean helloV2 = false; // XXX: messages may be chunked across multiple records; does this // include the SSLv2 message? I don't think it does, but we should // make sure. - if (!getUseClientMode () && type == ContentType.CLIENT_HELLO_V2) + if (!getUseClientMode() && (source.get(source.position()) & 0x80) == 0x80) { - if (!insec.cipherSuite ().equals (CipherSuite.TLS_NULL_WITH_NULL_NULL)) - throw new SSLException ("received SSLv2 client hello in encrypted session; this is invalid."); - logger.log (Component.SSL_RECORD_LAYER, "converting SSLv2 client hello to version 3 hello"); - ClientHelloV2 v2 = new ClientHelloV2 (source.slice ()); - List suites = v2.cipherSpecs (); - - // For the length of the "fake" v3 hello we need: - // 1 for the content type - // 2 for the protocol version - // 2 for the record length - // 2 for the client version - // 32 for the random value - // 33 for the session ID - // 2 for the cipher suites length - // 2*n for the n cipher suites - // 2 for the singleton compression method list, with length - int len = 76 + 2 * suites.size (); - ByteBuffer buf = ByteBuffer.allocate (len); - Record rec = new Record (buf); - rec.setContentType (ContentType.HANDSHAKE); - rec.setVersion (v2.version ()); - rec.setLength (len - 5); - - Handshake handshake = new Handshake (rec.fragment ()); - handshake.setType (Handshake.Type.CLIENT_HELLO); - handshake.setLength (len - 9); - - ClientHello hello = (ClientHello) handshake.body (); - hello.setVersion (v2.version ()); - - Random random = hello.random (); - byte[] challenge = v2.challenge (); + if (handshake == null) + beginHandshake(); + int hellolen = source.getShort(source.position()) & 0x7FFF; + this.handshake.handleV2Hello(source.slice()); + if (!insec.cipherSuite().equals (CipherSuite.TLS_NULL_WITH_NULL_NULL)) + throw new SSLException ("received SSLv2 client hello in encrypted " + + "session; this is invalid."); + if (Debug.DEBUG) + logger.log (Component.SSL_RECORD_LAYER, + "converting SSLv2 client hello to version 3 hello"); + + source.getShort(); // skip length + ClientHelloV2 v2 = new ClientHelloV2(source.slice()); + + if (Debug.DEBUG) + logger.log(Component.SSL_RECORD_LAYER, "v2 hello: {0}", v2); + + List<CipherSuite> suites = v2.cipherSpecs(); + + ClientHelloBuilder hello = new ClientHelloBuilder(); + hello.setVersion(v2.version ()); + + Random random = hello.random(); + byte[] challenge = v2.challenge(); if (challenge.length < 32) { byte[] b = new byte[32]; @@ -368,28 +423,39 @@ public final class SSLEngineImpl extends SSLEngine challenge.length); challenge = b; } - random.setGmtUnixTime ((challenge[0] & 0xFF) << 24 - | (challenge[1] & 0xFF) << 16 - | (challenge[2] & 0xFF) << 8 - | (challenge[3] & 0xFF)); - random.setRandomBytes (challenge, 4); - - byte[] sessionId = v2.sessionId (); - hello.setSessionId (sessionId, 0, sessionId.length); - - CipherSuiteList mySuites = hello.cipherSuites (); - mySuites.setSize (2 * suites.size ()); - for (int i = 0; i < suites.size (); i++) - mySuites.put (i, (CipherSuite) suites.get (i)); - - CompressionMethodList comps = hello.compressionMethods (); - comps.setSize (1); - comps.put (0, CompressionMethod.NULL); - - record = rec; + random.setGmtUnixTime((challenge[0] & 0xFF) << 24 + | (challenge[1] & 0xFF) << 16 + | (challenge[2] & 0xFF) << 8 + | (challenge[3] & 0xFF)); + random.setRandomBytes(challenge, 4); + + byte[] sessionId = v2.sessionId(); + hello.setSessionId(sessionId, 0, sessionId.length); + hello.setCipherSuites(suites); + ArrayList<CompressionMethod> comps = new ArrayList<CompressionMethod>(1); + comps.add(CompressionMethod.NULL); + hello.setCompressionMethods(comps); + + record = new Record(ByteBuffer.allocate(hello.length() + 9)); + record.setContentType(ContentType.HANDSHAKE); + record.setVersion(v2.version()); + record.setLength(hello.length() + 4); + + Handshake handshake = new Handshake(record.fragment()); + handshake.setLength(hello.length()); + handshake.setType(Handshake.Type.CLIENT_HELLO); + + handshake.bodyBuffer().put(hello.buffer()); + source.position(source.position() + hellolen); + helloV2 = true; } - - logger.log (Component.SSL_RECORD_LAYER, "read record {0}", record); + else + record = new Record(source); + + ContentType type = record.contentType (); + + if (Debug.DEBUG) + logger.log(Component.SSL_RECORD_LAYER, "input record:\n{0}", record); if (record.length() > session.getPacketBufferSize() - 5) { @@ -398,12 +464,8 @@ public final class SSLEngineImpl extends SSLEngine throw new AlertException(lastAlert); } - ByteBufferOutputStream sysMsg = null; - - // We will get a message, when decompressed, that does not exceed - // 2^14 bytes. - if (record.contentType() != ContentType.APPLICATION_DATA) - sysMsg = new ByteBufferOutputStream(); + ByteBufferOutputStream sysMsg = null; + ByteBuffer msg = null; int produced = 0; try @@ -413,12 +475,25 @@ public final class SSLEngineImpl extends SSLEngine if (record.contentType() == ContentType.APPLICATION_DATA) produced = insec.decrypt(record, sinks, offset, length); else - insec.decrypt(record, sysMsg); + { + if (insec.cipherSuite() == CipherSuite.TLS_NULL_WITH_NULL_NULL) + msg = record.fragment(); + else + { + sysMsg = new ByteBufferOutputStream(); + insec.decrypt(record, sysMsg); + } + } + + // Advance the input buffer past the record we just read. + if (!helloV2) + source.position(source.position() + record.length() + 5); } catch (BufferOverflowException boe) { // We throw this if the output buffers are not large enough; signal // the caller about this. + logger.log(Component.SSL_RECORD_LAYER, "buffer overflow when decrypting", boe); return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, 0); } @@ -452,9 +527,12 @@ public final class SSLEngineImpl extends SSLEngine // If we need to handle the output here, do it. Otherwise, the output // has been stored in the supplied output buffers. - ByteBuffer msg = null; if (sysMsg != null) - msg = sysMsg.buffer(); + { + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "sysmessage {0}", sysMsg); + msg = sysMsg.buffer(); + } if (type == ContentType.CHANGE_CIPHER_SPEC) { @@ -464,7 +542,7 @@ public final class SSLEngineImpl extends SSLEngine { result = new SSLEngineResult (SSLEngineResult.Status.OK, handshakeStatus, - record.length(), 0); + record.length() + 5, 0); } else { @@ -474,11 +552,11 @@ public final class SSLEngineImpl extends SSLEngine InputSecurityParameters params = handshake.getInputParams(); logger.log (Component.SSL_RECORD_LAYER, "switching to input security parameters {0}", - params.cipherSuite ()); + params.cipherSuite()); insec = params; result = new SSLEngineResult (SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - record.length(), 0); // XXXX + handshakeStatus, + record.length() + 5, 0); // XXXX } } else if (type == ContentType.ALERT) @@ -508,28 +586,35 @@ public final class SSLEngineImpl extends SSLEngine { if (alerts[i].level () == Alert.Level.FATAL) throw new AlertException (alerts[i]); - logger.log (java.util.logging.Level.WARNING, "received alert: {0}", alerts[i]); - if (alerts[i].description () == Alert.Description.CLOSE_NOTIFY) + logger.log (java.util.logging.Level.WARNING, + "received alert: {0}", alerts[i]); + if (alerts[i].description() == Alert.Description.CLOSE_NOTIFY) inClosed = true; } - if (msg.hasRemaining ()) - alertBuffer.position (0).limit (2); + if (msg.hasRemaining()) + alertBuffer.position(0).limit(2); - result = new SSLEngineResult (inClosed ? SSLEngineResult.Status.CLOSED - : SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - record.length (), 0); + result = new SSLEngineResult (SSLEngineResult.Status.OK, + handshakeStatus, + record.length() + 5, 0); } else if (type == ContentType.HANDSHAKE) { if (handshake == null) beginHandshake(); handshakeStatus = handshake.handleInput(msg); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", handshakeStatus); result = new SSLEngineResult(SSLEngineResult.Status.OK, handshakeStatus, record.length() + 5, 0); + if (handshakeStatus == HandshakeStatus.FINISHED) + { + handshake = null; + handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; + } } else if (type == ContentType.APPLICATION_DATA) { @@ -554,6 +639,9 @@ public final class SSLEngineImpl extends SSLEngine throw new SSLException ("unknown content type: " + type); } + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "return result: {0}", result); + return result; } @@ -561,9 +649,13 @@ public final class SSLEngineImpl extends SSLEngine ByteBuffer sink) throws SSLException { - if (mode == MODE_NONE) + if (mode == null) throw new IllegalStateException ("setUseClientMode was never called"); + if (outClosed) + return new SSLEngineResult(SSLEngineResult.Status.CLOSED, + handshakeStatus, 0, 0); + ContentType type = null; ByteBuffer sysMessage = null; @@ -574,21 +666,54 @@ public final class SSLEngineImpl extends SSLEngine Alert alert = new Alert(sysMessage); alert.setDescription(lastAlert.description()); alert.setLevel(lastAlert.level()); + if (lastAlert.description() == Alert.Description.CLOSE_NOTIFY) + outClosed = true; } else if (changeCipherSpec) { type = ContentType.CHANGE_CIPHER_SPEC; sysMessage = ByteBuffer.allocate(1); sysMessage.put(0, (byte) 1); - outsec = handshake.getOutputParams(); - changeCipherSpec = false; } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { + // If we are not encrypting, optimize the handshake to fill + // the buffer directly. + if (outsec.suite() == CipherSuite.TLS_NULL_WITH_NULL_NULL) + { + int orig = sink.position(); + sink.order(ByteOrder.BIG_ENDIAN); + sink.put((byte) ContentType.HANDSHAKE.getValue()); + sink.putShort((short) session.version.rawValue()); + sink.putShort((short) 0); + handshakeStatus = handshake.handleOutput(sink); + int produced = sink.position() - orig; + sink.putShort(orig + 3, (short) (produced - 5)); + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "emitting record:\n{0}", + new Record((ByteBuffer) sink.duplicate().position(orig))); + SSLEngineResult result = new SSLEngineResult(SSLEngineResult.Status.OK, + handshakeStatus, 0, produced); + + // Note, this will only happen if we transition from + // TLS_NULL_WITH_NULL_NULL *to* TLS_NULL_WITH_NULL_NULL, which + // doesn't make a lot of sense, but we support it anyway. + if (handshakeStatus == HandshakeStatus.FINISHED) + { + handshake = null; // finished with it. + handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; + } + return result; + } + // Rough guideline; XXX. sysMessage = ByteBuffer.allocate(sink.remaining() - 2048); type = ContentType.HANDSHAKE; handshakeStatus = handshake.handleOutput(sysMessage); + sysMessage.flip(); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", + handshakeStatus); } int produced = 0; @@ -596,20 +721,27 @@ public final class SSLEngineImpl extends SSLEngine try { + int orig = sink.position(); int[] inout = null; if (sysMessage != null) { + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "encrypt system message {0} to {1}", sysMessage, sink); inout = outsec.encrypt(new ByteBuffer[] { sysMessage }, 0, 1, - type, sink); + type, sink); produced = inout[1]; } else { inout = outsec.encrypt(sources, offset, length, - ContentType.APPLICATION_DATA, sink); + ContentType.APPLICATION_DATA, sink); consumed = inout[0]; produced = inout[1]; } + + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "emitting record:\n{0}", + new Record((ByteBuffer) sink.duplicate().position(orig).limit(produced))); } catch (ShortBufferException sbe) { @@ -638,8 +770,22 @@ public final class SSLEngineImpl extends SSLEngine lastAlert = null; throw ae; } - return new SSLEngineResult(SSLEngineResult.Status.OK, - handshakeStatus, consumed, produced); + + if (changeCipherSpec) + { + outsec = handshake.getOutputParams(); + changeCipherSpec = false; + } + SSLEngineResult result + = new SSLEngineResult(outClosed ? SSLEngineResult.Status.CLOSED + : SSLEngineResult.Status.OK, + handshakeStatus, consumed, produced); + if (handshakeStatus == HandshakeStatus.FINISHED) + { + handshake = null; // done with it. + handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; + } + return result; } // Package-private methods. diff --git a/gnu/javax/net/ssl/provider/SSLRSASignatureImpl.java b/gnu/javax/net/ssl/provider/SSLRSASignatureImpl.java new file mode 100644 index 000000000..415efc6f5 --- /dev/null +++ b/gnu/javax/net/ssl/provider/SSLRSASignatureImpl.java @@ -0,0 +1,233 @@ +/* SSLRSASignatureImpl.java -- SSL/TLS RSA implementation. + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is a part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +USA + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.javax.net.ssl.provider; + +import gnu.classpath.debug.Component; +import gnu.classpath.debug.SystemLogger; +import gnu.java.security.sig.rsa.RSA; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.SignatureSpi; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Arrays; + +/** + * An implementation of of the RSA signature algorithm; this is an RSA + * encrypted MD5 hash followed by a SHA-1 hash. + * + * @author Casey Marshall (csm@gnu.org) + */ +public class SSLRSASignatureImpl extends SignatureSpi +{ + private static final SystemLogger logger = SystemLogger.SYSTEM; + private RSAPublicKey pubkey; + private RSAPrivateKey privkey; + private final MessageDigest md5, sha; + private boolean initSign = false; + private boolean initVerify = false; + + public SSLRSASignatureImpl() throws NoSuchAlgorithmException + { + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA-1"); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineInitVerify(java.security.PublicKey) + */ + @Override protected void engineInitVerify(PublicKey publicKey) + throws InvalidKeyException + { + try + { + pubkey = (RSAPublicKey) publicKey; + initVerify = true; + initSign = false; + privkey = null; + } + catch (ClassCastException cce) + { + throw new InvalidKeyException(cce); + } + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineInitSign(java.security.PrivateKey) + */ + @Override protected void engineInitSign(PrivateKey privateKey) + throws InvalidKeyException + { + try + { + privkey = (RSAPrivateKey) privateKey; + initSign = true; + initVerify = false; + pubkey = null; + } + catch (ClassCastException cce) + { + throw new InvalidKeyException(cce); + } + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineUpdate(byte) + */ + @Override protected void engineUpdate(byte b) throws SignatureException + { + if (!initSign && !initVerify) + throw new IllegalStateException("not initialized"); + if (Debug.DEBUG) + logger.log(Component.SSL_HANDSHAKE, "SSL/RSA update 0x{0}", + Util.formatInt(b & 0xFF, 16, 2)); + md5.update(b); + sha.update(b); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineUpdate(byte[], int, int) + */ + @Override protected void engineUpdate(byte[] b, int off, int len) + throws SignatureException + { + if (!initSign && !initVerify) + throw new IllegalStateException("not initialized"); + if (Debug.DEBUG) + logger.log(Component.SSL_HANDSHAKE, "SSL/RSA update\n{0}", + Util.hexDump(b, off, len, ">> ")); + md5.update(b, off, len); + sha.update(b, off, len); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineSign() + */ + @Override protected byte[] engineSign() throws SignatureException + { + // FIXME we need to add RSA blinding to this, somehow. + + if (!initSign) + throw new SignatureException("not initialized for signing"); + // Pad the hash results with RSA block type 1. + final int k = (privkey.getModulus().bitLength() + 7) >>> 3; + final byte[] d = Util.concat(md5.digest(), sha.digest()); + if (k - 11 < d.length) + throw new SignatureException("message too long"); + final byte[] eb = new byte[k]; + eb[0] = 0x00; + eb[1] = 0x01; + for (int i = 2; i < k - d.length - 1; i++) + eb[i] = (byte) 0xFF; + System.arraycopy(d, 0, eb, k - d.length, d.length); + BigInteger EB = new BigInteger(eb); + + // Private-key encrypt the padded hashes. + BigInteger EM = RSA.sign(privkey, EB); + return Util.trim(EM); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineVerify(byte[]) + */ + @Override protected boolean engineVerify(byte[] sigBytes) + throws SignatureException + { + if (!initVerify) + throw new SignatureException("not initialized for verifying"); + + // Public-key decrypt the signature representative. + BigInteger EM = new BigInteger(1, (byte[]) sigBytes); + BigInteger EB = RSA.verify(pubkey, EM); + + // Unpad the decrypted message. + int i = 0; + final byte[] eb = EB.toByteArray(); + if (eb[0] == 0x00) + { + for (i = 0; i < eb.length && eb[i] == 0x00; i++); + } + else if (eb[0] == 0x01) + { + for (i = 1; i < eb.length && eb[i] != 0x00; i++) + { + if (eb[i] != (byte) 0xFF) + { + throw new SignatureException("bad padding"); + } + } + i++; + } + else + { + throw new SignatureException("decryption failed"); + } + byte[] d1 = Util.trim(eb, i, eb.length - i); + byte[] d2 = Util.concat(md5.digest(), sha.digest()); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "SSL/RSA d1:{0} d2:{1}", + Util.toHexString(d1, ':'), Util.toHexString(d2, ':')); + return Arrays.equals(d1, d2); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineSetParameter(java.lang.String, java.lang.Object) + */ + @Override protected void engineSetParameter(String param, Object value) + throws InvalidParameterException + { + throw new InvalidParameterException("parameters not supported"); + } + + /* (non-Javadoc) + * @see java.security.SignatureSpi#engineGetParameter(java.lang.String) + */ + @Override protected Object engineGetParameter(String param) + throws InvalidParameterException + { + throw new InvalidParameterException("parameters not supported"); + } +} diff --git a/gnu/javax/net/ssl/provider/ServerDHParams.java b/gnu/javax/net/ssl/provider/ServerDHParams.java index d28571f95..c3e06a389 100644 --- a/gnu/javax/net/ssl/provider/ServerDHParams.java +++ b/gnu/javax/net/ssl/provider/ServerDHParams.java @@ -118,7 +118,7 @@ public class ServerDHParams implements Builder, ServerKeyExchangeParams public ByteBuffer buffer() { - return (ByteBuffer) buffer.duplicate().limit(length()); + return (ByteBuffer) buffer.duplicate().position(0).limit(length()); } /** diff --git a/gnu/javax/net/ssl/provider/ServerHandshake.java b/gnu/javax/net/ssl/provider/ServerHandshake.java index 0ff324f44..047b6f306 100644 --- a/gnu/javax/net/ssl/provider/ServerHandshake.java +++ b/gnu/javax/net/ssl/provider/ServerHandshake.java @@ -46,13 +46,18 @@ import static gnu.javax.net.ssl.provider.Handshake.Type.*; import gnu.classpath.Configuration; import gnu.classpath.debug.Component; +import gnu.java.security.action.GetSecurityPropertyAction; import gnu.javax.crypto.key.dh.GnuDHPublicKey; +import gnu.javax.net.ssl.AbstractSessionContext; +import gnu.javax.net.ssl.Session; +import gnu.javax.net.ssl.Session.ID; import gnu.javax.net.ssl.provider.CertificateRequest.ClientCertificateType; import gnu.javax.net.ssl.provider.ServerNameList.ServerName; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.security.AccessController; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyPair; @@ -66,12 +71,9 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.logging.Level; -import java.util.logging.Logger; import java.util.zip.Deflater; import java.util.zip.Inflater; @@ -83,19 +85,16 @@ import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; import javax.security.auth.x500.X500Principal; class ServerHandshake extends AbstractHandshake @@ -144,8 +143,6 @@ class ServerHandshake extends AbstractHandshake private final SSLEngineImpl engine; /* Handshake result fields. */ - private ProtocolVersion version; - private CipherSuite suite; private CompressionMethod compression; private Random clientRandom; private Random serverRandom; @@ -156,6 +153,14 @@ class ServerHandshake extends AbstractHandshake private String keyAlias = null; private X509Certificate clientCert = null; private X509Certificate localCert = null; + private boolean helloV2 = false; + + /** + * We remember this to ensure that we use a different one if the client's + * session ID happens to be the same as our random one (this is possible, + * if the random we're initialized with is predictable). + */ + private Session.ID clientSessionID; private InputSecurityParameters inParams; private OutputSecurityParameters outParams; @@ -206,16 +211,27 @@ class ServerHandshake extends AbstractHandshake * Choose the first cipher suite in the client's requested list that * we have enabled. */ - private static CipherSuite chooseSuite (final CipherSuiteList clientSuites, - final String[] enabledSuites, - final ProtocolVersion version) + private CipherSuite chooseSuite (final CipherSuiteList clientSuites, + final String[] enabledSuites, + final ProtocolVersion version) throws SSLException { - HashSet<CipherSuite> suites = new HashSet<CipherSuite> (enabledSuites.length); + // Figure out which SignatureAlgorithms we can support. + HashSet<SignatureAlgorithm> sigs = new HashSet<SignatureAlgorithm>(2); + X509ExtendedKeyManager km = engine.contextImpl.keyManager; + if (km.getServerAliases(SignatureAlgorithm.DSA.name(), null).length != 0) + sigs.add(SignatureAlgorithm.DSA); + if (km.getServerAliases(SignatureAlgorithm.RSA.name(), null).length != 0) + sigs.add(SignatureAlgorithm.RSA); + + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "we have certs for signature algorithms {0}", sigs); + + HashSet<CipherSuite> suites = new HashSet<CipherSuite>(enabledSuites.length); for (String s : enabledSuites) { CipherSuite suite = CipherSuite.forName(s); - if (suite != null) + if (suite != null && sigs.contains(suite.signatureAlgorithm())) suites.add (suite); } for (CipherSuite suite : clientSuites) @@ -238,11 +254,17 @@ class ServerHandshake extends AbstractHandshake private static CompressionMethod chooseCompression (final CompressionMethodList comps) throws SSLException { + GetSecurityPropertyAction gspa + = new GetSecurityPropertyAction("jessie.enable.compression"); + String enable = AccessController.doPrivileged(gspa); // Scan for ZLIB first. - for (CompressionMethod cm : comps) + if (Boolean.valueOf(enable)) { - if (cm.equals (CompressionMethod.ZLIB)) - return CompressionMethod.ZLIB; + for (CompressionMethod cm : comps) + { + if (cm.equals (CompressionMethod.ZLIB)) + return CompressionMethod.ZLIB; + } } for (CompressionMethod cm : comps) { @@ -255,67 +277,66 @@ class ServerHandshake extends AbstractHandshake protected @Override boolean doHash() { - return state != WRITE_HELLO_REQUEST; + boolean b = helloV2; + helloV2 = false; + return (state != WRITE_HELLO_REQUEST) && !b; } - public @Override HandshakeStatus handleInput (ByteBuffer fragment) + public @Override HandshakeStatus implHandleInput() throws SSLException - { + { if (state == DONE) return HandshakeStatus.FINISHED; - if (state.isWriteState() || outBuffer.hasRemaining()) + if (state.isWriteState() + || (outBuffer != null && outBuffer.hasRemaining())) return HandshakeStatus.NEED_WRAP; - // If we don't have a message already waiting... - if (!hasMessage()) - { - // Try to read another... - if (!pollHandshake (fragment)) - return HandshakeStatus.NEED_UNWRAP; - - // Otherwise, we've got something to process. - } - - while (hasMessage() && state.isReadState()) - { - // Copy the current buffer, and prepare it for reading. - ByteBuffer buffer = handshakeBuffer.duplicate (); - buffer.flip(); - buffer.position(handshakeOffset); - Handshake handshake = new Handshake(buffer.slice()); + // Copy the current buffer, and prepare it for reading. + ByteBuffer buffer = handshakeBuffer.duplicate (); + buffer.flip(); + buffer.position(handshakeOffset); + Handshake handshake = new Handshake(buffer.slice(), + engine.session().suite, + engine.session().version); - if (Configuration.DEBUG) - logger.logv(Component.SSL_HANDSHAKE, "processing in state {0}: {1}", - state, handshake); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "processing in state {0}:\n{1}", + state, handshake); - switch (state) - { - // Client Hello. - // - // This message is sent by the client to initiate a new handshake. - // On a new connection, it is the first handshake message sent. - // - // The state of the handshake, after this message is processed, - // will have a protocol version, cipher suite, compression method, - // session ID, and various extensions (that the server also - // supports). - case READ_CLIENT_HELLO: - if (handshake.type () != CLIENT_HELLO) - throw new SSLException ("expecting client hello"); - // XXX throw better exception. + switch (state) + { + // Client Hello. + // + // This message is sent by the client to initiate a new handshake. + // On a new connection, it is the first handshake message sent. + // + // The state of the handshake, after this message is processed, + // will have a protocol version, cipher suite, compression method, + // session ID, and various extensions (that the server also + // supports). + case READ_CLIENT_HELLO: + if (handshake.type () != CLIENT_HELLO) + throw new SSLException ("expecting client hello"); + // XXX throw better exception. { ClientHello hello = (ClientHello) handshake.body (); engine.session().version = chooseProtocol (hello.version (), engine.getEnabledProtocols ()); - engine.session().suite - = chooseSuite (hello.cipherSuites (), - engine.getEnabledCipherSuites (), version); + engine.session().suite = + chooseSuite (hello.cipherSuites (), + engine.getEnabledCipherSuites (), + engine.session().version); compression = chooseCompression (hello.compressionMethods ()); - clientRandom = hello.random ().copy (); - byte[] sessionId = hello.sessionId (); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, + "chose version:{0} suite:{1} compression:{2}", + engine.session().version, engine.session().suite, + compression); + clientRandom = hello.random().copy(); + byte[] sessionId = hello.sessionId(); if (hello.hasExtensions()) { ExtensionList exts = hello.extensions(); @@ -334,7 +355,7 @@ class ServerHandshake extends AbstractHandshake case MAX_FRAGMENT_LENGTH: MaxFragmentLength len = (MaxFragmentLength) e.value(); engine.session().maxLength = len; - engine.session().setPacketBufferSize(len.maxLength() + 2048); + engine.session().setApplicationBufferSize(len.maxLength()); break; case SERVER_NAME: @@ -351,36 +372,50 @@ class ServerHandshake extends AbstractHandshake } } } - SSLSessionContext sessions = + AbstractSessionContext sessions = (AbstractSessionContext) engine.contextImpl.engineGetServerSessionContext(); SSLSession s = sessions.getSession(sessionId); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "looked up saved session {0}", s); if (s != null && s.isValid() && (s instanceof SessionImpl)) { engine.setSession((SessionImpl) s); continuedSession = true; - version = ((SessionImpl) s).version; + } + else + { + // We *may* wind up with a badly seeded PRNG, and emit the + // same session ID over and over (this did happen to me, + // so we add this sanity check just in case). + if (engine.session().id().equals(new Session.ID(sessionId))) + { + byte[] newId = new byte[32]; + engine.session().random().nextBytes(newId); + engine.session().setId(new Session.ID(newId)); + } + sessions.put(engine.session()); } state = WRITE_SERVER_HELLO; } break; - // Certificate. - // - // This message is sent by the client if the server had previously - // requested that the client authenticate itself with a certificate, - // and if the client has an appropriate certificate available. - // - // Processing this message will save the client's certificate, - // rejecting it if the certificate is not trusted, in preparation - // for the certificate verify message that will follow. - case READ_CERTIFICATE: + // Certificate. + // + // This message is sent by the client if the server had previously + // requested that the client authenticate itself with a certificate, + // and if the client has an appropriate certificate available. + // + // Processing this message will save the client's certificate, + // rejecting it if the certificate is not trusted, in preparation + // for the certificate verify message that will follow. + case READ_CERTIFICATE: { if (handshake.type() != CERTIFICATE) { if (engine.getNeedClientAuth()) // XXX throw better exception. throw new SSLException("client auth required"); state = READ_CLIENT_KEY_EXCHANGE; - continue; + return HandshakeStatus.NEED_UNWRAP; } Certificate cert = (Certificate) handshake.body(); @@ -415,15 +450,15 @@ class ServerHandshake extends AbstractHandshake } break; - // Client Key Exchange. - // - // The client's key exchange. This message is sent either following - // the certificate message, or if no certificate is available or - // requested, following the server's hello done message. - // - // After receipt of this message, the session keys for this - // session will have been created. - case READ_CLIENT_KEY_EXCHANGE: + // Client Key Exchange. + // + // The client's key exchange. This message is sent either following + // the certificate message, or if no certificate is available or + // requested, following the server's hello done message. + // + // After receipt of this message, the session keys for this + // session will have been created. + case READ_CLIENT_KEY_EXCHANGE: { if (handshake.type() != CLIENT_KEY_EXCHANGE) throw new SSLException("expecting client key exchange"); @@ -489,30 +524,34 @@ class ServerHandshake extends AbstractHandshake engine.session()); try { - CipherSuite suite = engine.session().suite; - Cipher inCipher = suite.cipher(); - Mac inMac = suite.mac(engine.session().version); + CipherSuite s = engine.session().suite; + Cipher inCipher = s.cipher(); + Mac inMac = s.mac(engine.session().version); Inflater inflater = (compression == CompressionMethod.ZLIB ? new Inflater() : null); inCipher.init(Cipher.DECRYPT_MODE, - new SecretKeySpec(keys[2], suite.cipherAlgorithm().toString()), + new SecretKeySpec(keys[2], + s.cipherAlgorithm().toString()), new IvParameterSpec(keys[4])); - inMac.init(new SecretKeySpec(keys[0], suite.macAlgorithm().toString())); + inMac.init(new SecretKeySpec(keys[0], + inMac.getAlgorithm())); inParams = new InputSecurityParameters(inCipher, inMac, inflater, - engine.session()); + engine.session(), s); - Cipher outCipher = suite.cipher(); - Mac outMac = suite.mac(engine.session().version); + Cipher outCipher = s.cipher(); + Mac outMac = s.mac(engine.session().version); Deflater deflater = (compression == CompressionMethod.ZLIB ? new Deflater() : null); outCipher.init(Cipher.ENCRYPT_MODE, - new SecretKeySpec(keys[3], suite.cipherAlgorithm().toString()), + new SecretKeySpec(keys[3], + s.cipherAlgorithm().toString()), new IvParameterSpec(keys[5])); - inMac.init(new SecretKeySpec(keys[1], suite.macAlgorithm().toString())); + outMac.init(new SecretKeySpec(keys[1], + outMac.getAlgorithm())); outParams = new OutputSecurityParameters(outCipher, outMac, deflater, - engine.session()); + engine.session(), s); } catch (InvalidAlgorithmParameterException iape) { @@ -535,21 +574,26 @@ class ServerHandshake extends AbstractHandshake state = READ_CERTIFICATE_VERIFY; else { - engine.changeCipherSpec(); - state = WRITE_FINISHED; + if (continuedSession) + { + engine.changeCipherSpec(); + state = WRITE_FINISHED; + } + else + state = READ_FINISHED; } } break; - // Certificate Verify. - // - // This message is sent following the client key exchange message, - // but only when the client included its certificate in a previous - // message. - // - // After receipt of this message, the client's certificate (and, - // to a degree, the client's identity) will have been verified. - case READ_CERTIFICATE_VERIFY: + // Certificate Verify. + // + // This message is sent following the client key exchange message, + // but only when the client included its certificate in a previous + // message. + // + // After receipt of this message, the client's certificate (and, + // to a degree, the client's identity) will have been verified. + case READ_CERTIFICATE_VERIFY: { if (handshake.type() != CERTIFICATE_VERIFY) throw new SSLException("expecting certificate verify message"); @@ -565,22 +609,29 @@ class ServerHandshake extends AbstractHandshake if (engine.getNeedClientAuth()) throw new SSLException("client auth failed", se); } + if (continuedSession) + { + engine.changeCipherSpec(); + state = WRITE_FINISHED; + } + else + state = READ_FINISHED; } break; - // Finished. - // - // This message is sent immediately following the change cipher - // spec message (which is sent outside of the handshake layer). - // After receipt of this message, the session keys for the client - // side will have been verified (this is the first message the - // client sends encrypted and authenticated with the newly - // negotiated keys). - // - // In the case of a continued session, the client sends its - // finished message first. Otherwise, the server will send its - // finished message first. - case READ_FINISHED: + // Finished. + // + // This message is sent immediately following the change cipher + // spec message (which is sent outside of the handshake layer). + // After receipt of this message, the session keys for the client + // side will have been verified (this is the first message the + // client sends encrypted and authenticated with the newly + // negotiated keys). + // + // In the case of a continued session, the client sends its + // finished message first. Otherwise, the server will send its + // finished message first. + case READ_FINISHED: { if (handshake.type() != FINISHED) { @@ -604,13 +655,14 @@ class ServerHandshake extends AbstractHandshake } Finished serverFinished = new Finished(generateFinished(md5copy, shacopy, - true, engine.session()), version); + true, engine.session()), + engine.session().version); - if (Configuration.DEBUG) + if (Debug.DEBUG) logger.log(Component.SSL_HANDSHAKE, "server finished: {0}", serverFinished); - if (version == ProtocolVersion.SSL_3) + if (engine.session().version == ProtocolVersion.SSL_3) { if (!Arrays.equals(clientFinished.md5Hash(), serverFinished.md5Hash()) @@ -640,11 +692,10 @@ class ServerHandshake extends AbstractHandshake } } break; - } - - handshakeOffset += handshake.length(); } + handshakeOffset += handshake.length() + 4; + if (state.isReadState()) return HandshakeStatus.NEED_UNWRAP; if (state.isWriteState()) @@ -656,6 +707,11 @@ class ServerHandshake extends AbstractHandshake public @Override HandshakeStatus implHandleOutput (ByteBuffer fragment) throws SSLException { + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, + "handle output state: {0}; output fragment: {1}", + state, fragment); + // Drain the output buffer, if it needs it. if (outBuffer != null && outBuffer.hasRemaining()) { @@ -671,7 +727,7 @@ class ServerHandshake extends AbstractHandshake else return HandshakeStatus.NEED_UNWRAP; } - + // XXX what we need to do here is generate a "stream" of handshake // messages, and insert them into fragment amounts that we have available. // A handshake message can span multiple records, and we can put @@ -732,6 +788,7 @@ class ServerHandshake extends AbstractHandshake // when we run out of space in the output buffer, and split the // overflow message. This sounds like the best, but also probably // the hardest to code. +output_loop: while (fragment.remaining() >= 4 && state.isWriteState()) { switch (state) @@ -746,11 +803,11 @@ class ServerHandshake extends AbstractHandshake handshake.setType(Handshake.Type.HELLO_REQUEST); handshake.setLength(0); fragment.position(fragment.position() + 4); - if (Configuration.DEBUG) + if (Debug.DEBUG) logger.log(Component.SSL_HANDSHAKE, "{0}", handshake); state = READ_CLIENT_HELLO; } - break; + break output_loop; // XXX temporary // Server Hello. // @@ -761,9 +818,7 @@ class ServerHandshake extends AbstractHandshake case WRITE_SERVER_HELLO: { ServerHelloBuilder hello = new ServerHelloBuilder(); - hello.setVersion (version); - hello.setCipherSuite (suite); - hello.setCompressionMethod(compression); + hello.setVersion (engine.session().version); Random r = hello.random(); r.setGmtUnixTime ((int) (System.currentTimeMillis() / 1000)); byte[] nonce = new byte[28]; @@ -771,12 +826,16 @@ class ServerHandshake extends AbstractHandshake r.setRandomBytes(nonce); serverRandom = r.copy (); hello.setSessionId(engine.session().getId()); + hello.setCipherSuite (engine.session().suite); + hello.setCompressionMethod(compression); if (clientHadExtensions) { // XXX figure this out. } + else // Don't send any extensions. + hello.setDisableExtensions(true); - if (Configuration.DEBUG) + if (Debug.DEBUG) logger.log(Component.SSL_HANDSHAKE, "{0}", hello); int typeLen = ((Handshake.Type.SERVER_HELLO.getValue() << 24) @@ -796,7 +855,7 @@ class ServerHandshake extends AbstractHandshake else state = WRITE_CERTIFICATE; } - break; + break output_loop; // XXX temporary // Certificate. // @@ -805,13 +864,13 @@ class ServerHandshake extends AbstractHandshake // itself (usually, servers must authenticate). case WRITE_CERTIFICATE: { - if (suite.keyExchangeAlgorithm() == null) + if (engine.session().suite.keyExchangeAlgorithm() == null) { state = WRITE_SERVER_KEY_EXCHANGE; break; } String sigAlg = null; - switch (suite.keyExchangeAlgorithm()) + switch (engine.session().suite.keyExchangeAlgorithm()) { case NONE: break; @@ -821,11 +880,11 @@ class ServerHandshake extends AbstractHandshake break; case DIFFIE_HELLMAN: - if (suite.isEphemeralDH()) + if (engine.session().suite.isEphemeralDH()) sigAlg = "DHE_"; else sigAlg = "DH_"; - switch (suite.signatureAlgorithm()) + switch (engine.session().suite.signatureAlgorithm()) { case RSA: sigAlg += "RSA"; @@ -837,7 +896,7 @@ class ServerHandshake extends AbstractHandshake } case SRP: - switch (suite.signatureAlgorithm()) + switch (engine.session().suite.signatureAlgorithm()) { case RSA: sigAlg = "SRP_RSA"; @@ -865,8 +924,11 @@ class ServerHandshake extends AbstractHandshake engine.session().setLocalCertificates(chain); localCert = chain[0]; - if (Configuration.DEBUG) - logger.log(Component.SSL_HANDSHAKE, "{0}", cert); + if (Debug.DEBUG) + { + logger.logv(Component.SSL_HANDSHAKE, "my cert:\n{0}", localCert); + logger.logv(Component.SSL_HANDSHAKE, "{0}", cert); + } int typeLen = ((CERTIFICATE.getValue() << 24) | (cert.length() & 0xFFFFFF)); @@ -879,7 +941,7 @@ class ServerHandshake extends AbstractHandshake state = WRITE_SERVER_KEY_EXCHANGE; } - break; + break output_loop; // XXX temporary // Server key exchange. // @@ -890,7 +952,7 @@ class ServerHandshake extends AbstractHandshake // implicit in the server's certificate). case WRITE_SERVER_KEY_EXCHANGE: { - KeyExchangeAlgorithm kex = suite.keyExchangeAlgorithm(); + KeyExchangeAlgorithm kex = engine.session().suite.keyExchangeAlgorithm(); if (kex == KeyExchangeAlgorithm.DIFFIE_HELLMAN) { @@ -902,7 +964,7 @@ class ServerHandshake extends AbstractHandshake if (kex != KeyExchangeAlgorithm.NONE && kex != KeyExchangeAlgorithm.RSA - && suite.isEphemeralDH()) + && engine.session().suite.isEphemeralDH()) { // This key exchange requires server params; construct them. ByteBuffer paramBuffer = null; @@ -915,22 +977,25 @@ class ServerHandshake extends AbstractHandshake pubKey.getParams().getG(), pubKey.getY()); paramBuffer = params.buffer(); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "server DH params:\n{0}", + Util.hexDump(paramBuffer)); } // XXX handle SRP - ByteBuffer sigBuffer = signParams(paramBuffer); - ServerKeyExchangeBuilder ske = new ServerKeyExchangeBuilder(suite); + ByteBuffer sigBuffer = signParams(paramBuffer.duplicate()); + ServerKeyExchangeBuilder ske + = new ServerKeyExchangeBuilder(engine.session().suite); ske.setParams(paramBuffer); ske.setSignature(sigBuffer); - if (Configuration.DEBUG) + if (Debug.DEBUG) logger.log(Component.SSL_HANDSHAKE, "{0}", ske); - fragment.putInt((SERVER_KEY_EXCHANGE.getValue() << 24) - | (paramBuffer.remaining() & 0xFFFFFF)); - outBuffer = ske.buffer(); int l = Math.min(fragment.remaining(), outBuffer.remaining()); + fragment.putInt((SERVER_KEY_EXCHANGE.getValue() << 24) + | (ske.length() & 0xFFFFFF)); fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); outBuffer.position(outBuffer.position() + l); } @@ -940,7 +1005,7 @@ class ServerHandshake extends AbstractHandshake else state = WRITE_SERVER_HELLO_DONE; } - break; + break output_loop; // XXX temporary // Certificate Request. // @@ -970,7 +1035,7 @@ class ServerHandshake extends AbstractHandshake issuers.add(cert.getIssuerX500Principal()); req.setAuthorities(issuers); - if (Configuration.DEBUG) + if (Debug.DEBUG) logger.log(Component.SSL_HANDSHAKE, "{0}", req); fragment.putInt((CERTIFICATE_REQUEST.getValue() << 24) @@ -983,7 +1048,7 @@ class ServerHandshake extends AbstractHandshake state = WRITE_SERVER_HELLO_DONE; } - break; + break output_loop; // XXX temporary // Server Hello Done. // @@ -997,9 +1062,11 @@ class ServerHandshake extends AbstractHandshake // ServerHelloDone is zero-length; just put in the type // field. fragment.putInt(SERVER_HELLO_DONE.getValue() << 24); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "writing ServerHelloDone"); state = READ_CERTIFICATE; } - break; + break output_loop; // XXX temporary // Finished. // @@ -1048,9 +1115,22 @@ class ServerHandshake extends AbstractHandshake break; } } - return (state.isWriteState() || outBuffer.hasRemaining() - ? HandshakeStatus.NEED_WRAP - : HandshakeStatus.NEED_UNWRAP); + if (state.isWriteState() || outBuffer.hasRemaining()) + return HandshakeStatus.NEED_WRAP; + if (state.isReadState()) + return HandshakeStatus.NEED_UNWRAP; + + return HandshakeStatus.FINISHED; + } + + @Override HandshakeStatus status() + { + if (state.isReadState()) + return HandshakeStatus.NEED_UNWRAP; + if (state.isWriteState()) + return HandshakeStatus.NEED_WRAP; + + return HandshakeStatus.FINISHED; } @Override InputSecurityParameters getInputParams() throws SSLException @@ -1067,6 +1147,14 @@ class ServerHandshake extends AbstractHandshake return outParams; } + @Override void handleV2Hello(ByteBuffer hello) + { + int len = hello.getShort(0) & 0x7FFF; + md5.update((ByteBuffer) hello.duplicate().position(2).limit(len+2)); + sha.update((ByteBuffer) hello.duplicate().position(2).limit(len+2)); + helloV2 = true; + } + /** * Generate, or fetch from our certificate, the Diffie-Hellman exchange * parameters. @@ -1077,11 +1165,18 @@ class ServerHandshake extends AbstractHandshake { try { - if (suite.isEphemeralDH()) + // XXX generating a DH key, especially given a large prime, can + // take a while. We should perhaps look into making this a delegated + // task, so we don't block other connections when generating keys. + // + // Also, I should investigate whether or not these DH parameters are + // "secure" or not; I mean, I'm not really worried, because SSH uses + // similar static parameters, but this *could* be a potential hole. + if (engine.session().suite.isEphemeralDH()) { KeyPairGenerator dhGen = KeyPairGenerator.getInstance("DH"); - // XXX figure this out, key size and shit. - dhGen.initialize(2048, engine.session().random()); + DHParameterSpec dhparams = DiffieHellman.getParams().getParams(); + dhGen.initialize(dhparams, engine.session().random()); dhPair = dhGen.generateKeyPair(); } else @@ -1092,11 +1187,15 @@ class ServerHandshake extends AbstractHandshake dhPair = new KeyPair(cert.getPublicKey(), key); } - if (Configuration.DEBUG) + if (Debug.DEBUG_KEY_EXCHANGE) logger.logv(Component.SSL_KEY_EXCHANGE, "Diffie-Hellman public:{0} private:{1}", dhPair.getPublic(), dhPair.getPrivate()); } + catch (InvalidAlgorithmParameterException iape) + { + throw new SSLException(iape); + } catch (NoSuchAlgorithmException nsae) { throw new SSLException(nsae); @@ -1107,15 +1206,18 @@ class ServerHandshake extends AbstractHandshake { try { + SignatureAlgorithm alg = engine.session().suite.signatureAlgorithm(); java.security.Signature sig - = java.security.Signature.getInstance(suite.signatureAlgorithm().name()); + = java.security.Signature.getInstance(alg.algorithm()); PrivateKey key = engine.contextImpl.keyManager.getPrivateKey(keyAlias); + if (Debug.DEBUG_KEY_EXCHANGE) + logger.logv(Component.SSL_HANDSHAKE, "server key: {0}", key); sig.initSign(key); sig.update(clientRandom.buffer()); sig.update(serverRandom.buffer()); sig.update(serverParams); byte[] sigVal = sig.sign(); - Signature signature = new Signature(sigVal, suite.signatureAlgorithm()); + Signature signature = new Signature(sigVal, engine.session().suite.signatureAlgorithm()); return signature.buffer(); } catch (NoSuchAlgorithmException nsae) @@ -1148,7 +1250,7 @@ class ServerHandshake extends AbstractHandshake } byte[] toSign = null; if (engine.session().version == ProtocolVersion.SSL_3) - toSign = genV2CertificateVerify(md5copy, shacopy, engine.session()); + toSign = genV3CertificateVerify(md5copy, shacopy, engine.session()); else { if (engine.session().suite.signatureAlgorithm() == SignatureAlgorithm.RSA) diff --git a/gnu/javax/net/ssl/provider/ServerHello.java b/gnu/javax/net/ssl/provider/ServerHello.java index f58a65f46..2bbce37fb 100644 --- a/gnu/javax/net/ssl/provider/ServerHello.java +++ b/gnu/javax/net/ssl/provider/ServerHello.java @@ -73,16 +73,15 @@ public class ServerHello implements Handshake.Body protected static final int SESSID_OFFSET2 = SESSID_OFFSET + 1; protected ByteBuffer buffer; - - /** The total length of the message, including the extensions. */ - private int totalLength; - + protected boolean disableExtensions; + // Constructor. // ------------------------------------------------------------------------- public ServerHello (final ByteBuffer buffer) { this.buffer = buffer; + disableExtensions = false; } public int length () @@ -90,7 +89,7 @@ public class ServerHello implements Handshake.Body int sessionLen = buffer.get(SESSID_OFFSET) & 0xFF; int len = SESSID_OFFSET2 + sessionLen + 3; int elen = 0; - if (len + 1 < buffer.limit() + if (!disableExtensions && len + 1 < buffer.limit() && (elen = buffer.getShort(len)) != 0) len += 2 + elen; return len; diff --git a/gnu/javax/net/ssl/provider/ServerHelloBuilder.java b/gnu/javax/net/ssl/provider/ServerHelloBuilder.java index a52e7a835..09ad1d9e8 100644 --- a/gnu/javax/net/ssl/provider/ServerHelloBuilder.java +++ b/gnu/javax/net/ssl/provider/ServerHelloBuilder.java @@ -96,6 +96,11 @@ public class ServerHelloBuilder extends ServerHello implements Builder // For extensions, we do reallocate the buffer. + public void setDisableExtensions(boolean disable) + { + disableExtensions = disable; + } + public void setExtensionsLength (final int length) { if (length < 0 || length > 16384) diff --git a/gnu/javax/net/ssl/provider/ServerNameList.java b/gnu/javax/net/ssl/provider/ServerNameList.java index 218e04a34..f119be637 100644 --- a/gnu/javax/net/ssl/provider/ServerNameList.java +++ b/gnu/javax/net/ssl/provider/ServerNameList.java @@ -6,6 +6,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.NoSuchElementException; @@ -42,12 +43,12 @@ public class ServerNameList extends Value implements Iterable<ServerNameList.Ser public ServerNameList (final ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } public int length() { - return buffer.getShort(0) & 0xFFFF; + return (buffer.getShort(0) & 0xFFFF) + 2; } public int size() @@ -70,7 +71,7 @@ public class ServerNameList extends Value implements Iterable<ServerNameList.Ser throw new IndexOutOfBoundsException("0; " + index); int n = 0; int i; - int l = 0; + int l = buffer.getShort(3); for (i = 2; i < len && n < index; ) { l = buffer.getShort(i+1); @@ -156,7 +157,7 @@ public class ServerNameList extends Value implements Iterable<ServerNameList.Ser public ServerName(final ByteBuffer buffer) { - this.buffer = buffer; + this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); } public int length() diff --git a/gnu/javax/net/ssl/provider/SessionImpl.java b/gnu/javax/net/ssl/provider/SessionImpl.java index 2e470977b..86dcb4915 100644 --- a/gnu/javax/net/ssl/provider/SessionImpl.java +++ b/gnu/javax/net/ssl/provider/SessionImpl.java @@ -69,6 +69,12 @@ public class SessionImpl extends Session transient PrivateData privateData; + public SessionImpl() + { + super(); + privateData = new PrivateData(); + } + SecureRandom random () { return random; @@ -148,15 +154,15 @@ public class SessionImpl extends Session { this.sealedPrivateData = so; } - - void setRandom(SecureRandom random) + + void setApplicationBufferSize(int size) { - this.random = random; + applicationBufferSize = size; } - void setPacketBufferSize(int size) + void setRandom(SecureRandom random) { - this.packetBufferSize = size; + this.random = random; } void setTruncatedMac(boolean truncatedMac) diff --git a/gnu/javax/net/ssl/provider/SignatureAlgorithm.java b/gnu/javax/net/ssl/provider/SignatureAlgorithm.java index e5c691b2e..a789576db 100644 --- a/gnu/javax/net/ssl/provider/SignatureAlgorithm.java +++ b/gnu/javax/net/ssl/provider/SignatureAlgorithm.java @@ -40,5 +40,23 @@ package gnu.javax.net.ssl.provider; public enum SignatureAlgorithm { - ANONYMOUS, RSA, DSA + ANONYMOUS, RSA, DSA; + + /** + * Returns the algorithm name for this signature algorithm, which can + * be used with the JCA API to get a {@link java.security.Signature} for + * that algorithm. + * + * @return The algorithm name. + */ + public String algorithm() + { + switch (this) + { + case ANONYMOUS: return null; + case RSA: return "TLSv1.1-RSA"; + case DSA: return "DSS"; + } + return null; + } } diff --git a/gnu/javax/net/ssl/provider/Util.java b/gnu/javax/net/ssl/provider/Util.java index f541c6c5c..0dae5a712 100644 --- a/gnu/javax/net/ssl/provider/Util.java +++ b/gnu/javax/net/ssl/provider/Util.java @@ -57,7 +57,7 @@ import java.security.Security; * * @author Casey Marshall (rsdio@metastatic.org) */ -final class Util +public final class Util { // Constants. @@ -77,7 +77,7 @@ final class Util * @param hex The hexadecimal string. * @return The converted bytes. */ - static byte[] toByteArray(String hex) + public static byte[] toByteArray(String hex) { hex = hex.toLowerCase(); byte[] buf = new byte[hex.length() / 2]; @@ -99,7 +99,7 @@ final class Util * @param len The number of bytes to format. * @return A hexadecimal representation of the specified bytes. */ - static String toHexString(byte[] buf, int off, int len) + public static String toHexString(byte[] buf, int off, int len) { StringBuffer str = new StringBuffer(); for (int i = 0; i < len; i++) @@ -113,7 +113,7 @@ final class Util /** * See {@link #toHexString(byte[],int,int)}. */ - static String toHexString(byte[] buf) + public static String toHexString(byte[] buf) { return Util.toHexString(buf, 0, buf.length); } @@ -128,7 +128,7 @@ final class Util * @param sep The character to insert between octets. * @return A hexadecimal representation of the specified bytes. */ - static String toHexString(byte[] buf, int off, int len, char sep) + public static String toHexString(byte[] buf, int off, int len, char sep) { StringBuffer str = new StringBuffer(); for (int i = 0; i < len; i++) @@ -144,7 +144,7 @@ final class Util /** * See {@link #toHexString(byte[],int,int,char)}. */ - static String toHexString(byte[] buf, char sep) + public static String toHexString(byte[] buf, char sep) { return Util.toHexString(buf, 0, buf.length, sep); } @@ -164,7 +164,7 @@ final class Util * @param prefix A string to prepend to every line. * @return The formatted string. */ - static String hexDump(byte[] buf, int off, int len, String prefix) + public static String hexDump(byte[] buf, int off, int len, String prefix) { String nl = getProperty("line.separator"); StringBuffer str = new StringBuffer(); @@ -192,48 +192,49 @@ final class Util return str.toString(); } - static String hexDump (ByteBuffer buf) + public static String hexDump (ByteBuffer buf) { return hexDump (buf, null); } - static String hexDump (ByteBuffer buf, String prefix) + public static String hexDump (ByteBuffer buf, String prefix) { + buf = buf.duplicate(); StringWriter str = new StringWriter (); PrintWriter out = new PrintWriter (str); int i = 0; - int len = buf.limit (); + int len = buf.remaining(); byte[] line = new byte[16]; while (i < len) { if (prefix != null) - out.print (prefix); - out.print (Util.formatInt (i, 16, 8)); - out.print (" "); - int l = Math.min (16, len - i); - buf.get (line, 0, l); - String s = Util.toHexString (line, 0, l, ' '); - out.print (s); - for (int j = s.length (); j < 49; j++) - out.print (' '); + out.print(prefix); + out.print(Util.formatInt (i, 16, 8)); + out.print(" "); + int l = Math.min(16, len - i); + buf.get(line, 0, l); + String s = Util.toHexString(line, 0, l, ' '); + out.print(s); + for (int j = s.length(); j < 49; j++) + out.print(' '); for (int j = 0; j < l; j++) { int c = line[j] & 0xFF; if (c < 0x20 || c > 0x7E) - out.print ('.'); + out.print('.'); else - out.print ((char) c); + out.print((char) c); } - out.println (); + out.println(); i += 16; } - return str.toString (); + return str.toString(); } /** * See {@link #hexDump(byte[],int,int,String)}. */ - static String hexDump(byte[] buf, int off, int len) + public static String hexDump(byte[] buf, int off, int len) { return hexDump(buf, off, len, ""); } @@ -241,7 +242,7 @@ final class Util /** * See {@link #hexDump(byte[],int,int,String)}. */ - static String hexDump(byte[] buf, String prefix) + public static String hexDump(byte[] buf, String prefix) { return hexDump(buf, 0, buf.length, prefix); } @@ -249,7 +250,7 @@ final class Util /** * See {@link #hexDump(byte[],int,int,String)}. */ - static String hexDump(byte[] buf) + public static String hexDump(byte[] buf) { return hexDump(buf, 0, buf.length); } @@ -263,7 +264,7 @@ final class Util * zero-padded to this length, but may be longer. * @return The formatted integer. */ - static String formatInt(int i, int radix, int len) + public static String formatInt(int i, int radix, int len) { String s = Integer.toString(i, radix); StringBuffer buf = new StringBuffer(); @@ -280,7 +281,7 @@ final class Util * @param b2 The second byte array. * @return The concatenation of b1 and b2. */ - static byte[] concat(byte[] b1, byte[] b2) + public static byte[] concat(byte[] b1, byte[] b2) { byte[] b3 = new byte[b1.length+b2.length]; System.arraycopy(b1, 0, b3, 0, b1.length); @@ -291,7 +292,7 @@ final class Util /** * See {@link #trim(byte[],int,int)}. */ - static byte[] trim(byte[] buffer, int len) + public static byte[] trim(byte[] buffer, int len) { return trim(buffer, 0, len); } @@ -309,7 +310,7 @@ final class Util * length. * @return The trimmed byte array. */ - static byte[] trim(byte[] buffer, int off, int len) + public static byte[] trim(byte[] buffer, int off, int len) { if (off < 0 || len < 0 || off > buffer.length) throw new IndexOutOfBoundsException("max=" + buffer.length + @@ -329,7 +330,7 @@ final class Util * @return The byte representation of the big integer, with any leading * zero removed. */ - static byte[] trim(BigInteger bi) + public static byte[] trim(BigInteger bi) { byte[] buf = bi.toByteArray(); if (buf[0] == 0x00 && !bi.equals(BigInteger.ZERO)) @@ -348,7 +349,7 @@ final class Util * * @return The current time, in seconds. */ - static int unixTime() + public static int unixTime() { return (int) (System.currentTimeMillis() / 1000L); } @@ -428,7 +429,7 @@ final class Util * @throws SecurityException If the Jessie code still does not have * permission to read the property. */ - static String getProperty(final String name) + @Deprecated static String getProperty(final String name) { return (String) AccessController.doPrivileged( new PrivilegedAction() @@ -450,7 +451,7 @@ final class Util * @throws SecurityException If the Jessie code still does not have * permission to read the property. */ - static String getSecurityProperty(final String name) + @Deprecated static String getSecurityProperty(final String name) { return (String) AccessController.doPrivileged( new PrivilegedAction() diff --git a/gnu/javax/net/ssl/provider/X509KeyManagerFactory.java b/gnu/javax/net/ssl/provider/X509KeyManagerFactory.java index 2e5e3ddd9..b82f5b71e 100644 --- a/gnu/javax/net/ssl/provider/X509KeyManagerFactory.java +++ b/gnu/javax/net/ssl/provider/X509KeyManagerFactory.java @@ -339,15 +339,16 @@ public class X509KeyManagerFactory extends KeyManagerFactorySpi !(pubKey instanceof RSAPublicKey)) continue; } - if (keyType.equalsIgnoreCase("DHE_DSS") + else if (keyType.equalsIgnoreCase("DHE_DSS") || keyType.equalsIgnoreCase("dss_sign") - || keyType.equalsIgnoreCase("SRP_DSS")) + || keyType.equalsIgnoreCase("SRP_DSS") + || keyType.equalsIgnoreCase("DSA")) { if (!(privKey instanceof DSAPrivateKey) || !(pubKey instanceof DSAPublicKey)) continue; } - if (keyType.equalsIgnoreCase("DH_RSA") + else if (keyType.equalsIgnoreCase("DH_RSA") || keyType.equalsIgnoreCase("rsa_fixed_dh")) { if (!(privKey instanceof DHPrivateKey) || @@ -356,7 +357,7 @@ public class X509KeyManagerFactory extends KeyManagerFactorySpi if (!chain[0].getSigAlgName().equalsIgnoreCase("RSA")) continue; } - if (keyType.equalsIgnoreCase("DH_DSS") + else if (keyType.equalsIgnoreCase("DH_DSS") || keyType.equalsIgnoreCase("dss_fixed_dh")) { if (!(privKey instanceof DHPrivateKey) || @@ -365,6 +366,8 @@ public class X509KeyManagerFactory extends KeyManagerFactorySpi if (!chain[0].getSigAlgName().equalsIgnoreCase("DSA")) continue; } + else // Unknown key type; ignore it. + continue; if (issuers == null || issuers.length == 0) { l.add(alias); |