/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.xnet.provider.jsse; import java.io.IOException; import java.security.AccessController; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PrivilegedExceptionAction; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Enumeration; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.interfaces.DHKey; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.X509ExtendedKeyManager; /** * Client side handshake protocol implementation. * Handshake protocol operates on top of the Record Protocol. * It is responsible for session negotiating. * * The implementation processes inbound server handshake messages, * creates and sends respond messages. Outbound messages are supplied * to Record Protocol. Detected errors are reported to the Alert protocol. * * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The * TLS Handshake Protocol</a> * */ public class ClientHandshakeImpl extends HandshakeProtocol { /** * Creates Client Handshake Implementation * * @param owner */ ClientHandshakeImpl(Object owner) { super(owner); } /** * Starts handshake * */ @Override public void start() { if (session == null) { // initial handshake session = findSessionToResume(); } else { // start session renegotiation if (clientHello != null && this.status != FINISHED) { // current negotiation has not completed return; // ignore } if (!session.isValid()) { session = null; } } if (session != null) { isResuming = true; } else if (parameters.getEnableSessionCreation()){ isResuming = false; session = new SSLSessionImpl(parameters.getSecureRandom()); session.protocol = ProtocolVersion.getLatestVersion(parameters .getEnabledProtocols()); recordProtocol.setVersion(session.protocol.version); } else { fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created "); } startSession(); } /** * Starts renegotiation on a new session * */ private void renegotiateNewSession() { if (parameters.getEnableSessionCreation()){ isResuming = false; session = new SSLSessionImpl(parameters.getSecureRandom()); session.protocol = ProtocolVersion.getLatestVersion(parameters .getEnabledProtocols()); recordProtocol.setVersion(session.protocol.version); startSession(); } else { status = NOT_HANDSHAKING; sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); } } /* * Starts/resumes session */ private void startSession() { CipherSuite[] cipher_suites; if (isResuming) { cipher_suites = new CipherSuite[] { session.cipherSuite }; } else { // BEGIN android-changed cipher_suites = parameters.getEnabledCipherSuitesMember(); // END android-changed } clientHello = new ClientHello(parameters.getSecureRandom(), session.protocol.version, session.id, cipher_suites); session.clientRandom = clientHello.random; send(clientHello); status = NEED_UNWRAP; } /** * Processes inbound handshake messages * @param bytes */ @Override public void unwrap(byte[] bytes) { if (this.delegatedTaskErr != null) { Exception e = this.delegatedTaskErr; this.delegatedTaskErr = null; this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e); } int handshakeType; io_stream.append(bytes); while (io_stream.available() > 0) { io_stream.mark(); int length; try { handshakeType = io_stream.read(); length = io_stream.readUint24(); if (io_stream.available() < length) { io_stream.reset(); return; } switch (handshakeType) { case 0: // HELLO_REQUEST // we don't need to take this message into account // during FINISH message verification, so remove it io_stream.removeFromMarkedPosition(); if (clientHello != null && (clientFinished == null || serverFinished == null)) { //currently negotiating - ignore break; } // renegotiate if (session.isValid()) { session = (SSLSessionImpl) session.clone(); isResuming = true; startSession(); } else { // if SSLSession is invalidated (e.g. timeout limit is // exceeded) connection can't resume the session. renegotiateNewSession(); } break; case 2: // SERVER_HELLO if (clientHello == null || serverHello != null) { unexpectedMessage(); return; } serverHello = new ServerHello(io_stream, length); //check protocol version ProtocolVersion servProt = ProtocolVersion .getByVersion(serverHello.server_version); String[] enabled = parameters.getEnabledProtocols(); find: { for (int i = 0; i < enabled.length; i++) { if (servProt.equals(ProtocolVersion .getByName(enabled[i]))) { break find; } } fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Bad server hello protocol version"); } // check compression method if (serverHello.compression_method != 0) { fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Bad server hello compression method"); } //check cipher_suite // BEGIN android-changed CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember(); // END android-changed find: { for (int i = 0; i < enabledSuites.length; i++) { if (serverHello.cipher_suite .equals(enabledSuites[i])) { break find; } } fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Bad server hello cipher suite"); } if (isResuming) { if (serverHello.session_id.length == 0) { // server is not willing to establish the new connection // using specified session isResuming = false; } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) { isResuming = false; } else if (!session.protocol.equals(servProt)) { fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Bad server hello protocol version"); } else if (!session.cipherSuite .equals(serverHello.cipher_suite)) { fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Bad server hello cipher suite"); } if (serverHello.server_version[1] == 1) { computerReferenceVerifyDataTLS("server finished"); } else { computerReferenceVerifyDataSSLv3(SSLv3Constants.server); } } session.protocol = servProt; recordProtocol.setVersion(session.protocol.version); session.cipherSuite = serverHello.cipher_suite; session.id = serverHello.session_id.clone(); session.serverRandom = serverHello.random; break; case 11: // CERTIFICATE if (serverHello == null || serverKeyExchange != null || serverCert != null || isResuming) { unexpectedMessage(); return; } serverCert = new CertificateMessage(io_stream, length); break; case 12: // SERVER_KEY_EXCHANGE if (serverHello == null || serverKeyExchange != null || isResuming) { unexpectedMessage(); return; } serverKeyExchange = new ServerKeyExchange(io_stream, length, session.cipherSuite.keyExchange); break; case 13: // CERTIFICATE_REQUEST if (serverCert == null || certificateRequest != null || session.cipherSuite.isAnonymous() || isResuming) { unexpectedMessage(); return; } certificateRequest = new CertificateRequest(io_stream, length); break; case 14: // SERVER_HELLO_DONE if (serverHello == null || serverHelloDone != null || isResuming) { unexpectedMessage(); return; } serverHelloDone = new ServerHelloDone(io_stream, length); if (this.nonBlocking) { delegatedTasks.add(new DelegatedTask(new PrivilegedExceptionAction<Void>() { public Void run() throws Exception { processServerHelloDone(); return null; } }, this, AccessController.getContext())); return; } processServerHelloDone(); break; case 20: // FINISHED if (!changeCipherSpecReceived) { unexpectedMessage(); return; } serverFinished = new Finished(io_stream, length); verifyFinished(serverFinished.getData()); session.lastAccessedTime = System.currentTimeMillis(); // BEGIN android-added session.context = parameters.getClientSessionContext(); // END android-added parameters.getClientSessionContext().putSession(session); if (isResuming) { sendChangeCipherSpec(); } else { session.lastAccessedTime = System.currentTimeMillis(); status = FINISHED; } // XXX there is no cleanup work break; default: unexpectedMessage(); return; } } catch (IOException e) { // io stream dosn't contain complete handshake message io_stream.reset(); return; } } } /** * Processes SSLv2 Hello message. * SSLv2 client hello message message is an unexpected message * for client side of handshake protocol. * @ see TLS 1.0 spec., E.1. Version 2 client hello * @param bytes */ @Override public void unwrapSSLv2(byte[] bytes) { unexpectedMessage(); } /** * Creates and sends Finished message */ @Override protected void makeFinished() { byte[] verify_data; if (serverHello.server_version[1] == 1) { verify_data = new byte[12]; computerVerifyDataTLS("client finished", verify_data); } else { verify_data = new byte[36]; computerVerifyDataSSLv3(SSLv3Constants.client, verify_data); } clientFinished = new Finished(verify_data); send(clientFinished); if (isResuming) { session.lastAccessedTime = System.currentTimeMillis(); status = FINISHED; } else { if (serverHello.server_version[1] == 1) { computerReferenceVerifyDataTLS("server finished"); } else { computerReferenceVerifyDataSSLv3(SSLv3Constants.server); } status = NEED_UNWRAP; } } /** * Processes ServerHelloDone: makes verification of the server messages; sends * client messages, computers masterSecret, sends ChangeCipherSpec */ void processServerHelloDone() { PrivateKey clientKey = null; if (serverCert != null) { if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { unexpectedMessage(); return; } verifyServerCert(); } else { if (session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon && session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon_EXPORT) { unexpectedMessage(); return; } } // Client certificate if (certificateRequest != null) { X509Certificate[] certs = null; String clientAlias = ((X509ExtendedKeyManager) parameters .getKeyManager()).chooseClientAlias(certificateRequest .getTypesAsString(), certificateRequest.certificate_authorities, null); if (clientAlias != null) { X509ExtendedKeyManager km = (X509ExtendedKeyManager) parameters .getKeyManager(); certs = km.getCertificateChain((clientAlias)); clientKey = km.getPrivateKey(clientAlias); } session.localCertificates = certs; clientCert = new CertificateMessage(certs); send(clientCert); } // Client key exchange if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { // RSA encrypted premaster secret message Cipher c; try { c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); if (serverKeyExchange != null) { c.init(Cipher.ENCRYPT_MODE, serverKeyExchange .getRSAPublicKey()); } else { c.init(Cipher.ENCRYPT_MODE, serverCert.certs[0]); } } catch (Exception e) { fatalAlert(AlertProtocol.INTERNAL_ERROR, "Unexpected exception", e); return; } preMasterSecret = new byte[48]; parameters.getSecureRandom().nextBytes(preMasterSecret); System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, 2); try { clientKeyExchange = new ClientKeyExchange(c .doFinal(preMasterSecret), serverHello.server_version[1] == 1); } catch (Exception e) { fatalAlert(AlertProtocol.INTERNAL_ERROR, "Unexpected exception", e); return; } } else { PublicKey serverPublic; KeyAgreement agreement = null; DHParameterSpec spec; try { KeyFactory kf = null; try { kf = KeyFactory.getInstance("DH"); } catch (NoSuchAlgorithmException e) { kf = KeyFactory.getInstance("DiffieHellman"); } try { agreement = KeyAgreement.getInstance("DH"); } catch (NoSuchAlgorithmException ee) { agreement = KeyAgreement.getInstance("DiffieHellman"); } KeyPairGenerator kpg = null; try { kpg = KeyPairGenerator.getInstance("DH"); } catch (NoSuchAlgorithmException e) { kpg = KeyPairGenerator.getInstance("DiffieHellman"); } if (serverKeyExchange != null) { serverPublic = kf.generatePublic(new DHPublicKeySpec( serverKeyExchange.par3, serverKeyExchange.par1, serverKeyExchange.par2)); spec = new DHParameterSpec(serverKeyExchange.par1, serverKeyExchange.par2); } else { serverPublic = serverCert.certs[0].getPublicKey(); spec = ((DHPublicKey) serverPublic).getParams(); } kpg.initialize(spec); KeyPair kp = kpg.generateKeyPair(); Key key = kp.getPublic(); if (clientCert != null && serverCert != null && (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS)) { PublicKey client_pk = clientCert.certs[0].getPublicKey(); PublicKey server_pk = serverCert.certs[0].getPublicKey(); if (client_pk instanceof DHKey && server_pk instanceof DHKey) { if (((DHKey) client_pk).getParams().getG().equals( ((DHKey) server_pk).getParams().getG()) && ((DHKey) client_pk).getParams().getP() .equals(((DHKey) server_pk).getParams().getG())) { // client cert message DH public key parameters // matched those specified by the // server in its certificate, clientKeyExchange = new ClientKeyExchange(); // empty } } } else { clientKeyExchange = new ClientKeyExchange( ((DHPublicKey) key).getY()); } key = kp.getPrivate(); agreement.init(key); agreement.doPhase(serverPublic, true); preMasterSecret = agreement.generateSecret(); } catch (Exception e) { fatalAlert(AlertProtocol.INTERNAL_ERROR, "Unexpected exception", e); return; } } if (clientKeyExchange != null) { send(clientKeyExchange); } computerMasterSecret(); // send certificate verify for all certificates except those containing // fixed DH parameters if (clientCert != null && !clientKeyExchange.isEmpty()) { // Certificate verify DigitalSignature ds = new DigitalSignature( session.cipherSuite.keyExchange); ds.init(clientKey); if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { ds.setMD5(io_stream.getDigestMD5()); ds.setSHA(io_stream.getDigestSHA()); } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) { ds.setSHA(io_stream.getDigestSHA()); // The Signature should be empty in case of anonimous signature algorithm: // } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon || // session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { } certificateVerify = new CertificateVerify(ds.sign()); send(certificateVerify); } sendChangeCipherSpec(); } /* * Verifies certificate path */ private void verifyServerCert() { String authType = null; switch (session.cipherSuite.keyExchange) { case 1: // KeyExchange_RSA authType = "RSA"; break; case 2: // KeyExchange_RSA_EXPORT if (serverKeyExchange != null ) { // ephemeral RSA key is used authType = "RSA_EXPORT"; } else { authType = "RSA"; } break; case 3: // KeyExchange_DHE_DSS case 4: // KeyExchange_DHE_DSS_EXPORT authType = "DHE_DSS"; break; case 5: // KeyExchange_DHE_RSA case 6: // KeyExchange_DHE_RSA_EXPORT authType = "DHE_RSA"; break; case 7: // KeyExchange_DH_DSS case 11: // KeyExchange_DH_DSS_EXPORT authType = "DH_DSS"; break; case 8: // KeyExchange_DH_RSA case 12: // KeyExchange_DH_RSA_EXPORT authType = "DH_RSA"; break; case 9: // KeyExchange_DH_anon case 10: // KeyExchange_DH_anon_EXPORT return; } try { parameters.getTrustManager().checkServerTrusted(serverCert.certs, authType); } catch (CertificateException e) { fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e); return; } session.peerCertificates = serverCert.certs; } /** * Processes ChangeCipherSpec message */ @Override public void receiveChangeCipherSpec() { if (isResuming) { if (serverHello == null) { unexpectedMessage(); } } else if (clientFinished == null) { unexpectedMessage(); } changeCipherSpecReceived = true; } // Find session to resume in client session context private SSLSessionImpl findSessionToResume() { // BEGIN android-changed String host = null; int port = -1; if (engineOwner != null) { host = engineOwner.getPeerHost(); port = engineOwner.getPeerPort(); } if (host == null || port == -1) { return null; // starts new session } ClientSessionContext context = parameters.getClientSessionContext(); SSLSessionImpl session = (SSLSessionImpl) context.getSession(host, port); if (session != null) { session = (SSLSessionImpl) session.clone(); } return session; // END android-changed } }