/* * Secucore * * Copyright (C) 2023 Trevor Hall * All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. * */ using SecuCore.TLS.Exceptions; using System; using System.Buffers; using System.Text; using static SecuCore.TLS.CipherSuites; namespace SecuCore.TLS { public static class TLSData { public static ushort[] preferredCipherSuites = new ushort[] { (ushort)CipherSuiteValue.TLS_RSA_WITH_AES_128_CBC_SHA, (ushort)CipherSuiteValue.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, (ushort)CipherSuiteValue.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA }; public static byte[] label_extmastersecret = Encoding.ASCII.GetBytes("extended master secret"); public static byte[] label_mastersecret = Encoding.ASCII.GetBytes("master secret"); public static byte[] label_keyexpansion = Encoding.ASCII.GetBytes("key expansion"); public static byte[] label_clientfinished = Encoding.ASCII.GetBytes("client finished"); public static byte[] label_serverfinished = Encoding.ASCII.GetBytes("server finished"); public const int RecordLengthLimit = 16383; public const int HandshakeLengthLimit = RecordLengthLimit - 4; public const int CertificateLengthLimit = RecordLengthLimit - 3 - 4; private static byte[] TLS_1_0 = new byte[] { 0x03, 0x01 }; private static byte[] TLS_1_1 = new byte[] { 0x03, 0x02 }; private static byte[] TLS_1_2 = new byte[] { 0x03, 0x03 }; public static byte[] GetVersionBytes(TLSVersion v) { byte[] versionbytes = null; if (v == TLSVersion.TLS10) versionbytes = TLS_1_0; else if (v == TLSVersion.TLS11) versionbytes = TLS_1_1; else if (v == TLSVersion.TLS12) versionbytes = TLS_1_2; return versionbytes; } public static byte[] GetUnixTime() { DateTime now = DateTime.Now.ToUniversalTime(); TimeSpan time = now.Subtract(new DateTime(1970, 1, 1)); byte[] ret = BitConverter.GetBytes((uint)time.TotalSeconds); if (BitConverter.IsLittleEndian) Array.Reverse(ret); return ret; } public static byte[] GetCipherSuite(ushort[] ciphers) { Array.Sort(ciphers); byte[] header = new byte[2]; uint cl2 = (uint)(ciphers.Length * 2); byte[] b = new byte[2 + cl2]; b[0] = (byte)((cl2 >> 8) & 0xff); b[1] = (byte)(cl2 & 0xff); int idx = 2; for (int i = 0; i < ciphers.Length; i++) { uint cipher = ciphers[i]; b[idx++] = (byte)((cipher >> 8) & 0xff); b[idx++] = (byte)((cipher) & 0xff); } return b; } public static byte[] GetExtensions(string externalServerName, bool useExtendedMasterSecret, string curvename) { byte[] bServerName = GetExtensionsServerName(externalServerName); byte[] bSupportedGroups = GetExtensionsSupportedGroups(curvename); byte[] bECPointFormats = GetExtensionsECPointFormats(); byte[] bKeySignatureAlgorithms = new byte[] { 0x00, 0x0d, 0x00, 0x04, 0x00, 0x02, 0x02, 0x01 }; byte[] bECSessionTicket = new byte[] { 0x00, 0x23, 0x00, 0x00 }; byte[] bECExtendedMasterSec = new byte[] { 0x00, 0x17, 0x00, 0x00 }; byte[] bRenegotiationInfo = GetExtensionsRenegotiationInfo(); byte[] last = bRenegotiationInfo; if (useExtendedMasterSecret) last = Tools.JoinAll(bECExtendedMasterSec, last); int totalLen = bServerName.Length + bSupportedGroups.Length + bECPointFormats.Length + bECSessionTicket.Length + bKeySignatureAlgorithms.Length + last.Length; byte[] header = new byte[2]; header[0] = (byte)((totalLen >> 8) & 0xff); header[1] = (byte)(totalLen & 0xff); byte[] output = Tools.JoinAll(header, bServerName, bSupportedGroups, bECPointFormats, bKeySignatureAlgorithms, bECSessionTicket, last); return output; } private static byte[] GetExtensionsServerName(string externalServerName) { int hnlen = externalServerName.Length; int lelen = hnlen + 3; int flelen = lelen + 2; byte[] header = new byte[] { 0x00, 0x00, (byte)((flelen >> 8) & 0xff), (byte)((flelen) & 0xff), (byte)((lelen >> 8) & 0xff), (byte)((lelen) & 0xff), 0x00, (byte)((hnlen >> 8) & 0xff), (byte)((hnlen) & 0xff) }; byte[] esnb = Encoding.ASCII.GetBytes(externalServerName); return Tools.JoinAll(header, esnb); } public static byte[] GetExtensionsSupportedGroups(string curvename) { if (curvename == "x25519") { return new byte[] { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d // x25519 }; } return new byte[] { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17 // secp256r1 }; } public static byte[] GetExtensionsECPointFormats() { return new byte[] { 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00 }; } public static byte[] GetExtensionsRenegotiationInfo() { return new byte[] { 0xff, 0x01, 0x00, 0x01, 0x00 }; } } public enum TLSVersion { TLS10, TLS11, TLS12 } public struct ServerHello { public TLSVersion tlsVersion; public byte[] serverRandom; public ushort selectedCipher; public static ServerHello Parse(HandshakeRecord hsr) { ServerHello svh = new ServerHello(); if (hsr.Data[0] != 0x03) throw new TLSProtocolException("Unknown server TLS version"); if (hsr.Data[1] > 0x03 || hsr.Data[1] == 0x00) throw new TLSProtocolException("Unknown server TLS version"); byte[] srand = new byte[32]; Buffer.BlockCopy(hsr.Data, 2, srand, 0, 32); svh.serverRandom = srand; int serversessionidlen = hsr.Data[34]; int hpos = 35; if (serversessionidlen > 0) hpos += serversessionidlen; int selcuite = 0; selcuite |= hsr.Data[hpos++] << 8; selcuite |= hsr.Data[hpos++]; svh.selectedCipher = (ushort)selcuite; // ignore everything else return svh; } } public struct KeyExchangeInfo { public ushort namedCurve; public ushort keySize; public byte[] keyExchangeData; public byte[] edchParams; public byte[] curveInfo; public byte[] publicKey; public byte[] signedMessage; public byte[] signature; public byte[] signatureAlgorithm; public static KeyExchangeInfo Parse(byte[] keyExchangeDat, TLSVersion version) { KeyExchangeInfo kei = new KeyExchangeInfo(); if (keyExchangeDat[0] != 0x03) throw new TLSProtocolException("Curve is not 'named_curve'"); int namedCurveIdent = 0; namedCurveIdent |= keyExchangeDat[1] << 8; namedCurveIdent |= keyExchangeDat[2]; kei.namedCurve = (ushort)namedCurveIdent; kei.curveInfo = new byte[] { keyExchangeDat[0], keyExchangeDat[1], keyExchangeDat[2] }; byte pubkeylen = keyExchangeDat[3]; kei.keySize = pubkeylen; byte[] pubkey = new byte[pubkeylen]; Buffer.BlockCopy(keyExchangeDat, 4, pubkey, 0, pubkeylen); int ecdhParamsLen = 4 + pubkeylen; byte[] ecdhParams = new byte[ecdhParamsLen]; Buffer.BlockCopy(keyExchangeDat, 0, ecdhParams, 0, ecdhParamsLen); kei.edchParams = ecdhParams; kei.publicKey = pubkey; int idx = 4 + pubkeylen; if (version == TLSVersion.TLS12) { kei.signatureAlgorithm = new byte[2]; kei.signatureAlgorithm[0] = keyExchangeDat[idx++]; kei.signatureAlgorithm[1] = keyExchangeDat[idx++]; } // sig starts here byte[] message = new byte[idx]; Buffer.BlockCopy(keyExchangeDat, 0, message, 0, idx); kei.signedMessage = message; int sigLen = 0; sigLen |= keyExchangeDat[idx++] << 8; sigLen |= keyExchangeDat[idx++]; if (sigLen > 8192) throw new TLSProtocolException("Signature exceeded size limit"); byte[] sigdat = new byte[sigLen]; Buffer.BlockCopy(keyExchangeDat, idx, sigdat, 0, sigLen); kei.signature = sigdat; return kei; } } public struct HandshakeRecord { public const byte HS_Client_Hello = 0x01; public const byte HS_Server_Hello = 0x02; public const byte HS_NewSessionTicket = 0x04; public const byte HS_Client_KeyExchange = 0x10; public const byte HS_Server_KeyExchange = 0x0c; public const byte HS_Finished = 0x14; public const byte HS_Server_HelloDone = 0x0e; public const byte HS_Certificate = 0x0b; public byte Type; public UInt32 Length; public byte[] Data; public byte[] Serialize() { int outlen = 4 + (int)Length; byte[] output = new byte[outlen]; output[0] = Type; output[1] = (byte)((Length >> 16) & 0xff); output[2] = (byte)((Length >> 8) & 0xff); output[3] = (byte)((Length) & 0xff); if (Length > 0) Buffer.BlockCopy(Data, 0, output, 4, Data.Length); return output; } public static HandshakeRecord Parse(byte[] header, byte[] fragment) { HandshakeRecord hsr = new HandshakeRecord(); hsr.Type = header[0]; uint len = 0; len |= (uint)(header[1] << 16); len |= (uint)(header[2] << 8); len |= (uint)(header[3]); hsr.Length = len; if (len > 0) { hsr.Data = fragment; } return hsr; } public static HandshakeRecord Parse(byte[] data) { if (data == null || data.Length == 0) throw new TLSHandshakeException("No data to parse"); if (data.Length < 4) throw new TLSHandshakeException("No Header"); HandshakeRecord hsr = new HandshakeRecord(); hsr.Type = data[0]; uint len = 0; len |= (uint)(data[1] << 16); len |= (uint)(data[2] << 8); len |= (uint)(data[3]); hsr.Length = len; if (len > 0) { if (len > TLSData.HandshakeLengthLimit) throw new TLSProtocolException("Handshake size exceeds limits"); byte[] hdat = new byte[len]; Buffer.BlockCopy(data, 4, hdat, 0, (int)len); hsr.Data = hdat; } return hsr; } } public struct TLSRecord { public const byte TLSR_ChangeSipherSpec = 0x14; public const byte TLSR_Alert = 0x15; public const byte TLSR_Handshake = 0x16; public const byte TLSR_ApplicationData = 0x17; public byte Type; public ushort Version; public ushort Length; public byte[] Data; public TLSRecord(byte[] ApplicationData, TLSVersion v) { Type = TLSR_ApplicationData; byte major = 0x03; byte minor = 0x01; if (v == TLSVersion.TLS10) minor = 0x01; else if (v == TLSVersion.TLS11) minor = 0x02; else if (v == TLSVersion.TLS12) minor = 0x03; Version = (ushort)((major << 8) | minor); Length = (ushort)ApplicationData.Length; Data = ApplicationData; } public TLSRecord(HandshakeRecord hsr, TLSVersion v) { byte major = 0x03; byte minor = 0x01; if (v == TLSVersion.TLS10) minor = 0x01; else if (v == TLSVersion.TLS11) minor = 0x02; else if (v == TLSVersion.TLS12) minor = 0x03; byte[] hsd = hsr.Serialize(); Type = 0x16; Version = (ushort)((major << 8) | minor); Length = (ushort)(hsd.Length); Data = hsd; } public byte[] Serialize() { if (Data == null || Data.Length == 0) { byte[] header = new byte[5]; header[0] = Type; header[1] = (byte)((Version >> 8) & 0xff); header[2] = (byte)((Version & 0xff)); header[3] = (byte)((Length >> 8) & 0xff); header[4] = (byte)((Length & 0xff)); return header; } byte[] outbuf = new byte[Data.Length + 5]; outbuf[0] = Type; outbuf[1] = (byte)((Version >> 8) & 0xff); outbuf[2] = (byte)((Version & 0xff)); outbuf[3] = (byte)((Length >> 8) & 0xff); outbuf[4] = (byte)((Length & 0xff)); Buffer.BlockCopy(Data, 0, outbuf, 5, Data.Length); return outbuf; } } }