├── docs ├── styles │ ├── main.css │ ├── main.js │ └── search-worker.js ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── logo.svg ├── search-stopwords.json └── api │ ├── toc.html │ ├── StreamExtended.Helpers.html │ ├── StreamExtended.Models.html │ ├── StreamExtended.html │ ├── StreamExtended.Network.html │ ├── StreamExtended.IBufferPool.html │ ├── StreamExtended.Network.ICustomStreamWriter.html │ ├── StreamExtended.Helpers.BufferPool.html │ ├── StreamExtended.DefaultBufferPool.html │ ├── StreamExtended.Network.DataEventArgs.html │ ├── StreamExtended.Models.SslExtension.html │ └── StreamExtended.SslTools.html ├── _config.yml ├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── .build ├── lib │ └── Newtonsoft.Json.dll ├── docfx.json ├── setup.ps1 └── build.ps1 ├── src ├── StreamExtended │ ├── StrongKey.snk │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── BufferPool │ │ ├── IBufferPool.cs │ │ └── DefaultBufferPool.cs │ ├── Network │ │ ├── ICustomStreamWriter.cs │ │ ├── DataEventArgs.cs │ │ ├── ICustomStreamReader.cs │ │ ├── CopyStream.cs │ │ ├── ServerHelloAlpnAdderStream.cs │ │ ├── ClientHelloAlpnAdderStream.cs │ │ └── CustomBufferedPeekStream.cs │ ├── StreamExtended.Mono.csproj │ ├── StreamExtended.NetCore.csproj │ ├── StreamExtended.csproj │ ├── StreamExtended.nuspec │ ├── TaskExtended.cs │ ├── Models │ │ └── SslExtension.cs │ ├── StreamExtended.Docs.csproj │ ├── ServerHelloInfo.cs │ ├── ClientHelloInfo.cs │ └── SslTools.cs ├── StreamExtended.Docs.sln ├── StreamExtended.sln ├── StreamExtended.Mono.sln └── StreamExtended.sln.DotSettings ├── omnisharp.json ├── .vscode ├── tasks.json └── settings.json ├── LICENSE ├── appveyor.yml ├── README.md └── .gitignore /docs/styles/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | baseurl: /StreamExtended -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /.build/lib/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/.build/lib/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /src/StreamExtended/StrongKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/src/StreamExtended/StrongKey.snk -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/StreamExtended/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/stream-extended/HEAD/src/StreamExtended/Properties/AssemblyInfo.cs -------------------------------------------------------------------------------- /docs/styles/main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileOptions": { 3 | "excludeSearchPatterns": [ 4 | "**/*.Docs.csproj", 5 | "**/*.DotSettings", 6 | "**/*.sln", 7 | "**/*.Mono.csproj", 8 | "**/*.Docs.csproj", 9 | "**/StreamExtended.csproj" 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /src/StreamExtended/BufferPool/IBufferPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StreamExtended 4 | { 5 | /// 6 | /// Use this interface to implement custom buffer pool. 7 | /// To use the default buffer pool implementation use DefaultBufferPool class. 8 | /// 9 | public interface IBufferPool : IDisposable 10 | { 11 | byte[] GetBuffer(int bufferSize); 12 | void ReturnBuffer(byte[] buffer); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "process", 7 | "command": "dotnet", 8 | "args": ["build","${workspaceFolder}/src/StreamExtended/StreamExtended.NetCore.csproj"], 9 | "problemMatcher": "$msCompile", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/StreamExtended/Network/ICustomStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace StreamExtended.Network 5 | { 6 | /// 7 | /// A concrete implementation of this interface is required when calling CopyStream. 8 | /// 9 | public interface ICustomStreamWriter 10 | { 11 | void Write(byte[] buffer, int i, int bufferLength); 12 | 13 | Task WriteAsync(byte[] buffer, int i, int bufferLength, CancellationToken cancellationToken); 14 | } 15 | } -------------------------------------------------------------------------------- /.build/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ "StreamExtended.Docs.sln"], 7 | "src": "../src/" 8 | } 9 | ], 10 | "dest": "obj/api" 11 | } 12 | ], 13 | "build": { 14 | "content": [ 15 | { 16 | "files": [ "**/*.yml" ], 17 | "src": "obj/api", 18 | "dest": "api" 19 | }, 20 | { 21 | "files": [ "*.md" ] 22 | } 23 | ], 24 | "resource": [ 25 | { 26 | "files": [ ""] 27 | } 28 | ], 29 | "overwrite": "specs/*.md", 30 | "globalMetadata": { 31 | "_appTitle": "Stream Extended", 32 | "_enableSearch": true 33 | }, 34 | "dest": "../docs", 35 | "xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/StreamExtended/StreamExtended.Mono.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net45 5 | True 6 | StrongKey.snk 7 | 1.0.0 8 | false 9 | Package Description 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/StreamExtended/StreamExtended.NetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3 5 | True 6 | StrongKey.snk 7 | 1.0.0 8 | false 9 | Package Description 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/StreamExtended/StreamExtended.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net45;netstandard1.3 5 | True 6 | StrongKey.snk 7 | 1.0.0 8 | false 9 | Package Description 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/StreamExtended/Network/DataEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace StreamExtended.Network 4 | { 5 | /// 6 | /// Wraps the data sent/received event argument. 7 | /// 8 | public class DataEventArgs : EventArgs 9 | { 10 | public DataEventArgs(byte[] buffer, int offset, int count) 11 | { 12 | Buffer = buffer; 13 | Offset = offset; 14 | Count = count; 15 | } 16 | 17 | /// 18 | /// The buffer with data. 19 | /// 20 | public byte[] Buffer { get; } 21 | 22 | /// 23 | /// Offset in buffer from which valid data begins. 24 | /// 25 | public int Offset { get; } 26 | 27 | /// 28 | /// Length from offset in buffer with valid data. 29 | /// 30 | public int Count { get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // The following will hide the js and map files in the editor 3 | "files.exclude": { 4 | "**/.build": true, 5 | "**/.nuget": true, 6 | "**/.vs": true, 7 | "**/docs": true, 8 | "**/packages": true, 9 | "**/bin": true, 10 | "**/obj": true, 11 | "**/*.DotSettings": true, 12 | "**/*.sln": true, 13 | "**/*.Mono.csproj": true, 14 | "**/*.Docs.csproj": true, 15 | "**/StreamExtended.csproj": true 16 | }, 17 | "search.exclude": { 18 | "**/.build": true, 19 | "**/.nuget": true, 20 | "**/.vs": true, 21 | "**/docs": true, 22 | "**/packages": true, 23 | "**/bin": true, 24 | "**/obj": true, 25 | "**/*.DotSettings": true, 26 | "**/*.sln": true, 27 | "**/*.Mono.csproj": true, 28 | "**/*.Docs.csproj": true, 29 | "**/StreamExtended.csproj": true 30 | } 31 | } -------------------------------------------------------------------------------- /src/StreamExtended/StreamExtended.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | StreamExtended 5 | $version$ 6 | Extended SslStream 7 | honfika, justcoding121 8 | 9 | https://github.com/justcoding121/Stream-Extended/blob/master/LICENSE 10 | https://github.com/justcoding121/Stream-Extended 11 | false 12 | SNI for SslStream. 13 | 14 | Copyright © Stream-Extended All rights reserved. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/StreamExtended/TaskExtended.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace StreamExtended 9 | { 10 | /// 11 | /// Mimic a Task but you can set AsyncState 12 | /// 13 | /// 14 | public class TaskResult : IAsyncResult 15 | { 16 | Task Task; 17 | object mAsyncState; 18 | 19 | public TaskResult(Task pTask, object state) 20 | { 21 | Task = pTask; 22 | mAsyncState = state; 23 | } 24 | 25 | public object AsyncState => mAsyncState; 26 | public WaitHandle AsyncWaitHandle => ((IAsyncResult)Task).AsyncWaitHandle; 27 | public bool CompletedSynchronously => ((IAsyncResult)Task).CompletedSynchronously; 28 | public bool IsCompleted => Task.IsCompleted; 29 | public T Result => Task.Result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 StreamExtended 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by Docfx 9 | 10 | 12 | 15 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/StreamExtended/BufferPool/DefaultBufferPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace StreamExtended 4 | { 5 | 6 | /// 7 | /// A concrete IBufferPool implementation using a thread-safe stack. 8 | /// Works well when all consumers ask for buffers with the same size. 9 | /// If your application would use variable size buffers consider implementing IBufferPool using System.Buffers library from Microsoft. 10 | /// 11 | public class DefaultBufferPool : IBufferPool 12 | { 13 | private readonly ConcurrentStack buffers = new ConcurrentStack(); 14 | 15 | /// 16 | /// Gets a buffer. 17 | /// 18 | /// Size of the buffer. 19 | /// 20 | public byte[] GetBuffer(int bufferSize) 21 | { 22 | if (!buffers.TryPop(out var buffer) || buffer.Length != bufferSize) 23 | { 24 | buffer = new byte[bufferSize]; 25 | } 26 | 27 | return buffer; 28 | } 29 | 30 | /// 31 | /// Returns the buffer. 32 | /// 33 | /// The buffer. 34 | public void ReturnBuffer(byte[] buffer) 35 | { 36 | if (buffer != null) 37 | { 38 | buffers.Push(buffer); 39 | } 40 | } 41 | 42 | public void Dispose() 43 | { 44 | buffers.Clear(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/StreamExtended.Docs.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B84B-9283-4E9C-8C43-A234E9AA3EAA}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\.nuget\NuGet.Config = ..\.nuget\NuGet.Config 9 | ..\.nuget\NuGet.exe = ..\.nuget\NuGet.exe 10 | ..\.nuget\NuGet.targets = ..\.nuget\NuGet.targets 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamExtended.Docs", "StreamExtended\StreamExtended.Docs.csproj", "{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Release|Any CPU.Build.0 = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | GlobalSection(ExtensibilityGlobals) = postSolution 30 | SolutionGuid = {A250E1E5-3ABA-4FED-9A0E-6C63EB0261E0} 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /src/StreamExtended/Models/SslExtension.cs: -------------------------------------------------------------------------------- 1 | namespace StreamExtended.Models 2 | { 3 | /// 4 | /// The SSL extension information. 5 | /// 6 | public class SslExtension 7 | { 8 | /// 9 | /// Gets the value. 10 | /// 11 | /// 12 | /// The value. 13 | /// 14 | public int Value { get; } 15 | 16 | /// 17 | /// Gets the name. 18 | /// 19 | /// 20 | /// The name. 21 | /// 22 | public string Name { get; } 23 | 24 | /// 25 | /// Gets the data. 26 | /// 27 | /// 28 | /// The data. 29 | /// 30 | public string Data { get; } 31 | 32 | /// 33 | /// Gets the position. 34 | /// 35 | /// 36 | /// The position. 37 | /// 38 | public int Position { get; } 39 | 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | /// The value. 44 | /// The name. 45 | /// The data. 46 | /// The position. 47 | public SslExtension(int value, string name, string data, int position) 48 | { 49 | Value = value; 50 | Name = name; 51 | Data = data; 52 | Position = position; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /docs/search-stopwords.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a", 3 | "able", 4 | "about", 5 | "across", 6 | "after", 7 | "all", 8 | "almost", 9 | "also", 10 | "am", 11 | "among", 12 | "an", 13 | "and", 14 | "any", 15 | "are", 16 | "as", 17 | "at", 18 | "be", 19 | "because", 20 | "been", 21 | "but", 22 | "by", 23 | "can", 24 | "cannot", 25 | "could", 26 | "dear", 27 | "did", 28 | "do", 29 | "does", 30 | "either", 31 | "else", 32 | "ever", 33 | "every", 34 | "for", 35 | "from", 36 | "get", 37 | "got", 38 | "had", 39 | "has", 40 | "have", 41 | "he", 42 | "her", 43 | "hers", 44 | "him", 45 | "his", 46 | "how", 47 | "however", 48 | "i", 49 | "if", 50 | "in", 51 | "into", 52 | "is", 53 | "it", 54 | "its", 55 | "just", 56 | "least", 57 | "let", 58 | "like", 59 | "likely", 60 | "may", 61 | "me", 62 | "might", 63 | "most", 64 | "must", 65 | "my", 66 | "neither", 67 | "no", 68 | "nor", 69 | "not", 70 | "of", 71 | "off", 72 | "often", 73 | "on", 74 | "only", 75 | "or", 76 | "other", 77 | "our", 78 | "own", 79 | "rather", 80 | "said", 81 | "say", 82 | "says", 83 | "she", 84 | "should", 85 | "since", 86 | "so", 87 | "some", 88 | "than", 89 | "that", 90 | "the", 91 | "their", 92 | "them", 93 | "then", 94 | "there", 95 | "these", 96 | "they", 97 | "this", 98 | "tis", 99 | "to", 100 | "too", 101 | "twas", 102 | "us", 103 | "wants", 104 | "was", 105 | "we", 106 | "were", 107 | "what", 108 | "when", 109 | "where", 110 | "which", 111 | "while", 112 | "who", 113 | "whom", 114 | "why", 115 | "will", 116 | "with", 117 | "would", 118 | "yet", 119 | "you", 120 | "your" 121 | ] 122 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor CI build file 2 | 3 | # Notes: 4 | # - Minimal appveyor.yml file is an empty file. All sections are optional. 5 | # - Indent each level of configuration with 2 spaces. Do not use tabs! 6 | # - All section names are case-sensitive. 7 | # - Section names should be unique on each level. 8 | 9 | # version format 10 | version: 1.0.{build} 11 | image: Visual Studio 2017 12 | 13 | shallow_clone: true 14 | 15 | #---------------------------------# 16 | # build configuration # 17 | #---------------------------------# 18 | 19 | # build platform, i.e. x86, x64, Any CPU. This setting is optional. 20 | platform: Any CPU 21 | 22 | # build Configuration, i.e. Debug, Release, etc. 23 | configuration: Release 24 | 25 | # to run your custom scripts instead of automatic MSBuild 26 | build_script: 27 | - cmd: build.bat Package 28 | 29 | assembly_info: 30 | patch: true 31 | file: AssemblyInfo.* 32 | assembly_version: "{version}" 33 | assembly_file_version: "{version}" 34 | assembly_informational_version: "{version}" 35 | 36 | # to disable automatic tests 37 | test: on 38 | 39 | # skip building commits that add tags (such as release tag) 40 | skip_tags: true 41 | 42 | skip_commits: 43 | author: buildbot171 44 | files: 45 | - docs/* 46 | - README.md 47 | - LICENSE 48 | #---------------------------------# 49 | # artifacts configuration # 50 | #---------------------------------# 51 | 52 | nuget: 53 | disable_publish_on_pr: true # disable publishing of .nupkg artifacts to account/project feeds for pull request builds 54 | 55 | artifacts: 56 | - path: '**\StreamExtended.*.nupkg' 57 | 58 | environment: 59 | github_access_token: 60 | secure: mZLeq0GTB9kb5b6+HnVpJB6hhiYMJIQ2+Zf/DwZ/LEIyxJaYB1nx36aGHXE9q1cN 61 | github_email: 62 | secure: iBJZGqxyiHVNeYI0uIW+MdGd3I3pg8brJtETNRkKe/A= 63 | nuget_access_token: 64 | secure: 65rklofkoUWJzPPja621kXlxrruHemmbREnIX7QgXK0cydW412yYMZJQsN42Js27 65 | deploy: 66 | - provider: GitHub 67 | auth_token: $(github_access_token) 68 | on: 69 | branch: /(stable|beta)/ 70 | - provider: NuGet 71 | api_key: $(nuget_access_token) 72 | on: 73 | branch: /(stable|beta)/ 74 | -------------------------------------------------------------------------------- /.build/setup.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$Action="default", 3 | [hashtable]$properties=@{}, 4 | [switch]$Help 5 | ) 6 | 7 | function Install-Chocolatey() 8 | { 9 | if(-not $env:ChocolateyInstall -or -not (Test-Path "$env:ChocolateyInstall\*")) 10 | { 11 | Write-Output "Chocolatey Not Found, Installing..." 12 | iex ((new-object net.webclient).DownloadString('http://chocolatey.org/install.ps1')) 13 | } 14 | $env:Path += ";${env:ChocolateyInstall}" 15 | } 16 | 17 | function Install-Psake() 18 | { 19 | if(!(Test-Path $env:ChocolateyInstall\lib\Psake\tools\Psake*)) 20 | { 21 | choco install psake -y 22 | } 23 | } 24 | 25 | function Install-Git() 26 | { 27 | if(!((Test-Path ${env:ProgramFiles(x86)}\Git*) -Or (Test-Path ${env:ProgramFiles}\Git*))) 28 | { 29 | choco install git.install 30 | } 31 | $env:Path += ";${env:ProgramFiles(x86)}\Git" 32 | $env:Path += ";${env:ProgramFiles}\Git" 33 | } 34 | 35 | function Install-DocFx() 36 | { 37 | if(!(Test-Path $env:ChocolateyInstall\lib\docfx\tools*)) 38 | { 39 | choco install docfx --version 2.40.1 40 | } 41 | $env:Path += ";$env:ChocolateyInstall\lib\docfx\tools" 42 | } 43 | 44 | #current directory 45 | $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 46 | 47 | $ErrorActionPreference = 'Stop' 48 | Set-StrictMode -Version Latest 49 | 50 | $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition 51 | $SolutionRoot = Split-Path -Parent $ScriptPath 52 | $ToolsPath = Join-Path -Path $SolutionRoot -ChildPath "lib" 53 | 54 | if(-not $env:ChocolateyInstall) 55 | { 56 | $env:ChocolateyInstall = "${env:ALLUSERSPROFILE}\chocolatey"; 57 | } 58 | 59 | Install-Chocolatey 60 | 61 | Install-Psake 62 | 63 | Install-Git 64 | 65 | Install-DocFx 66 | 67 | $psakeDirectory = (Resolve-Path $env:ChocolateyInstall\lib\Psake*) 68 | 69 | #appveyor for some reason have different location for psake (it has older psake version?) 70 | if(Test-Path $psakeDirectory\tools\Psake\Psake.psm*) 71 | { 72 | Import-Module (Join-Path $psakeDirectory "tools\Psake\Psake.psm1") 73 | } 74 | else 75 | { 76 | Import-Module (Join-Path $psakeDirectory "tools\Psake.psm1") 77 | } 78 | 79 | 80 | #invoke the task 81 | Invoke-Psake -buildFile "$Here\build.ps1" -parameters $properties -tasklist $Action 82 | -------------------------------------------------------------------------------- /src/StreamExtended.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\LICENSE = ..\LICENSE 9 | ..\README.md = ..\README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}" 13 | ProjectSection(SolutionItems) = preProject 14 | ..\.build\build.ps1 = ..\.build\build.ps1 15 | ..\.build\docfx.json = ..\.build\docfx.json 16 | ..\.build\setup.ps1 = ..\.build\setup.ps1 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreamExtended", "StreamExtended\StreamExtended.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{AB523956-A791-4355-A6D9-EA9F0A619980}" 22 | ProjectSection(SolutionItems) = preProject 23 | ..\.nuget\NuGet.Config = ..\.nuget\NuGet.Config 24 | ..\.nuget\NuGet.exe = ..\.nuget\NuGet.exe 25 | ..\.nuget\NuGet.targets = ..\.nuget\NuGet.targets 26 | EndProjectSection 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 44 | SolutionGuid = {07A53586-3213-448B-AA07-A6BBCDF56174} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /src/StreamExtended.Mono.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\LICENSE = ..\LICENSE 9 | ..\README.md = ..\README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}" 13 | ProjectSection(SolutionItems) = preProject 14 | ..\.build\build.ps1 = ..\.build\build.ps1 15 | ..\.build\docfx.json = ..\.build\docfx.json 16 | ..\.build\setup.ps1 = ..\.build\setup.ps1 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreamExtended", "StreamExtended\StreamExtended.Mono.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{AB523956-A791-4355-A6D9-EA9F0A619980}" 22 | ProjectSection(SolutionItems) = preProject 23 | ..\.nuget\NuGet.Config = ..\.nuget\NuGet.Config 24 | ..\.nuget\NuGet.exe = ..\.nuget\NuGet.exe 25 | ..\.nuget\NuGet.targets = ..\.nuget\NuGet.targets 26 | EndProjectSection 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 44 | SolutionGuid = {07A53586-3213-448B-AA07-A6BBCDF56174} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /docs/styles/search-worker.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | importScripts('lunr.min.js'); 3 | 4 | var lunrIndex; 5 | 6 | var stopWords = null; 7 | var searchData = {}; 8 | 9 | lunr.tokenizer.seperator = /[\s\-\.]+/; 10 | 11 | var stopWordsRequest = new XMLHttpRequest(); 12 | stopWordsRequest.open('GET', '../search-stopwords.json'); 13 | stopWordsRequest.onload = function () { 14 | if (this.status != 200) { 15 | return; 16 | } 17 | stopWords = JSON.parse(this.responseText); 18 | buildIndex(); 19 | } 20 | stopWordsRequest.send(); 21 | 22 | var searchDataRequest = new XMLHttpRequest(); 23 | 24 | searchDataRequest.open('GET', '../index.json'); 25 | searchDataRequest.onload = function () { 26 | if (this.status != 200) { 27 | return; 28 | } 29 | searchData = JSON.parse(this.responseText); 30 | 31 | buildIndex(); 32 | 33 | postMessage({ e: 'index-ready' }); 34 | } 35 | searchDataRequest.send(); 36 | 37 | onmessage = function (oEvent) { 38 | var q = oEvent.data.q; 39 | var hits = lunrIndex.search(q); 40 | var results = []; 41 | hits.forEach(function (hit) { 42 | var item = searchData[hit.ref]; 43 | results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords }); 44 | }); 45 | postMessage({ e: 'query-ready', q: q, d: results }); 46 | } 47 | 48 | function buildIndex() { 49 | if (stopWords !== null && !isEmpty(searchData)) { 50 | lunrIndex = lunr(function () { 51 | this.pipeline.remove(lunr.stopWordFilter); 52 | this.ref('href'); 53 | this.field('title', { boost: 50 }); 54 | this.field('keywords', { boost: 20 }); 55 | 56 | for (var prop in searchData) { 57 | if (searchData.hasOwnProperty(prop)) { 58 | this.add(searchData[prop]); 59 | } 60 | } 61 | 62 | var docfxStopWordFilter = lunr.generateStopWordFilter(stopWords); 63 | lunr.Pipeline.registerFunction(docfxStopWordFilter, 'docfxStopWordFilter'); 64 | this.pipeline.add(docfxStopWordFilter); 65 | this.searchPipeline.add(docfxStopWordFilter); 66 | }); 67 | } 68 | } 69 | 70 | function isEmpty(obj) { 71 | if(!obj) return true; 72 | 73 | for (var prop in obj) { 74 | if (obj.hasOwnProperty(prop)) 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | })(); 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Stream extended 2 | 3 | ### Note: This Project is no longer maintained. 4 | 5 | * An extended SslStream with support for SNI 6 | * An extended BufferedStream with support for reading bytes and string 7 | 8 | ![Build Status](https://ci.appveyor.com/api/projects/status/3vp1pdya9ncmlqwq?svg=true) 9 | 10 | ## Installation 11 | 12 | Install by [nuget](https://www.nuget.org/packages/StreamExtended) 13 | 14 | Install-Package StreamExtended 15 | 16 | * [API Documentation](https://justcoding121.github.io/stream-extended/api/StreamExtended.html) 17 | 18 | Supports 19 | 20 | * .Net Standard 1.3 or above 21 | * .Net Framework 4.5 or above 22 | 23 | ### Development environment 24 | 25 | #### Windows 26 | * Visual Studio Code as IDE for .NET core 27 | * Visual Studio 2017 as IDE for .NET framework/.NET core 28 | 29 | #### Mac OS 30 | * Visual Studio Code as IDE for .NET core 31 | * Visual Studio 2017 as IDE for Mono 32 | 33 | #### Linux 34 | * Visual Studio Code as IDE for .NET core 35 | * Mono develop as IDE for Mono 36 | 37 | ## Usage 38 | 39 | ### Server Name Indication 40 | 41 | ```csharp 42 | var bufferSize = 4096; 43 | var bufferPool = new DefaultBufferPool(); 44 | var yourClientStream = new CustomBufferedStream(clientStream, bufferPool, bufferSize) 45 | var clientSslHelloInfo = await SslTools.PeekClientHello(yourClientStream, bufferPool); 46 | 47 | //will be null if no client hello was received (not a SSL connection) 48 | if (clientSslHelloInfo != null) 49 | { 50 | string sniHostName = clientSslHelloInfo.Extensions?.FirstOrDefault(x => x.Name == "server_name")?.Data; 51 | 52 | //create yourClientCertificate based on sniHostName 53 | 54 | //and now as usual 55 | var sslStream = new SslStream(yourClientStream); 56 | await sslStream.AuthenticateAsServerAsync(yourClientCertificate, false, SupportedSslProtocols, false); 57 | } 58 | ``` 59 | 60 | 61 | ## Peek SSL Information 62 | 63 | ### Peek Client SSL Hello 64 | ```csharp 65 | var bufferSize = 4096; 66 | var bufferPool = new DefaultBufferPool(); 67 | var yourClientStream = new CustomBufferedStream(clientStream, bufferPool, bufferSize) 68 | var clientSslHelloInfo = await SslTools.PeekClientHello(yourClientStream, bufferPool); 69 | 70 | //will be null if no client hello was received (not a SSL connection) 71 | if(clientSslHelloInfo!=null) 72 | { 73 | //and now as usual 74 | var sslStream = new SslStream(yourClientStream); 75 | await sslStream.AuthenticateAsServerAsync(yourClientCertificate, false, SupportedSslProtocols, false); 76 | } 77 | ``` 78 | 79 | ### Peek Server SSL Hello 80 | ```csharp 81 | var bufferSize = 4096; 82 | var bufferPool = new DefaultBufferPool(); 83 | var yourServerStream = new CustomBufferedStream(serverStream, bufferPool, bufferSize) 84 | var serverSslHelloInfo = await SslTools.PeekServerHello(yourServerStream, bufferPool); 85 | 86 | //will be null if no server hello was received (not a SSL connection) 87 | if(serverSslHelloInfo!=null) 88 | { 89 | //and now as usual 90 | var sslStream = new SslStream(yourServerStream, false, null, null); 91 | await sslStream.AuthenticateAsClientAsync(yourRemoteHostName, null, yourSupportedSslProtocols, false); 92 | 93 | } 94 | ``` 95 | 96 | ## Note to contributors 97 | 98 | Special thanks to [@honfika](https://github.com/honfika) who contributed this code [originally in Titanium Web Proxy](https://github.com/justcoding121/Titanium-Web-Proxy/issues/293) project. 99 | 100 | ### Collaborators 101 | 102 | * [honfika](https://github.com/honfika) 103 | -------------------------------------------------------------------------------- /docs/api/toc.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 69 |
70 |
71 |
72 |
-------------------------------------------------------------------------------- /src/StreamExtended/Network/ICustomStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace StreamExtended.Network 6 | { 7 | /// 8 | /// This concrete implemetation of interface acts as the source stream for CopyStream class. 9 | /// 10 | public interface ICustomStreamReader 11 | { 12 | int BufferSize { get; } 13 | 14 | int Available { get; } 15 | 16 | bool DataAvailable { get; } 17 | 18 | /// 19 | /// Fills the buffer asynchronous. 20 | /// 21 | /// 22 | Task FillBufferAsync(CancellationToken cancellationToken = default(CancellationToken)); 23 | 24 | /// 25 | /// Peeks a byte from buffer. 26 | /// 27 | /// The index. 28 | /// 29 | /// Index is out of buffer size 30 | byte PeekByteFromBuffer(int index); 31 | 32 | /// 33 | /// Peeks a byte asynchronous. 34 | /// 35 | /// The index. 36 | /// The cancellation token. 37 | /// 38 | Task PeekByteAsync(int index, CancellationToken cancellationToken = default(CancellationToken)); 39 | 40 | /// 41 | /// Peeks bytes asynchronous. 42 | /// 43 | /// The index. 44 | /// The cancellation token. 45 | /// 46 | Task PeekBytesAsync(int index, int size, CancellationToken cancellationToken = default(CancellationToken)); 47 | 48 | byte ReadByteFromBuffer(); 49 | 50 | /// 51 | /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. 52 | /// 53 | /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. 54 | /// The zero-based byte offset in at which to begin storing the data read from the current stream. 55 | /// The maximum number of bytes to be read from the current stream. 56 | /// 57 | /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. 58 | /// 59 | int Read(byte[] buffer, int offset, int count); 60 | 61 | /// 62 | /// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// The number of bytes read 69 | Task ReadAsync(byte[] buffer, int offset, int bytesToRead, 70 | CancellationToken cancellationToken = default(CancellationToken)); 71 | 72 | /// 73 | /// Read a line from the byte stream 74 | /// 75 | /// 76 | Task ReadLineAsync(CancellationToken cancellationToken = default(CancellationToken)); 77 | } 78 | } -------------------------------------------------------------------------------- /src/StreamExtended/StreamExtended.Docs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EBF2EA46-EA00-4350-BE1D-D86AFD699DB3} 8 | Library 9 | Properties 10 | StreamExtended 11 | StreamExtended 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | bin\Debug\StreamExtended.xml 26 | latest 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | # TODO: Comment the next line if you want to checkin your web deploy settings 137 | # but database connection strings (with potential passwords) will be unencrypted 138 | *.pubxml 139 | *.publishproj 140 | 141 | # NuGet Packages 142 | *.nupkg 143 | # The packages folder can be ignored because of Package Restore 144 | **/packages/* 145 | # except build/, which is used as an MSBuild target. 146 | !**/packages/build/ 147 | # Uncomment if necessary however generally it will be regenerated when needed 148 | #!**/packages/repositories.config 149 | 150 | # Windows Azure Build Output 151 | csx/ 152 | *.build.csdef 153 | 154 | # Windows Store app package directory 155 | AppPackages/ 156 | 157 | # Visual Studio cache files 158 | # files ending in .cache can be ignored 159 | *.[Cc]ache 160 | # but keep track of directories ending in .cache 161 | !*.[Cc]ache/ 162 | 163 | # Others 164 | ClientBin/ 165 | [Ss]tyle[Cc]op.* 166 | ~$* 167 | *~ 168 | *.dbmdl 169 | *.dbproj.schemaview 170 | *.pfx 171 | *.publishsettings 172 | node_modules/ 173 | orleans.codegen.cs 174 | 175 | # RIA/Silverlight projects 176 | Generated_Code/ 177 | 178 | # Backup & report files from converting an old project file 179 | # to a newer Visual Studio version. Backup files are not needed, 180 | # because we have git ;-) 181 | _UpgradeReport_Files/ 182 | Backup*/ 183 | UpgradeLog*.XML 184 | UpgradeLog*.htm 185 | 186 | # SQL Server files 187 | *.mdf 188 | *.ldf 189 | 190 | # Business Intelligence projects 191 | *.rdl.data 192 | *.bim.layout 193 | *.bim_*.settings 194 | 195 | # Microsoft Fakes 196 | FakesAssemblies/ 197 | 198 | # Node.js Tools for Visual Studio 199 | .ntvs_analysis.dat 200 | 201 | # Visual Studio 6 build log 202 | *.plg 203 | 204 | # Visual Studio 6 workspace options file 205 | *.opt 206 | 207 | # Docfx 208 | docs/manifest.json -------------------------------------------------------------------------------- /src/StreamExtended/ServerHelloInfo.cs: -------------------------------------------------------------------------------- 1 | using StreamExtended.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace StreamExtended 8 | { 9 | /// 10 | /// Wraps up the server SSL hello information. 11 | /// 12 | public class ServerHelloInfo 13 | { 14 | private static readonly string[] compressions = { 15 | "null", 16 | "DEFLATE" 17 | }; 18 | 19 | public int HandshakeVersion { get; set; } 20 | 21 | public int MajorVersion { get; set; } 22 | 23 | public int MinorVersion { get; set; } 24 | 25 | public byte[] Random { get; set; } 26 | 27 | public DateTime Time 28 | { 29 | get 30 | { 31 | DateTime time = DateTime.MinValue; 32 | if (Random.Length > 3) 33 | { 34 | time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) 35 | .AddSeconds(((uint)Random[3] << 24) + ((uint)Random[2] << 16) + ((uint)Random[1] << 8) + (uint)Random[0]).ToLocalTime(); 36 | } 37 | 38 | return time; 39 | } 40 | } 41 | 42 | public byte[] SessionId { get; set; } 43 | 44 | public int CipherSuite { get; set; } 45 | 46 | public byte CompressionMethod { get; set; } 47 | 48 | internal int ServerHelloLength { get; set; } 49 | 50 | internal int EntensionsStartPosition { get; set; } 51 | 52 | public Dictionary Extensions { get; set; } 53 | 54 | private static string SslVersionToString(int major, int minor) 55 | { 56 | string str = "Unknown"; 57 | if (major == 3 && minor == 3) 58 | str = "TLS/1.2"; 59 | else if (major == 3 && minor == 2) 60 | str = "TLS/1.1"; 61 | else if (major == 3 && minor == 1) 62 | str = "TLS/1.0"; 63 | else if (major == 3 && minor == 0) 64 | str = "SSL/3.0"; 65 | else if (major == 2 && minor == 0) 66 | str = "SSL/2.0"; 67 | 68 | return $"{major}.{minor} ({str})"; 69 | } 70 | 71 | /// 72 | /// Returns a that represents this instance. 73 | /// 74 | /// 75 | /// A that represents this instance. 76 | /// 77 | public override string ToString() 78 | { 79 | var sb = new StringBuilder(); 80 | sb.AppendLine($"A SSLv{HandshakeVersion}-compatible ServerHello handshake was found. Titanium extracted the parameters below."); 81 | sb.AppendLine(); 82 | sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}"); 83 | sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}"); 84 | sb.AppendLine($"\"Time\": {Time}"); 85 | sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}"); 86 | 87 | if (Extensions != null) 88 | { 89 | sb.AppendLine("Extensions:"); 90 | foreach (var extension in Extensions.Values.OrderBy(x => x.Position)) 91 | { 92 | sb.AppendLine($"{extension.Name}: {extension.Data}"); 93 | } 94 | } 95 | 96 | string compression = compressions.Length > CompressionMethod 97 | ? compressions[CompressionMethod] 98 | : $"unknown [0x{CompressionMethod:X2}]"; 99 | sb.AppendLine($"Compression: {compression}"); 100 | 101 | sb.Append("Cipher:"); 102 | if (!SslCiphers.Ciphers.TryGetValue(CipherSuite, out string cipherStr)) 103 | { 104 | cipherStr = "unknown"; 105 | } 106 | 107 | sb.AppendLine($"[0x{CipherSuite:X4}] {cipherStr}"); 108 | 109 | return sb.ToString(); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /.build/build.ps1: -------------------------------------------------------------------------------- 1 | $PSake.use_exit_on_error = $true 2 | 3 | $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 4 | 5 | $RepoRoot = $(Split-Path -parent $Here) 6 | $SolutionRoot = "$RepoRoot\src" 7 | 8 | $ProjectName = "StreamExtended" 9 | $GitHubProjectName = "StreamExtended" 10 | $GitHubUserName = "justcoding121" 11 | 12 | $SolutionFile = "$SolutionRoot\$ProjectName.sln" 13 | 14 | ## This comes from the build server iteration 15 | if(!$BuildNumber) { $BuildNumber = $env:APPVEYOR_BUILD_NUMBER } 16 | if(!$BuildNumber) { $BuildNumber = "0"} 17 | 18 | ## The build configuration, i.e. Debug/Release 19 | if(!$Configuration) { $Configuration = $env:Configuration } 20 | if(!$Configuration) { $Configuration = "Release" } 21 | 22 | if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION } 23 | if(!$Version) { $Version = "0.0.$BuildNumber" } 24 | 25 | if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH } 26 | if(!$Branch) { $Branch = "local" } 27 | 28 | if($Branch -eq "beta" ) { $Version = "$Version-beta" } 29 | 30 | $NuGet = Join-Path $RepoRoot ".nuget\nuget.exe" 31 | 32 | $MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" 33 | $MSBuild -replace ' ', '` ' 34 | 35 | FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) 36 | 37 | #default task 38 | Task default -depends Clean, Build, Document, Package 39 | 40 | #cleans obj, b 41 | Task Clean { 42 | Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { Remove-Item $_.fullname -Force -Recurse } 43 | exec { . $MSBuild $SolutionFile /t:Clean /v:quiet } 44 | } 45 | 46 | #install build tools 47 | Task Install-BuildTools -depends Clean { 48 | if(!(Test-Path $MSBuild)) 49 | { 50 | cinst microsoft-build-tools -y 51 | } 52 | } 53 | 54 | #restore nuget packages 55 | Task Restore-Packages -depends Install-BuildTools { 56 | exec { . dotnet restore "$SolutionRoot\$ProjectName.sln" } 57 | } 58 | 59 | #build 60 | Task Build -depends Restore-Packages{ 61 | exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /t:restore } 62 | } 63 | 64 | #publish API documentation changes for GitHub pages under master\docs directory 65 | Task Document -depends Build { 66 | 67 | if($Branch -eq "master") 68 | { 69 | 70 | #use docfx to generate API documentation from source metadata 71 | docfx docfx.json 72 | 73 | #patch index.json so that it is always sorted 74 | #otherwise git will think file was changed 75 | $IndexJsonFile = "$RepoRoot\docs\index.json" 76 | $unsorted = Get-Content $IndexJsonFile | Out-String 77 | [Reflection.Assembly]::LoadFile("$Here\lib\Newtonsoft.Json.dll") 78 | [System.Reflection.Assembly]::LoadWithPartialName("System") 79 | $hashTable = [Newtonsoft.Json.JsonConvert]::DeserializeObject($unsorted, [System.Collections.Generic.SortedDictionary[[string],[object]]]) 80 | $obj = [Newtonsoft.Json.JsonConvert]::SerializeObject($hashTable, [Newtonsoft.Json.Formatting]::Indented) 81 | Set-Content -Path $IndexJsonFile -Value $obj 82 | 83 | #setup clone directory 84 | $TEMP_REPO_DIR =(Split-Path -parent $RepoRoot) + "\temp-repo-clone" 85 | 86 | If(test-path $TEMP_REPO_DIR) 87 | { 88 | Remove-Item $TEMP_REPO_DIR -Force -Recurse 89 | } 90 | 91 | New-Item -ItemType Directory -Force -Path $TEMP_REPO_DIR 92 | 93 | #clone 94 | git clone https://github.com/$GitHubUserName/$GitHubProjectName.git --branch master $TEMP_REPO_DIR 95 | 96 | If(test-path "$TEMP_REPO_DIR\docs") 97 | { 98 | Remove-Item "$TEMP_REPO_DIR\docs" -Force -Recurse 99 | } 100 | New-Item -ItemType Directory -Force -Path "$TEMP_REPO_DIR\docs" 101 | 102 | #cd to docs folder 103 | cd "$TEMP_REPO_DIR\docs" 104 | 105 | #copy docs to clone directory\docs 106 | Copy-Item -Path "$RepoRoot\docs\*" -Destination "$TEMP_REPO_DIR\docs" -Recurse -Force 107 | 108 | #push changes to master 109 | git config --global credential.helper store 110 | Add-Content "$HOME\.git-credentials" "https://$($env:github_access_token):x-oauth-basic@github.com`n" 111 | git config --global user.email $env:github_email 112 | git config --global user.name "buildbot171" 113 | git add . -A 114 | git commit -m "API documentation update by build server" 115 | git push origin master 116 | 117 | #move cd back to current location 118 | cd $Here 119 | } 120 | } 121 | 122 | #package nuget files 123 | Task Package -depends Document { 124 | exec { . $NuGet pack "$SolutionRoot\$ProjectName\$ProjectName.nuspec" -Properties Configuration=$Configuration -OutputDirectory "$RepoRoot" -Version "$Version" } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/StreamExtended/ClientHelloInfo.cs: -------------------------------------------------------------------------------- 1 | using StreamExtended.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace StreamExtended 8 | { 9 | /// 10 | /// Wraps up the client SSL hello information. 11 | /// 12 | public class ClientHelloInfo 13 | { 14 | private static readonly string[] compressions = { 15 | "null", 16 | "DEFLATE" 17 | }; 18 | 19 | public int HandshakeVersion { get; set; } 20 | 21 | public int MajorVersion { get; set; } 22 | 23 | public int MinorVersion { get; set; } 24 | 25 | public byte[] Random { get; set; } 26 | 27 | public DateTime Time 28 | { 29 | get 30 | { 31 | DateTime time = DateTime.MinValue; 32 | if (Random.Length > 3) 33 | { 34 | time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) 35 | .AddSeconds(((uint)Random[3] << 24) + ((uint)Random[2] << 16) + ((uint)Random[1] << 8) + (uint)Random[0]).ToLocalTime(); 36 | } 37 | 38 | return time; 39 | } 40 | } 41 | 42 | public byte[] SessionId { get; set; } 43 | 44 | public int[] Ciphers { get; set; } 45 | 46 | public byte[] CompressionData { get; set; } 47 | 48 | internal int ClientHelloLength { get; set; } 49 | 50 | internal int EntensionsStartPosition { get; set; } 51 | 52 | public Dictionary Extensions { get; set; } 53 | 54 | private static string SslVersionToString(int major, int minor) 55 | { 56 | string str = "Unknown"; 57 | if (major == 3 && minor == 3) 58 | str = "TLS/1.2"; 59 | else if (major == 3 && minor == 2) 60 | str = "TLS/1.1"; 61 | else if (major == 3 && minor == 1) 62 | str = "TLS/1.0"; 63 | else if (major == 3 && minor == 0) 64 | str = "SSL/3.0"; 65 | else if (major == 2 && minor == 0) 66 | str = "SSL/2.0"; 67 | 68 | return $"{major}.{minor} ({str})"; 69 | } 70 | 71 | /// 72 | /// Returns a that represents this instance. 73 | /// 74 | /// 75 | /// A that represents this instance. 76 | /// 77 | public override string ToString() 78 | { 79 | var sb = new StringBuilder(); 80 | sb.AppendLine($"A SSLv{HandshakeVersion}-compatible ClientHello handshake was found. Titanium extracted the parameters below."); 81 | sb.AppendLine(); 82 | sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}"); 83 | sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}"); 84 | sb.AppendLine($"\"Time\": {Time}"); 85 | sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}"); 86 | 87 | if (Extensions != null) 88 | { 89 | sb.AppendLine("Extensions:"); 90 | foreach (var extension in Extensions.Values.OrderBy(x => x.Position)) 91 | { 92 | sb.AppendLine($"{extension.Name}: {extension.Data}"); 93 | } 94 | } 95 | 96 | if (CompressionData != null && CompressionData.Length > 0) 97 | { 98 | int compressionMethod = CompressionData[0]; 99 | string compression = compressions.Length > compressionMethod 100 | ? compressions[compressionMethod] 101 | : $"unknown [0x{compressionMethod:X2}]"; 102 | sb.AppendLine($"Compression: {compression}"); 103 | } 104 | 105 | if (Ciphers.Length > 0) 106 | { 107 | sb.AppendLine("Ciphers:"); 108 | foreach (int cipherSuite in Ciphers) 109 | { 110 | if (!SslCiphers.Ciphers.TryGetValue(cipherSuite, out string cipherStr)) 111 | { 112 | cipherStr = "unknown"; 113 | } 114 | 115 | sb.AppendLine($"[0x{cipherSuite:X4}] {cipherStr}"); 116 | } 117 | } 118 | 119 | return sb.ToString(); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/StreamExtended/Network/CopyStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace StreamExtended.Network 6 | { 7 | /// 8 | /// Copies the source stream to destination stream. 9 | /// But this let users to peek and read the copying process. 10 | /// 11 | public class CopyStream : ICustomStreamReader, IDisposable 12 | { 13 | private readonly ICustomStreamReader reader; 14 | 15 | private readonly ICustomStreamWriter writer; 16 | 17 | private readonly IBufferPool bufferPool; 18 | 19 | public int BufferSize { get; } 20 | 21 | private int bufferLength; 22 | 23 | private byte[] buffer; 24 | 25 | private bool disposed; 26 | 27 | public int Available => reader.Available; 28 | 29 | public bool DataAvailable => reader.DataAvailable; 30 | 31 | public long ReadBytes { get; private set; } 32 | 33 | public CopyStream(ICustomStreamReader reader, ICustomStreamWriter writer, IBufferPool bufferPool, int bufferSize) 34 | { 35 | this.reader = reader; 36 | this.writer = writer; 37 | BufferSize = bufferSize; 38 | buffer = bufferPool.GetBuffer(bufferSize); 39 | } 40 | 41 | public async Task FillBufferAsync(CancellationToken cancellationToken = default(CancellationToken)) 42 | { 43 | await FlushAsync(cancellationToken); 44 | return await reader.FillBufferAsync(cancellationToken); 45 | } 46 | 47 | public byte PeekByteFromBuffer(int index) 48 | { 49 | return reader.PeekByteFromBuffer(index); 50 | } 51 | 52 | public Task PeekByteAsync(int index, CancellationToken cancellationToken = default(CancellationToken)) 53 | { 54 | return reader.PeekByteAsync(index, cancellationToken); 55 | } 56 | 57 | public Task PeekBytesAsync(int index, int size, CancellationToken cancellationToken = default(CancellationToken)) 58 | { 59 | return reader.PeekBytesAsync(index, size, cancellationToken); 60 | } 61 | 62 | public void Flush() 63 | { 64 | //send out the current data from from the buffer 65 | if (bufferLength > 0) 66 | { 67 | writer.Write(buffer, 0, bufferLength); 68 | bufferLength = 0; 69 | } 70 | } 71 | 72 | public async Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) 73 | { 74 | //send out the current data from from the buffer 75 | if (bufferLength > 0) 76 | { 77 | await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken); 78 | bufferLength = 0; 79 | } 80 | } 81 | 82 | public byte ReadByteFromBuffer() 83 | { 84 | byte b = reader.ReadByteFromBuffer(); 85 | buffer[bufferLength++] = b; 86 | ReadBytes++; 87 | return b; 88 | } 89 | 90 | public int Read(byte[] buffer, int offset, int count) 91 | { 92 | int result = reader.Read(buffer, offset, count); 93 | if (result > 0) 94 | { 95 | if (bufferLength + result > BufferSize) 96 | { 97 | Flush(); 98 | } 99 | 100 | Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result); 101 | bufferLength += result; 102 | ReadBytes += result; 103 | Flush(); 104 | } 105 | 106 | return result; 107 | } 108 | 109 | public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken)) 110 | { 111 | int result = await reader.ReadAsync(buffer, offset, count, cancellationToken); 112 | if (result > 0) 113 | { 114 | if (bufferLength + result > BufferSize) 115 | { 116 | await FlushAsync(cancellationToken); 117 | } 118 | 119 | Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result); 120 | bufferLength += result; 121 | ReadBytes += result; 122 | await FlushAsync(cancellationToken); 123 | } 124 | 125 | return result; 126 | } 127 | 128 | public Task ReadLineAsync(CancellationToken cancellationToken = default(CancellationToken)) 129 | { 130 | return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); 131 | } 132 | 133 | public void Dispose() 134 | { 135 | if (!disposed) 136 | { 137 | disposed = true; 138 | var b = buffer; 139 | buffer = null; 140 | bufferPool.ReturnBuffer(b); 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/StreamExtended/Network/ServerHelloAlpnAdderStream.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Threading; 4 | 5 | namespace StreamExtended.Network 6 | { 7 | public class ServerHelloAlpnAdderStream : Stream 8 | { 9 | private readonly IBufferPool bufferPool; 10 | private readonly CustomBufferedStream stream; 11 | 12 | private bool called; 13 | 14 | public ServerHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool) 15 | { 16 | this.bufferPool = bufferPool; 17 | this.stream = stream; 18 | } 19 | 20 | public override void Flush() 21 | { 22 | stream.Flush(); 23 | } 24 | 25 | public override long Seek(long offset, SeekOrigin origin) 26 | { 27 | return stream.Seek(offset, origin); 28 | } 29 | 30 | public override void SetLength(long value) 31 | { 32 | stream.SetLength(value); 33 | } 34 | 35 | [DebuggerStepThrough] 36 | public override int Read(byte[] buffer, int offset, int count) 37 | { 38 | return stream.Read(buffer, offset, count); 39 | } 40 | 41 | public override void Write(byte[] buffer, int offset, int count) 42 | { 43 | if (called) 44 | { 45 | stream.Write(buffer, offset, count); 46 | return; 47 | } 48 | 49 | called = true; 50 | var ms = new MemoryStream(buffer, offset, count); 51 | 52 | //this can be non async, because reads from a memory stream 53 | var cts = new CancellationTokenSource(); 54 | var serverHello = SslTools.PeekServerHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result; 55 | if (serverHello != null) 56 | { 57 | // 0x00 0x10: ALPN identifier 58 | // 0x00 0x0e: length of ALPN data 59 | // 0x00 0x0c: length of ALPN data again:) 60 | var dataToAdd = new byte[] 61 | { 62 | 0x0, 0x10, 0x0, 0x5, 0x0, 0x3, 63 | 2, (byte)'h', (byte)'2' 64 | }; 65 | 66 | int newByteCount = serverHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length; 67 | var buffer2 = new byte[buffer.Length + newByteCount]; 68 | 69 | for (int i = 0; i < buffer.Length; i++) 70 | { 71 | buffer2[i] = buffer[i]; 72 | } 73 | 74 | //this is a hacky solution, but works 75 | int length = (buffer[offset + 3] << 8) + buffer[offset + 4]; 76 | length += newByteCount; 77 | buffer2[offset + 3] = (byte)(length >> 8); 78 | buffer2[offset + 4] = (byte)length; 79 | 80 | length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8]; 81 | length += newByteCount; 82 | buffer2[offset + 6] = (byte)(length >> 16); 83 | buffer2[offset + 7] = (byte)(length >> 8); 84 | buffer2[offset + 8] = (byte)length; 85 | 86 | int pos = offset + serverHello.EntensionsStartPosition; 87 | int endPos = offset + serverHello.ServerHelloLength; 88 | if (serverHello.Extensions != null) 89 | { 90 | // update ALPN length 91 | length = (buffer[pos] << 8) + buffer[pos + 1]; 92 | length += newByteCount; 93 | buffer2[pos] = (byte)(length >> 8); 94 | buffer2[pos + 1] = (byte)length; 95 | } 96 | else 97 | { 98 | // add ALPN length 99 | length = dataToAdd.Length; 100 | buffer2[pos] = (byte)(length >> 8); 101 | buffer2[pos + 1] = (byte)length; 102 | endPos += 2; 103 | } 104 | 105 | for (int i = 0; i < dataToAdd.Length; i++) 106 | { 107 | buffer2[endPos + i] = dataToAdd[i]; 108 | } 109 | 110 | // copy the reamining data if any 111 | for (int i = serverHello.ServerHelloLength; i < count; i++) 112 | { 113 | buffer2[offset + newByteCount + i] = buffer[offset + i]; 114 | } 115 | 116 | buffer = buffer2; 117 | count += newByteCount; 118 | } 119 | 120 | stream.Write(buffer, offset, count); 121 | } 122 | 123 | public override bool CanRead => stream.CanRead; 124 | 125 | public override bool CanSeek => stream.CanSeek; 126 | 127 | public override bool CanWrite => stream.CanWrite; 128 | 129 | public override long Length => stream.Length; 130 | 131 | public override long Position 132 | { 133 | get => stream.Position; 134 | set => stream.Position = value; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/StreamExtended/Network/ClientHelloAlpnAdderStream.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Threading; 4 | 5 | namespace StreamExtended.Network 6 | { 7 | public class ClientHelloAlpnAdderStream : Stream 8 | { 9 | private readonly CustomBufferedStream stream; 10 | private readonly IBufferPool bufferPool; 11 | 12 | private bool called; 13 | 14 | public ClientHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool) 15 | { 16 | this.stream = stream; 17 | } 18 | 19 | public override void Flush() 20 | { 21 | stream.Flush(); 22 | } 23 | 24 | public override long Seek(long offset, SeekOrigin origin) 25 | { 26 | return stream.Seek(offset, origin); 27 | } 28 | 29 | public override void SetLength(long value) 30 | { 31 | stream.SetLength(value); 32 | } 33 | 34 | [DebuggerStepThrough] 35 | public override int Read(byte[] buffer, int offset, int count) 36 | { 37 | return stream.Read(buffer, offset, count); 38 | } 39 | 40 | public override void Write(byte[] buffer, int offset, int count) 41 | { 42 | if (called) 43 | { 44 | stream.Write(buffer, offset, count); 45 | return; 46 | } 47 | 48 | called = true; 49 | var ms = new MemoryStream(buffer, offset, count); 50 | 51 | //this can be non async, because reads from a memory stream 52 | var cts = new CancellationTokenSource(); 53 | var clientHello = SslTools.PeekClientHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result; 54 | if (clientHello != null) 55 | { 56 | // 0x00 0x10: ALPN identifier 57 | // 0x00 0x0e: length of ALPN data 58 | // 0x00 0x0c: length of ALPN data again:) 59 | var dataToAdd = new byte[] 60 | { 61 | 0x0, 0x10, 0x0, 0xE, 0x0, 0xC, 62 | 2, (byte)'h', (byte)'2', 63 | 8, (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'1' 64 | }; 65 | 66 | int newByteCount = clientHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length; 67 | var buffer2 = new byte[buffer.Length + newByteCount]; 68 | 69 | for (int i = 0; i < buffer.Length; i++) 70 | { 71 | buffer2[i] = buffer[i]; 72 | } 73 | 74 | //this is a hacky solution, but works 75 | int length = (buffer[offset + 3] << 8) + buffer[offset + 4]; 76 | length += newByteCount; 77 | buffer2[offset + 3] = (byte)(length >> 8); 78 | buffer2[offset + 4] = (byte)length; 79 | 80 | length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8]; 81 | length += newByteCount; 82 | buffer2[offset + 6] = (byte)(length >> 16); 83 | buffer2[offset + 7] = (byte)(length >> 8); 84 | buffer2[offset + 8] = (byte)length; 85 | 86 | int pos = offset + clientHello.EntensionsStartPosition; 87 | int endPos = offset + clientHello.ClientHelloLength; 88 | if (clientHello.Extensions != null) 89 | { 90 | // update ALPN length 91 | length = (buffer[pos] << 8) + buffer[pos + 1]; 92 | length += newByteCount; 93 | buffer2[pos] = (byte)(length >> 8); 94 | buffer2[pos + 1] = (byte)length; 95 | } 96 | else 97 | { 98 | // add ALPN length 99 | length = dataToAdd.Length; 100 | buffer2[pos] = (byte)(length >> 8); 101 | buffer2[pos + 1] = (byte)length; 102 | endPos += 2; 103 | } 104 | 105 | for (int i = 0; i < dataToAdd.Length; i++) 106 | { 107 | buffer2[endPos + i] = dataToAdd[i]; 108 | } 109 | 110 | // copy the reamining data if any 111 | for (int i = clientHello.ClientHelloLength; i < count; i++) 112 | { 113 | buffer2[offset + newByteCount + i] = buffer[offset + i]; 114 | } 115 | 116 | buffer = buffer2; 117 | count += newByteCount; 118 | } 119 | 120 | stream.Write(buffer, offset, count); 121 | } 122 | 123 | public override bool CanRead => stream.CanRead; 124 | 125 | public override bool CanSeek => stream.CanSeek; 126 | 127 | public override bool CanWrite => stream.CanWrite; 128 | 129 | public override long Length => stream.Length; 130 | 131 | public override long Position 132 | { 133 | get => stream.Position; 134 | set => stream.Position = value; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /docs/api/StreamExtended.Helpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Namespace StreamExtended.Helpers 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 52 | 53 | 60 |
61 |
62 | 63 |
64 |
65 |
66 |

67 |
68 |
    69 |
    70 |
    71 | 108 | 109 |
    110 |
    111 | 120 |
    121 |
    122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Models.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Namespace StreamExtended.Models 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    27 |
    28 | 29 | 52 | 53 | 60 |
    61 |
    62 | 63 |
    64 |
    65 |
    66 |

    67 |
    68 |
      69 |
      70 |
      71 | 109 | 110 |
      111 |
      112 | 121 |
      122 |
      123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/StreamExtended.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | False 3 | True 4 | NEVER 5 | NEVER 6 | LINE_BREAK 7 | LINE_BREAK 8 | False 9 | True 10 | 160 11 | UseExplicitType 12 | UseVar 13 | BC 14 | CN 15 | DN 16 | EKU 17 | KU 18 | MTA 19 | OID 20 | OIDS 21 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 22 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 23 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 24 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 25 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 26 | True 27 | True 28 | True 29 | True 30 | True 31 | True 32 | True 33 | True -------------------------------------------------------------------------------- /src/StreamExtended/Network/CustomBufferedPeekStream.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace StreamExtended.Network 5 | { 6 | internal class CustomBufferedPeekStream : ICustomStreamReader 7 | { 8 | private readonly IBufferPool bufferPool; 9 | private readonly ICustomStreamReader baseStream; 10 | 11 | internal int Position { get; private set; } 12 | 13 | internal CustomBufferedPeekStream(ICustomStreamReader baseStream, IBufferPool bufferPool, int startPosition = 0) 14 | { 15 | this.bufferPool = bufferPool; 16 | this.baseStream = baseStream; 17 | Position = startPosition; 18 | } 19 | 20 | int ICustomStreamReader.BufferSize => baseStream.BufferSize; 21 | 22 | /// 23 | /// Gets a value indicating whether data is available. 24 | /// 25 | bool ICustomStreamReader.DataAvailable => Available > 0; 26 | 27 | /// 28 | /// Gets the available data size. 29 | /// 30 | public int Available => baseStream.Available - Position; 31 | 32 | internal async Task EnsureBufferLength(int length, CancellationToken cancellationToken) 33 | { 34 | var val = await baseStream.PeekByteAsync(Position + length - 1, cancellationToken); 35 | return val != -1; 36 | } 37 | 38 | internal byte ReadByte() 39 | { 40 | return baseStream.PeekByteFromBuffer(Position++); 41 | } 42 | 43 | internal int ReadInt16() 44 | { 45 | int i1 = ReadByte(); 46 | int i2 = ReadByte(); 47 | return (i1 << 8) + i2; 48 | } 49 | 50 | internal int ReadInt24() 51 | { 52 | int i1 = ReadByte(); 53 | int i2 = ReadByte(); 54 | int i3 = ReadByte(); 55 | return (i1 << 16) + (i2 << 8) + i3; 56 | } 57 | 58 | internal byte[] ReadBytes(int length) 59 | { 60 | var buffer = new byte[length]; 61 | for (int i = 0; i < buffer.Length; i++) 62 | { 63 | buffer[i] = ReadByte(); 64 | } 65 | 66 | return buffer; 67 | } 68 | 69 | /// 70 | /// Fills the buffer asynchronous. 71 | /// 72 | /// 73 | Task ICustomStreamReader.FillBufferAsync(CancellationToken cancellationToken) 74 | { 75 | return baseStream.FillBufferAsync(cancellationToken); 76 | } 77 | 78 | /// 79 | /// Peeks a byte from buffer. 80 | /// 81 | /// The index. 82 | /// 83 | byte ICustomStreamReader.PeekByteFromBuffer(int index) 84 | { 85 | return baseStream.PeekByteFromBuffer(index); 86 | } 87 | 88 | /// 89 | /// Peeks bytes asynchronous. 90 | /// 91 | /// The index. 92 | /// The cancellation token. 93 | /// 94 | Task ICustomStreamReader.PeekBytesAsync(int index, int size, CancellationToken cancellationToken) 95 | { 96 | return baseStream.PeekBytesAsync(index, size, cancellationToken); 97 | } 98 | 99 | /// 100 | /// Peeks a byte asynchronous. 101 | /// 102 | /// The index. 103 | /// The cancellation token. 104 | /// 105 | Task ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken) 106 | { 107 | return baseStream.PeekByteAsync(index, cancellationToken); 108 | } 109 | 110 | /// 111 | /// Reads a byte from buffer. 112 | /// 113 | /// 114 | /// Buffer is empty 115 | byte ICustomStreamReader.ReadByteFromBuffer() 116 | { 117 | return ReadByte(); 118 | } 119 | 120 | int ICustomStreamReader.Read(byte[] buffer, int offset, int count) 121 | { 122 | return baseStream.Read(buffer, offset, count); 123 | } 124 | 125 | /// 126 | /// Reads the asynchronous. 127 | /// 128 | /// The buffer. 129 | /// The offset. 130 | /// The count. 131 | /// The cancellation token. 132 | /// 133 | Task ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 134 | { 135 | return baseStream.ReadAsync(buffer, offset, count, cancellationToken); 136 | } 137 | 138 | /// 139 | /// Read a line from the byte stream 140 | /// 141 | /// 142 | /// 143 | Task ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken) 144 | { 145 | return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); 146 | } 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Namespace StreamExtended 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
      27 |
      28 | 29 | 52 | 53 | 60 |
      61 |
      62 | 63 |
      64 |
      65 |
      66 |

      67 |
      68 |
        69 |
        70 |
        71 | 126 | 127 |
        128 |
        129 | 138 |
        139 |
        140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Network.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Namespace StreamExtended.Network 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
        27 |
        28 | 29 | 52 | 53 | 60 |
        61 |
        62 | 63 |
        64 |
        65 |
        66 |

        67 |
        68 |
          69 |
          70 |
          71 | 126 | 127 |
          128 |
          129 | 138 |
          139 |
          140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.IBufferPool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Interface IBufferPool 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
          27 |
          28 | 29 | 52 | 53 | 60 |
          61 |
          62 | 63 |
          64 |
          65 |
          66 |

          67 |
          68 |
            69 |
            70 |
            71 | 189 | 190 |
            191 |
            192 | 201 |
            202 |
            203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Network.ICustomStreamWriter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Interface ICustomStreamWriter 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
            27 |
            28 | 29 | 52 | 53 | 60 |
            61 |
            62 | 63 |
            64 |
            65 |
            66 |

            67 |
            68 |
              69 |
              70 |
              71 | 207 | 208 |
              209 |
              210 | 219 |
              220 |
              221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Helpers.BufferPool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Class BufferPool 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
              27 |
              28 | 29 | 52 | 53 | 60 |
              61 |
              62 | 63 |
              64 |
              65 |
              66 |

              67 |
              68 |
                69 |
                70 |
                71 | 214 | 215 |
                216 |
                217 | 226 |
                227 |
                228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.DefaultBufferPool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Class DefaultBufferPool 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                27 |
                28 | 29 | 52 | 53 | 60 |
                61 |
                62 | 63 |
                64 |
                65 |
                66 |

                67 |
                68 |
                  69 |
                  70 |
                  71 | 239 | 240 |
                  241 |
                  242 | 251 |
                  252 |
                  253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Network.DataEventArgs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Class DataEventArgs 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                  27 |
                  28 | 29 | 52 | 53 | 60 |
                  61 |
                  62 | 63 |
                  64 |
                  65 |
                  66 |

                  67 |
                  68 |
                    69 |
                    70 |
                    71 | 263 | 264 |
                    265 |
                    266 | 275 |
                    276 |
                    277 | 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.Models.SslExtension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Class SslExtension 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                    27 |
                    28 | 29 | 52 | 53 | 60 |
                    61 |
                    62 | 63 |
                    64 |
                    65 |
                    66 |

                    67 |
                    68 |
                      69 |
                      70 |
                      71 | 299 | 300 |
                      301 |
                      302 | 311 |
                      312 |
                      313 | 314 | 315 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /src/StreamExtended/SslTools.cs: -------------------------------------------------------------------------------- 1 | using StreamExtended.Models; 2 | using StreamExtended.Network; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace StreamExtended 9 | { 10 | /// 11 | /// Use this class to peek SSL client/server hello information. 12 | /// 13 | public class SslTools 14 | { 15 | /// 16 | /// Is the given stream starts with an SSL client hello? 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static async Task IsClientHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken) 23 | { 24 | var clientHello = await PeekClientHello(stream, bufferPool, cancellationToken); 25 | return clientHello != null; 26 | } 27 | 28 | /// 29 | /// Peek the SSL client hello information. 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public static async Task PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default (CancellationToken)) 36 | { 37 | //detects the HTTPS ClientHello message as it is described in the following url: 38 | //https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format 39 | 40 | int recordType = await clientStream.PeekByteAsync(0, cancellationToken); 41 | if (recordType == -1) 42 | { 43 | return null; 44 | } 45 | 46 | if ((recordType & 0x80) == 0x80) 47 | { 48 | //SSL 2 49 | var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); 50 | 51 | // length value + minimum length 52 | if (!await peekStream.EnsureBufferLength(10, cancellationToken)) 53 | { 54 | return null; 55 | } 56 | 57 | int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte(); 58 | if (recordLength < 9) 59 | { 60 | // Message body too short. 61 | return null; 62 | } 63 | 64 | if (peekStream.ReadByte() != 0x01) 65 | { 66 | // should be ClientHello 67 | return null; 68 | } 69 | 70 | int majorVersion = peekStream.ReadByte(); 71 | int minorVersion = peekStream.ReadByte(); 72 | 73 | int ciphersCount = peekStream.ReadInt16() / 3; 74 | int sessionIdLength = peekStream.ReadInt16(); 75 | int randomLength = peekStream.ReadInt16(); 76 | 77 | if (!await peekStream.EnsureBufferLength(ciphersCount * 3 + sessionIdLength + randomLength, cancellationToken)) 78 | { 79 | return null; 80 | } 81 | 82 | int[] ciphers = new int[ciphersCount]; 83 | for (int i = 0; i < ciphers.Length; i++) 84 | { 85 | ciphers[i] = (peekStream.ReadByte() << 16) + (peekStream.ReadByte() << 8) + peekStream.ReadByte(); 86 | } 87 | 88 | byte[] sessionId = peekStream.ReadBytes(sessionIdLength); 89 | byte[] random = peekStream.ReadBytes(randomLength); 90 | 91 | var clientHelloInfo = new ClientHelloInfo 92 | { 93 | HandshakeVersion = 2, 94 | MajorVersion = majorVersion, 95 | MinorVersion = minorVersion, 96 | Random = random, 97 | SessionId = sessionId, 98 | Ciphers = ciphers, 99 | ClientHelloLength = peekStream.Position, 100 | }; 101 | 102 | return clientHelloInfo; 103 | } 104 | else if (recordType == 0x16) 105 | { 106 | var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); 107 | 108 | //should contain at least 43 bytes 109 | // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length 110 | if (!await peekStream.EnsureBufferLength(43, cancellationToken)) 111 | { 112 | return null; 113 | } 114 | 115 | //SSL 3.0 or TLS 1.0, 1.1 and 1.2 116 | int majorVersion = peekStream.ReadByte(); 117 | int minorVersion = peekStream.ReadByte(); 118 | 119 | int recordLength = peekStream.ReadInt16(); 120 | 121 | if (peekStream.ReadByte() != 0x01) 122 | { 123 | // should be ClientHello 124 | return null; 125 | } 126 | 127 | var length = peekStream.ReadInt24(); 128 | 129 | majorVersion = peekStream.ReadByte(); 130 | minorVersion = peekStream.ReadByte(); 131 | 132 | byte[] random = peekStream.ReadBytes(32); 133 | length = peekStream.ReadByte(); 134 | 135 | // sessionid + 2 ciphersData length 136 | if (!await peekStream.EnsureBufferLength(length + 2, cancellationToken)) 137 | { 138 | return null; 139 | } 140 | 141 | byte[] sessionId = peekStream.ReadBytes(length); 142 | 143 | length = peekStream.ReadInt16(); 144 | 145 | // ciphersData + compressionData length 146 | if (!await peekStream.EnsureBufferLength(length + 1, cancellationToken)) 147 | { 148 | return null; 149 | } 150 | 151 | byte[] ciphersData = peekStream.ReadBytes(length); 152 | int[] ciphers = new int[ciphersData.Length / 2]; 153 | for (int i = 0; i < ciphers.Length; i++) 154 | { 155 | ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1]; 156 | } 157 | 158 | length = peekStream.ReadByte(); 159 | if (length < 1) 160 | { 161 | return null; 162 | } 163 | 164 | // compressionData 165 | if (!await peekStream.EnsureBufferLength(length, cancellationToken)) 166 | { 167 | return null; 168 | } 169 | 170 | byte[] compressionData = peekStream.ReadBytes(length); 171 | 172 | int extenstionsStartPosition = peekStream.Position; 173 | 174 | Dictionary extensions = null; 175 | 176 | if(extenstionsStartPosition < recordLength + 5) 177 | { 178 | extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken); 179 | } 180 | 181 | var clientHelloInfo = new ClientHelloInfo 182 | { 183 | HandshakeVersion = 3, 184 | MajorVersion = majorVersion, 185 | MinorVersion = minorVersion, 186 | Random = random, 187 | SessionId = sessionId, 188 | Ciphers = ciphers, 189 | CompressionData = compressionData, 190 | ClientHelloLength = peekStream.Position, 191 | EntensionsStartPosition = extenstionsStartPosition, 192 | Extensions = extensions, 193 | }; 194 | 195 | return clientHelloInfo; 196 | } 197 | 198 | return null; 199 | } 200 | 201 | 202 | /// 203 | /// Is the given stream starts with an SSL client hello? 204 | /// 205 | /// 206 | /// 207 | /// 208 | /// 209 | public static async Task IsServerHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken) 210 | { 211 | var serverHello = await PeekServerHello(stream, bufferPool, cancellationToken); 212 | return serverHello != null; 213 | } 214 | /// 215 | /// Peek the SSL client hello information. 216 | /// 217 | /// 218 | /// 219 | /// 220 | /// 221 | public static async Task PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default(CancellationToken)) 222 | { 223 | //detects the HTTPS ClientHello message as it is described in the following url: 224 | //https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format 225 | 226 | int recordType = await serverStream.PeekByteAsync(0, cancellationToken); 227 | if (recordType == -1) 228 | { 229 | return null; 230 | } 231 | 232 | if ((recordType & 0x80) == 0x80) 233 | { 234 | //SSL 2 235 | // not tested. SSL2 is deprecated 236 | var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); 237 | 238 | // length value + minimum length 239 | if (!await peekStream.EnsureBufferLength(39, cancellationToken)) 240 | { 241 | return null; 242 | } 243 | 244 | int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte(); 245 | if (recordLength < 38) 246 | { 247 | // Message body too short. 248 | return null; 249 | } 250 | 251 | if (peekStream.ReadByte() != 0x04) 252 | { 253 | // should be ServerHello 254 | return null; 255 | } 256 | 257 | int majorVersion = peekStream.ReadByte(); 258 | int minorVersion = peekStream.ReadByte(); 259 | 260 | // 32 bytes random + 1 byte sessionId + 2 bytes cipherSuite 261 | if (!await peekStream.EnsureBufferLength(35, cancellationToken)) 262 | { 263 | return null; 264 | } 265 | 266 | byte[] random = peekStream.ReadBytes(32); 267 | byte[] sessionId = peekStream.ReadBytes(1); 268 | int cipherSuite = peekStream.ReadInt16(); 269 | 270 | var serverHelloInfo = new ServerHelloInfo 271 | { 272 | HandshakeVersion = 2, 273 | MajorVersion = majorVersion, 274 | MinorVersion = minorVersion, 275 | Random = random, 276 | SessionId = sessionId, 277 | CipherSuite = cipherSuite, 278 | ServerHelloLength = peekStream.Position, 279 | }; 280 | 281 | return serverHelloInfo; 282 | } 283 | else if (recordType == 0x16) 284 | { 285 | var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); 286 | 287 | //should contain at least 43 bytes 288 | // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length 289 | if (!await peekStream.EnsureBufferLength(43, cancellationToken)) 290 | { 291 | return null; 292 | } 293 | 294 | //SSL 3.0 or TLS 1.0, 1.1 and 1.2 295 | int majorVersion = peekStream.ReadByte(); 296 | int minorVersion = peekStream.ReadByte(); 297 | 298 | int recordLength = peekStream.ReadInt16(); 299 | 300 | if (peekStream.ReadByte() != 0x02) 301 | { 302 | // should be ServerHello 303 | return null; 304 | } 305 | 306 | var length = peekStream.ReadInt24(); 307 | 308 | majorVersion = peekStream.ReadByte(); 309 | minorVersion = peekStream.ReadByte(); 310 | 311 | byte[] random = peekStream.ReadBytes(32); 312 | length = peekStream.ReadByte(); 313 | 314 | // sessionid + cipherSuite + compressionMethod 315 | if (!await peekStream.EnsureBufferLength(length + 2 + 1, cancellationToken)) 316 | { 317 | return null; 318 | } 319 | 320 | byte[] sessionId = peekStream.ReadBytes(length); 321 | 322 | int cipherSuite = peekStream.ReadInt16(); 323 | byte compressionMethod = peekStream.ReadByte(); 324 | 325 | int extenstionsStartPosition = peekStream.Position; 326 | 327 | Dictionary extensions = null; 328 | 329 | if (extenstionsStartPosition < recordLength + 5) 330 | { 331 | extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken); 332 | } 333 | 334 | var serverHelloInfo = new ServerHelloInfo 335 | { 336 | HandshakeVersion = 3, 337 | MajorVersion = majorVersion, 338 | MinorVersion = minorVersion, 339 | Random = random, 340 | SessionId = sessionId, 341 | CipherSuite = cipherSuite, 342 | CompressionMethod = compressionMethod, 343 | ServerHelloLength = peekStream.Position, 344 | EntensionsStartPosition = extenstionsStartPosition, 345 | Extensions = extensions, 346 | }; 347 | 348 | return serverHelloInfo; 349 | } 350 | 351 | return null; 352 | } 353 | 354 | private static async Task> ReadExtensions(int majorVersion, int minorVersion, CustomBufferedPeekStream peekStream, IBufferPool bufferPool, CancellationToken cancellationToken) 355 | { 356 | Dictionary extensions = null; 357 | if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1) 358 | { 359 | if (await peekStream.EnsureBufferLength(2, cancellationToken)) 360 | { 361 | int extensionsLength = peekStream.ReadInt16(); 362 | 363 | if (await peekStream.EnsureBufferLength(extensionsLength, cancellationToken)) 364 | { 365 | extensions = new Dictionary(); 366 | int idx = 0; 367 | while (extensionsLength > 3) 368 | { 369 | int id = peekStream.ReadInt16(); 370 | int length = peekStream.ReadInt16(); 371 | byte[] data = peekStream.ReadBytes(length); 372 | var extension = SslExtensions.GetExtension(id, data, idx++); 373 | extensions[extension.Name] = extension; 374 | extensionsLength -= 4 + length; 375 | } 376 | } 377 | } 378 | } 379 | 380 | return extensions; 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /docs/api/StreamExtended.SslTools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Class SslTools 9 | | Stream Extended 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                      27 |
                      28 | 29 | 52 | 53 | 60 |
                      61 |
                      62 | 63 |
                      64 |
                      65 |
                      66 |

                      67 |
                      68 |
                        69 |
                        70 |
                        71 | 354 | 355 |
                        356 |
                        357 | 366 |
                        367 |
                        368 | 369 | 370 | 371 | 372 | 373 | 374 | --------------------------------------------------------------------------------