| /* 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 static gnu.javax.net.ssl.provider.KeyExchangeAlgorithm.*; |
| |
| 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 gnu.javax.net.ssl.provider.ServerNameList.NameType; |
| import gnu.javax.net.ssl.provider.ServerNameList.ServerName; |
| |
| 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.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| 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.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; |
| private MaxFragmentLength maxFragmentLengthSent; |
| private boolean truncatedHMacSent; |
| private ProtocolVersion sentVersion; |
| |
| // 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()); |
| } |
| ExtensionList extensions = hello.extensions(); |
| if (extensions != null) |
| { |
| for (Extension extension : extensions) |
| { |
| Extension.Type type = extension.type(); |
| if (type == null) |
| continue; |
| switch (type) |
| { |
| case MAX_FRAGMENT_LENGTH: |
| MaxFragmentLength mfl |
| = (MaxFragmentLength) extension.value(); |
| if (maxFragmentLengthSent == mfl) |
| engine.session().setApplicationBufferSize(mfl.maxLength()); |
| break; |
| |
| case TRUNCATED_HMAC: |
| if (truncatedHMacSent) |
| engine.session().setTruncatedMac(true); |
| break; |
| } |
| } |
| } |
| |
| KeyExchangeAlgorithm kex = engine.session().suite.keyExchangeAlgorithm(); |
| if (continuedSession) |
| { |
| byte[][] keys = generateKeys(clientRandom, serverRandom, |
| engine.session()); |
| setupSecurityParameters(keys, true, engine, compression); |
| state = READ_FINISHED; |
| } |
| else if (kex == RSA || kex == DH_DSS || kex == DH_RSA |
| || kex == DHE_DSS || kex == DHE_RSA || kex == RSA_PSK) |
| state = READ_CERTIFICATE; |
| else if (kex == DH_anon || kex == PSK || kex == DHE_PSK) |
| state = READ_SERVER_KEY_EXCHANGE; |
| else |
| state = READ_CERTIFICATE_REQUEST; |
| } |
| 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. |
| KeyExchangeAlgorithm kea = engine.session().suite.keyExchangeAlgorithm(); |
| if (kea == RSA || kea == RSA_PSK) |
| { |
| keyExchange = new RSAGen(kea == RSA); |
| tasks.add(keyExchange); |
| if (kea == RSA) |
| state = READ_CERTIFICATE_REQUEST; |
| else |
| state = READ_SERVER_KEY_EXCHANGE; |
| } |
| else |
| state = READ_SERVER_KEY_EXCHANGE; |
| } |
| break; |
| |
| // Server Key Exchange. |
| case READ_SERVER_KEY_EXCHANGE: |
| { |
| CipherSuite s = engine.session().suite; |
| KeyExchangeAlgorithm kexalg = s.keyExchangeAlgorithm(); |
| // XXX also SRP. |
| if (kexalg != DHE_DSS && kexalg != DHE_RSA && kexalg != DH_anon |
| && kexalg != DHE_PSK && kexalg != PSK && kexalg != RSA_PSK) |
| throw new AlertException(new Alert(Level.FATAL, |
| Description.UNEXPECTED_MESSAGE)); |
| |
| if (handshake.type() != Handshake.Type.SERVER_KEY_EXCHANGE) |
| { |
| if (kexalg != RSA_PSK && kexalg != PSK) |
| throw new AlertException(new Alert(Level.FATAL, |
| Description.UNEXPECTED_MESSAGE)); |
| state = READ_CERTIFICATE_REQUEST; |
| return HandshakeStatus.NEED_UNWRAP; |
| } |
| |
| ServerKeyExchange skex = (ServerKeyExchange) handshake.body(); |
| ByteBuffer paramsBuffer = null; |
| if (kexalg == DHE_DSS || kexalg == DHE_RSA || kexalg == DH_anon) |
| { |
| 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 (kexalg == DHE_DSS || kexalg == DHE_RSA || kexalg == DH_anon) |
| { |
| 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, true); |
| tasks.add(keyExchange); |
| } |
| if (kexalg == DHE_PSK) |
| { |
| ServerDHE_PSKParameters pskParams = (ServerDHE_PSKParameters) |
| skex.params(); |
| ServerDHParams dhParams = pskParams.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, false); |
| 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()); |
| sentVersion = chooseVersion(); |
| hello.setVersion(sentVersion); |
| 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(); |
| if (enableExtensions()) |
| { |
| List<Extension> extensions = new LinkedList<Extension>(); |
| MaxFragmentLength fraglen = maxFragmentLength(); |
| if (fraglen != null) |
| { |
| extensions.add(new Extension(Extension.Type.MAX_FRAGMENT_LENGTH, |
| fraglen)); |
| maxFragmentLengthSent = fraglen; |
| } |
| |
| String host = engine.getPeerHost(); |
| if (host != null) |
| { |
| ServerName name |
| = new ServerName(NameType.HOST_NAME, host); |
| ServerNameList names |
| = new ServerNameList(Collections.singletonList(name)); |
| extensions.add(new Extension(Extension.Type.SERVER_NAME, |
| names)); |
| } |
| |
| if (truncatedHMac()) |
| { |
| extensions.add(new Extension(Extension.Type.TRUNCATED_HMAC, |
| new TruncatedHMAC())); |
| truncatedHMacSent = true; |
| } |
| |
| ExtensionList elist = new ExtensionList(extensions); |
| hello.setExtensions(elist.buffer()); |
| } |
| else |
| hello.setDisableExtensions(true); |
| |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_HANDSHAKE, "{0}", hello); |
| |
| 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 == DHE_DSS || kea == DHE_RSA || kea == DH_anon |
| || kea == DH_DSS || kea == DH_RSA) |
| { |
| assert(dhPair != null); |
| DHPublicKey pubkey = (DHPublicKey) dhPair.getPublic(); |
| ClientDiffieHellmanPublic pub |
| = new ClientDiffieHellmanPublic(pubkey.getY()); |
| ckex.setExchangeKeys(pub.buffer()); |
| } |
| if (kea == RSA || kea == RSA_PSK) |
| { |
| 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); |
| if (kea == RSA) |
| ckex.setExchangeKeys(epms.buffer()); |
| else |
| { |
| String identity = getPSKIdentity(); |
| if (identity == null) |
| throw new SSLException("no pre-shared-key identity;" |
| + " set the security property" |
| + " \"jessie.client.psk.identity\""); |
| ClientRSA_PSKParameters params = |
| new ClientRSA_PSKParameters(identity, epms.buffer()); |
| ckex.setExchangeKeys(params.buffer()); |
| generatePSKSecret(identity, preMasterSecret, true); |
| } |
| } |
| if (kea == DHE_PSK) |
| { |
| assert(keyExchange instanceof ClientDHGen); |
| assert(dhPair != null); |
| String identity = getPSKIdentity(); |
| if (identity == null) |
| throw new SSLException("no pre-shared key identity; set" |
| + " the security property" |
| + " \"jessie.client.psk.identity\""); |
| DHPublicKey pubkey = (DHPublicKey) dhPair.getPublic(); |
| ClientDHE_PSKParameters params = |
| new ClientDHE_PSKParameters(identity, |
| new ClientDiffieHellmanPublic(pubkey.getY())); |
| ckex.setExchangeKeys(params.buffer()); |
| generatePSKSecret(identity, preMasterSecret, true); |
| } |
| if (kea == PSK) |
| { |
| String identity = getPSKIdentity(); |
| if (identity == null) |
| throw new SSLException("no pre-shared key identity; set" |
| + " the security property" |
| + " \"jessie.client.psk.identity\""); |
| generatePSKSecret(identity, null, true); |
| ClientPSKParameters params = new ClientPSKParameters(identity); |
| ckex.setExchangeKeys(params.buffer()); |
| } |
| if (kea == NONE) |
| { |
| Inflater inflater = null; |
| Deflater deflater = null; |
| if (compression == CompressionMethod.ZLIB) |
| { |
| inflater = new Inflater(); |
| deflater = new Deflater(); |
| } |
| inParams = new InputSecurityParameters(null, null, inflater, |
| engine.session(), |
| engine.session().suite); |
| outParams = new OutputSecurityParameters(null, null, deflater, |
| engine.session(), |
| engine.session().suite); |
| engine.session().privateData.masterSecret = new byte[0]; |
| } |
| |
| 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() & 0xFFFFFF)); |
| 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(md5, sha); |
| 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: |
| { |
| assert(genCertVerify != null); |
| assert(genCertVerify.hasRun()); |
| CertificateVerify verify = new CertificateVerify(genCertVerify.signed(), |
| engine.session().suite.signatureAlgorithm()); |
| |
| outBuffer = verify.buffer(); |
| fragment.putInt((Handshake.Type.CERTIFICATE_VERIFY.getValue() << 24) |
| | (verify.length() & 0xFFFFFF)); |
| int l = Math.min(fragment.remaining(), outBuffer.remaining()); |
| fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l)); |
| outBuffer.position(outBuffer.position() + l); |
| |
| // XXX This is a potential problem: we may not have drained |
| // outBuffer, but set the changeCipherSpec toggle. |
| 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; |
| } |
| |
| private boolean enableExtensions() |
| { |
| GetSecurityPropertyAction action |
| = new GetSecurityPropertyAction("jessie.client.enable.extensions"); |
| return Boolean.valueOf(AccessController.doPrivileged(action)); |
| } |
| |
| private MaxFragmentLength maxFragmentLength() |
| { |
| GetSecurityPropertyAction action |
| = new GetSecurityPropertyAction("jessie.client.maxFragmentLength"); |
| String s = AccessController.doPrivileged(action); |
| if (s != null) |
| { |
| try |
| { |
| int len = Integer.parseInt(s); |
| switch (len) |
| { |
| case 9: |
| case (1 << 9): return MaxFragmentLength.LEN_2_9; |
| case 10: |
| case (1 << 10): return MaxFragmentLength.LEN_2_10; |
| case 11: |
| case (1 << 11): return MaxFragmentLength.LEN_2_11; |
| case 12: |
| case (1 << 12): return MaxFragmentLength.LEN_2_12; |
| } |
| } |
| catch (NumberFormatException nfe) |
| { |
| } |
| } |
| return null; |
| } |
| |
| private boolean truncatedHMac() |
| { |
| GetSecurityPropertyAction action |
| = new GetSecurityPropertyAction("jessie.client.truncatedHMac"); |
| return Boolean.valueOf(AccessController.doPrivileged(action)); |
| } |
| |
| private String getPSKIdentity() |
| { |
| GetSecurityPropertyAction action |
| = new GetSecurityPropertyAction("jessie.client.psk.identity"); |
| return AccessController.doPrivileged(action); |
| } |
| |
| // 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; |
| private final boolean full; |
| |
| ClientDHGen(DHPublicKey serverKey, DHParameterSpec params, boolean full) |
| { |
| this.serverKey = serverKey; |
| this.params = params; |
| this.full = full; |
| } |
| |
| 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()); |
| |
| initDiffieHellman((DHPrivateKey) dhPair.getPrivate(), engine.session().random()); |
| |
| // We have enough info to do the full key exchange; so let's do it. |
| DHPhase phase = new DHPhase(serverKey, full); |
| phase.run(); |
| if (phase.thrown() != null) |
| throw new SSLException(phase.thrown()); |
| } |
| |
| DHPublicKey serverKey() |
| { |
| return serverKey; |
| } |
| } |
| |
| 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; |
| private final boolean full; |
| |
| RSAGen() |
| { |
| this(true); |
| } |
| |
| RSAGen(boolean full) |
| { |
| this.full = full; |
| } |
| |
| 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) sentVersion.major(); |
| preMasterSecret[1] = (byte) sentVersion.minor(); |
| Cipher rsa = Cipher.getInstance("RSA"); |
| java.security.cert.Certificate cert |
| = engine.session().getPeerCertificates()[0]; |
| if (cert instanceof X509Certificate) |
| { |
| boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage(); |
| if (keyUsage != null && !keyUsage[2]) |
| throw new InvalidKeyException("certificate's keyUsage does not permit keyEncipherment"); |
| } |
| rsa.init(Cipher.ENCRYPT_MODE, cert.getPublicKey()); |
| encryptedPreMasterSecret = rsa.doFinal(preMasterSecret); |
| |
| // Generate our session keys, because we can. |
| if (full) |
| { |
| 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 |
| { |
| private final MessageDigest md5, sha; |
| private byte[] signed; |
| |
| GenCertVerify(MessageDigest md5, MessageDigest sha) |
| { |
| try |
| { |
| this.md5 = (MessageDigest) md5.clone(); |
| this.sha = (MessageDigest) sha.clone(); |
| } |
| catch (CloneNotSupportedException cnse) |
| { |
| // Our message digests *should* be cloneable. |
| throw new Error(cnse); |
| } |
| } |
| |
| public void implRun() |
| throws InvalidKeyException, NoSuchAlgorithmException, SignatureException |
| { |
| byte[] toSign; |
| if (engine.session().version == ProtocolVersion.SSL_3) |
| { |
| toSign = genV3CertificateVerify(md5, sha, engine.session()); |
| } |
| else |
| { |
| if (engine.session().suite.signatureAlgorithm() == SignatureAlgorithm.RSA) |
| toSign = Util.concat(md5.digest(), sha.digest()); |
| else |
| toSign = sha.digest(); |
| } |
| |
| java.security.Signature sig = |
| java.security.Signature.getInstance(engine.session().suite.signatureAlgorithm().name()); |
| sig.initSign(privateKey); |
| sig.update(toSign); |
| signed = sig.sign(); |
| } |
| |
| byte[] signed() |
| { |
| return signed; |
| } |
| } |
| } |