blob: 0af1944c9bc1a8d0da4c44e954547d046e83cd0d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.xnet.provider.jsse;
import java.io.IOException;
import java.security.AccessController;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PrivilegedExceptionAction;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.security.auth.x500.X500Principal;
/**
* Client side handshake protocol implementation.
* Handshake protocol operates on top of the Record Protocol.
* It is responsible for session negotiating.
*
* The implementation processes inbound server handshake messages,
* creates and sends respond messages. Outbound messages are supplied
* to Record Protocol. Detected errors are reported to the Alert protocol.
*
* @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The
* TLS Handshake Protocol</a>
*
*/
public class ClientHandshakeImpl extends HandshakeProtocol {
/**
* Creates Client Handshake Implementation
*
* @param owner
*/
ClientHandshakeImpl(Object owner) {
super(owner);
}
/**
* Starts handshake
*
*/
@Override
public void start() {
if (session == null) { // initial handshake
session = findSessionToResume();
} else { // start session renegotiation
if (clientHello != null && this.status != FINISHED) {
// current negotiation has not completed
return; // ignore
}
if (!session.isValid()) {
session = null;
}
}
if (session != null) {
isResuming = true;
} else if (parameters.getEnableSessionCreation()){
isResuming = false;
session = new SSLSessionImpl(parameters.getSecureRandom());
if (engineOwner != null) {
session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
} else {
session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort());
}
session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
recordProtocol.setVersion(session.protocol.version);
} else {
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created ");
}
startSession();
}
/**
* Starts renegotiation on a new session
*
*/
private void renegotiateNewSession() {
if (parameters.getEnableSessionCreation()){
isResuming = false;
session = new SSLSessionImpl(parameters.getSecureRandom());
if (engineOwner != null) {
session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
} else {
session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort());
}
session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
recordProtocol.setVersion(session.protocol.version);
startSession();
} else {
status = NOT_HANDSHAKING;
sendWarningAlert(AlertProtocol.NO_RENEGOTIATION);
}
}
/*
* Starts/resumes session
*/
private void startSession() {
CipherSuite[] cipher_suites;
if (isResuming) {
cipher_suites = new CipherSuite[] { session.cipherSuite };
} else {
cipher_suites = parameters.getEnabledCipherSuitesMember();
}
clientHello = new ClientHello(parameters.getSecureRandom(),
session.protocol.version, session.id, cipher_suites);
session.clientRandom = clientHello.random;
send(clientHello);
status = NEED_UNWRAP;
}
/**
* Processes inbound handshake messages
* @param bytes
*/
@Override
public void unwrap(byte[] bytes) {
if (this.delegatedTaskErr != null) {
Exception e = this.delegatedTaskErr;
this.delegatedTaskErr = null;
this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e);
}
int handshakeType;
io_stream.append(bytes);
while (io_stream.available() > 0) {
io_stream.mark();
int length;
try {
handshakeType = io_stream.read();
length = io_stream.readUint24();
if (io_stream.available() < length) {
io_stream.reset();
return;
}
switch (handshakeType) {
case 0: // HELLO_REQUEST
// we don't need to take this message into account
// during FINISH message verification, so remove it
io_stream.removeFromMarkedPosition();
if (clientHello != null
&& (clientFinished == null || serverFinished == null)) {
//currently negotiating - ignore
break;
}
// renegotiate
if (session.isValid()) {
session = (SSLSessionImpl) session.clone();
isResuming = true;
startSession();
} else {
// if SSLSession is invalidated (e.g. timeout limit is
// exceeded) connection can't resume the session.
renegotiateNewSession();
}
break;
case 2: // SERVER_HELLO
if (clientHello == null || serverHello != null) {
unexpectedMessage();
return;
}
serverHello = new ServerHello(io_stream, length);
//check protocol version
ProtocolVersion servProt = ProtocolVersion.getByVersion(serverHello.server_version);
String[] enabled = parameters.getEnabledProtocols();
find: {
for (int i = 0; i < enabled.length; i++) {
if (servProt.equals(ProtocolVersion.getByName(enabled[i]))) {
break find;
}
}
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
"Bad server hello protocol version");
}
// check compression method
if (serverHello.compression_method != 0) {
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
"Bad server hello compression method");
}
//check cipher_suite
CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember();
find: {
for (int i = 0; i < enabledSuites.length; i++) {
if (serverHello.cipher_suite.equals(enabledSuites[i])) {
break find;
}
}
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
"Bad server hello cipher suite");
}
if (isResuming) {
if (serverHello.session_id.length == 0) {
// server is not willing to establish the new connection
// using specified session
isResuming = false;
} else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) {
isResuming = false;
} else if (!session.protocol.equals(servProt)) {
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
"Bad server hello protocol version");
} else if (!session.cipherSuite.equals(serverHello.cipher_suite)) {
fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
"Bad server hello cipher suite");
}
if (serverHello.server_version[1] == 1) {
computerReferenceVerifyDataTLS("server finished");
} else {
computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
}
}
session.protocol = servProt;
recordProtocol.setVersion(session.protocol.version);
session.cipherSuite = serverHello.cipher_suite;
session.id = serverHello.session_id.clone();
session.serverRandom = serverHello.random;
break;
case 11: // CERTIFICATE
if (serverHello == null || serverKeyExchange != null
|| serverCert != null || isResuming) {
unexpectedMessage();
return;
}
serverCert = new CertificateMessage(io_stream, length);
break;
case 12: // SERVER_KEY_EXCHANGE
if (serverHello == null || serverKeyExchange != null
|| isResuming) {
unexpectedMessage();
return;
}
serverKeyExchange = new ServerKeyExchange(io_stream,
length, session.cipherSuite.keyExchange);
break;
case 13: // CERTIFICATE_REQUEST
if (serverCert == null || certificateRequest != null
|| session.cipherSuite.isAnonymous() || isResuming) {
unexpectedMessage();
return;
}
certificateRequest = new CertificateRequest(io_stream, length);
break;
case 14: // SERVER_HELLO_DONE
if (serverHello == null || serverHelloDone != null || isResuming) {
unexpectedMessage();
return;
}
serverHelloDone = new ServerHelloDone(io_stream, length);
if (this.nonBlocking) {
delegatedTasks.add(new DelegatedTask(new Runnable() {
public void run() {
processServerHelloDone();
}
}, this));
return;
}
processServerHelloDone();
break;
case 20: // FINISHED
if (!changeCipherSpecReceived) {
unexpectedMessage();
return;
}
serverFinished = new Finished(io_stream, length);
verifyFinished(serverFinished.getData());
session.lastAccessedTime = System.currentTimeMillis();
session.context = parameters.getClientSessionContext();
parameters.getClientSessionContext().putSession(session);
if (isResuming) {
sendChangeCipherSpec();
} else {
session.lastAccessedTime = System.currentTimeMillis();
status = FINISHED;
}
// XXX there is no cleanup work
break;
default:
unexpectedMessage();
return;
}
} catch (IOException e) {
// io stream dosn't contain complete handshake message
io_stream.reset();
return;
}
}
}
/**
* Processes SSLv2 Hello message.
* SSLv2 client hello message message is an unexpected message
* for client side of handshake protocol.
* @ see TLS 1.0 spec., E.1. Version 2 client hello
* @param bytes
*/
@Override
public void unwrapSSLv2(byte[] bytes) {
unexpectedMessage();
}
/**
* Creates and sends Finished message
*/
@Override
protected void makeFinished() {
byte[] verify_data;
if (serverHello.server_version[1] == 1) {
verify_data = new byte[12];
computerVerifyDataTLS("client finished", verify_data);
} else {
verify_data = new byte[36];
computerVerifyDataSSLv3(SSLv3Constants.client, verify_data);
}
clientFinished = new Finished(verify_data);
send(clientFinished);
if (isResuming) {
session.lastAccessedTime = System.currentTimeMillis();
status = FINISHED;
} else {
if (serverHello.server_version[1] == 1) {
computerReferenceVerifyDataTLS("server finished");
} else {
computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
}
status = NEED_UNWRAP;
}
}
/**
* Processes ServerHelloDone: makes verification of the server messages; sends
* client messages, computers masterSecret, sends ChangeCipherSpec
*/
void processServerHelloDone() {
PrivateKey clientKey = null;
if (serverCert != null) {
if (session.cipherSuite.isAnonymous()) {
unexpectedMessage();
return;
}
verifyServerCert();
} else {
if (!session.cipherSuite.isAnonymous()) {
unexpectedMessage();
return;
}
}
// Client certificate
if (certificateRequest != null) {
X509Certificate[] certs = null;
// obtain certificates from key manager
String alias = null;
String[] certTypes = certificateRequest.getTypesAsString();
X500Principal[] issuers = certificateRequest.certificate_authorities;
X509KeyManager km = parameters.getKeyManager();
if (km instanceof X509ExtendedKeyManager) {
X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km;
if (this.socketOwner != null) {
alias = ekm.chooseClientAlias(certTypes, issuers, this.socketOwner);
} else {
alias = ekm.chooseEngineClientAlias(certTypes, issuers, this.engineOwner);
}
if (alias != null) {
certs = ekm.getCertificateChain(alias);
}
} else {
alias = km.chooseClientAlias(certTypes, issuers, this.socketOwner);
if (alias != null) {
certs = km.getCertificateChain(alias);
}
}
session.localCertificates = certs;
clientCert = new CertificateMessage(certs);
clientKey = km.getPrivateKey(alias);
send(clientCert);
}
// Client key exchange
if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
// RSA encrypted premaster secret message
Cipher c;
try {
c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
if (serverKeyExchange != null) {
if (!session.cipherSuite.isAnonymous()) {
DigitalSignature ds = new DigitalSignature(serverCert.getAuthType());
ds.init(serverCert.certs[0]);
ds.update(clientHello.getRandom());
ds.update(serverHello.getRandom());
if (!serverKeyExchange.verifySignature(ds)) {
fatalAlert(AlertProtocol.DECRYPT_ERROR, "Cannot verify RSA params");
return;
}
}
c.init(Cipher.WRAP_MODE, serverKeyExchange
.getRSAPublicKey());
} else {
c.init(Cipher.WRAP_MODE, serverCert.certs[0]);
}
} catch (Exception e) {
fatalAlert(AlertProtocol.INTERNAL_ERROR,
"Unexpected exception", e);
return;
}
preMasterSecret = new byte[48];
parameters.getSecureRandom().nextBytes(preMasterSecret);
System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, 2);
try {
clientKeyExchange = new ClientKeyExchange(c
.wrap(new SecretKeySpec(preMasterSecret, "preMasterSecret")),
serverHello.server_version[1] == 1);
} catch (Exception e) {
fatalAlert(AlertProtocol.INTERNAL_ERROR,
"Unexpected exception", e);
return;
}
} else if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS_EXPORT
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA_EXPORT
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon
|| session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon_EXPORT) {
/*
* All other key exchanges should have had a DH key communicated via
* ServerKeyExchange beforehand.
*/
if (serverKeyExchange == null) {
fatalAlert(AlertProtocol.UNEXPECTED_MESSAGE, "Expected ServerKeyExchange");
return;
}
if (session.cipherSuite.isAnonymous() != serverKeyExchange.isAnonymous()) {
fatalAlert(AlertProtocol.DECRYPT_ERROR, "Wrong type in ServerKeyExchange");
return;
}
try {
if (!session.cipherSuite.isAnonymous()) {
DigitalSignature ds = new DigitalSignature(serverCert.getAuthType());
ds.init(serverCert.certs[0]);
ds.update(clientHello.getRandom());
ds.update(serverHello.getRandom());
if (!serverKeyExchange.verifySignature(ds)) {
fatalAlert(AlertProtocol.DECRYPT_ERROR, "Cannot verify DH params");
return;
}
}
KeyFactory kf = KeyFactory.getInstance("DH");
KeyAgreement agreement = KeyAgreement.getInstance("DH");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
PublicKey serverDhPublic = kf.generatePublic(new DHPublicKeySpec(
serverKeyExchange.par3, serverKeyExchange.par1,
serverKeyExchange.par2));
DHParameterSpec spec = new DHParameterSpec(serverKeyExchange.par1,
serverKeyExchange.par2);
kpg.initialize(spec);
KeyPair kp = kpg.generateKeyPair();
DHPublicKey pubDhKey = (DHPublicKey) kp.getPublic();
clientKeyExchange = new ClientKeyExchange(pubDhKey.getY());
PrivateKey privDhKey = kp.getPrivate();
agreement.init(privDhKey);
agreement.doPhase(serverDhPublic, true);
preMasterSecret = agreement.generateSecret();
} catch (Exception e) {
fatalAlert(AlertProtocol.INTERNAL_ERROR,
"Unexpected exception", e);
return;
}
} else {
fatalAlert(AlertProtocol.DECRYPT_ERROR, "Unsupported handshake type");
return;
}
if (clientKeyExchange != null) {
send(clientKeyExchange);
}
computerMasterSecret();
// send certificate verify for all certificates except those containing
// fixed DH parameters
if (clientCert != null && !clientKeyExchange.isEmpty()) {
// Certificate verify
String authType = clientKey.getAlgorithm();
DigitalSignature ds = new DigitalSignature(authType);
ds.init(clientKey);
if ("RSA".equals(authType)) {
ds.setMD5(io_stream.getDigestMD5());
ds.setSHA(io_stream.getDigestSHA());
} else if ("DSA".equals(authType)) {
ds.setSHA(io_stream.getDigestSHA());
// The Signature should be empty in case of anonymous signature algorithm:
// } else if ("DH".equals(authType)) {
}
certificateVerify = new CertificateVerify(ds.sign());
send(certificateVerify);
}
sendChangeCipherSpec();
}
/*
* Verifies certificate path
*/
private void verifyServerCert() {
String authType = session.cipherSuite.getAuthType(serverKeyExchange != null);
if (authType == null) {
return;
}
try {
parameters.getTrustManager().checkServerTrusted(serverCert.certs, authType);
} catch (CertificateException e) {
fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e);
return;
}
session.peerCertificates = serverCert.certs;
}
/**
* Processes ChangeCipherSpec message
*/
@Override
public void receiveChangeCipherSpec() {
if (isResuming) {
if (serverHello == null) {
unexpectedMessage();
}
} else if (clientFinished == null) {
unexpectedMessage();
}
changeCipherSpecReceived = true;
}
// Find session to resume in client session context
private SSLSessionImpl findSessionToResume() {
String host = null;
int port = -1;
if (engineOwner != null) {
host = engineOwner.getPeerHost();
port = engineOwner.getPeerPort();
} else {
host = socketOwner.getInetAddress().getHostName();
port = socketOwner.getPort();
}
if (host == null || port == -1) {
return null; // starts new session
}
ClientSessionContext context = parameters.getClientSessionContext();
SSLSessionImpl session
= (SSLSessionImpl) context.getSession(host, port);
if (session != null) {
session = (SSLSessionImpl) session.clone();
}
return session;
}
}