SecuCore/TLS/TLS.cs
2026-03-03 23:53:04 -05:00

360 lines
13 KiB
C#

/*
* 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;
}
}
}