Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
673 views
in Technique[技术] by (71.8m points)

ssl - .NET SslStream doesn't close TLS connection properly

.NET's SslStream class does not send the close_notify alert before closing the connection.

How can I send the close_notify alert manually?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Thanks for this question. It pointed me into the right direction, that there is a bug in .Net, which I do not very often think about.

I bumped into this problem during writing of my implementation of FTPS server and Filezilla (or GnuTLS probably) client was complaining "GnuTLS error -110 in gnutls_record_recv: The TLS connection was non-properly terminated". I think it is a quite significant drawback in SslStream implementation.

So I ended up with writing a wrapper which sends this alert before closing the stream:

public class FixedSslStream : SslStream {
    public FixedSslStream(Stream innerStream)
        : base(innerStream) {
    }
    public FixedSslStream(Stream innerStream, bool leaveInnerStreamOpen)
        : base(innerStream, leaveInnerStreamOpen) {
    }
    public FixedSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
        : base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) {
    }
    public FixedSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
        : base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback) {
    }
    public FixedSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback, EncryptionPolicy encryptionPolicy)
        : base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback, encryptionPolicy) {
    }
    public override void Close() {
        try {
            SslDirectCall.CloseNotify(this);
        } finally {
            base.Close();
        }
    }
}

And the following code is to make it working (this code requires assembly to be 'unsafe'):

public unsafe static class SslDirectCall {
    public static void CloseNotify(SslStream sslStream) {
        if (sslStream.IsAuthenticated) {
            bool isServer = sslStream.IsServer;

            byte[] result;
            int resultSz;
            var asmbSystem = typeof(System.Net.Authorization).Assembly;

            int SCHANNEL_SHUTDOWN = 1;
            var workArray = BitConverter.GetBytes(SCHANNEL_SHUTDOWN);

            var sslstate = ReflectUtil.GetField(sslStream, "_SslState");
            var context = ReflectUtil.GetProperty(sslstate, "Context");

            var securityContext = ReflectUtil.GetField(context, "m_SecurityContext");
            var securityContextHandleOriginal = ReflectUtil.GetField(securityContext, "_handle");
            NativeApi.SSPIHandle securityContextHandle = default(NativeApi.SSPIHandle);
            securityContextHandle.HandleHi = (IntPtr)ReflectUtil.GetField(securityContextHandleOriginal, "HandleHi");
            securityContextHandle.HandleLo = (IntPtr)ReflectUtil.GetField(securityContextHandleOriginal, "HandleLo");

            var credentialsHandle = ReflectUtil.GetField(context, "m_CredentialsHandle");
            var credentialsHandleHandleOriginal = ReflectUtil.GetField(credentialsHandle, "_handle");
            NativeApi.SSPIHandle credentialsHandleHandle = default(NativeApi.SSPIHandle);
            credentialsHandleHandle.HandleHi = (IntPtr)ReflectUtil.GetField(credentialsHandleHandleOriginal, "HandleHi");
            credentialsHandleHandle.HandleLo = (IntPtr)ReflectUtil.GetField(credentialsHandleHandleOriginal, "HandleLo");

            int bufferSize = 1;
            NativeApi.SecurityBufferDescriptor securityBufferDescriptor = new NativeApi.SecurityBufferDescriptor(bufferSize);
            NativeApi.SecurityBufferStruct[] unmanagedBuffer = new NativeApi.SecurityBufferStruct[bufferSize];

            fixed (NativeApi.SecurityBufferStruct* ptr = unmanagedBuffer)
            fixed (void* workArrayPtr = workArray) {
                securityBufferDescriptor.UnmanagedPointer = (void*)ptr;

                unmanagedBuffer[0].token = (IntPtr)workArrayPtr;
                unmanagedBuffer[0].count = workArray.Length;
                unmanagedBuffer[0].type = NativeApi.BufferType.Token;

                NativeApi.SecurityStatus status;
                status = (NativeApi.SecurityStatus)NativeApi.ApplyControlToken(ref securityContextHandle, securityBufferDescriptor);
                if (status == NativeApi.SecurityStatus.OK) {
                    unmanagedBuffer[0].token = IntPtr.Zero;
                    unmanagedBuffer[0].count = 0;
                    unmanagedBuffer[0].type = NativeApi.BufferType.Token;

                    NativeApi.SSPIHandle contextHandleOut = default(NativeApi.SSPIHandle);
                    NativeApi.ContextFlags outflags = NativeApi.ContextFlags.Zero;
                    long ts = 0;

                    var inflags = NativeApi.ContextFlags.SequenceDetect |
                                NativeApi.ContextFlags.ReplayDetect |
                                NativeApi.ContextFlags.Confidentiality |
                                NativeApi.ContextFlags.AcceptExtendedError |
                                NativeApi.ContextFlags.AllocateMemory |
                                NativeApi.ContextFlags.InitStream;

                    if (isServer) {
                        status = (NativeApi.SecurityStatus)NativeApi.AcceptSecurityContext(ref credentialsHandleHandle, ref securityContextHandle, null,
                            inflags, NativeApi.Endianness.Native, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
                    } else {
                        status = (NativeApi.SecurityStatus)NativeApi.InitializeSecurityContextW(ref credentialsHandleHandle, ref securityContextHandle, null,
                            inflags, 0, NativeApi.Endianness.Native, null, 0, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
                    }
                    if (status == NativeApi.SecurityStatus.OK) {
                        byte[] resultArr = new byte[unmanagedBuffer[0].count];
                        Marshal.Copy(unmanagedBuffer[0].token, resultArr, 0, resultArr.Length);
                        Marshal.FreeCoTaskMem(unmanagedBuffer[0].token);
                        result = resultArr;
                        resultSz = resultArr.Length;
                    } else {
                        throw new InvalidOperationException(string.Format("AcceptSecurityContext/InitializeSecurityContextW returned [{0}] during CloseNotify.", status));
                    }
                } else {
                    throw new InvalidOperationException(string.Format("ApplyControlToken returned [{0}] during CloseNotify.", status));
                }
            }

            var innerStream = (Stream)ReflectUtil.GetProperty(sslstate, "InnerStream");
            innerStream.Write(result, 0, resultSz);
        }
    }
}

Windows API used:

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 credential

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...