Initial commit

This commit is contained in:
Trevor Hall 2026-03-03 23:53:04 -05:00
commit 5584446828
37 changed files with 7962 additions and 0 deletions

View file

@ -0,0 +1,262 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using Microsoft.VisualStudio.Threading;
using SecuCore.Shared;
using SecuCore.TLS.Exceptions;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SecuCore.TLS
{
class ApplicationDataController : IDataController
{
TLSRecordLayer _recordLayer;
public bool isFaulted = false;
public string lastError = "";
public Queue<DataDispatch> outgoing = new Queue<DataDispatch>();
public Queue<DataRequest> requests = new Queue<DataRequest>();
byte[] localBuffer;
public ApplicationDataController(TLSRecordLayer recordLayer, int recieveBufferSize)
{
localBuffer = new byte[recieveBufferSize];
_recordLayer = recordLayer;
_ = Start();
}
private bool shutdownRequested = false;
private AsyncAutoResetEvent requestReset = new AsyncAutoResetEvent();
private AsyncAutoResetEvent flushReset = new AsyncAutoResetEvent();
private AsyncAutoResetEvent nextEmpty = new AsyncAutoResetEvent();
private async Task Start()
{
_ = WriteLoop();
_ = ReadLoop();
}
private bool GetNextDataDispatch(out DataDispatch next)
{
if (outgoing == null || shutdownRequested)
{
next = null;
return false;
}
return outgoing.TryDequeue(out next);
}
private bool GetNextDataRequest(out DataRequest next)
{
if (requests == null || shutdownRequested)
{
next = null;
return false;
}
return requests.TryDequeue(out next);
}
private async Task WriteLoop()
{
while (!shutdownRequested)
{
await flushReset.WaitAsync().ConfigureAwait(false);
while (GetNextDataDispatch(out DataDispatch next))
{
if (!await DispatchAsync(next).ConfigureAwait(false))
{
Shutdown(true);
return;
}
}
}
}
private async Task ReadLoop()
{
while (!shutdownRequested)
{
await requestReset.WaitAsync().ConfigureAwait(false);
while (GetNextDataRequest(out DataRequest next))
{
if (!await HandleRequestAsync(next).ConfigureAwait(false))
{
if (next.tcs != null)
next.tcs.TrySetException(new Exception("Network failure"));
Shutdown(true);
return;
}
}
}
}
bool shutdownCalled = false;
public void Shutdown(bool faulted)
{
if (shutdownCalled)
return;
shutdownCalled = true;
shutdownRequested = true;
if (faulted)
{
this.isFaulted = true;
// clear buffers and inform failure
while (outgoing.TryDequeue(out DataDispatch next))
{
if (next.tcs != null)
next.tcs.TrySetException(new Exception("Application Layer failure: " + this.lastError));
}
while (requests.TryDequeue(out DataRequest next))
{
if (next.tcs != null)
next.tcs.TrySetException(new Exception("Application Layer failure: " + this.lastError));
}
}
else
{
while (outgoing.TryDequeue(out DataDispatch next))
{
if (next.tcs != null)
next.tcs.TrySetCanceled();
}
while (requests.TryDequeue(out DataRequest next))
{
if (next.tcs != null)
next.tcs.TrySetCanceled();
}
}
flushReset.Set();
nextEmpty.Set();
requestReset.Set();
_recordLayer = null;
outgoing = null;
requests = null;
localBuffer = null;
}
private async ValueTask<bool> DispatchAsync(DataDispatch data)
{
if (shutdownRequested)
return false;
try
{
await _recordLayer.SendApplicationDataAsync(data.data).ConfigureAwait(false);
if (data.tcs != null)
data.tcs.TrySetResult();
return true;
}
catch (Exception e)
{
lastError = e.Message;
}
return false;
}
private async ValueTask<bool> HandleRequestAsync(DataRequest request)
{
if (shutdownRequested)
return false;
if (request.length == -1)
{
// any amount of data
try
{
int len = await _recordLayer.ReadApplicationDataAsync(localBuffer, 0, localBuffer.Length).ConfigureAwait(false);
if (len > 0)
{
byte[] recieved = new byte[len];
Buffer.BlockCopy(localBuffer, 0, recieved, 0, len);
if (!request.tcs.TrySetResult(recieved))
{
lastError = "failed to set result to datarequest";
return false;
}
return true;
}
else
{
lastError = "Failed to read application data";
if (!request.tcs.TrySetException(new Exception("no data")))
{
}
return false;
}
}
catch (Exception)
{
if (!request.tcs.TrySetException(new Exception("no data")))
{
}
return false;
}
}
else
{
byte[] buffer = new byte[request.length];
int pos = 0;
int remain = 0;
while (remain > 0)
{
int len = await _recordLayer.ReadApplicationDataAsync(buffer, pos, remain).ConfigureAwait(false);
if (len > 0)
{
pos += len;
remain -= len;
}
else
{
request.tcs.TrySetException(new Exception("no data"));
lastError = "Failed to read application data";
return false;
}
}
// success
if (!request.tcs.TrySetResult(buffer))
{
lastError = "failed to set result to datarequest";
return false;
}
return true;
}
}
public void RequestData(DataRequest request)
{
if (shutdownRequested)
throw new TLSDataException("Shutdown has been requested");
requests.Enqueue(request);
requestReset.Set();
}
public void QueueData(DataDispatch dispatch)
{
if (shutdownRequested)
throw new TLSDataException("Shutdown has been requested");
outgoing.Enqueue(dispatch);
}
public void FlushData()
{
if (shutdownRequested)
throw new TLSDataException("Shutdown has been requested");
flushReset.Set();
}
}
}

375
TLS/CipherSuites.cs Normal file
View file

@ -0,0 +1,375 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using SecuCore.Security;
namespace SecuCore.TLS
{
public static class CipherSuites
{
public enum ConnectionEnd
{
SERVER,
CLIENT
}
public enum PRFAlgorithm
{
LEGACY,
TLS_PRF_SHA256,
TLS_PRF_SHA384
}
public enum KeyExchangeAlgorithm
{
NULL,
RSA,
RSA_EXPORT,
DHE_DSS,
DHE_DSS_EXPORT,
DHE_RSA,
DHE_RSA_EXPORT,
DH_DSS,
DH_DSS_EXPORT,
DH_RSA,
DH_RSA_EXPORT,
DH_anon,
DH_anon_EXPORT,
//RFC 4279
PSK,
DHE_PSK,
RSA_PSK,
//RFC 4429
ECDH_ECDSA,
ECDHE_ECDSA,
ECDH_RSA,
ECDHE_RSA,
ECDH_anon,
//RFC 5054
SRP,
SRP_DSS,
SRP_RSA,
//RFC 5489
ECDHE_PSK
}
public enum BulkCipherAlgorithm
{
NULL,
RC4,
RC2,
DES,
_3DES,
DES40,
AES,
IDEA
}
public enum CipherType
{
STREAM,
BLOCK,
AEAD
}
public enum MACAlgorithm
{
NULL,
MD5,
SHA,
HMAC_MD5 = MD5,
HMAC_SHA1 = SHA,
HMAC_SHA256,
HMAC_SHA384,
HMAC_SHA512
}
public enum CompressionMethod
{
NULL,
DEFLATE
}
public class SecurityParameters
{
public ConnectionEnd entity;
public BulkCipherAlgorithm bulk_cipher_algorithm;
public CipherType cipher_type;
public byte key_size;
public CompressionMethod compression_algorithm;
public byte[] master_secret;
public byte[] client_random;
public byte[] server_random;
}
public enum BulkAlgorithmType
{
Stream,
Block,
AEAD
}
public class TLSEncryptionProvider
{
public TLSCipherSuite tcs;
public BulkAlgorithmType bulkAlgorithmType;
public int AuthenticationTagSize;
public int HashSize;
public int IVSize;
public int BlockSize;
public KeyedHashAlgorithm LocalHasher;
public KeyedHashAlgorithm RemoteHasher;
public ICryptoTransform Encryptor;
public ICryptoTransform Decryptor;
private void DisposeIfExists(IDisposable d)
{
if (d != null) d.Dispose();
}
public void Dispose()
{
DisposeIfExists(Decryptor);
DisposeIfExists(Encryptor);
DisposeIfExists(LocalHasher);
DisposeIfExists(RemoteHasher);
}
}
public class TLSCipherImplementation
{
public Func<SymmetricAlgorithm> getBulkFunc;
public Func<HashAlgorithm> getHashFunc;
public Func<byte[], HMAC> createHMACFunc;
public Func<byte[], byte[], X509Cert, bool> verifyHashFunc;
public TLSCipherImplementation(Func<SymmetricAlgorithm> _getBulkFunction, Func<HashAlgorithm> _getHashFunction, Func<byte[], HMAC> _createHMACFunc, Func<byte[], byte[], X509Cert, bool> _verifyHashFunc)
{
this.getBulkFunc = _getBulkFunction;
this.getHashFunc = _getHashFunction;
this.createHMACFunc = _createHMACFunc;
this.verifyHashFunc = _verifyHashFunc;
}
public SymmetricAlgorithm GetBulker() => getBulkFunc.Invoke();
public HashAlgorithm GetHasher() => getHashFunc.Invoke();
public HMAC CreateHMAC(byte[] key) => createHMACFunc.Invoke(key);
public bool VerifyHash(byte[] hash, byte[] sig, X509Cert cert) => verifyHashFunc.Invoke(hash, sig, cert);
}
public struct TLSCipherParameters
{
public int CipherSuite;
public string BulkCipherAlgorithmName;
public int BlockSize;
public int HashSize;
public int BulkKeySize;
public int BulkIVSize;
public CipherMode cipherMode;
public KeyExchangeAlgorithm keyExchangeAlgorithm;
}
public class TLSCipherSuite
{
public TLSCipherParameters tlsparams;
TLSCipherImplementation implementation;
public byte[] clWriteKey;
public byte[] clWriteMAC;
public TLSCipherSuite(TLSCipherImplementation impl, TLSCipherParameters _tlsparams)
{
this.implementation = impl;
this.tlsparams = _tlsparams;
}
public SymmetricAlgorithm GetBulker()
{
SymmetricAlgorithm bulker = implementation.GetBulker();
bulker.Mode = tlsparams.cipherMode;
bulker.Padding = PaddingMode.None;
return bulker;
}
public HashAlgorithm GetHasher() => implementation.GetHasher();
public HMAC CreateHMAC(byte[] key) => implementation.CreateHMAC(key);
public bool VerifyHash(byte[] hash, byte[] sig, X509Cert cert) => implementation.VerifyHash(hash, sig, cert);
public TLSEncryptionProvider InitializeEncryption(KeyDerivation.KeyExpansionResult keyring)
{
clWriteKey = keyring.clientWriteKey;
clWriteMAC = keyring.clientWriteMac;
SymmetricAlgorithm localBulker = GetBulker();
localBulker.Key = keyring.clientWriteKey;
SymmetricAlgorithm remoteBulker = GetBulker();
remoteBulker.Key = keyring.serverWriteKey;
KeyedHashAlgorithm localHasher = new HMACSHA1(keyring.clientWriteMac);
KeyedHashAlgorithm remoteHasher = new HMACSHA1(keyring.serverWriteMac);
return new TLSEncryptionProvider
{
tcs = this,
AuthenticationTagSize = 0,
bulkAlgorithmType = BulkAlgorithmType.Block,
BlockSize = tlsparams.BlockSize,
IVSize = tlsparams.BulkIVSize,
HashSize = tlsparams.HashSize,
Encryptor = localBulker.CreateEncryptor(),
Decryptor = remoteBulker.CreateDecryptor(),
LocalHasher = localHasher,
RemoteHasher = remoteHasher
};
}
}
private static bool VerifyHashEDCSA(byte[] hash, byte[] sig, X509Cert publicCert)
{
using (ECDsa ecd = ECDsaCertificateExtensions.GetECDsaPublicKey(new X509Certificate2(publicCert.sourceData)))
{
bool valid = ecd.VerifyHash(hash, sig, DSASignatureFormat.Rfc3279DerSequence);
return valid;
}
}
private static bool VerifyHashRSA(byte[] hash, byte[] sig, X509Cert publicCert)
{
RSAPublicKey rpk = publicCert.GetRSAPublicKey();
byte[] decrypted = rpk.DecryptSignature(sig);
byte[] actualhash;
if (decrypted.Length == 36)
{
actualhash = decrypted;
}
else
{
actualhash = Asn1Tools.ParseHash(decrypted);
}
if (Asn1Tools.HashesEqual(hash, actualhash))
{
return true;
}
return false;
}
private static HashAlgorithm CreateSha1()
{
return SHA1.Create();
}
private static HashAlgorithm CreateSha256()
{
return SHA256.Create();
}
private static SymmetricAlgorithm CreateAes()
{
AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider
{
BlockSize = 128,
KeySize = 128,
Padding = PaddingMode.None,
Mode = CipherMode.CBC,
};
return aesProvider;
}
private static HMAC CreateSha1Hmac(byte[] key)
{
return new HMACSHA1(key);
}
private static HMAC CreateSha256Hmac(byte[] key)
{
return new HMACSHA256(key);
}
public static ushort[] GetSupportedCipherSuites()
{
CipherSuiteValue[] cipherSuiteValues = SupportedSuites.Keys.ToList().ToArray();
List<ushort> usv = new List<ushort>();
foreach (var cipherSuiteValue in cipherSuiteValues) usv.Add((ushort)cipherSuiteValue);
return usv.ToArray();
}
public static TLSCipherSuite InitializeCipherSuite(ushort ciphersuitevalue)
{
CipherSuiteValue key = (CipherSuiteValue)ciphersuitevalue;
return SupportedSuites[key].Invoke();
}
public static Dictionary<CipherSuiteValue, Func<TLSCipherSuite>> SupportedSuites = new Dictionary<CipherSuiteValue, Func<TLSCipherSuite>>
{
//TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
{
CipherSuiteValue.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
()=>{
return
new TLSCipherSuite(
new TLSCipherImplementation(CreateAes,CreateSha1,CreateSha256Hmac,VerifyHashRSA),
new TLSCipherParameters()
{
BlockSize = 16,
BulkCipherAlgorithmName = "AES",
BulkIVSize = 16,
BulkKeySize = 16,
HashSize = 20,
cipherMode = CipherMode.CBC,
CipherSuite = (int)CipherSuiteValue.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
keyExchangeAlgorithm = KeyExchangeAlgorithm.ECDHE_RSA
}
);
}
},
//TLS_RSA_WITH_AES_128_CBC_SHA
{
CipherSuiteValue.TLS_RSA_WITH_AES_128_CBC_SHA,
()=>{
return
new TLSCipherSuite(
new TLSCipherImplementation(CreateAes,CreateSha1,CreateSha256Hmac,VerifyHashRSA),
new TLSCipherParameters()
{
BlockSize = 16,
BulkCipherAlgorithmName = "AES",
BulkIVSize = 16,
BulkKeySize = 16,
HashSize = 20,
cipherMode = CipherMode.CBC,
CipherSuite = (int)CipherSuiteValue.TLS_RSA_WITH_AES_128_CBC_SHA,
keyExchangeAlgorithm = KeyExchangeAlgorithm.RSA
}
);
}
},
{
CipherSuiteValue.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
()=>{
return
new TLSCipherSuite(
new TLSCipherImplementation(CreateAes,CreateSha1,CreateSha256Hmac,VerifyHashEDCSA),
new TLSCipherParameters()
{
BlockSize = 16,
BulkCipherAlgorithmName = "AES",
BulkIVSize = 16,
BulkKeySize = 16,
HashSize = 20,
cipherMode = CipherMode.CBC,
CipherSuite = (int)CipherSuiteValue.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
keyExchangeAlgorithm = KeyExchangeAlgorithm.ECDHE_ECDSA
}
);
}
}
};
public enum CipherSuiteValue
{
TLS_RSA_WITH_AES_128_CBC_SHA = 47,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 49171,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 49161
}
}
}

