예제 #1
0
        /// <summary>
        ///     Determines whether the stream starts with the given string.
        /// </summary>
        /// <returns>
        ///     1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
        /// </returns>
        private static async ValueTask <int> startsWith(IPeekStream httpReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
        {
            const int lengthToCheck = 10;

            if (bufferPool.BufferSize < lengthToCheck)
            {
                throw new Exception($"Buffer is too small. Minimum size is {lengthToCheck} bytes");
            }

            byte[] buffer = bufferPool.GetBuffer(bufferPool.BufferSize);
            try
            {
                bool isExpected = true;
                int  i          = 0;
                while (i < lengthToCheck)
                {
                    int peeked = await httpReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken);

                    if (peeked <= 0)
                    {
                        return(-1);
                    }

                    peeked += i;

                    while (i < peeked)
                    {
                        int b = buffer[i];

                        if (b == ' ' && i > 2)
                        {
                            return(isExpected ? 1 : 0);
                        }
                        else
                        {
                            char ch = (char)b;
                            if (ch < 'A' || ch > 'z' || (ch > 'Z' && ch < 'a')) // ASCII letter
                            {
                                return(-1);
                            }
                            else if (i >= expectedStart.Length || ch != expectedStart[i])
                            {
                                isExpected = false;
                            }
                        }

                        i++;
                    }
                }

                // only letters
                return(0);
            }
            finally
            {
                bufferPool.ReturnBuffer(buffer);
            }
        }
예제 #2
0
        /// <summary>
        ///     Gets the HTTP method from the stream.
        /// </summary>
        public static async ValueTask <KnownMethod> GetMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
        {
            const int lengthToCheck = 20;

            if (bufferPool.BufferSize < lengthToCheck)
            {
                throw new Exception($"Buffer is too small. Minimum size is {lengthToCheck} bytes");
            }

            byte[] buffer = bufferPool.GetBuffer(bufferPool.BufferSize);
            try
            {
                int i = 0;
                while (i < lengthToCheck)
                {
                    int peeked = await httpReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken);

                    if (peeked <= 0)
                    {
                        return(KnownMethod.Invalid);
                    }

                    peeked += i;

                    while (i < peeked)
                    {
                        int b = buffer[i];

                        if (b == ' ' && i > 2)
                        {
                            return(getKnownMethod(buffer.AsSpan(0, i)));
                        }

                        char ch = (char)b;
                        if ((ch < 'A' || ch > 'z' || (ch > 'Z' && ch < 'a')) && (ch != '-')) // ASCII letter
                        {
                            return(KnownMethod.Invalid);
                        }

                        i++;
                    }
                }

                // only letters, but no space (or shorter than 3 characters)
                return(KnownMethod.Invalid);
            }
            finally
            {
                bufferPool.ReturnBuffer(buffer);
            }
        }
예제 #3
0
 public PeekStreamReader(IPeekStream baseStream, int startPosition = 0)
 {
     this.baseStream = baseStream;
     Position        = startPosition;
 }
예제 #4
0
 /// <summary>
 ///     Determines whether is pri method (HTTP/2).
 /// </summary>
 /// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
 internal static ValueTask <int> IsPriMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
 {
     return(startsWith(httpReader, bufferPool, "PRI", cancellationToken));
 }
예제 #5
0
        /// <summary>
        ///     Peek the SSL client hello information.
        /// </summary>
        /// <param name="clientStream"></param>
        /// <param name="bufferPool"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task <ClientHelloInfo?> PeekClientHello(IPeekStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
        {
            // detects the HTTPS ClientHello message as it is described in the following url:
            // https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format

            int recordType = await clientStream.PeekByteAsync(0, cancellationToken);

            if (recordType == -1)
            {
                return(null);
            }

            if ((recordType & 0x80) == 0x80)
            {
                // SSL 2
                var peekStream = new PeekStreamReader(clientStream, 1);

                // length value + minimum length
                if (!await peekStream.EnsureBufferLength(10, cancellationToken))
                {
                    return(null);
                }

                int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte();
                if (recordLength < 9)
                {
                    // Message body too short.
                    return(null);
                }

                if (peekStream.ReadByte() != 0x01)
                {
                    // should be ClientHello
                    return(null);
                }

                int majorVersion = peekStream.ReadByte();
                int minorVersion = peekStream.ReadByte();

                int ciphersCount    = peekStream.ReadInt16() / 3;
                int sessionIdLength = peekStream.ReadInt16();
                int randomLength    = peekStream.ReadInt16();

                if (!await peekStream.EnsureBufferLength(ciphersCount * 3 + sessionIdLength + randomLength, cancellationToken))
                {
                    return(null);
                }

                int[] ciphers = new int[ciphersCount];
                for (int i = 0; i < ciphers.Length; i++)
                {
                    ciphers[i] = (peekStream.ReadByte() << 16) + (peekStream.ReadByte() << 8) + peekStream.ReadByte();
                }

                byte[] sessionId = peekStream.ReadBytes(sessionIdLength);
                byte[] random    = peekStream.ReadBytes(randomLength);

                var clientHelloInfo = new ClientHelloInfo(2, majorVersion, minorVersion, random, sessionId, ciphers,
                                                          peekStream.Position);

                return(clientHelloInfo);
            }
            else if (recordType == 0x16)
            {
                var peekStream = new PeekStreamReader(clientStream, 1);

                // should contain at least 43 bytes
                // 2 version + 2 length + 1 type + 3 length(?) + 2 version +  32 random + 1 sessionid length
                if (!await peekStream.EnsureBufferLength(43, cancellationToken))
                {
                    return(null);
                }

                // SSL 3.0 or TLS 1.0, 1.1 and 1.2
                int majorVersion = peekStream.ReadByte();
                int minorVersion = peekStream.ReadByte();

                int recordLength = peekStream.ReadInt16();

                if (peekStream.ReadByte() != 0x01)
                {
                    // should be ClientHello
                    return(null);
                }

                var length = peekStream.ReadInt24();

                majorVersion = peekStream.ReadByte();
                minorVersion = peekStream.ReadByte();

                byte[] random = peekStream.ReadBytes(32);
                length = peekStream.ReadByte();

                // sessionid + 2 ciphersData length
                if (!await peekStream.EnsureBufferLength(length + 2, cancellationToken))
                {
                    return(null);
                }

                byte[] sessionId = peekStream.ReadBytes(length);

                length = peekStream.ReadInt16();

                // ciphersData + compressionData length
                if (!await peekStream.EnsureBufferLength(length + 1, cancellationToken))
                {
                    return(null);
                }

                int[] ciphers = new int[length / 2];
                for (int i = 0; i < ciphers.Length; i++)
                {
                    ciphers[i] = peekStream.ReadInt16();
                }

                length = peekStream.ReadByte();
                if (length < 1)
                {
                    return(null);
                }

                // compressionData
                if (!await peekStream.EnsureBufferLength(length, cancellationToken))
                {
                    return(null);
                }

                byte[] compressionData = peekStream.ReadBytes(length);

                int extensionsStartPosition = peekStream.Position;

                Dictionary <string, SslExtension>?extensions = null;

                if (extensionsStartPosition < recordLength + 5)
                {
                    extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, cancellationToken);
                }

                var clientHelloInfo = new ClientHelloInfo(3, majorVersion, minorVersion, random, sessionId, ciphers, peekStream.Position)
                {
                    ExtensionsStartPosition = extensionsStartPosition,
                    CompressionData         = compressionData,
                    Extensions = extensions,
                };

                return(clientHelloInfo);
            }

            return(null);
        }
