1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.xnet.provider.jsse;
19
20import java.io.IOException;
21import java.security.AccessController;
22import java.security.Key;
23import java.security.KeyFactory;
24import java.security.KeyPair;
25import java.security.KeyPairGenerator;
26import java.security.NoSuchAlgorithmException;
27import java.security.PrivateKey;
28import java.security.PrivilegedExceptionAction;
29import java.security.PublicKey;
30import java.security.cert.CertificateException;
31import java.security.cert.X509Certificate;
32import java.util.Arrays;
33import java.util.Enumeration;
34
35import javax.crypto.Cipher;
36import javax.crypto.KeyAgreement;
37import javax.crypto.interfaces.DHKey;
38import javax.crypto.interfaces.DHPublicKey;
39import javax.crypto.spec.DHParameterSpec;
40import javax.crypto.spec.DHPublicKeySpec;
41import javax.net.ssl.SSLSession;
42import javax.net.ssl.SSLSessionContext;
43import javax.net.ssl.X509ExtendedKeyManager;
44
45/**
46 * Client side handshake protocol implementation.
47 * Handshake protocol operates on top of the Record Protocol.
48 * It is responsible for session negotiating.
49 *
50 * The implementation processes inbound server handshake messages,
51 * creates and sends respond messages. Outbound messages are supplied
52 * to Record Protocol. Detected errors are reported to the Alert protocol.
53 *
54 * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The
55 * TLS Handshake Protocol</a>
56 *
57 */
58public class ClientHandshakeImpl extends HandshakeProtocol {
59
60    /**
61     * Creates Client Handshake Implementation
62     *
63     * @param owner
64     */
65    ClientHandshakeImpl(Object owner) {
66        super(owner);
67    }
68
69    /**
70     * Starts handshake
71     *
72     */
73    @Override
74    public void start() {
75        if (session == null) { // initial handshake
76            session = findSessionToResume();
77        } else { // start session renegotiation
78            if (clientHello != null && this.status != FINISHED) {
79                // current negotiation has not completed
80                return; // ignore
81            }
82            if (!session.isValid()) {
83                session = null;
84            }
85        }
86        if (session != null) {
87            isResuming = true;
88        } else if (parameters.getEnableSessionCreation()){
89            isResuming = false;
90            session = new SSLSessionImpl(parameters.getSecureRandom());
91            session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
92            session.protocol = ProtocolVersion.getLatestVersion(parameters
93                    .getEnabledProtocols());
94            recordProtocol.setVersion(session.protocol.version);
95        } else {
96            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created ");
97        }
98        startSession();
99    }
100
101    /**
102     * Starts renegotiation on a new session
103     *
104     */
105    private void renegotiateNewSession() {
106        if (parameters.getEnableSessionCreation()){
107            isResuming = false;
108            session = new SSLSessionImpl(parameters.getSecureRandom());
109            session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
110            session.protocol = ProtocolVersion.getLatestVersion(parameters
111                    .getEnabledProtocols());
112            recordProtocol.setVersion(session.protocol.version);
113            startSession();
114        } else {
115            status = NOT_HANDSHAKING;
116            sendWarningAlert(AlertProtocol.NO_RENEGOTIATION);
117        }
118    }
119
120    /*
121     * Starts/resumes session
122     */
123    private void startSession() {
124        CipherSuite[] cipher_suites;
125        if (isResuming) {
126            cipher_suites = new CipherSuite[] { session.cipherSuite };
127        } else {
128            // BEGIN android-changed
129            cipher_suites = parameters.getEnabledCipherSuitesMember();
130            // END android-changed
131        }
132        clientHello = new ClientHello(parameters.getSecureRandom(),
133                session.protocol.version, session.id, cipher_suites);
134        session.clientRandom = clientHello.random;
135        send(clientHello);
136        status = NEED_UNWRAP;
137    }
138
139    /**
140     * Processes inbound handshake messages
141     * @param bytes
142     */
143    @Override
144    public void unwrap(byte[] bytes) {
145        if (this.delegatedTaskErr != null) {
146            Exception e = this.delegatedTaskErr;
147            this.delegatedTaskErr = null;
148            this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e);
149        }
150        int handshakeType;
151        io_stream.append(bytes);
152        while (io_stream.available() > 0) {
153            io_stream.mark();
154            int length;
155            try {
156                handshakeType = io_stream.read();
157                length = io_stream.readUint24();
158                if (io_stream.available() < length) {
159                    io_stream.reset();
160                    return;
161                }
162                switch (handshakeType) {
163                case 0: // HELLO_REQUEST
164                    // we don't need to take this message into account
165                    // during FINISH message verification, so remove it
166                    io_stream.removeFromMarkedPosition();
167                    if (clientHello != null
168                            && (clientFinished == null || serverFinished == null)) {
169                        //currently negotiating - ignore
170                        break;
171                    }
172                    // renegotiate
173                    if (session.isValid()) {
174                        session = (SSLSessionImpl) session.clone();
175                        isResuming = true;
176                        startSession();
177                    } else {
178                        // if SSLSession is invalidated (e.g. timeout limit is
179                        // exceeded) connection can't resume the session.
180                        renegotiateNewSession();
181                    }
182                    break;
183                case 2: // SERVER_HELLO
184                    if (clientHello == null || serverHello != null) {
185                        unexpectedMessage();
186                        return;
187                    }
188                    serverHello = new ServerHello(io_stream, length);
189
190                    //check protocol version
191                    ProtocolVersion servProt = ProtocolVersion
192                            .getByVersion(serverHello.server_version);
193                    String[] enabled = parameters.getEnabledProtocols();
194                    find: {
195                        for (int i = 0; i < enabled.length; i++) {
196                            if (servProt.equals(ProtocolVersion
197                                    .getByName(enabled[i]))) {
198                                break find;
199                            }
200                        }
201                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
202                                "Bad server hello protocol version");
203                    }
204
205                    // check compression method
206                    if (serverHello.compression_method != 0) {
207                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
208                                "Bad server hello compression method");
209                    }
210
211                    //check cipher_suite
212                    // BEGIN android-changed
213                    CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember();
214                    // END android-changed
215                    find: {
216                        for (int i = 0; i < enabledSuites.length; i++) {
217                            if (serverHello.cipher_suite
218                                    .equals(enabledSuites[i])) {
219                                break find;
220                            }
221                        }
222                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
223                                "Bad server hello cipher suite");
224                    }
225
226                    if (isResuming) {
227                        if (serverHello.session_id.length == 0) {
228                            // server is not willing to establish the new connection
229                            // using specified session
230                            isResuming = false;
231                        } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) {
232                            isResuming = false;
233                        } else if (!session.protocol.equals(servProt)) {
234                            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
235                                    "Bad server hello protocol version");
236                        } else if (!session.cipherSuite
237                                .equals(serverHello.cipher_suite)) {
238                            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
239                                    "Bad server hello cipher suite");
240                        }
241                        if (serverHello.server_version[1] == 1) {
242                            computerReferenceVerifyDataTLS("server finished");
243                        } else {
244                            computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
245                        }
246                    }
247                    session.protocol = servProt;
248                    recordProtocol.setVersion(session.protocol.version);
249                    session.cipherSuite = serverHello.cipher_suite;
250                    session.id = serverHello.session_id.clone();
251                    session.serverRandom = serverHello.random;
252                    break;
253                case 11: // CERTIFICATE
254                    if (serverHello == null || serverKeyExchange != null
255                            || serverCert != null || isResuming) {
256                        unexpectedMessage();
257                        return;
258                    }
259                    serverCert = new CertificateMessage(io_stream, length);
260                    break;
261                case 12: // SERVER_KEY_EXCHANGE
262                    if (serverHello == null || serverKeyExchange != null
263                            || isResuming) {
264                        unexpectedMessage();
265                        return;
266                    }
267                    serverKeyExchange = new ServerKeyExchange(io_stream,
268                            length, session.cipherSuite.keyExchange);
269                    break;
270                case 13: // CERTIFICATE_REQUEST
271                    if (serverCert == null || certificateRequest != null
272                            || session.cipherSuite.isAnonymous() || isResuming) {
273                        unexpectedMessage();
274                        return;
275                    }
276                    certificateRequest = new CertificateRequest(io_stream,
277                            length);
278                    break;
279                case 14: // SERVER_HELLO_DONE
280                    if (serverHello == null || serverHelloDone != null
281                            || isResuming) {
282                        unexpectedMessage();
283                        return;
284                    }
285                    serverHelloDone = new ServerHelloDone(io_stream, length);
286                    if (this.nonBlocking) {
287                        delegatedTasks.add(new DelegatedTask(new PrivilegedExceptionAction<Void>() {
288                            public Void run() throws Exception {
289                                processServerHelloDone();
290                                return null;
291                            }
292                        }, this, AccessController.getContext()));
293                        return;
294                    }
295                    processServerHelloDone();
296                    break;
297                case 20: // FINISHED
298                    if (!changeCipherSpecReceived) {
299                        unexpectedMessage();
300                        return;
301                    }
302                    serverFinished = new Finished(io_stream, length);
303                    verifyFinished(serverFinished.getData());
304                    session.lastAccessedTime = System.currentTimeMillis();
305                    // BEGIN android-added
306                    session.context = parameters.getClientSessionContext();
307                    // END android-added
308                    parameters.getClientSessionContext().putSession(session);
309                    if (isResuming) {
310                        sendChangeCipherSpec();
311                    } else {
312                        session.lastAccessedTime = System.currentTimeMillis();
313                        status = FINISHED;
314                    }
315                    // XXX there is no cleanup work
316                    break;
317                default:
318                    unexpectedMessage();
319                    return;
320                }
321            } catch (IOException e) {
322                // io stream dosn't contain complete handshake message
323                io_stream.reset();
324                return;
325            }
326        }
327
328    }
329
330    /**
331     * Processes SSLv2 Hello message.
332     * SSLv2 client hello message message is an unexpected message
333     * for client side of handshake protocol.
334     * @ see TLS 1.0 spec., E.1. Version 2 client hello
335     * @param bytes
336     */
337    @Override
338    public void unwrapSSLv2(byte[] bytes) {
339        unexpectedMessage();
340    }
341
342    /**
343     * Creates and sends Finished message
344     */
345    @Override
346    protected void makeFinished() {
347        byte[] verify_data;
348        if (serverHello.server_version[1] == 1) {
349            verify_data = new byte[12];
350            computerVerifyDataTLS("client finished", verify_data);
351        } else {
352            verify_data = new byte[36];
353            computerVerifyDataSSLv3(SSLv3Constants.client, verify_data);
354        }
355        clientFinished = new Finished(verify_data);
356        send(clientFinished);
357        if (isResuming) {
358            session.lastAccessedTime = System.currentTimeMillis();
359            status = FINISHED;
360        } else {
361            if (serverHello.server_version[1] == 1) {
362                computerReferenceVerifyDataTLS("server finished");
363            } else {
364                computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
365            }
366            status = NEED_UNWRAP;
367        }
368    }
369
370    /**
371     * Processes ServerHelloDone: makes verification of the server messages; sends
372     * client messages, computers masterSecret, sends ChangeCipherSpec
373     */
374    void processServerHelloDone() {
375        PrivateKey clientKey = null;
376
377        if (serverCert != null) {
378            if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon
379                    || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) {
380                unexpectedMessage();
381                return;
382            }
383            verifyServerCert();
384        } else {
385            if (session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon
386                    && session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon_EXPORT) {
387                unexpectedMessage();
388                return;
389            }
390        }
391
392        // Client certificate
393        if (certificateRequest != null) {
394            X509Certificate[] certs = null;
395            String clientAlias = ((X509ExtendedKeyManager) parameters
396                    .getKeyManager()).chooseClientAlias(certificateRequest
397                    .getTypesAsString(),
398                    certificateRequest.certificate_authorities, null);
399            if (clientAlias != null) {
400                X509ExtendedKeyManager km = (X509ExtendedKeyManager) parameters
401                        .getKeyManager();
402                certs = km.getCertificateChain((clientAlias));
403                clientKey = km.getPrivateKey(clientAlias);
404            }
405            session.localCertificates = certs;
406            clientCert = new CertificateMessage(certs);
407            send(clientCert);
408        }
409        // Client key exchange
410        if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA
411                || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) {
412            // RSA encrypted premaster secret message
413            Cipher c;
414            try {
415                c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
416                if (serverKeyExchange != null) {
417                    c.init(Cipher.ENCRYPT_MODE, serverKeyExchange
418                            .getRSAPublicKey());
419                } else {
420                    c.init(Cipher.ENCRYPT_MODE, serverCert.certs[0]);
421                }
422            } catch (Exception e) {
423                fatalAlert(AlertProtocol.INTERNAL_ERROR,
424                        "Unexpected exception", e);
425                return;
426            }
427            preMasterSecret = new byte[48];
428            parameters.getSecureRandom().nextBytes(preMasterSecret);
429            System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0,
430                    2);
431            try {
432                clientKeyExchange = new ClientKeyExchange(c
433                        .doFinal(preMasterSecret),
434                        serverHello.server_version[1] == 1);
435            } catch (Exception e) {
436                fatalAlert(AlertProtocol.INTERNAL_ERROR,
437                        "Unexpected exception", e);
438                return;
439            }
440        } else {
441            PublicKey serverPublic;
442            KeyAgreement agreement = null;
443            DHParameterSpec spec;
444            try {
445                KeyFactory kf = null;
446                try {
447                    kf = KeyFactory.getInstance("DH");
448                } catch (NoSuchAlgorithmException e) {
449                    kf = KeyFactory.getInstance("DiffieHellman");
450                }
451
452                try {
453                    agreement = KeyAgreement.getInstance("DH");
454                } catch (NoSuchAlgorithmException ee) {
455                    agreement = KeyAgreement.getInstance("DiffieHellman");
456                }
457
458                KeyPairGenerator kpg = null;
459                try {
460                    kpg = KeyPairGenerator.getInstance("DH");
461                } catch (NoSuchAlgorithmException e) {
462                    kpg = KeyPairGenerator.getInstance("DiffieHellman");
463                }
464                if (serverKeyExchange != null) {
465                    serverPublic = kf.generatePublic(new DHPublicKeySpec(
466                            serverKeyExchange.par3, serverKeyExchange.par1,
467                            serverKeyExchange.par2));
468                    spec = new DHParameterSpec(serverKeyExchange.par1,
469                            serverKeyExchange.par2);
470                } else {
471                    serverPublic = serverCert.certs[0].getPublicKey();
472                    spec = ((DHPublicKey) serverPublic).getParams();
473                }
474                kpg.initialize(spec);
475
476                KeyPair kp = kpg.generateKeyPair();
477                Key key = kp.getPublic();
478                if (clientCert != null
479                        && serverCert != null
480                        && (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA
481                                || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS)) {
482                    PublicKey client_pk = clientCert.certs[0].getPublicKey();
483                    PublicKey server_pk = serverCert.certs[0].getPublicKey();
484                    if (client_pk instanceof DHKey
485                            && server_pk instanceof DHKey) {
486                        if (((DHKey) client_pk).getParams().getG().equals(
487                                ((DHKey) server_pk).getParams().getG())
488                                && ((DHKey) client_pk).getParams().getP()
489                                    .equals(((DHKey) server_pk).getParams().getG())) {
490                            // client cert message DH public key parameters
491                            // matched those specified by the
492                            //   server in its certificate,
493                            clientKeyExchange = new ClientKeyExchange(); // empty
494                        }
495                    }
496                } else {
497                    clientKeyExchange = new ClientKeyExchange(
498                            ((DHPublicKey) key).getY());
499                }
500                key = kp.getPrivate();
501                agreement.init(key);
502                agreement.doPhase(serverPublic, true);
503                preMasterSecret = agreement.generateSecret();
504            } catch (Exception e) {
505                fatalAlert(AlertProtocol.INTERNAL_ERROR,
506                        "Unexpected exception", e);
507                return;
508            }
509        }
510        if (clientKeyExchange != null) {
511            send(clientKeyExchange);
512        }
513
514        computerMasterSecret();
515
516        // send certificate verify for all certificates except those containing
517        // fixed DH parameters
518        if (clientCert != null && !clientKeyExchange.isEmpty()) {
519            // Certificate verify
520            DigitalSignature ds = new DigitalSignature(
521                    session.cipherSuite.keyExchange);
522            ds.init(clientKey);
523
524            if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT
525                    || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA
526                    || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA
527                    || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) {
528                ds.setMD5(io_stream.getDigestMD5());
529                ds.setSHA(io_stream.getDigestSHA());
530            } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS
531                    || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) {
532                ds.setSHA(io_stream.getDigestSHA());
533            // The Signature should be empty in case of anonimous signature algorithm:
534            // } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon ||
535            //         session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) {
536            }
537            certificateVerify = new CertificateVerify(ds.sign());
538            send(certificateVerify);
539        }
540
541        sendChangeCipherSpec();
542    }
543
544    /*
545     * Verifies certificate path
546     */
547    private void verifyServerCert() {
548        String authType = null;
549        switch (session.cipherSuite.keyExchange) {
550        case 1: // KeyExchange_RSA
551            authType = "RSA";
552            break;
553        case 2: // KeyExchange_RSA_EXPORT
554            if (serverKeyExchange != null ) {
555                // ephemeral RSA key is used
556                authType = "RSA_EXPORT";
557            } else {
558                authType = "RSA";
559            }
560            break;
561        case 3: // KeyExchange_DHE_DSS
562        case 4: // KeyExchange_DHE_DSS_EXPORT
563            authType = "DHE_DSS";
564            break;
565        case 5: // KeyExchange_DHE_RSA
566        case 6: // KeyExchange_DHE_RSA_EXPORT
567            authType = "DHE_RSA";
568            break;
569        case 7: // KeyExchange_DH_DSS
570        case 11: // KeyExchange_DH_DSS_EXPORT
571            authType = "DH_DSS";
572            break;
573        case 8: // KeyExchange_DH_RSA
574        case 12: // KeyExchange_DH_RSA_EXPORT
575            authType = "DH_RSA";
576            break;
577        case 9: // KeyExchange_DH_anon
578        case 10: // KeyExchange_DH_anon_EXPORT
579            return;
580        }
581        try {
582            parameters.getTrustManager().checkServerTrusted(serverCert.certs,
583                    authType);
584        } catch (CertificateException e) {
585            fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e);
586            return;
587        }
588        session.peerCertificates = serverCert.certs;
589    }
590
591    /**
592     * Processes ChangeCipherSpec message
593     */
594    @Override
595    public void receiveChangeCipherSpec() {
596        if (isResuming) {
597            if (serverHello == null) {
598                unexpectedMessage();
599            }
600        } else if (clientFinished == null) {
601            unexpectedMessage();
602        }
603        changeCipherSpecReceived = true;
604    }
605
606    // Find session to resume in client session context
607    private SSLSessionImpl findSessionToResume() {
608        // BEGIN android-changed
609        String host = null;
610        int port = -1;
611        if (engineOwner != null) {
612            host = engineOwner.getPeerHost();
613            port = engineOwner.getPeerPort();
614        }
615        if (host == null || port == -1) {
616            return null; // starts new session
617        }
618
619        ClientSessionContext context = parameters.getClientSessionContext();
620        SSLSessionImpl session
621                = (SSLSessionImpl) context.getSession(host, port);
622        if (session != null) {
623            session = (SSLSessionImpl) session.clone();
624        }
625        return session;
626        // END android-changed
627    }
628
629}
630