648
TLS/Curves/Curve25519.cs Normal file
View file

@ -0,0 +1,648 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using System.Security.Cryptography;
namespace SecuCore.Curves
{
public class Curve25519
{
public const int KeySize = 32;
static readonly byte[] Order = { 237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 };
public static void ClampPrivateKeyInline(byte[] key)
{
if (key == null)
throw new ArgumentNullException("key");
if (key.Length != 32)
throw new ArgumentException(String.Format("key must be 32 bytes long (but was {0} bytes long)", key.Length));
key[31] &= 0x7F;
key[31] |= 0x40;
key[0] &= 0xF8;
}
public static byte[] ClampPrivateKey(byte[] rawKey)
{
if (rawKey == null)
throw new ArgumentNullException("rawKey");
if (rawKey.Length != 32)
throw new ArgumentException(String.Format("rawKey must be 32 bytes long (but was {0} bytes long)", rawKey.Length), "rawKey");
var res = new byte[32];
Array.Copy(rawKey, res, 32);
res[31] &= 0x7F;
res[31] |= 0x40;
res[0] &= 0xF8;
return res;
}
public static byte[] CreateRandomPrivateKey()
{
var privateKey = new byte[32];
privateKey = RandomNumberGenerator.GetBytes(32);
ClampPrivateKeyInline(privateKey);
return privateKey;
}
public static void KeyGenInline(byte[] publicKey, byte[] signingKey, byte[] privateKey)
{
if (publicKey == null)
throw new ArgumentNullException("publicKey");
if (publicKey.Length != 32)
throw new ArgumentException(String.Format("publicKey must be 32 bytes long (but was {0} bytes long)", publicKey.Length), "publicKey");
if (signingKey == null)
throw new ArgumentNullException("signingKey");
if (signingKey.Length != 32)
throw new ArgumentException(String.Format("signingKey must be 32 bytes long (but was {0} bytes long)", signingKey.Length), "signingKey");
if (privateKey == null)
throw new ArgumentNullException("privateKey");
if (privateKey.Length != 32)
throw new ArgumentException(String.Format("privateKey must be 32 bytes long (but was {0} bytes long)", privateKey.Length), "privateKey");
privateKey = RandomNumberGenerator.GetBytes(32);
ClampPrivateKeyInline(privateKey);
Core(publicKey, signingKey, privateKey, null);
}
public static byte[] GetPublicKey(byte[] privateKey)
{
var publicKey = new byte[32];
Core(publicKey, null, privateKey, null);
return publicKey;
}
public static byte[] GetSigningKey(byte[] privateKey)
{
var signingKey = new byte[32];
var publicKey = new byte[32];
Core(publicKey, signingKey, privateKey, null);
return signingKey;
}
public static byte[] GetSharedSecret(byte[] privateKey, byte[] peerPublicKey)
{
var sharedSecret = new byte[32];
Core(sharedSecret, null, privateKey, peerPublicKey);
return sharedSecret;
}
private sealed class Long10
{
public Long10() { }
public Long10(long n0, long n1, long n2, long n3, long n4, long n5, long n6, long n7, long n8, long n9)
{
N0 = n0;
N1 = n1;
N2 = n2;
N3 = n3;
N4 = n4;
N5 = n5;
N6 = n6;
N7 = n7;
N8 = n8;
N9 = n9;
}
public long N0, N1, N2, N3, N4, N5, N6, N7, N8, N9;
}
static void Copy32(byte[] source, byte[] destination) { Array.Copy(source, 0, destination, 0, 32); }
static int
MultiplyArraySmall(byte[] p, byte[] q, int m, byte[] x, int n, int z)
{
int v = 0;
for (int i = 0; i < n; ++i)
{
v += (q[i + m] & 0xFF) + z * (x[i] & 0xFF);
p[i + m] = (byte)v;
v >>= 8;
}
return v;
}
static void
MultiplyArray32(byte[] p, byte[] x, byte[] y, int t, int z)
{
const int n = 31;
int w = 0;
int i = 0;
for (; i < t; i++)
{
int zy = z * (y[i] & 0xFF);
w += MultiplyArraySmall(p, p, i, x, n, zy) + (p[i + n] & 0xFF) + zy * (x[n] & 0xFF);
p[i + n] = (byte)w;
w >>= 8;
}
p[i + n] = (byte)(w + (p[i + n] & 0xFF));
}
static void
DivMod(byte[] q, byte[] r, int n, byte[] d, int t)
{
int rn = 0;
int dt = ((d[t - 1] & 0xFF) << 8);
if (t > 1)
{
dt |= (d[t - 2] & 0xFF);
}
while (n-- >= t)
{
int z = (rn << 16) | ((r[n] & 0xFF) << 8);
if (n > 0)
{
z |= (r[n - 1] & 0xFF);
}
z /= dt;
rn += MultiplyArraySmall(r, r, n - t + 1, d, t, -z);
q[n - t + 1] = (byte)((z + rn) & 0xFF);
MultiplyArraySmall(r, r, n - t + 1, d, t, -rn);
rn = (r[n] & 0xFF);
r[n] = 0;
}
r[t - 1] = (byte)rn;
}
static int
GetNumSize(byte[] num, int maxSize)
{
for (int i = maxSize; i >= 0; i++)
{
if (num[i] == 0)
return i + 1;
}
return 0;
}
static byte[] Egcd32(byte[] x, byte[] y, byte[] a, byte[] b)
{
int bn = 32;
int i;
for (i = 0; i < 32; i++)
x[i] = y[i] = 0;
x[0] = 1;
int an = GetNumSize(a, 32);
if (an == 0)
return y;
var temp = new byte[32];
while (true)
{
int qn = bn - an + 1;
DivMod(temp, b, bn, a, an);
bn = GetNumSize(b, bn);
if (bn == 0)
return x;
MultiplyArray32(y, x, temp, qn, -1);
qn = an - bn + 1;
DivMod(temp, a, an, b, bn);
an = GetNumSize(a, an);
if (an == 0)
return y;
MultiplyArray32(x, y, temp, qn, -1);
}
}
private const int P25 = 33554431;
private const int P26 = 67108863;
static void
Unpack(Long10 x, byte[] m)
{
x.N0 = ((m[0] & 0xFF)) | ((m[1] & 0xFF)) << 8 | (m[2] & 0xFF) << 16 | ((m[3] & 0xFF) & 3) << 24;
x.N1 = ((m[3] & 0xFF) & ~3) >> 2 | (m[4] & 0xFF) << 6 | (m[5] & 0xFF) << 14 | ((m[6] & 0xFF) & 7) << 22;
x.N2 = ((m[6] & 0xFF) & ~7) >> 3 | (m[7] & 0xFF) << 5 | (m[8] & 0xFF) << 13 | ((m[9] & 0xFF) & 31) << 21;
x.N3 = ((m[9] & 0xFF) & ~31) >> 5 | (m[10] & 0xFF) << 3 | (m[11] & 0xFF) << 11 | ((m[12] & 0xFF) & 63) << 19;
x.N4 = ((m[12] & 0xFF) & ~63) >> 6 | (m[13] & 0xFF) << 2 | (m[14] & 0xFF) << 10 | (m[15] & 0xFF) << 18;
x.N5 = (m[16] & 0xFF) | (m[17] & 0xFF) << 8 | (m[18] & 0xFF) << 16 | ((m[19] & 0xFF) & 1) << 24;
x.N6 = ((m[19] & 0xFF) & ~1) >> 1 | (m[20] & 0xFF) << 7 | (m[21] & 0xFF) << 15 | ((m[22] & 0xFF) & 7) << 23;
x.N7 = ((m[22] & 0xFF) & ~7) >> 3 | (m[23] & 0xFF) << 5 | (m[24] & 0xFF) << 13 | ((m[25] & 0xFF) & 15) << 21;
x.N8 = ((m[25] & 0xFF) & ~15) >> 4 | (m[26] & 0xFF) << 4 | (m[27] & 0xFF) << 12 | ((m[28] & 0xFF) & 63) << 20;
x.N9 = ((m[28] & 0xFF) & ~63) >> 6 | (m[29] & 0xFF) << 2 | (m[30] & 0xFF) << 10 | (m[31] & 0xFF) << 18;
}
static bool
IsOverflow(Long10 x)
{
return (((x.N0 > P26 - 19)) & ((x.N1 & x.N3 & x.N5 & x.N7 & x.N9) == P25) & ((x.N2 & x.N4 & x.N6 & x.N8) == P26)) || (x.N9 > P25);
}
static void
Pack(Long10 x, byte[] m)
{
int ld = (IsOverflow(x) ? 1 : 0) - ((x.N9 < 0) ? 1 : 0);
int ud = ld * -(P25 + 1);
ld *= 19;
long t = ld + x.N0 + (x.N1 << 26);
m[0] = (byte)t;
m[1] = (byte)(t >> 8);
m[2] = (byte)(t >> 16);
m[3] = (byte)(t >> 24);
t = (t >> 32) + (x.N2 << 19);
m[4] = (byte)t;
m[5] = (byte)(t >> 8);
m[6] = (byte)(t >> 16);
m[7] = (byte)(t >> 24);
t = (t >> 32) + (x.N3 << 13);
m[8] = (byte)t;
m[9] = (byte)(t >> 8);
m[10] = (byte)(t >> 16);
m[11] = (byte)(t >> 24);
t = (t >> 32) + (x.N4 << 6);
m[12] = (byte)t;
m[13] = (byte)(t >> 8);
m[14] = (byte)(t >> 16);
m[15] = (byte)(t >> 24);
t = (t >> 32) + x.N5 + (x.N6 << 25);
m[16] = (byte)t;
m[17] = (byte)(t >> 8);
m[18] = (byte)(t >> 16);
m[19] = (byte)(t >> 24);
t = (t >> 32) + (x.N7 << 19);
m[20] = (byte)t;
m[21] = (byte)(t >> 8);
m[22] = (byte)(t >> 16);
m[23] = (byte)(t >> 24);
t = (t >> 32) + (x.N8 << 12);
m[24] = (byte)t;
m[25] = (byte)(t >> 8);
m[26] = (byte)(t >> 16);
m[27] = (byte)(t >> 24);
t = (t >> 32) + ((x.N9 + ud) << 6);
m[28] = (byte)t;
m[29] = (byte)(t >> 8);
m[30] = (byte)(t >> 16);
m[31] = (byte)(t >> 24);
}
static void
Copy(Long10 numOut, Long10 numIn)
{
numOut.N0 = numIn.N0;
numOut.N1 = numIn.N1;
numOut.N2 = numIn.N2;
numOut.N3 = numIn.N3;
numOut.N4 = numIn.N4;
numOut.N5 = numIn.N5;
numOut.N6 = numIn.N6;
numOut.N7 = numIn.N7;
numOut.N8 = numIn.N8;
numOut.N9 = numIn.N9;
}
static void
Set(Long10 numOut, int numIn)
{
numOut.N0 = numIn;
numOut.N1 = 0;
numOut.N2 = 0;
numOut.N3 = 0;
numOut.N4 = 0;
numOut.N5 = 0;
numOut.N6 = 0;
numOut.N7 = 0;
numOut.N8 = 0;
numOut.N9 = 0;
}
static void
Add(Long10 xy, Long10 x, Long10 y)
{
xy.N0 = x.N0 + y.N0;
xy.N1 = x.N1 + y.N1;
xy.N2 = x.N2 + y.N2;
xy.N3 = x.N3 + y.N3;
xy.N4 = x.N4 + y.N4;
xy.N5 = x.N5 + y.N5;
xy.N6 = x.N6 + y.N6;
xy.N7 = x.N7 + y.N7;
xy.N8 = x.N8 + y.N8;
xy.N9 = x.N9 + y.N9;
}
static void
Sub(Long10 xy, Long10 x, Long10 y)
{
xy.N0 = x.N0 - y.N0;
xy.N1 = x.N1 - y.N1;
xy.N2 = x.N2 - y.N2;
xy.N3 = x.N3 - y.N3;
xy.N4 = x.N4 - y.N4;
xy.N5 = x.N5 - y.N5;
xy.N6 = x.N6 - y.N6;
xy.N7 = x.N7 - y.N7;
xy.N8 = x.N8 - y.N8;
xy.N9 = x.N9 - y.N9;
}
static void
MulSmall(Long10 xy, Long10 x, long y)
{
long temp = (x.N8 * y);
xy.N8 = (temp & ((1 << 26) - 1));
temp = (temp >> 26) + (x.N9 * y);
xy.N9 = (temp & ((1 << 25) - 1));
temp = 19 * (temp >> 25) + (x.N0 * y);
xy.N0 = (temp & ((1 << 26) - 1));
temp = (temp >> 26) + (x.N1 * y);
xy.N1 = (temp & ((1 << 25) - 1));
temp = (temp >> 25) + (x.N2 * y);
xy.N2 = (temp & ((1 << 26) - 1));
temp = (temp >> 26) + (x.N3 * y);
xy.N3 = (temp & ((1 << 25) - 1));
temp = (temp >> 25) + (x.N4 * y);
xy.N4 = (temp & ((1 << 26) - 1));
temp = (temp >> 26) + (x.N5 * y);
xy.N5 = (temp & ((1 << 25) - 1));
temp = (temp >> 25) + (x.N6 * y);
xy.N6 = (temp & ((1 << 26) - 1));
temp = (temp >> 26) + (x.N7 * y);
xy.N7 = (temp & ((1 << 25) - 1));
temp = (temp >> 25) + xy.N8;
xy.N8 = (temp & ((1 << 26) - 1));
xy.N9 += (temp >> 26);
}
static void
Multiply(Long10 xy, Long10 x, Long10 y)
{
long x0 = x.N0, x1 = x.N1, x2 = x.N2, x3 = x.N3, x4 = x.N4, x5 = x.N5, x6 = x.N6, x7 = x.N7, x8 = x.N8, x9 = x.N9;
long y0 = y.N0, y1 = y.N1, y2 = y.N2, y3 = y.N3, y4 = y.N4, y5 = y.N5, y6 = y.N6, y7 = y.N7, y8 = y.N8, y9 = y.N9;
long t = (x0 * y8) + (x2 * y6) + (x4 * y4) + (x6 * y2) + (x8 * y0) + 2 * ((x1 * y7) + (x3 * y5) + (x5 * y3) + (x7 * y1)) + 38 * (x9 * y9);
xy.N8 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x0 * y9) + (x1 * y8) + (x2 * y7) + (x3 * y6) + (x4 * y5) + (x5 * y4) + (x6 * y3) + (x7 * y2) + (x8 * y1) + (x9 * y0);
xy.N9 = (t & ((1 << 25) - 1));
t = (x0 * y0) + 19 * ((t >> 25) + (x2 * y8) + (x4 * y6) + (x6 * y4) + (x8 * y2)) + 38 * ((x1 * y9) + (x3 * y7) + (x5 * y5) + (x7 * y3) + (x9 * y1));
xy.N0 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x0 * y1) + (x1 * y0) + 19 * ((x2 * y9) + (x3 * y8) + (x4 * y7) + (x5 * y6) + (x6 * y5) + (x7 * y4) + (x8 * y3) + (x9 * y2));
xy.N1 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x0 * y2) + (x2 * y0) + 19 * ((x4 * y8) + (x6 * y6) + (x8 * y4)) + 2 * (x1 * y1) + 38 * ((x3 * y9) + (x5 * y7) + (x7 * y5) + (x9 * y3));
xy.N2 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x0 * y3) + (x1 * y2) + (x2 * y1) + (x3 * y0) + 19 * ((x4 * y9) + (x5 * y8) + (x6 * y7) + (x7 * y6) + (x8 * y5) + (x9 * y4));
xy.N3 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x0 * y4) + (x2 * y2) + (x4 * y0) + 19 * ((x6 * y8) + (x8 * y6)) + 2 * ((x1 * y3) + (x3 * y1)) + 38 * ((x5 * y9) + (x7 * y7) + (x9 * y5));
xy.N4 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x0 * y5) + (x1 * y4) + (x2 * y3) + (x3 * y2) + (x4 * y1) + (x5 * y0) + 19 * ((x6 * y9) + (x7 * y8) + (x8 * y7) + (x9 * y6));
xy.N5 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x0 * y6) + (x2 * y4) + (x4 * y2) + (x6 * y0) + 19 * (x8 * y8) + 2 * ((x1 * y5) + (x3 * y3) + (x5 * y1)) + 38 * ((x7 * y9) + (x9 * y7));
xy.N6 = (t & ((1 << 26) - 1));
t = (t >> 26) + (x0 * y7) + (x1 * y6) + (x2 * y5) + (x3 * y4) + (x4 * y3) + (x5 * y2) + (x6 * y1) + (x7 * y0) + 19 * ((x8 * y9) + (x9 * y8));
xy.N7 = (t & ((1 << 25) - 1));
t = (t >> 25) + xy.N8;
xy.N8 = (t & ((1 << 26) - 1));
xy.N9 += (t >> 26);
}
static void
Square(Long10 xsqr, Long10 x)
{
long x0 = x.N0, x1 = x.N1, x2 = x.N2, x3 = x.N3, x4 = x.N4, x5 = x.N5, x6 = x.N6, x7 = x.N7, x8 = x.N8, x9 = x.N9;
long t = (x4 * x4) + 2 * ((x0 * x8) + (x2 * x6)) + 38 * (x9 * x9) + 4 * ((x1 * x7) + (x3 * x5));
xsqr.N8 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x0 * x9) + (x1 * x8) + (x2 * x7) + (x3 * x6) + (x4 * x5));
xsqr.N9 = (t & ((1 << 25) - 1));
t = 19 * (t >> 25) + (x0 * x0) + 38 * ((x2 * x8) + (x4 * x6) + (x5 * x5)) + 76 * ((x1 * x9) + (x3 * x7));
xsqr.N0 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * (x0 * x1) + 38 * ((x2 * x9) + (x3 * x8) + (x4 * x7) + (x5 * x6));
xsqr.N1 = (t & ((1 << 25) - 1));
t = (t >> 25) + 19 * (x6 * x6) + 2 * ((x0 * x2) + (x1 * x1)) + 38 * (x4 * x8) + 76 * ((x3 * x9) + (x5 * x7));
xsqr.N2 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x0 * x3) + (x1 * x2)) + 38 * ((x4 * x9) + (x5 * x8) + (x6 * x7));
xsqr.N3 = (t & ((1 << 25) - 1));
t = (t >> 25) + (x2 * x2) + 2 * (x0 * x4) + 38 * ((x6 * x8) + (x7 * x7)) + 4 * (x1 * x3) + 76 * (x5 * x9);
xsqr.N4 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x0 * x5) + (x1 * x4) + (x2 * x3)) + 38 * ((x6 * x9) + (x7 * x8));
xsqr.N5 = (t & ((1 << 25) - 1));
t = (t >> 25) + 19 * (x8 * x8) + 2 * ((x0 * x6) + (x2 * x4) + (x3 * x3)) + 4 * (x1 * x5) + 76 * (x7 * x9);
xsqr.N6 = (t & ((1 << 26) - 1));
t = (t >> 26) + 2 * ((x0 * x7) + (x1 * x6) + (x2 * x5) + (x3 * x4)) + 38 * (x8 * x9);
xsqr.N7 = (t & ((1 << 25) - 1));
t = (t >> 25) + xsqr.N8;
xsqr.N8 = (t & ((1 << 26) - 1));
xsqr.N9 += (t >> 26);
}
static void
Reciprocal(Long10 y, Long10 x, bool sqrtAssist)
{
Long10 t0 = new Long10(), t1 = new Long10(), t2 = new Long10(), t3 = new Long10(), t4 = new Long10();
int i;
Square(t1, x);
Square(t2, t1);
Square(t0, t2);
Multiply(t2, t0, x);
Multiply(t0, t2, t1);
Square(t1, t0);
Multiply(t3, t1, t2);
Square(t1, t3);
Square(t2, t1);
Square(t1, t2);
Square(t2, t1);
Square(t1, t2);
Multiply(t2, t1, t3);
Square(t1, t2);
Square(t3, t1);
for (i = 1; i < 5; i++)
{
Square(t1, t3);
Square(t3, t1);
}
Multiply(t1, t3, t2);
Square(t3, t1);
Square(t4, t3);
for (i = 1; i < 10; i++)
{
Square(t3, t4);
Square(t4, t3);
}
Multiply(t3, t4, t1);
for (i = 0; i < 5; i++)
{
Square(t1, t3);
Square(t3, t1);
}
Multiply(t1, t3, t2);
Square(t2, t1);
Square(t3, t2);
for (i = 1; i < 25; i++)
{
Square(t2, t3);
Square(t3, t2);
}
Multiply(t2, t3, t1);
Square(t3, t2);
Square(t4, t3);
for (i = 1; i < 50; i++)
{
Square(t3, t4);
Square(t4, t3);
}
Multiply(t3, t4, t2);
for (i = 0; i < 25; i++)
{
Square(t4, t3);
Square(t3, t4);
}
Multiply(t2, t3, t1);
Square(t1, t2);
Square(t2, t1);
if (sqrtAssist)
{
Multiply(y, x, t2);
}
else
{
Square(t1, t2);
Square(t2, t1);
Square(t1, t2);
Multiply(y, t1, t0);
}
}
static int
IsNegative(Long10 x)
{
return (int)(((IsOverflow(x) | (x.N9 < 0)) ? 1 : 0) ^ (x.N0 & 1));
}
static void
MontyPrepare(Long10 t1, Long10 t2, Long10 ax, Long10 az)
{
Add(t1, ax, az);
Sub(t2, ax, az);
}
static void
MontyAdd(Long10 t1, Long10 t2, Long10 t3, Long10 t4, Long10 ax, Long10 az, Long10 dx)
{
Multiply(ax, t2, t3);
Multiply(az, t1, t4);
Add(t1, ax, az);
Sub(t2, ax, az);
Square(ax, t1);
Square(t1, t2);
Multiply(az, t1, dx);
}
static void
MontyDouble(Long10 t1, Long10 t2, Long10 t3, Long10 t4, Long10 bx, Long10 bz)
{
Square(t1, t3);
Square(t2, t4);
Multiply(bx, t1, t2);
Sub(t2, t1, t2);
MulSmall(bz, t2, 121665);
Add(t1, t1, bz);
Multiply(bz, t1, t2);
}
static void
CurveEquationInline(Long10 y2, Long10 x, Long10 temp)
{
Square(temp, x);
MulSmall(y2, x, 486662);
Add(temp, temp, y2);
temp.N0++;
Multiply(y2, temp, x);
}
static void Core(byte[] publicKey, byte[] signingKey, byte[] privateKey, byte[] peerPublicKey)
{
if (publicKey == null)
throw new ArgumentNullException("publicKey");
if (publicKey.Length != 32)
throw new ArgumentException(String.Format("publicKey must be 32 bytes long (but was {0} bytes long)", publicKey.Length), "publicKey");
if (signingKey != null && signingKey.Length != 32)
throw new ArgumentException(String.Format("signingKey must be null or 32 bytes long (but was {0} bytes long)", signingKey.Length), "signingKey");
if (privateKey == null)
throw new ArgumentNullException("privateKey");
if (privateKey.Length != 32)
throw new ArgumentException(String.Format("privateKey must be 32 bytes long (but was {0} bytes long)", privateKey.Length), "privateKey");
if (peerPublicKey != null && peerPublicKey.Length != 32)
throw new ArgumentException(String.Format("peerPublicKey must be null or 32 bytes long (but was {0} bytes long)", peerPublicKey.Length), "peerPublicKey");
Long10 dx = new Long10(), t1 = new Long10(), t2 = new Long10(), t3 = new Long10(), t4 = new Long10();
Long10[] x = { new Long10(), new Long10() }, z = { new Long10(), new Long10() };
if (peerPublicKey != null)
Unpack(dx, peerPublicKey);
else
Set(dx, 9);
Set(x[0], 1);
Set(z[0], 0);
Copy(x[1], dx);
Set(z[1], 1);
for (int i = 32; i-- != 0;)
{
for (int j = 8; j-- != 0;)
{
int bit1 = (privateKey[i] & 0xFF) >> j & 1;
int bit0 = ~(privateKey[i] & 0xFF) >> j & 1;
Long10 ax = x[bit0];
Long10 az = z[bit0];
Long10 bx = x[bit1];
Long10 bz = z[bit1];
MontyPrepare(t1, t2, ax, az);
MontyPrepare(t3, t4, bx, bz);
MontyAdd(t1, t2, t3, t4, ax, az, dx);
MontyDouble(t1, t2, t3, t4, bx, bz);
}
}
Reciprocal(t1, z[0], false);
Multiply(dx, x[0], t1);
Pack(dx, publicKey);
if (signingKey != null)
{
CurveEquationInline(t1, dx, t2);
Reciprocal(t3, z[1], false);
Multiply(t2, x[1], t3);
Add(t2, t2, dx);
t2.N0 += 9 + 486662;
dx.N0 -= 9;
Square(t3, dx);
Multiply(dx, t2, t3);
Sub(dx, dx, t1);
dx.N0 -= 39420360;
Multiply(t1, dx, BaseR2Y);
if (IsNegative(t1) != 0)
Copy32(privateKey, signingKey);
else
MultiplyArraySmall(signingKey, OrderTimes8, 0, privateKey, 32, -1);
var temp1 = new byte[32];
var temp2 = new byte[64];
var temp3 = new byte[64];
Copy32(Order, temp1);
Copy32(Egcd32(temp2, temp3, signingKey, temp1), signingKey);
if ((signingKey[31] & 0x80) != 0)
MultiplyArraySmall(signingKey, signingKey, 0, Order, 32, 1);
}
}
static readonly byte[] OrderTimes8 = { 104, 159, 174, 231, 210, 24, 147, 192, 178, 230, 188, 23, 245, 206, 247, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 };
static readonly Long10 BaseR2Y = new Long10(5744, 8160848, 4790893, 13779497, 35730846, 12541209, 49101323, 30047407, 40071253, 6226132);
}
}

