package org.bouncycastle.crypto.tls;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.agreement.DHBasicAgreement;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSABlindedEngine;
import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
import org.bouncycastle.crypto.io.SignerInputStream;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
import org.bouncycastle.crypto.params.DHParameters;
import org.bouncycastle.crypto.params.DHPublicKeyParameters;
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.prng.ThreadedSeedGenerator;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.util.BigIntegers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* An implementation of all high level protocols in TLS 1.0.
*/
public class TlsProtocolHandler
{
private static final BigInteger ONE = BigInteger.valueOf(1);
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final short RL_CHANGE_CIPHER_SPEC = 20;
private static final short RL_ALERT = 21;
private static final short RL_HANDSHAKE = 22;
private static final short RL_APPLICATION_DATA = 23;
/*
hello_request(0), client_hello(1), server_hello(2),
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255)
*/
private static final short HP_HELLO_REQUEST = 0;
private static final short HP_CLIENT_HELLO = 1;
private static final short HP_SERVER_HELLO = 2;
private static final short HP_CERTIFICATE = 11;
private static final short HP_SERVER_KEY_EXCHANGE = 12;
private static final short HP_CERTIFICATE_REQUEST = 13;
private static final short HP_SERVER_HELLO_DONE = 14;
private static final short HP_CERTIFICATE_VERIFY = 15;
private static final short HP_CLIENT_KEY_EXCHANGE = 16;
private static final short HP_FINISHED = 20;
/*
* Our Connection states
*/
private static final short CS_CLIENT_HELLO_SEND = 1;
private static final short CS_SERVER_HELLO_RECEIVED = 2;
private static final short CS_SERVER_CERTIFICATE_RECEIVED = 3;
private static final short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4;
private static final short CS_CERTIFICATE_REQUEST_RECEIVED = 5;
private static final short CS_SERVER_HELLO_DONE_RECEIVED = 6;
private static final short CS_CLIENT_KEY_EXCHANGE_SEND = 7;
private static final short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 8;
private static final short CS_CLIENT_FINISHED_SEND = 9;
private static final short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 10;
private static final short CS_DONE = 11;
protected static final short AP_close_notify = 0;
protected static final short AP_unexpected_message = 10;
protected static final short AP_bad_record_mac = 20;
protected static final short AP_decryption_failed = 21;
protected static final short AP_record_overflow = 22;
protected static final short AP_decompression_failure = 30;
protected static final short AP_handshake_failure = 40;
protected static final short AP_bad_certificate = 42;
protected static final short AP_unsupported_certificate = 43;
protected static final short AP_certificate_revoked = 44;
protected static final short AP_certificate_expired = 45;
protected static final short AP_certificate_unknown = 46;
protected static final short AP_illegal_parameter = 47;
protected static final short AP_unknown_ca = 48;
protected static final short AP_access_denied = 49;
protected static final short AP_decode_error = 50;
protected static final short AP_decrypt_error = 51;
protected static final short AP_export_restriction = 60;
protected static final short AP_protocol_version = 70;
protected static final short AP_insufficient_security = 71;
protected static final short AP_internal_error = 80;
protected static final short AP_user_canceled = 90;
protected static final short AP_no_renegotiation = 100;
protected static final short AL_warning = 1;
protected static final short AL_fatal = 2;
private static final byte[] emptybuf = new byte[0];
private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
/*
* Queues for data from some protocols.
*/
private ByteQueue applicationDataQueue = new ByteQueue();
private ByteQueue changeCipherSpecQueue = new ByteQueue();
private ByteQueue alertQueue = new ByteQueue();
private ByteQueue handshakeQueue = new ByteQueue();
/*
* The Record Stream we use
*/
private RecordStream rs;
private SecureRandom random;
/*
* The public key of the server.
*/
private AsymmetricKeyParameter serverPublicKey = null;
private TlsInputStream tlsInputStream = null;
private TlsOuputStream tlsOutputStream = null;
private boolean closed = false;
private boolean failedWithError = false;
private boolean appDataReady = false;
private byte[] clientRandom;
private byte[] serverRandom;
private byte[] ms;
private TlsCipherSuite chosenCipherSuite = null;
private BigInteger Yc;
private byte[] pms;
private CertificateVerifyer verifyer = null;
public TlsProtocolHandler(InputStream is, OutputStream os)
{
/*
* We use our threaded seed generator to generate a good random
* seed. If the user has a better random seed, he should use
* the constructor with a SecureRandom.
*/
ThreadedSeedGenerator tsg = new ThreadedSeedGenerator();
this.random = new SecureRandom();
/*
* Hopefully, 20 bytes in fast mode are good enough.
*/
this.random.setSeed(tsg.generateSeed(20, true));
this.rs = new RecordStream(this, is, os);
}
public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr)
{
this.random = sr;
this.rs = new RecordStream(this, is, os);
}
private short connection_state;
protected void processData(short protocol, byte[] buf, int offset, int len)
throws IOException
{
/*
* Have a look at the protocol type, and add it to the correct queue.
*/
switch (protocol)
{
case RL_CHANGE_CIPHER_SPEC:
changeCipherSpecQueue.addData(buf, offset, len);
processChangeCipherSpec();
break;
case RL_ALERT:
alertQueue.addData(buf, offset, len);
processAlert();
break;
case RL_HANDSHAKE:
handshakeQueue.addData(buf, offset, len);
processHandshake();
break;
case RL_APPLICATION_DATA:
if (!appDataReady)
{
this.failWithError(AL_fatal, AP_unexpected_message);
}
applicationDataQueue.addData(buf, offset, len);
processApplicationData();
break;
default:
/*
* Uh, we don't know this protocol.
*
* RFC2246 defines on page 13, that we should ignore this.
*/
}
}
private void processHandshake() throws IOException
{
boolean read;
do
{
read = false;
/*
* We need the first 4 bytes, they contain type and length of
* the message.
*/
if (handshakeQueue.size() >= 4)
{
byte[] beginning = new byte[4];
handshakeQueue.read(beginning, 0, 4, 0);
ByteArrayInputStream bis = new ByteArrayInputStream(beginning);
short type = TlsUtils.readUint8(bis);
int len = TlsUtils.readUint24(bis);
/*
* Check if we have enough bytes in the buffer to read
* the full message.
*/
if (handshakeQueue.size() >= (len + 4))
{
/*
* Read the message.
*/
byte[] buf = new byte[len];
handshakeQueue.read(buf, 0, len, 4);
handshakeQueue.removeData(len + 4);
/*
* If it is not a finished message, update our hashes
* we prepare for the finish message.
*/
if (type != HP_FINISHED)
{
rs.hash1.update(beginning, 0, 4);
rs.hash2.update(beginning, 0, 4);
rs.hash1.update(buf, 0, len);
rs.hash2.update(buf, 0, len);
}
/*
* Now, parse the message.
*/
ByteArrayInputStream is = new ByteArrayInputStream(buf);
/*
* Check the type.
*/
switch (type)
{
case HP_CERTIFICATE:
{
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
{
/*
* Parse the certificates.
*/
Certificate cert = Certificate.parse(is);
assertEmpty(is);
X509CertificateStructure x509Cert = cert.certs[0];
SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
try
{
this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
}
catch (RuntimeException e)
{
this.failWithError(AL_fatal, AP_unsupported_certificate);
}
// Sanity check the PublicKeyFactory
if (this.serverPublicKey.isPrivate())
{
this.failWithError(AL_fatal, AP_internal_error);
}
/*
* Perform various checks per RFC2246 7.4.2
* TODO "Unless otherwise specified, the signing algorithm for the certificate
* must be the same as the algorithm for the certificate key."
*/
switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
{
case TlsCipherSuite.KE_RSA:
if (!(this.serverPublicKey instanceof RSAKeyParameters))
{
this.failWithError(AL_fatal, AP_certificate_unknown);
}
validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
break;
case TlsCipherSuite.KE_DHE_RSA:
if (!(this.serverPublicKey instanceof RSAKeyParameters))
{
this.failWithError(AL_fatal, AP_certificate_unknown);
}
validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
break;
case TlsCipherSuite.KE_DHE_DSS:
if (!(this.serverPublicKey instanceof DSAPublicKeyParameters))
{
this.failWithError(AL_fatal, AP_certificate_unknown);
}
break;
default:
this.failWithError(AL_fatal, AP_unsupported_certificate);
}
/*
* Verify them.
*/
if (!this.verifyer.isValid(cert.getCerts()))
{
this.failWithError(AL_fatal, AP_user_canceled);
}
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
read = true;
break;
}
case HP_FINISHED:
switch (connection_state)
{
case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
/*
* Read the checksum from the finished message,
* it has always 12 bytes.
*/
byte[] receivedChecksum = new byte[12];
TlsUtils.readFully(receivedChecksum, is);
assertEmpty(is);
/*
* Calculate our own checksum.
*/
byte[] checksum = new byte[12];
byte[] md5andsha1 = new byte[16 + 20];
rs.hash2.doFinal(md5andsha1, 0);
TlsUtils.PRF(this.ms, TlsUtils.toByteArray("server finished"), md5andsha1, checksum);
/*
* Compare both checksums.
*/
for (int i = 0; i < receivedChecksum.length; i++)
{
if (receivedChecksum[i] != checksum[i])
{
/*
* Wrong checksum in the finished message.
*/
this.failWithError(AL_fatal, AP_handshake_failure);
}
}
connection_state = CS_DONE;
/*
* We are now ready to receive application data.
*/
this.appDataReady = true;
read = true;
break;
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
break;
case HP_SERVER_HELLO:
switch (connection_state)
{
case CS_CLIENT_HELLO_SEND:
/*
* Read the server hello message
*/
TlsUtils.checkVersion(is, this);
/*
* Read the server random
*/
this.serverRandom = new byte[32];
TlsUtils.readFully(this.serverRandom, is);
/*
* Currently, we don't support session ids
*/
short sessionIdLength = TlsUtils.readUint8(is);
byte[] sessionId = new byte[sessionIdLength];
TlsUtils.readFully(sessionId, is);
/*
* Find out which ciphersuite the server has
* chosen. If we don't support this ciphersuite,
* the TlsCipherSuiteManager will throw an
* exception.
*/
this.chosenCipherSuite = TlsCipherSuiteManager.getCipherSuite(TlsUtils.readUint16(is), this);
/*
* We support only the null compression which
* means no compression.
*/
short compressionMethod = TlsUtils.readUint8(is);
if (compressionMethod != 0)
{
this.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter);
}
assertEmpty(is);
connection_state = CS_SERVER_HELLO_RECEIVED;
read = true;
break;
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
break;
case HP_SERVER_HELLO_DONE:
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
/*
* There was no server key exchange message, check
* that we are doing RSA key exchange.
*/
if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA)
{
this.failWithError(AL_fatal, AP_unexpected_message);
}
/*
* NB: Fall through to next case label to continue RSA key exchange
*/
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
case CS_CERTIFICATE_REQUEST_RECEIVED:
assertEmpty(is);
boolean isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED);
connection_state = CS_SERVER_HELLO_DONE_RECEIVED;
if (isCertReq)
{
sendClientCertificate();
}
/*
* Send the client key exchange message, depending
* on the key exchange we are using in our
* ciphersuite.
*/
switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
{
case TlsCipherSuite.KE_RSA:
{
/*
* We are doing RSA key exchange. We will
* choose a pre master secret and send it
* rsa encrypted to the server.
*
* Prepare pre master secret.
*/
pms = new byte[48];
random.nextBytes(pms);
pms[0] = 3;
pms[1] = 1;
/*
* Encode the pms and send it to the server.
*
* Prepare an PKCS1Encoding with good random
* padding.
*/
RSABlindedEngine rsa = new RSABlindedEngine();
PKCS1Encoding encoding = new PKCS1Encoding(rsa);
encoding.init(true, new ParametersWithRandom(this.serverPublicKey, this.random));
byte[] encrypted = null;
try
{
encrypted = encoding.processBlock(pms, 0, pms.length);
}
catch (InvalidCipherTextException e)
{
/*
* This should never happen, only during decryption.
*/
this.failWithError(AL_fatal, AP_internal_error);
}
/*
* Send the encrypted pms.
*/
sendClientKeyExchange(encrypted);
break;
}
case TlsCipherSuite.KE_DHE_DSS:
case TlsCipherSuite.KE_DHE_RSA:
{
/*
* Send the Client Key Exchange message for
* DHE key exchange.
*/
byte[] YcByte = BigIntegers.asUnsignedByteArray(this.Yc);
sendClientKeyExchange(YcByte);
break;
}
default:
/*
* Problem during handshake, we don't know
* how to handle this key exchange method.
*/
this.failWithError(AL_fatal, AP_unexpected_message);
}
connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;
/*
* Now, we send change cipher state
*/
byte[] cmessage = new byte[1];
cmessage[0] = 1;
rs.writeMessage((short)RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.length);
connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;
/*
* Calculate the ms
*/
this.ms = new byte[48];
byte[] random = new byte[clientRandom.length + serverRandom.length];
System.arraycopy(clientRandom, 0, random, 0, clientRandom.length);
System.arraycopy(serverRandom, 0, random, clientRandom.length, serverRandom.length);
TlsUtils.PRF(pms, TlsUtils.toByteArray("master secret"), random, this.ms);
/*
* Initialize our cipher suite
*/
rs.writeSuite = this.chosenCipherSuite;
rs.writeSuite.init(this.ms, clientRandom, serverRandom);
/*
* Send our finished message.
*/
byte[] checksum = new byte[12];
byte[] md5andsha1 = new byte[16 + 20];
rs.hash1.doFinal(md5andsha1, 0);
TlsUtils.PRF(this.ms, TlsUtils.toByteArray("client finished"), md5andsha1, checksum);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_FINISHED, bos);
TlsUtils.writeUint24(12, bos);
bos.write(checksum);
byte[] message = bos.toByteArray();
rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
this.connection_state = CS_CLIENT_FINISHED_SEND;
read = true;
break;
default:
this.failWithError(AL_fatal, AP_handshake_failure);
}
break;
case HP_SERVER_KEY_EXCHANGE:
{
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
{
/*
* Check that we are doing DHE key exchange
*/
switch (this.chosenCipherSuite.getKeyExchangeAlgorithm())
{
case TlsCipherSuite.KE_DHE_RSA:
{
processDHEKeyExchange(is, new TlsRSASigner());
break;
}
case TlsCipherSuite.KE_DHE_DSS:
{
processDHEKeyExchange(is, new TlsDSSSigner());
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
read = true;
break;
}
case HP_CERTIFICATE_REQUEST:
{
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
/*
* There was no server key exchange message, check
* that we are doing RSA key exchange.
*/
if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA)
{
this.failWithError(AL_fatal, AP_unexpected_message);
}
/*
* NB: Fall through to next case label to continue RSA key exchange
*/
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
{
short typesLength = TlsUtils.readUint8(is);
byte[] types = new byte[typesLength];
TlsUtils.readFully(types, is);
int authsLength = TlsUtils.readUint16(is);
byte[] auths = new byte[authsLength];
TlsUtils.readFully(auths, is);
assertEmpty(is);
break;
}
default:
this.failWithError(AL_fatal, AP_unexpected_message);
}
this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
read = true;
break;
}
case HP_HELLO_REQUEST:
case HP_CLIENT_KEY_EXCHANGE:
case HP_CERTIFICATE_VERIFY:
case HP_CLIENT_HELLO:
default:
// We do not support this!
this.failWithError(AL_fatal, AP_unexpected_message);
break;
}
}
}
}
while (read);
}
private void processApplicationData()
{
/*
* There is nothing we need to do here.
*
* This function could be used for callbacks when application
* data arrives in the future.
*/
}
private void processAlert() throws IOException
{
while (alertQueue.size() >= 2)
{
/*
* An alert is always 2 bytes. Read the alert.
*/
byte[] tmp = new byte[2];
alertQueue.read(tmp, 0, 2, 0);
alertQueue.removeData(2);
short level = tmp[0];
short description = tmp[1];
if (level == AL_fatal)
{
/*
* This is a fatal error.
*/
this.failedWithError = true;
this.closed = true;
/*
* Now try to close the stream, ignore errors.
*/
try
{
rs.close();
}
catch (Exception e)
{
}
throw new IOException(TLS_ERROR_MESSAGE);
}
else
{
/*
* This is just a warning.
*/
if (description == AP_close_notify)
{
/*
* Close notify
*/
this.failWithError(AL_warning, AP_close_notify);
}
/*
* If it is just a warning, we continue.
*/
}
}
}
/**
* This method is called, when a change cipher spec message is received.
*
* @throws IOException If the message has an invalid content or the
* handshake is not in the correct state.
*/
private void processChangeCipherSpec() throws IOException
{
while (changeCipherSpecQueue.size() > 0)
{
/*
* A change cipher spec message is only one byte with the value 1.
*/
byte[] b = new byte[1];
changeCipherSpecQueue.read(b, 0, 1, 0);
changeCipherSpecQueue.removeData(1);
if (b[0] != 1)
{
/*
* This should never happen.
*/
this.failWithError(AL_fatal, AP_unexpected_message);
}
else
{
/*
* Check if we are in the correct connection state.
*/
if (this.connection_state == CS_CLIENT_FINISHED_SEND)
{
rs.readSuite = rs.writeSuite;
this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
}
else
{
/*
* We are not in the correct connection state.
*/
this.failWithError(AL_fatal, AP_handshake_failure);
}
}
}
}
private void processDHEKeyExchange(ByteArrayInputStream is, Signer signer)
throws IOException
{
InputStream sigIn = is;
if (signer != null)
{
signer.init(false, this.serverPublicKey);
signer.update(this.clientRandom, 0, this.clientRandom.length);
signer.update(this.serverRandom, 0, this.serverRandom.length);
sigIn = new SignerInputStream(is, signer);
}
/*
* Parse the Structure
*/
int pLength = TlsUtils.readUint16(sigIn);
byte[] pByte = new byte[pLength];
TlsUtils.readFully(pByte, sigIn);
int gLength = TlsUtils.readUint16(sigIn);
byte[] gByte = new byte[gLength];
TlsUtils.readFully(gByte, sigIn);
int YsLength = TlsUtils.readUint16(sigIn);
byte[] YsByte = new byte[YsLength];
TlsUtils.readFully(YsByte, sigIn);
if (signer != null)
{
int sigLength = TlsUtils.readUint16(is);
byte[] sigByte = new byte[sigLength];
TlsUtils.readFully(sigByte, is);
/*
* Verify the Signature.
*/
if (!signer.verifySignature(sigByte))
{
this.failWithError(AL_fatal, AP_bad_certificate);
}
}
this.assertEmpty(is);
/*
* Do the DH calculation.
*/
BigInteger p = new BigInteger(1, pByte);
BigInteger g = new BigInteger(1, gByte);
BigInteger Ys = new BigInteger(1, YsByte);
/*
* Check the DH parameter values
*/
if (!p.isProbablePrime(10))
{
this.failWithError(AL_fatal, AP_illegal_parameter);
}
if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0)
{
this.failWithError(AL_fatal, AP_illegal_parameter);
}
// TODO For static DH public values, see additional checks in RFC 2631 2.1.5
if (Ys.compareTo(TWO) < 0 || Ys.compareTo(p.subtract(ONE)) > 0)
{
this.failWithError(AL_fatal, AP_illegal_parameter);
}
/*
* Diffie-Hellman basic key agreement
*/
DHParameters dhParams = new DHParameters(p, g);
// Generate a keypair
DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator();
dhGen.init(new DHKeyGenerationParameters(random, dhParams));
AsymmetricCipherKeyPair dhPair = dhGen.generateKeyPair();
// Store the public value to send to server
this.Yc = ((DHPublicKeyParameters)dhPair.getPublic()).getY();
// Calculate the shared secret
DHBasicAgreement dhAgree = new DHBasicAgreement();
dhAgree.init(dhPair.getPrivate());
BigInteger agreement = dhAgree.calculateAgreement(new DHPublicKeyParameters(Ys, dhParams));
this.pms = BigIntegers.asUnsignedByteArray(agreement);
}
private void validateKeyUsage(X509CertificateStructure c, int keyUsageBits)
throws IOException
{
X509Extensions exts = c.getTBSCertificate().getExtensions();
if (exts != null)
{
X509Extension ext = exts.getExtension(X509Extensions.KeyUsage);
if (ext != null)
{
DERBitString ku = KeyUsage.getInstance(ext);
int bits = ku.getBytes()[0] & 0xff;
if ((bits & keyUsageBits) != keyUsageBits)
{
this.failWithError(AL_fatal, AP_certificate_unknown);
}
}
}
}
private void sendClientCertificate() throws IOException
{
/*
* just write back the "no client certificate" message
* see also gnutls, auth_cert.c:643 (0B 00 00 03 00 00 00)
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CERTIFICATE, bos);
TlsUtils.writeUint24(3, bos);
TlsUtils.writeUint24(0, bos);
byte[] message = bos.toByteArray();
rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
}
private void sendClientKeyExchange(byte[] keData) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CLIENT_KEY_EXCHANGE, bos);
TlsUtils.writeUint24(keData.length + 2, bos);
TlsUtils.writeUint16(keData.length, bos);
bos.write(keData);
byte[] message = bos.toByteArray();
rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length);
}
/**
* Connects to the remote system.
*
* @param verifyer Will be used when a certificate is received to verify
* that this certificate is accepted by the client.
* @throws IOException If handshake was not successful.
*/
public void connect(CertificateVerifyer verifyer) throws IOException
{
this.verifyer = verifyer;
/*
* Send Client hello
*
* First, generate some random data.
*/
this.clientRandom = new byte[32];
random.nextBytes(this.clientRandom);
int t = (int)(System.currentTimeMillis() / 1000);
this.clientRandom[0] = (byte)(t >> 24);
this.clientRandom[1] = (byte)(t >> 16);
this.clientRandom[2] = (byte)(t >> 8);
this.clientRandom[3] = (byte)t;
ByteArrayOutputStream os = new ByteArrayOutputStream();
TlsUtils.writeVersion(os);
os.write(this.clientRandom);
/*
* Length of Session id
*/
TlsUtils.writeUint8((short)0, os);
/*
* Cipher suites
*/
TlsCipherSuiteManager.writeCipherSuites(os);
/*
* Compression methods, just the null method.
*/
byte[] compressionMethods = new byte[]{0x00};
TlsUtils.writeUint8((short)compressionMethods.length, os);
os.write(compressionMethods);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TlsUtils.writeUint8(HP_CLIENT_HELLO, bos);
TlsUtils.writeUint24(os.size(), bos);
bos.write(os.toByteArray());
byte[] message = bos.toByteArray();
rs.writeMessage(RL_HANDSHAKE, message, 0, message.length);
connection_state = CS_CLIENT_HELLO_SEND;
/*
* We will now read data, until we have completed the handshake.
*/
while (connection_state != CS_DONE)
{
rs.readData();
}
this.tlsInputStream = new TlsInputStream(this);
this.tlsOutputStream = new TlsOuputStream(this);
}
/**
* Read data from the network. The method will return immediately, if there is
* still some data left in the buffer, or block until some application
* data has been read from the network.
*
* @param buf The buffer where the data will be copied to.
* @param offset The position where the data will be placed in the buffer.
* @param len The maximum number of bytes to read.
* @return The number of bytes read.
* @throws IOException If something goes wrong during reading data.
*/
protected int readApplicationData(byte[] buf, int offset, int len) throws IOException
{
while (applicationDataQueue.size() == 0)
{
/*
* We need to read some data.
*/
if (this.failedWithError)
{
/*
* Something went terribly wrong, we should throw an IOException
*/
throw new IOException(TLS_ERROR_MESSAGE);
}
if (this.closed)
{
/*
* Connection has been closed, there is no more data to read.
*/
return -1;
}
try
{
rs.readData();
}
catch (IOException e)
{
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
catch (RuntimeException e)
{
if (!this.closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
}
len = Math.min(len, applicationDataQueue.size());
applicationDataQueue.read(buf, offset, len, 0);
applicationDataQueue.removeData(len);
return len;
}
/**
* Send some application data to the remote system.
* <p/>
* The method will handle fragmentation internally.
*
* @param buf The buffer with the data.
* @param offset The position in the buffer where the data is placed.
* @param len The length of the data.
* @throws IOException If something goes wrong during sending.
*/
protected void writeData(byte[] buf, int offset, int len) throws IOException
{
if (this.failedWithError)
{
throw new IOException(TLS_ERROR_MESSAGE);
}
if (this.closed)
{
throw new IOException("Sorry, connection has been closed, you cannot write more data");
}
/*
* Protect against known IV attack!
*
* DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT
* YOU ARE DOING HERE.
*/
rs.writeMessage(RL_APPLICATION_DATA, emptybuf, 0, 0);
do
{
/*
* We are only allowed to write fragments up to 2^14 bytes.
*/
int toWrite = Math.min(len, 1 << 14);
try
{
rs.writeMessage(RL_APPLICATION_DATA, buf, offset, toWrite);
}
catch (IOException e)
{
if (!closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
catch (RuntimeException e)
{
if (!closed)
{
this.failWithError(AL_fatal, AP_internal_error);
}
throw e;
}
offset += toWrite;
len -= toWrite;
}
while (len > 0);
}
/** @deprecated use 'getOutputStream' instead */
public TlsOuputStream getTlsOuputStream()
{
return this.tlsOutputStream;
}
/**
* @return An OutputStream which can be used to send data.
*/
public OutputStream getOutputStream()
{
return this.tlsOutputStream;
}
/** @deprecated use 'getInputStream' instead */
public TlsInputStream getTlsInputStream()
{
return this.tlsInputStream;
}
/**
* @return An InputStream which can be used to read data.
*/
public InputStream getInputStream()
{
return this.tlsInputStream;
}
/**
* Terminate this connection whith an alert.
* <p/>
* Can be used for normal closure too.
*
* @param alertLevel The level of the alert, an be AL_fatal or AL_warning.
* @param alertDescription The exact alert message.
* @throws IOException If alert was fatal.
*/
protected void failWithError(short alertLevel, short alertDescription) throws IOException
{
/*
* Check if the connection is still open.
*/
if (!closed)
{
/*
* Prepare the message
*/
byte[] error = new byte[2];
error[0] = (byte)alertLevel;
error[1] = (byte)alertDescription;
this.closed = true;
if (alertLevel == AL_fatal)
{
/*
* This is a fatal message.
*/
this.failedWithError = true;
}
rs.writeMessage(RL_ALERT, error, 0, 2);
rs.close();
if (alertLevel == AL_fatal)
{
throw new IOException(TLS_ERROR_MESSAGE);
}
}
else
{
throw new IOException(TLS_ERROR_MESSAGE);
}
}
/**
* Closes this connection.
*
* @throws IOException If something goes wrong during closing.
*/
public void close() throws IOException
{
if (!closed)
{
this.failWithError((short)1, (short)0);
}
}
/**
* Make sure the InputStream is now empty. Fail otherwise.
*
* @param is The InputStream to check.
* @throws IOException If is is not empty.
*/
protected void assertEmpty(ByteArrayInputStream is) throws IOException
{
if (is.available() > 0)
{
this.failWithError(AL_fatal, AP_decode_error);
}
}
protected void flush() throws IOException
{
rs.flush();
}
}