private void ProcessHandshakeMessage(HandshakeType type, byte[] buf)
{
MemoryStream inStr = new MemoryStream(buf, false);
/*
* Check the type.
*/
switch (type)
{
case HandshakeType.certificate:
{
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
{
// Parse the Certificate message and send to cipher suite
Certificate serverCertificate = Certificate.Parse(inStr);
AssertEmpty(inStr);
this.keyExchange.ProcessServerCertificate(serverCertificate);
this.authentication = tlsClient.GetAuthentication();
this.authentication.NotifyServerCertificate(serverCertificate);
break;
}
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
break;
}
case HandshakeType.finished:
switch (connection_state)
{
case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
/*
* Read the checksum from the finished message, it has always 12 bytes.
*/
byte[] serverVerifyData = new byte[12];
TlsUtilities.ReadFully(serverVerifyData, inStr);
AssertEmpty(inStr);
/*
* Calculate our own checksum.
*/
byte[] expectedServerVerifyData = TlsUtilities.PRF(
securityParameters.masterSecret, "server finished",
rs.GetCurrentHash(), 12);
/*
* Compare both checksums.
*/
if (!Arrays.ConstantTimeAreEqual(expectedServerVerifyData, serverVerifyData))
{
/*
* Wrong checksum in the finished message.
*/
this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
}
connection_state = CS_DONE;
/*
* We are now ready to receive application data.
*/
this.appDataReady = true;
break;
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
break;
case HandshakeType.server_hello:
switch (connection_state)
{
case CS_CLIENT_HELLO_SEND:
/*
* Read the server hello message
*/
TlsUtilities.CheckVersion(inStr);
/*
* Read the server random
*/
securityParameters.serverRandom = new byte[32];
TlsUtilities.ReadFully(securityParameters.serverRandom, inStr);
byte[] sessionID = TlsUtilities.ReadOpaque8(inStr);
if (sessionID.Length > 32)
{
this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
}
this.tlsClient.NotifySessionID(sessionID);
/*
* Find out which CipherSuite the server has chosen and check that
* it was one of the offered ones.
*/
CipherSuite selectedCipherSuite = (CipherSuite)TlsUtilities.ReadUint16(inStr);
if (!ArrayContains(offeredCipherSuites, selectedCipherSuite)
|| selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
{
this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
}
this.tlsClient.NotifySelectedCipherSuite(selectedCipherSuite);
/*
* Find out which CompressionMethod the server has chosen and check that
* it was one of the offered ones.
*/
CompressionMethod selectedCompressionMethod = (CompressionMethod)TlsUtilities.ReadUint8(inStr);
if (!ArrayContains(offeredCompressionMethods, selectedCompressionMethod))
{
this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
}
this.tlsClient.NotifySelectedCompressionMethod(selectedCompressionMethod);
/*
* RFC3546 2.2 The extended server hello message format MAY be
* sent in place of the server hello message when the client has
* requested extended functionality via the extended client hello
* message specified in Section 2.1.
* ...
* Note that the extended server hello message is only sent in response
* to an extended client hello message. This prevents the possibility
* that the extended server hello message could "break" existing TLS 1.0
* clients.
*/
/*
* TODO RFC 3546 2.3
* If [...] the older session is resumed, then the server MUST ignore
* extensions appearing in the client hello, and send a server hello
* containing no extensions.
*/
// ExtensionType -> byte[]
IDictionary serverExtensions = Platform.CreateHashtable();
if (inStr.Position < inStr.Length)
{
// Process extensions from extended server hello
byte[] extBytes = TlsUtilities.ReadOpaque16(inStr);
MemoryStream ext = new MemoryStream(extBytes, false);
while (ext.Position < ext.Length)
{
ExtensionType extType = (ExtensionType)TlsUtilities.ReadUint16(ext);
byte[] extValue = TlsUtilities.ReadOpaque16(ext);
// Note: RFC 5746 makes a special case for EXT_RenegotiationInfo
if (extType != ExtensionType.renegotiation_info
&& !clientExtensions.Contains(extType))
{
/*
* RFC 3546 2.3
* Note that for all extension types (including those defined in
* future), the extension type MUST NOT appear in the extended server
* hello unless the same extension type appeared in the corresponding
* client hello. Thus clients MUST abort the handshake if they receive
* an extension type in the extended server hello that they did not
* request in the associated (extended) client hello.
*/
this.FailWithError(AlertLevel.fatal, AlertDescription.unsupported_extension);
}
if (serverExtensions.Contains(extType))
{
/*
* RFC 3546 2.3
* Also note that when multiple extensions of different types are
* present in the extended client hello or the extended server hello,
* the extensions may appear in any order. There MUST NOT be more than
* one extension of the same type.
*/
this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
}
serverExtensions.Add(extType, extValue);
}
}
AssertEmpty(inStr);
/*
* RFC 5746 3.4. When a ServerHello is received, the client MUST check if it
* includes the "renegotiation_info" extension:
*/
{
bool secure_negotiation = serverExtensions.Contains(ExtensionType.renegotiation_info);
/*
* If the extension is present, set the secure_renegotiation flag
* to TRUE. The client MUST then verify that the length of the
* "renegotiated_connection" field is zero, and if it is not, MUST
* abort the handshake (by sending a fatal handshake_failure
* alert).
*/
if (secure_negotiation)
{
byte[] renegExtValue = (byte[])serverExtensions[ExtensionType.renegotiation_info];
if (!Arrays.ConstantTimeAreEqual(renegExtValue,
CreateRenegotiationInfo(emptybuf)))
{
this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
}
}
tlsClient.NotifySecureRenegotiation(secure_negotiation);
}
if (clientExtensions != null)
{
tlsClient.ProcessServerExtensions(serverExtensions);
}
this.keyExchange = tlsClient.GetKeyExchange();
connection_state = CS_SERVER_HELLO_RECEIVED;
break;
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
break;
case HandshakeType.server_hello_done:
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
case CS_SERVER_CERTIFICATE_RECEIVED:
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
case CS_CERTIFICATE_REQUEST_RECEIVED:
// NB: Original code used case label fall-through
if (connection_state == CS_SERVER_HELLO_RECEIVED)
{
// There was no server certificate message; check it's OK
this.keyExchange.SkipServerCertificate();
this.authentication = null;
// There was no server key exchange message; check it's OK
this.keyExchange.SkipServerKeyExchange();
}
else if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
{
// There was no server key exchange message; check it's OK
this.keyExchange.SkipServerKeyExchange();
}
AssertEmpty(inStr);
connection_state = CS_SERVER_HELLO_DONE_RECEIVED;
TlsCredentials clientCreds = null;
if (certificateRequest == null)
{
this.keyExchange.SkipClientCredentials();
}
else
{
clientCreds = this.authentication.GetClientCredentials(certificateRequest);
Certificate clientCert;
if (clientCreds == null)
{
this.keyExchange.SkipClientCredentials();
clientCert = Certificate.EmptyChain;
}
else
{
this.keyExchange.ProcessClientCredentials(clientCreds);
clientCert = clientCreds.Certificate;
}
SendClientCertificate(clientCert);
}
/*
* Send the client key exchange message, depending on the key
* exchange we are using in our CipherSuite.
*/
SendClientKeyExchange();
connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;
if (clientCreds != null && clientCreds is TlsSignerCredentials)
{
TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds;
byte[] md5andsha1 = rs.GetCurrentHash();
byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature(
md5andsha1);
SendCertificateVerify(clientCertificateSignature);
connection_state = CS_CERTIFICATE_VERIFY_SEND;
}
/*
* Now, we send change cipher state
*/
byte[] cmessage = new byte[1];
cmessage[0] = 1;
rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length);
connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;
/*
* Calculate the master_secret
*/
byte[] pms = this.keyExchange.GeneratePremasterSecret();
securityParameters.masterSecret = TlsUtilities.PRF(pms, "master secret",
TlsUtilities.Concat(securityParameters.clientRandom, securityParameters.serverRandom),
48);
// TODO Is there a way to ensure the data is really overwritten?
/*
* RFC 2246 8.1. The pre_master_secret should be deleted from
* memory once the master_secret has been computed.
*/
Array.Clear(pms, 0, pms.Length);
/*
* Initialize our cipher suite
*/
rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());
/*
* Send our finished message.
*/
byte[] clientVerifyData = TlsUtilities.PRF(securityParameters.masterSecret,
"client finished", rs.GetCurrentHash(), 12);
MemoryStream bos = new MemoryStream();
TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos);
TlsUtilities.WriteOpaque24(clientVerifyData, bos);
byte[] message = bos.ToArray();
rs.WriteMessage(ContentType.handshake, message, 0, message.Length);
this.connection_state = CS_CLIENT_FINISHED_SEND;
break;
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
break;
}
break;
case HandshakeType.server_key_exchange:
{
switch (connection_state)
{
case CS_SERVER_HELLO_RECEIVED:
case CS_SERVER_CERTIFICATE_RECEIVED:
{
// NB: Original code used case label fall-through
if (connection_state == CS_SERVER_HELLO_RECEIVED)
{
// There was no server certificate message; check it's OK
this.keyExchange.SkipServerCertificate();
this.authentication = null;
}
this.keyExchange.ProcessServerKeyExchange(inStr);
AssertEmpty(inStr);
break;
}
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
break;
}
case HandshakeType.certificate_request:
switch (connection_state)
{
case CS_SERVER_CERTIFICATE_RECEIVED:
case CS_SERVER_KEY_EXCHANGE_RECEIVED:
{
// NB: Original code used case label fall-through
if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
{
// There was no server key exchange message; check it's OK
this.keyExchange.SkipServerKeyExchange();
}
if (this.authentication == null)
{
/*
* RFC 2246 7.4.4. It is a fatal handshake_failure alert
* for an anonymous server to request client identification.
*/
this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
}
int numTypes = TlsUtilities.ReadUint8(inStr);
ClientCertificateType[] certificateTypes = new ClientCertificateType[numTypes];
for (int i = 0; i < numTypes; ++i)
{
certificateTypes[i] = (ClientCertificateType)TlsUtilities.ReadUint8(inStr);
}
byte[] authorities = TlsUtilities.ReadOpaque16(inStr);
AssertEmpty(inStr);
IList authorityDNs = Platform.CreateArrayList();
MemoryStream bis = new MemoryStream(authorities, false);
while (bis.Position < bis.Length)
{
byte[] dnBytes = TlsUtilities.ReadOpaque16(bis);
// TODO Switch to X500Name when available
authorityDNs.Add(X509Name.GetInstance(Asn1Object.FromByteArray(dnBytes)));
}
this.certificateRequest = new CertificateRequest(certificateTypes,
authorityDNs);
this.keyExchange.ValidateCertificateRequest(this.certificateRequest);
break;
}
default:
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
break;
case HandshakeType.hello_request:
/*
* RFC 2246 7.4.1.1 Hello request
* This message will be ignored by the client if the client is currently
* negotiating a session. This message may be ignored by the client if it
* does not wish to renegotiate a session, or the client may, if it wishes,
* respond with a no_renegotiation alert.
*/
if (connection_state == CS_DONE)
{
// Renegotiation not supported yet
SendAlert(AlertLevel.warning, AlertDescription.no_renegotiation);
}
break;
case HandshakeType.client_key_exchange:
case HandshakeType.certificate_verify:
case HandshakeType.client_hello:
default:
// We do not support this!
this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
break;
}
}