537
TLS/Curves/Ed25519.cs Normal file
View file

@ -0,0 +1,537 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System.Security.Cryptography;
using System.Numerics;
using System;
using System.IO;
using System.Linq;
namespace MXM3.MXMailer.Crypto
{
public class EdSecp256r1
{
private const int BitLength = 256;
private static readonly BigInteger TwoPowBitLengthMinusTwo = BigInteger.Pow(2, BitLength - 2);
private static readonly BigInteger[] TwoPowCache = Enumerable.Range(0, 2 * BitLength).Select(i => BigInteger.Pow(2, i)).ToArray();
private static readonly BigInteger Q = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819949");
private static readonly BigInteger Qm2 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819947");
private static readonly BigInteger Qp3 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819952");
private static readonly BigInteger L = BigInteger.Parse("7237005577332262213973186563042994240857116359379907606001950938285454250989");
private static readonly BigInteger D = BigInteger.Parse("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740");
private static readonly BigInteger I = BigInteger.Parse("19681161376707505956807079304988542015446066515923890162744021073123829784752");
private static readonly BigInteger By = BigInteger.Parse("46316835694926478169428394003475163141307993866256225615783033603165251855960");
private static readonly BigInteger Bx = BigInteger.Parse("15112221349535400772501151409588531511454012693041857206046113283949847762202");
private static readonly Tuple<BigInteger, BigInteger> B = new Tuple<BigInteger, BigInteger>(Bx.Mod(Q), By.Mod(Q));
private static readonly BigInteger Un = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967");
private static readonly BigInteger Two = new BigInteger(2);
private static readonly BigInteger Eight = new BigInteger(8);
private static byte[] ComputeHash(byte[] m)
{
using (var sha512 = SHA512Managed.Create()) { return sha512.ComputeHash(m); }
}
private static BigInteger
ExpMod(BigInteger number, BigInteger exponent, BigInteger modulo)
{
if (exponent.Equals(BigInteger.Zero))
{
return BigInteger.One;
}
BigInteger t = BigInteger.Pow(ExpMod(number, exponent / Two, modulo), 2).Mod(modulo);
if (!exponent.IsEven)
{
t *= number;
t = t.Mod(modulo);
}
return t;
}
private static BigInteger
Inv(BigInteger x)
{
return ExpMod(x, Qm2, Q);
}
private static BigInteger
RecoverX(BigInteger y)
{
BigInteger y2 = y * y;
BigInteger xx = (y2 - 1) * Inv(D * y2 + 1);
BigInteger x = ExpMod(xx, Qp3 / Eight, Q);
if (!(x * x - xx).Mod(Q).Equals(BigInteger.Zero))
{
x = (x * I).Mod(Q);
}
if (!x.IsEven)
{
x = Q - x;
}
return x;
}
private static Tuple<BigInteger, BigInteger>
Edwards(BigInteger px, BigInteger py, BigInteger qx, BigInteger qy)
{
BigInteger xx12 = px * qx;
BigInteger yy12 = py * qy;
BigInteger dtemp = D * xx12 * yy12;
BigInteger x3 = (px * qy + qx * py) * (Inv(1 + dtemp));
BigInteger y3 = (py * qy + xx12) * (Inv(1 - dtemp));
return new Tuple<BigInteger, BigInteger>(x3.Mod(Q), y3.Mod(Q));
}
private static Tuple<BigInteger, BigInteger>
EdwardsSquare(BigInteger x, BigInteger y)
{
BigInteger xx = x * x;
BigInteger yy = y * y;
BigInteger dtemp = D * xx * yy;
BigInteger x3 = (2 * x * y) * (Inv(1 + dtemp));
BigInteger y3 = (yy + xx) * (Inv(1 - dtemp));
return new Tuple<BigInteger, BigInteger>(x3.Mod(Q), y3.Mod(Q));
}
private static Tuple<BigInteger, BigInteger>
ScalarMul(Tuple<BigInteger, BigInteger> p, BigInteger e)
{
if (e.Equals(BigInteger.Zero))
{
return new Tuple<BigInteger, BigInteger>(BigInteger.Zero, BigInteger.One);
}
var q = ScalarMul(p, e / Two);
q = EdwardsSquare(q.Item1, q.Item2);
if (!e.IsEven)
q = Edwards(q.Item1, q.Item2, p.Item1, p.Item2);
return q;
}
public static byte[] EncodeInt(BigInteger y)
{
byte[] nin = y.ToByteArray();
var nout = new byte[Math.Max(nin.Length, 32)];
Array.Copy(nin, nout, nin.Length);
return nout;
}
public static byte[] EncodePoint(BigInteger x, BigInteger y)
{
byte[] nout = EncodeInt(y);
nout[nout.Length - 1] |= (x.IsEven ? (byte)0 : (byte)0x80);
return nout;
}
private static int
GetBit(byte[] h, int i)
{
return h[i / 8] >> (i % 8) & 1;
}
public static byte[] PublicKey(byte[] signingKey)
{
byte[] h = ComputeHash(signingKey);
BigInteger a = TwoPowBitLengthMinusTwo;
for (int i = 3; i < (BitLength - 2); i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
a += TwoPowCache[i];
}
}
var bigA = ScalarMul(B, a);
return EncodePoint(bigA.Item1, bigA.Item2);
}
private static BigInteger HashInt(byte[] m)
{
byte[] h = ComputeHash(m);
BigInteger hsum = BigInteger.Zero;
for (int i = 0; i < 2 * BitLength; i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
hsum += TwoPowCache[i];
}
}
return hsum;
}
public static byte[] Signature(byte[] message, byte[] signingKey, byte[] publicKey)
{
byte[] h = ComputeHash(signingKey);
BigInteger a = TwoPowBitLengthMinusTwo;
for (int i = 3; i < (BitLength - 2); i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
a += TwoPowCache[i];
}
}
BigInteger r;
using (var rsub = new MemoryStream((BitLength / 8) + message.Length))
{
rsub.Write(h, BitLength / 8, BitLength / 4 - BitLength / 8);
rsub.Write(message, 0, message.Length);
r = HashInt(rsub.ToArray());
}
var bigR = ScalarMul(B, r);
BigInteger s;
var encodedBigR = EncodePoint(bigR.Item1, bigR.Item2);
using (var stemp = new MemoryStream(32 + publicKey.Length + message.Length))
{
stemp.Write(encodedBigR, 0, encodedBigR.Length);
stemp.Write(publicKey, 0, publicKey.Length);
stemp.Write(message, 0, message.Length);
s = (r + HashInt(stemp.ToArray()) * a).Mod(L);
}
using (var nout = new MemoryStream(64))
{
nout.Write(encodedBigR, 0, encodedBigR.Length);
var encodeInt = EncodeInt(s);
nout.Write(encodeInt, 0, encodeInt.Length);
return nout.ToArray();
}
}
private static bool
IsOnCurve(BigInteger x, BigInteger y)
{
BigInteger xx = x * x;
BigInteger yy = y * y;
BigInteger dxxyy = D * yy * xx;
return (yy - xx - dxxyy - 1).Mod(Q).Equals(BigInteger.Zero);
}
private static BigInteger DecodeInt(byte[] s) { return new BigInteger(s) & Un; }
private static Tuple<BigInteger, BigInteger> DecodePoint(byte[] pointBytes)
{
BigInteger y = new BigInteger(pointBytes) & Un;
BigInteger x = RecoverX(y);
if ((x.IsEven ? 0 : 1) != GetBit(pointBytes, BitLength - 1))
{
x = Q - x;
}
var point = new Tuple<BigInteger, BigInteger>(x, y);
if (!IsOnCurve(x, y))
throw new ArgumentException("Decoding point that is not on curve");
return point;
}
public static bool CheckValid(byte[] signature, byte[] message, byte[] publicKey)
{
if (signature.Length != BitLength / 4)
throw new ArgumentException("Signature length is wrong");
if (publicKey.Length != BitLength / 8)
throw new ArgumentException("Public key length is wrong");
byte[] rByte = Arrays.CopyOfRange(signature, 0, BitLength / 8);
var r = DecodePoint(rByte);
var a = DecodePoint(publicKey);
byte[] sByte = Arrays.CopyOfRange(signature, BitLength / 8, BitLength / 4);
BigInteger s = DecodeInt(sByte);
BigInteger h;
using (var stemp = new MemoryStream(32 + publicKey.Length + message.Length))
{
var encodePoint = EncodePoint(r.Item1, r.Item2);
stemp.Write(encodePoint, 0, encodePoint.Length);
stemp.Write(publicKey, 0, publicKey.Length);
stemp.Write(message, 0, message.Length);
h = HashInt(stemp.ToArray());
}
var ra = ScalarMul(B, s);
var ah = ScalarMul(a, h);
var rb = Edwards(r.Item1, r.Item2, ah.Item1, ah.Item2);
if (!ra.Item1.Equals(rb.Item1) || !ra.Item2.Equals(rb.Item2))
return false;
return true;
}
}
public class Ed25519
{
private const int BitLength = 256;
private static readonly BigInteger TwoPowBitLengthMinusTwo = BigInteger.Pow(2, BitLength - 2);
private static readonly BigInteger[] TwoPowCache = Enumerable.Range(0, 2 * BitLength).Select(i => BigInteger.Pow(2, i)).ToArray();
private static readonly BigInteger Q = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819949");
private static readonly BigInteger Qm2 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819947");
private static readonly BigInteger Qp3 = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819952");
private static readonly BigInteger L = BigInteger.Parse("7237005577332262213973186563042994240857116359379907606001950938285454250989");
private static readonly BigInteger D = BigInteger.Parse("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740");
private static readonly BigInteger I = BigInteger.Parse("19681161376707505956807079304988542015446066515923890162744021073123829784752");
private static readonly BigInteger By = BigInteger.Parse("46316835694926478169428394003475163141307993866256225615783033603165251855960");
private static readonly BigInteger Bx = BigInteger.Parse("15112221349535400772501151409588531511454012693041857206046113283949847762202");
private static readonly Tuple<BigInteger, BigInteger> B = new Tuple<BigInteger, BigInteger>(Bx.Mod(Q), By.Mod(Q));
private static readonly BigInteger Un = BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967");
private static readonly BigInteger Two = new BigInteger(2);
private static readonly BigInteger Eight = new BigInteger(8);
private static byte[] ComputeHash(byte[] m)
{
using (var sha512 = SHA512Managed.Create()) { return sha512.ComputeHash(m); }
}
private static BigInteger
ExpMod(BigInteger number, BigInteger exponent, BigInteger modulo)
{
if (exponent.Equals(BigInteger.Zero))
{
return BigInteger.One;
}
BigInteger t = BigInteger.Pow(ExpMod(number, exponent / Two, modulo), 2).Mod(modulo);
if (!exponent.IsEven)
{
t *= number;
t = t.Mod(modulo);
}
return t;
}
private static BigInteger
Inv(BigInteger x)
{
return ExpMod(x, Qm2, Q);
}
private static BigInteger
RecoverX(BigInteger y)
{
BigInteger y2 = y * y;
BigInteger xx = (y2 - 1) * Inv(D * y2 + 1);
BigInteger x = ExpMod(xx, Qp3 / Eight, Q);
if (!(x * x - xx).Mod(Q).Equals(BigInteger.Zero))
{
x = (x * I).Mod(Q);
}
if (!x.IsEven)
{
x = Q - x;
}
return x;
}
private static Tuple<BigInteger, BigInteger>
Edwards(BigInteger px, BigInteger py, BigInteger qx, BigInteger qy)
{
BigInteger xx12 = px * qx;
BigInteger yy12 = py * qy;
BigInteger dtemp = D * xx12 * yy12;
BigInteger x3 = (px * qy + qx * py) * (Inv(1 + dtemp));
BigInteger y3 = (py * qy + xx12) * (Inv(1 - dtemp));
return new Tuple<BigInteger, BigInteger>(x3.Mod(Q), y3.Mod(Q));
}
private static Tuple<BigInteger, BigInteger>
EdwardsSquare(BigInteger x, BigInteger y)
{
BigInteger xx = x * x;
BigInteger yy = y * y;
BigInteger dtemp = D * xx * yy;
BigInteger x3 = (2 * x * y) * (Inv(1 + dtemp));
BigInteger y3 = (yy + xx) * (Inv(1 - dtemp));
return new Tuple<BigInteger, BigInteger>(x3.Mod(Q), y3.Mod(Q));
}
private static Tuple<BigInteger, BigInteger>
ScalarMul(Tuple<BigInteger, BigInteger> p, BigInteger e)
{
if (e.Equals(BigInteger.Zero))
{
return new Tuple<BigInteger, BigInteger>(BigInteger.Zero, BigInteger.One);
}
var q = ScalarMul(p, e / Two);
q = EdwardsSquare(q.Item1, q.Item2);
if (!e.IsEven)
q = Edwards(q.Item1, q.Item2, p.Item1, p.Item2);
return q;
}
public static byte[] EncodeInt(BigInteger y)
{
byte[] nin = y.ToByteArray();
var nout = new byte[Math.Max(nin.Length, 32)];
Array.Copy(nin, nout, nin.Length);
return nout;
}
public static byte[] EncodePoint(BigInteger x, BigInteger y)
{
byte[] nout = EncodeInt(y);
nout[nout.Length - 1] |= (x.IsEven ? (byte)0 : (byte)0x80);
return nout;
}
private static int
GetBit(byte[] h, int i)
{
return h[i / 8] >> (i % 8) & 1;
}
public static byte[] PublicKey(byte[] signingKey)
{
byte[] h = ComputeHash(signingKey);
BigInteger a = TwoPowBitLengthMinusTwo;
for (int i = 3; i < (BitLength - 2); i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
a += TwoPowCache[i];
}
}
var bigA = ScalarMul(B, a);
return EncodePoint(bigA.Item1, bigA.Item2);
}
private static BigInteger HashInt(byte[] m)
{
byte[] h = ComputeHash(m);
BigInteger hsum = BigInteger.Zero;
for (int i = 0; i < 2 * BitLength; i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
hsum += TwoPowCache[i];
}
}
return hsum;
}
public static byte[] Signature(byte[] message, byte[] signingKey, byte[] publicKey)
{
byte[] h = ComputeHash(signingKey);
BigInteger a = TwoPowBitLengthMinusTwo;
for (int i = 3; i < (BitLength - 2); i++)
{
var bit = GetBit(h, i);
if (bit != 0)
{
a += TwoPowCache[i];
}
}
BigInteger r;
using (var rsub = new MemoryStream((BitLength / 8) + message.Length))
{
rsub.Write(h, BitLength / 8, BitLength / 4 - BitLength / 8);
rsub.Write(message, 0, message.Length);
r = HashInt(rsub.ToArray());
}
var bigR = ScalarMul(B, r);
BigInteger s;
var encodedBigR = EncodePoint(bigR.Item1, bigR.Item2);
using (var stemp = new MemoryStream(32 + publicKey.Length + message.Length))
{
stemp.Write(encodedBigR, 0, encodedBigR.Length);
stemp.Write(publicKey, 0, publicKey.Length);
stemp.Write(message, 0, message.Length);
s = (r + HashInt(stemp.ToArray()) * a).Mod(L);
}
using (var nout = new MemoryStream(64))
{
nout.Write(encodedBigR, 0, encodedBigR.Length);
var encodeInt = EncodeInt(s);
nout.Write(encodeInt, 0, encodeInt.Length);
return nout.ToArray();
}
}
private static bool
IsOnCurve(BigInteger x, BigInteger y)
{
BigInteger xx = x * x;
BigInteger yy = y * y;
BigInteger dxxyy = D * yy * xx;
return (yy - xx - dxxyy - 1).Mod(Q).Equals(BigInteger.Zero);
}
private static BigInteger DecodeInt(byte[] s) { return new BigInteger(s) & Un; }
private static Tuple<BigInteger, BigInteger> DecodePoint(byte[] pointBytes)
{
BigInteger y = new BigInteger(pointBytes) & Un;
BigInteger x = RecoverX(y);
if ((x.IsEven ? 0 : 1) != GetBit(pointBytes, BitLength - 1))
{
x = Q - x;
}
var point = new Tuple<BigInteger, BigInteger>(x, y);
if (!IsOnCurve(x, y))
throw new ArgumentException("Decoding point that is not on curve");
return point;
}
public static bool CheckValid(byte[] signature, byte[] message, byte[] publicKey)
{
if (signature.Length != BitLength / 4)
throw new ArgumentException("Signature length is wrong");
if (publicKey.Length != BitLength / 8)
throw new ArgumentException("Public key length is wrong");
byte[] rByte = Arrays.CopyOfRange(signature, 0, BitLength / 8);
var r = DecodePoint(rByte);
var a = DecodePoint(publicKey);
byte[] sByte = Arrays.CopyOfRange(signature, BitLength / 8, BitLength / 4);
BigInteger s = DecodeInt(sByte);
BigInteger h;
using (var stemp = new MemoryStream(32 + publicKey.Length + message.Length))
{
var encodePoint = EncodePoint(r.Item1, r.Item2);
stemp.Write(encodePoint, 0, encodePoint.Length);
stemp.Write(publicKey, 0, publicKey.Length);
stemp.Write(message, 0, message.Length);
h = HashInt(stemp.ToArray());
}
var ra = ScalarMul(B, s);
var ah = ScalarMul(a, h);
var rb = Edwards(r.Item1, r.Item2, ah.Item1, ah.Item2);
if (!ra.Item1.Equals(rb.Item1) || !ra.Item2.Equals(rb.Item2))
return false;
return true;
}
}
internal static class Arrays
{
public static byte[] CopyOfRange(byte[] original, int from, int to)
{
int length = to - from;
var result = new byte[length];
Array.Copy(original, from, result, 0, length);
return result;
}
}
internal static class BigIntegerHelpers
{
public static BigInteger
Mod(this BigInteger num, BigInteger modulo)
{
var result = num % modulo;
return result < 0 ? result + modulo : result;
}
}
}

