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