FileDocCategorySizeDatePackage
ClientHandshakeImpl.javaAPI DocAndroid 1.5 API25806Wed May 06 22:41:06 BST 2009org.apache.harmony.xnet.provider.jsse

ClientHandshakeImpl

public class ClientHandshakeImpl extends HandshakeProtocol
Client side handshake protocol implementation. Handshake protocol operates on top of the Record Protocol. It is responsible for session negotiating. The implementation proceses 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
TLS 1.0 spec., 7. The TLS Handshake Protocol

Fields Summary
Constructors Summary
ClientHandshakeImpl(Object owner)
Creates Client Handshake Implementation

param
owner

        super(owner);
    
Methods Summary
private SSLSessionImplfindSessionToResume()

        // 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
    
protected voidmakeFinished()
Creates and sends Finished message

        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;
        }
    
voidprocessServerHelloDone()
Processes ServerHelloDone: makes verification of the server messages; sends client messages, computers masterSecret, sends ChangeCipherSpec

        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();
    
public voidreceiveChangeCipherSpec()
Proceses ChangeCipherSpec message

        if (isResuming) {
            if (serverHello == null) {
                unexpectedMessage();
            }
        } else if (clientFinished == null) {
            unexpectedMessage();
        } 
        changeCipherSpecReceived = true;
    
private voidrenegotiateNewSession()
Starts renegotiation on a new session

        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);
        }        
    
public voidstart()
Starts handshake

        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();
    
private voidstartSession()

        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;
    
public voidunwrap(byte[] bytes)
Proceses inbound handshake messages

param
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 = (byte[]) 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(){ 
                            public Object 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;
            }
        }

    
public voidunwrapSSLv2(byte[] bytes)
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

        unexpectedMessage();
    
private voidverifyServerCert()

        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;