130
TLS/Encryption.cs Normal file
View file

@ -0,0 +1,130 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using System.Security.Cryptography;
using SecuCore.TLS.Exceptions;
using static SecuCore.TLS.CipherSuites;
namespace SecuCore.TLS
{
class Encryption
{
private static byte[] MacHeader(ulong seqNum, byte type, ushort version, int fregmentlen)
{
// 8 bytes(sequence) + 5 bytes(header)
byte[] result = new byte[13];
result[0] = ((byte)(seqNum >> 56 & 0xff));
result[1] = ((byte)(seqNum >> 48 & 0xff));
result[2] = ((byte)(seqNum >> 40 & 0xff));
result[3] = ((byte)(seqNum >> 32 & 0xff));
result[4] = ((byte)(seqNum >> 24 & 0xff));
result[5] = ((byte)(seqNum >> 16 & 0xff));
result[6] = ((byte)(seqNum >> 8 & 0xff));
result[7] = ((byte)(seqNum & 0xff));
result[8] = type;
result[9] = (byte)((version >> 8) & 0xff);
result[10] = (byte)(version & 0xff);
result[11] = (byte)((fregmentlen >> 8) & 0xff);
result[12] = (byte)(fregmentlen & 0xff);
return result;
}
private const int IVSize = 16;
public static TLSRecord EncryptRecord(TLSRecord tlso, TLSEncryptionProvider provider, ulong sequence)
{
// generate mac
byte[] fragment = tlso.Data;
byte[] macheader = MacHeader(sequence, tlso.Type, tlso.Version, fragment.Length);
provider.LocalHasher.TransformBlock(macheader, 0, macheader.Length, macheader, 0);
provider.LocalHasher.TransformFinalBlock(fragment, 0, fragment.Length);
byte[] mac = provider.LocalHasher.Hash;
ICryptoTransform tfm = provider.Encryptor;
int blockSize = provider.BlockSize;
int paddingLength = blockSize - ((fragment.Length + mac.Length) % blockSize);
byte padb = (byte)(paddingLength - 1);
byte[] inputbytes = new byte[IVSize + fragment.Length + mac.Length + paddingLength];
byte[] ivguid = Guid.NewGuid().ToByteArray();
int inpofs = 0;
Buffer.BlockCopy(ivguid, 0, inputbytes, 0, ivguid.Length);
inpofs += ivguid.Length;
Buffer.BlockCopy(fragment, 0, inputbytes, inpofs, fragment.Length);
inpofs += fragment.Length;
Buffer.BlockCopy(mac, 0, inputbytes, inpofs, mac.Length);
inpofs += mac.Length;
Array.Fill(inputbytes, padb, inpofs, inputbytes.Length - inpofs);
int inputblocks = (inputbytes.Length / tfm.InputBlockSize);
byte[] outputbytes = new byte[inputblocks * tfm.OutputBlockSize];
if (tfm.CanTransformMultipleBlocks)
{
tfm.TransformBlock(inputbytes, 0, inputbytes.Length, outputbytes, 0);
}
else
{
int outofs = 0;
for (int i = 0; i < inputblocks; i++)
outofs += tfm.TransformBlock(inputbytes, i * tfm.InputBlockSize, tfm.InputBlockSize, outputbytes, outofs);
}
fragment = outputbytes;
tlso.Data = fragment;
tlso.Length = (ushort)fragment.Length;
return tlso;
}
public static TLSRecord DecryptRecord(TLSRecord tlso, TLSEncryptionProvider provider, ulong sequence)
{
byte[] fragment = tlso.Data;
ICryptoTransform tfm = provider.Decryptor;
byte[] dec = tfm.TransformFinalBlock(fragment, 0, fragment.Length);
fragment = dec;
int startidx = 0;
int fraglen = fragment.Length;
// discard iv
startidx += provider.IVSize;
fraglen -= provider.IVSize;
// remove padding if necessary
if (provider.bulkAlgorithmType == BulkAlgorithmType.Block)
{
int padding = fragment[fragment.Length - 1] + 1;
// Verify the correctness of padding
if (padding > fragment.Length)
throw new TLSEncryptionException("padding removal failed");
else
fraglen -= padding;
}
// remove mac
int macidx = (startidx + fraglen) - provider.HashSize;
byte[] remotemac = Tools.SubArray(fragment, macidx, provider.HashSize);
fraglen -= provider.HashSize;
byte[] macinputheader = MacHeader(sequence, tlso.Type, tlso.Version, fraglen);
provider.RemoteHasher.Initialize();
provider.RemoteHasher.TransformBlock(macinputheader, 0, macinputheader.Length, macinputheader, 0);
provider.RemoteHasher.TransformFinalBlock(fragment, startidx, fraglen);
byte[] mac = provider.RemoteHasher.Hash;
if (!Tools.ArraysEqual(mac, remotemac))
throw new TLSEncryptionException("Mac verification failed on decrypt");
fragment = Tools.SubArray(fragment, startidx, fraglen);
tlso.Data = fragment;
tlso.Length = (ushort)fragment.Length;
return tlso;
}
}
}