예제 #6
0
        /// <summary>
        ///     Peek the SSL client hello information.
        /// </summary>
        /// <param name="serverStream"></param>
        /// <param name="bufferPool"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task <ServerHelloInfo?> PeekServerHello(IPeekStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
        {
            // detects the HTTPS ClientHello message as it is described in the following url:
            // https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format

            int recordType = await serverStream.PeekByteAsync(0, cancellationToken);

            if (recordType == -1)
            {
                return(null);
            }

            if ((recordType & 0x80) == 0x80)
            {
                // SSL 2
                // not tested. SSL2 is deprecated
                var peekStream = new PeekStreamReader(serverStream, 1);

                // length value + minimum length
                if (!await peekStream.EnsureBufferLength(39, cancellationToken))
                {
                    return(null);
                }

                int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte();
                if (recordLength < 38)
                {
                    // Message body too short.
                    return(null);
                }

                if (peekStream.ReadByte() != 0x04)
                {
                    // should be ServerHello
                    return(null);
                }

                int majorVersion = peekStream.ReadByte();
                int minorVersion = peekStream.ReadByte();

                // 32 bytes random + 1 byte sessionId + 2 bytes cipherSuite
                if (!await peekStream.EnsureBufferLength(35, cancellationToken))
                {
                    return(null);
                }

                byte[] random      = peekStream.ReadBytes(32);
                byte[] sessionId   = peekStream.ReadBytes(1);
                int    cipherSuite = peekStream.ReadInt16();

                var serverHelloInfo = new ServerHelloInfo(2, majorVersion, minorVersion, random, sessionId, cipherSuite,
                                                          peekStream.Position);

                return(serverHelloInfo);
            }
            else if (recordType == 0x16)
            {
                var peekStream = new PeekStreamReader(serverStream, 1);

                // should contain at least 43 bytes
                // 2 version + 2 length + 1 type + 3 length(?) + 2 version +  32 random + 1 sessionid length
                if (!await peekStream.EnsureBufferLength(43, cancellationToken))
                {
                    return(null);
                }

                // SSL 3.0 or TLS 1.0, 1.1 and 1.2
                int majorVersion = peekStream.ReadByte();
                int minorVersion = peekStream.ReadByte();

                int recordLength = peekStream.ReadInt16();

                if (peekStream.ReadByte() != 0x02)
                {
                    // should be ServerHello
                    return(null);
                }

                var length = peekStream.ReadInt24();

                majorVersion = peekStream.ReadByte();
                minorVersion = peekStream.ReadByte();

                byte[] random = peekStream.ReadBytes(32);
                length = peekStream.ReadByte();

                // sessionid + cipherSuite + compressionMethod
                if (!await peekStream.EnsureBufferLength(length + 2 + 1, cancellationToken))
                {
                    return(null);
                }

                byte[] sessionId = peekStream.ReadBytes(length);

                int  cipherSuite       = peekStream.ReadInt16();
                byte compressionMethod = peekStream.ReadByte();

                int extensionsStartPosition = peekStream.Position;

                Dictionary <string, SslExtension>?extensions = null;

                if (extensionsStartPosition < recordLength + 5)
                {
                    extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, cancellationToken);
                }

                var serverHelloInfo = new ServerHelloInfo(3, majorVersion, minorVersion, random, sessionId, cipherSuite, peekStream.Position)
                {
                    CompressionMethod       = compressionMethod,
                    EntensionsStartPosition = extensionsStartPosition,
                    Extensions = extensions,
                };

                return(serverHelloInfo);
            }

            return(null);
        }
예제 #7
0
        /// <summary>
        ///     Is the given stream starts with an SSL client hello?
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="bufferPool"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task <bool> IsServerHello(IPeekStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
        {
            var serverHello = await PeekServerHello(stream, bufferPool, cancellationToken);

            return(serverHello != null);
        }