Initial commit
This commit is contained in:
commit
5584446828
37 changed files with 7962 additions and 0 deletions
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
540
Clients/HTTPClient.cs
Normal file
540
Clients/HTTPClient.cs
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
/*
|
||||
* 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.Proxies;
|
||||
using System.Text;
|
||||
using SecuCore.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO.Compression;
|
||||
using System.IO;
|
||||
|
||||
namespace SecuCore.Clients
|
||||
{
|
||||
class HTTPCookie
|
||||
{
|
||||
public string Name;
|
||||
public string Value;
|
||||
public string Domain;
|
||||
public string Path;
|
||||
public DateTime Expires;
|
||||
}
|
||||
public class HTTPClient
|
||||
{
|
||||
public string UA = "SecuCore/1.0 HTTPClient";
|
||||
public string LastHeaders { get { return _lastHeaders; } }
|
||||
public string LastRedirect { get { return _lastHeaders; } }
|
||||
|
||||
private Proxy _proxy;
|
||||
private TCPSocket _tcpSocket;
|
||||
private TCPNetworkStream _tcpns;
|
||||
private SecuredTCPStream _stcps;
|
||||
private string _lastErrorText = "";
|
||||
private string _lastHeaders = "";
|
||||
private string _lastRedirect = "";
|
||||
private List<HTTPCookie> _cookies;
|
||||
private bool _connectedWithTLS = false;
|
||||
public string LastError { get { return _lastErrorText; } }
|
||||
|
||||
public int ConnectTimeout
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = 10000;
|
||||
public int WriteTimeout
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = 10000;
|
||||
public int ReadTimeout
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = 10000;
|
||||
|
||||
private int _wsrbs = 8192;
|
||||
private int _wssbs = 8192;
|
||||
public int WsaSockRecieveBufSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _wsrbs;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_wsrbs != value)
|
||||
{
|
||||
_wsrbs = value;
|
||||
if (_tcpSocket != null)
|
||||
{
|
||||
_tcpSocket.SetWSASockBufferSizes(_wsrbs, _wssbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public int WsaSockSendBufSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _wssbs;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_wssbs != value)
|
||||
{
|
||||
_wssbs = value;
|
||||
_tcpSocket?.SetWSASockBufferSizes(_wsrbs, _wssbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TLS.TLSRecordLayer.ValidateServerCertificate validationCallback;
|
||||
public TLS.TLSRecordLayer.ClientCertificateRequest certificateRequestCallback;
|
||||
|
||||
public HTTPClient(string proxy = "", ProxyProtocol proxyProtocol = ProxyProtocol.HTTP)
|
||||
{
|
||||
_cookies = new List<HTTPCookie>();
|
||||
if (!string.IsNullOrEmpty(proxy)) this._proxy = new Proxy(proxy, proxyProtocol);
|
||||
}
|
||||
public void SetCallbacks(TLS.TLSRecordLayer.ValidateServerCertificate valCallback, TLS.TLSRecordLayer.ClientCertificateRequest certReqCallback)
|
||||
{
|
||||
this.validationCallback = valCallback;
|
||||
this.certificateRequestCallback = certReqCallback;
|
||||
}
|
||||
|
||||
public static string UrlEncode(string value)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
foreach (char symbol in value)
|
||||
{
|
||||
if ((symbol >= '0' && symbol <= '9') ||
|
||||
(symbol >= 'a' && symbol <= 'z') ||
|
||||
(symbol >= 'A' && symbol <= 'Z') ||
|
||||
symbol == '-' || symbol == '_')
|
||||
{
|
||||
result.Append(symbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append('%' + string.Format("{0:X2}", (int)symbol));
|
||||
}
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
public string BuildGETRequest(string host, string path)
|
||||
{
|
||||
List<(string Name, string Value)> headers = new List<(string Name, string Value)>
|
||||
{
|
||||
("Host", host),
|
||||
("Connection", "keep-alive"),
|
||||
("User-Agent", UA),
|
||||
("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
|
||||
("Accept-Encoding", "gzip")
|
||||
};
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(string.Format("GET {0} HTTP/1.1\r\n", path));
|
||||
|
||||
StringBuilder csb = new StringBuilder();
|
||||
foreach (HTTPCookie hc in _cookies)
|
||||
{
|
||||
if (hc.Domain.EndsWith(host))
|
||||
{
|
||||
if (path.StartsWith(hc.Path))
|
||||
{
|
||||
csb.Append(String.Format("{0}={1}", hc.Name, hc.Value));
|
||||
if (hc != _cookies.Last()) csb.Append("; ");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (csb.Length > 0) headers.Add(("Cookie", csb.ToString()));
|
||||
foreach ((string Name, string Value) in headers)
|
||||
{
|
||||
sb.Append(string.Format("{0}: {1}\r\n", Name, Value));
|
||||
}
|
||||
sb.Append("\r\n");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string BuildPOSTRequest(string host, string path, List<(string,string)> postvals)
|
||||
{
|
||||
|
||||
StringBuilder psb = new StringBuilder();
|
||||
foreach((string,string) nv in postvals)
|
||||
{
|
||||
psb.Append(String.Format("{0}={1}", UrlEncode(nv.Item1), UrlEncode(nv.Item2)));
|
||||
if (nv != postvals.Last()) psb.Append("&");
|
||||
}
|
||||
string urlencoded_postdata = psb.ToString();
|
||||
|
||||
List<(string, string)> headers = new List<(string, string)>
|
||||
{
|
||||
("Host", host),
|
||||
("Connection", "keep-alive"),
|
||||
("User-Agent", UA),
|
||||
("Content-Type", "application/x-www-form-urlencoded"),
|
||||
("Content-Length", urlencoded_postdata.Length.ToString()),
|
||||
("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
|
||||
("Accept-Encoding", "gzip")
|
||||
};
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(string.Format("POST {0} HTTP/1.1\r\n", path));
|
||||
|
||||
StringBuilder csb = new StringBuilder();
|
||||
foreach (HTTPCookie hc in _cookies)
|
||||
{
|
||||
if (hc.Domain.EndsWith(host))
|
||||
{
|
||||
if (path.StartsWith(hc.Path))
|
||||
{
|
||||
csb.Append(String.Format("{0}={1}", hc.Name, hc.Value));
|
||||
if (hc != _cookies.Last()) csb.Append("; ");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (csb.Length > 0) headers.Add(("Cookie", csb.ToString()));
|
||||
foreach ((string,string) hv in headers)
|
||||
{
|
||||
sb.Append(string.Format("{0}: {1}\r\n", hv.Item1, hv.Item2));
|
||||
}
|
||||
sb.Append("\r\n");
|
||||
sb.Append(urlencoded_postdata);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void ParseCookies()
|
||||
{
|
||||
//parse cookies
|
||||
MatchCollection cookmc = Regex.Matches(_lastHeaders, @"[Ss]et\-[Cc]ookie:\ ?([^\r\n]+)");
|
||||
if (cookmc.Count > 0)
|
||||
{
|
||||
foreach (Match cm in cookmc)
|
||||
{
|
||||
string cookievalues = cm.Groups[1].Value;
|
||||
string[] cookiepairs = cookievalues.Split(';');
|
||||
HTTPCookie httpc = new HTTPCookie();
|
||||
httpc.Expires = DateTime.Now.AddYears(1);
|
||||
bool cookievalid = true;
|
||||
foreach (string cookpair in cookiepairs)
|
||||
{
|
||||
string trimmed = cookpair.Trim();
|
||||
if (trimmed.Length > 0)
|
||||
{
|
||||
if (cookpair == cookiepairs.First())
|
||||
{
|
||||
//name and value
|
||||
if (trimmed.Contains("="))
|
||||
{
|
||||
httpc.Name = trimmed.Substring(0, trimmed.IndexOf("="));
|
||||
httpc.Value = trimmed.Substring(trimmed.IndexOf("=") + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
cookievalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string trimmedtl = trimmed.ToLower();
|
||||
if (trimmed.Contains("="))
|
||||
{
|
||||
if (trimmedtl.StartsWith("domain="))
|
||||
{
|
||||
httpc.Domain = trimmed.Substring(trimmed.IndexOf("=") + 1);
|
||||
}
|
||||
else if (trimmedtl.StartsWith("expires="))
|
||||
{
|
||||
DateTime.TryParse(trimmed.Substring(trimmed.IndexOf("=") + 1), out httpc.Expires);
|
||||
}
|
||||
else if (trimmedtl.StartsWith("path="))
|
||||
{
|
||||
httpc.Path = trimmed.Substring(trimmed.IndexOf("=") + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cookievalid)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (HTTPCookie cookie in _cookies)
|
||||
{
|
||||
if (cookie.Name == httpc.Name && cookie.Domain == httpc.Domain)
|
||||
{
|
||||
//replace the value
|
||||
cookie.Value = httpc.Value;
|
||||
cookie.Path = httpc.Path;
|
||||
cookie.Expires = httpc.Expires;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) _cookies.Add(httpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<string> GetAsync(string url, string referer)
|
||||
{
|
||||
return RequestAsync(url, referer, false, null);
|
||||
}
|
||||
public Task<string> PostAsync(string url, string referer, List<(string, string)> postvals)
|
||||
{
|
||||
return RequestAsync(url, referer, true, postvals);
|
||||
}
|
||||
public async Task<string> RequestAsync(string url, string referer, bool post, List<(string, string)> postvals)
|
||||
{
|
||||
_lastRedirect = "";
|
||||
//parse destination url
|
||||
try
|
||||
{
|
||||
Match m = Regex.Match(url, @"(https?)\:\/\/([^\:\/\r\n]+)(\/[^\r\n\ ]+)?");
|
||||
if(m.Success)
|
||||
{
|
||||
string protocol = m.Groups[1].Value;
|
||||
string host = m.Groups[2].Value;
|
||||
string path = m.Groups[3].Value;
|
||||
if(string.IsNullOrEmpty(path)) path = "/";
|
||||
|
||||
int destprt = (protocol == "https" ? 443 : 80);
|
||||
if(!await ConnectAsync(host, destprt).ConfigureAwait(false))
|
||||
{
|
||||
_lastErrorText = "Failed to connect";
|
||||
return "";
|
||||
}
|
||||
string request;
|
||||
if(post)
|
||||
{
|
||||
request = BuildPOSTRequest(host, path, postvals);
|
||||
}
|
||||
else
|
||||
{
|
||||
request = BuildGETRequest(host, path);
|
||||
}
|
||||
|
||||
|
||||
INetStream ins = (_connectedWithTLS ? _stcps : _tcpns);
|
||||
byte[] requestdata = System.Text.Encoding.UTF8.GetBytes(request);
|
||||
await ins.WriteAsync(requestdata, 0, requestdata.Length).ConfigureAwait(false);
|
||||
|
||||
_lastHeaders = System.Text.Encoding.UTF8.GetString(await ins.ReadUntilCRLFCRLF().ConfigureAwait(false));
|
||||
int contentLength = 0;
|
||||
Match clm = Regex.Match(_lastHeaders, @"[Cc]ontent-[Ll]ength\: (\d+)");
|
||||
if(clm.Success)
|
||||
{
|
||||
string clenStr = clm.Groups[1].Value;
|
||||
int.TryParse(clenStr, out contentLength);
|
||||
}
|
||||
|
||||
//check for redirect
|
||||
Match rlm = Regex.Match(_lastHeaders, @"[Ll]ocation:\ ?([^\r\n]+)");
|
||||
if(rlm.Success) _lastRedirect = rlm.Groups[1].Value;
|
||||
if (_lastRedirect == url) _lastRedirect = null;
|
||||
|
||||
//parse cookies
|
||||
ParseCookies();
|
||||
bool chunked = _lastHeaders.ToLower().Contains("transfer-encoding: chunked");
|
||||
bool gzip = _lastHeaders.ToLower().Contains("content-encoding: gzip");
|
||||
|
||||
//get response body if any
|
||||
string content = null;
|
||||
if (contentLength >= 0)
|
||||
{
|
||||
byte[] rbuf = new byte[contentLength];
|
||||
int rlen = await ins.ReadAsync(rbuf, 0, contentLength).ConfigureAwait(false);
|
||||
if (rlen > 0)
|
||||
{
|
||||
byte[] responseData = new byte[rlen];
|
||||
Buffer.BlockCopy(rbuf, 0, responseData, 0, rlen);
|
||||
if (gzip)
|
||||
{
|
||||
byte[] decompressed = Decompress(responseData);
|
||||
content = Encoding.UTF8.GetString(decompressed, 0, decompressed.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = Encoding.UTF8.GetString(responseData, 0, responseData.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_lastRedirect))
|
||||
{
|
||||
if (!_lastRedirect.ToLower().StartsWith("http")) _lastRedirect = protocol + "://" + host + _lastRedirect;
|
||||
return await GetAsync(_lastRedirect, url).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chunked)
|
||||
{
|
||||
using (MemoryStream chunk_ms = new MemoryStream())
|
||||
{
|
||||
string nextchunkhex = System.Text.Encoding.UTF8.GetString(await ins.ReadUntilCRLF().ConfigureAwait(false));
|
||||
if (nextchunkhex.Length < 0) return "";
|
||||
int nextchunki = Convert.ToInt32("0x" + nextchunkhex.Substring(0, nextchunkhex.Length - 2), 16);
|
||||
nextchunki += 2;
|
||||
while (nextchunki > 0)
|
||||
{
|
||||
byte[] rbuf = new byte[nextchunki];
|
||||
int cpos = 0;
|
||||
int remaining = nextchunki;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int bytesread = await ins.ReadAsync(rbuf, cpos, remaining);
|
||||
if (bytesread <= 0) break;
|
||||
remaining -= bytesread;
|
||||
cpos += bytesread;
|
||||
}
|
||||
if (cpos <= 0) break;
|
||||
byte[] chunkdata = new byte[nextchunki - 2];
|
||||
Buffer.BlockCopy(rbuf, 0, chunkdata, 0, nextchunki - 2);
|
||||
chunk_ms.Write(chunkdata, 0, chunkdata.Length);
|
||||
nextchunki = 0;
|
||||
|
||||
byte[] nextchunkb = await ins.ReadUntilCRLF().ConfigureAwait(false);
|
||||
if (nextchunkb != null)
|
||||
{
|
||||
nextchunkhex = System.Text.Encoding.UTF8.GetString(nextchunkb, 0, nextchunkb.Length);
|
||||
|
||||
string nexthvstr = nextchunkhex.Substring(0, nextchunkhex.Length);
|
||||
nextchunki = Convert.ToInt32("0x" + nexthvstr.Substring(0, nexthvstr.Length - 2), 16);
|
||||
if (nextchunki == 0) break;
|
||||
if (nextchunki > 0) nextchunki += 2;
|
||||
}
|
||||
}
|
||||
byte[] responseData = chunk_ms.ToArray();
|
||||
if (gzip)
|
||||
{
|
||||
byte[] decompressed = Decompress(responseData);
|
||||
return Encoding.UTF8.GetString(decompressed, 0, decompressed.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encoding.UTF8.GetString(responseData, 0, responseData.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (Exception ex) {
|
||||
_lastErrorText = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if(_tcpSocket != null) _tcpSocket.Dispose();
|
||||
}
|
||||
catch (Exception ex) { }
|
||||
_tcpSocket = null;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
static byte[] Decompress(byte[] gzip)
|
||||
{
|
||||
using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
|
||||
{
|
||||
const int size = 4096;
|
||||
byte[] buffer = new byte[size];
|
||||
using (MemoryStream memory = new MemoryStream())
|
||||
{
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
count = stream.Read(buffer, 0, size);
|
||||
if (count > 0)
|
||||
{
|
||||
memory.Write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
while (count > 0);
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync(string remotehost, int destprt)
|
||||
{
|
||||
if(_tcpSocket != null)
|
||||
{
|
||||
if (_tcpSocket.Connected()) _tcpSocket.Dispose();
|
||||
}
|
||||
if (_tcpSocket == null)
|
||||
{
|
||||
_tcpSocket = new TCPSocket(remotehost, destprt, this._proxy, this.WsaSockRecieveBufSize, this.WsaSockSendBufSize);
|
||||
}
|
||||
_tcpSocket.ConnectTimeout = this.ConnectTimeout;
|
||||
_tcpSocket.WriteTimeout = this.WriteTimeout;
|
||||
_tcpSocket.ReadTimeout = this.ReadTimeout;
|
||||
if (!await _tcpSocket.ConnectAsync().ConfigureAwait(false)) return false;
|
||||
_tcpns = _tcpSocket.GetStream();
|
||||
|
||||
if (destprt == 443)
|
||||
{
|
||||
SecuredTCPStream stcps = new SecuredTCPStream(_tcpSocket);
|
||||
this._stcps = stcps;
|
||||
_connectedWithTLS = false;
|
||||
try
|
||||
{
|
||||
_connectedWithTLS = await stcps.NegotiateTLSConnection(remotehost, this.validationCallback, this.certificateRequestCallback).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception){}
|
||||
if (_connectedWithTLS) return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task Dispose()
|
||||
{
|
||||
if (_stcps != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _stcps.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
if (_tcpns != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcpns.Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
if (_tcpSocket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcpSocket.Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
459
DNS/AsyncDNSResolver.cs
Normal file
459
DNS/AsyncDNSResolver.cs
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.DNS
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static void WriteBigEndian16(this BinaryWriter bw, ushort littleEndian)
|
||||
{
|
||||
ushort swapped = (ushort)((littleEndian >> 8) | ((littleEndian & 255) << 8));
|
||||
bw.Write(swapped);
|
||||
}
|
||||
}
|
||||
public static class AsyncDNSResolver
|
||||
{
|
||||
//Uses Google Public DNS by Default
|
||||
private static readonly IPAddress[] DNSServers = new IPAddress[]
|
||||
{
|
||||
IPAddress.Parse("8.8.8.8"), IPAddress.Parse("8.8.4.4")
|
||||
};
|
||||
|
||||
public static int Timeout { get; set; } = 120000;
|
||||
private const int minPayloadSize = 17;
|
||||
private const int maxPayloadSize = 2048;
|
||||
|
||||
//Connects utilizing DNS through TCP, Port 53
|
||||
private async static Task<bool> ConnectDNS(this Socket socket, IPAddress ip)
|
||||
{
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(ip, 53).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{ }
|
||||
return socket.Connected;
|
||||
}
|
||||
|
||||
|
||||
//The Primary Sending/Recieving Functions for this class
|
||||
private async static Task<int> Send(Socket socket, byte[] buf)
|
||||
{
|
||||
return await Task<int>.Factory.FromAsync(socket.BeginSend(buf, 0, buf.Length, SocketFlags.None, null, null), socket.EndSend).ConfigureAwait(false);
|
||||
}
|
||||
private async static Task<int> Recieve(Socket socket, byte[] buf)
|
||||
{
|
||||
int result = 0;
|
||||
try
|
||||
{
|
||||
result = await socket.ReceiveAsync(buf, SocketFlags.None).WithTimeout(TimeSpan.FromMilliseconds(Timeout)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{ }
|
||||
return result;
|
||||
}
|
||||
|
||||
public async static Task<string> GetFirstMXrecord(string domain)
|
||||
{
|
||||
string[] mxr = await GetMXrecords(domain).ConfigureAwait(false);
|
||||
if (mxr.Length > 0)
|
||||
return mxr[0];
|
||||
return "";
|
||||
}
|
||||
public async static Task<string> GetFirstArecord(string domain)
|
||||
{
|
||||
string[] mxr = await GetArecords(domain).ConfigureAwait(false);
|
||||
if (mxr.Length > 0)
|
||||
return mxr[0];
|
||||
return "";
|
||||
}
|
||||
|
||||
public static Task<string[]> GetMXrecords(string domain)
|
||||
{
|
||||
return GetByHost(domain, QueryType.MX);
|
||||
}
|
||||
public static Task<string[]> GetArecords(string domain)
|
||||
{
|
||||
return GetByHost(domain, QueryType.A);
|
||||
}
|
||||
public static Task<string[]> GetAAAArecords(string domain)
|
||||
{
|
||||
return GetByHost(domain, QueryType.AAAA);
|
||||
}
|
||||
public static Task<string[]> GetTXTrecords(string domain)
|
||||
{
|
||||
return GetByHost(domain, QueryType.TXT);
|
||||
}
|
||||
private static readonly List<ushort> transactionids = new();
|
||||
private static SemaphoreSlim tsem = new SemaphoreSlim(1);
|
||||
private static async Task<ushort> GetTransactionID()
|
||||
{
|
||||
await tsem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
ushort result = 0;
|
||||
while (true)
|
||||
{
|
||||
ushort tID = RandomNumbers.GetNext16();
|
||||
if (!transactionids.Contains(tID))
|
||||
{
|
||||
transactionids.Add(tID);
|
||||
if (transactionids.Count > 10000) transactionids.RemoveAt(0);
|
||||
result = tID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return RandomNumbers.GetNext16();
|
||||
}
|
||||
finally
|
||||
{
|
||||
tsem.Release();
|
||||
}
|
||||
}
|
||||
private async static Task ReturnTransactionID(ushort utid)
|
||||
{
|
||||
await tsem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (transactionids.Contains(utid))
|
||||
{
|
||||
transactionids.Remove(utid);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{ }
|
||||
tsem.Release();
|
||||
}
|
||||
public static bool ResultWasWithin(int value, ref int destination, int min, int max)
|
||||
{
|
||||
if (value >= min & value <= max)
|
||||
{
|
||||
destination = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private async static Task<string[]> GetByHost(string domain, QueryType qt)
|
||||
{
|
||||
using Socket dnsSock = new(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
if (!await dnsSock.ConnectDNS(DNSServers[RandomNumbers.GetNext(0, DNSServers.Length - 1)]).ConfigureAwait(false)) return null; // could not establish connection
|
||||
|
||||
ushort transactionID = await GetTransactionID().ConfigureAwait(false);
|
||||
byte[] payload = null;
|
||||
if (!DNSPacket.CreateRecursiveQuestionPayload(transactionID, domain, qt, ref payload))
|
||||
{
|
||||
await ReturnTransactionID(transactionID).ConfigureAwait(false);
|
||||
return null; // internal error attempting to create query
|
||||
}
|
||||
if (await Send(dnsSock, payload).ConfigureAwait(false) == 0)
|
||||
{
|
||||
await ReturnTransactionID(transactionID).ConfigureAwait(false);
|
||||
return null; // error sending query
|
||||
}
|
||||
byte[] rBuf = new byte[maxPayloadSize];
|
||||
int len = 0;
|
||||
if (!ResultWasWithin(await Recieve(dnsSock, rBuf).ConfigureAwait(false), ref len, minPayloadSize, maxPayloadSize))
|
||||
{
|
||||
await ReturnTransactionID(transactionID).ConfigureAwait(false);
|
||||
return null; // result was malformed
|
||||
}
|
||||
Array.Resize<byte>(ref rBuf, len); // prune the buffer
|
||||
await ReturnTransactionID(transactionID).ConfigureAwait(false);
|
||||
DNSPacket dnsResponse = null;
|
||||
if (!DNSPacket.ParseResponse(rBuf, ref dnsResponse)) return null; // failed to parse response, or no answers were contained
|
||||
return dnsResponse.GetAnswers(qt);
|
||||
}
|
||||
}
|
||||
public enum QueryType
|
||||
{
|
||||
A = 1,
|
||||
AAAA = 28,
|
||||
CNAME = 5,
|
||||
MX = 15,
|
||||
NS = 2,
|
||||
PTR = 12,
|
||||
TXT = 16
|
||||
}
|
||||
public class DNSPacket
|
||||
{
|
||||
public ushort transactionID;
|
||||
public ushort flags;
|
||||
public ushort questions;
|
||||
public ushort answerRRs;
|
||||
public ushort authorityRRs;
|
||||
public ushort additionalRRs;
|
||||
public List<DNSQuestion> queries = new();
|
||||
public List<DNSAnswer> answers = new();
|
||||
public DNSPacket(byte[] buf)
|
||||
{
|
||||
this.ParseBuffer(buf);
|
||||
}
|
||||
public DNSPacket(ushort transactionID)
|
||||
{
|
||||
this.transactionID = transactionID;
|
||||
}
|
||||
private DNSPacket()
|
||||
{ }
|
||||
public static bool ParseResponse(byte[] buf, ref DNSPacket rPack)
|
||||
{
|
||||
try
|
||||
{
|
||||
rPack = new DNSPacket(buf);
|
||||
if (rPack.answers.Count > 0) return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{ }
|
||||
return false;
|
||||
}
|
||||
public static bool CreateRecursiveQuestionPayload(ushort transactionID, string question, QueryType qt, ref byte[] buf)
|
||||
{
|
||||
try
|
||||
{
|
||||
DNSPacket dnsQ = new(transactionID);
|
||||
dnsQ.SetRecursive();
|
||||
dnsQ.AddQuestion(question, qt);
|
||||
buf = dnsQ.GetBytes();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{ }
|
||||
return false;
|
||||
}
|
||||
public void SetRecursive()
|
||||
{
|
||||
flags = (ushort)(flags | System.Convert.ToUInt16(Math.Pow(2, 8)));
|
||||
}
|
||||
public void AddQuestion(string name, QueryType type)
|
||||
{
|
||||
queries.Add(new DNSQuestion(name, (ushort)type));
|
||||
questions = (ushort)queries.Count;
|
||||
}
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
using System.IO.MemoryStream packetms = new();
|
||||
using BinaryWriter bw = new(packetms);
|
||||
additionalRRs = 1;
|
||||
bw.WriteBigEndian16(transactionID);
|
||||
bw.WriteBigEndian16(flags);
|
||||
bw.WriteBigEndian16(questions);
|
||||
bw.WriteBigEndian16(answerRRs);
|
||||
bw.WriteBigEndian16(authorityRRs);
|
||||
bw.WriteBigEndian16(additionalRRs);
|
||||
foreach (DNSQuestion q in queries)
|
||||
q.WriteTo(bw);
|
||||
byte[] optpacket = new byte[11];
|
||||
optpacket[2] = (byte)41;
|
||||
optpacket[3] = (byte)5;
|
||||
bw.Write(optpacket, 0, optpacket.Length);
|
||||
return packetms.ToArray();
|
||||
}
|
||||
private static uint ReadInt(ref uint index, byte[] buffer)
|
||||
{
|
||||
uint value = (uint)((buffer[index] << 24) | ((int)(buffer[index + 1]) << 16) | ((int)(buffer[index + 2]) << 8) | (int)(buffer[index + 3]));
|
||||
index += 4;
|
||||
return value;
|
||||
}
|
||||
private static ushort ReadShort(ref uint index, byte[] buffer)
|
||||
{
|
||||
ushort value = (ushort)(((int)(buffer[index]) << 8) | ((int)(buffer[(index + 1)])));
|
||||
index += 2;
|
||||
return value;
|
||||
}
|
||||
private string ReadName(ref uint idx, byte[] buf)
|
||||
{
|
||||
string @out = "";
|
||||
while (idx < buf.Length)
|
||||
{
|
||||
int lbl = buf[idx];
|
||||
idx += 1;
|
||||
if (lbl == 0) return @out;
|
||||
else if (lbl > 63)
|
||||
{
|
||||
uint namePosition = (ushort)(((lbl << 8) | buf[idx]) & 0x3FFF);
|
||||
idx += 1;
|
||||
return (@out != "" ? @out + "." : "") + ReadName(ref namePosition, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@out += (@out != "" ? "." : "") + Encoding.ASCII.GetString(buf, (int)idx, lbl);
|
||||
idx += (uint)lbl;
|
||||
}
|
||||
}
|
||||
return @out;
|
||||
}
|
||||
public static int ByteAt(int input, int position)
|
||||
{
|
||||
if (position > 3) throw new Exception("You can only use positions 0 through 3");
|
||||
return (input >> ((3 - position) * 8)) & 255;
|
||||
}
|
||||
public void ParseBuffer(byte[] buf)
|
||||
{
|
||||
uint idx = 0;
|
||||
transactionID = ReadShort(ref idx, buf);
|
||||
flags = ReadShort(ref idx, buf);
|
||||
questions = ReadShort(ref idx, buf);
|
||||
answerRRs = ReadShort(ref idx, buf);
|
||||
authorityRRs = ReadShort(ref idx, buf);
|
||||
additionalRRs = ReadShort(ref idx, buf);
|
||||
Int16 rescode = ((short)(flags & 63));
|
||||
if (rescode > 0)
|
||||
{
|
||||
switch (rescode)
|
||||
{
|
||||
case 1:
|
||||
throw new Exception("Format Error");
|
||||
case 2:
|
||||
throw new Exception("Server Failure");
|
||||
case 3:
|
||||
throw new Exception("Name Error");
|
||||
case 4:
|
||||
throw new Exception("Not Implemented");
|
||||
case 5:
|
||||
throw new Exception("Refused");
|
||||
}
|
||||
throw new Exception("Unknown Error");
|
||||
}
|
||||
for (int i = 0; i <= questions - 1; i++)
|
||||
{
|
||||
string nm = ReadName(ref idx, buf);
|
||||
ushort qtype = ReadShort(ref idx, buf);
|
||||
ushort qclass = ReadShort(ref idx, buf);
|
||||
DNSQuestion q = new(nm, qtype);
|
||||
queries.Add(q);
|
||||
}
|
||||
for (int i = 0; i <= answerRRs - 1; i++)
|
||||
{
|
||||
DNSAnswer ans = new()
|
||||
{
|
||||
name = ReadName(ref idx, buf),
|
||||
type = ReadShort(ref idx, buf),
|
||||
@class = ReadShort(ref idx, buf),
|
||||
TTL = ReadInt(ref idx, buf),
|
||||
dataLength = ReadShort(ref idx, buf)
|
||||
};
|
||||
QueryType anstype = (QueryType)ans.type;
|
||||
if (anstype == QueryType.MX)
|
||||
{
|
||||
ans.preference = ReadShort(ref idx, buf);
|
||||
ans.rdata = ReadName(ref idx, buf);
|
||||
}
|
||||
else if (anstype == QueryType.CNAME)
|
||||
{
|
||||
ans.rdata = ReadName(ref idx, buf);
|
||||
}
|
||||
else if (anstype == QueryType.TXT)
|
||||
{
|
||||
string txtDat = "";
|
||||
int bytesRead = 0;
|
||||
while (ans.dataLength > bytesRead)
|
||||
{
|
||||
byte nLen = buf[idx];
|
||||
idx++;
|
||||
bytesRead++;
|
||||
string txtStr = System.Text.Encoding.ASCII.GetString(buf, (int)idx, nLen);
|
||||
txtDat += txtStr;
|
||||
idx += nLen;
|
||||
bytesRead += nLen;
|
||||
}
|
||||
ans.rdata = txtDat;
|
||||
}
|
||||
else if (anstype == QueryType.A && ans.dataLength == 4)
|
||||
{
|
||||
int ip32 = (int)ReadInt(ref idx, buf);
|
||||
ans.rdata = ByteAt(ip32, 0) + "." + ByteAt(ip32, 1) + "." + ByteAt(ip32, 2) + "." + ByteAt(ip32, 3);
|
||||
}
|
||||
else if (anstype == QueryType.AAAA)
|
||||
{
|
||||
byte[] ipv6 = new byte[16];
|
||||
Array.Copy(buf, idx, ipv6, 0, 16);
|
||||
IPAddress ipv6Addr = new(ipv6);
|
||||
ans.rdata = ipv6Addr.ToString();
|
||||
}
|
||||
if (ans.rdata != "") answers.Add(ans);
|
||||
}
|
||||
}
|
||||
public string[] GetAnswers(QueryType qt)
|
||||
{
|
||||
List<DNSAnswer> qualified = new List<DNSAnswer>();
|
||||
foreach(DNSAnswer answer in answers)
|
||||
{
|
||||
if (answer.type == (ushort)qt) qualified.Add(answer);
|
||||
}
|
||||
if(qt == QueryType.MX)
|
||||
{
|
||||
qualified = qualified.OrderBy(x =>
|
||||
{
|
||||
return x.preference;
|
||||
}).ToList();
|
||||
}
|
||||
return Array.ConvertAll<DNSAnswer, string>(qualified.ToArray(), new Converter<DNSAnswer, string>(d =>
|
||||
{
|
||||
return d.rdata;
|
||||
}));
|
||||
}
|
||||
}
|
||||
public class DNSQuestion
|
||||
{
|
||||
public string name;
|
||||
public ushort type;
|
||||
private readonly ushort @class = 1;
|
||||
public DNSQuestion(string name, ushort type)
|
||||
{
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
public void WriteTo(BinaryWriter bw)
|
||||
{
|
||||
string[] labels = name.Split('.');
|
||||
foreach (string lbl in labels)
|
||||
{
|
||||
bw.Write(System.Convert.ToByte(lbl.Length));
|
||||
bw.Write(Encoding.ASCII.GetBytes(lbl));
|
||||
}
|
||||
bw.Write(System.Convert.ToByte(0));
|
||||
bw.WriteBigEndian16(type);
|
||||
bw.WriteBigEndian16(@class);
|
||||
}
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
using MemoryStream ms = new();
|
||||
using BinaryWriter bw = new(ms);
|
||||
WriteTo(bw);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
public class DNSAnswer
|
||||
{
|
||||
public string name;
|
||||
public ushort type;
|
||||
public ushort @class;
|
||||
public UInt32 TTL;
|
||||
public ushort dataLength;
|
||||
public ushort preference;
|
||||
public string rdata;
|
||||
}
|
||||
}
|
||||
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Trevor Hall
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
52
Proxies/HTTPProxyProtocol.cs
Normal file
52
Proxies/HTTPProxyProtocol.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Proxies
|
||||
{
|
||||
|
||||
public class HTTPProxyProtocol : IProxyProtocol
|
||||
{
|
||||
private const string httpConnectHostPort = "CONNECT {0}:{1} HTTP/1.1\r\n\r\n";
|
||||
private const string httpConnectHostPortAuth = "CONNECT {0}:{1} HTTP/1.1\r\nProxy-Authorization: Basic {2}\r\n\r\n";
|
||||
private static string CreateHTTPConnectHeaders(string destHost, int destport, string proxyUsername, string proxyPassword)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(proxyUsername) && !string.IsNullOrEmpty(proxyPassword))
|
||||
{
|
||||
//auth
|
||||
string b64 = Convert.ToBase64String(Encoding.ASCII.GetBytes(proxyUsername + ":" + proxyPassword));
|
||||
return string.Format(httpConnectHostPortAuth, destHost, destport, b64);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format(httpConnectHostPort, destHost, destport);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> ConnectAsync(Sockets.TCPNetworkStream tcpns, string remoteHost, int remotePort, string proxyUsername, string proxyPassword, string extra)
|
||||
{
|
||||
string http_connect_headers = CreateHTTPConnectHeaders(remoteHost, remotePort, proxyUsername, proxyPassword);
|
||||
byte[] header_data = Encoding.ASCII.GetBytes(http_connect_headers);
|
||||
await tcpns.WriteAsync(header_data, 0, header_data.Length).ConfigureAwait(false);
|
||||
byte[] received = await tcpns.ReadUntilCRLFCRLF().ConfigureAwait(false);
|
||||
if (received == null) return false;
|
||||
int rlen = received.Length;
|
||||
if (rlen <= 9) return false;
|
||||
string result = Encoding.ASCII.GetString(received, 0, rlen);
|
||||
string code = result.Substring(9, 3);
|
||||
if (code != "200") return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Proxies/IProxyProtocol.cs
Normal file
25
Proxies/IProxyProtocol.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.Sockets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Proxies
|
||||
{
|
||||
interface IProxyProtocol
|
||||
{
|
||||
public static async Task<bool> ConnectAsync(TCPSocket tcpns, string remoteHost, int remotePort, string proxyUsername, string proxyPassword, string extra) => false;
|
||||
}
|
||||
}
|
||||
76
Proxies/Proxy.cs
Normal file
76
Proxies/Proxy.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using SecuCore.Sockets;
|
||||
|
||||
namespace SecuCore.Proxies
|
||||
{
|
||||
public enum ProxyProtocol
|
||||
{
|
||||
HTTP,
|
||||
SOCKS4,
|
||||
SOCKS5
|
||||
}
|
||||
public class Proxy
|
||||
{
|
||||
private string proxyHost;
|
||||
private int proxyPort;
|
||||
private string proxyUsername;
|
||||
private string proxyPassword;
|
||||
public ProxyProtocol proxyProtocol;
|
||||
|
||||
private IPAddress proxyAddress;
|
||||
|
||||
public Proxy(string proxyString, ProxyProtocol protocol)
|
||||
{
|
||||
if (proxyString.Contains(':'))
|
||||
{
|
||||
string[] pspl = proxyString.Split(':');
|
||||
if (string.IsNullOrEmpty(pspl[0])) throw new Exception("Proxy host parse error");
|
||||
this.proxyHost = pspl[0];
|
||||
if(!int.TryParse(pspl[1], out this.proxyPort)) throw new Exception("Proxy port parse error");
|
||||
if (pspl.Length > 2) this.proxyUsername = pspl[2];
|
||||
if(pspl.Length > 3) this.proxyPassword = pspl[3];
|
||||
this.proxyProtocol = protocol;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Proxy input parse error");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> ConnectAsync(TCPNetworkStream tcpns, string remoteHost, int remotePort)
|
||||
{
|
||||
if (this.proxyProtocol == ProxyProtocol.HTTP)
|
||||
{
|
||||
return HTTPProxyProtocol.ConnectAsync(tcpns, remoteHost, remotePort, proxyUsername, proxyPassword, proxyHost + ":" + proxyPort);
|
||||
}else if(this.proxyProtocol == ProxyProtocol.SOCKS5)
|
||||
{
|
||||
return SOCKS5ProxyProtocol.ConnectAsync(tcpns, remoteHost, remotePort, proxyUsername, proxyPassword);
|
||||
}else if(this.proxyProtocol == ProxyProtocol.SOCKS4)
|
||||
{
|
||||
return SOCKS4ProxyProtocol.ConnectAsync(tcpns, remoteHost, remotePort, proxyUsername, proxyPassword);
|
||||
}
|
||||
return Task<bool>.FromResult(false);
|
||||
}
|
||||
public IPEndPoint GetRemoteEndpoint()
|
||||
{
|
||||
if(proxyAddress == null)
|
||||
{
|
||||
proxyAddress = IPAddress.Parse(proxyHost);
|
||||
}
|
||||
return new IPEndPoint(proxyAddress, proxyPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Proxies/SOCKS4ProxyProtocol.cs
Normal file
82
Proxies/SOCKS4ProxyProtocol.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace SecuCore.Proxies
|
||||
{
|
||||
public class SOCKS4ProxyProtocol : IProxyProtocol
|
||||
{
|
||||
public static async Task<bool> ConnectAsync(Sockets.TCPNetworkStream tcpns, string remoteHost, int remotePort, string proxyUsername, string proxyPassword)
|
||||
{
|
||||
byte[] conp = Socks4ConnectPacket(remoteHost, remotePort);
|
||||
await tcpns.WriteAsync(conp, 0, conp.Length).ConfigureAwait(false);
|
||||
byte[] readb = new byte[64];
|
||||
int len = await tcpns.ReadAsync(readb, 0, readb.Length).ConfigureAwait(false);
|
||||
if (len <= 0) return false;
|
||||
|
||||
if (readb[0] == 0x04)
|
||||
{
|
||||
if (readb[1] == 0x5A)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static byte[] Socks4ConnectPacket(string host, int port)
|
||||
{
|
||||
List<byte> connectionPacket = new();
|
||||
{
|
||||
var withBlock = connectionPacket;
|
||||
// Write packet feader
|
||||
withBlock.Add(4); // socks version 4
|
||||
withBlock.Add(1); // TCP Stream Connection
|
||||
|
||||
// Write port, network order
|
||||
withBlock.AddRange(PortToBytes(port));
|
||||
|
||||
// Write address, network order
|
||||
IPAddress dest = IPAddress.Parse(host);
|
||||
byte[] ipb = dest.GetAddressBytes();
|
||||
Array.Reverse(ipb);
|
||||
withBlock.AddRange(ipb);
|
||||
withBlock.Add(0);
|
||||
}
|
||||
return connectionPacket.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] PortToBytes(int port)
|
||||
{
|
||||
ushort ps = (ushort)port;
|
||||
byte[] pb = BitConverter.GetBytes(ps);
|
||||
|
||||
// network order bytes
|
||||
byte[] nb = new byte[2];
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
nb[0] = pb[1];
|
||||
nb[1] = pb[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
nb[0] = pb[0];
|
||||
nb[1] = pb[1];
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
Proxies/SOCKS5ProxyProtocol.cs
Normal file
174
Proxies/SOCKS5ProxyProtocol.cs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace SecuCore.Proxies
|
||||
{
|
||||
public class SOCKS5ProxyProtocol : IProxyProtocol
|
||||
{
|
||||
public static readonly byte[] Socks5greeting = { 0x5, 0x2, 0x0, 0x2 };
|
||||
|
||||
public static async Task<bool> ConnectAsync(Sockets.TCPNetworkStream tcpns, string remoteHost, int remotePort, string proxyUsername, string proxyPassword)
|
||||
{
|
||||
byte[] socksReadBuf = new byte[64];
|
||||
|
||||
await tcpns.WriteAsync(Socks5greeting, 0, Socks5greeting.Length).ConfigureAwait(false);
|
||||
|
||||
if (await tcpns.ReadAsync(socksReadBuf, 0, 2).ConfigureAwait(false) < 2) return false;
|
||||
|
||||
int vers = socksReadBuf[0];
|
||||
int authmethod = socksReadBuf[1];
|
||||
if (vers != 5 || authmethod != 0) return false;
|
||||
|
||||
byte[] conp = Socks5ConnectPacket(remoteHost, remotePort);
|
||||
|
||||
await tcpns.WriteAsync(conp, 0, conp.Length).ConfigureAwait(false);
|
||||
|
||||
int len = await tcpns.ReadAsync(socksReadBuf, 0, socksReadBuf.Length).ConfigureAwait(false);
|
||||
if (len <= 0) return false;
|
||||
|
||||
vers = (int)socksReadBuf[0];
|
||||
if (vers == 0x05)
|
||||
{
|
||||
Socks5ConnectResult status = (Socks5ConnectResult)socksReadBuf[1];
|
||||
if (status == Socks5ConnectResult.Succcess)
|
||||
{
|
||||
int boundType = socksReadBuf[3];
|
||||
int totalResponseSize = 4;
|
||||
if (boundType == 1)
|
||||
{
|
||||
totalResponseSize += 4;
|
||||
}
|
||||
else if (boundType == 3)
|
||||
{
|
||||
int strl = socksReadBuf[4];
|
||||
totalResponseSize += strl + 1;
|
||||
}
|
||||
else if (boundType == 4)
|
||||
{
|
||||
totalResponseSize += 16;
|
||||
}
|
||||
totalResponseSize += 2;
|
||||
|
||||
int remainingSize = totalResponseSize - len;
|
||||
|
||||
if (remainingSize > 0)
|
||||
{
|
||||
byte[] remainb = new byte[remainingSize];
|
||||
|
||||
len = await tcpns.ReadAsync(remainb, 0, remainb.Length).ConfigureAwait(false);
|
||||
if (len <= 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Socks5ConnectResult
|
||||
{
|
||||
Succcess,
|
||||
Failure,
|
||||
NotAllowedByRuleSet,
|
||||
NetworkUnreachable,
|
||||
HostUnreachable,
|
||||
ConnectionRefused,
|
||||
TTLExpired,
|
||||
NotSupportedProtocol
|
||||
}
|
||||
public static byte[] Socks5ConnectPacket(string host, int port)
|
||||
{
|
||||
int hostType;
|
||||
if (Regex.IsMatch(host, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"))
|
||||
hostType = 1; // ipv4 address
|
||||
else if (Regex.IsMatch(host, @"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"))
|
||||
hostType = 4; // ipv6 address
|
||||
else
|
||||
hostType = 3; // domain name
|
||||
|
||||
List<byte> connectionPacket = new();
|
||||
|
||||
{
|
||||
var withBlock = connectionPacket;
|
||||
// Write packet feader
|
||||
withBlock.Add(5); // socks version 5
|
||||
withBlock.Add(1); // TCP Stream Connection
|
||||
withBlock.Add(0); // RFC Reserved 0
|
||||
|
||||
// Write address
|
||||
withBlock.Add((byte)hostType); // Address Type
|
||||
switch (hostType)
|
||||
{
|
||||
case 1 // ipv4
|
||||
:
|
||||
{
|
||||
IPAddress dest = IPAddress.Parse(host);
|
||||
withBlock.AddRange(dest.GetAddressBytes());
|
||||
break;
|
||||
}
|
||||
|
||||
case 4 // ipv6
|
||||
:
|
||||
{
|
||||
IPAddress dest = IPAddress.Parse(host);
|
||||
withBlock.AddRange(dest.GetAddressBytes());
|
||||
break;
|
||||
}
|
||||
|
||||
case 3 // domain
|
||||
:
|
||||
{
|
||||
withBlock.Add((byte)host.Length);
|
||||
withBlock.AddRange(System.Text.Encoding.UTF8.GetBytes(host));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write port
|
||||
withBlock.AddRange(PortToBytes(port));
|
||||
}
|
||||
|
||||
return connectionPacket.ToArray();
|
||||
}
|
||||
private static byte[] PortToBytes(int port)
|
||||
{
|
||||
ushort ps = (ushort)port;
|
||||
byte[] pb = BitConverter.GetBytes(ps);
|
||||
|
||||
// network order bytes
|
||||
byte[] nb = new byte[2];
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
nb[0] = pb[1];
|
||||
nb[1] = pb[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
nb[0] = pb[0];
|
||||
nb[1] = pb[1];
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
README.md
Normal file
54
README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# SecuCore: TCP network library for .Net with raw TLS implementation
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**I created and use this library at my own risk, and you must fully evaluate any potential risks before implementing this in your own software. Any usage of this library is done at your own risk.**
|
||||
|
||||
## Overview
|
||||
|
||||
SecuCore is a customizable TCP Socket Library with TLS Support. It includes a custom async DNS resolver. This library is created for more discrete control over how secure connections are negotiated using .Net sockets.
|
||||
|
||||
## Features
|
||||
|
||||
### TCP Socket and Network Streams
|
||||
|
||||
- IPV4 connectivity (no IPV6 available)
|
||||
- HTTP and SOCKS4/SOCKS5 proxy support natively (see the class definitions in "/Proxies")
|
||||
- Cancellation after timeout
|
||||
- Lightweight and scalable TCP connections with less resource usage compared to regular .Net implementations.
|
||||
|
||||
### HTTP Client
|
||||
|
||||
I created an HTTPClient object for usage and demonstration, but you can create other clients or tools using TCPSocket, TCPNetworkStream, and SecuredTCPStream. The HTTP client provides:
|
||||
|
||||
- Basic Cookie Support
|
||||
- GET and POST requests
|
||||
- TLS/SSL elevation when "https" is at the beginning of the URL.
|
||||
- Chunked transfer encoding support
|
||||
- Gzip support
|
||||
|
||||
### TLS Support
|
||||
|
||||
Although not very robust, the library provides support for TLS11 and TLS12 with three available cipher suites. It offers an option outside of "SslStream" for handling TLS, including key generation, encryption, verification, and signing. The available ciphersuites are:
|
||||
|
||||
- TLS_RSA_WITH_AES_128_CBC_SHA
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
|
||||
|
||||
## Note
|
||||
|
||||
This library heavily relies on TPL and async/await, with many missing options for regular blocking calls.
|
||||
|
||||
## Simple Example: HTTP Get Request
|
||||
|
||||
```C#
|
||||
string url = "https://www.google.com";
|
||||
string refererurl = "https://www.amazon.com";
|
||||
SecuCore.Clients.HTTPClient cl = new SecuCore.Clients.HTTPClient();
|
||||
string response_body = await cl.GetAsync(url, refererurl).ConfigureAwait(false);
|
||||
string response_headers = cl.LastHeaders;
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
SecuCore is designed with an emphasis on avoiding application crashes and lockups, even when things do not behave as expected. Use it to enhance your control over TCP socket connections and TLS negotiations, all while benefiting from its lightweight and efficient design.
|
||||
48
SecuCore.csproj
Normal file
48
SecuCore.csproj
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Title>SecuCore</Title>
|
||||
<NoWin32Manifest>true</NoWin32Manifest>
|
||||
<Copyright>Trevor Hall</Copyright>
|
||||
<Description>A library for communicating with servers through TCP Sockets. An alternative to the Clients provided by Microsoft.</Description>
|
||||
<AnalysisLevel>none</AnalysisLevel>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<PackageTags>TCP, Socket, TLS</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Optimize>True</Optimize>
|
||||
<DefineConstants />
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>full</DebugType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<Optimize>True</Optimize>
|
||||
<DebugType>full</DebugType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.6.40" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.6.40">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EditorConfigFiles Remove="C:\Users\Trevor\source\repos\MXM3-nocawaitfalse\SecuCore\.editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
SecuCore.sln
Normal file
25
SecuCore.sln
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecuCore", "SecuCore.csproj", "{8C9D91B1-CB0D-47C4-96ED-B211156862FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8C9D91B1-CB0D-47C4-96ED-B211156862FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C9D91B1-CB0D-47C4-96ED-B211156862FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C9D91B1-CB0D-47C4-96ED-B211156862FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C9D91B1-CB0D-47C4-96ED-B211156862FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DA5614EC-1536-4978-96A6-DCEBA6F89B84}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
520
Security/Asn1.cs
Normal file
520
Security/Asn1.cs
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
/*
|
||||
* 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.Text;
|
||||
|
||||
namespace SecuCore.Security
|
||||
{
|
||||
public class AsnException : Exception
|
||||
{
|
||||
public AsnException(string s = "") : base( "Error parsing ASN: " + s){}
|
||||
}
|
||||
|
||||
public enum Asn1Type
|
||||
{
|
||||
EndOfContent,
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
BIT_STRING,
|
||||
OCTET_STRING,
|
||||
NULL,
|
||||
OBJECT_IDENTIFIER,
|
||||
ObjectDescriptor,
|
||||
EXTERNAL,
|
||||
REAL,
|
||||
ENUMERATED,
|
||||
EMBEDDED_PDV,
|
||||
UTF8String,
|
||||
RELATIVE_OID,
|
||||
TIME,
|
||||
RESERVED,
|
||||
SEQUENCE,
|
||||
SET,
|
||||
NumericString,
|
||||
PrintableString,
|
||||
T61String,
|
||||
VideotexString,
|
||||
IA5String,
|
||||
UTCTime,
|
||||
GeneralizedTime,
|
||||
GraphicString,
|
||||
VisibleString,
|
||||
GeneralString,
|
||||
UniversalString,
|
||||
CHARACTER_STRING,
|
||||
BMPString,
|
||||
DATE,
|
||||
TIME_OF_DAY,
|
||||
DATE_TIME,
|
||||
DURATION,
|
||||
OID_URI,
|
||||
RELATIVE_OID_URI
|
||||
}
|
||||
|
||||
public enum AsnTagClass
|
||||
{
|
||||
Universal,
|
||||
Application,
|
||||
ContextSpecific,
|
||||
Private
|
||||
}
|
||||
|
||||
public struct Asn1Tag
|
||||
{
|
||||
public AsnTagClass tagClass;
|
||||
public bool constructed;
|
||||
public uint tagNumber;
|
||||
public Asn1Tag(AsnTagClass atc, bool pc, uint tn)
|
||||
{
|
||||
tagClass = atc;
|
||||
constructed = pc;
|
||||
tagNumber = tn;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Asn1Info
|
||||
{
|
||||
public Asn1Tag tag;
|
||||
public Asn1Type type;
|
||||
public int contentOffset;
|
||||
public bool lengthIsDefinite;
|
||||
public int length;
|
||||
public Asn1Info(Asn1Tag atag, int coffset, bool lendefinite, int alen)
|
||||
{
|
||||
tag = atag;
|
||||
type = (Asn1Type)(atag.tagNumber);
|
||||
contentOffset = coffset;
|
||||
lengthIsDefinite = lendefinite;
|
||||
length = alen;
|
||||
}
|
||||
public byte[] GetData(byte[] parent, ref int pos)
|
||||
{
|
||||
pos += length;
|
||||
byte[] dat = new byte[length];
|
||||
Buffer.BlockCopy(parent, (int)contentOffset, dat, 0, (int)length);
|
||||
return dat;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DerDecoder {
|
||||
|
||||
public static Asn1Info GetAsn1Data(byte[] data, ref int offset)
|
||||
{
|
||||
byte first = data[offset++];
|
||||
AsnTagClass atc = (AsnTagClass)((first >> 6) & 0x3);
|
||||
bool constructed = (((first >> 5) & 0x1) == 1);
|
||||
byte tagbits = ((byte)(first & 0x1f));
|
||||
int tagnumber = 0;
|
||||
|
||||
//if constructed is true and tag is 17 (10001) DER doesnt encode the type in additional bytes
|
||||
if (tagbits == 17 && constructed == false)
|
||||
{
|
||||
// long form
|
||||
while (true)
|
||||
{
|
||||
byte tnb = data[offset++];
|
||||
bool moreBytes = ((tnb >> 7) == 1);
|
||||
int bval = (int)(tnb & 127);
|
||||
tagnumber <<= 7;
|
||||
tagnumber |= bval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tagnumber = (int)tagbits;
|
||||
}
|
||||
Asn1Tag asn1Tag = new Asn1Tag(atc, constructed, (uint)tagnumber);
|
||||
|
||||
//read length octets
|
||||
byte lfirst = data[offset++];
|
||||
if (lfirst == 0xff) throw new AsnException("First length octet of 0xff is reserved");
|
||||
|
||||
bool definite = true;
|
||||
int length = 0;
|
||||
bool bit8 = ((lfirst >> 7) == 1);
|
||||
int lenbits = (lfirst & 127);
|
||||
if (bit8)
|
||||
{
|
||||
if (lenbits == 0)
|
||||
{
|
||||
//indefinite, parser will have to read until 2 EndOfContents octets are found
|
||||
definite = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//definite, long
|
||||
for (int i = 0; i < lenbits; i++)
|
||||
{
|
||||
byte next = data[offset++];
|
||||
length <<= 8;
|
||||
length |= next;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//definite short
|
||||
length = lenbits;
|
||||
}
|
||||
|
||||
return new Asn1Info(asn1Tag, offset, definite, length);
|
||||
}
|
||||
|
||||
public static string ParseOid(byte[] data, int length, ref int offset)
|
||||
{
|
||||
List<int> idelements = new List<int>();
|
||||
int endindex = offset + length;
|
||||
|
||||
byte first = data[offset++];
|
||||
idelements.Add(first / 40);
|
||||
idelements.Add(first % 40);
|
||||
|
||||
int currentvalue = 0;
|
||||
while (offset < endindex)
|
||||
{
|
||||
byte oide = data[offset++];
|
||||
currentvalue <<= 7;
|
||||
currentvalue |= (oide & 127);
|
||||
|
||||
if (oide >> 7 == 0)
|
||||
{
|
||||
idelements.Add(currentvalue);
|
||||
currentvalue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join('.', idelements);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Asn1Index : IComparable<int[]>, IEquatable<int[]>
|
||||
{
|
||||
private int[] data;
|
||||
public Asn1Index(params int[] columns)
|
||||
{
|
||||
data = columns;
|
||||
}
|
||||
public int CompareTo(int[] other)
|
||||
{
|
||||
int cmp = -1;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (other.Length <= i) return cmp;
|
||||
|
||||
cmp = data[i].CompareTo(other[i]);
|
||||
if (cmp != 0) return cmp;
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
public bool Equals(int[] other)
|
||||
{
|
||||
if (other.Length != this.data.Length) return false;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (!data[i].Equals(other[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join('.', data);
|
||||
}
|
||||
}
|
||||
|
||||
public class Asn1Value
|
||||
{
|
||||
public Asn1Type asnType;
|
||||
public int offset;
|
||||
public int length;
|
||||
}
|
||||
|
||||
public class IndexedASNData
|
||||
{
|
||||
public List<KeyValuePair<Asn1Index, Asn1Value>> AsnDataPairs = new List<KeyValuePair<Asn1Index, Asn1Value>>();
|
||||
|
||||
public IndexedASNData(byte[] input, int offset)
|
||||
{
|
||||
X509Cert cert = new X509Cert();
|
||||
int ofs = offset;
|
||||
List<int> indexes = new List<int>();
|
||||
Stack<int> endstack = new Stack<int>();
|
||||
|
||||
int curridx = 0;
|
||||
int datend = input.Length;
|
||||
int currdatend = datend;
|
||||
while (ofs < datend)
|
||||
{
|
||||
Asn1Info ai = DerDecoder.GetAsn1Data(input, ref ofs);
|
||||
if (ai.tag.tagClass == AsnTagClass.ContextSpecific)
|
||||
{
|
||||
|
||||
ai = DerDecoder.GetAsn1Data(input, ref ofs);
|
||||
}
|
||||
|
||||
if (ai.type == Asn1Type.SEQUENCE || ai.type == Asn1Type.SET)
|
||||
{
|
||||
int[] indints = new int[indexes.Count + 1];
|
||||
indexes.CopyTo(indints);
|
||||
indints[indints.Length - 1] = curridx;
|
||||
Asn1Index aind = new Asn1Index(indints);
|
||||
Asn1Value av = new Asn1Value()
|
||||
{
|
||||
asnType = ai.type,
|
||||
length = ai.length,
|
||||
offset = ofs
|
||||
};
|
||||
AsnDataPairs.Add(new KeyValuePair<Asn1Index, Asn1Value>(aind, av));
|
||||
|
||||
indexes.Add(curridx);
|
||||
endstack.Push(currdatend);
|
||||
curridx = 0;
|
||||
currdatend = ofs + ai.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] indints = new int[indexes.Count + 1];
|
||||
indexes.CopyTo(indints);
|
||||
indints[indints.Length - 1] = curridx;
|
||||
Asn1Index aind = new Asn1Index(indints);
|
||||
Asn1Value av = new Asn1Value()
|
||||
{
|
||||
asnType = ai.type,
|
||||
length = ai.length,
|
||||
offset = ofs
|
||||
};
|
||||
ofs += ai.length;
|
||||
AsnDataPairs.Add(new KeyValuePair<Asn1Index, Asn1Value>(aind, av));
|
||||
while (ofs >= currdatend && endstack.Count > 0)
|
||||
{
|
||||
currdatend = endstack.Pop();
|
||||
curridx = indexes[indexes.Count - 1];
|
||||
indexes.RemoveAt(indexes.Count - 1);
|
||||
}
|
||||
curridx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool Lookup(out Asn1Value av, params int[] Index)
|
||||
{
|
||||
for(int i = 0; i < AsnDataPairs.Count; i++)
|
||||
{
|
||||
if(AsnDataPairs[i].Key.Equals(Index))
|
||||
{
|
||||
av = AsnDataPairs[i].Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
av = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class RDNSequence
|
||||
{
|
||||
public string country = "";
|
||||
public string organization = "";
|
||||
public string commonname = "";
|
||||
public RDNSequence(byte[] data, int offset, int length)
|
||||
{
|
||||
Asn1Info ai;
|
||||
while (true)
|
||||
{
|
||||
ai = DerDecoder.GetAsn1Data(data, ref offset);
|
||||
if (ai.type != Asn1Type.SET) break;
|
||||
ai = DerDecoder.GetAsn1Data(data, ref offset);
|
||||
if (ai.type != Asn1Type.SEQUENCE) break;
|
||||
ai = DerDecoder.GetAsn1Data(data, ref offset);
|
||||
if (ai.type != Asn1Type.OBJECT_IDENTIFIER) break;
|
||||
string oid = Asn1Tools.Poid(data, ai.contentOffset, ai.length);
|
||||
offset += ai.length;
|
||||
ai = DerDecoder.GetAsn1Data(data, ref offset);
|
||||
if (ai.type != Asn1Type.PrintableString && ai.type != Asn1Type.UTF8String) break;
|
||||
string strval = Asn1Tools.Strc(data, ai.contentOffset, ai.length);
|
||||
offset += ai.length;
|
||||
if (oid == "2.5.4.6") country = strval;
|
||||
else if (oid == "2.5.4.10") organization = strval;
|
||||
else if (oid == "2.5.4.3") commonname = strval;
|
||||
}
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return organization + " " + commonname + " " + country;
|
||||
}
|
||||
}
|
||||
|
||||
public class Asn1Tools
|
||||
{
|
||||
public static int Btoi(byte[] dat, int offset, int length)
|
||||
{
|
||||
int val = 0;
|
||||
for (int i = offset; i < (offset + length); i++)
|
||||
{
|
||||
val <<= 8;
|
||||
val |= dat[i];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public static byte[] Cpy(byte[] dat, int offset, int length)
|
||||
{
|
||||
byte[] copy = new byte[length];
|
||||
Buffer.BlockCopy(dat, offset, copy, 0, length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static string Poid(byte[] data, int offset, int length)
|
||||
{
|
||||
List<int> idelements = new List<int>();
|
||||
int endindex = offset + length;
|
||||
|
||||
byte first = data[offset++];
|
||||
idelements.Add(first / 40);
|
||||
idelements.Add(first % 40);
|
||||
|
||||
int currentvalue = 0;
|
||||
while (offset < endindex)
|
||||
{
|
||||
byte oide = data[offset++];
|
||||
currentvalue <<= 7;
|
||||
currentvalue |= (oide & 127);
|
||||
|
||||
if (oide >> 7 == 0)
|
||||
{
|
||||
idelements.Add(currentvalue);
|
||||
currentvalue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join('.', idelements);
|
||||
}
|
||||
|
||||
public static string Strc(byte[] data, int offset, int length)
|
||||
{
|
||||
return Encoding.ASCII.GetString(data, offset, length);
|
||||
}
|
||||
|
||||
public static DateTime Utc(byte[] data, int offset)
|
||||
{
|
||||
DateTime dt = new DateTime();
|
||||
int dp = offset;
|
||||
int yy = ((data[dp + 0] - 48) * 10) + (data[dp + 1] - 48);
|
||||
int mm = ((data[dp + 2] - 48) * 10) + (data[dp + 3] - 48);
|
||||
int dd = ((data[dp + 4] - 48) * 10) + (data[dp + 5] - 48);
|
||||
int thh = ((data[dp + 6] - 48) * 10) + (data[dp + 7] - 48);
|
||||
int tmm = ((data[dp + 8] - 48) * 10) + (data[dp + 9] - 48);
|
||||
int tss = ((data[dp + 10] - 48) * 10) + (data[dp + 11] - 48);
|
||||
int year = 2000 + yy;
|
||||
return new DateTime(year, mm, dd, thh, tmm, tss);
|
||||
}
|
||||
|
||||
public static DateTime Generalized(byte[] data, int offset)
|
||||
{
|
||||
return DateTime.Now;
|
||||
}
|
||||
|
||||
public static string OctStr(byte[] data, int offset, int length)
|
||||
{
|
||||
int dp = offset;
|
||||
int de = offset + length;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (dp < de)
|
||||
{
|
||||
Asn1Info ai = DerDecoder.GetAsn1Data(data, ref dp);
|
||||
|
||||
if (ai.type == Asn1Type.SEQUENCE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if(ai.type == Asn1Type.OCTET_STRING || (ai.type == Asn1Type.EndOfContent && ai.tag.tagClass == AsnTagClass.ContextSpecific))
|
||||
{
|
||||
byte[] hexdat = new byte[ai.length];
|
||||
Buffer.BlockCopy(data, ai.contentOffset, hexdat, 0, ai.length);
|
||||
|
||||
sb.Append(ToHexString(hexdat));
|
||||
dp += ai.length;
|
||||
}
|
||||
else if (ai.type == Asn1Type.INTEGER)
|
||||
{
|
||||
string strdat = Encoding.ASCII.GetString(data, ai.contentOffset, ai.length);
|
||||
dp += ai.length;
|
||||
sb.AppendLine(strdat);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ToHexString(byte[] input)
|
||||
{
|
||||
char[] outchars = new char[input.Length * 2];
|
||||
int outp = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
outchars[outp++] = ToHexChar(input[i] / 16);
|
||||
outchars[outp++] = ToHexChar(input[i] % 16);
|
||||
}
|
||||
return new string(outchars);
|
||||
}
|
||||
private static char ToHexChar(int input)
|
||||
{
|
||||
if (input < 0 || input > 15) throw new Exception("Hex conversion error");
|
||||
|
||||
if (input < 10)
|
||||
{
|
||||
return (char)(48 + input);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (char)(97 + (input - 10));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HashesEqual(byte[] hasha, byte[] hashb)
|
||||
{
|
||||
if (hasha == null && hashb != null) return false;
|
||||
if (hasha != null && hashb == null) return false;
|
||||
if (hasha.Length != hashb.Length) return false;
|
||||
for (int i = 0; i < hasha.Length; i++)
|
||||
{
|
||||
if (hasha[i] != hashb[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static byte[] ParseHash(byte[] decrypted)
|
||||
{
|
||||
int idx = 0;
|
||||
Asn1Info ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
|
||||
if (ai.type != Asn1Type.SEQUENCE) return null;
|
||||
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
|
||||
if (ai.type != Asn1Type.SEQUENCE) return null;
|
||||
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
|
||||
if (ai.type != Asn1Type.OBJECT_IDENTIFIER) return null;
|
||||
string oid = Asn1Tools.Poid(decrypted, ai.contentOffset, ai.length);
|
||||
idx += ai.length;
|
||||
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
|
||||
if (ai.type != Asn1Type.NULL) return null;
|
||||
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
|
||||
if (ai.type != Asn1Type.OCTET_STRING) return null;
|
||||
byte[] hash = Asn1Tools.Cpy(decrypted, ai.contentOffset, ai.length);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Security/RSAPublicKey.cs
Normal file
109
Security/RSAPublicKey.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.Numerics;
|
||||
|
||||
namespace SecuCore.Security
|
||||
{
|
||||
public class RSAPublicKey
|
||||
{
|
||||
public int keysize;
|
||||
public byte[] keydat;
|
||||
public byte[] modulusbytes;
|
||||
public byte[] exponentbytes;
|
||||
|
||||
public RSAPublicKey(byte[] dat, int offset = 0)
|
||||
{
|
||||
keydat = dat;
|
||||
int dp = offset;
|
||||
Asn1Info ai = DerDecoder.GetAsn1Data(dat, ref dp);
|
||||
if (ai.type != Asn1Type.SEQUENCE) throw new AsnException("Malformed RSA key");
|
||||
ai = DerDecoder.GetAsn1Data(dat, ref dp);
|
||||
if (ai.type != Asn1Type.INTEGER) throw new AsnException("Malformed RSA key");
|
||||
modulusbytes = new byte[ai.length - 1];
|
||||
Buffer.BlockCopy(dat, ai.contentOffset + 1, modulusbytes, 0, ai.length - 1);
|
||||
|
||||
keysize = modulusbytes.Length;
|
||||
dp += ai.length;
|
||||
ai = DerDecoder.GetAsn1Data(dat, ref dp);
|
||||
if (ai.type != Asn1Type.INTEGER) throw new AsnException("Malformed RSA key");
|
||||
exponentbytes = new byte[ai.length];
|
||||
Buffer.BlockCopy(dat, ai.contentOffset, exponentbytes, 0, ai.length);
|
||||
}
|
||||
|
||||
public byte[] EncryptData(byte[] data)
|
||||
{
|
||||
SecRNG sr = new SecRNG();
|
||||
BigInteger modulus = new BigInteger(modulusbytes, true, true);
|
||||
BigInteger exponent = new BigInteger(exponentbytes, true, true);
|
||||
byte[] block = new byte[keysize];
|
||||
byte[] brng = new byte[keysize];
|
||||
int bp = 0;
|
||||
while (bp < keysize)
|
||||
{
|
||||
sr.GetRandomBytes(brng, 0, brng.Length);
|
||||
for(int i = 0; i < brng.Length; i++)
|
||||
{
|
||||
if (brng[i] > 2) block[bp++] = brng[i];
|
||||
if (bp >= keysize) break;
|
||||
}
|
||||
}
|
||||
block[0] = 0x00;
|
||||
block[1] = 0x02;
|
||||
block[block.Length - data.Length - 1] = 0x00;
|
||||
Buffer.BlockCopy(data, 0, block, block.Length - data.Length, data.Length);
|
||||
BigInteger B = new BigInteger(block, true, true);
|
||||
BigInteger D = ModPow(B, exponent, modulus);
|
||||
byte[] bigendian = D.ToByteArray(true, true);
|
||||
return bigendian;
|
||||
}
|
||||
|
||||
public byte[] DecryptSignature(byte[] signature)
|
||||
{
|
||||
BigInteger modulus = new BigInteger(modulusbytes, true, true);
|
||||
BigInteger exponent = new BigInteger(exponentbytes, true, true);
|
||||
BigInteger B = new BigInteger(signature, true, true);
|
||||
BigInteger D = ModPow(B, exponent, modulus);
|
||||
byte[] bigendian = D.ToByteArray(false, true);
|
||||
int start = 0;
|
||||
for(int i = 0; i < bigendian.Length; i++)
|
||||
{
|
||||
if(bigendian[i] == 0)
|
||||
{
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
byte[] cropped = new byte[bigendian.Length - start];
|
||||
Buffer.BlockCopy(bigendian, start, cropped, 0, bigendian.Length - start);
|
||||
return cropped;
|
||||
}
|
||||
|
||||
|
||||
private static BigInteger ModPow(BigInteger basenum, BigInteger exponent, BigInteger modulus)
|
||||
{
|
||||
if (modulus == 1) return 0;
|
||||
BigInteger curPow = basenum % modulus;
|
||||
BigInteger res = 1;
|
||||
|
||||
|
||||
while (exponent > 0)
|
||||
{
|
||||
if ((exponent & 0x01) == 1) res = (res * curPow) % modulus;
|
||||
exponent >>= 1;
|
||||
curPow = (curPow * curPow) % modulus;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Security/SecRNG.cs
Normal file
77
Security/SecRNG.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.Security
|
||||
{
|
||||
class SecRNG
|
||||
{
|
||||
private RStep stepper;
|
||||
public SecRNG()
|
||||
{
|
||||
uint seed = BitConverter.ToUInt32(Guid.NewGuid().ToByteArray());
|
||||
stepper = new RStep(seed);
|
||||
}
|
||||
|
||||
public void GetRandomBytes(byte[] target, int offset, int length)
|
||||
{
|
||||
int ceil = offset + length - 1;
|
||||
int i = offset;
|
||||
while(i <= ceil)
|
||||
{
|
||||
uint nu = stepper.Next32();
|
||||
if (i > ceil) break;
|
||||
target[i++] = (byte)((nu >> 24) & 0xff);
|
||||
if (i > ceil) break;
|
||||
target[i++] = (byte)((nu >> 16) & 0xff);
|
||||
if (i > ceil) break;
|
||||
target[i++] = (byte)((nu >> 8) & 0xff);
|
||||
if (i > ceil) break;
|
||||
target[i++] = (byte)((nu) & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct RStep
|
||||
{
|
||||
const uint iY = 842502087, iZ = 3579807591, iW = 273326509;
|
||||
uint x;
|
||||
uint y;
|
||||
uint z;
|
||||
uint w;
|
||||
uint next;
|
||||
|
||||
public RStep(uint seed)
|
||||
{
|
||||
x = seed;
|
||||
y = iY;
|
||||
z = iZ;
|
||||
w = iW;
|
||||
next = 0;
|
||||
Step();
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y;
|
||||
y = z;
|
||||
z = w;
|
||||
next = (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
|
||||
}
|
||||
public uint Next32()
|
||||
{
|
||||
Step();
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Security/TrustedCA.cs
Normal file
53
Security/TrustedCA.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Security
|
||||
{
|
||||
class TrustedCA
|
||||
{
|
||||
public static Dictionary<string, X509Cert> TrustedCerts;
|
||||
static TrustedCA()
|
||||
{
|
||||
TrustedCerts = new Dictionary<string, X509Cert>();
|
||||
|
||||
//GlobalSign
|
||||
LoadForSubjectKeyIdent("607b661a450d97ca89502f7d04cd34a8fffcfd4b", "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==");
|
||||
//DigiCert
|
||||
LoadForSubjectKeyIdent("b13ec36903f8bf4701d498261a0802ef63642bc3", "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCevEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K");
|
||||
//UserTrust
|
||||
LoadForSubjectKeyIdent("5379bf5aaa2b4acf5480e1d89bc09df2b20366cb", "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfGjjxDah2nGN59PRbxYvnKkKj9");
|
||||
}
|
||||
|
||||
private static void LoadForSubjectKeyIdent(string ski, string base64)
|
||||
{
|
||||
byte[] b64data = Convert.FromBase64String(base64);
|
||||
X509Cert trusted = X509Cert.FromASN(b64data, 0);
|
||||
TrustedCerts.Add(ski.ToLower(), trusted);
|
||||
}
|
||||
|
||||
public static X509Cert GetTrustedCertificate(string subjectKeyIndentifier)
|
||||
{
|
||||
subjectKeyIndentifier = subjectKeyIndentifier.ToLower();
|
||||
if (TrustedCerts.TryGetValue(subjectKeyIndentifier, out X509Cert trusted))
|
||||
{
|
||||
return trusted;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Security/X509Cert.cs
Normal file
182
Security/X509Cert.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
namespace SecuCore.Security
|
||||
{
|
||||
public class CertificateException : Exception
|
||||
{
|
||||
public CertificateException(string s = "") : base("Error parsing Certificate: " + s)
|
||||
{
|
||||
}
|
||||
}
|
||||
public class X509Cert
|
||||
{
|
||||
public int version;
|
||||
public byte[] sourceData;
|
||||
public byte[] serialNumber;
|
||||
public string sigOid;
|
||||
public string encOid;
|
||||
public RDNSequence issuer;
|
||||
public RDNSequence subject;
|
||||
public DateTime notBefore;
|
||||
public DateTime notAfter;
|
||||
public byte[] publicKey;
|
||||
public string altNames;
|
||||
|
||||
public string subjectKeyIdentifier;
|
||||
public string authorityKeyIdentifier;
|
||||
|
||||
public string algorithmIdentifier;
|
||||
public byte[] certificateHash;
|
||||
public byte[] signature;
|
||||
|
||||
private RSAPublicKey rpk;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
serialNumber = null;
|
||||
publicKey = null;
|
||||
certificateHash = null;
|
||||
signature = null;
|
||||
issuer = null;
|
||||
subject = null;
|
||||
rpk = null;
|
||||
}
|
||||
|
||||
public RSAPublicKey GetRSAPublicKey()
|
||||
{
|
||||
if(rpk == null) rpk = new RSAPublicKey(publicKey, 0);
|
||||
return rpk;
|
||||
}
|
||||
|
||||
public static X509Cert FromASN(byte[] asnData, int offset)
|
||||
{
|
||||
X509Cert cert = new X509Cert();
|
||||
cert.sourceData = asnData;
|
||||
IndexedASNData iad = new IndexedASNData(asnData, offset);
|
||||
if (!iad.Lookup(out Asn1Value av, 0, 0, 0)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.INTEGER) throw new AsnException("Malformed asn data");
|
||||
cert.version = Asn1Tools.Btoi(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 1)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.INTEGER) throw new AsnException("Malformed asn data");
|
||||
cert.serialNumber = Asn1Tools.Cpy(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 2, 0)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
|
||||
cert.sigOid = Asn1Tools.Poid(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 3)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.SEQUENCE) throw new AsnException("Malformed asn data");
|
||||
cert.issuer = new RDNSequence(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 4, 0)) throw new AsnException("Malformed asn data");
|
||||
if(av.asnType == Asn1Type.GeneralizedTime)
|
||||
{
|
||||
cert.notBefore = Asn1Tools.Generalized(asnData, av.offset);
|
||||
}
|
||||
else if (av.asnType == Asn1Type.UTCTime)
|
||||
{
|
||||
cert.notBefore = Asn1Tools.Utc(asnData, av.offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AsnException("Malformed asn data");
|
||||
}
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 4, 1)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType == Asn1Type.GeneralizedTime)
|
||||
{
|
||||
cert.notAfter = Asn1Tools.Generalized(asnData, av.offset);
|
||||
}
|
||||
else if (av.asnType == Asn1Type.UTCTime)
|
||||
{
|
||||
cert.notAfter = Asn1Tools.Utc(asnData, av.offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AsnException("Malformed asn data");
|
||||
}
|
||||
if (!iad.Lookup(out av, 0, 0, 5)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.SEQUENCE) throw new AsnException("Malformed asn data");
|
||||
cert.subject = new RDNSequence(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 6, 0, 0)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
|
||||
cert.encOid = Asn1Tools.Poid(asnData, av.offset, av.length);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 0, 6, 1)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.BIT_STRING) throw new AsnException("Malformed asn data");
|
||||
cert.publicKey = Asn1Tools.Cpy(asnData, av.offset + 1, av.length - 1);
|
||||
|
||||
if (!iad.Lookup(out av, 0, 1, 0)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
|
||||
cert.algorithmIdentifier = Asn1Tools.Poid(asnData, av.offset, av.length);
|
||||
|
||||
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.11")
|
||||
{
|
||||
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
|
||||
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
|
||||
cert.certificateHash = System.Security.Cryptography.SHA256.HashData(certspan);
|
||||
}
|
||||
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.12")
|
||||
{
|
||||
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
|
||||
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
|
||||
cert.certificateHash = System.Security.Cryptography.SHA384.HashData(certspan);
|
||||
}
|
||||
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.5")
|
||||
{
|
||||
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
|
||||
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
|
||||
cert.certificateHash = System.Security.Cryptography.SHA1.HashData(certspan);
|
||||
}
|
||||
if (!iad.Lookup(out av, 0, 2)) throw new AsnException("Malformed asn data");
|
||||
if (av.asnType != Asn1Type.BIT_STRING) throw new AsnException("Malformed asn data");
|
||||
cert.signature = Asn1Tools.Cpy(asnData, av.offset, av.length);
|
||||
|
||||
//find optional data
|
||||
for (int i = 0; i < iad.AsnDataPairs.Count; i++)
|
||||
{
|
||||
KeyValuePair<Asn1Index, Asn1Value> pair = iad.AsnDataPairs[i];
|
||||
if(pair.Value.asnType == Asn1Type.OBJECT_IDENTIFIER)
|
||||
{
|
||||
string oidstr = Asn1Tools.Poid(asnData, pair.Value.offset, pair.Value.length);
|
||||
if (oidstr == "2.5.29.17") // subjectaltname
|
||||
{
|
||||
i++;
|
||||
Asn1Value altnamesoct = iad.AsnDataPairs[i].Value;
|
||||
cert.altNames = Asn1Tools.OctStr(asnData, altnamesoct.offset, altnamesoct.length);
|
||||
}
|
||||
else if (oidstr == "2.5.29.14") //subjectKeyIdentifier
|
||||
{
|
||||
i++;
|
||||
Asn1Value subkeyid = iad.AsnDataPairs[i].Value;
|
||||
cert.subjectKeyIdentifier = Asn1Tools.OctStr(asnData, subkeyid.offset, subkeyid.length);
|
||||
}
|
||||
else if (oidstr == "2.5.29.35") //authorityKeyIdentifier
|
||||
{
|
||||
i++;
|
||||
Asn1Value authkeyid = iad.AsnDataPairs[i].Value;
|
||||
cert.authorityKeyIdentifier = Asn1Tools.OctStr(asnData, authkeyid.offset, authkeyid.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cert;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
161
Security/X509CertChain.cs
Normal file
161
Security/X509CertChain.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
namespace SecuCore.Security
|
||||
{
|
||||
public class X509CertChain
|
||||
{
|
||||
public List<X509Cert> certs;
|
||||
|
||||
public X509CertChain()
|
||||
{
|
||||
certs = new List<X509Cert>();
|
||||
}
|
||||
|
||||
public void AddLink(X509Cert cert)
|
||||
{
|
||||
certs.Add(cert);
|
||||
}
|
||||
|
||||
public bool Verify()
|
||||
{
|
||||
if (certs.Count <= 1) return true;
|
||||
|
||||
for(int i = 0; i < certs.Count; i++)
|
||||
{
|
||||
X509Cert certa = certs[i];
|
||||
X509Cert certb = null;
|
||||
|
||||
if(i == certs.Count - 1)
|
||||
{
|
||||
if (HasTrustedRoot(certa, out certb)) {
|
||||
if(certb == null)
|
||||
{
|
||||
//self signed root was already trusted
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
certb = certs[i + 1];
|
||||
}
|
||||
RSAPublicKey rpkb = certb.GetRSAPublicKey();
|
||||
byte[] decrypted = rpkb.DecryptSignature(certa.signature);
|
||||
byte[] actualhash = Asn1Tools.ParseHash(decrypted);
|
||||
if(Asn1Tools.HashesEqual(certa.certificateHash, actualhash))
|
||||
{
|
||||
//passed
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool HasTrustedRoot(X509Cert cert, out X509Cert root)
|
||||
{
|
||||
root = null;
|
||||
string certificateIssuer = cert.issuer.ToString();
|
||||
string certificateSubject = cert.subject.ToString();
|
||||
|
||||
if (certificateIssuer != certificateSubject)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cert.authorityKeyIdentifier))
|
||||
{
|
||||
X509Cert trusted = TrustedCA.GetTrustedCertificate(cert.authorityKeyIdentifier);
|
||||
if (trusted != null)
|
||||
{
|
||||
root = trusted;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cert.subjectKeyIdentifier))
|
||||
{
|
||||
X509Cert trusted = TrustedCA.GetTrustedCertificate(cert.subjectKeyIdentifier);
|
||||
if (trusted != null)
|
||||
{
|
||||
//we trust this
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static string ToHexString(byte[] input)
|
||||
{
|
||||
char[] outchars = new char[input.Length * 2];
|
||||
int outp = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
outchars[outp++] = ToHexChar(input[i] / 16);
|
||||
outchars[outp++] = ToHexChar(input[i] % 16);
|
||||
}
|
||||
return new string(outchars);
|
||||
}
|
||||
private static char ToHexChar(int input)
|
||||
{
|
||||
if (input < 0 || input > 15) throw new Exception("Hex conversion error");
|
||||
|
||||
if (input < 10)
|
||||
{
|
||||
return (char)(48 + input);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (char)(65 + (input - 10));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
if(certs is null)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
certs.Clear();
|
||||
certs = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Shared/DataController.cs
Normal file
32
Shared/DataController.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Shared
|
||||
{
|
||||
public class DataDispatch
|
||||
{
|
||||
public TaskCompletionSource tcs;
|
||||
public byte[] data;
|
||||
}
|
||||
public class DataRequest
|
||||
{
|
||||
public TaskCompletionSource<byte[]> tcs;
|
||||
public int length;
|
||||
}
|
||||
public interface IDataController
|
||||
{
|
||||
public void RequestData(DataRequest request);
|
||||
public void QueueData(DataDispatch dispatch);
|
||||
public void FlushData();
|
||||
}
|
||||
}
|
||||
35
Shared/RandomNumbers.cs
Normal file
35
Shared/RandomNumbers.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
namespace SecuCore.Shared
|
||||
{
|
||||
class RandomNumbers
|
||||
{
|
||||
public static ushort GetNext16()
|
||||
{
|
||||
byte[] ib = RandomNumberGenerator.GetBytes(2);
|
||||
return BitConverter.ToUInt16(ib);
|
||||
}
|
||||
public static int GetNext(int min, int max)
|
||||
{
|
||||
byte[] ib = RandomNumberGenerator.GetBytes(4);
|
||||
int ival = BitConverter.ToInt32(ib, 0);
|
||||
ival = (ival >= 0 ? ival : -ival);
|
||||
int ceil = max + 1;
|
||||
int diff = ceil - min;
|
||||
int rn = ival % diff;
|
||||
return min + rn;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Shared/Tools.cs
Normal file
68
Shared/Tools.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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
|
||||
{
|
||||
public static class Tools
|
||||
{
|
||||
public static byte[] JoinAll(params byte[][] arrs)
|
||||
{
|
||||
int totallen = 0;
|
||||
for(int i = 0; i < arrs.Length; i++)
|
||||
{
|
||||
totallen += arrs[i].Length;
|
||||
}
|
||||
byte[] outputbuf = new byte[totallen];
|
||||
int bufp = 0;
|
||||
for(int i = 0; i < arrs.Length; i++)
|
||||
{
|
||||
int len = arrs[i].Length;
|
||||
Buffer.BlockCopy(arrs[i], 0, outputbuf, bufp, len);
|
||||
bufp += len;
|
||||
}
|
||||
return outputbuf;
|
||||
}
|
||||
|
||||
public static bool ArraysEqual(byte[] a, byte[] b)
|
||||
{
|
||||
if (a == null && b != null) return false;
|
||||
if (a != null && b == null) return false;
|
||||
if (a.Length != b.Length) return false;
|
||||
for(int i = 0; i < a.Length; i++)
|
||||
{
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static byte[] SubArray(byte[] input, int offset, int length)
|
||||
{
|
||||
byte[] output = new byte[length];
|
||||
Buffer.BlockCopy(input, offset, output, 0, length);
|
||||
return output;
|
||||
}
|
||||
public static byte[] Append(byte[] a, byte[] b)
|
||||
{
|
||||
byte[] output = new byte[a.Length + b.Length];
|
||||
Buffer.BlockCopy(a, 0, output, 0, a.Length);
|
||||
Buffer.BlockCopy(b, 0, output, a.Length, b.Length);
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void PushTo(byte[] outputb, byte[] inputb, ref int offsetint)
|
||||
{
|
||||
Buffer.BlockCopy(inputb, 0, outputb, offsetint, inputb.Length);
|
||||
offsetint += inputb.Length;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
23
Sockets/INetStream.cs
Normal file
23
Sockets/INetStream.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Sockets
|
||||
{
|
||||
public interface INetStream
|
||||
{
|
||||
public Task<bool> WriteAsync(byte[] dat, int offset, int length);
|
||||
public Task<int> ReadAsync(byte[] dst, int offset, int length);
|
||||
public Task<byte[]> ReadUntilCRLF();
|
||||
public Task<byte[]> ReadUntilCRLFCRLF();
|
||||
}
|
||||
}
|
||||
266
Sockets/SecuredTCPStream.cs
Normal file
266
Sockets/SecuredTCPStream.cs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* 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.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Sockets
|
||||
{
|
||||
public class SecuredTCPStream : INetStream
|
||||
{
|
||||
private TlsStream _tls;
|
||||
|
||||
public int recvBufferSize;
|
||||
private byte[] readBuffer;
|
||||
private int rbpos = 0;
|
||||
private int rbavail = 0;
|
||||
|
||||
public SecuredTCPStream(TCPSocket _tcps)
|
||||
{
|
||||
_tls = new TlsStream(_tcps);
|
||||
readBuffer = new byte[_tcps.RecvBufferSize];
|
||||
}
|
||||
|
||||
public void SetRecvBufferSize(int bufsz)
|
||||
{
|
||||
if (recvBufferSize != bufsz)
|
||||
{
|
||||
recvBufferSize = bufsz;
|
||||
byte[] recvbf = new byte[recvBufferSize];
|
||||
int copysz = readBuffer.Length;
|
||||
if (copysz > recvbf.Length) copysz = recvbf.Length;
|
||||
Buffer.BlockCopy(readBuffer, 0, recvbf, 0, copysz);
|
||||
readBuffer = recvbf;
|
||||
}
|
||||
}
|
||||
|
||||
private int FindCRLF(byte[] inbuf, int offset, int count)
|
||||
{
|
||||
for (int i = offset; i < offset + count - 1; i++)
|
||||
{
|
||||
if (inbuf[i] == 0x0d)
|
||||
{
|
||||
if (inbuf[i + 1] == 0x0a)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
private int FindCRLFCRLF(byte[] inbuf, int offset, int count)
|
||||
{
|
||||
for (int i = offset; i < offset + count - 3; i++)
|
||||
{
|
||||
if (inbuf[i] == 0x0d)
|
||||
{
|
||||
if (inbuf[i + 1] == 0x0a)
|
||||
{
|
||||
if (inbuf[i + 2] == 0x0d)
|
||||
{
|
||||
if (inbuf[i + 3] == 0x0a)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private async Task<bool> ReadAny()
|
||||
{
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
int rlen = await _tls.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false);
|
||||
if (rlen <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
rbavail = rlen;
|
||||
return true;
|
||||
}
|
||||
private async Task<int> ReadAsyncInternal(byte[] dest, int offset, int count)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (rbavail > 0)
|
||||
{
|
||||
if (rbavail > count)
|
||||
{
|
||||
Buffer.BlockCopy(readBuffer, rbpos, dest, offset, count);
|
||||
rbpos += count;
|
||||
rbavail -= count;
|
||||
if (rbavail == 0) rbpos = 0;
|
||||
return count;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(readBuffer, rbpos, dest, offset, rbavail);
|
||||
int read = rbavail;
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
return read;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public async Task<bool> NegotiateTLSConnection(string targetDomain, TLS.TLSRecordLayer.ValidateServerCertificate validationCallback, TLS.TLSRecordLayer.ClientCertificateRequest clientRequestCallback)
|
||||
{
|
||||
return await _tls.AuthenticateAsClientAsync(targetDomain, validationCallback, clientRequestCallback).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest)
|
||||
{
|
||||
return ReadAsyncInternal(dest, 0, dest.Length);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest, int offset, int length)
|
||||
{
|
||||
return ReadAsyncInternal(dest, offset, length);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest, CancellationToken token)
|
||||
{
|
||||
return ReadAsyncInternal(dest, 0, dest.Length);
|
||||
}
|
||||
public async Task<int> ReadAsync(Memory<byte> destmem, CancellationToken token)
|
||||
{
|
||||
byte[] localBuffer = new byte[destmem.Length];
|
||||
int len = await ReadAsyncInternal(localBuffer, 0, localBuffer.Length).ConfigureAwait(false);
|
||||
if (len > 0)
|
||||
{
|
||||
Memory<byte> lmem = new Memory<byte>(localBuffer, 0, len);
|
||||
lmem.CopyTo(destmem);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
public async Task<byte[]> ReadUntilCRLF()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
int crlfi = -1;
|
||||
while (crlfi == -1)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false)) break;
|
||||
}
|
||||
crlfi = FindCRLF(readBuffer, rbpos, rbavail);
|
||||
//int resplen = ((crlfi + 2) - rbpos);
|
||||
|
||||
if (crlfi == -1)
|
||||
{
|
||||
await ms.WriteAsync(readBuffer, rbpos, rbavail).ConfigureAwait(false);
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we found crlf
|
||||
int sublen = (crlfi + 2) - rbpos;
|
||||
await ms.WriteAsync(readBuffer, rbpos, sublen).ConfigureAwait(false);
|
||||
rbavail -= sublen;
|
||||
rbpos += sublen;
|
||||
if (rbavail <= 0)
|
||||
{
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (ms.Position == 0) return null;
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
public async Task<byte[]> ReadUntilCRLFCRLF()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
int crlfi = -1;
|
||||
while (crlfi == -1)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false)) break;
|
||||
}
|
||||
crlfi = FindCRLFCRLF(readBuffer, rbpos, rbavail);
|
||||
int resplen = ((crlfi + 4) - rbpos);
|
||||
|
||||
if (crlfi == -1)
|
||||
{
|
||||
await ms.WriteAsync(readBuffer, rbpos, rbavail).ConfigureAwait(false);
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we found crlf
|
||||
await ms.WriteAsync(readBuffer, rbpos, resplen).ConfigureAwait(false);
|
||||
rbpos += resplen;
|
||||
rbavail -= resplen;
|
||||
if (rbavail <= 0)
|
||||
{
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (ms.Position == 0) return null;
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
public async Task<bool> WriteAsync(byte[] dat)
|
||||
{
|
||||
await _tls.WriteAsync(dat).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> WriteAsync(byte[] dat, int offset, int length)
|
||||
{
|
||||
await _tls.WriteAsync(dat, offset, length).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> WriteAsync(ReadOnlyMemory<byte> datrom, CancellationToken token)
|
||||
{
|
||||
await _tls.WriteAsync(datrom.ToArray(), token).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _tls.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
260
Sockets/TCPNetworkStream.cs
Normal file
260
Sockets/TCPNetworkStream.cs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* 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.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SecuCore.Sockets
|
||||
{
|
||||
public class TCPNetworkStream : INetStream
|
||||
{
|
||||
private static readonly byte[] crlfcrlf = new byte[] { 0x0d, 0x0a, 0x0d, 0x0a };
|
||||
private TCPSocket _tcpsocket;
|
||||
public int recvBufferSize;
|
||||
private byte[] readBuffer;
|
||||
private int rbpos = 0;
|
||||
private int rbavail = 0;
|
||||
|
||||
public TCPNetworkStream(TCPSocket tcpSocket)
|
||||
{
|
||||
_tcpsocket = tcpSocket;
|
||||
readBuffer = new byte[_tcpsocket.RecvBufferSize];
|
||||
recvBufferSize = _tcpsocket.RecvBufferSize;
|
||||
}
|
||||
public void SetRecvBufferSize(int bufsz)
|
||||
{
|
||||
if (recvBufferSize != bufsz)
|
||||
{
|
||||
recvBufferSize = bufsz;
|
||||
byte[] recvbf = new byte[recvBufferSize];
|
||||
int copysz = readBuffer.Length;
|
||||
if (copysz > recvbf.Length) copysz = recvbf.Length;
|
||||
Buffer.BlockCopy(readBuffer, 0, recvbf, 0, copysz);
|
||||
readBuffer = recvbf;
|
||||
}
|
||||
}
|
||||
|
||||
private int FindCRLF(byte[] inbuf, int offset, int count)
|
||||
{
|
||||
for (int i = offset; i < offset + count - 1; i++)
|
||||
{
|
||||
if (inbuf[i] == 0x0d && inbuf[i + 1] == 0x0a) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int FindCRLFCRLF(byte[] inbuf, int offset, int count)
|
||||
{
|
||||
for (int i = offset; i < offset + count - 3; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
if ((inbuf[i + j] != crlfcrlf[j]))
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private async Task<bool> ReadAny()
|
||||
{
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
int rlen = await _tcpsocket.RecieveAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false);
|
||||
if (rlen <= 0) return false;
|
||||
rbavail = rlen;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<int> ReadAsyncInternal(byte[] dest, int offset, int count)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false)) return 0;
|
||||
}
|
||||
|
||||
if (rbavail > 0)
|
||||
{
|
||||
if (rbavail > count)
|
||||
{
|
||||
Buffer.BlockCopy(readBuffer, rbpos, dest, offset, count);
|
||||
rbpos += count;
|
||||
rbavail -= count;
|
||||
if (rbavail == 0) rbpos = 0;
|
||||
return count;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer.BlockCopy(readBuffer, rbpos, dest, offset, rbavail);
|
||||
int read = rbavail;
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
return read;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadUntilCRLF()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
int crlfi = -1;
|
||||
while (crlfi == -1)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false)) return null;
|
||||
}
|
||||
crlfi = FindCRLF(readBuffer, rbpos, rbavail);
|
||||
int resplen = ((crlfi+2) - rbpos);
|
||||
|
||||
if (crlfi == -1)
|
||||
{
|
||||
await ms.WriteAsync(readBuffer, rbpos, rbavail).ConfigureAwait(false);
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we found crlf
|
||||
await ms.WriteAsync(readBuffer, rbpos, resplen).ConfigureAwait(false);
|
||||
rbpos += resplen;
|
||||
rbavail -= resplen;
|
||||
if (rbavail <= 0)
|
||||
{
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (ms.Position == 0) return null;
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadUntilCRLFCRLF()
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
int crlfi = -1;
|
||||
while (crlfi == -1)
|
||||
{
|
||||
if (rbavail == 0)
|
||||
{
|
||||
if (!await ReadAny().ConfigureAwait(false))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
crlfi = FindCRLFCRLF(readBuffer, rbpos, rbavail);
|
||||
int resplen = ((crlfi + 4) - rbpos);
|
||||
if (crlfi == -1)
|
||||
{
|
||||
await ms.WriteAsync(readBuffer, rbpos, rbavail).ConfigureAwait(false);
|
||||
rbavail = 0;
|
||||
rbpos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//we found crlf
|
||||
await ms.WriteAsync(readBuffer, rbpos, resplen).ConfigureAwait(false);
|
||||
rbpos += resplen;
|
||||
rbavail -= resplen;
|
||||
if (rbavail <= 0)
|
||||
{
|
||||
rbpos = 0;
|
||||
rbavail = 0;
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (ms.Position == 0) return null;
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
private Task<bool> WriteAsyncInternal(byte[] buf)
|
||||
{
|
||||
return _tcpsocket.SendAsync(buf);
|
||||
}
|
||||
|
||||
public Task<bool> WriteAsync(ReadOnlyMemory<byte> datrom, CancellationToken token)
|
||||
{
|
||||
byte[] localCopy = datrom.ToArray();
|
||||
return _tcpsocket.SendAsync(localCopy);
|
||||
}
|
||||
|
||||
public Task<bool> WriteAsync(byte[] dat)
|
||||
{
|
||||
return WriteAsyncInternal(dat);
|
||||
}
|
||||
public Task<bool> WriteAsync(byte[] dat, int offset, int length)
|
||||
{
|
||||
if(dat.Length == length)
|
||||
{
|
||||
return _tcpsocket.SendAsync(dat);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] localCopy = new byte[length];
|
||||
Buffer.BlockCopy(dat, offset, localCopy, 0, length);
|
||||
return _tcpsocket.SendAsync(localCopy);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest)
|
||||
{
|
||||
return ReadAsyncInternal(dest, 0, dest.Length);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest, int offset, int length)
|
||||
{
|
||||
return ReadAsyncInternal(dest, offset, length);
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] dest, CancellationToken token)
|
||||
{
|
||||
return ReadAsyncInternal(dest, 0, dest.Length);
|
||||
}
|
||||
public async Task<int> ReadAsync(Memory<byte> destmem, CancellationToken token)
|
||||
{
|
||||
byte[] localBuffer = new byte[destmem.Length];
|
||||
int len = await ReadAsyncInternal(localBuffer, 0, localBuffer.Length);
|
||||
if (len > 0)
|
||||
{
|
||||
Memory<byte> lmem = new Memory<byte>(localBuffer, 0, len);
|
||||
lmem.CopyTo(destmem);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
readBuffer = null;
|
||||
_tcpsocket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
208
Sockets/TCPSocket.cs
Normal file
208
Sockets/TCPSocket.cs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SecuCore.Proxies;
|
||||
namespace SecuCore.Sockets
|
||||
{
|
||||
public class TCPSocket
|
||||
{
|
||||
private Socket _socket;
|
||||
private Proxy _proxy;
|
||||
private TCPNetworkStream _tcpns;
|
||||
private string _remoteHost;
|
||||
private int _remotePort;
|
||||
public int connectionLevelReached = 0;
|
||||
public int ConnectTimeout { get; set; } = 10000;
|
||||
public int WriteTimeout { get; set; } = 10000;
|
||||
public int ReadTimeout { get; set; } = 10000;
|
||||
public bool NoDelay { get; set; } = false;
|
||||
private int _socketRBS = 0;
|
||||
private int _socketSBS = 0;
|
||||
private int _recvBufSz = 0;
|
||||
private int _sendBufSz = 0;
|
||||
public int SendBufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sendBufSz;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _sendBufSz)
|
||||
{
|
||||
_sendBufSz = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public int RecvBufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _recvBufSz;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _recvBufSz)
|
||||
{
|
||||
_recvBufSz = value;
|
||||
if (_tcpns != null)
|
||||
{
|
||||
_tcpns.SetRecvBufferSize(_recvBufSz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool ResetOnClose { get; set; } = false;
|
||||
public TCPSocket(string remoteHost, int remotePort, Proxy proxy = null, int wsaSockRecieveBufferSize = 8192, int wsaSockSendBufferSize = 8192)
|
||||
{
|
||||
_remoteHost = remoteHost;
|
||||
_remotePort = remotePort;
|
||||
_proxy = proxy;
|
||||
_socketRBS = wsaSockRecieveBufferSize;
|
||||
_socketSBS = wsaSockSendBufferSize;
|
||||
_recvBufSz = wsaSockRecieveBufferSize;
|
||||
_sendBufSz = wsaSockSendBufferSize;
|
||||
}
|
||||
public void SetWSASockBufferSizes(int recieve, int send)
|
||||
{
|
||||
if (_socket != null)
|
||||
{
|
||||
_socket.ReceiveBufferSize = recieve;
|
||||
_socket.SendBufferSize = send;
|
||||
}
|
||||
}
|
||||
public void SetProxy(string proxyStr, ProxyProtocol protocol)
|
||||
{
|
||||
try
|
||||
{
|
||||
this._proxy = new Proxy(proxyStr, protocol);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException(e.Message);
|
||||
}
|
||||
}
|
||||
public void SetTimeout(int milliseconds)
|
||||
{
|
||||
ConnectTimeout = milliseconds;
|
||||
WriteTimeout = milliseconds;
|
||||
ReadTimeout = milliseconds;
|
||||
}
|
||||
public TCPNetworkStream GetStream()
|
||||
{
|
||||
if (_tcpns == null) _tcpns = new TCPNetworkStream(this);
|
||||
return _tcpns;
|
||||
}
|
||||
public bool Connected() => _socket.Connected;
|
||||
public async Task<bool> ConnectAsync()
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource())
|
||||
{
|
||||
try
|
||||
{
|
||||
bool usingProxy = (_proxy != null);
|
||||
if (!IPAddress.TryParse(_remoteHost, out IPAddress rhip))
|
||||
{
|
||||
string remoteip = await DNS.AsyncDNSResolver.GetFirstArecord(_remoteHost);
|
||||
if (!IPAddress.TryParse(remoteip, out rhip))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_remoteHost = rhip.ToString();
|
||||
}
|
||||
IPEndPoint ipep = (usingProxy ? _proxy.GetRemoteEndpoint() : new IPEndPoint(rhip, _remotePort));
|
||||
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_socket.ReceiveTimeout = this.ReadTimeout;
|
||||
_socket.SendTimeout = this.WriteTimeout;
|
||||
_socket.NoDelay = this.NoDelay;
|
||||
_socket.ReceiveBufferSize = _socketRBS;
|
||||
_socket.SendBufferSize = _socketSBS;
|
||||
if (ResetOnClose) _socket.LingerState = new LingerOption(true, 0);
|
||||
Task delayTask = Task.Delay(ConnectTimeout, cts.Token);
|
||||
Task conTask = _socket.ConnectAsync(ipep, cts.Token).AsTask();
|
||||
Task ret = await Task.WhenAny(conTask, delayTask).ConfigureAwait(false);
|
||||
cts.Cancel();
|
||||
if (ret == delayTask) return false;
|
||||
if (_socket.Connected)
|
||||
{
|
||||
_socket.NoDelay = this.NoDelay;
|
||||
connectionLevelReached = 1;
|
||||
if (usingProxy)
|
||||
{
|
||||
bool proxyconres = await _proxy.ConnectAsync(GetStream(), _remoteHost, _remotePort).ConfigureAwait(false);
|
||||
if (proxyconres) connectionLevelReached = 2;
|
||||
return proxyconres;
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionLevelReached = 2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{ }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public virtual async Task<bool> SendAsync(byte[] data)
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(WriteTimeout))
|
||||
{
|
||||
ReadOnlyMemory<byte> rom = new ReadOnlyMemory<byte>(data, 0, data.Length);
|
||||
try
|
||||
{
|
||||
int remain = data.Length;
|
||||
while (remain > 0)
|
||||
{
|
||||
int sentdata = await _socket.SendAsync(rom, SocketFlags.None, cts.Token).ConfigureAwait(false);
|
||||
if (sentdata <= 0) return false;
|
||||
remain -= sentdata;
|
||||
if (remain <= 0) return true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{ }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public virtual async Task<int> RecieveAsync(byte[] dest, int offset, int count)
|
||||
{
|
||||
Memory<byte> mem = new Memory<byte>(dest, offset, count);
|
||||
try
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(ReadTimeout))
|
||||
{
|
||||
return await _socket.ReceiveAsync(mem, SocketFlags.None, cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{ }
|
||||
return 0;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
if (ResetOnClose)
|
||||
{
|
||||
if (_socket != null) _socket.Close(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_socket.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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