58
TLS/Exceptions.cs Normal file
View file

@ -0,0 +1,58 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
namespace SecuCore.TLS.Exceptions
{
public class TLSHandshakeException : Exception
{
public TLSHandshakeException(string s = "") : base("Error during tls handshake: " + s)
{
}
}
public class TLSEncryptionException : Exception
{
public TLSEncryptionException(string s = "") : base("Error during tls encryption: " + s)
{
}
}
public class TLSValidationException : Exception
{
public TLSValidationException(string s = "") : base("Error during tls validation: " + s)
{
}
}
public class TLSProtocolException : Exception
{
public TLSProtocolException(string s = "") : base("TLS Protocol was broken: " + s)
{
}
}
public class TLSRecordException : Exception
{
public TLSRecordException(string s = "") : base("TLS Record threw an exception: " + s)
{
}
}
public class TLSDataException : Exception
{
public TLSDataException(string s = "") : base("Error with TLS application data: " + s)
{
}
}
public class TLSNetworkException : Exception
{
public TLSNetworkException(string s = "") : base("Error during network communication: " + s)
{
}
}
}

282
TLS/KeyDerivation.cs Normal file
View file

@ -0,0 +1,282 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using System.Security.Cryptography;
using SecuCore.Curves;
using SecuCore.TLS;
using System;
using System.Linq;
namespace SecuCore
{
public class KeyDerivation
{
public struct KeyExpansionResult
{
public byte[] clientWriteMac;
public byte[] serverWriteMac;
public byte[] clientWriteKey;
public byte[] serverWriteKey;
public byte[] clientWriteIV;
public byte[] serverWriteIV;
}
private static byte[] Kexpand11(int minbytes, byte[] seed, HMACMD5 hmacmd5, HMACSHA1 hmacsha1)
{
byte[] md5bytes = Kexp11(minbytes, seed, hmacmd5);
byte[] sha1bytes = Kexp11(minbytes, seed, hmacsha1);
byte[] output = new byte[minbytes];
for (int i = 0; i < output.Length; i++)
{
output[i] = (byte)(md5bytes[i] ^ sha1bytes[i]);
}
return output;
}
private static byte[] Kexp11(int minbytes, byte[] seed, HMAC hm)
{
byte[] output = new byte[minbytes];
byte[] a = seed;
int hs = hm.HashSize / 8;
byte[] b1 = new byte[hs];
byte[] b2 = new byte[hs];
int pos = 0;
while (pos < output.Length)
{
b1 = hm.ComputeHash(a, 0, a.Length);
a = b1;
hm.Initialize();
hm.TransformBlock(b1, 0, b1.Length, b1, 0);
hm.TransformFinalBlock(seed, 0, seed.Length);
b2 = hm.Hash;
int copysize = hs;
if (copysize > (output.Length - pos))
copysize = (output.Length - pos);
Buffer.BlockCopy(b2, 0, output, pos, copysize);
pos += hs;
}
return output;
}
public static byte[] GenerateVerifyData11(byte[] label, byte[] hash, byte[] master)
{
int secretLength = (master.Length + 1) / 2;
byte[] md5Secret = new byte[secretLength];
Buffer.BlockCopy(master, 0, md5Secret, 0, secretLength);
byte[] sha1Secret = new byte[secretLength];
Buffer.BlockCopy(master, master.Length - secretLength, sha1Secret, 0, secretLength);
using (HMACMD5 hmacmd5 = new HMACMD5(md5Secret))
{
using (HMACSHA1 hmacsha1 = new HMACSHA1(sha1Secret))
{
byte[] seed = label.Concat(hash).ToArray();
byte[] verifyMaterial = Kexpand11(12, seed, hmacmd5, hmacsha1);
return Tools.SubArray(verifyMaterial, 0, 12);
}
}
}
public static byte[] GenerateMasterSecret11(byte[] sharedsecret, byte[] clientRandom, byte[] serverRandom)
{
byte[] label = TLSData.label_mastersecret;
byte[] seed = clientRandom.Concat(serverRandom).ToArray();
seed = label.Concat(seed).ToArray();
// split in half and give each half to a hasher
int secretLength = (sharedsecret.Length + 1) / 2;
byte[] md5Secret = new byte[secretLength];
Buffer.BlockCopy(sharedsecret, 0, md5Secret, 0, secretLength);
byte[] sha1Secret = new byte[secretLength];
Buffer.BlockCopy(sharedsecret, sharedsecret.Length - secretLength, sha1Secret, 0, secretLength);
using (HMACMD5 hmacmd5 = new HMACMD5(md5Secret))
{
using (HMACSHA1 hmacsha1 = new HMACSHA1(sha1Secret))
{
byte[] master = Kexpand11(48, seed, hmacmd5, hmacsha1);
return master;
}
}
}
public static KeyExpansionResult PerformKeyExpansionTLS11(byte[] master, byte[] clientRandom, byte[] serverRandom, int MacSize, int KeySize, int IVSize)
{
byte[] label = TLSData.label_keyexpansion;
byte[] seed = label.Concat(serverRandom).Concat(clientRandom).ToArray();
// split in half and give each half to a hasher
int secretLength = (master.Length + 1) / 2;
byte[] md5Secret = new byte[secretLength];
Buffer.BlockCopy(master, 0, md5Secret, 0, secretLength);
byte[] sha1Secret = new byte[secretLength];
Buffer.BlockCopy(master, secretLength, sha1Secret, 0, secretLength);
int requiredMaterial = (MacSize * 2) + (KeySize * 2) + (IVSize * 2);
byte[] keyMaterial = null;
using (HMACMD5 hmacmd5 = new HMACMD5(md5Secret))
{
using (HMACSHA1 hmacsha1 = new HMACSHA1(sha1Secret))
{
keyMaterial = Kexpand11(requiredMaterial, seed, hmacmd5, hmacsha1);
int pos = 0;
byte[] cliwritemackey = new byte[MacSize];
byte[] serwritemackey = new byte[MacSize];
byte[] cliwritekey = new byte[KeySize];
byte[] serwritekey = new byte[KeySize];
byte[] cliwriteiv = new byte[IVSize];
byte[] serwriteiv = new byte[IVSize];
Buffer.BlockCopy(keyMaterial, pos, cliwritemackey, 0, MacSize);
pos += MacSize;
Buffer.BlockCopy(keyMaterial, pos, serwritemackey, 0, MacSize);
pos += MacSize;
Buffer.BlockCopy(keyMaterial, pos, cliwritekey, 0, KeySize);
pos += KeySize;
Buffer.BlockCopy(keyMaterial, pos, serwritekey, 0, KeySize);
pos += KeySize;
Buffer.BlockCopy(keyMaterial, pos, cliwriteiv, 0, IVSize);
pos += IVSize;
Buffer.BlockCopy(keyMaterial, pos, serwriteiv, 0, IVSize);
pos += IVSize;
return new KeyExpansionResult() { clientWriteMac = cliwritemackey, serverWriteMac = serwritemackey, clientWriteKey = cliwritekey, serverWriteKey = serwritekey, clientWriteIV = cliwriteiv, serverWriteIV = serwriteiv };
}
}
}
public static KeyExpansionResult PerformKeyExpansion(byte[] master, byte[] clientRandom, byte[] serverRandom, int MacSize, int KeySize, int IVSize, HMAC hm)
{
byte[] label = TLSData.label_keyexpansion;
byte[] seed = label.Concat(serverRandom).Concat(clientRandom).ToArray();
int requiredMaterial = (MacSize * 2) + (KeySize * 2) + (IVSize * 2);
byte[] keyMaterial = Kexpand(requiredMaterial, seed, hm);
byte[] cliwritemackey = new byte[MacSize];
byte[] serwritemackey = new byte[MacSize];
byte[] cliwritekey = new byte[KeySize];
byte[] serwritekey = new byte[KeySize];
byte[] cliwriteiv = new byte[IVSize];
byte[] serwriteiv = new byte[IVSize];
int pos = 0;
Buffer.BlockCopy(keyMaterial, pos, cliwritemackey, 0, MacSize);
pos += MacSize;
Buffer.BlockCopy(keyMaterial, pos, serwritemackey, 0, MacSize);
pos += MacSize;
Buffer.BlockCopy(keyMaterial, pos, cliwritekey, 0, KeySize);
pos += KeySize;
Buffer.BlockCopy(keyMaterial, pos, serwritekey, 0, KeySize);
pos += KeySize;
Buffer.BlockCopy(keyMaterial, pos, cliwriteiv, 0, IVSize);
pos += IVSize;
Buffer.BlockCopy(keyMaterial, pos, serwriteiv, 0, IVSize);
pos += IVSize;
return new KeyExpansionResult() { clientWriteMac = cliwritemackey, serverWriteMac = serwritemackey, clientWriteKey = cliwritekey, serverWriteKey = serwritekey, clientWriteIV = cliwriteiv, serverWriteIV = serwriteiv };
}
public static byte[] GenerateVerifyData(byte[] label, byte[] hash, HMAC hm)
{
byte[] seed = label.Concat(hash).ToArray();
byte[] verifyMaterial = Kexpand(12, seed, hm);
return Tools.SubArray(verifyMaterial, 0, 12);
}
public static byte[] GenerateMasterSecret(bool extended, byte[] clientRandom, byte[] serverRandom, HMAC hm)
{
byte[] label = (extended ? TLSData.label_extmastersecret : TLSData.label_mastersecret);
byte[] seed = clientRandom.Concat(serverRandom).ToArray();
seed = label.Concat(seed).ToArray();
byte[] master = Kexpand(48, seed, hm);
return master;
}
private static byte[] Kexpand(int minbytes, byte[] seed, HMAC hm)
{
byte[] outputbytes = new byte[minbytes];
int pos = 0;
byte[] k = (byte[])seed.Clone();
while (pos < minbytes)
{
k = hm.ComputeHash(k);
byte[] p = hm.ComputeHash(k.Concat(seed).ToArray());
int copysize = p.Length;
if (pos + copysize >= (minbytes))
copysize = minbytes - pos;
Buffer.BlockCopy(p, 0, outputbytes, pos, copysize);
pos += copysize;
}
return outputbytes;
}
private static byte[] BigIntegerToByteArray(BigInteger input, int length)
{
byte[] result = new byte[length];
byte[] inputBytes = input.ToByteArray();
Array.Reverse(inputBytes);
Buffer.BlockCopy(inputBytes, 0, result, 0, System.Math.Min(inputBytes.Length, result.Length));
Array.Reverse(result);
return result;
}
public static byte[] CalculateSharedSecretx25519(byte[] clientPrivate, byte[] serverPublic)
{
byte[] clipri = Curve25519.ClampPrivateKey(clientPrivate);
byte[] serpub;
if (serverPublic.Length == 32)
{
serpub = serverPublic;
}
else
{
serpub = new byte[serverPublic.Length - 1];
Buffer.BlockCopy(serverPublic, 1, serpub, 0, serverPublic.Length - 1);
}
byte[] shared = Curve25519.GetSharedSecret(clipri, serpub);
return shared;
}
public static byte[] CalculateSharedSecret(byte[] clientPrivate, byte[] serverPublic, string curveName)
{
if (curveName == "x25519")
return CalculateSharedSecretx25519(clientPrivate, serverPublic);
byte[] sqx = new byte[serverPublic.Length / 2];
byte[] sqy = new byte[sqx.Length];
Buffer.BlockCopy(serverPublic, 1, sqx, 0, sqx.Length);
Buffer.BlockCopy(serverPublic, 1 + sqx.Length, sqy, 0, sqy.Length);
X9ECParameters ecParams = SecNamedCurves.GetByName(curveName);
ECDomainParameters domainParams = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
Org.BouncyCastle.Math.EC.ECPoint serverPoint = ecParams.Curve.DecodePoint(serverPublic);
BigInteger privateBI = new BigInteger(1, clientPrivate, 1, 32);
ECPublicKeyParameters theirPublicKey = new ECPublicKeyParameters(serverPoint, domainParams);
ECPrivateKeyParameters myPrivateKey = new ECPrivateKeyParameters(privateBI, domainParams);
// Calculate the actual agreement
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.Init(myPrivateKey);
BigInteger agreementBI = agreement.CalculateAgreement(theirPublicKey);
byte[] sharedSecret = BigIntegerToByteArray(agreementBI, 32);
return sharedSecret;
}
}
}

