HandshakeType SendClientHello(ref int offset) { _pendingConnState = new ConnectionState(); _handshakeData = new HandshakeData { HandshakeHash1 = Hasher.Create(TLSHashAlgorithm.SHA256), HandshakeHash2 = Hasher.Create(TLSHashAlgorithm.SHA256), HandshakeHash1_384 = Hasher.Create(TLSHashAlgorithm.SHA384), HandshakeHash2_384 = Hasher.Create(TLSHashAlgorithm.SHA384), HandshakeHash1_MD5SHA1 = Hasher.Create(TLSHashAlgorithm.MD5SHA1), HandshakeHash2_MD5SHA1 = Hasher.Create(TLSHashAlgorithm.MD5SHA1), CertificateVerifyHash_MD5 = Hasher.Create(TLSHashAlgorithm.MD5), CertificateVerifyHash_SHA1 = Hasher.Create(TLSHashAlgorithm.SHA1) }; // Highest version supported offset += Utils.WriteUInt16(_buf, offset, (ushort)HighestTlsVersionSupported); // Client random var timestamp = (uint)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; _pendingConnState.ClientRandom = new byte[32]; _rng.GetBytes(_pendingConnState.ClientRandom); Utils.WriteUInt32(_pendingConnState.ClientRandom, 0, timestamp); Buffer.BlockCopy(_pendingConnState.ClientRandom, 0, _buf, offset, 32); offset += 32; // No session id _buf[offset++] = 0; // Cipher suites var supportedCipherSuites = CipherSuiteInfo.Supported; /* if (HighestTlsVersionSupported != TlsVersion.TLSv1_2) supportedCipherSuites = supportedCipherSuites.Where(cs => cs.IsAllowedBefore1_2).ToArray(); */ offset += Utils.WriteUInt16(_buf, offset, (ushort)(supportedCipherSuites.Length * sizeof(ushort))); foreach (var suite in supportedCipherSuites) { offset += Utils.WriteUInt16(_buf, offset, (ushort)suite.Id); } // Compression methods _buf[offset++] = 1; // Length _buf[offset++] = 0; // "null" compression method // Extensions length, fill in later var extensionLengthOffset = offset; offset += 2; // Renegotiation extension offset += Utils.WriteUInt16(_buf, offset, (ushort)ExtensionType.RenegotiationInfo); if (_connState.SecureRenegotiation) { // Extension length offset += Utils.WriteUInt16(_buf, offset, 13); // Renegotiated connection length _buf[offset++] = 12; // Renegotiated connection data Buffer.BlockCopy(_connState.ClientVerifyData, 0, _buf, offset, 12); offset += 12; } else { // Extension length offset += Utils.WriteUInt16(_buf, offset, 1); // Renegotiated connection length _buf[offset++] = 0; } // SNI extension if (_hostName != null) { // TODO: IDN Unicode -> Punycode // NOTE: IP addresses should not use SNI extension, per specification. System.Net.IPAddress ip; if (!System.Net.IPAddress.TryParse(_hostName, out ip)) { offset += Utils.WriteUInt16(_buf, offset, (ushort)ExtensionType.ServerName); var byteLen = Encoding.ASCII.GetBytes(_hostName, 0, _hostName.Length, _buf, offset + 7); offset += Utils.WriteUInt16(_buf, offset, (ushort)(5 + byteLen)); offset += Utils.WriteUInt16(_buf, offset, (ushort)(3 + byteLen)); _buf[offset++] = 0; // host_name offset += Utils.WriteUInt16(_buf, offset, (ushort)byteLen); offset += byteLen; } } if (HighestTlsVersionSupported == TlsVersion.TLSv1_2) { // Signature algorithms extension. At least IIS 7.5 needs this or it immediately resets the connection. // Used to specify what kind of server certificate hash/signature algorithms we can use to verify it. offset += Utils.WriteUInt16(_buf, offset, (ushort)ExtensionType.SignatureAlgorithms); offset += Utils.WriteUInt16(_buf, offset, 20); offset += Utils.WriteUInt16(_buf, offset, 18); _buf[offset++] = (byte)TLSHashAlgorithm.SHA1; _buf[offset++] = (byte)SignatureAlgorithm.ECDSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA256; _buf[offset++] = (byte)SignatureAlgorithm.ECDSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA384; _buf[offset++] = (byte)SignatureAlgorithm.ECDSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA512; _buf[offset++] = (byte)SignatureAlgorithm.ECDSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA1; _buf[offset++] = (byte)SignatureAlgorithm.RSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA256; _buf[offset++] = (byte)SignatureAlgorithm.RSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA384; _buf[offset++] = (byte)SignatureAlgorithm.RSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA512; _buf[offset++] = (byte)SignatureAlgorithm.RSA; _buf[offset++] = (byte)TLSHashAlgorithm.SHA1; _buf[offset++] = (byte)SignatureAlgorithm.DSA; } if (supportedCipherSuites.Any(s => s.KeyExchange == KeyExchange.ECDHE_RSA || s.KeyExchange == KeyExchange.ECDHE_ECDSA)) { // Supported Elliptic Curves Extension offset += Utils.WriteUInt16(_buf, offset, (ushort)ExtensionType.SupportedEllipticCurves); offset += Utils.WriteUInt16(_buf, offset, 8); offset += Utils.WriteUInt16(_buf, offset, 6); offset += Utils.WriteUInt16(_buf, offset, (ushort)NamedCurve.secp256r1); offset += Utils.WriteUInt16(_buf, offset, (ushort)NamedCurve.secp384r1); offset += Utils.WriteUInt16(_buf, offset, (ushort)NamedCurve.secp521r1); // Supported Point Formats Extension offset += Utils.WriteUInt16(_buf, offset, (ushort)ExtensionType.SupportedPointFormats); offset += Utils.WriteUInt16(_buf, offset, 2); _buf[offset++] = 1; // Length _buf[offset++] = 0; // Uncompressed } Utils.WriteUInt16(_buf, extensionLengthOffset, (ushort)(offset - (extensionLengthOffset + 2))); return HandshakeType.ClientHello; }
void ParseChangeCipherSpec() { if (_plaintextLen != 1 || _buf[_plaintextStart] != 1) SendAlertFatal(AlertDescription.IllegalParameter); if (_pendingConnState != _connState) SendAlertFatal(AlertDescription.UnexpectedMessage); // We don't accept buffered handshake messages to be completed after Change Cipher Spec, // since they would not be encrypted correctly if (_handshakeMessagesBuffer.HasBufferedData) SendAlertFatal(AlertDescription.UnexpectedMessage); _readConnState.Dispose(); _readConnState = _connState; _pendingConnState = null; }
// Here we send all client messages in response to server hello int GenerateHandshakeResponse() { var offset = 0; var ivLen = _connState.IvLen; if (_handshakeData.CertificateTypes != null) // Certificate request has been sent by the server { SendHandshakeMessage(SendClientCertificate, ref offset, ivLen); } switch (_pendingConnState.CipherSuite.KeyExchange) { case KeyExchange.DHE_RSA: case KeyExchange.DHE_DSS: SendHandshakeMessage(SendClientKeyExchangeDhe, ref offset, ivLen); break; case KeyExchange.ECDHE_RSA: case KeyExchange.ECDHE_ECDSA: SendHandshakeMessage(SendClientKeyExchangeEcdhe, ref offset, ivLen); break; case KeyExchange.ECDH_ECDSA: case KeyExchange.ECDH_RSA: SendHandshakeMessage(SendClientKeyExchangeEcdh, ref offset, ivLen); break; case KeyExchange.RSA: SendHandshakeMessage(SendClientKeyExchangeRsa, ref offset, ivLen); break; default: throw new InvalidOperationException(); } if (_handshakeData.CertificateTypes != null && _handshakeData.SelectedClientCertificate != null) { SendHandshakeMessage(SendCertificateVerify, ref offset, ivLen); } var cipherSpecStart = offset; SendChangeCipherSpec(ref offset, ivLen); offset = Encrypt(cipherSpecStart, 1); // Key generation from Master Secret var mode = _pendingConnState.CipherSuite.AesMode; var isCbc = mode == AesMode.CBC; var isGcm = mode == AesMode.GCM; var concRandom = new byte[_pendingConnState.ServerRandom.Length + _pendingConnState.ClientRandom.Length]; Buffer.BlockCopy(_pendingConnState.ServerRandom, 0, concRandom, 0, _pendingConnState.ServerRandom.Length); Buffer.BlockCopy(_pendingConnState.ClientRandom, 0, concRandom, _pendingConnState.ServerRandom.Length, _pendingConnState.ClientRandom.Length); var macLen = isCbc ? _pendingConnState.CipherSuite.MACLen / 8 : 0; var aesKeyLen = _pendingConnState.CipherSuite.AesKeyLen / 8; var IVLen = isGcm ? 4 : _pendingConnState.TlsVersion != TlsVersion.TLSv1_0 ? 0 : _pendingConnState.BlockLen; var keyBlock = Utils.PRF(_pendingConnState.PRFAlgorithm, _pendingConnState.MasterSecret, "key expansion", concRandom, macLen * 2 + aesKeyLen * 2 + IVLen * 2); byte[] writeMac = new byte[macLen], readMac = new byte[macLen], writeKey = new byte[aesKeyLen], readKey = new byte[aesKeyLen]; Buffer.BlockCopy(keyBlock, 0, writeMac, 0, macLen); Buffer.BlockCopy(keyBlock, macLen, readMac, 0, macLen); Buffer.BlockCopy(keyBlock, macLen * 2, writeKey, 0, aesKeyLen); Buffer.BlockCopy(keyBlock, macLen * 2 + aesKeyLen, readKey, 0, aesKeyLen); if (isCbc) { _pendingConnState.WriteMac = _pendingConnState.CipherSuite.CreateHMAC(writeMac); _pendingConnState.ReadMac = _pendingConnState.CipherSuite.CreateHMAC(readMac); } if (IVLen != 0) { // For GCM we make it bigger to later fill in sequence numbers var writeIv = new byte[isGcm ? 16 : IVLen]; var readIv = new byte[isGcm ? 16 : IVLen]; Buffer.BlockCopy(keyBlock, macLen * 2 + aesKeyLen * 2, writeIv, 0, IVLen); Buffer.BlockCopy(keyBlock, macLen * 2 + aesKeyLen * 2 + IVLen, readIv, 0, IVLen); _pendingConnState.WriteIv = writeIv; _pendingConnState.ReadIv = readIv; } else { _pendingConnState.ReadIv = _pendingConnState.WriteIv = new byte[_pendingConnState.BlockLen]; } _pendingConnState.WriteAes = Aes.Create(); _pendingConnState.WriteAes.Key = writeKey; _pendingConnState.WriteAes.Mode = isCbc ? CipherMode.CBC : CipherMode.ECB; _pendingConnState.WriteAes.Padding = PaddingMode.None; _pendingConnState.ReadAes = Aes.Create(); _pendingConnState.ReadAes.Key = readKey; _pendingConnState.ReadAes.Mode = isCbc ? CipherMode.CBC : CipherMode.ECB; _pendingConnState.ReadAes.Padding = PaddingMode.None; // int tmpOffset = macLen * 2 + aesKeyLen * 2; if (isGcm) { _pendingConnState.WriteAesECB = _pendingConnState.WriteAes.CreateEncryptor(writeKey, null); _pendingConnState.ReadAesECB = _pendingConnState.ReadAes.CreateEncryptor(readKey, null); _pendingConnState.WriteGCMTable = GaloisCounterMode.GetH(_pendingConnState.WriteAesECB); _pendingConnState.ReadGCMTable = GaloisCounterMode.GetH(_pendingConnState.ReadAesECB); if (_temp512 == null) _temp512 = new byte[512]; } Utils.ClearArray(writeMac); Utils.ClearArray(readMac); Utils.ClearArray(writeKey); Utils.ClearArray(readKey); ivLen = _pendingConnState.IvLen; _connState = _pendingConnState; SendHandshakeMessage(SendFinished, ref offset, ivLen); //_handshakeData.HandshakeHash2.Final(); // _buf is now ready to be written to the base stream, from pos 0 to offset return offset; }
/// <summary> /// Creates a new TlsClientStream with the given underlying stream. /// The handshake must be manually initiated with the method PerformInitialHandshake. /// </summary> /// <param name="baseStream">Base stream</param> public TlsClientStream(Stream baseStream) { _connState = new ConnectionState() { TlsVersion = TlsVersion.TLSv1_0 }; _readConnState = _connState; _baseStream = baseStream; }