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