360
TLS/TLS.cs Normal file
View file

@ -0,0 +1,360 @@
/*
* 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;
}
}
}

250
TLS/TLSNetworkController.cs Normal file
View file

@ -0,0 +1,250 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using Microsoft.VisualStudio.Threading;
using SecuCore.Shared;
using SecuCore.Sockets;
using SecuCore.TLS.Exceptions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SecuCore.TLS
{
class TLSNetworkController : IDataController
{
TCPSocket _socket;
public bool isFaulted = false;
public string lastError = "";
public TLSNetworkController(TCPSocket tcps)
{
_socket = tcps;
_ = Start();
}
private bool shutdownRequested = false;
private AsyncAutoResetEvent requestReset = new AsyncAutoResetEvent();
private AsyncAutoResetEvent flushReset = new AsyncAutoResetEvent();
private AsyncAutoResetEvent nextEmpty = new AsyncAutoResetEvent();
private async Task Start()
{
_ = WriteLoop();
_ = ReadLoop();
}
private bool GetNextDataDispatch(out DataDispatch next)
{
if (outgoing == null || shutdownRequested)
{
next = null;
return false;
}
return outgoing.TryDequeue(out next);
}
private bool GetNextDataRequest(out DataRequest next)
{
if (requests == null || shutdownRequested)
{
next = null;
return false;
}
return requests.TryDequeue(out next);
}
private async Task WriteLoop()
{
while (!shutdownRequested)
{
await flushReset.WaitAsync().ConfigureAwait(false);
while (GetNextDataDispatch(out DataDispatch next))
{
if (!await DispatchAsync(next).ConfigureAwait(false))
{
Shutdown(true);
return;
}
}
}
}
private async Task ReadLoop()
{
while (!shutdownRequested)
{
await requestReset.WaitAsync().ConfigureAwait(false);
try
{
while (GetNextDataRequest(out DataRequest next))
{
if (!await HandleRequestAsync(next).ConfigureAwait(false))
{
Shutdown(true);
return;
}
}
}catch (Exception) {}
}
}
bool shutdownCalled = false;
private void Shutdown(bool faulted)
{
if (shutdownCalled)
return;
shutdownCalled = true;
shutdownRequested = true;
if (faulted)
{
this.isFaulted = true;
// clear buffers and inform failure
while (outgoing.TryDequeue(out DataDispatch next))
{
if (next.tcs != null)
next.tcs.TrySetException(new Exception("Network controller failure: " + this.lastError));
}
while (requests.TryDequeue(out DataRequest next))
{
if (next.tcs != null)
next.tcs.TrySetException(new Exception("Network controller failure: " + this.lastError));
}
}
else
{
while (outgoing.TryDequeue(out DataDispatch next))
{
if (next.tcs != null)
next.tcs.TrySetCanceled();
}
while (requests.TryDequeue(out DataRequest next))
{
if (next.tcs != null)
next.tcs.TrySetCanceled();
}
}
flushReset.Set();
nextEmpty.Set();
requestReset.Set();
_socket = null;
outgoing = null;
requests = null;
}
private async ValueTask<bool> DispatchAsync(DataDispatch data)
{
if (data == null)
return false;
bool sent = await WriteNSAsync(data.data).ConfigureAwait(false);
if (data.tcs != null)
data.tcs.TrySetResult();
return sent;
}
private async ValueTask<bool> HandleRequestAsync(DataRequest request)
{
byte[] buffer = new byte[request.length];
int pos = 0;
int remain = request.length;
while (remain > 0)
{
int len = await ReadNSAsync(buffer, pos, remain).ConfigureAwait(false);
if (len > 0)
{
pos += len;
remain -= len;
}
else
{
request.tcs.TrySetException(new Exception("Network failure or timeout"));
return false;
}
}
// success
if (!request.tcs.TrySetResult(buffer))
{
lastError = "failed to set result to datarequest";
return false;
}
return true;
}
private async ValueTask<int> ReadNSAsync(byte[] buf, int offset, int length)
{
try
{
int rlen = await _socket.RecieveAsync(buf, offset, length).ConfigureAwait(false);
if (rlen > 0)
{
return rlen;
}
}
catch (Exception ex)
{
this.lastError = ex.Message;
}
return -1;
}
private async ValueTask<bool> WriteNSAsync(byte[] dat)
{
try
{
await _socket.SendAsync(dat).ConfigureAwait(false);
return true;
}
catch (Exception ex)
{
this.lastError = ex.Message;
}
return false;
}
public Queue<DataDispatch> outgoing = new Queue<DataDispatch>();
public Queue<DataRequest> requests = new Queue<DataRequest>();
public void RequestData(DataRequest request)
{
if (shutdownRequested)
throw new TLSNetworkException("Shutdown has been requested");
requests.Enqueue(request);
requestReset.Set();
}
public void QueueData(DataDispatch dispatch)
{
if (shutdownRequested)
throw new TLSNetworkException("Shutdown has been requested");
outgoing.Enqueue(dispatch);
}
public void FlushData()
{
if (shutdownRequested)
throw new TLSNetworkException("Shutdown has been requested");
flushReset.Set();
}
public async Task FlushDataFully()
{
if (shutdownRequested)
throw new TLSNetworkException("Shutdown has been requested");
flushReset.Set();
await nextEmpty.WaitAsync().ConfigureAwait(false);
}
public void Dispose() { Shutdown(false); }
}
}

897
TLS/TLSRecordLayer.cs Normal file
View file

@ -0,0 +1,897 @@
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using SecuCore.Curves;
using SecuCore.Security;
using SecuCore.Shared;
using SecuCore.TLS.Exceptions;
using static SecuCore.KeyDerivation;
using static SecuCore.TLS.CipherSuites;
using static SecuCore.TLS.TLSData;
namespace SecuCore.TLS
{
public class TLSRecordLayer
{
public static TLSVersion preferredVersion = TLSVersion.TLS12;
public static string preferredNamedCurve = "x25519";
public static int preferredFragmentSize = 8192;
public static bool verifyServerSignature = true;
public static ushort[] AllImplementedCipherSuites = CipherSuites.GetSupportedCipherSuites();
//for handshake process
X509Cert remoteServerCertificate;
X509CertChain serverCertChain;
KeyExchangeInfo serverKeyExchangeInfo;
TLSCipherSuite cipherSuite;
ulong sessionTicketLifetime = 0;
byte[] sessionTicket = null;
byte[] tlsEncryptedPremaster;
byte[] tlsPremasterSecret;
byte[] tlsMasterSecret;
byte[] clientRandom;
byte[] serverRandom;
//for the key exchange
byte[] clientPrivateKey;
byte[] clientPublicKey;
//for encryption and decryption
KeyExpansionResult keyRing;
IDataController dataController;
TLSVersion vers;
private string lastErrorString;
private byte[] versionb;
private SecRNG srng = new SecRNG();
public delegate bool ValidateServerCertificate(X509CertChain serverCertificates, bool handshakeSignatureValid);
public delegate X509Cert ClientCertificateRequest();
private ValidateServerCertificate certificateValidationCallback = null;
private ClientCertificateRequest clientCertificateRequestCallback = null;
private TLSEncryptionProvider tlsCrypt = null;
private AsyncAutoResetEvent negotiationCompleted = new AsyncAutoResetEvent();
public bool isConnected = false;
public bool isFaulted = false;
private bool trackHandshakes = true;
private byte[] hsbuf;
private int hsbufpos = 0;
private bool sendEncrypted = false;
private bool recieveEncrypted = false;
ulong seq_local = 0;
ulong seq_server = 0;
private int AvailableApplicationDataBytes = 0;
private byte[] AvailableApplicationData = null;
private bool serverHelloDoneRecieved = false;
private bool serverFinishedRecieved = false;
private const bool use_extended_master_secret = false;
private bool verify_server_signature = true;
private bool shutdownRequested = false;
public void Dispose()
{
shutdownRequested = true;
negotiationCompleted.Set();
if (tlsCrypt != null) tlsCrypt.Dispose();
if (remoteServerCertificate != null) remoteServerCertificate.Dispose();
if (serverCertChain != null) serverCertChain.Dispose();
sessionTicket = null;
tlsMasterSecret = null;
clientRandom = null;
serverRandom = null;
clientPrivateKey = null;
clientPublicKey = null;
versionb = null;
hsbuf = null;
AvailableApplicationData = null;
AvailableApplicationDataBytes = 0;
dataController = null;
}
public TLSRecordLayer(IDataController _dataController)
{
vers = preferredVersion;
this.dataController = _dataController;
this.versionb = GetVersionBytes(vers);
}
public TLSRecordLayer(IDataController _dataController, TLSVersion version = TLSVersion.TLS12)
{
vers = version;
this.dataController = _dataController;
this.versionb = GetVersionBytes(version);
}
public async ValueTask<bool> EstablishTLSConnection(string hostname, ValidateServerCertificate ValidateServerCertificateCallback, ClientCertificateRequest CertificateRequestCallback)
{
this.certificateValidationCallback = ValidateServerCertificateCallback;
this.clientCertificateRequestCallback = CertificateRequestCallback;
try
{
_ = HandshakeProcess(hostname);
await negotiationCompleted.WaitAsync().ConfigureAwait(false);
return isConnected;
}
catch (Exception e)
{
lastErrorString = e.Message;
isFaulted = true;
}
return false;
}
private async Task HandshakeProcess(string hostname)
{
try
{
StartLoggingHandshakes();
HandshakeRecord clienthello = CreateClientHello(hostname);
SendHandshakeRecord(clienthello);
FlushOutput();
while (!serverHelloDoneRecieved)
{
if (shutdownRequested) return;
if (!await ReadOne().ConfigureAwait(false)) return;
}
HandshakeRecord clientkex;
if(tlsEncryptedPremaster != null) clientkex = CreateClientPremasterExchange();
else clientkex = CreateClientKeyExchange();
SendHandshakeRecord(clientkex);
FlushOutput();
TLSRecord changeCipherSpec = GetClientChangeCipherSpec();
ScheduleSend(changeCipherSpec);
FlushOutput();
HandshakeRecord clientfinished = CreateClientFinishedRecord();
SendHandshakeRecord(clientfinished);
FlushOutput();
while (!serverFinishedRecieved)
{
if (shutdownRequested) return;
if (!await ReadOne().ConfigureAwait(false)) return;
}
isConnected = true;
negotiationCompleted.Set();
}
catch (Exception e)
{
isFaulted = true;
lastErrorString = e.Message;
negotiationCompleted.Set();
}
}
private HandshakeRecord CreateClientKeyExchange()
{
int pubkeylen = this.clientPublicKey.Length;
byte[] publicKeyData = new byte[pubkeylen + 1];
publicKeyData[0] = (byte)pubkeylen;
Buffer.BlockCopy(this.clientPublicKey, 0, publicKeyData, 1, pubkeylen);
return new HandshakeRecord()
{
Type = HandshakeRecord.HS_Client_KeyExchange,
Length = (uint)publicKeyData.Length,
Data = publicKeyData
};
}
private HandshakeRecord CreateClientPremasterExchange()
{
byte[] encryptedPremasterPlusLength = new byte[tlsEncryptedPremaster.Length + 2];
encryptedPremasterPlusLength[0] = (byte)((tlsEncryptedPremaster.Length >> 8) & 0xff);
encryptedPremasterPlusLength[1] = (byte)(tlsEncryptedPremaster.Length & 0xff);
Buffer.BlockCopy(tlsEncryptedPremaster, 0, encryptedPremasterPlusLength, 2, tlsEncryptedPremaster.Length);
return new HandshakeRecord() {
Type = HandshakeRecord.HS_Client_KeyExchange,
Length = (uint)encryptedPremasterPlusLength.Length,
Data = encryptedPremasterPlusLength
};
}
private HandshakeRecord CreateClientHello(string externalHost)
{
byte[] cliRandom = new byte[32];
byte[] cliTime = GetUnixTime();
Buffer.BlockCopy(cliTime, 0, cliRandom, 0, 4);
srng.GetRandomBytes(cliRandom, 4, 28);
this.clientRandom = cliRandom;
byte[] cipherSuite = GetCipherSuite(AllImplementedCipherSuites);
byte[] compressionMethods = new byte[] { 0x01, 0x00 };
byte[] extensions = GetExtensions(externalHost, use_extended_master_secret, preferredNamedCurve);
byte[] hellodata = Tools.JoinAll(versionb, cliRandom, new byte[1] {0}, cipherSuite, compressionMethods, extensions);
HandshakeRecord hsr = new HandshakeRecord()
{
Type = HandshakeRecord.HS_Client_Hello,
Length = (uint)hellodata.Length,
Data = hellodata
};
return hsr;
}
private HandshakeRecord CreateClientFinishedRecord()
{
//create verification data
byte[] allshakeshash = GetFinishedHash();
using(HMAC hm = cipherSuite.CreateHMAC(tlsMasterSecret))
{
byte[] verifyDat = null;
if(vers == TLSVersion.TLS12)
{
verifyDat = GenerateVerifyData(TLSData.label_clientfinished, allshakeshash, hm);
}
else
{
verifyDat = GenerateVerifyData11(TLSData.label_clientfinished, allshakeshash, tlsMasterSecret);
}
return new HandshakeRecord()
{
Type = HandshakeRecord.HS_Finished,
Length = (uint)verifyDat.Length,
Data = verifyDat
};
}
}
private void SendHandshakeRecord(HandshakeRecord hsr)
{
UpdateHandshakes(hsr);
TLSRecord tlsr = new TLSRecord(hsr, vers);
ScheduleSend(tlsr);
}
public Queue<HandshakeRecord> handshakeRecords = new Queue<HandshakeRecord>();
private async ValueTask<bool> ReadOne()
{
if (shutdownRequested) return false;
try
{
if(handshakeRecords.Count == 0)
{
TLSRecord tlsr = await ReadNextRecord().ConfigureAwait(false);
if(tlsr.Type == TLSRecord.TLSR_Handshake)
{
//Parse them here
byte[] hsdat = tlsr.Data;
int idx = 0;
int remain = hsdat.Length;
while(remain > 0)
{
byte[] header = new byte[4];
Buffer.BlockCopy(hsdat, idx, header, 0, 4); idx += 4;
int hskl = (header[1] << 16 | header[2] << 8 | header[3]);
if (hskl > remain) throw new TLSHandshakeException("Error parsing handshake messages");
byte[] fragment = new byte[hskl];
Buffer.BlockCopy(hsdat, idx, fragment, 0, hskl); idx += hskl;
remain -= hskl + 4;
HandshakeRecord shk = HandshakeRecord.Parse(header, fragment);
handshakeRecords.Enqueue(shk);
}
}
else if (tlsr.Type == TLSRecord.TLSR_ChangeSipherSpec)
{
recieveEncrypted = true;
}
}
if(handshakeRecords.TryDequeue(out HandshakeRecord hsr))
{
if (hsr.Type == HandshakeRecord.HS_Server_Hello)
{
ProcessServerHello(hsr);
}
else if (hsr.Type == HandshakeRecord.HS_Server_KeyExchange)
{
ProcessServerKeyExchange(hsr);
}
else if (hsr.Type == HandshakeRecord.HS_Certificate)
{
ProcessServerCertificate(hsr);
}
else if (hsr.Type == HandshakeRecord.HS_Server_HelloDone)
{
ProcessServerHelloDone(hsr);
}
else if (hsr.Type == HandshakeRecord.HS_NewSessionTicket)
{
ProcessServerNewSessionTicket(hsr);
}
else if (hsr.Type == HandshakeRecord.HS_Finished)
{
ProcessServerFinished(hsr);
}
UpdateHandshakes(hsr);
}
return true;
}
catch (Exception ex)
{
lastErrorString = ex.Message;
negotiationCompleted.Set();
}
return false;
}
private void ProcessServerHello(HandshakeRecord hsr)
{
ServerHello svh = ServerHello.Parse(hsr);
serverRandom = svh.serverRandom;
if (!preferredCipherSuites.Contains(svh.selectedCipher)) throw new TLSHandshakeException("Server attempting to use unsupported Ciphersuite");
cipherSuite = InitializeCipherSuite(svh.selectedCipher);
}
private void ProcessServerKeyExchange(HandshakeRecord hsr)
{
serverKeyExchangeInfo = KeyExchangeInfo.Parse(hsr.Data, vers);
}
private void ProcessServerCertificate(HandshakeRecord hsr)
{
if (!ParseServerCertificates(hsr)) throw new TLSHandshakeException("Unable to acquire server certificate");
}
private void ProcessServerHelloDone(HandshakeRecord hsr)
{
bool sigVerified = true;
if (verify_server_signature)
{
if (cipherSuite.tlsparams.keyExchangeAlgorithm != KeyExchangeAlgorithm.RSA) sigVerified = VerifyServerSignature();
if (certificateValidationCallback != null)
{
if (!certificateValidationCallback.Invoke(serverCertChain, sigVerified)) throw new TLSValidationException("Parent client rejected server certificate");
}
}
if (!CreateKeys()) throw new TLSEncryptionException("Failed to create encryption keys");
serverHelloDoneRecieved = true;
}
private void ProcessServerNewSessionTicket(HandshakeRecord hsr)
{
uint lifetime = 0;
lifetime |= (uint)(hsr.Data[0] << 24);
lifetime |= (uint)(hsr.Data[1] << 16);
lifetime |= (uint)(hsr.Data[2] << 8);
lifetime |= (uint)(hsr.Data[3]);
ushort ticketlen = 0;
ticketlen |= (ushort)(hsr.Data[4] << 8);
ticketlen |= (ushort)(hsr.Data[5]);
if (ticketlen > 8192) throw new TLSProtocolException("Session Ticket exceeds internal limit");
byte[] sessticket = new byte[ticketlen];
Buffer.BlockCopy(hsr.Data, 6, sessticket, 0, ticketlen);
this.sessionTicket = sessticket;
this.sessionTicketLifetime = lifetime;
}
private void ProcessServerFinished(HandshakeRecord hsr)
{
byte[] verifyData = hsr.Data;
//create verification data
byte[] allshakeshash = GetFinishedHash();
using(HMAC hm = cipherSuite.CreateHMAC(tlsMasterSecret))
{
byte[] expectedverifyDat = null;
if(vers == TLSVersion.TLS12)
{
expectedverifyDat = GenerateVerifyData(TLSData.label_serverfinished, allshakeshash, hm);
}
else
{
expectedverifyDat = GenerateVerifyData11(TLSData.label_serverfinished, allshakeshash, tlsMasterSecret);
}
if (!Tools.ArraysEqual(verifyData, expectedverifyDat)) throw new TLSHandshakeException("Failed to verify server finished message");
FinishLoggingHandshakes();
serverFinishedRecieved = true;
}
}
private bool CreateKeys()
{
try
{
byte[] premaster = null;
KeyExchangeAlgorithm exch = cipherSuite.tlsparams.keyExchangeAlgorithm;
if (exch == KeyExchangeAlgorithm.RSA)
{
//generate random premaster secret
premaster = new byte[48];
premaster[0] = versionb[0];
premaster[1] = versionb[1];
srng.GetRandomBytes(premaster, 2, 46);
RSAPublicKey rpk = remoteServerCertificate.GetRSAPublicKey();
tlsEncryptedPremaster = rpk.EncryptData(premaster);
}
else if (exch == KeyExchangeAlgorithm.ECDHE_RSA || exch == KeyExchangeAlgorithm.ECDHE_ECDSA || exch == KeyExchangeAlgorithm.ECDHE_PSK)
{
//first generate our keys for the key exchange, the public key will be sent on 'client key exchange'
GenerateClientKeys(preferredNamedCurve);
premaster = CalculateSharedSecret(clientPrivateKey, serverKeyExchangeInfo.publicKey, preferredNamedCurve);
}
else
{
//unsupported
throw new TLSProtocolException("Unsupported key exchange algorithm");
}
byte[] master = null;
tlsPremasterSecret = premaster;
//generate master secret
if(vers == TLSVersion.TLS12)
{
using (HMAC hm = cipherSuite.CreateHMAC(premaster))
{
master = GenerateMasterSecret(use_extended_master_secret, clientRandom, serverRandom, hm);
}
tlsMasterSecret = master;
using (HMAC hm = cipherSuite.CreateHMAC(tlsMasterSecret))
{
this.keyRing = PerformKeyExpansion(master, clientRandom, serverRandom, cipherSuite.tlsparams.HashSize, cipherSuite.tlsparams.BulkKeySize, cipherSuite.tlsparams.BulkIVSize, hm);
}
return true;
}
else if (vers == TLSVersion.TLS11)
{
master = GenerateMasterSecret11(premaster, clientRandom, serverRandom);
tlsMasterSecret = master;
this.keyRing = PerformKeyExpansionTLS11(master, clientRandom, serverRandom, cipherSuite.tlsparams.HashSize, cipherSuite.tlsparams.BulkKeySize, cipherSuite.tlsparams.BulkIVSize);
return true;
}
else
{
return false;
}
}
catch (Exception)
{
return false;
}
}
private void GenerateClientKeys(string curvename)
{
if(curvename == "x25519")
{
byte[] prirnd = new byte[32];
srng.GetRandomBytes(prirnd, 0, 32);
byte[] clipri = Curve25519.ClampPrivateKey(prirnd);
byte[] clipub = Curve25519.GetPublicKey(clipri);
clientPrivateKey = prirnd;
clientPublicKey = clipub;
return;
}
//define curve
ECCurve curve = ECCurve.CreateFromFriendlyName(curvename);
//generate master secret
using (ECDiffieHellman clientECDH = ECDiffieHellman.Create(curve))
{
ECParameters ecp = clientECDH.ExportParameters(true);
byte[] privateKey = ecp.D;
byte[] Qx = ecp.Q.X;
byte[] Qy = ecp.Q.Y;
byte[] cprivatekey = new byte[1 + privateKey.Length];
cprivatekey[0] = 0x04;
Buffer.BlockCopy(privateKey, 0, cprivatekey, 1, privateKey.Length);
byte[] cpublicKey = new byte[1 + Qx.Length + Qy.Length];
cpublicKey[0] = 0x04;
Buffer.BlockCopy(Qx, 0, cpublicKey, 1, Qx.Length);
Buffer.BlockCopy(Qy, 0, cpublicKey, 1 + Qx.Length, Qy.Length);
clientPrivateKey = cprivatekey;
clientPublicKey = cpublicKey;
}
}
private bool ParseServerCertificates(HandshakeRecord hsr)
{
int certificatesLen = 0;
certificatesLen |= hsr.Data[0] << 16;
certificatesLen |= hsr.Data[1] << 8;
certificatesLen |= hsr.Data[2];
byte[] certificatesData = new byte[certificatesLen];
Buffer.BlockCopy(hsr.Data, 3, certificatesData, 0, certificatesLen);
X509CertChain certchn = new X509CertChain();
int bufp = 0;
int idx = 0;
while (bufp < certificatesLen)
{
int nextCertLen = 0;
nextCertLen |= certificatesData[bufp++] << 16;
nextCertLen |= certificatesData[bufp++] << 8;
nextCertLen |= certificatesData[bufp++];
if (nextCertLen > TLSData.CertificateLengthLimit) throw new TLSProtocolException("Certificate[" + idx + "] exceeds bounds");
byte[] certDat = new byte[nextCertLen];
Buffer.BlockCopy(certificatesData, bufp, certDat, 0, nextCertLen);
bufp += nextCertLen;
X509Cert cert = Security.X509Cert.FromASN(certDat, 0);
certchn.AddLink(cert);
if (idx == 0)
{
remoteServerCertificate = cert;
}
idx++;
}
if (remoteServerCertificate != null)
{
this.serverCertChain = certchn;
return true;
}
return false;
}
private bool VerifyServerSignature()
{
try
{
byte[] pms = clientRandom.Concat(serverRandom).Concat(serverKeyExchangeInfo.edchParams).ToArray();
byte[] sig = serverKeyExchangeInfo.signature;
if(vers == TLSVersion.TLS12)
{
using (HashAlgorithm ha = cipherSuite.GetHasher())
{
byte[] dataHash = ha.ComputeHash(pms);
return cipherSuite.VerifyHash(dataHash, sig, remoteServerCertificate);
}
}
else
{
using (MD5 md5 = new MD5CryptoServiceProvider())
{
using(SHA1 sha1 = new SHA1CryptoServiceProvider())
{
byte[] md5Hash = md5.ComputeHash(pms);
byte[] sha1Hash = sha1.ComputeHash(pms);
byte[] hash = new byte[md5Hash.Length + sha1Hash.Length];
Buffer.BlockCopy(md5Hash, 0, hash, 0, md5Hash.Length);
Buffer.BlockCopy(sha1Hash, 0, hash, md5Hash.Length, sha1Hash.Length);
return cipherSuite.VerifyHash(hash, sig, remoteServerCertificate);
}
}
}
}
catch (Exception)
{
return false;
}
}
private TLSRecord GetClientChangeCipherSpec()
{
TLSRecord tlsr = new TLSRecord()
{
Type = 0x14,
Version = (ushort)(versionb[0] << 8 | versionb[1]),
Length = (ushort)0x01,
Data = new byte[] { 0x01 }
};
return tlsr;
}
private void StartLoggingHandshakes()
{
hsbuf = new byte[8192];
hsbufpos = 0;
trackHandshakes = true;
}
private void FinishLoggingHandshakes()
{
hsbuf = null;
hsbufpos = 0;
trackHandshakes = false;
}
private void UpdateHandshakes(HandshakeRecord hsr)
{
if (!trackHandshakes) return;
byte[] hshk = hsr.Serialize();
if (hsbufpos + hshk.Length > hsbuf.Length)
{
//this almost never happen
byte[] rsz = new byte[hsbuf.Length + 4096];
Buffer.BlockCopy(hsbuf, 0, rsz, 0, hsbufpos);
hsbuf = rsz;
}
Buffer.BlockCopy(hshk, 0, hsbuf, hsbufpos, hshk.Length);
hsbufpos += hshk.Length;
}
private byte[] GetFinishedHash()
{
byte[] hshdat = Tools.SubArray(hsbuf, 0, hsbufpos);
if(vers == TLSVersion.TLS12)
{
using (HashAlgorithm ha = SHA256.Create())
{
byte[] hshhash = ha.ComputeHash(hshdat);
return hshhash;
}
}
else
{
byte[] hash = new byte[36];
using (MD5 md5 = MD5.Create())
{
byte[] hshmd5 = md5.ComputeHash(hshdat);
Buffer.BlockCopy(hshmd5, 0, hash, 0, 16);
}
using (SHA1 sha1 = SHA1.Create())
{
byte[] hshsha1 = sha1.ComputeHash(hshdat);
Buffer.BlockCopy(hshsha1, 0, hash, 16, 20);
}
return hash;
}
}
private void InitializeEncryption()
{
tlsCrypt = cipherSuite.InitializeEncryption(this.keyRing);
}
private void ScheduleSend(TLSRecord tlso, TaskCompletionSource _tcs = null)
{
if (shutdownRequested) return;
if (sendEncrypted)
{
tlso = Encryption.EncryptRecord(tlso, tlsCrypt, seq_local);
seq_local++;
}
if (tlso.Type == TLSRecord.TLSR_ChangeSipherSpec)
{
//we are changing to an encrypted state
InitializeEncryption();
sendEncrypted = true;
seq_local = 0;
}
dataController.QueueData(new DataDispatch()
{
data = tlso.Serialize(),
tcs = _tcs
});
}
private void FlushOutput()
{
dataController.FlushData();
}
private ValueTask<byte[]> AskForData(int len)
{
TaskCompletionSource<byte[]> dtcs = new TaskCompletionSource<byte[]>();
DataRequest dr = new DataRequest()
{
length = len,
tcs = dtcs
};
dataController.RequestData(dr);
return new ValueTask<byte[]>(dtcs.Task);
}
private async ValueTask<TLSRecord> ReadNextRecord()
{
byte[] header = await AskForData(5).ConfigureAwait(false);
if (header.Length != 5) throw new TLSNetworkException("Failed while reading TLS record");
byte _type = header[0];
ushort _version = (ushort)(header[2] | (header[1] << 8));
ushort _recordLength = (ushort)(header[4] | (header[3] << 8));
if (_recordLength > TLSData.RecordLengthLimit)
{
//throw new TLSProtocolException("Record exceeds length limits");
}
byte[] _data = await AskForData(_recordLength).ConfigureAwait(false);
if (_type == 0x15)
{
ushort _alertLen = (ushort)(header[4] | (header[3] << 8));
bool fatal = (_data[0] == 2);
if (fatal)
{
throw new TLSRecordException("TLS Alert fatal: " + _data[1].ToString("X2"));
}
}
TLSRecord tlsr = new TLSRecord()
{
Type = _type,
Version = _version,
Length = _recordLength,
Data = _data
};
if (recieveEncrypted) tlsr = Encryption.DecryptRecord(tlsr, tlsCrypt, seq_server);
seq_server++;
if(tlsr.Type == TLSRecord.TLSR_ChangeSipherSpec)
{
seq_server = 0;
}
return tlsr;
}
public async ValueTask<int> ReadApplicationDataAsync(byte[] dest, int offset, int length)
{
if (shutdownRequested) return -1;
try
{
int read = 0;
if (AvailableApplicationDataBytes > 0)
{
if (AvailableApplicationDataBytes > length)
{
//we have more than needed
Buffer.BlockCopy(AvailableApplicationData, 0, dest, offset, length);
read += length;
byte[] remain = new byte[AvailableApplicationDataBytes - length];
Buffer.BlockCopy(AvailableApplicationData, length, remain, 0, remain.Length);
AvailableApplicationData = remain;
AvailableApplicationDataBytes -= length;
return read;
}
else
{
//we can exhaust our buffer, but we require more
int copylen = AvailableApplicationDataBytes;
Buffer.BlockCopy(AvailableApplicationData, 0, dest, offset, copylen);
read += copylen;
AvailableApplicationData = null;
AvailableApplicationDataBytes = 0;
offset += copylen;
length -= copylen;
if (length <= 0) return read;
}
}
TLSRecord next = await ReadNextRecord().ConfigureAwait(false);
if (next.Type == TLSRecord.TLSR_Alert)
{
if(next.Data.Length >= 2)
{
if (next.Data[1] == 0)
{
//close notify
return read;
}
}
}
if (next.Type == TLSRecord.TLSR_ApplicationData)
{
byte[] newApplicationData = next.Data;
if (newApplicationData.Length > length)
{
Buffer.BlockCopy(newApplicationData, 0, dest, offset, length);
byte[] remain = new byte[newApplicationData.Length - length];
Buffer.BlockCopy(newApplicationData, length, remain, 0, remain.Length);
AvailableApplicationData = remain;
AvailableApplicationDataBytes = remain.Length;
read += length;
}
else
{
//give them what we can, no need to save any available data
Buffer.BlockCopy(newApplicationData, 0, dest, offset, newApplicationData.Length);
read += newApplicationData.Length;
}
return read;
}
else
{
return -1;
}
}
catch (Exception ex) { }
return -1;
}
public ValueTask SendApplicationDataAsync(byte[] data)
{
if (shutdownRequested) return new ValueTask();
int datlen = data.Length;
int limit = 16384 - 5 - 20 - 100;
limit = preferredFragmentSize;
if (datlen > limit)
{
int bpos = 0;
int remain = datlen;
while (remain > 0)
{
int copysize = preferredFragmentSize;
if (copysize > remain) copysize = remain;
byte[] recdat = new byte[copysize];
Buffer.BlockCopy(data, bpos, recdat, 0, copysize);
bpos += copysize;
remain -= copysize;
TLSRecord fragrec = new TLSRecord(recdat, vers);
if (remain <= 0)
{
//if this is the last in the sequence
TaskCompletionSource tcs = new TaskCompletionSource();
ScheduleSend(fragrec, tcs);
FlushOutput();
return new ValueTask(tcs.Task);
}
else
{
ScheduleSend(fragrec);
}
}
}
else
{
TaskCompletionSource tcs = new TaskCompletionSource();
TLSRecord apld = new TLSRecord(data, vers);
ScheduleSend(apld, tcs);
FlushOutput();
return new ValueTask(tcs.Task);
}
return new ValueTask(Task.FromException(new Exception("Failure during dispatch")));
}
}
}

187
TLS/TlsStream.cs Normal file
View file

@ -0,0 +1,187 @@
/*
* 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.Shared;
using SecuCore.TLS;
using SecuCore.TLS.Exceptions;
using System.Threading;
using System.Threading.Tasks;
using System;
namespace SecuCore.Sockets
{
public class TlsStream
{
ApplicationDataController applicationDataController;
TLSNetworkController networkController;
TLSRecordLayer recordLayer;
private int availablebytes = 0;
private byte[] availabledat = null;
public TlsStream(TCPSocket tcps)
{
networkController = new TLSNetworkController(tcps);
recordLayer = new TLSRecordLayer(networkController);
applicationDataController = new ApplicationDataController(recordLayer, 8192);
}
public TlsStream(TCPSocket tcps, TLSVersion version, int applicationReadBufferSize = 8192)
{
networkController = new TLSNetworkController(tcps);
recordLayer = new TLSRecordLayer(networkController, version);
applicationDataController = new ApplicationDataController(recordLayer, applicationReadBufferSize);
}
public Task<bool> AuthenticateAsClientAsync(string target, TLSRecordLayer.ValidateServerCertificate validateServerCertificateCallback, TLSRecordLayer.ClientCertificateRequest clientCertificateRequestCallback)
{
return recordLayer.EstablishTLSConnection(target, validateServerCertificateCallback, clientCertificateRequestCallback).AsTask();
}
private Task<byte[]> AskForDataAsync(int len)
{
TaskCompletionSource<byte[]> dtcs = new TaskCompletionSource<byte[]>();
DataRequest dr = new DataRequest()
{
length = len,
tcs = dtcs
};
applicationDataController.RequestData(dr);
return dtcs.Task;
}
public static async Task<T> TimeoutAfter<T>(Task<T> task, int millisecondsTimeout)
{
using(CancellationTokenSource cts = new CancellationTokenSource())
{
if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout, cts.Token)).ConfigureAwait(false))
{
cts.Cancel();
return await task;
}
else
{
cts.Cancel();
throw new TLSNetworkException("operation timed out");
}
}
}
private async ValueTask WriteInternalAsync(byte[] buffer)
{
TaskCompletionSource dtcs = null;
dtcs = new TaskCompletionSource();
DataDispatch datd = new DataDispatch()
{
data = buffer,
tcs = dtcs
};
applicationDataController.QueueData(datd);
applicationDataController.FlushData();
await dtcs.Task.ConfigureAwait(false);
}
private async ValueTask<int> ReadInternal(byte[] buffer, int offset, int len)
{
try
{
int read = 0;
if (availablebytes == 0)
{
byte[] recieve = null;
try
{
recieve = await AskForDataAsync(-1).ConfigureAwait(false);
}
catch (Exception ex) { }
if (recieve != null)
{
if (recieve.Length > 0)
{
availabledat = recieve;
availablebytes = recieve.Length;
}
else
{
return -1;
}
}
else
{
return -1;
}
}
if (availablebytes > len)
{
//we have more than needed
Buffer.BlockCopy(availabledat, 0, buffer, offset, len);
read += len;
byte[] remain = new byte[availablebytes - len];
Buffer.BlockCopy(availabledat, len, remain, 0, remain.Length);
availabledat = remain;
availablebytes -= len;
return read;
}
else
{
//we can exhaust our buffer
int copylen = availablebytes;
Buffer.BlockCopy(availabledat, 0, buffer, offset, copylen);
read += copylen;
availabledat = null;
availablebytes = 0;
offset += copylen;
len -= copylen;
}
return read;
}
catch(Exception ex)
{
return -1;
}
}
public ValueTask<int> ReadAsync(byte[] buffer, int offset, int count)
{
return ReadInternal(buffer, offset, count);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken))
{
if (offset == 0 && count == buffer.Length)
{
return WriteInternalAsync(buffer).AsTask();
}
byte[] localBuffer = new byte[count];
Buffer.BlockCopy(buffer, offset, localBuffer, 0, count);
return WriteInternalAsync(localBuffer).AsTask();
}
public Task WriteAsync(byte[] buffer, CancellationToken cancellationToken)
{
return WriteInternalAsync(buffer).AsTask();
}
public Task WriteAsync(byte[] buffer)
{
return WriteInternalAsync(buffer).AsTask();
}
public async ValueTask DisposeAsync()
{
recordLayer.Dispose();
networkController.Dispose();
applicationDataController.Shutdown(false);
recordLayer = null;
networkController = null;
applicationDataController = null;
}
}
}