diff options
author | Casey Marshall <csm@gnu.org> | 2006-07-12 19:11:02 +0000 |
---|---|---|
committer | Casey Marshall <csm@gnu.org> | 2006-07-12 19:11:02 +0000 |
commit | 72b81548e9dbe0b788102063f36c51155fa542f3 (patch) | |
tree | 875df06881514974087a0f3accf30f14ac270087 | |
parent | 7b7589d526048282a55c5765b6fac08920c290b9 (diff) | |
download | classpath-72b81548e9dbe0b788102063f36c51155fa542f3.tar.gz |
2006-07-12 Casey Marshall <csm@gnu.org>
* gnu/javax/net/ssl/provider/AbstractHandshake.java
(engine, inParams, outParams, tasks, serverRandom, clientRandom,
compression): new fields.
(<init>): take an SSLEngineImpl parameter; init `tasks.'
(handleInput): return NEED_TASK if we have tasks.
(getInputParams, getOutputParams): implement here; mark final.
(getTask): new method.
(checkKeyExchange): new method.
(reallocateBuffer): use `compact.'
(diffieHellmanPhase1, diffieHellmanPhase2): removed.
(DHPhase, CertVerifier): new classes.
(generateMasterSecret): add asserts.
(setupSecurityParameters): new method.
* gnu/javax/net/ssl/provider/Certificate.java (certificates): fix
reading multiple certificates.
* gnu/javax/net/ssl/provider/ClientCertificateTypeList.java:
implement Iterable<ClientCertificateType>.
(iterator): new method.
* gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java: make
public; implement Builder.
(<init>): make public.
(<init>): new constructor.
(wrap): new method.
(buffer): new method.
(publicValue): make public; use `rewind.'
(setPublicValue): use `Util.trim;' use `rewind.'
(length): return proper length.
* gnu/javax/net/ssl/provider/ClientHandshake.java: new file.
* gnu/javax/net/ssl/provider/ClientKeyExchange.java: remove unused
imports; make public, non-final.
(buffer): make protected, non-final.
(suite, version): make protected.
(<init>): make public.
(length): return 0 for NONE key exchange algorithm.
* gnu/javax/net/ssl/provider/ClientKeyExchangeBuilder.java: new
file.
* gnu/javax/net/ssl/provider/DelegatedTask.java: new file.
* gnu/javax/net/ssl/provider/DiffieHellman.java (getParams): use
AccessController instead of Util.
* gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java: make
public; implement Builder.
(<init>): make public.
(<init>): new constructor.
(buffer): new method.
(encryptedSecret): make public; fix SSLv3 handling.
(setEncryptedSecret): make public; rewind the buffer after putting
the value.
(length): fix length computation.
* gnu/javax/net/ssl/provider/ExchangeKeys.java: make public.
(buffer): make protected, non-final.
(<init>): made public; don't check null.
* gnu/javax/net/ssl/provider/Jessie.java (<init>): add "SSL" alias.
* gnu/javax/net/ssl/provider/ServerHandshake.java: clean up unused
imports.
(engine, compression, clientRandom, serverRandom, clientSessionID,
inParams, outParams, keyAgreement): moved to superclass.
(genDH, certVerifier, certLoader, keyExchangeTask): new fields.
(<init>): pass engine to superclass constructor.
(implHandleInput): throw `AlertException' when it makes sense; run
long-running tasks as delegated tasks; return NEED_TASK if we
scheduled a delegated task.
(implHandleOutput): generate keys for continued sessions; run
long-running tasks as delegated tasks; return NEED_TASK if we
scheduled a delegated task.
(status): also return NEED_TASK as appropriate.
(getInputParams, getOutputParams): removed.
(checkKeyExchange): new method.
(genDiffieHellman): removed.
(signParams): throw exceptions.
(CertLoader, GenDH, RSAKeyExchange): new classes.
* gnu/javax/net/ssl/provider/SSLContextImpl.java
(engineGetServerSocketFactory): implement.
(engineGetSocketFactory): implement.
(defaultRandom): use AccessController instead of Util.
* gnu/javax/net/ssl/provider/SSLEngineImpl.java (<init>): use
`defaultSuites.'
(defaultSuites): new method.
(startHandshake): start client handshake in client mode.
(getDelegatedTask): implement.
(unwrap, wrap): send alert if we catch an AlertException during
handshaking.
* gnu/javax/net/ssl/provider/SSLServerSocketFactoryImpl.java: new
file.
* gnu/javax/net/ssl/provider/SSLServerSocketImpl.java: new file.
* gnu/javax/net/ssl/provider/SSLSocketFactoryImpl.java: new file.
* gnu/javax/net/ssl/provider/SSLSocketImpl.java: new file.
* gnu/javax/net/ssl/provider/X509TrustManagerFactory.java
(sep, JSSE_CERTS, CA_CERTS, engineInit): use AccessController, not
Util.
(checkTrusted): don't require revocation checking.
* java/util/Collections.java (CheckedMap.entrySet): casting hack.
* java/util/concurrent/CopyOnWriteArrayList.java: new file.
23 files changed, 3507 insertions, 467 deletions
diff --git a/ChangeLog-ssl-nio b/ChangeLog-ssl-nio index bd3a86232..8e68d46b2 100644 --- a/ChangeLog-ssl-nio +++ b/ChangeLog-ssl-nio @@ -1,3 +1,98 @@ +2006-07-12 Casey Marshall <csm@gnu.org> + + * gnu/javax/net/ssl/provider/AbstractHandshake.java + (engine, inParams, outParams, tasks, serverRandom, clientRandom, + compression): new fields. + (<init>): take an SSLEngineImpl parameter; init `tasks.' + (handleInput): return NEED_TASK if we have tasks. + (getInputParams, getOutputParams): implement here; mark final. + (getTask): new method. + (checkKeyExchange): new method. + (reallocateBuffer): use `compact.' + (diffieHellmanPhase1, diffieHellmanPhase2): removed. + (DHPhase, CertVerifier): new classes. + (generateMasterSecret): add asserts. + (setupSecurityParameters): new method. + * gnu/javax/net/ssl/provider/Certificate.java (certificates): fix + reading multiple certificates. + * gnu/javax/net/ssl/provider/ClientCertificateTypeList.java: + implement Iterable<ClientCertificateType>. + (iterator): new method. + * gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java: make + public; implement Builder. + (<init>): make public. + (<init>): new constructor. + (wrap): new method. + (buffer): new method. + (publicValue): make public; use `rewind.' + (setPublicValue): use `Util.trim;' use `rewind.' + (length): return proper length. + * gnu/javax/net/ssl/provider/ClientHandshake.java: new file. + * gnu/javax/net/ssl/provider/ClientKeyExchange.java: remove unused + imports; make public, non-final. + (buffer): make protected, non-final. + (suite, version): make protected. + (<init>): make public. + (length): return 0 for NONE key exchange algorithm. + * gnu/javax/net/ssl/provider/ClientKeyExchangeBuilder.java: new + file. + * gnu/javax/net/ssl/provider/DelegatedTask.java: new file. + * gnu/javax/net/ssl/provider/DiffieHellman.java (getParams): use + AccessController instead of Util. + * gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java: make + public; implement Builder. + (<init>): make public. + (<init>): new constructor. + (buffer): new method. + (encryptedSecret): make public; fix SSLv3 handling. + (setEncryptedSecret): make public; rewind the buffer after putting + the value. + (length): fix length computation. + * gnu/javax/net/ssl/provider/ExchangeKeys.java: make public. + (buffer): make protected, non-final. + (<init>): made public; don't check null. + * gnu/javax/net/ssl/provider/Jessie.java (<init>): add "SSL" alias. + * gnu/javax/net/ssl/provider/ServerHandshake.java: clean up unused + imports. + (engine, compression, clientRandom, serverRandom, clientSessionID, + inParams, outParams, keyAgreement): moved to superclass. + (genDH, certVerifier, certLoader, keyExchangeTask): new fields. + (<init>): pass engine to superclass constructor. + (implHandleInput): throw `AlertException' when it makes sense; run + long-running tasks as delegated tasks; return NEED_TASK if we + scheduled a delegated task. + (implHandleOutput): generate keys for continued sessions; run + long-running tasks as delegated tasks; return NEED_TASK if we + scheduled a delegated task. + (status): also return NEED_TASK as appropriate. + (getInputParams, getOutputParams): removed. + (checkKeyExchange): new method. + (genDiffieHellman): removed. + (signParams): throw exceptions. + (CertLoader, GenDH, RSAKeyExchange): new classes. + * gnu/javax/net/ssl/provider/SSLContextImpl.java + (engineGetServerSocketFactory): implement. + (engineGetSocketFactory): implement. + (defaultRandom): use AccessController instead of Util. + * gnu/javax/net/ssl/provider/SSLEngineImpl.java (<init>): use + `defaultSuites.' + (defaultSuites): new method. + (startHandshake): start client handshake in client mode. + (getDelegatedTask): implement. + (unwrap, wrap): send alert if we catch an AlertException during + handshaking. + * gnu/javax/net/ssl/provider/SSLServerSocketFactoryImpl.java: new + file. + * gnu/javax/net/ssl/provider/SSLServerSocketImpl.java: new file. + * gnu/javax/net/ssl/provider/SSLSocketFactoryImpl.java: new file. + * gnu/javax/net/ssl/provider/SSLSocketImpl.java: new file. + * gnu/javax/net/ssl/provider/X509TrustManagerFactory.java + (sep, JSSE_CERTS, CA_CERTS, engineInit): use AccessController, not + Util. + (checkTrusted): don't require revocation checking. + * java/util/Collections.java (CheckedMap.entrySet): casting hack. + * java/util/concurrent/CopyOnWriteArrayList.java: new file. + 2006-07-09 Casey Marshall <csm@gnu.org> * gnu/java/io/ByteBufferOutputStream.java (write): new method. diff --git a/gnu/javax/net/ssl/provider/AbstractHandshake.java b/gnu/javax/net/ssl/provider/AbstractHandshake.java index 6166bbde2..f930f2301 100644 --- a/gnu/javax/net/ssl/provider/AbstractHandshake.java +++ b/gnu/javax/net/ssl/provider/AbstractHandshake.java @@ -39,28 +39,46 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; import gnu.classpath.ByteArray; -import gnu.classpath.Configuration; import gnu.classpath.debug.Component; import gnu.classpath.debug.SystemLogger; +import gnu.java.security.action.GetSecurityPropertyAction; import gnu.java.security.prng.IRandom; import gnu.java.security.prng.LimitReachedException; +import gnu.javax.security.auth.callback.CertificateCallback; +import gnu.javax.security.auth.callback.DefaultCallbackHandler; import java.nio.ByteBuffer; +import java.security.AccessController; import java.security.DigestException; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedExceptionAction; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashMap; -import java.util.logging.Logger; +import java.util.LinkedList; +import java.util.zip.Deflater; +import java.util.zip.Inflater; +import javax.crypto.Cipher; import javax.crypto.KeyAgreement; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; +import javax.net.ssl.X509TrustManager; import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.ConfirmationCallback; /** * The base interface for handshake implementations. Concrete @@ -149,13 +167,23 @@ public abstract class AbstractHandshake protected MessageDigest sha; protected MessageDigest md5; + protected final SSLEngineImpl engine; protected KeyAgreement keyAgreement; protected byte[] preMasterSecret; - - protected AbstractHandshake() throws NoSuchAlgorithmException + protected InputSecurityParameters inParams; + protected OutputSecurityParameters outParams; + protected LinkedList<DelegatedTask> tasks; + protected Random serverRandom; + protected Random clientRandom; + protected CompressionMethod compression; + + protected AbstractHandshake(SSLEngineImpl engine) + throws NoSuchAlgorithmException { + this.engine = engine; sha = MessageDigest.getInstance("SHA-1"); md5 = MessageDigest.getInstance("MD5"); + tasks = new LinkedList<DelegatedTask>(); } /** @@ -171,6 +199,9 @@ public abstract class AbstractHandshake public final HandshakeStatus handleInput (ByteBuffer fragment) throws SSLException { + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; + HandshakeStatus status = status(); if (status != HandshakeStatus.NEED_UNWRAP) return status; @@ -227,9 +258,12 @@ public abstract class AbstractHandshake * appropriately. * @return An {@link SSLEngineResult} describing the result. */ - public final SSLEngineResult.HandshakeStatus handleOutput (ByteBuffer fragment) + public final HandshakeStatus handleOutput (ByteBuffer fragment) throws SSLException { + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; + int orig = fragment.position(); SSLEngineResult.HandshakeStatus status = implHandleOutput(fragment); if (doHash()) @@ -266,7 +300,11 @@ public abstract class AbstractHandshake * @return The input parameters for the newly established session. * @throws SSLException If the handshake is not complete. */ - abstract InputSecurityParameters getInputParams() throws SSLException; + final InputSecurityParameters getInputParams() throws SSLException + { + checkKeyExchange(); + return inParams; + } /** * Return a new instance of output security parameters, initialized with @@ -276,7 +314,23 @@ public abstract class AbstractHandshake * @return The output parameters for the newly established session. * @throws SSLException If the handshake is not complete. */ - abstract OutputSecurityParameters getOutputParams() throws SSLException; + final OutputSecurityParameters getOutputParams() throws SSLException + { + checkKeyExchange(); + return outParams; + } + + /** + * Fetch a delegated task waiting to run, if any. + * + * @return The task. + */ + final Runnable getTask() + { + if (tasks.isEmpty()) + return null; + return tasks.removeFirst(); + } /** * Used by the skeletal code to query the current status of the handshake. @@ -286,7 +340,20 @@ public abstract class AbstractHandshake * * @return The current handshake status. */ - abstract SSLEngineResult.HandshakeStatus status(); + abstract HandshakeStatus status(); + + /** + * Check if the key exchange completed successfully, throwing an exception + * if not. + * + * <p>Note that we assume that the caller of our SSLEngine is correct, and + * that they did run the delegated tasks that encapsulate the key exchange. + * What we are primarily checking, therefore, is that no error occurred in the + * key exchange operation itself. + * + * @throws SSLException If the key exchange did not complete successfully. + */ + abstract void checkKeyExchange() throws SSLException; /** * Handle an SSLv2 client hello. This is only used by SSL servers. @@ -376,11 +443,8 @@ public abstract class AbstractHandshake // down. if (handshakeOffset > 0) { - ByteBuffer tmp = handshakeBuffer.duplicate(); - tmp.flip(); - tmp.position(handshakeOffset); - handshakeBuffer.position(0); - handshakeBuffer.put(tmp); + handshakeBuffer.flip().position(handshakeOffset); + handshakeBuffer.compact(); handshakeOffset = 0; } return; @@ -664,29 +728,116 @@ Certificate.signature.sha_hash } } - protected void diffieHellmanPhase1(DHPublicKey dhKey) throws SSLException + protected class DHPhase extends DelegatedTask { - try - { - keyAgreement.doPhase(dhKey, false); - } - catch (InvalidKeyException ike) - { - throw new SSLException(ike); - } + private final DHPublicKey key; + + protected DHPhase(DHPublicKey key) + { + this.key = key; + } + + protected void implRun() throws InvalidKeyException, SSLException + { + keyAgreement.doPhase(key, true); + preMasterSecret = keyAgreement.generateSecret(); + generateMasterSecret(clientRandom, serverRandom, engine.session()); + byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session()); + setupSecurityParameters(keys, engine.getUseClientMode(), engine, compression); + } } - protected void diffieHellmanPhase2(DHPublicKey dhKey) throws SSLException + protected class CertVerifier extends DelegatedTask { - try - { - keyAgreement.doPhase(dhKey, true); - preMasterSecret = keyAgreement.generateSecret(); - } - catch (InvalidKeyException ike) - { - throw new SSLException(ike); - } + private final boolean clientSide; + private final X509Certificate[] chain; + private boolean verified; + + protected CertVerifier(boolean clientSide, X509Certificate[] chain) + { + this.clientSide = clientSide; + this.chain = chain; + } + + boolean verified() + { + return verified; + } + + protected void implRun() + { + X509TrustManager tm = engine.contextImpl.trustManager; + if (clientSide) + { + try + { + tm.checkServerTrusted(chain, null); + verified = true; + } + catch (CertificateException ce) + { + if (Debug.DEBUG) + logger.log(Component.SSL_DELEGATED_TASK, "cert verify", ce); + // For client connections, ask the user if the certificate is OK. + CallbackHandler verify = new DefaultCallbackHandler(); + GetSecurityPropertyAction gspa + = new GetSecurityPropertyAction("jessie.certificate.handler"); + String clazz = AccessController.doPrivileged(gspa); + try + { + ClassLoader cl = + AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>() + { + public ClassLoader run() throws Exception + { + return ClassLoader.getSystemClassLoader(); + } + }); + verify = (CallbackHandler) cl.loadClass(clazz).newInstance(); + } + catch (Exception x) + { + // Ignore. + if (Debug.DEBUG) + logger.log(Component.SSL_DELEGATED_TASK, + "callback handler loading", x); + } + // XXX Internationalize + CertificateCallback confirm = + new CertificateCallback(chain[0], + "The server's certificate could not be verified. There is no proof " + + "that this server is who it claims to be, or that their certificate " + + "is valid. Do you wish to continue connecting? "); + + try + { + verify.handle(new Callback[] { confirm }); + verified = confirm.getSelectedIndex() == ConfirmationCallback.YES; + } + catch (Exception x) + { + if (Debug.DEBUG) + logger.log(Component.SSL_DELEGATED_TASK, + "callback handler exception", x); + verified = false; + } + } + } + else + { + try + { + tm.checkClientTrusted(chain, null); + } + catch (CertificateException ce) + { + verified = false; + } + } + + if (verified) + engine.session().setPeerVerified(true); + } } protected void generateMasterSecret(Random clientRandom, @@ -694,6 +845,10 @@ Certificate.signature.sha_hash SessionImpl session) throws SSLException { + assert(clientRandom != null); + assert(serverRandom != null); + assert(session != null); + if (Debug.DEBUG_KEY_EXCHANGE) logger.logv(Component.SSL_KEY_EXCHANGE, "preMasterSecret:\n{0}", new ByteArray(preMasterSecret)); @@ -771,4 +926,62 @@ Certificate.signature.sha_hash for (int i = 0; i < preMasterSecret.length; i++) preMasterSecret[i] = 0; } + + protected void setupSecurityParameters(byte[][] keys, boolean isClient, + SSLEngineImpl engine, + CompressionMethod compression) + throws SSLException + { + assert(keys.length == 6); + assert(engine != null); + assert(compression != null); + + try + { + 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[isClient ? 3 : 2], + s.cipherAlgorithm().toString()), + new IvParameterSpec(keys[isClient ? 5 : 4])); + inMac.init(new SecretKeySpec(keys[isClient ? 1 : 0], + inMac.getAlgorithm())); + inParams = new InputSecurityParameters(inCipher, inMac, + inflater, + engine.session(), s); + + 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[isClient ? 2 : 3], + s.cipherAlgorithm().toString()), + new IvParameterSpec(keys[isClient ? 4 : 5])); + outMac.init(new SecretKeySpec(keys[isClient ? 0 : 1], + outMac.getAlgorithm())); + outParams = new OutputSecurityParameters(outCipher, outMac, + deflater, + engine.session(), s); + } + catch (InvalidAlgorithmParameterException iape) + { + throw new SSLException(iape); + } + catch (InvalidKeyException ike) + { + throw new SSLException(ike); + } + catch (NoSuchAlgorithmException nsae) + { + throw new SSLException(nsae); + } + catch (NoSuchPaddingException nspe) + { + throw new SSLException(nspe); + } + } }
\ No newline at end of file diff --git a/gnu/javax/net/ssl/provider/Certificate.java b/gnu/javax/net/ssl/provider/Certificate.java index 81f5b60ca..8ff91e557 100644 --- a/gnu/javax/net/ssl/provider/Certificate.java +++ b/gnu/javax/net/ssl/provider/Certificate.java @@ -109,11 +109,11 @@ public class Certificate implements Handshake.Body int length2 = (((b.get () & 0xFF) << 16) | (b.getShort () & 0xFFFF)); byte[] buf = new byte[length2]; - buffer.position(i+3); - buffer.get (buf); + b.position(i+3); + b.get (buf); list.add(factory.generateCertificate (new ByteArrayInputStream (buf))); i += length2 + 3; - buffer.position(i); + b.position(i); } return list; } diff --git a/gnu/javax/net/ssl/provider/ClientCertificateTypeList.java b/gnu/javax/net/ssl/provider/ClientCertificateTypeList.java index eedf34342..1a1886b88 100644 --- a/gnu/javax/net/ssl/provider/ClientCertificateTypeList.java +++ b/gnu/javax/net/ssl/provider/ClientCertificateTypeList.java @@ -49,7 +49,7 @@ import java.util.ConcurrentModificationException; import java.util.ListIterator; import java.util.NoSuchElementException; -public class ClientCertificateTypeList +public class ClientCertificateTypeList implements Iterable<ClientCertificateType> { private final ByteBuffer buffer; private int modCount; @@ -74,6 +74,11 @@ public class ClientCertificateTypeList return CertificateRequest.ClientCertificateType.forValue (buffer.get (index + 1) & 0xFF); } + + public java.util.Iterator<ClientCertificateType> iterator() + { + return new Iterator(); + } public void put (final int index, final CertificateRequest.ClientCertificateType type) { diff --git a/gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java b/gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java index cccf7f1d6..8af8b850b 100644 --- a/gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java +++ b/gnu/javax/net/ssl/provider/ClientDiffieHellmanPublic.java @@ -56,40 +56,55 @@ struct { } dh_public; } ClientDiffieHellmanPublic;</pre> */ -class ClientDiffieHellmanPublic extends ExchangeKeys +public class ClientDiffieHellmanPublic extends ExchangeKeys implements Builder { - ClientDiffieHellmanPublic (final ByteBuffer buffer) + public ClientDiffieHellmanPublic(final ByteBuffer buffer) { - super (buffer); + super(buffer); + } + + public ClientDiffieHellmanPublic(final BigInteger Yc) + { + super(wrap(Yc)); + } + + private static ByteBuffer wrap(BigInteger Yc) + { + byte[] b = Util.trim(Yc); + ByteBuffer ret = ByteBuffer.allocate(b.length + 2); + ret.putShort((short) b.length); + ret.put(b); + return (ByteBuffer) ret.rewind(); } - BigInteger publicValue () + public ByteBuffer buffer() + { + return (ByteBuffer) buffer.duplicate().rewind().limit(length()); + } + + public BigInteger publicValue() { - int len = length (); + int len = length() - 2; byte[] b = new byte[len]; - buffer.position (2); - buffer.get (b); - return new BigInteger (1, b); + buffer.position(2); + buffer.get(b); + buffer.rewind(); + return new BigInteger(1, b); } - void setPublicValue (final BigInteger y) + public void setPublicValue(final BigInteger Yc) { - byte[] buf = y.toByteArray (); - int length = buf.length; - int offset = 0; - if (buf[0] == 0) - { - length--; - offset++; - } - buffer.putShort (0, (short) length); - buffer.position (2); - buffer.put (buf, offset, length); + byte[] buf = Util.trim(Yc); + if (buffer.capacity() < buf.length + 2) + buffer = ByteBuffer.allocate(buf.length + 2); + buffer.putShort((short) buf.length); + buffer.put(buf); + buffer.rewind(); } public int length () { - return buffer.getShort (0) & 0xFFFF; + return (buffer.getShort(0) & 0xFFFF) + 2; } public String toString () diff --git a/gnu/javax/net/ssl/provider/ClientHandshake.java b/gnu/javax/net/ssl/provider/ClientHandshake.java new file mode 100644 index 000000000..2b0ebf407 --- /dev/null +++ b/gnu/javax/net/ssl/provider/ClientHandshake.java @@ -0,0 +1,860 @@ +/* ClientHandshake.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 static gnu.javax.net.ssl.provider.ClientHandshake.State.*; + +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.provider.Alert.Description; +import gnu.javax.net.ssl.provider.Alert.Level; +import gnu.javax.net.ssl.provider.CertificateRequest.ClientCertificateType; + +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.security.auth.x500.X500Principal; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public class ClientHandshake extends AbstractHandshake +{ + static enum State + { + WRITE_CLIENT_HELLO (false, true), + READ_SERVER_HELLO (true, false), + READ_CERTIFICATE (true, false), + READ_SERVER_KEY_EXCHANGE (true, false), + READ_CERTIFICATE_REQUEST (true, false), + READ_SERVER_HELLO_DONE (true, false), + WRITE_CERTIFICATE (false, true), + WRITE_CLIENT_KEY_EXCHANGE (false, true), + WRITE_CERTIFICATE_VERIFY (false, true), + WRITE_FINISHED (false, true), + READ_FINISHED (true, false), + DONE (false, false); + + private final boolean isWriteState; + private final boolean isReadState; + + private State(boolean isReadState, boolean isWriteState) + { + this.isReadState = isReadState; + this.isWriteState = isWriteState; + } + + boolean isReadState() + { + return isReadState; + } + + boolean isWriteState() + { + return isWriteState; + } + } + + private State state; + private ByteBuffer outBuffer; + private boolean continuedSession; + private SessionImpl continued; + private KeyPair dhPair; + private String keyAlias; + private PrivateKey privateKey; + + // Delegated tasks. + private CertVerifier certVerifier; + private ParamsVerifier paramsVerifier; + private DelegatedTask keyExchange; + private CertLoader certLoader; + private GenCertVerify genCertVerify; + + public ClientHandshake(SSLEngineImpl engine) throws NoSuchAlgorithmException + { + super(engine); + state = WRITE_CLIENT_HELLO; + continuedSession = false; + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.AbstractHandshake#implHandleInput() + */ + @Override protected HandshakeStatus implHandleInput() throws SSLException + { + if (state == DONE) + return HandshakeStatus.FINISHED; + + if (state.isWriteState() + || (outBuffer != null && outBuffer.hasRemaining())) + return HandshakeStatus.NEED_WRAP; + + // 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 (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "processing in state {0}:\n{1}", + state, handshake); + + switch (state) + { + // Server Hello. + case READ_SERVER_HELLO: + { + if (handshake.type() != Handshake.Type.SERVER_HELLO) + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.UNEXPECTED_MESSAGE)); + ServerHello hello = (ServerHello) handshake.body(); + serverRandom = hello.random().copy(); + engine.session().suite = hello.cipherSuite(); + engine.session().version = hello.version(); + compression = hello.compressionMethod(); + Session.ID serverId = new Session.ID(hello.sessionId()); + if (continued != null + && continued.id().equals(serverId)) + { + continuedSession = true; + engine.setSession(continued); + } + else if (engine.getEnableSessionCreation()) + { + ((AbstractSessionContext) engine.contextImpl + .engineGetClientSessionContext()).put(engine.session()); + } + + if (continuedSession) + { + byte[][] keys = generateKeys(clientRandom, serverRandom, + engine.session()); + setupSecurityParameters(keys, true, engine, compression); + state = READ_FINISHED; + } + else + state = READ_CERTIFICATE; + } + break; + + // Server Certificate. + case READ_CERTIFICATE: + { + if (handshake.type() != Handshake.Type.CERTIFICATE) + { + // We need a certificate for non-anonymous suites. + if (engine.session().suite.signatureAlgorithm() != SignatureAlgorithm.ANONYMOUS) + throw new AlertException(new Alert(Level.FATAL, + Description.UNEXPECTED_MESSAGE)); + state = READ_SERVER_KEY_EXCHANGE; + } + Certificate cert = (Certificate) handshake.body(); + X509Certificate[] chain = null; + try + { + chain = cert.certificates().toArray(new X509Certificate[0]); + } + catch (CertificateException ce) + { + throw new AlertException(new Alert(Level.FATAL, + Description.BAD_CERTIFICATE), + ce); + } + catch (NoSuchAlgorithmException nsae) + { + throw new AlertException(new Alert(Level.FATAL, + Description.UNSUPPORTED_CERTIFICATE), + nsae); + } + engine.session().setPeerCertificates(chain); + certVerifier = new CertVerifier(true, chain); + tasks.add(certVerifier); + + // If we are doing an RSA key exchange, generate our parameters. + if (engine.session().suite.keyExchangeAlgorithm() + == KeyExchangeAlgorithm.RSA) + { + keyExchange = new RSAGen(); + tasks.add(keyExchange); + state = READ_CERTIFICATE_REQUEST; + } + else + state = READ_SERVER_KEY_EXCHANGE; + } + break; + + // Server Key Exchange. + case READ_SERVER_KEY_EXCHANGE: + { + CipherSuite s = engine.session().suite; + // XXX also SRP. + if (!s.isEphemeralDH() && + !(s.keyExchangeAlgorithm() == KeyExchangeAlgorithm.DIFFIE_HELLMAN + && s.signatureAlgorithm() == SignatureAlgorithm.ANONYMOUS)) + throw new AlertException(new Alert(Level.FATAL, + Description.UNEXPECTED_MESSAGE)); + + ServerKeyExchange skex = (ServerKeyExchange) handshake.body(); + ByteBuffer paramsBuffer = null; + if (s.keyExchangeAlgorithm() == KeyExchangeAlgorithm.DIFFIE_HELLMAN) + { + ServerDHParams dhParams = (ServerDHParams) skex.params(); + ByteBuffer b = dhParams.buffer(); + paramsBuffer = ByteBuffer.allocate(b.remaining()); + paramsBuffer.put(b); + } + + if (s.signatureAlgorithm() != SignatureAlgorithm.ANONYMOUS) + { + byte[] signature = skex.signature().signature(); + paramsVerifier = new ParamsVerifier(paramsBuffer, signature); + tasks.add(paramsVerifier); + } + + if (s.keyExchangeAlgorithm() == KeyExchangeAlgorithm.DIFFIE_HELLMAN) + { + ServerDHParams dhParams = (ServerDHParams) skex.params(); + DHPublicKey serverKey = new GnuDHPublicKey(null, + dhParams.p(), + dhParams.g(), + dhParams.y()); + DHParameterSpec params = new DHParameterSpec(dhParams.p(), + dhParams.g()); + keyExchange = new ClientDHGen(serverKey, params); + tasks.add(keyExchange); + } + state = READ_CERTIFICATE_REQUEST; + } + break; + + // Certificate Request. + case READ_CERTIFICATE_REQUEST: + { + if (handshake.type() != Handshake.Type.CERTIFICATE_REQUEST) + { + state = READ_SERVER_HELLO_DONE; + return HandshakeStatus.NEED_UNWRAP; + } + + CertificateRequest req = (CertificateRequest) handshake.body(); + ClientCertificateTypeList types = req.types(); + LinkedList<String> typeList = new LinkedList<String>(); + for (ClientCertificateType t : types) + typeList.add(t.name()); + + X500PrincipalList issuers = req.authorities(); + LinkedList<X500Principal> issuerList = new LinkedList<X500Principal>(); + for (X500Principal p : issuers) + issuerList.add(p); + + certLoader = new CertLoader(typeList, issuerList); + tasks.add(certLoader); + } + break; + + // Server Hello Done. + case READ_SERVER_HELLO_DONE: + { + if (handshake.type() != Handshake.Type.SERVER_HELLO_DONE) + throw new AlertException(new Alert(Level.FATAL, + Description.UNEXPECTED_MESSAGE)); + state = WRITE_CERTIFICATE; + } + break; + + // Finished. + case READ_FINISHED: + { + if (handshake.type() != Handshake.Type.FINISHED) + throw new AlertException(new Alert(Level.FATAL, + Description.UNEXPECTED_MESSAGE)); + + Finished serverFinished = (Finished) handshake.body(); + MessageDigest md5copy = null; + MessageDigest shacopy = null; + try + { + md5copy = (MessageDigest) md5.clone(); + shacopy = (MessageDigest) sha.clone(); + } + catch (CloneNotSupportedException cnse) + { + // We're improperly configured to use a non-cloneable + // md5/sha-1, OR there's a runtime bug. + throw new SSLException(cnse); + } + Finished clientFinished = + new Finished(generateFinished(md5copy, shacopy, + false, engine.session()), + engine.session().version); + + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "clientFinished: {0}", + clientFinished); + + if (engine.session().version == ProtocolVersion.SSL_3) + { + if (!Arrays.equals(clientFinished.md5Hash(), + serverFinished.md5Hash()) + || !Arrays.equals(clientFinished.shaHash(), + serverFinished.shaHash())) + { + engine.session().invalidate(); + throw new SSLException("session verify failed"); + } + } + else + { + if (!Arrays.equals(clientFinished.verifyData(), + serverFinished.verifyData())) + { + engine.session().invalidate(); + throw new SSLException("session verify failed"); + } + } + + if (continuedSession) + { + engine.changeCipherSpec(); + state = WRITE_FINISHED; + } + else + state = DONE; + } + break; + + default: + throw new IllegalStateException("invalid state: " + state); + } + + handshakeOffset += handshake.length() + 4; + + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; + if (state.isWriteState() + || (outBuffer != null && outBuffer.hasRemaining())) + return HandshakeStatus.NEED_WRAP; + if (state.isReadState()) + return HandshakeStatus.NEED_UNWRAP; + + return HandshakeStatus.FINISHED; + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.AbstractHandshake#implHandleOutput(java.nio.ByteBuffer) + */ + @Override protected HandshakeStatus implHandleOutput(ByteBuffer fragment) + throws SSLException + { + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "output to {0}; state:{1}; outBuffer:{2}", + fragment, state, outBuffer); + + // Drain the output buffer, if it needs it. + if (outBuffer != null && outBuffer.hasRemaining()) + { + int l = Math.min(fragment.remaining(), outBuffer.remaining()); + fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); + outBuffer.position(outBuffer.position() + l); + } + + if (!fragment.hasRemaining()) + { + if (state.isWriteState() || outBuffer.hasRemaining()) + return HandshakeStatus.NEED_WRAP; + else + return HandshakeStatus.NEED_UNWRAP; + } + +outer_loop: + while (fragment.remaining() >= 4 && state.isWriteState()) + { + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "loop state={0}", state); + + switch (state) + { + case WRITE_CLIENT_HELLO: + { + ClientHelloBuilder hello = new ClientHelloBuilder(); + AbstractSessionContext ctx = (AbstractSessionContext) + engine.contextImpl.engineGetClientSessionContext(); + continued = (SessionImpl) ctx.getSession(engine.getPeerHost(), + engine.getPeerPort()); + engine.session().setId(new Session.ID(new byte[0])); + Session.ID sid = engine.session().id(); + // If we have a session that we may want to continue, send + // that ID. + if (continued != null) + sid = continued.id(); + + hello.setSessionId(sid.id()); + hello.setVersion(chooseVersion()); + hello.setCipherSuites(getSuites()); + hello.setCompressionMethods(getCompressionMethods()); + Random r = hello.random(); + r.setGmtUnixTime(Util.unixTime()); + byte[] nonce = new byte[28]; + engine.session().random().nextBytes(nonce); + r.setRandomBytes(nonce); + clientRandom = r.copy(); + // XXX extensions? + + fragment.putInt((Handshake.Type.CLIENT_HELLO.getValue() << 24) + | (hello.length() & 0xFFFFFF)); + outBuffer = hello.buffer(); + int l = Math.min(fragment.remaining(), outBuffer.remaining()); + fragment.put((ByteBuffer) outBuffer.duplicate() + .limit(outBuffer.position() + l)); + outBuffer.position(outBuffer.position() + l); + + state = READ_SERVER_HELLO; + } + break; + + case WRITE_CERTIFICATE: + { + java.security.cert.Certificate[] chain + = engine.session().getLocalCertificates(); + if (chain != null) + { + CertificateBuilder cert + = new CertificateBuilder(CertificateType.X509); + try + { + cert.setCertificates(Arrays.asList(chain)); + } + catch (CertificateException ce) + { + throw new AlertException(new Alert(Level.FATAL, + Description.INTERNAL_ERROR), + ce); + } + + outBuffer = cert.buffer(); + + fragment.putInt((Handshake.Type.CERTIFICATE.getValue() << 24) + | (cert.length() & 0xFFFFFF)); + + int l = Math.min(fragment.remaining(), outBuffer.remaining()); + fragment.put((ByteBuffer) outBuffer.duplicate() + .limit(outBuffer.position() + l)); + outBuffer.position(outBuffer.position() + l); + } + state = WRITE_CLIENT_KEY_EXCHANGE; + } + break; + + case WRITE_CLIENT_KEY_EXCHANGE: + { + KeyExchangeAlgorithm kea = engine.session().suite.keyExchangeAlgorithm(); + ClientKeyExchangeBuilder ckex + = new ClientKeyExchangeBuilder(engine.session().suite, + engine.session().version); + if (kea == KeyExchangeAlgorithm.DIFFIE_HELLMAN) + { + assert(dhPair != null); + DHPublicKey pubkey = (DHPublicKey) dhPair.getPublic(); + ClientDiffieHellmanPublic pub + = new ClientDiffieHellmanPublic(pubkey.getY()); + ckex.setExchangeKeys(pub.buffer()); + } + if (kea == KeyExchangeAlgorithm.RSA) + { + assert(keyExchange instanceof RSAGen); + assert(keyExchange.hasRun()); + if (keyExchange.thrown() != null) + throw new AlertException(new Alert(Level.FATAL, + Description.HANDSHAKE_FAILURE), + keyExchange.thrown()); + EncryptedPreMasterSecret epms + = new EncryptedPreMasterSecret(((RSAGen) keyExchange).encryptedSecret(), + engine.session().version); + ckex.setExchangeKeys(epms.buffer()); + } + + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "{0}", ckex); + + outBuffer = ckex.buffer(); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, "client kex buffer {0}", outBuffer); + fragment.putInt((Handshake.Type.CLIENT_KEY_EXCHANGE.getValue() << 24) + | (ckex.length() & 0xFFFF)); + int l = Math.min(fragment.remaining(), outBuffer.remaining()); + fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); + outBuffer.position(outBuffer.position() + l); + + if (privateKey != null) + { + genCertVerify = new GenCertVerify(); + tasks.add(genCertVerify); + state = WRITE_CERTIFICATE_VERIFY; + } + else + { + engine.changeCipherSpec(); + state = WRITE_FINISHED; + } + } + // Both states terminate in a NEED_TASK, or a need to change cipher + // specs; so we can't write any more messages here. + break outer_loop; + + case WRITE_CERTIFICATE_VERIFY: + { + // XXX + engine.changeCipherSpec(); + state = WRITE_FINISHED; + } + break outer_loop; + + case WRITE_FINISHED: + { + MessageDigest md5copy = null; + MessageDigest shacopy = null; + try + { + md5copy = (MessageDigest) md5.clone(); + shacopy = (MessageDigest) sha.clone(); + } + catch (CloneNotSupportedException cnse) + { + // We're improperly configured to use a non-cloneable + // md5/sha-1, OR there's a runtime bug. + throw new SSLException(cnse); + } + outBuffer + = generateFinished(md5copy, shacopy, true, + engine.session()); + + fragment.putInt((Handshake.Type.FINISHED.getValue() << 24) + | outBuffer.remaining() & 0xFFFFFF); + + int l = Math.min(outBuffer.remaining(), fragment.remaining()); + fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); + outBuffer.position(outBuffer.position() + l); + + if (continuedSession) + state = DONE; + else + state = READ_FINISHED; + } + break; + + default: + throw new IllegalStateException("invalid state: " + state); + } + } + + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; + if (state.isWriteState() || + (outBuffer != null && outBuffer.hasRemaining())) + return HandshakeStatus.NEED_WRAP; + if (state.isReadState()) + return HandshakeStatus.NEED_UNWRAP; + + return HandshakeStatus.FINISHED; + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.AbstractHandshake#status() + */ + @Override HandshakeStatus status() + { + if (state.isReadState()) + return HandshakeStatus.NEED_UNWRAP; + if (state.isWriteState()) + return HandshakeStatus.NEED_WRAP; + return HandshakeStatus.FINISHED; + } + + @Override void checkKeyExchange() throws SSLException + { + // XXX implement. + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.AbstractHandshake#handleV2Hello(java.nio.ByteBuffer) + */ + @Override void handleV2Hello(ByteBuffer hello) throws SSLException + { + throw new SSLException("this should be impossible"); + } + + private ProtocolVersion chooseVersion() throws SSLException + { + // Select the highest enabled version, for our initial key exchange. + ProtocolVersion version = null; + for (String ver : engine.getEnabledProtocols()) + { + try + { + ProtocolVersion v = ProtocolVersion.forName(ver); + if (version == null || version.compareTo(v) < 0) + version = v; + } + catch (Exception x) + { + continue; + } + } + + if (version == null) + throw new SSLException("no suitable enabled versions"); + + return version; + } + + private List<CipherSuite> getSuites() throws SSLException + { + List<CipherSuite> suites = new LinkedList<CipherSuite>(); + for (String s : engine.getEnabledCipherSuites()) + { + CipherSuite suite = CipherSuite.forName(s); + if (suite != null) + suites.add(suite); + } + if (suites.isEmpty()) + throw new SSLException("no cipher suites enabled"); + return suites; + } + + private List<CompressionMethod> getCompressionMethods() + { + List<CompressionMethod> methods = new LinkedList<CompressionMethod>(); + GetSecurityPropertyAction gspa = new GetSecurityPropertyAction("jessie.enable.compression"); + if (Boolean.valueOf(AccessController.doPrivileged(gspa))) + methods.add(CompressionMethod.ZLIB); + methods.add(CompressionMethod.NULL); + return methods; + } + + // Delegated tasks. + + class ParamsVerifier extends DelegatedTask + { + private final ByteBuffer paramsBuffer; + private final byte[] signature; + private boolean verified; + + ParamsVerifier(ByteBuffer paramsBuffer, byte[] signature) + { + this.paramsBuffer = paramsBuffer; + this.signature = signature; + } + + public void implRun() + throws InvalidKeyException, NoSuchAlgorithmException, + SSLPeerUnverifiedException, SignatureException + { + java.security.Signature s + = java.security.Signature.getInstance(engine.session().suite + .signatureAlgorithm().algorithm()); + s.initVerify(engine.session().getPeerCertificates()[0]); + s.update(paramsBuffer); + verified = s.verify(signature); + synchronized (this) + { + notifyAll(); + } + } + + boolean verified() + { + return verified; + } + } + + class ClientDHGen extends DelegatedTask + { + private final DHPublicKey serverKey; + private final DHParameterSpec params; + + ClientDHGen(DHPublicKey serverKey, DHParameterSpec params) + { + this.serverKey = serverKey; + this.params = params; + } + + public void implRun() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, + SSLException + { + if (Debug.DEBUG) + logger.log(Component.SSL_DELEGATED_TASK, "running client DH phase"); + if (paramsVerifier != null) + { + synchronized (paramsVerifier) + { + try + { + while (!paramsVerifier.hasRun()) + paramsVerifier.wait(500); + } + catch (InterruptedException ie) + { + // Ignore. + } + } + } + KeyPairGenerator gen = KeyPairGenerator.getInstance("DH"); + gen.initialize(params, engine.session().random()); + dhPair = gen.generateKeyPair(); + if (Debug.DEBUG_KEY_EXCHANGE) + logger.logv(Component.SSL_KEY_EXCHANGE, + "client keys public:{0} private:{1}", dhPair.getPublic(), + dhPair.getPrivate()); + + // We have enough info to do the full key exchange; so let's do it. + initDiffieHellman((DHPrivateKey) dhPair.getPrivate(), engine.session().random()); + DHPhase phase = new DHPhase(serverKey); + phase.run(); + if (phase.thrown() != null) + throw new SSLException(phase.thrown()); + } + } + + class CertLoader extends DelegatedTask + { + private final List<String> keyTypes; + private final List<X500Principal> issuers; + + CertLoader(List<String> keyTypes, List<X500Principal> issuers) + { + this.keyTypes = keyTypes; + this.issuers = issuers; + } + + public void implRun() + { + X509ExtendedKeyManager km = engine.contextImpl.keyManager; + if (km == null) + return; + keyAlias = km.chooseEngineClientAlias(keyTypes.toArray(new String[keyTypes.size()]), + issuers.toArray(new X500Principal[issuers.size()]), + engine); + engine.session().setLocalCertificates(km.getCertificateChain(keyAlias)); + privateKey = km.getPrivateKey(keyAlias); + } + } + + class RSAGen extends DelegatedTask + { + private byte[] encryptedPreMasterSecret; + + public void implRun() + throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, + SSLException + { + if (certVerifier != null) + { + synchronized (certVerifier) + { + try + { + while (!certVerifier.hasRun()) + certVerifier.wait(500); + } + catch (InterruptedException ie) + { + // Ignore. + } + } + } + preMasterSecret = new byte[48]; + engine.session().random().nextBytes(preMasterSecret); + preMasterSecret[0] = (byte) engine.session().version.major(); + preMasterSecret[1] = (byte) engine.session().version.minor(); + Cipher rsa = Cipher.getInstance("RSA"); + rsa.init(Cipher.ENCRYPT_MODE, engine.session().getPeerCertificates()[0]); + encryptedPreMasterSecret = rsa.doFinal(preMasterSecret); + + // Generate our session keys, because we can. + generateMasterSecret(clientRandom, serverRandom, engine.session()); + byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session()); + setupSecurityParameters(keys, true, engine, compression); + } + + byte[] encryptedSecret() + { + return encryptedPreMasterSecret; + } + } + + class GenCertVerify extends DelegatedTask + { + public void implRun() + { + // XXX implement me. + } + } +} diff --git a/gnu/javax/net/ssl/provider/ClientKeyExchange.java b/gnu/javax/net/ssl/provider/ClientKeyExchange.java index 199905a99..439725856 100644 --- a/gnu/javax/net/ssl/provider/ClientKeyExchange.java +++ b/gnu/javax/net/ssl/provider/ClientKeyExchange.java @@ -38,24 +38,12 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; import java.io.PrintWriter; -import java.io.StringReader; import java.io.StringWriter; -import java.math.BigInteger; - import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.PublicKey; -import java.security.interfaces.RSAKey; -import javax.crypto.interfaces.DHPublicKey; - /** * The client key exchange message. * @@ -67,21 +55,21 @@ struct { } exchange_keys; } ClientKeyExchange;</pre> */ -final class ClientKeyExchange implements Handshake.Body +public class ClientKeyExchange implements Handshake.Body { // Fields. // ------------------------------------------------------------------------- - private final ByteBuffer buffer; - private final CipherSuite suite; - private final ProtocolVersion version; + protected ByteBuffer buffer; + protected final CipherSuite suite; + protected final ProtocolVersion version; // Constructors. // ------------------------------------------------------------------------- - ClientKeyExchange (final ByteBuffer buffer, final CipherSuite suite, - final ProtocolVersion version) + public ClientKeyExchange (final ByteBuffer buffer, final CipherSuite suite, + final ProtocolVersion version) { suite.getClass(); version.getClass (); @@ -93,7 +81,7 @@ final class ClientKeyExchange implements Handshake.Body // Instance methods. // ------------------------------------------------------------------------- - ExchangeKeys exchangeKeys () + public ExchangeKeys exchangeKeys () { KeyExchangeAlgorithm alg = suite.keyExchangeAlgorithm(); if (alg == KeyExchangeAlgorithm.RSA) @@ -103,9 +91,11 @@ final class ClientKeyExchange implements Handshake.Body throw new IllegalArgumentException("unsupported key exchange"); } - public int length () + public int length() { - return exchangeKeys ().length (); + if (suite.keyExchangeAlgorithm() == KeyExchangeAlgorithm.NONE) + return 0; + return exchangeKeys().length(); } public String toString () diff --git a/gnu/javax/net/ssl/provider/ClientKeyExchangeBuilder.java b/gnu/javax/net/ssl/provider/ClientKeyExchangeBuilder.java new file mode 100644 index 000000000..ebebdcc0e --- /dev/null +++ b/gnu/javax/net/ssl/provider/ClientKeyExchangeBuilder.java @@ -0,0 +1,75 @@ +/* ClientKeyExchangeBuilder.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; + +/** + * Builder for {@link ClientKeyExchange} objects. + * + * @author Casey Marshall (csm@gnu.org) + */ +public class ClientKeyExchangeBuilder extends ClientKeyExchange + implements Builder +{ + public ClientKeyExchangeBuilder(CipherSuite suite, ProtocolVersion version) + { + super(ByteBuffer.allocate(512), suite, version); + } + + /* (non-Javadoc) + * @see gnu.javax.net.ssl.provider.Builder#buffer() + */ + public ByteBuffer buffer() + { + return ((ByteBuffer) buffer.duplicate().position(0).limit(length())).slice(); + } + + public void setExchangeKeys(ByteBuffer exchangeKeys) + { + // For SSLv3 and RSA key exchange, the message is sent without length. + // So we use the precise capacity of the buffer to signal the size of + // the message. + if (buffer.capacity() < exchangeKeys.remaining() + || (suite.keyExchangeAlgorithm() == KeyExchangeAlgorithm.RSA + && version == ProtocolVersion.SSL_3)) + buffer = ByteBuffer.allocate(exchangeKeys.remaining()); + ((ByteBuffer) buffer.duplicate().position(0)).put(exchangeKeys); + } +} diff --git a/gnu/javax/net/ssl/provider/DelegatedTask.java b/gnu/javax/net/ssl/provider/DelegatedTask.java new file mode 100644 index 000000000..200d4d457 --- /dev/null +++ b/gnu/javax/net/ssl/provider/DelegatedTask.java @@ -0,0 +1,93 @@ +/* DelegatedTask.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 gnu.classpath.debug.Component; +import gnu.classpath.debug.SystemLogger; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public abstract class DelegatedTask implements Runnable +{ + private static final SystemLogger logger = SystemLogger.SYSTEM; + private boolean hasRun; + protected Throwable thrown; + + protected DelegatedTask() + { + hasRun = false; + } + + public final void run() + { + if (hasRun) + throw new IllegalStateException("task already ran"); + try + { + if (Debug.DEBUG) + logger.logv(Component.SSL_DELEGATED_TASK, + "running delegated task {0} in {1}", this, + Thread.currentThread()); + implRun(); + } + catch (Throwable t) + { + if (Debug.DEBUG) + logger.log(Component.SSL_DELEGATED_TASK, "task threw exception", t); + thrown = t; + } + finally + { + hasRun = true; + } + } + + public final boolean hasRun() + { + return hasRun; + } + + public final Throwable thrown() + { + return thrown; + } + + protected abstract void implRun() throws Throwable; +} diff --git a/gnu/javax/net/ssl/provider/DiffieHellman.java b/gnu/javax/net/ssl/provider/DiffieHellman.java index ad48c7959..5a5275712 100644 --- a/gnu/javax/net/ssl/provider/DiffieHellman.java +++ b/gnu/javax/net/ssl/provider/DiffieHellman.java @@ -39,6 +39,9 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; import java.math.BigInteger; +import java.security.AccessController; + +import gnu.java.security.action.GetSecurityPropertyAction; import gnu.javax.crypto.key.dh.GnuDHPrivateKey; /** @@ -72,7 +75,8 @@ final class DiffieHellman static GnuDHPrivateKey getParams() { BigInteger p = DiffieHellman.GROUP_5; - String group = Util.getSecurityProperty("jessie.key.dh.group"); + String group = AccessController.doPrivileged + (new GetSecurityPropertyAction("jessie.key.dh.group")); if (group != null) { group = group.trim(); diff --git a/gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java b/gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java index adb3e1ea1..edfc6f566 100644 --- a/gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java +++ b/gnu/javax/net/ssl/provider/EncryptedPreMasterSecret.java @@ -51,47 +51,66 @@ struct { public-key-encrypted PreMasterSecret pre_master_secret; } EncryptedPreMasterSecret;</pre> */ -final class EncryptedPreMasterSecret extends ExchangeKeys +public final class EncryptedPreMasterSecret extends ExchangeKeys implements Builder { private final ProtocolVersion version; - EncryptedPreMasterSecret (final ByteBuffer buffer, final ProtocolVersion version) + public EncryptedPreMasterSecret(ByteBuffer buffer, ProtocolVersion version) { - super (buffer); - version.getClass (); + super(buffer); + version.getClass(); this.version = version; } + + public EncryptedPreMasterSecret(byte[] encryptedSecret, ProtocolVersion version) + { + this(ByteBuffer.allocate(version == ProtocolVersion.SSL_3 + ? encryptedSecret.length + : encryptedSecret.length + 2), version); + ByteBuffer b = buffer.duplicate(); + if (version != ProtocolVersion.SSL_3) + b.putShort((short) encryptedSecret.length); + b.put(encryptedSecret); + } + + public ByteBuffer buffer() + { + return (ByteBuffer) buffer.duplicate().rewind(); + } - byte[] encryptedSecret () + public byte[] encryptedSecret() { byte[] secret; if (version == ProtocolVersion.SSL_3) { buffer.position (0); secret = new byte[buffer.limit ()]; + buffer.get(secret); } else { - int len = buffer.getShort (0) & 0xFFFF; + int len = buffer.getShort(0) & 0xFFFF; secret = new byte[len]; - buffer.position (2); - buffer.get (secret); + buffer.position(2); + buffer.get(secret); } return secret; } - void setEncryptedSecret (final byte[] secret, final int offset, final int length) + public void setEncryptedSecret(final byte[] secret, final int offset, final int length) { if (version == ProtocolVersion.SSL_3) { - buffer.position (0); - buffer.put (secret, offset, length); + buffer.position(0); + buffer.put(secret, offset, length); + buffer.rewind(); } else { - buffer.putShort (0, (short) length); - buffer.position (2); - buffer.put (secret, offset, length); + buffer.putShort(0, (short) length); + buffer.position(2); + buffer.put(secret, offset, length); + buffer.rewind(); } } @@ -99,11 +118,11 @@ final class EncryptedPreMasterSecret extends ExchangeKeys { if (version == ProtocolVersion.SSL_3) { - return buffer.position (0).limit (); + return buffer.capacity(); } else { - return buffer.getShort (0) & 0xFFFF; + return (buffer.getShort(0) & 0xFFFF) + 2; } } diff --git a/gnu/javax/net/ssl/provider/ExchangeKeys.java b/gnu/javax/net/ssl/provider/ExchangeKeys.java index 3a48963e0..3ee79d7d0 100644 --- a/gnu/javax/net/ssl/provider/ExchangeKeys.java +++ b/gnu/javax/net/ssl/provider/ExchangeKeys.java @@ -40,14 +40,13 @@ package gnu.javax.net.ssl.provider; import java.nio.ByteBuffer; -abstract class ExchangeKeys implements Constructed +public abstract class ExchangeKeys implements Constructed { - final ByteBuffer buffer; + protected ByteBuffer buffer; - ExchangeKeys (final ByteBuffer buffer) + public ExchangeKeys (final ByteBuffer buffer) { - buffer.getClass (); this.buffer = buffer; } }
\ No newline at end of file diff --git a/gnu/javax/net/ssl/provider/Jessie.java b/gnu/javax/net/ssl/provider/Jessie.java index 5886b6c36..66f2576b3 100644 --- a/gnu/javax/net/ssl/provider/Jessie.java +++ b/gnu/javax/net/ssl/provider/Jessie.java @@ -59,6 +59,7 @@ import java.security.Provider; */ public class Jessie extends Provider { + private static final long serialVersionUID = -1; public static final String VERSION = "2.0.0"; public static final double VERSION_DOUBLE = 2.0; @@ -80,6 +81,7 @@ public class Jessie extends Provider put("Alg.Alias.SSLContext.TLSv1", "TLSv1.1"); put("Alg.Alias.SSLContext.TLSv1.0", "TLSv1.1"); put("Alg.Alias.SSLContext.TLS", "TLSv1.1"); + put("Alg.Alias.SSLContext.SSL", "TLSv1.1"); put("KeyManagerFactory.JessieX509", X509KeyManagerFactory.class.getName()); put("TrustManagerFactory.JessieX509", X509TrustManagerFactory.class.getName()); diff --git a/gnu/javax/net/ssl/provider/SSLContextImpl.java b/gnu/javax/net/ssl/provider/SSLContextImpl.java index 22fcbd7d8..e1a8ea252 100644 --- a/gnu/javax/net/ssl/provider/SSLContextImpl.java +++ b/gnu/javax/net/ssl/provider/SSLContextImpl.java @@ -38,12 +38,12 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; +import gnu.java.security.action.GetSecurityPropertyAction; import gnu.javax.net.ssl.AbstractSessionContext; import gnu.javax.net.ssl.NullManagerParameters; import gnu.javax.net.ssl.SRPTrustManager; -import gnu.javax.net.ssl.StaticTrustAnchors; -import java.security.InvalidAlgorithmParameterException; +import java.security.AccessController; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -63,7 +63,6 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; /** @@ -71,8 +70,7 @@ import javax.net.ssl.X509TrustManager; * * @author Casey Marshall (csm@gnu.org) */ -public final class SSLContextImpl - extends SSLContextSpi +public final class SSLContextImpl extends SSLContextSpi { AbstractSessionContext serverContext; AbstractSessionContext clientContext; @@ -145,8 +143,7 @@ public final class SSLContextImpl */ protected @Override SSLServerSocketFactory engineGetServerSocketFactory() { - // TODO Auto-generated method stub - return null; + return new SSLServerSocketFactoryImpl(this); } /* (non-Javadoc) @@ -154,8 +151,7 @@ public final class SSLContextImpl */ protected @Override SSLSocketFactory engineGetSocketFactory() { - // TODO Auto-generated method stub - return null; + return new SSLSocketFactoryImpl(this); } /* (non-Javadoc) @@ -302,11 +298,11 @@ public final class SSLContextImpl */ private SecureRandom defaultRandom() { - String alg = Util.getSecurityProperty("gnu.javax.net.ssl.secureRandom"); + GetSecurityPropertyAction gspa + = new GetSecurityPropertyAction("gnu.javax.net.ssl.secureRandom"); + String alg = AccessController.doPrivileged(gspa); if (alg == null) - { - alg = "Fortuna"; - } + alg = "Fortuna"; SecureRandom rand = null; try { diff --git a/gnu/javax/net/ssl/provider/SSLEngineImpl.java b/gnu/javax/net/ssl/provider/SSLEngineImpl.java index b7f19946a..c8c55979a 100644 --- a/gnu/javax/net/ssl/provider/SSLEngineImpl.java +++ b/gnu/javax/net/ssl/provider/SSLEngineImpl.java @@ -44,8 +44,6 @@ import gnu.classpath.debug.SystemLogger; import gnu.java.io.ByteBufferOutputStream; import gnu.javax.net.ssl.Session; import gnu.javax.net.ssl.SSLRecordHandler; -import gnu.javax.net.ssl.provider.Alert.Description; -import gnu.javax.net.ssl.provider.Alert.Level; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; @@ -54,18 +52,14 @@ 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; -import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.ShortBufferException; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.Status; @@ -149,7 +143,12 @@ public final class SSLEngineImpl extends SSLEngine ProtocolVersion.TLS_1.toString(), ProtocolVersion.SSL_3.toString() }; - enabledSuites = new String[] { + enabledSuites = defaultSuites(); + } + + static String[] defaultSuites() + { + return 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(), @@ -224,8 +223,15 @@ public final class SSLEngineImpl extends SSLEngine break; case CLIENT: - throw new UnsupportedOperationException("client handshake not yet implemented"); - //break; + try + { + handshake = new ClientHandshake(this); + } + catch (NoSuchAlgorithmException nsae) + { + throw new SSLException(nsae); + } + break; } } @@ -244,7 +250,9 @@ public final class SSLEngineImpl extends SSLEngine @Override public Runnable getDelegatedTask() { - return null; + if (handshake == null) + return null; + return handshake.getTask(); } @Override @@ -266,9 +274,11 @@ public final class SSLEngineImpl extends SSLEngine } @Override - public SSLEngineResult.HandshakeStatus getHandshakeStatus() + public HandshakeStatus getHandshakeStatus() { - return handshakeStatus; + if (handshake == null) + return HandshakeStatus.NOT_HANDSHAKING; + return handshake.status(); } @Override @@ -603,7 +613,17 @@ public final class SSLEngineImpl extends SSLEngine { if (handshake == null) beginHandshake(); - handshakeStatus = handshake.handleInput(msg); + try + { + handshakeStatus = handshake.handleInput(msg); + } + catch (AlertException ae) + { + lastAlert = ae.alert(); + return new SSLEngineResult(SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, 0); + } if (Debug.DEBUG) logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", handshakeStatus); result = new SSLEngineResult(SSLEngineResult.Status.OK, @@ -658,7 +678,9 @@ public final class SSLEngineImpl extends SSLEngine ContentType type = null; ByteBuffer sysMessage = null; - + if (Debug.DEBUG) + logger.logv(Component.SSL_RECORD_LAYER, "wrap {0} {1} {2} {3} / {4}", + sources, offset, length, sink, getHandshakeStatus()); if (lastAlert != null) { type = ContentType.ALERT; @@ -675,7 +697,7 @@ public final class SSLEngineImpl extends SSLEngine sysMessage = ByteBuffer.allocate(1); sysMessage.put(0, (byte) 1); } - else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) + else if (getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) { // If we are not encrypting, optimize the handshake to fill // the buffer directly. @@ -692,8 +714,8 @@ public final class SSLEngineImpl extends SSLEngine 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); + 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 @@ -709,7 +731,16 @@ public final class SSLEngineImpl extends SSLEngine // Rough guideline; XXX. sysMessage = ByteBuffer.allocate(sink.remaining() - 2048); type = ContentType.HANDSHAKE; - handshakeStatus = handshake.handleOutput(sysMessage); + try + { + handshakeStatus = handshake.handleOutput(sysMessage); + } + catch (AlertException ae) + { + lastAlert = ae.alert(); + return new SSLEngineResult(Status.OK, + HandshakeStatus.NEED_WRAP, 0, 0); + } sysMessage.flip(); if (Debug.DEBUG) logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", diff --git a/gnu/javax/net/ssl/provider/SSLServerSocketFactoryImpl.java b/gnu/javax/net/ssl/provider/SSLServerSocketFactoryImpl.java new file mode 100644 index 000000000..dc80dc782 --- /dev/null +++ b/gnu/javax/net/ssl/provider/SSLServerSocketFactoryImpl.java @@ -0,0 +1,108 @@ +/* SSLServerSocketFactoryImpl.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.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import javax.net.ssl.SSLServerSocketFactory; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public class SSLServerSocketFactoryImpl extends SSLServerSocketFactory +{ + private final SSLContextImpl contextImpl; + + public SSLServerSocketFactoryImpl(SSLContextImpl contextImpl) + { + this.contextImpl = contextImpl; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocketFactory#getDefaultCipherSuites() + */ + @Override public String[] getDefaultCipherSuites() + { + return SSLEngineImpl.defaultSuites(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocketFactory#getSupportedCipherSuites() + */ + @Override public String[] getSupportedCipherSuites() + { + return CipherSuite.availableSuiteNames().toArray(new String[0]); + } + + /* (non-Javadoc) + * @see javax.net.ServerSocketFactory#createServerSocket(int) + */ + @Override public SSLServerSocketImpl createServerSocket(int port) + throws IOException + { + SSLServerSocketImpl socket = new SSLServerSocketImpl(contextImpl); + socket.bind(new InetSocketAddress(port)); + return socket; + } + + /* (non-Javadoc) + * @see javax.net.ServerSocketFactory#createServerSocket(int, int) + */ + @Override public SSLServerSocketImpl createServerSocket(int port, int backlog) + throws IOException + { + SSLServerSocketImpl socket = new SSLServerSocketImpl(contextImpl); + socket.bind(new InetSocketAddress(port), backlog); + return socket; + } + + /* (non-Javadoc) + * @see javax.net.ServerSocketFactory#createServerSocket(int, int, java.net.InetAddress) + */ + @Override public SSLServerSocketImpl createServerSocket(int port, int backlog, + InetAddress bindAddress) + throws IOException + { + SSLServerSocketImpl socket = new SSLServerSocketImpl(contextImpl); + socket.bind(new InetSocketAddress(bindAddress, port), backlog); + return socket; + } +} diff --git a/gnu/javax/net/ssl/provider/SSLServerSocketImpl.java b/gnu/javax/net/ssl/provider/SSLServerSocketImpl.java new file mode 100644 index 000000000..41ef5f1cf --- /dev/null +++ b/gnu/javax/net/ssl/provider/SSLServerSocketImpl.java @@ -0,0 +1,199 @@ +/* SSLServerSocketImpl.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.io.IOException; + +import javax.net.ssl.SSLServerSocket; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public class SSLServerSocketImpl extends SSLServerSocket +{ + private final SSLContextImpl contextImpl; + + private boolean enableSessionCreation; + private String[] enabledCipherSuites; + private String[] enabledProtocols; + private boolean needClientAuth; + private boolean wantClientAuth; + private boolean clientMode; + + public SSLServerSocketImpl(SSLContextImpl contextImpl) throws IOException + { + super(); + this.contextImpl = contextImpl; + enableSessionCreation = true; + enabledCipherSuites = SSLEngineImpl.defaultSuites(); + enabledProtocols = new String[] { ProtocolVersion.SSL_3.toString(), + ProtocolVersion.TLS_1.toString(), + ProtocolVersion.TLS_1_1.toString() }; + needClientAuth = false; + wantClientAuth = false; + clientMode = false; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getEnableSessionCreation() + */ + @Override public boolean getEnableSessionCreation() + { + return enableSessionCreation; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getEnabledCipherSuites() + */ + @Override public String[] getEnabledCipherSuites() + { + return (String[]) enabledCipherSuites.clone(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getEnabledProtocols() + */ + @Override public String[] getEnabledProtocols() + { + return (String[]) enabledProtocols.clone(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getNeedClientAuth() + */ + @Override public boolean getNeedClientAuth() + { + return needClientAuth; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getSupportedCipherSuites() + */ + @Override public String[] getSupportedCipherSuites() + { + return CipherSuite.availableSuiteNames().toArray(new String[0]); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getSupportedProtocols() + */ + @Override public String[] getSupportedProtocols() + { + return new String[] { ProtocolVersion.SSL_3.toString(), + ProtocolVersion.TLS_1.toString(), + ProtocolVersion.TLS_1_1.toString() }; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getUseClientMode() + */ + @Override public boolean getUseClientMode() + { + return clientMode; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#getWantClientAuth() + */ + @Override public boolean getWantClientAuth() + { + return wantClientAuth; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setEnableSessionCreation(boolean) + */ + @Override public void setEnableSessionCreation(final boolean enabled) + { + enableSessionCreation = enabled; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setEnabledCipherSuites(java.lang.String[]) + */ + @Override public void setEnabledCipherSuites(final String[] suites) + { + enabledCipherSuites = (String[]) suites.clone(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setEnabledProtocols(java.lang.String[]) + */ + @Override public void setEnabledProtocols(final String[] protocols) + { + enabledProtocols = (String[]) protocols.clone(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setNeedClientAuth(boolean) + */ + @Override public void setNeedClientAuth(final boolean needAuth) + { + needClientAuth = needAuth; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setUseClientMode(boolean) + */ + @Override public void setUseClientMode(final boolean clientMode) + { + this.clientMode = clientMode; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLServerSocket#setWantClientAuth(boolean) + */ + @Override public void setWantClientAuth(final boolean wantAuth) + { + wantClientAuth = wantAuth; + } + + @Override public SSLSocketImpl accept() throws IOException + { + SSLSocketImpl socketImpl = new SSLSocketImpl(contextImpl, null, -1); + implAccept(socketImpl); + socketImpl.setEnableSessionCreation(enableSessionCreation); + socketImpl.setEnabledCipherSuites(enabledCipherSuites); + socketImpl.setEnabledProtocols(enabledProtocols); + socketImpl.setNeedClientAuth(needClientAuth); + socketImpl.setUseClientMode(clientMode); + socketImpl.setWantClientAuth(wantClientAuth); + return socketImpl; + } +} diff --git a/gnu/javax/net/ssl/provider/SSLSocketFactoryImpl.java b/gnu/javax/net/ssl/provider/SSLSocketFactoryImpl.java new file mode 100644 index 000000000..6c804f9c6 --- /dev/null +++ b/gnu/javax/net/ssl/provider/SSLSocketFactoryImpl.java @@ -0,0 +1,137 @@ +/* SSLSocketFactoryImpl.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.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLSocketFactory; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public class SSLSocketFactoryImpl extends SSLSocketFactory +{ + /** + * The SSLContextImpl that created us. + */ + private final SSLContextImpl contextImpl; + + public SSLSocketFactoryImpl(SSLContextImpl contextImpl) + { + this.contextImpl = contextImpl; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket, java.lang.String, int, boolean) + */ + @Override public Socket createSocket(Socket socket, String host, int port, + boolean autoClose) + throws IOException + { + return new SSLSocketImpl(contextImpl, host, port, socket, autoClose); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites() + */ + @Override public String[] getDefaultCipherSuites() + { + return SSLEngineImpl.defaultSuites(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites() + */ + @Override public String[] getSupportedCipherSuites() + { + return CipherSuite.availableSuiteNames().toArray(new String[0]); + } + + /* (non-Javadoc) + * @see javax.net.SocketFactory#createSocket(java.lang.String, int) + */ + @Override public SSLSocketImpl createSocket(String host, int port) + throws IOException, UnknownHostException + { + SSLSocketImpl socket = new SSLSocketImpl(contextImpl, host, port); + InetSocketAddress endpoint = new InetSocketAddress(host, port); + socket.connect(endpoint); + return socket; + } + + /* (non-Javadoc) + * @see javax.net.SocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int) + */ + @Override public SSLSocketImpl createSocket(String host, int port, + InetAddress localHost, int localPort) + throws IOException, UnknownHostException + { + SSLSocketImpl socket = createSocket(host, port); + socket.bind(new InetSocketAddress(localHost, localPort)); + return socket; + } + + /* (non-Javadoc) + * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int) + */ + @Override public SSLSocketImpl createSocket(InetAddress host, int port) + throws IOException + { + SSLSocketImpl socket = new SSLSocketImpl(contextImpl, + host.getCanonicalHostName(), port); + socket.connect(new InetSocketAddress(host, port)); + return socket; + } + + /* (non-Javadoc) + * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int, java.net.InetAddress, int) + */ + @Override public SSLSocketImpl createSocket(InetAddress host, int port, + InetAddress localHost, int localPort) + throws IOException + { + SSLSocketImpl socket = createSocket(host, port); + socket.bind(new InetSocketAddress(localHost, localPort)); + return socket; + } +} diff --git a/gnu/javax/net/ssl/provider/SSLSocketImpl.java b/gnu/javax/net/ssl/provider/SSLSocketImpl.java new file mode 100644 index 000000000..284094314 --- /dev/null +++ b/gnu/javax/net/ssl/provider/SSLSocketImpl.java @@ -0,0 +1,814 @@ +/* SSLSocketImpl.java -- implementation of an SSL client socket. + 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 java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.HashSet; +import java.util.Set; + +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; + +/** + * @author Casey Marshall (csm@gnu.org) + */ +public class SSLSocketImpl extends SSLSocket +{ + private class SocketOutputStream extends OutputStream + { + private final ByteBuffer buffer; + private final OutputStream out; + + SocketOutputStream() throws IOException + { + buffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + if (underlyingSocket != null) + out = underlyingSocket.getOutputStream(); + else + out = SSLSocketImpl.super.getOutputStream(); + } + + @Override public void write(byte[] buf, int off, int len) throws IOException + { + if (!initialHandshakeDone + || engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) + doHandshake(); + + int k = 0; + while (k < len) + { + synchronized (engine) + { + int l = Math.min(len-k, getSession().getApplicationBufferSize()); + ByteBuffer in = ByteBuffer.wrap(buf, off+k, l); + SSLEngineResult result = engine.wrap(in, buffer); + if (result.getStatus() == Status.CLOSED) + return; + if (result.getStatus() != Status.OK) + throw new SSLException("unexpected SSL state " + result.getStatus()); + buffer.flip(); + out.write(buffer.array(), 0, buffer.limit()); + k += result.bytesConsumed(); + } + } + } + + @Override public void write(int b) throws IOException + { + write(new byte[] { (byte) b }); + } + + @Override public void close() throws IOException + { + SSLSocketImpl.this.close(); + } + } + + private class SocketInputStream extends InputStream + { + private final ByteBuffer inBuffer; + private final ByteBuffer appBuffer; + private final DataInputStream in; + + SocketInputStream() throws IOException + { + inBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + inBuffer.limit(0); + appBuffer = ByteBuffer.allocate(getSession().getApplicationBufferSize()); + appBuffer.flip(); + if (underlyingSocket != null) + in = new DataInputStream(underlyingSocket.getInputStream()); + else + in = new DataInputStream(SSLSocketImpl.super.getInputStream()); + } + + @Override public int read(byte[] buf, int off, int len) throws IOException + { + if (!initialHandshakeDone || + engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) + doHandshake(); + + if (!appBuffer.hasRemaining()) + { + int x = in.read(); + if (x == -1) + return -1; + inBuffer.clear(); + inBuffer.put((byte) x); + inBuffer.putInt(in.readInt()); + int reclen = inBuffer.getShort(3) & 0xFFFF; + in.readFully(inBuffer.array(), 5, reclen); + inBuffer.position(0).limit(reclen + 5); + synchronized (engine) + { + appBuffer.clear(); + SSLEngineResult result = engine.unwrap(inBuffer, appBuffer); + Status status = result.getStatus(); + if (status == Status.CLOSED && result.bytesProduced() == 0) + return -1; + } + inBuffer.compact(); + appBuffer.flip(); + } + int l = Math.min(len, appBuffer.remaining()); + appBuffer.get(buf, off, l); + return l; + } + + @Override public int read() throws IOException + { + byte[] b = new byte[1]; + if (read(b) == -1) + return -1; + return b[0] & 0xFF; + } + } + + private static final SystemLogger logger = SystemLogger.getSystemLogger(); + + private SSLEngineImpl engine; + private Set<HandshakeCompletedListener> listeners; + private Socket underlyingSocket; + private boolean isHandshaking; + private IOException handshakeException; + private boolean initialHandshakeDone = false; + private final boolean autoClose; + + public SSLSocketImpl(SSLContextImpl contextImpl, String host, int port) + { + this(contextImpl, host, port, null, false); + } + + public SSLSocketImpl(SSLContextImpl contextImpl, String host, int port, + Socket underlyingSocket, boolean autoClose) + { + engine = new SSLEngineImpl(contextImpl, host, port); + engine.setUseClientMode(true); // default to client mode + listeners = new HashSet<HandshakeCompletedListener>(); + this.underlyingSocket = underlyingSocket; + this.autoClose = autoClose; + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#addHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener) + */ + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) + { + listeners.add(listener); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getEnableSessionCreation() + */ + @Override public boolean getEnableSessionCreation() + { + return engine.getEnableSessionCreation(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getEnabledCipherSuites() + */ + @Override public String[] getEnabledCipherSuites() + { + return engine.getEnabledCipherSuites(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getEnabledProtocols() + */ + @Override public String[] getEnabledProtocols() + { + return engine.getEnabledProtocols(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getNeedClientAuth() + */ + @Override public boolean getNeedClientAuth() + { + return engine.getNeedClientAuth(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getSession() + */ + @Override public SSLSession getSession() + { + return engine.getSession(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getSupportedCipherSuites() + */ + @Override public String[] getSupportedCipherSuites() + { + return engine.getSupportedCipherSuites(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getSupportedProtocols() + */ + @Override public String[] getSupportedProtocols() + { + return engine.getSupportedProtocols(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getUseClientMode() + */ + @Override public boolean getUseClientMode() + { + return engine.getUseClientMode(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#getWantClientAuth() + */ + @Override public boolean getWantClientAuth() + { + return engine.getWantClientAuth(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(javax.net.ssl.HandshakeCompletedListener) + */ + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) + { + listeners.remove(listener); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean) + */ + @Override public void setEnableSessionCreation(boolean enable) + { + engine.setEnableSessionCreation(enable); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setEnabledCipherSuites(java.lang.String[]) + */ + @Override public void setEnabledCipherSuites(String[] suites) + { + engine.setEnabledCipherSuites(suites); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setEnabledProtocols(java.lang.String[]) + */ + @Override public void setEnabledProtocols(String[] protocols) + { + engine.setEnabledProtocols(protocols); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setNeedClientAuth(boolean) + */ + @Override public void setNeedClientAuth(boolean needAuth) + { + engine.setNeedClientAuth(needAuth); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setUseClientMode(boolean) + */ + @Override public void setUseClientMode(boolean clientMode) + { + engine.setUseClientMode(clientMode); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#setWantClientAuth(boolean) + */ + @Override public void setWantClientAuth(boolean wantAuth) + { + engine.setWantClientAuth(wantAuth); + } + + /* (non-Javadoc) + * @see javax.net.ssl.SSLSocket#startHandshake() + */ + @Override public void startHandshake() throws IOException + { + if (isHandshaking) + return; + + if (handshakeException != null) + throw handshakeException; + + Thread t = new Thread(new Runnable() + { + public void run() + { + try + { + doHandshake(); + } + catch (IOException ioe) + { + handshakeException = ioe; + } + } + }, "HandshakeThread@" + System.identityHashCode(this)); + t.start(); + } + + void doHandshake() throws IOException + { + synchronized (engine) + { + if (isHandshaking) + { + try + { + engine.wait(); + } + catch (InterruptedException ie) + { + } + return; + } + isHandshaking = true; + } + + if (initialHandshakeDone) + throw new SSLException("rehandshaking not yet implemented"); + + long now = -System.currentTimeMillis(); + engine.beginHandshake(); + + HandshakeStatus status = engine.getHandshakeStatus(); + assert(status != HandshakeStatus.NOT_HANDSHAKING); + + ByteBuffer inBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + inBuffer.position(inBuffer.limit()); + ByteBuffer outBuffer = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + ByteBuffer emptyBuffer = ByteBuffer.allocate(0); + SSLEngineResult result = null; + + DataInputStream sockIn = null; + if (underlyingSocket != null) + sockIn = new DataInputStream(underlyingSocket.getInputStream()); + else + sockIn = new DataInputStream(super.getInputStream()); + + OutputStream sockOut = null; + if (underlyingSocket != null) + sockOut = underlyingSocket.getOutputStream(); + else + sockOut = super.getOutputStream(); + + while (status != HandshakeStatus.NOT_HANDSHAKING + && status != HandshakeStatus.FINISHED) + { + logger.logv(Component.SSL_HANDSHAKE, "socket processing state {0}", + status); + + if (inBuffer.capacity() != getSession().getPacketBufferSize()) + { + ByteBuffer b + = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + if (inBuffer.hasRemaining()) + b.put(inBuffer).flip(); + inBuffer = b; + } + if (outBuffer.capacity() != getSession().getPacketBufferSize()) + outBuffer + = ByteBuffer.wrap(new byte[getSession().getPacketBufferSize()]); + + switch (status) + { + case NEED_UNWRAP: + // Read in a single SSL record. + inBuffer.clear(); + int i = sockIn.read(); + if (i == -1) + throw new EOFException(); + if ((i & 0x80) == 0x80) // SSLv2 client hello. + { + inBuffer.put((byte) i); + int v2len = (i & 0x7f) << 8; + i = sockIn.read(); + v2len = v2len | (i & 0xff); + inBuffer.put((byte) i); + sockIn.readFully(inBuffer.array(), 2, v2len); + inBuffer.position(0).limit(v2len + 2); + } + else + { + inBuffer.put((byte) i); + inBuffer.putInt(sockIn.readInt()); + int reclen = inBuffer.getShort(3) & 0xFFFF; + sockIn.readFully(inBuffer.array(), 5, reclen); + inBuffer.position(0).limit(reclen + 5); + } + result = engine.unwrap(inBuffer, emptyBuffer); + status = result.getHandshakeStatus(); + if (result.getStatus() != Status.OK) + throw new SSLException("unexpected SSL status " + + result.getStatus()); + break; + + case NEED_WRAP: + { + outBuffer.clear(); + result = engine.wrap(emptyBuffer, outBuffer); + status = result.getHandshakeStatus(); + if (result.getStatus() != Status.OK) + throw new SSLException("unexpected SSL status " + + result.getStatus()); + outBuffer.flip(); + sockOut.write(outBuffer.array(), outBuffer.position(), + outBuffer.limit()); + } + break; + + case NEED_TASK: + { + Runnable task; + while ((task = engine.getDelegatedTask()) != null) + task.run(); + status = engine.getHandshakeStatus(); + } + break; + + case FINISHED: + break; + } + } + + initialHandshakeDone = true; + + HandshakeCompletedEvent hce = new HandshakeCompletedEvent(this, getSession()); + for (HandshakeCompletedListener l : listeners) + { + try + { + l.handshakeCompleted(hce); + } + catch (ThreadDeath td) + { + throw td; + } + catch (Throwable x) + { + logger.log(Component.WARNING, + "HandshakeCompletedListener threw exception", x); + } + } + + now += System.currentTimeMillis(); + if (Debug.DEBUG) + logger.logv(Component.SSL_HANDSHAKE, + "handshake completed in {0}ms in thread {1}", now, + Thread.currentThread().getName()); + + synchronized (engine) + { + isHandshaking = false; + engine.notifyAll(); + } + } + + // Methods overriding Socket. + + @Override public void bind(SocketAddress bindpoint) throws IOException + { + if (underlyingSocket != null) + underlyingSocket.bind(bindpoint); + else + super.bind(bindpoint); + } + + @Override public void connect(SocketAddress endpoint) throws IOException + { + if (underlyingSocket != null) + underlyingSocket.connect(endpoint); + else + super.connect(endpoint); + } + + @Override public void connect(SocketAddress endpoint, int timeout) + throws IOException + { + if (underlyingSocket != null) + underlyingSocket.connect(endpoint, timeout); + else + super.connect(endpoint, timeout); + } + + @Override public InetAddress getInetAddress() + { + if (underlyingSocket != null) + return underlyingSocket.getInetAddress(); + return super.getInetAddress(); + } + + @Override public InetAddress getLocalAddress() + { + if (underlyingSocket != null) + return underlyingSocket.getLocalAddress(); + return super.getLocalAddress(); + } + + @Override public int getPort() + { + if (underlyingSocket != null) + return underlyingSocket.getPort(); + return super.getPort(); + } + + @Override public int getLocalPort() + { + if (underlyingSocket != null) + return underlyingSocket.getLocalPort(); + return super.getLocalPort(); + } + + @Override public SocketAddress getRemoteSocketAddress() + { + if (underlyingSocket != null) + return underlyingSocket.getRemoteSocketAddress(); + return super.getRemoteSocketAddress(); + } + + public SocketAddress getLocalSocketAddress() + { + if (underlyingSocket != null) + return underlyingSocket.getLocalSocketAddress(); + return super.getLocalSocketAddress(); + } + + @Override public SocketChannel getChannel() + { + throw new UnsupportedOperationException("use javax.net.ssl.SSLEngine for NIO"); + } + + @Override public InputStream getInputStream() throws IOException + { + return new SocketInputStream(); + } + + @Override public OutputStream getOutputStream() throws IOException + { + return new SocketOutputStream(); + } + + @Override public void setTcpNoDelay(boolean on) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setTcpNoDelay(on); + else + super.setTcpNoDelay(on); + } + + @Override public boolean getTcpNoDelay() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getTcpNoDelay(); + return super.getTcpNoDelay(); + } + + @Override public void setSoLinger(boolean on, int linger) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setSoLinger(on, linger); + else + super.setSoLinger(on, linger); + } + + public int getSoLinger() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getSoLinger(); + return super.getSoLinger(); + } + + @Override public void sendUrgentData(int x) throws IOException + { + throw new UnsupportedOperationException("not supported"); + } + + @Override public void setOOBInline(boolean on) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setOOBInline(on); + else + super.setOOBInline(on); + } + + @Override public boolean getOOBInline() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getOOBInline(); + return super.getOOBInline(); + } + + @Override public void setSoTimeout(int timeout) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setSoTimeout(timeout); + else + super.setSoTimeout(timeout); + } + + @Override public int getSoTimeout() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getSoTimeout(); + return super.getSoTimeout(); + } + + @Override public void setSendBufferSize(int size) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setSendBufferSize(size); + else + super.setSendBufferSize(size); + } + + @Override public int getSendBufferSize() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getSendBufferSize(); + return super.getSendBufferSize(); + } + + @Override public void setReceiveBufferSize(int size) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setReceiveBufferSize(size); + else + underlyingSocket.setReceiveBufferSize(size); + } + + @Override public int getReceiveBufferSize() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getReceiveBufferSize(); + return super.getReceiveBufferSize(); + } + + @Override public void setKeepAlive(boolean on) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setKeepAlive(on); + else + super.setKeepAlive(on); + } + + @Override public boolean getKeepAlive() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getKeepAlive(); + return super.getKeepAlive(); + } + + @Override public void setTrafficClass(int tc) throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setTrafficClass(tc); + else + super.setTrafficClass(tc); + } + + @Override public int getTrafficClass() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getTrafficClass(); + return super.getTrafficClass(); + } + + @Override public void setReuseAddress(boolean reuseAddress) + throws SocketException + { + if (underlyingSocket != null) + underlyingSocket.setReuseAddress(reuseAddress); + else + super.setReuseAddress(reuseAddress); + } + + @Override public boolean getReuseAddress() throws SocketException + { + if (underlyingSocket != null) + return underlyingSocket.getReuseAddress(); + return super.getReuseAddress(); + } + + @Override public void close() throws IOException + { + // XXX closure alerts. + if (underlyingSocket != null && autoClose) + underlyingSocket.close(); + else + super.close(); + } + + @Override public void shutdownInput() throws IOException + { + if (underlyingSocket != null) + underlyingSocket.shutdownInput(); + else + super.shutdownInput(); + } + + @Override public void shutdownOutput() throws IOException + { + if (underlyingSocket != null) + underlyingSocket.shutdownOutput(); + else + super.shutdownOutput(); + } + + @Override public boolean isConnected() + { + if (underlyingSocket != null) + return underlyingSocket.isConnected(); + return super.isConnected(); + } + + @Override public boolean isBound() + { + if (underlyingSocket != null) + return underlyingSocket.isBound(); + return super.isBound(); + } + + @Override public boolean isClosed() + { + if (underlyingSocket != null) + return underlyingSocket.isClosed(); + return super.isClosed(); + } + + @Override public boolean isInputShutdown() + { + if (underlyingSocket != null) + return underlyingSocket.isInputShutdown(); + return super.isInputShutdown(); + } + + @Override public boolean isOutputShutdown() + { + if (underlyingSocket != null) + return underlyingSocket.isOutputShutdown(); + return super.isOutputShutdown(); + } +} diff --git a/gnu/javax/net/ssl/provider/ServerHandshake.java b/gnu/javax/net/ssl/provider/ServerHandshake.java index 047b6f306..d339c1cc5 100644 --- a/gnu/javax/net/ssl/provider/ServerHandshake.java +++ b/gnu/javax/net/ssl/provider/ServerHandshake.java @@ -38,23 +38,17 @@ exception statement from your version. */ package gnu.javax.net.ssl.provider; -import static gnu.javax.net.ssl.provider.Extension.Type.*; -import static gnu.javax.net.ssl.provider.KeyExchangeAlgorithm.*; import static gnu.javax.net.ssl.provider.ServerHandshake.State.*; -import static gnu.javax.net.ssl.provider.SignatureAlgorithm.*; 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.Alert.Description; 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; @@ -74,26 +68,18 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.logging.Level; -import java.util.zip.Deflater; -import java.util.zip.Inflater; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyAgreement; -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.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.X509TrustManager; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.security.auth.x500.X500Principal; @@ -140,12 +126,7 @@ class ServerHandshake extends AbstractHandshake private State state; - private final SSLEngineImpl engine; - /* Handshake result fields. */ - private CompressionMethod compression; - private Random clientRandom; - private Random serverRandom; private ByteBuffer outBuffer; private boolean clientHadExtensions = false; private boolean continuedSession = false; @@ -154,28 +135,22 @@ class ServerHandshake extends AbstractHandshake 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; - - private KeyAgreement keyAgreement; private KeyPair dhPair; + + // Delegated tasks we use. + private GenDH genDH; + private CertVerifier certVerifier; + private CertLoader certLoader; + private DelegatedTask keyExchangeTask; ServerHandshake (boolean writeHelloRequest, final SSLEngineImpl engine) throws NoSuchAlgorithmException { + super(engine); if (writeHelloRequest) state = WRITE_HELLO_REQUEST; else state = READ_CLIENT_HELLO; - this.engine = engine; handshakeOffset = 0; } @@ -249,7 +224,6 @@ class ServerHandshake extends AbstractHandshake * implementation. * * XXX Maybe consider implementing lzo (GNUTLS supports that). - * XXX Maybe add way to disable zlib support, through properties. */ private static CompressionMethod chooseCompression (final CompressionMethodList comps) throws SSLException @@ -284,7 +258,7 @@ class ServerHandshake extends AbstractHandshake public @Override HandshakeStatus implHandleInput() throws SSLException - { + { if (state == DONE) return HandshakeStatus.FINISHED; @@ -317,9 +291,9 @@ class ServerHandshake extends AbstractHandshake // supports). case READ_CLIENT_HELLO: if (handshake.type () != CLIENT_HELLO) - throw new SSLException ("expecting client hello"); - // XXX throw better exception. - + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.UNEXPECTED_MESSAGE)); + { ClientHello hello = (ClientHello) handshake.body (); engine.session().version @@ -426,8 +400,8 @@ class ServerHandshake extends AbstractHandshake = cert.certificates().toArray(new X509Certificate[0]); if (chain.length == 0) throw new CertificateException("no certificates in chain"); - X509TrustManager tm = engine.contextImpl.trustManager; - tm.checkClientTrusted(chain, null); + certVerifier = new CertVerifier(false, chain); + tasks.add(certVerifier); engine.session().setPeerCertificates(chain); clientCert = chain[0]; // Delay setting 'peerVerified' until CertificateVerify. @@ -474,114 +448,24 @@ class ServerHandshake extends AbstractHandshake new GnuDHPublicKey(null, myKey.getParams().getP(), myKey.getParams().getG(), pub.publicValue()); - diffieHellmanPhase2(clientKey); + keyExchangeTask = new DHPhase(clientKey); + tasks.add(keyExchangeTask); } + if (engine.session().suite.keyExchangeAlgorithm() == KeyExchangeAlgorithm.RSA) { EncryptedPreMasterSecret secret = (EncryptedPreMasterSecret) kex.exchangeKeys(); - Cipher rsa = null; - try - { - rsa = Cipher.getInstance("RSA"); - rsa.init(Cipher.DECRYPT_MODE, localCert); - } - catch (InvalidKeyException ike) - { - throw new SSLException(ike); - } - catch (NoSuchAlgorithmException nsae) - { - throw new SSLException(nsae); - } - catch (NoSuchPaddingException nspe) - { - // Shouldn't happen; RSA only has one padding here. - throw new SSLException(nspe); - } - - try - { - preMasterSecret = rsa.doFinal(secret.encryptedSecret()); - } - catch (BadPaddingException bpe) - { - throw new SSLException(bpe); - } - catch (IllegalBlockSizeException ibse) - { - throw new SSLException(ibse); - } + keyExchangeTask = new RSAKeyExchange(secret.encryptedSecret()); + tasks.add(keyExchangeTask); } // XXX SRP - // Generate the master keys. - generateMasterSecret(clientRandom, serverRandom, engine.session()); - - // Initialize our security parameters. - byte[][] keys = generateKeys(clientRandom, serverRandom, - engine.session()); - try - { - 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], - s.cipherAlgorithm().toString()), - new IvParameterSpec(keys[4])); - inMac.init(new SecretKeySpec(keys[0], - inMac.getAlgorithm())); - inParams = new InputSecurityParameters(inCipher, inMac, - inflater, - engine.session(), s); - - 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], - s.cipherAlgorithm().toString()), - new IvParameterSpec(keys[5])); - outMac.init(new SecretKeySpec(keys[1], - outMac.getAlgorithm())); - outParams = new OutputSecurityParameters(outCipher, outMac, - deflater, - engine.session(), s); - } - catch (InvalidAlgorithmParameterException iape) - { - throw new SSLException(iape); - } - catch (InvalidKeyException ike) - { - throw new SSLException(ike); - } - catch (NoSuchAlgorithmException nsae) - { - throw new SSLException(nsae); - } - catch (NoSuchPaddingException nspe) - { - throw new SSLException(nspe); - } - if (clientCert != null) state = READ_CERTIFICATE_VERIFY; else - { - if (continuedSession) - { - engine.changeCipherSpec(); - state = WRITE_FINISHED; - } - else - state = READ_FINISHED; - } + state = READ_FINISHED; } break; @@ -602,7 +486,8 @@ class ServerHandshake extends AbstractHandshake try { verifyClient(verify.signature()); - engine.session().setPeerVerified(true); + if (certVerifier != null && certVerifier.verified()) + engine.session().setPeerVerified(true); } catch (SignatureException se) { @@ -634,10 +519,9 @@ class ServerHandshake extends AbstractHandshake case READ_FINISHED: { if (handshake.type() != FINISHED) - { - throw new SSLException("expecting FINISHED message"); - } - + throw new AlertException(new Alert(Alert.Level.FATAL, + Description.UNEXPECTED_MESSAGE)); + Finished clientFinished = (Finished) handshake.body(); MessageDigest md5copy = null; @@ -696,12 +580,14 @@ class ServerHandshake extends AbstractHandshake handshakeOffset += handshake.length() + 4; + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; if (state.isReadState()) return HandshakeStatus.NEED_UNWRAP; if (state.isWriteState()) return HandshakeStatus.NEED_WRAP; - return HandshakeStatus.FINISHED; // XXX ??? + return HandshakeStatus.FINISHED; } public @Override HandshakeStatus implHandleOutput (ByteBuffer fragment) @@ -818,15 +704,15 @@ output_loop: case WRITE_SERVER_HELLO: { ServerHelloBuilder hello = new ServerHelloBuilder(); - hello.setVersion (engine.session().version); + hello.setVersion(engine.session().version); Random r = hello.random(); - r.setGmtUnixTime ((int) (System.currentTimeMillis() / 1000)); + r.setGmtUnixTime(Util.unixTime()); byte[] nonce = new byte[28]; engine.session().random().nextBytes(nonce); r.setRandomBytes(nonce); - serverRandom = r.copy (); + serverRandom = r.copy(); hello.setSessionId(engine.session().getId()); - hello.setCipherSuite (engine.session().suite); + hello.setCipherSuite(engine.session().suite); hello.setCompressionMethod(compression); if (clientHadExtensions) { @@ -847,15 +733,38 @@ output_loop: fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); outBuffer.position(outBuffer.position() + l); + if (continuedSession) { + byte[][] keys = generateKeys(clientRandom, serverRandom, + engine.session()); + setupSecurityParameters(keys, false, engine, compression); engine.changeCipherSpec(); state = WRITE_FINISHED; } + else if (engine.session().suite.signatureAlgorithm() + != SignatureAlgorithm.ANONYMOUS) + { + certLoader = new CertLoader(); + tasks.add(certLoader); + state = WRITE_CERTIFICATE; + break output_loop; + } + else if (engine.session().suite.isEphemeralDH()) + { + genDH = new GenDH(); + tasks.add(genDH); + state = WRITE_SERVER_KEY_EXCHANGE; + break output_loop; + } + else if (engine.getWantClientAuth() || engine.getNeedClientAuth()) + { + state = WRITE_CERTIFICATE_REQUEST; + } else - state = WRITE_CERTIFICATE; + state = WRITE_SERVER_HELLO_DONE; } - break output_loop; // XXX temporary + break; // Certificate. // @@ -864,54 +773,15 @@ output_loop: // itself (usually, servers must authenticate). case WRITE_CERTIFICATE: { - if (engine.session().suite.keyExchangeAlgorithm() == null) - { - state = WRITE_SERVER_KEY_EXCHANGE; - break; - } - String sigAlg = null; - switch (engine.session().suite.keyExchangeAlgorithm()) - { - case NONE: - break; - - case RSA: - sigAlg = "RSA"; - break; - - case DIFFIE_HELLMAN: - if (engine.session().suite.isEphemeralDH()) - sigAlg = "DHE_"; - else - sigAlg = "DH_"; - switch (engine.session().suite.signatureAlgorithm()) - { - case RSA: - sigAlg += "RSA"; - break; - - case DSA: - sigAlg += "DSS"; - break; - } - - case SRP: - switch (engine.session().suite.signatureAlgorithm()) - { - case RSA: - sigAlg = "SRP_RSA"; - break; - - case DSA: - sigAlg = "SRP_DSS"; - break; - } - } - X509ExtendedKeyManager km = engine.contextImpl.keyManager; - Principal[] issuers = null; // XXX use TrustedAuthorities extension. - keyAlias = km.chooseEngineServerAlias(sigAlg, issuers, engine); - X509Certificate[] chain = km.getCertificateChain(keyAlias); - + // We must have scheduled a certificate loader to run. + assert(certLoader != null); + assert(certLoader.hasRun()); + if (certLoader.thrown() != null) + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.HANDSHAKE_FAILURE), + certLoader.thrown()); + java.security.cert.Certificate[] chain + = engine.session().getLocalCertificates(); CertificateBuilder cert = new CertificateBuilder(CertificateType.X509); try { @@ -921,8 +791,6 @@ output_loop: { throw new SSLException(ce); } - engine.session().setLocalCertificates(chain); - localCert = chain[0]; if (Debug.DEBUG) { @@ -939,7 +807,22 @@ output_loop: fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); outBuffer.position(outBuffer.position() + l); - state = WRITE_SERVER_KEY_EXCHANGE; + CipherSuite s = engine.session().suite; + if (s.isEphemeralDH() + || (s.keyExchangeAlgorithm() == KeyExchangeAlgorithm.DIFFIE_HELLMAN + && s.signatureAlgorithm() == SignatureAlgorithm.ANONYMOUS)) + { + genDH = new GenDH(); + tasks.add(genDH); + state = WRITE_SERVER_KEY_EXCHANGE; + break output_loop; + } + else if (engine.getWantClientAuth() || engine.getNeedClientAuth()) + { + state = WRITE_CERTIFICATE_REQUEST; + } + else + state = WRITE_SERVER_HELLO_DONE; } break output_loop; // XXX temporary @@ -954,58 +837,46 @@ output_loop: { KeyExchangeAlgorithm kex = engine.session().suite.keyExchangeAlgorithm(); + ByteBuffer paramBuffer = null; + ByteBuffer sigBuffer = null; if (kex == KeyExchangeAlgorithm.DIFFIE_HELLMAN) { - genDiffieHellman(); + assert(genDH != null); + assert(genDH.hasRun()); + if (genDH.thrown() != null) + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.HANDSHAKE_FAILURE), + genDH.thrown()); + assert(dhPair != null); initDiffieHellman((DHPrivateKey) dhPair.getPrivate(), engine.session().random()); + paramBuffer = genDH.paramsBuffer; + sigBuffer = genDH.sigBuffer; } // XXX handle SRP - if (kex != KeyExchangeAlgorithm.NONE - && kex != KeyExchangeAlgorithm.RSA - && engine.session().suite.isEphemeralDH()) - { - // This key exchange requires server params; construct them. - ByteBuffer paramBuffer = null; + ServerKeyExchangeBuilder ske + = new ServerKeyExchangeBuilder(engine.session().suite); + ske.setParams(paramBuffer); + if (sigBuffer != null) + ske.setSignature(sigBuffer); - if (kex == KeyExchangeAlgorithm.DIFFIE_HELLMAN) - { - DHPublicKey pubKey = (DHPublicKey) dhPair.getPublic(); - ServerDHParams params = - new ServerDHParams(pubKey.getParams().getP(), - 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.duplicate()); - ServerKeyExchangeBuilder ske - = new ServerKeyExchangeBuilder(engine.session().suite); - ske.setParams(paramBuffer); - ske.setSignature(sigBuffer); - - if (Debug.DEBUG) - logger.log(Component.SSL_HANDSHAKE, "{0}", ske); + if (Debug.DEBUG) + logger.log(Component.SSL_HANDSHAKE, "{0}", ske); - 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); - } + 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); if (engine.getWantClientAuth() || engine.getNeedClientAuth()) state = WRITE_CERTIFICATE_REQUEST; else state = WRITE_SERVER_HELLO_DONE; } - break output_loop; // XXX temporary + break; // Certificate Request. // @@ -1048,7 +919,7 @@ output_loop: state = WRITE_SERVER_HELLO_DONE; } - break output_loop; // XXX temporary + break; // Server Hello Done. // @@ -1115,6 +986,8 @@ output_loop: break; } } + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; if (state.isWriteState() || outBuffer.hasRemaining()) return HandshakeStatus.NEED_WRAP; if (state.isReadState()) @@ -1125,6 +998,8 @@ output_loop: @Override HandshakeStatus status() { + if (!tasks.isEmpty()) + return HandshakeStatus.NEED_TASK; if (state.isReadState()) return HandshakeStatus.NEED_UNWRAP; if (state.isWriteState()) @@ -1132,19 +1007,21 @@ output_loop: return HandshakeStatus.FINISHED; } - - @Override InputSecurityParameters getInputParams() throws SSLException - { - if (inParams == null) - throw new SSLException("premature usage of input security parameters"); - return inParams; - } - - @Override OutputSecurityParameters getOutputParams() throws SSLException + + @Override void checkKeyExchange() throws SSLException { - if (outParams == null) - throw new SSLException("premature usage of output security parameters"); - return outParams; + if (continuedSession) // No key exchange needed. + return; + if (keyExchangeTask == null) // An error if we never created one. + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.INTERNAL_ERROR)); + if (!keyExchangeTask.hasRun()) // An error if the caller never ran it. + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.INTERNAL_ERROR)); + if (keyExchangeTask.thrown() != null) // An error was thrown. + throw new AlertException(new Alert(Alert.Level.FATAL, + Alert.Description.HANDSHAKE_FAILURE), + keyExchangeTask.thrown()); } @Override void handleV2Hello(ByteBuffer hello) @@ -1154,84 +1031,23 @@ output_loop: sha.update((ByteBuffer) hello.duplicate().position(2).limit(len+2)); helloV2 = true; } - - /** - * Generate, or fetch from our certificate, the Diffie-Hellman exchange - * parameters. - * - * @throws SSLException - */ - private void genDiffieHellman() throws SSLException - { - try - { - // 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"); - DHParameterSpec dhparams = DiffieHellman.getParams().getParams(); - dhGen.initialize(dhparams, engine.session().random()); - dhPair = dhGen.generateKeyPair(); - } - else - { - X509Certificate cert = (X509Certificate) - engine.session().getLocalCertificates()[0]; - PrivateKey key = engine.contextImpl.keyManager.getPrivateKey(keyAlias); - dhPair = new KeyPair(cert.getPublicKey(), key); - } - 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); - } - } - - private ByteBuffer signParams(ByteBuffer serverParams) throws SSLException + private ByteBuffer signParams(ByteBuffer serverParams) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - try - { - SignatureAlgorithm alg = engine.session().suite.signatureAlgorithm(); - java.security.Signature sig - = 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, engine.session().suite.signatureAlgorithm()); - return signature.buffer(); - } - catch (NoSuchAlgorithmException nsae) - { - throw new SSLException(nsae); - } - catch (InvalidKeyException ike) - { - throw new SSLException(ike); - } - catch (SignatureException se) - { - throw new SSLException(se); - } + SignatureAlgorithm alg = engine.session().suite.signatureAlgorithm(); + java.security.Signature sig + = 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, engine.session().suite.signatureAlgorithm()); + return signature.buffer(); } private void verifyClient(byte[] sigValue) throws SSLException, SignatureException @@ -1275,4 +1091,126 @@ output_loop: throw new SSLException(nsae); } } + + // Delegated tasks. + + class CertLoader extends DelegatedTask + { + CertLoader() + { + } + + public void implRun() throws SSLException + { + String sigAlg = null; + switch (engine.session().suite.keyExchangeAlgorithm()) + { + case NONE: + break; + + case RSA: + sigAlg = "RSA"; + break; + + case DIFFIE_HELLMAN: + if (engine.session().suite.isEphemeralDH()) + sigAlg = "DHE_"; + else + sigAlg = "DH_"; + switch (engine.session().suite.signatureAlgorithm()) + { + case RSA: + sigAlg += "RSA"; + break; + + case DSA: + sigAlg += "DSS"; + break; + } + + case SRP: + switch (engine.session().suite.signatureAlgorithm()) + { + case RSA: + sigAlg = "SRP_RSA"; + break; + + case DSA: + sigAlg = "SRP_DSS"; + break; + } + } + X509ExtendedKeyManager km = engine.contextImpl.keyManager; + Principal[] issuers = null; // XXX use TrustedAuthorities extension. + keyAlias = km.chooseEngineServerAlias(sigAlg, issuers, engine); + if (keyAlias == null) + throw new SSLException("no certificates available"); + X509Certificate[] chain = km.getCertificateChain(keyAlias); + engine.session().setLocalCertificates(chain); + localCert = chain[0]; + if (engine.session().suite.keyExchangeAlgorithm() == KeyExchangeAlgorithm.DIFFIE_HELLMAN + && !engine.session().suite.isEphemeralDH()) + dhPair = new KeyPair(localCert.getPublicKey(), + km.getPrivateKey(keyAlias)); + } + } + + /** + * Delegated task for generating Diffie-Hellman parameters. + */ + private class GenDH extends DelegatedTask + { + ByteBuffer paramsBuffer; + ByteBuffer sigBuffer; + + protected void implRun() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, + InvalidKeyException, SignatureException + { + KeyPairGenerator dhGen = KeyPairGenerator.getInstance("DH"); + DHParameterSpec dhparams = DiffieHellman.getParams().getParams(); + dhGen.initialize(dhparams, engine.session().random()); + dhPair = dhGen.generateKeyPair(); + DHPublicKey pub = (DHPublicKey) dhPair.getPublic(); + + // Generate the parameters message. + ServerDHParams params = new ServerDHParams(pub.getParams().getP(), + pub.getParams().getG(), + pub.getY()); + paramsBuffer = params.buffer(); + + // Sign the parameters, if needed. + if (engine.session().suite.signatureAlgorithm() != SignatureAlgorithm.ANONYMOUS) + { + sigBuffer = signParams(paramsBuffer); + paramsBuffer.rewind(); + } + if (Debug.DEBUG_KEY_EXCHANGE) + logger.logv(Component.SSL_KEY_EXCHANGE, + "Diffie-Hellman public:{0} private:{1}", + dhPair.getPublic(), dhPair.getPrivate()); + } + } + + class RSAKeyExchange extends DelegatedTask + { + private final byte[] encryptedPreMasterSecret; + + RSAKeyExchange(byte[] encryptedPreMasterSecret) + { + this.encryptedPreMasterSecret = encryptedPreMasterSecret; + } + + public void implRun() + throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, SSLException + { + Cipher rsa = Cipher.getInstance("RSA"); + rsa.init(Cipher.DECRYPT_MODE, localCert); + preMasterSecret = rsa.doFinal(encryptedPreMasterSecret); + generateMasterSecret(clientRandom, serverRandom, engine.session()); + byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session()); + setupSecurityParameters(keys, false, engine, compression); + } + } }
\ No newline at end of file diff --git a/gnu/javax/net/ssl/provider/X509TrustManagerFactory.java b/gnu/javax/net/ssl/provider/X509TrustManagerFactory.java index b41c8bc3a..1a0591284 100644 --- a/gnu/javax/net/ssl/provider/X509TrustManagerFactory.java +++ b/gnu/javax/net/ssl/provider/X509TrustManagerFactory.java @@ -47,6 +47,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.Set; +import java.security.AccessController; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -65,6 +66,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactorySpi; import javax.net.ssl.X509TrustManager; +import gnu.java.security.action.GetPropertyAction; import gnu.java.security.x509.X509CertPath; import gnu.javax.net.ssl.NullManagerParameters; import gnu.javax.net.ssl.StaticTrustAnchors; @@ -79,19 +81,22 @@ public class X509TrustManagerFactory extends TrustManagerFactorySpi // Constants and fields. // ------------------------------------------------------------------------- - private static final String sep = Util.getProperty("file.separator"); + private static final String sep + = AccessController.doPrivileged(new GetPropertyAction("file.separator")); /** * The location of the JSSE key store. */ - private static final String JSSE_CERTS = Util.getProperty("java.home") - + sep + "lib" + sep + "security" + sep + "jssecerts"; + private static final String JSSE_CERTS + = AccessController.doPrivileged(new GetPropertyAction("java.home")) + + sep + "lib" + sep + "security" + sep + "jssecerts"; /** * The location of the system key store, containing the CA certs. */ - private static final String CA_CERTS = Util.getProperty("java.home") - + sep + "lib" + sep + "security" + sep + "cacerts"; + private static final String CA_CERTS + = AccessController.doPrivileged(new GetPropertyAction("java.home")) + + sep + "lib" + sep + "security" + sep + "cacerts"; private Manager current; @@ -136,13 +141,14 @@ public class X509TrustManagerFactory extends TrustManagerFactorySpi { if (store == null) { - String s = Util.getProperty("javax.net.ssl.trustStoreType"); + GetPropertyAction gpa = new GetPropertyAction("javax.net.ssl.trustStoreType"); + String s = AccessController.doPrivileged(gpa); if (s == null) s = KeyStore.getDefaultType(); store = KeyStore.getInstance(s); try { - s = Util.getProperty("javax.net.ssl.trustStore"); + s = AccessController.doPrivileged(gpa.setParameters("javax.net.ssl.trustStore")); FileInputStream in = null; if (s == null) { @@ -159,20 +165,20 @@ public class X509TrustManagerFactory extends TrustManagerFactorySpi { in = new FileInputStream(s); } - String p = Util.getProperty("javax.net.ssl.trustStorePassword"); + String p = AccessController.doPrivileged(gpa.setParameters("javax.net.ssl.trustStorePassword")); store.load(in, p != null ? p.toCharArray() : null); } catch (IOException ioe) { - throw new KeyStoreException(ioe.toString()); + throw new KeyStoreException(ioe); } catch (CertificateException ce) { - throw new KeyStoreException(ce.toString()); + throw new KeyStoreException(ce); } catch (NoSuchAlgorithmException nsae) { - throw new KeyStoreException(nsae.toString()); + throw new KeyStoreException(nsae); } } @@ -263,6 +269,9 @@ public class X509TrustManagerFactory extends TrustManagerFactorySpi try { params = new PKIXParameters(anchors); + // XXX we probably do want to enable revocation, but it's a pain + // in the ass. + params.setRevocationEnabled(false); } catch (InvalidAlgorithmParameterException iape) { diff --git a/java/util/Collections.java b/java/util/Collections.java index 40268379a..9c91bf279 100644 --- a/java/util/Collections.java +++ b/java/util/Collections.java @@ -6619,7 +6619,7 @@ public class Collections if (entries == null) { Class<Map.Entry<K,V>> klass = - (Class<Map.Entry<K,V>>) Map.Entry.class; + (Class<Map.Entry<K,V>>) (Class) Map.Entry.class; entries = new CheckedEntrySet<Map.Entry<K,V>,K,V>(m.entrySet(), klass, keyType, diff --git a/java/util/concurrent/CopyOnWriteArrayList.java b/java/util/concurrent/CopyOnWriteArrayList.java new file mode 100644 index 000000000..ee9f7ac3a --- /dev/null +++ b/java/util/concurrent/CopyOnWriteArrayList.java @@ -0,0 +1,438 @@ +/* CopyOnWriteArrayList.java + Copyright (C) 2006 Free Software Foundation + +This file is 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, 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; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, 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 java.util.concurrent; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +/** @since 1.5 */ +public class CopyOnWriteArrayList<E> extends AbstractList<E> implements + List<E>, RandomAccess, Cloneable, Serializable +{ + /** + * Where the data is stored. + */ + private transient E[] data; + + /** + * Construct a new ArrayList with the default capacity (16). + */ + public CopyOnWriteArrayList() + { + data = (E[]) new Object[0]; + } + + /** + * Construct a new ArrayList, and initialize it with the elements in the + * supplied Collection. The initial capacity is 110% of the Collection's size. + * + * @param c + * the collection whose elements will initialize this list + * @throws NullPointerException + * if c is null + */ + public CopyOnWriteArrayList(Collection< ? extends E> c) + { + // FIXME ... correct? use c.toArray() + data = (E[]) new Object[c.size()]; + int index = 0; + for (E value : c) + data[index++] = value; + } + + /** + * Returns the number of elements in this list. + * + * @return the list size + */ + public int size() + { + return data.length; + } + + /** + * Checks if the list is empty. + * + * @return true if there are no elements + */ + public boolean isEmpty() + { + return data.length == 0; + } + + /** + * Returns true iff element is in this ArrayList. + * + * @param e + * the element whose inclusion in the List is being tested + * @return true if the list contains e + */ + public boolean contains(Object e) + { + return indexOf(e) != -1; + } + + /** + * Returns the lowest index at which element appears in this List, or -1 if it + * does not appear. + * + * @param e + * the element whose inclusion in the List is being tested + * @return the index where e was found + */ + public int indexOf(Object e) + { + E[] data = this.data; + for (int i = 0; i < data.length; i++) + if (equals(e, data[i])) + return i; + return -1; + } + + /** + * Returns the highest index at which element appears in this List, or -1 if + * it does not appear. + * + * @param e + * the element whose inclusion in the List is being tested + * @return the index where e was found + */ + public int lastIndexOf(Object e) + { + E[] data = this.data; + for (int i = data.length - 1; i >= 0; i--) + if (equals(e, data[i])) + return i; + return -1; + } + + /** + * Creates a shallow copy of this ArrayList (elements are not cloned). + * + * @return the cloned object + */ + public Object clone() + { + CopyOnWriteArrayList<E> clone = null; + try + { + clone = (CopyOnWriteArrayList<E>) super.clone(); + clone.data = (E[]) data.clone(); + } + catch (CloneNotSupportedException e) + { + // Impossible to get here. + } + return clone; + } + + /** + * Returns an Object array containing all of the elements in this ArrayList. + * The array is independent of this list. + * + * @return an array representation of this list + */ + public Object[] toArray() + { + E[] data = this.data; + E[] array = (E[]) new Object[data.length]; + System.arraycopy(data, 0, array, 0, data.length); + return array; + } + + /** + * Returns an Array whose component type is the runtime component type of the + * passed-in Array. The returned Array is populated with all of the elements + * in this ArrayList. If the passed-in Array is not large enough to store all + * of the elements in this List, a new Array will be created and returned; if + * the passed-in Array is <i>larger</i> than the size of this List, then + * size() index will be set to null. + * + * @param a + * the passed-in Array + * @return an array representation of this list + * @throws ArrayStoreException + * if the runtime type of a does not allow an element in this list + * @throws NullPointerException + * if a is null + */ + public <T> T[] toArray(T[] a) + { + E[] data = this.data; + if (a.length < data.length) + a = (T[]) Array.newInstance(a.getClass().getComponentType(), data.length); + else if (a.length > data.length) + a[data.length] = null; + System.arraycopy(data, 0, a, 0, data.length); + return a; + } + + /** + * Retrieves the element at the user-supplied index. + * + * @param index + * the index of the element we are fetching + * @throws IndexOutOfBoundsException + * if index < 0 || index >= size() + */ + public E get(int index) + { + return data[index]; + } + + /** + * Sets the element at the specified index. The new element, e, can be an + * object of any type or null. + * + * @param index + * the index at which the element is being set + * @param e + * the element to be set + * @return the element previously at the specified index + * @throws IndexOutOfBoundsException + * if index < 0 || index >= 0 + */ + public synchronized E set(int index, E e) + { + E result = data[index]; + E[] newData = data.clone(); + newData[index] = e; + data = newData; + return result; + } + + /** + * Appends the supplied element to the end of this list. The element, e, can + * be an object of any type or null. + * + * @param e + * the element to be appended to this list + * @return true, the add will always succeed + */ + public synchronized boolean add(E e) + { + E[] data = this.data; + E[] newData = (E[]) new Object[data.length]; + System.arraycopy(data, 0, newData, 0, data.length); + newData[data.length] = e; + this.data = newData; + return true; + } + + /** + * Adds the supplied element at the specified index, shifting all elements + * currently at that index or higher one to the right. The element, e, can be + * an object of any type or null. + * + * @param index + * the index at which the element is being added + * @param e + * the item being added + * @throws IndexOutOfBoundsException + * if index < 0 || index > size() + */ + public synchronized void add(int index, E e) + { + E[] data = this.data; + E[] newData = (E[]) new Object[data.length]; + System.arraycopy(data, 0, newData, 0, index); + newData[index] = e; + System.arraycopy(data, index, newData, index + 1, data.length - index); + this.data = newData; + } + + /** + * Removes the element at the user-supplied index. + * + * @param index + * the index of the element to be removed + * @return the removed Object + * @throws IndexOutOfBoundsException + * if index < 0 || index >= size() + */ + public synchronized E remove(int index) + { + E[] data = this.data; + E[] newData = (E[]) new Object[data.length - 1]; + System.arraycopy(data, 0, newData, 0, index - 1); + System.arraycopy(data, index + 1, newData, index, + data.length - index - 1); + E r = data[index]; + this.data = newData; + return r; + } + + /** + * Removes all elements from this List + */ + public synchronized void clear() + { + data = (E[]) new Object[0]; + } + + /** + * Add each element in the supplied Collection to this List. It is undefined + * what happens if you modify the list while this is taking place; for + * example, if the collection contains this list. c can contain objects of any + * type, as well as null values. + * + * @param c + * a Collection containing elements to be added to this List + * @return true if the list was modified, in other words c is not empty + * @throws NullPointerException + * if c is null + */ + public synchronized boolean addAll(Collection< ? extends E> c) + { + return addAll(data.length, c); + } + + /** + * Add all elements in the supplied collection, inserting them beginning at + * the specified index. c can contain objects of any type, as well as null + * values. + * + * @param index + * the index at which the elements will be inserted + * @param c + * the Collection containing the elements to be inserted + * @throws IndexOutOfBoundsException + * if index < 0 || index > 0 + * @throws NullPointerException + * if c is null + */ + public synchronized boolean addAll(int index, Collection< ? extends E> c) + { + E[] data = this.data; + Iterator<? extends E> itr = c.iterator(); + int csize = c.size(); + if (csize == 0) + return false; + + E[] newData = (E[]) new Object[data.length + csize]; + System.arraycopy(data, 0, newData, 0, data.length); + int end = data.length; + for (E value : c) + newData[end++] = value; + this.data = newData; + return true; + } + + public synchronized boolean addIfAbsent(E val) + { + if (contains(val)) + return false; + add(val); + return true; + } + + public synchronized int addAllAbsent(Collection<? extends E> c) + { + int result = 0; + for (E val : c) + { + if (addIfAbsent(val)) + ++result; + } + return result; + } + + /** + * Serializes this object to the given stream. + * + * @param s + * the stream to write to + * @throws IOException + * if the underlying stream fails + * @serialData the size field (int), the length of the backing array (int), + * followed by its elements (Objects) in proper order. + */ + private void writeObject(ObjectOutputStream s) throws IOException + { + // The 'size' field. + s.defaultWriteObject(); + // We serialize unused list entries to preserve capacity. + int len = data.length; + s.writeInt(len); + // it would be more efficient to just write "size" items, + // this need readObject read "size" items too. + for (int i = 0; i < data.length; i++) + s.writeObject(data[i]); + } + + /** + * Deserializes this object from the given stream. + * + * @param s + * the stream to read from + * @throws ClassNotFoundException + * if the underlying stream fails + * @throws IOException + * if the underlying stream fails + * @serialData the size field (int), the length of the backing array (int), + * followed by its elements (Objects) in proper order. + */ + private void readObject(ObjectInputStream s) throws IOException, + ClassNotFoundException + { + // the `size' field. + s.defaultReadObject(); + int capacity = s.readInt(); + data = (E[]) new Object[capacity]; + for (int i = 0; i < capacity; i++) + data[i] = (E) s.readObject(); + } + + static final boolean equals(Object o1, Object o2) + { + return o1 == null ? o2 == null : o1.equals(o2); + } + + Object[] getArray() + { + return data; + } +} |