Skip to content

Commit

Permalink
Example for the SSL factory implementation
Browse files Browse the repository at this point in the history
Included a fixed version of GnuSslStream (only required on Windows...)
  • Loading branch information
fubar-coder committed Mar 27, 2019
1 parent 86da869 commit 27d8bec
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 7 deletions.
50 changes: 50 additions & 0 deletions samples/TestFtpServer/CustomSslStreamWrapperFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <copyright file="CustomSslStreamWrapperFactory.cs" company="Fubar Development Junker">
// Copyright (c) Fubar Development Junker. All rights reserved.
// </copyright>

using System.IO;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

using FubarDev.FtpServer.Authentication;

namespace TestFtpServer
{
public class CustomSslStreamWrapperFactory : ISslStreamWrapperFactory
{
/// <inheritdoc />
public async Task<Stream> WrapStreamAsync(
Stream unencryptedStream,
bool keepOpen,
X509Certificate certificate,
CancellationToken cancellationToken)
{
var sslStream = new GnuSslStream(unencryptedStream, keepOpen);
try
{
await sslStream.AuthenticateAsServerAsync(certificate)
.ConfigureAwait(false);
}
catch
{
sslStream.Dispose();
throw;
}

return sslStream;
}

/// <inheritdoc />
public Task CloseStreamAsync(Stream sslStream, CancellationToken cancellationToken)
{
if (sslStream is GnuSslStream s)
{
s.Close();
}

return Task.CompletedTask;
}
}
}
68 changes: 68 additions & 0 deletions samples/TestFtpServer/GnuSslStream/GnuSslStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.IO;

/* About the annoying BUG in SslStream:
* .NET's SslStream class does not send the close_notify alert before closing the connection,
* which is needed by GnuTLS. If we don't send this signal, GnuTLS will throw an error like this:
* [GnuTLS error -110: The TLS connection was non-properly terminated.]
* So we have to fix this issue by ourselves.(Microsoft refused to fix this, see:https://connect.microsoft.com/VisualStudio/feedback/details/788752/sslstream-does-not-properly-send-the-close-notify-alert)
* The Following solution was provided by Neco(Nikolay Uvaliyev) at http://stackoverflow.com/questions/237807/net-sslstream-doesnt-close-tls-connection-properly .
* Much Thanks to him!
* P.S. SslStream in Mono works correctly.
* by Ulysses , 2014
*/
/* 关于SslStream的BUG:
* .NET的SslStream在关闭之前不会发送close_notify信号,而在很多TLS库(除了微软)之外是需要这个信号的。
* 如果不发这个信号,GnuTLS就会报错:服务器没有正常的关闭 TLS 连接。微软表示现阶段不会修复这个问题(网址见上)。
* 这个解决方法是StackOverflow的Neco(Nikolay Uvaliyev)提供的(这位老外的昵称难道是Neko(猫)吗……网址见上)。非常感谢他!
* 经验证,Mono中的Sslstream无此问题!
* by Ulysses , 2014
*/


