Initial commit
This commit is contained in:
commit
5584446828
37 changed files with 7962 additions and 0 deletions
262
TLS/ApplicationDataController.cs
Normal file
262
TLS/ApplicationDataController.cs
Normal 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
375
TLS/CipherSuites.cs
Normal 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
648
TLS/Curves/Curve25519.cs
Normal 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
537
TLS/Curves/Ed25519.cs
Normal 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
130
TLS/Encryption.cs
Normal 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
58
TLS/Exceptions.cs
Normal 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
282
TLS/KeyDerivation.cs
Normal 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
360
TLS/TLS.cs
Normal 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
250
TLS/TLSNetworkController.cs
Normal 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
897
TLS/TLSRecordLayer.cs
Normal 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
187
TLS/TlsStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue