/* * Mentalis.org Security Library * * Copyright © 2002-2005, The KPD-Team * All rights reserved. * http://www.mentalis.org/ * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Neither the name of the KPD-Team, nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.IO; using System.Text; using System.Collections; using System.Collections.Specialized; using System.Security.Cryptography; using System.Runtime.InteropServices; using Org.Mentalis.Security.Cryptography; using Org.Mentalis.Security.Certificates; using Org.Mentalis.Security.Ssl; namespace Org.Mentalis.Security.Ssl.Shared { /* Client Server ClientHello --------> ServerHello Certificate* ServerKeyExchange* CertificateRequest* <-------- ServerHelloDone Certificate* ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------> [ChangeCipherSpec] <-------- Finished Application Data <-------> Application Data */ internal abstract class ClientHandshakeLayer : HandshakeLayer { public ClientHandshakeLayer(RecordLayer recordLayer, SecurityOptions options) : base(recordLayer, options) {} public ClientHandshakeLayer(HandshakeLayer handshakeLayer) : base(handshakeLayer) {} protected override SslHandshakeStatus ProcessMessage(HandshakeMessage message) { // throws SslExceptions SslHandshakeStatus ret = new SslHandshakeStatus(); switch(message.type) { case HandshakeType.ServerHello: ret = ProcessServerHello(message); break; case HandshakeType.Certificate: // optional ret = ProcessCertificate(message, true); break; case HandshakeType.ServerKeyExchange: // optional ret = ProcessServerKeyExchange(message); break; case HandshakeType.CertificateRequest: // optional ret = ProcessCertificateRequest(message); break; case HandshakeType.ServerHelloDone: ret = ProcessServerHelloDone(message); break; case HandshakeType.Finished: ret = ProcessFinished(message); break; case HandshakeType.HelloRequest: ret = ProcessHelloRequest(message); break; default: throw new SslException(AlertDescription.UnexpectedMessage, "The received message was not expected from a server."); } return ret; } protected override byte[] GetClientHello() { if (m_State != HandshakeType.Nothing && m_State != HandshakeType.Finished) throw new SslException(AlertDescription.UnexpectedMessage, "ClientHello message must be the first message or must be preceded by a Finished message."); m_IsNegotiating = true; m_State = HandshakeType.ClientHello; byte[] ciphers = CipherSuites.GetCipherAlgorithmBytes(m_Options.AllowedAlgorithms); byte[] compr = CompressionAlgorithm.GetCompressionAlgorithmBytes(m_Options.AllowedAlgorithms); HandshakeMessage temp = new HandshakeMessage(HandshakeType.ClientHello, new byte[38 + ciphers.Length + compr.Length]); m_ClientTime = GetUnixTime(); m_ClientRandom = new byte[28]; m_RNG.GetBytes(m_ClientRandom); ProtocolVersion pv = CompatibilityLayer.GetMaxProtocol(m_Options.Protocol); temp.fragment[0] = pv.major; temp.fragment[1] = pv.minor; Array.Copy(m_ClientTime, 0, temp.fragment, 2, 4); Array.Copy(m_ClientRandom, 0, temp.fragment, 6, 28); temp.fragment[34] = 0; // do not resume a session, and do not let the other side cache it temp.fragment[35] = (byte)(ciphers.Length / 256); temp.fragment[36] = (byte)(ciphers.Length % 256); Array.Copy(ciphers, 0, temp.fragment, 37, ciphers.Length); temp.fragment[37 + ciphers.Length] = (byte)compr.Length; Array.Copy(compr, 0, temp.fragment, 38 + ciphers.Length, compr.Length); byte[] ret = temp.ToBytes(); UpdateHashes(ret, HashUpdate.All); // client hello message return m_RecordLayer.EncryptBytes(ret, 0, ret.Length, ContentType.Handshake); } protected SslHandshakeStatus ProcessServerHello(HandshakeMessage message) { if (m_State != HandshakeType.ClientHello && m_State != HandshakeType.HelloRequest) throw new SslException(AlertDescription.UnexpectedMessage, "ServerHello message must be preceded by a ClientHello message."); UpdateHashes(message, HashUpdate.All); // input message if (message.fragment.Length < 2 || message.fragment[0] != GetVersion().major || message.fragment[1] != GetVersion().minor) throw new SslException(AlertDescription.IllegalParameter, "Unknown protocol version of the client."); try { // extract the time from the client [== 1 uint] m_ServerTime = new byte[4]; Array.Copy(message.fragment, 2, m_ServerTime, 0, 4); // extract the random bytes [== 28 bytes] m_ServerRandom = new byte[28]; Array.Copy(message.fragment, 6, m_ServerRandom, 0, 28); // extact the session ID [== 0..32 bytes] int length = message.fragment[34]; if (length > 32) throw new SslException(AlertDescription.IllegalParameter, "The length of the SessionID cannot be more than 32 bytes."); m_SessionID = new byte[length]; Array.Copy(message.fragment, 35, m_SessionID, 0, length); // extract the selected cipher suite m_EncryptionScheme = CipherSuites.GetCipherAlgorithmType(message.fragment, 35 + length); // extract the selected compression method m_CompressionMethod = CompressionAlgorithm.GetCompressionAlgorithmType(message.fragment, 37 + length); } catch (Exception e) { throw new SslException(e, AlertDescription.InternalError, "The message is invalid."); } return new SslHandshakeStatus(SslStatus.MessageIncomplete, null); } protected SslHandshakeStatus ProcessServerKeyExchange(HandshakeMessage message) { if (m_State != HandshakeType.Certificate) throw new SslException(AlertDescription.UnexpectedMessage, "ServerKeyExchange message must be preceded by a Certificate message."); CipherDefinition cd = CipherSuites.GetCipherDefinition(m_EncryptionScheme); if (!cd.Exportable) throw new SslException(AlertDescription.HandshakeFailure, "The ServerKeyExchange message should not be sent for non-exportable ciphers."); if (m_RemoteCertificate.GetPublicKeyLength() <= 512) throw new SslException(AlertDescription.HandshakeFailure, "The ServerKeyExchange message should not be sent because the server certificate public key is of exportable length."); UpdateHashes(message, HashUpdate.All); // input message // extract modulus and exponent RSAParameters pars = new RSAParameters(); int size = message.fragment[0] * 256 + message.fragment[1]; pars.Modulus = new byte[size]; Array.Copy(message.fragment, 2, pars.Modulus, 0, size); int offset = size + 2; size = message.fragment[offset] * 256 + message.fragment[offset + 1]; pars.Exponent = new byte[size]; Array.Copy(message.fragment, offset + 2, pars.Exponent, 0, size); offset += size + 2; pars.Modulus = RemoveLeadingZeros(pars.Modulus); pars.Exponent = RemoveLeadingZeros(pars.Exponent); m_KeyCipher = new RSACryptoServiceProvider(); m_KeyCipher.ImportParameters(pars); // compute verification hashes MD5SHA1CryptoServiceProvider ms = new MD5SHA1CryptoServiceProvider(); ms.TransformBlock(m_ClientTime, 0, m_ClientTime.Length, m_ClientTime, 0); ms.TransformBlock(m_ClientRandom, 0, m_ClientRandom.Length, m_ClientRandom, 0); ms.TransformBlock(m_ServerTime, 0, m_ServerTime.Length, m_ServerTime, 0); ms.TransformBlock(m_ServerRandom, 0, m_ServerRandom.Length, m_ServerRandom, 0); ms.TransformFinalBlock(message.fragment, 0, offset); // verify the signature size = message.fragment[offset] * 256 + message.fragment[offset + 1]; byte[] signature = new byte[size]; // holds the signature returned by the server Array.Copy(message.fragment, offset + 2, signature, 0, size); if (!ms.VerifySignature(m_RemoteCertificate, signature)) throw new SslException(AlertDescription.HandshakeFailure, "The data was not signed by the server certificate."); ms.Clear(); return new SslHandshakeStatus(SslStatus.MessageIncomplete, null); } private byte[] RemoveLeadingZeros(byte[] input) { int occ = 0; for(int i = 0; i < input.Length; i++) { if (input[i] != 0) break; occ++; } if (occ == 0) { return input; } else { byte[] buffer = new byte[input.Length - occ]; Array.Copy(input, occ, buffer, 0, buffer.Length); return buffer; } } protected SslHandshakeStatus ProcessCertificateRequest(HandshakeMessage message) { if (m_State == HandshakeType.ServerKeyExchange) { CipherDefinition cd = CipherSuites.GetCipherDefinition(m_EncryptionScheme); if (this.m_RemoteCertificate.GetPublicKeyLength() <= 512 || !cd.Exportable) throw new SslException(AlertDescription.HandshakeFailure, "Invalid message."); } else if (m_State != HandshakeType.Certificate) { throw new SslException(AlertDescription.UnexpectedMessage, "CertificateRequest message must be preceded by a Certificate or ServerKeyExchange message."); } UpdateHashes(message, HashUpdate.All); // input message // get supported certificate types bool supportsRsaCerts = false; byte[] certTypes = new byte[message.fragment[0]]; // currently we're not doing anything with the supported certificate types Array.Copy(message.fragment, 1, certTypes, 0, certTypes.Length); for(int i = 0; i < certTypes.Length; i++) { if (certTypes[i] == 1) { // rsa_sign supportsRsaCerts = true; break; } } // get list of distinguished names if (m_Options.RequestHandler != null && supportsRsaCerts) { // make sure the client passed a delegate Queue q = new Queue(); DistinguishedNameList r = new DistinguishedNameList(); int size, offset = message.fragment[0] + 3; byte[] buffer; while(offset < message.fragment.Length) { size = message.fragment[offset] * 256 + message.fragment[offset + 1]; buffer = new byte[size]; Array.Copy(message.fragment, offset + 2, buffer, 0, size); q.Enqueue(buffer); offset += size + 2; } // decode RDN structures while(q.Count > 0) { r.Add(ProcessName((byte[])q.Dequeue())); } RequestEventArgs e = new RequestEventArgs(); try { m_Options.RequestHandler(Parent, r, e); if (e.Certificate != null) m_Options.Certificate = e.Certificate; } catch (Exception de) { throw new SslException(de, AlertDescription.InternalError, "The code in the CertRequestEventHandler delegate threw an error."); } } if (!supportsRsaCerts) m_Options.Certificate = null; // do not send client certificate m_MutualAuthentication = true; return new SslHandshakeStatus(SslStatus.MessageIncomplete, null); } private DistinguishedName ProcessName(byte[] buffer) { GCHandle gch = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { return new DistinguishedName(gch.AddrOfPinnedObject(), buffer.Length); } finally { gch.Free(); } } protected SslHandshakeStatus ProcessServerHelloDone(HandshakeMessage message) { if (m_State != HandshakeType.Certificate && m_State != HandshakeType.ServerKeyExchange && m_State != HandshakeType.CertificateRequest) throw new SslException(AlertDescription.UnexpectedMessage, "ServerHello message must be preceded by a ClientHello message."); if (message.fragment.Length != 0) throw new SslException(AlertDescription.IllegalParameter, "The ServerHelloDone message is invalid."); UpdateHashes(message, HashUpdate.All); // input message MemoryStream ms = new MemoryStream(); HandshakeMessage hm = new HandshakeMessage(HandshakeType.ClientKeyExchange, null); byte[] buffer; // send Certificate [optional] if (m_MutualAuthentication) { hm.type = HandshakeType.Certificate; hm.fragment = GetCertificateBytes(m_Options.Certificate); buffer = m_RecordLayer.EncryptBytes(hm.ToBytes(), 0, hm.fragment.Length + 4, ContentType.Handshake); ms.Write(buffer, 0, buffer.Length); UpdateHashes(hm, HashUpdate.All); // output message } // send ClientKeyExchange if (m_KeyCipher == null) m_KeyCipher = (RSACryptoServiceProvider)m_RemoteCertificate.PublicKey; RSAKeyTransform kf = new RSAKeyTransform(m_KeyCipher); byte[] preMasterSecret = new byte[48]; m_RNG.GetBytes(preMasterSecret); ProtocolVersion pv = CompatibilityLayer.GetMaxProtocol(m_Options.Protocol); preMasterSecret[0] = pv.major; preMasterSecret[1] = pv.minor; buffer = kf.CreateKeyExchange(preMasterSecret); // public-key-encrypt the preMasterSecret hm.type = HandshakeType.ClientKeyExchange; if (GetProtocol() == SecureProtocol.Ssl3) { // SSL hm.fragment = buffer; } else { // TLS hm.fragment = new byte[buffer.Length + 2]; Array.Copy(buffer, 0, hm.fragment, 2, buffer.Length); hm.fragment[0] = (byte)(buffer.Length / 256); // prepend the length of the preMasterSecret hm.fragment[1] = (byte)(buffer.Length % 256); } GenerateCiphers(preMasterSecret); // generate the local ciphers buffer = m_RecordLayer.EncryptBytes(hm.ToBytes(), 0, hm.fragment.Length + 4, ContentType.Handshake); ms.Write(buffer, 0, buffer.Length); UpdateHashes(hm, HashUpdate.All); // output message m_KeyCipher.Clear(); m_KeyCipher = null; // send CertificateVerify [optional] if (m_MutualAuthentication && m_Options.Certificate != null) { m_CertSignHash.MasterKey = this.m_MasterSecret; m_CertSignHash.TransformFinalBlock(buffer, 0, 0); // finalize hash buffer = m_CertSignHash.CreateSignature(m_Options.Certificate); hm.type = HandshakeType.CertificateVerify; hm.fragment = new byte[buffer.Length + 2]; hm.fragment[0] = (byte)(buffer.Length / 256); hm.fragment[1] = (byte)(buffer.Length % 256); Array.Copy(buffer, 0, hm.fragment, 2, buffer.Length); buffer = m_RecordLayer.EncryptBytes(hm.ToBytes(), 0, hm.fragment.Length + 4, ContentType.Handshake); ms.Write(buffer, 0, buffer.Length); UpdateHashes(hm, HashUpdate.LocalRemote); // output message } // send ChangeCipherSpec buffer = m_RecordLayer.EncryptBytes(new byte[]{1}, 0, 1, ContentType.ChangeCipherSpec); ms.Write(buffer, 0, buffer.Length); m_RecordLayer.ChangeLocalState(null, m_CipherSuite.Encryptor, m_CipherSuite.LocalHasher); // send Finished buffer = GetFinishedMessage(); UpdateHashes(buffer, HashUpdate.Remote); // output message buffer = m_RecordLayer.EncryptBytes(buffer, 0, buffer.Length, ContentType.Handshake); ms.Write(buffer, 0, buffer.Length); // send empty record [http://www.openssl.org/~bodo/tls-cbc.txt] if (this.m_CipherSuite.Encryptor.OutputBlockSize != 1) { // is bulk cipher? if (((int)m_Options.Flags & (int)SecurityFlags.DontSendEmptyRecord) == 0) { byte[] empty = m_RecordLayer.EncryptBytes(new byte[0], 0, 0, ContentType.ApplicationData); ms.Write(empty, 0, empty.Length); } } // finalize buffer = ms.ToArray(); ms.Close(); return new SslHandshakeStatus(SslStatus.ContinueNeeded, buffer); } protected byte[] GetCertificateBytes(Certificate certificate) { if (certificate == null) return new byte[]{0, 0, 0}; byte[] cert_bytes = certificate.ToCerBuffer(); byte[] ret = new byte[6 + cert_bytes.Length]; int size = cert_bytes.Length + 3; ret[0] = (byte)(size / 65536); ret[1] = (byte)((size % 65536) / 256); ret[2] = (byte)(size % 256); ret[3] = (byte)(cert_bytes.Length / 65536); ret[4] = (byte)((cert_bytes.Length % 65536) / 256); ret[5] = (byte)(cert_bytes.Length % 256); Array.Copy(cert_bytes, 0, ret, 6, cert_bytes.Length); return ret; } protected SslHandshakeStatus ProcessFinished(HandshakeMessage message) { if (m_State != HandshakeType.ChangeCipherSpec) throw new SslException(AlertDescription.UnexpectedMessage, "Finished message must be preceded by a ChangeCipherSpec message."); // check hash received from client this.VerifyFinishedMessage(message.fragment); m_IsNegotiating = false; ClearHandshakeStructures(); return new SslHandshakeStatus(SslStatus.OK, null); } protected SslHandshakeStatus ProcessHelloRequest(HandshakeMessage message) { if (IsNegotiating()) return new SslHandshakeStatus(SslStatus.OK, null); // ignore hello request return new SslHandshakeStatus(SslStatus.ContinueNeeded, GetClientHello()); } protected override SslHandshakeStatus ProcessChangeCipherSpec(RecordMessage message) { if (message.length != 1 || message.fragment[0] != 1) throw new SslException(AlertDescription.IllegalParameter, "The ChangeCipherSpec message was invalid."); if (m_State == HandshakeType.ServerHelloDone) { m_RecordLayer.ChangeRemoteState(null, m_CipherSuite.Decryptor, m_CipherSuite.RemoteHasher); return new SslHandshakeStatus(SslStatus.MessageIncomplete, null); // needs a finished message } else { throw new SslException(AlertDescription.UnexpectedMessage, "ChangeCipherSpec message must be preceded by a ServerHelloDone message."); } } protected override byte[] GetRenegotiateBytes() { if (IsNegotiating()) return null; return GetClientHello(); } public override SslHandshakeStatus ProcessSsl2Hello(byte[] hello) { throw new SslException(AlertDescription.InternalError, "This is a client socket; it cannot accept a client hello messages"); } } }