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