namespace System.Net.Security
{
public class GnuSslStream : SslStream
{
public GnuSslStream(Stream innerStream)
: base(innerStream)
{
}
public GnuSslStream(Stream innerStream, bool leaveInnerStreamOpen)
: base(innerStream, leaveInnerStreamOpen)
{
}
public GnuSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback)
{
}
public GnuSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback)
{
}
//Mono不支持此重载构造函数 索性取消
#if WINDOWS
public GnuSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback, EncryptionPolicy encryptionPolicy)
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback, encryptionPolicy)
{
}
#endif
public override void Close()
{
//MARK: SslStream in Mono works correctly.
if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix)
{
base.Close();
return;
}
try
{
SslDirectCall.CloseNotify(this);
base.Flush();
}
finally
{
base.Close();
}
}
}
}
158 changes: 158 additions & 0 deletions samples/TestFtpServer/GnuSslStream/NativeApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace System.Net.Security
{
public unsafe static class NativeApi
{
internal enum BufferType
{
Empty,
Data,
Token,
Parameters,
Missing,
Extra,
Trailer,
Header,
Padding = 9,
Stream,
ChannelBindings = 14,
TargetHost = 16,
ReadOnlyFlag = -2147483648,
ReadOnlyWithChecksum = 268435456
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct SSPIHandle
{
public IntPtr HandleHi;
public IntPtr HandleLo;
public bool IsZero
{
get
{
return this.HandleHi == IntPtr.Zero && this.HandleLo == IntPtr.Zero;
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal void SetToInvalid()
{
this.HandleHi = IntPtr.Zero;
this.HandleLo = IntPtr.Zero;
}
public override string ToString()
{
return this.HandleHi.ToString("x") + ":" + this.HandleLo.ToString("x");
}
}
[StructLayout(LayoutKind.Sequential)]
internal class SecurityBufferDescriptor
{
public readonly int Version;
public readonly int Count;
public unsafe void* UnmanagedPointer;
public SecurityBufferDescriptor(int count)
{
this.Version = 0;
this.Count = count;
this.UnmanagedPointer = null;
}
}

[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBufferStruct
{
public int count;
public BufferType type;
public IntPtr token;
public static readonly int Size = sizeof(SecurityBufferStruct);
}

internal enum SecurityStatus
{
OK,
ContinueNeeded = 590610,
CompleteNeeded,
CompAndContinue,
ContextExpired = 590615,
CredentialsNeeded = 590624,
Renegotiate,
OutOfMemory = -2146893056,
InvalidHandle,
Unsupported,
TargetUnknown,
InternalError,
PackageNotFound,
NotOwner,
CannotInstall,
InvalidToken,
CannotPack,
QopNotSupported,
NoImpersonation,
LogonDenied,
UnknownCredentials,
NoCredentials,
MessageAltered,
OutOfSequence,
NoAuthenticatingAuthority,
IncompleteMessage = -2146893032,
IncompleteCredentials = -2146893024,
BufferNotEnough,
WrongPrincipal,
TimeSkew = -2146893020,
UntrustedRoot,
IllegalMessage,
CertUnknown,
CertExpired,
AlgorithmMismatch = -2146893007,
SecurityQosFailed,
SmartcardLogonRequired = -2146892994,
UnsupportedPreauth = -2146892989,
BadBinding = -2146892986
}
[Flags]
internal enum ContextFlags
{
Zero = 0,
Delegate = 1,
MutualAuth = 2,
ReplayDetect = 4,
SequenceDetect = 8,
Confidentiality = 16,
UseSessionKey = 32,
AllocateMemory = 256,
Connection = 2048,
InitExtendedError = 16384,
AcceptExtendedError = 32768,
InitStream = 32768,
AcceptStream = 65536,
InitIntegrity = 65536,
AcceptIntegrity = 131072,
InitManualCredValidation = 524288,
InitUseSuppliedCreds = 128,
InitIdentify = 131072,
AcceptIdentify = 524288,
ProxyBindings = 67108864,
AllowMissingBindings = 268435456,
UnverifiedTargetName = 536870912
}
internal enum Endianness
{
Network,
Native = 16
}

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern int ApplyControlToken(ref SSPIHandle contextHandle, [In] [Out] SecurityBufferDescriptor outputBuffer);

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal unsafe static extern int AcceptSecurityContext(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] SecurityBufferDescriptor inputBuffer, [In] ContextFlags inFlags, [In] Endianness endianness, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
internal unsafe static extern int InitializeSecurityContextW(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] byte* targetName, [In] ContextFlags inFlags, [In] int reservedI, [In] Endianness endianness, [In] SecurityBufferDescriptor inputBuffer, [In] int reservedII, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);
}
}
87 changes: 87 additions & 0 deletions samples/TestFtpServer/GnuSslStream/ReflectUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace System.Net.Security
{
public static class ReflectUtil
{
public static object GetField(object obj, string fieldName)
{
var tp = obj.GetType();
var info = GetAllFields(tp)
.Where(f => f.Name == fieldName).SingleOrDefault();
return info?.GetValue(obj);
}

public static object GetProperty(object obj, string propertyName)
{
var tp = obj.GetType();
var info = GetAllProperties(tp)
.Where(f => f.Name == propertyName).Single();
return info.GetValue(obj, null);
}
public static object CallMethod(object obj, string methodName, params object[] prm)
{
var tp = obj.GetType();
var info = GetAllMethods(tp)
.Where(f => f.Name == methodName && f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(obj, prm);
return rez;
}
public static object NewInstance(Assembly assembly, string typeName, params object[] prm)
{
var tp = assembly.GetType(typeName);
var info = tp.GetConstructors()
.Where(f => f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(prm);
return rez;
}
public static object InvokeStaticMethod(Assembly assembly, string typeName, string methodName, params object[] prm)
{
var tp = assembly.GetType(typeName);
var info = GetAllMethods(tp)
.Where(f => f.IsStatic)
.Where(f => f.Name == methodName && f.GetParameters().Length == prm.Length).Single();
object rez = info.Invoke(null, prm);
return rez;
}
public static object GetEnumValue(Assembly assembly, string typeName, int value)
{
var tp = assembly.GetType(typeName);
object rez = Enum.ToObject(tp, value);
return rez;
}

private static IEnumerable<FieldInfo> GetAllFields(Type t)
{
if (t == null)
return Enumerable.Empty<FieldInfo>();

BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance |
BindingFlags.DeclaredOnly;
return t.GetFields(flags).Concat(GetAllFields(t.BaseType));
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type t)
{
if (t == null)
return Enumerable.Empty<PropertyInfo>();

BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance |
BindingFlags.DeclaredOnly;
return t.GetProperties(flags).Concat(GetAllProperties(t.BaseType));
}
private static IEnumerable<MethodInfo> GetAllMethods(Type t)
{
if (t == null)
return Enumerable.Empty<MethodInfo>();

BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance |
BindingFlags.DeclaredOnly;
return t.GetMethods(flags).Concat(GetAllMethods(t.BaseType));
}
}
}
Loading

0 comments on commit 27d8bec

Please sign in to comment.