This repository was archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
SslStream Improvements for Unix Performance #22998
Closed
Closed
Changes from all commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
4b4b72c
Added reference to AppSet/AppGet from openssl
Drawaes 4079b92
Added Bio methods to the openssl native shim
Drawaes 1fa274a
Add basic custom bio interop code, write and read still needed
Drawaes d5fc983
Added BIO.Custom file
Drawaes 7bbcc3d
Added Write Buffer and changed disposable to be public to match inter…
Drawaes dd8fae2
Added the custom bio into the main code line for SslStream
Drawaes 7c51afe
Add the BIO Control method
Drawaes 7080b69
Removed pending reference from DoHandshake
Drawaes 860bba1
Added custom bio read code
Drawaes 6b19a38
changed read flags
Drawaes 7f7f47a
Added write method
Drawaes 0be51ac
fixed indexing on resize
Drawaes 4694662
updated the output ref on encrypt
Drawaes 5f4e2bf
Added short circuit for the dohandshake to return the shutdown alert
Drawaes 293ea41
Simplify the write bio
Drawaes d83a091
Changed the write to use a span
Drawaes 05b412b
Typo, intput
Drawaes 56a7e43
Fix span copyto
Drawaes e6dd27e
Added length update
Drawaes 93e0ad9
Change the pinnable write buffer pool size and the max message size t…
Drawaes 7904f68
Remove some flag settings
Drawaes bebfc15
Simplify the read bio
Drawaes 6033a15
Changed the write path to allow for multiple writes to the output buffer
Drawaes f4c0005
Resetting the pinned buffers to the original size
Drawaes b4d2301
Changed pinnable buffers on write to use one for the write then retur…
Drawaes f4dbc39
Reset Sln Changes before PR
Drawaes 0e78e71
Added the true flag to the new methods
Drawaes 3014c47
Change from Macros that aren't in older OpenSSL to the underlying fun…
Drawaes a47874f
Removed var voliations in files edited (there was one in an loop i di…
Drawaes fb87ba4
Added named parameters to increase readability
Drawaes dcbed7e
Removed raw set flags methods and provided shim methods for the two f…
Drawaes 3ec53c6
React to shim changes in the managed layer
Drawaes 3c1d3bd
React to shim changes in the managed layer
Drawaes f6dcc55
Fix the flags change to be the correct direction
Drawaes 0a27d81
Cleaned up spacing and formatting and the interop code
Drawaes bc46993
Reacting to Review
Drawaes 5ba0c75
Fixing file bit mode to remove execute caused by windows file copy
Drawaes a049480
More bit mode changes
Drawaes fc6f327
Fix style using clang-format and moved statics and typedefs to the top
Drawaes 8fd60cc
Reacting to review changes
Drawaes f51c9e8
Changed Take Bytes
Drawaes 03da084
nit var
Drawaes 172a1de
nit sln changes
Drawaes 8249c15
removed initialize for static constructor, nit readonly
Drawaes 5e97bf7
removed initialize for static constructor, nit readonly
Drawaes f8e86a6
Changed if to be more concise
Drawaes 9a800a3
Removed finalizers, added assert removed usless is allocated test. ad…
Drawaes 90eb0f7
wrong space removed
Drawaes 3f35c68
Changed span to bye[], changed continuation to run sync and to propag…
Drawaes 155e8e9
Fixed compile error
Drawaes e3f1577
Reverted Continutation
Drawaes c67a9e2
Fixed bug around write being triggered during decrypt. Now correctly …
Drawaes a1ea5b5
PinnableBufferCache -> ArrayPool
benaadams 354a992
Merge pull request #3 from benaadams/arraypool
Drawaes 4a1734b
Fixed issue with the maxsize calculation and removed pinnable tests
Drawaes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ManagedSslBio.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System; | ||
using System.Runtime.InteropServices; | ||
using Microsoft.Win32.SafeHandles; | ||
using System.Diagnostics; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Crypto | ||
{ | ||
internal static class ManagedSslBio | ||
{ | ||
private unsafe readonly static ReadDelegate s_readDelegate; | ||
private unsafe readonly static WriteDelegate s_writeDelegate; | ||
|
||
internal static SafeBioHandle CreateManagedSslBio() => Crypto.CreateManagedSslBio(); | ||
|
||
unsafe static ManagedSslBio() | ||
{ | ||
s_writeDelegate = Write; | ||
s_readDelegate = Read; | ||
Crypto.InitManagedSslBioMethod(s_writeDelegate, s_readDelegate); | ||
} | ||
|
||
internal static void BioSetGCHandle(SafeBioHandle bio, GCHandle handle) | ||
{ | ||
IntPtr pointer = handle.IsAllocated ? GCHandle.ToIntPtr(handle) : IntPtr.Zero; | ||
Crypto.BioSetAppData(bio, pointer); | ||
} | ||
|
||
private static unsafe int Write(IntPtr bio, void* input, int size, IntPtr data) | ||
{ | ||
GCHandle handle = GCHandle.FromIntPtr(data); | ||
Debug.Assert(handle.IsAllocated); | ||
|
||
if (handle.Target is SafeSslHandle.WriteBioBuffer buffer) | ||
{ | ||
return buffer.Write(new Span<byte>(input, size)); | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
private static unsafe int Read(IntPtr bio, void* output, int size, IntPtr data) | ||
{ | ||
GCHandle handle = GCHandle.FromIntPtr(data); | ||
Debug.Assert(handle.IsAllocated); | ||
|
||
if (handle.Target is SafeSslHandle.ReadBioBuffer buffer) | ||
{ | ||
return buffer.Read(new Span<byte>(output, size)); | ||
} | ||
|
||
return -1; | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,10 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50 | |
// https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html | ||
Ssl.SslCtxSetQuietShutdown(innerContext); | ||
|
||
// This allows the write buffer to move during a multi call write, this stops us having to pin it | ||
// across multiple calls where there is an async output to the innerstream inbetween | ||
Ssl.SslCtxSetAcceptMovingWriteBuffer(innerContext); | ||
|
||
if (!Ssl.SetEncryptionPolicy(innerContext, policy)) | ||
{ | ||
throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); | ||
|
@@ -93,7 +97,7 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50 | |
Ssl.SslCtxSetVerify(innerContext, | ||
s_verifyClientCertificate); | ||
|
||
//update the client CA list | ||
// update the client CA list | ||
UpdateCAListFromRootStore(innerContext); | ||
} | ||
|
||
|
@@ -135,49 +139,36 @@ internal static bool DoSslHandshake(SafeSslHandle context, byte[] recvBuf, int r | |
{ | ||
sendBuf = null; | ||
sendCount = 0; | ||
if ((recvBuf != null) && (recvCount > 0)) | ||
|
||
sendCount = context.OutputBio.TakeBytes(out sendBuf); | ||
if (recvBuf == null && sendCount > 0) | ||
{ | ||
BioWrite(context.InputBio, recvBuf, recvOffset, recvCount); | ||
return false; | ||
} | ||
|
||
context.InputBio.SetData(recvBuf, recvOffset, recvCount); | ||
context.OutputBio.SetData(buffer: null, isHandshake: true); | ||
|
||
int retVal = Ssl.SslDoHandshake(context); | ||
|
||
if (retVal != 1) | ||
{ | ||
Exception innerError; | ||
Ssl.SslErrorCode error = GetSslError(context, retVal, out innerError); | ||
Ssl.SslErrorCode error = GetSslError(context, retVal, out Exception innerError); | ||
|
||
if ((retVal != -1) || (error != Ssl.SslErrorCode.SSL_ERROR_WANT_READ)) | ||
{ | ||
throw new SslException(SR.Format(SR.net_ssl_handshake_failed_error, error), innerError); | ||
} | ||
} | ||
|
||
sendCount = Crypto.BioCtrlPending(context.OutputBio); | ||
|
||
if (sendCount > 0) | ||
{ | ||
sendBuf = new byte[sendCount]; | ||
|
||
try | ||
{ | ||
sendCount = BioRead(context.OutputBio, sendBuf, sendCount); | ||
} | ||
finally | ||
{ | ||
if (sendCount <= 0) | ||
{ | ||
sendBuf = null; | ||
sendCount = 0; | ||
} | ||
} | ||
} | ||
sendCount = context.OutputBio.TakeBytes(out sendBuf); | ||
|
||
bool stateOk = Ssl.IsSslStateOK(context); | ||
if (stateOk) | ||
{ | ||
context.MarkHandshakeCompleted(); | ||
} | ||
|
||
return stateOk; | ||
} | ||
|
||
|
@@ -190,6 +181,7 @@ internal static int Encrypt(SafeSslHandle context, byte[] input, int offset, int | |
Debug.Assert(input.Length - offset >= count); | ||
|
||
errorCode = Ssl.SslErrorCode.SSL_ERROR_NONE; | ||
context.OutputBio.SetData(output, isHandshake: false); | ||
|
||
int retVal; | ||
unsafe | ||
|
@@ -202,8 +194,7 @@ internal static int Encrypt(SafeSslHandle context, byte[] input, int offset, int | |
|
||
if (retVal != count) | ||
{ | ||
Exception innerError; | ||
errorCode = GetSslError(context, retVal, out innerError); | ||
errorCode = GetSslError(context, retVal, out Exception innerError); | ||
retVal = 0; | ||
|
||
switch (errorCode) | ||
|
@@ -212,52 +203,46 @@ internal static int Encrypt(SafeSslHandle context, byte[] input, int offset, int | |
case Ssl.SslErrorCode.SSL_ERROR_ZERO_RETURN: | ||
case Ssl.SslErrorCode.SSL_ERROR_WANT_READ: | ||
break; | ||
|
||
// indicates we need to write the out buffer and write again | ||
case Ssl.SslErrorCode.SSL_ERROR_WANT_WRITE: | ||
break; | ||
default: | ||
throw new SslException(SR.Format(SR.net_ssl_encrypt_failed, errorCode), innerError); | ||
} | ||
} | ||
else | ||
{ | ||
int capacityNeeded = Crypto.BioCtrlPending(context.OutputBio); | ||
|
||
if (output == null || output.Length < capacityNeeded) | ||
{ | ||
output = new byte[capacityNeeded]; | ||
} | ||
|
||
retVal = BioRead(context.OutputBio, output, capacityNeeded); | ||
} | ||
|
||
return retVal; | ||
int bytesWritten = context.OutputBio.BytesWritten; | ||
context.OutputBio.Reset(); | ||
return bytesWritten; | ||
} | ||
|
||
internal static int Decrypt(SafeSslHandle context, byte[] outBuffer, int offset, int count, out Ssl.SslErrorCode errorCode) | ||
{ | ||
Debug.Assert(offset >= 0); | ||
Debug.Assert(offset <= outBuffer.Length); | ||
|
||
errorCode = Ssl.SslErrorCode.SSL_ERROR_NONE; | ||
|
||
int retVal = BioWrite(context.InputBio, outBuffer, offset, count); | ||
context.InputBio.SetData(outBuffer, offset, count); | ||
|
||
if (retVal == count) | ||
int retVal; | ||
unsafe | ||
{ | ||
unsafe | ||
fixed (byte* fixedBuffer = outBuffer) | ||
{ | ||
fixed (byte* fixedBuffer = outBuffer) | ||
{ | ||
retVal = Ssl.SslRead(context, fixedBuffer + offset, outBuffer.Length); | ||
} | ||
retVal = Ssl.SslRead(context, fixedBuffer + offset, outBuffer.Length - offset); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to take There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No this is our buffer we own internally, we can always use the entire thing. The count is the number of encrypted bytes currently in the same buffer for decrypting. This is the pattern for how it works pre the PR. |
||
} | ||
} | ||
|
||
if (retVal > 0) | ||
{ | ||
count = retVal; | ||
} | ||
if (retVal > 0) | ||
{ | ||
count = retVal; | ||
} | ||
|
||
|
||
if (retVal != count) | ||
{ | ||
Exception innerError; | ||
errorCode = GetSslError(context, retVal, out innerError); | ||
errorCode = GetSslError(context, retVal, out Exception innerError); | ||
retVal = 0; | ||
|
||
switch (errorCode) | ||
|
@@ -345,10 +330,10 @@ private static void AddX509Names(SafeX509NameStackHandle nameStack, StoreLocatio | |
{ | ||
store.Open(OpenFlags.ReadOnly); | ||
|
||
foreach (var certificate in store.Certificates) | ||
foreach (X509Certificate2 certificate in store.Certificates) | ||
{ | ||
//Check if issuer name is already present | ||
//Avoiding duplicate names | ||
// Check if issuer name is already present | ||
// Avoiding duplicate names | ||
if (!issuerNameHashSet.Add(certificate.Issuer)) | ||
{ | ||
continue; | ||
|
@@ -458,7 +443,7 @@ private static void SetSslCertificate(SafeSslContextHandle contextPtr, SafeX509H | |
throw CreateSslException(SR.net_ssl_use_private_key_failed); | ||
} | ||
|
||
//check private key | ||
// check private key | ||
retVal = Ssl.SslCtxCheckPrivateKey(contextPtr); | ||
|
||
if (1 != retVal) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we know this to be always safe? What guarantees does OpenSSL need on the stability of the memory, and what guarantees can we meet with regard to GC compaction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OpenSsl requires that the memory is the same (as in the content) but that the pointer itself can move. The length must also be the same. So I feel this is fine.
Its really only in place to stop silly mistakes, in this case we know we won't change the content (unless the user is silly enough to modify the input array that is in a method and not returned and modify it else where in another thread). Now it is possible for a user to do this, but then you end up with the same situation as the user doing this while you are mid writing or copying the data anyway.
I do have an idea of how I can possibly remove this, but its not an easy change due to the APM model of the SslInternalStream, the large number of callback chaining in there makes state a tough call.