Beispiel #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientMsgProtobuf"/> class.
        /// This is a recieve constructor.
        /// </summary>
        /// <param name="msg">The packet message to build this client message from.</param>
        public ClientMsgProtobuf(IPacketMsg msg)
            : this(msg.MsgType)
        {
            DebugLog.Assert(msg.IsProto, "ClientMsgProtobuf", "ClientMsgProtobuf used for non-proto message!");

            Deserialize(msg.GetData());
        }
Beispiel #2
0
        /// <summary>
        /// Downloads the specified depot chunk, and optionally processes the chunk and verifies the checksum if the depot decryption key has been provided.
        /// </summary>
        /// <remarks>
        /// This function will also validate the length of the downloaded chunk with the value of <see cref="DepotManifest.ChunkData.CompressedLength"/>,
        /// if it has been assigned a value.
        /// </remarks>
        /// <param name="chunk">
        /// A <see cref="DepotManifest.ChunkData"/> instance that represents the chunk to download.
        /// This value should come from a manifest downloaded with <see cref="CDNClient.DownloadManifest"/>.
        /// </param>
        /// <returns>A <see cref="DepotChunk"/> instance that contains the data for the given chunk.</returns>
        /// <exception cref="System.ArgumentNullException">chunk's <see cref="DepotManifest.ChunkData.ChunkID"/> was null.</exception>
        public DepotChunk DownloadDepotChunk(DepotManifest.ChunkData chunk)
        {
            if (chunk.ChunkID == null)
            {
                throw new ArgumentNullException("chunk.ChunkID");
            }

            string chunkId = Utils.EncodeHexString(chunk.ChunkID);

            byte[] chunkData = DoRawCommand(connectedServer, "depot", doAuth: true, args: string.Format("{0}/chunk/{1}", depotId, chunkId));

            if (chunk.CompressedLength != default(uint))
            {
                // assert that lengths match only if the chunk has a length assigned.
                DebugLog.Assert(chunkData.Length == chunk.CompressedLength, "CDNClient", "Length mismatch after downloading depot chunk!");
            }

            var depotChunk = new DepotChunk
            {
                ChunkInfo = chunk,
                Data      = chunkData,
            };

            if (depotKey != null)
            {
                // if we have the depot key, we can process the chunk immediately
                depotChunk.Process(depotKey);
            }

            return(depotChunk);
        }
Beispiel #3
0
        /// <summary>
        /// Dispatches up to one message to the rest of SteamKit
        /// </summary>
        /// <returns>True if a message was dispatched, false otherwise</returns>
        private bool DispatchMessage()
        {
            uint numPackets = ReadyMessageParts();

            if (numPackets == 0)
            {
                return(false);
            }

            MemoryStream payload = new MemoryStream();

            for (uint i = 0; i < numPackets; i++)
            {
                var handled = inPackets.TryGetValue(++inSeqHandled, out var packet);
                DebugLog.Assert(handled, nameof(UdpConnection), "Should have retrieved next packet info.");
                inPackets.Remove(inSeqHandled);

                packet !.Payload.WriteTo(payload);
            }

            byte[] data = payload.ToArray();

            log.LogDebug("UdpConnection", "Dispatching message; {0} bytes", data.Length);

            NetMsgReceived?.Invoke(this, new NetMsgEventArgs(data, CurrentEndPoint !));

            return(true);
        }
Beispiel #4
0
        /// <summary>
        /// Decrypts using AES/ECB/PKCS7
        /// </summary>
        public static byte[] SymmetricDecryptECB(byte[] input, byte[] key)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            DebugLog.Assert(key.Length == 32, "CryptoHelper", "SymmetricDecryptECB used with non 32 byte key!");

            using (var aes = Aes.Create())
            {
                aes.BlockSize = 128;
                aes.KeySize   = 256;
                aes.Mode      = CipherMode.ECB;
                aes.Padding   = PaddingMode.PKCS7;

                using (var aesTransform = aes.CreateDecryptor(key, null))
                {
                    byte[] output = aesTransform.TransformFinalBlock(input, 0, input.Length);

                    return(output);
                }
            }
        }
Beispiel #5
0
        private void TryConnect(int timeout)
        {
            DebugLog.Assert(cancellationToken != null, nameof(TcpConnection), "null CancellationToken in TryConnect");

            if (cancellationToken.IsCancellationRequested)
            {
                log.LogDebug(nameof(TcpConnection), "Connection to {0} cancelled by user", CurrentEndPoint);
                Release(userRequestedDisconnect: true);
                return;
            }

            DebugLog.Assert(socket != null, nameof(TcpConnection), "socket should not be null when connecting (we hold the net lock)");
            DebugLog.Assert(CurrentEndPoint != null, nameof(TcpConnection), "CurrentEndPoint should be non-null when connecting.");

            try
            {
                using var timeoutTokenSource  = new CancellationTokenSource(timeout);
                using var connectCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Token, timeoutTokenSource.Token);

                using (connectCancellation.Token.Register(s => (( Socket )s !).Dispose(), socket))
                {
                    socket.Connect(CurrentEndPoint);
                }
            }
            catch (SocketException) when(cancellationToken.IsCancellationRequested)
            {
                // Ignore, this will be handled by ConnectCompleted.
            }
            catch (Exception ex)
            {
                log.LogDebug(nameof(TcpConnection), "Exception while connecting to {0}: {1}", CurrentEndPoint, ex);
            }

            ConnectCompleted(socket.Connected);
        }
Beispiel #6
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientMsg&lt;BodyType&gt;"/> class.
        /// This is a recieve constructor.
        /// </summary>
        /// <param name="msg">The packet message to build this client message from.</param>
        public ClientMsg(IPacketMsg msg)
            : this()
        {
            DebugLog.Assert(!msg.IsProto, "ClientMsg", $"ClientMsg<{typeof(BodyType).FullName}> used for proto message!");

            Deserialize(msg.GetData());
        }
Beispiel #7
0
        async Task <DepotChunk> DownloadDepotChunkCoreAsync(uint depotId, DepotManifest.ChunkData chunk, Server server, string cdnAuthToken, byte[] depotKey)
        {
            var chunkID = Utils.EncodeHexString(chunk.ChunkID);

            var chunkData = await DoRawCommandAsync(server, HttpMethod.Get, "depot", doAuth : true, args : string.Format("{0}/chunk/{1}", depotId, chunkID), authtoken : cdnAuthToken).ConfigureAwait(false);

            if (chunk.CompressedLength != default(uint))
            {
                // assert that lengths match only if the chunk has a length assigned.
                DebugLog.Assert(chunkData.Length == chunk.CompressedLength, "CDNClient", "Length mismatch after downloading depot chunk!");
            }

            var depotChunk = new DepotChunk
            {
                ChunkInfo = chunk,
                Data      = chunkData,
            };

            if (depotKey != null)
            {
                // if we have the depot key, we can process the chunk immediately
                depotChunk.Process(depotKey);
            }

            return(depotChunk);
        }
Beispiel #8
0
        public NetFilterEncryption(byte[] sessionKey, ILogContext log)
        {
            DebugLog.Assert(sessionKey.Length == 32, nameof(NetFilterEncryption), "AES session key was not 32 bytes!");

            this.sessionKey = sessionKey;
            this.log        = log ?? throw new ArgumentNullException(nameof(log));
        }
Beispiel #9
0
        private void TryConnect(object sender)
        {
            DebugLog.Assert(cancellationToken != null, nameof(TcpConnection), "null CancellationToken in TryConnect");

            int timeout = (int)sender;

            if (cancellationToken.IsCancellationRequested)
            {
                log.LogDebug("TcpConnection", "Connection to {0} cancelled by user", CurrentEndPoint);
                Release(userRequestedDisconnect: true);
                return;
            }

            var asyncResult = socket !.BeginConnect(CurrentEndPoint, null, null);

            if (WaitHandle.WaitAny(new WaitHandle[] { asyncResult.AsyncWaitHandle, cancellationToken.Token.WaitHandle }, timeout) == 0)
            {
                try
                {
                    socket.EndConnect(asyncResult);
                    ConnectCompleted(true);
                }
                catch (Exception ex)
                {
                    log.LogDebug("TcpConnection", "Socket exception while completing connection request to {0}: {1}", CurrentEndPoint, ex);
                    ConnectCompleted(false);
                }
            }
            else
            {
                ConnectCompleted(false);
            }
        }
Beispiel #10
0
        /// <summary>
        /// Connects this client to a UFS server.
        /// This begins the process of connecting and encrypting the data channel between the client and the UFS server.
        /// Results are returned asynchronously in a <see cref="ConnectedCallback"/>.
        /// If the UFS server that this client attempts to connect to is down, a <see cref="DisconnectedCallback"/> will be posted instead.
        /// <see cref="UFSClient"/> will not attempt to reconnect to Steam, you must handle this callback and call <see cref="Connect"/> again, preferrably after a short delay.
        /// In order to connect to the UFS server, the parent <see cref="SteamClient"/> must be connected to the CM server.
        /// </summary>
        /// <param name="ufsServer">
        /// The <see cref="System.Net.IPEndPoint"/> of the UFS server to connect to.
        /// If <c>null</c>, <see cref="UFSClient"/> will randomly select a UFS server from the <see cref="SteamClient"/>'s list of servers.
        /// </param>
        public void Connect(IPEndPoint ufsServer = null)
        {
            DebugLog.Assert(steamClient.IsConnected, nameof(UFSClient), "CMClient is not connected!");

            Disconnect();
            Debug.Assert(connection == null);

            if (ufsServer == null)
            {
                var serverList = steamClient.GetServersOfType(EServerType.UFS);

                if (serverList.Count == 0)
                {
                    DebugLog.WriteLine(nameof(UFSClient), "No UFS server addresses were provided yet.");
                    Disconnected(this, new DisconnectedEventArgs(userInitiated: false));
                    return;
                }

                var random = new Random();
                ufsServer = serverList[random.Next(serverList.Count)];
            }

            // steamclient has the connection type hardcoded as TCP
            // todo: determine if UFS supports UDP and if we want to support it
            connection = new EnvelopeEncryptedConnection(new TcpConnection(), steamClient.Universe);

            connection.NetMsgReceived += NetMsgReceived;
            connection.Connected      += Connected;
            connection.Disconnected   += Disconnected;

            connection.Connect(ufsServer, ( int )ConnectionTimeout.TotalMilliseconds);
        }
Beispiel #11
0
            /// <summary>
            /// Initializes a new instance of the <see cref="JobCallback&lt;T&gt;"/> class.
            /// </summary>
            /// <param name="jobId">The for this callback.</param>
            /// <param name="callback">The inner callback object.</param>
            public JobCallback(JobID jobId, T callback)
                : base(jobId)
            {
                DebugLog.Assert(jobId != ulong.MaxValue, "JobCallback", "JobCallback used for non job based callback!");

                Callback = callback;
            }
Beispiel #12
0
        public static byte[]? GetMachineID(IMachineInfoProvider machineInfoProvider)
        {
            if (!generationTable.TryGetValue(machineInfoProvider, out var generateTask))
            {
                DebugLog.WriteLine(nameof(HardwareUtils), "GetMachineID() called before Init()");
                return(null);
            }

            DebugLog.Assert(generateTask != null, nameof(HardwareUtils), "GetMachineID() found null task - should be impossible.");

            try
            {
                bool didComplete = generateTask.Wait(TimeSpan.FromSeconds(30));

                if (!didComplete)
                {
                    DebugLog.WriteLine(nameof(HardwareUtils), "Unable to generate machine_id in a timely fashion, logons may fail");
                    return(null);
                }
            }
            catch (AggregateException ex) when(ex.InnerException != null && generateTask.IsFaulted)
            {
                // Rethrow the original exception rather than a wrapped AggregateException.
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
            }

            MachineID machineId = generateTask.Result;

            using MemoryStream ms = new MemoryStream();
            machineId.WriteToStream(ms);
            return(ms.ToArray());
        }
Beispiel #13
0
        /// <summary>
        /// Attempts to decrypts file names with the given encryption key.
        /// </summary>
        /// <param name="encryptionKey">The encryption key.</param>
        /// <returns><c>true</c> if the file names were successfully decrypted; otherwise, <c>false</c>.</returns>
        public bool DecryptFilenames(byte[] encryptionKey)
        {
            if (!FilenamesEncrypted)
            {
                return(true);
            }

            DebugLog.Assert(Files != null, nameof(DepotManifest), "Files was null when attempting to decrypt filenames.");

            foreach (var file in Files)
            {
                byte[] enc_filename = Convert.FromBase64String(file.FileName);
                byte[] filename;
                try
                {
                    filename = CryptoHelper.SymmetricDecrypt(enc_filename, encryptionKey);
                }
                catch (Exception)
                {
                    return(false);
                }

                file.FileName = Encoding.UTF8.GetString(filename).TrimEnd(new char[] { '\0' }).Replace(altDirChar, Path.DirectorySeparatorChar);
            }

            FilenamesEncrypted = false;
            return(true);
        }
Beispiel #14
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientMsg&lt;BodyType&gt;"/> class.
        /// This is a recieve constructor.
        /// </summary>
        /// <param name="msg">The packet message to build this client message from.</param>
        public ClientMsg(IPacketMsg msg)
            : this()
        {
            DebugLog.Assert(!msg.IsProto, "ClientMsg", "ClientMsg used for proto message!");

            Deserialize(msg.GetData());
        }
Beispiel #15
0
        // this is now a steamkit meme
        /// <summary>
        /// Nets the loop.
        /// </summary>
        void NetLoop()
        {
            // poll for readable data every 100ms
            const int POLL_MS = 100;

            DebugLog.Assert(cancellationToken != null, nameof(TcpConnection), "null cancellationToken in NetLoop");

            while (!cancellationToken.IsCancellationRequested)
            {
                bool canRead = false;

                try
                {
                    canRead = socket !.Poll(POLL_MS * 1000, SelectMode.SelectRead);
                }
                catch (SocketException ex)
                {
                    log.LogDebug(nameof(TcpConnection), "Socket exception while polling: {0}", ex);
                    break;
                }

                if (!canRead)
                {
                    // nothing to read yet
                    continue;
                }

                byte[] packData;

                try
                {
                    // read the packet off the network
                    packData = ReadPacket();
                }
                catch (IOException ex)
                {
                    log.LogDebug(nameof(TcpConnection), "Socket exception occurred while reading packet: {0}", ex);
                    break;
                }

                try
                {
                    NetMsgReceived?.Invoke(this, new NetMsgEventArgs(packData, CurrentEndPoint !));
                }
                catch (Exception ex)
                {
                    log.LogDebug(nameof(TcpConnection), "Unexpected exception propogated back to NetLoop: {0}", ex);
                }
            }

            // Thread is shutting down, ensure socket is shut down and disposed
            bool userShutdown = cancellationToken.IsCancellationRequested;

            if (userShutdown)
            {
                Shutdown();
            }
            Release(userShutdown);
        }
        public NetFilterEncryptionWithHMAC(byte[] sessionKey)
        {
            DebugLog.Assert(sessionKey.Length == 32, nameof(NetFilterEncryption), "AES session key was not 32 bytes!");

            this.sessionKey = sessionKey;
            this.hmacSecret = new byte[16];
            Array.Copy(sessionKey, 0, hmacSecret, 0, hmacSecret.Length);
        }
Beispiel #17
0
        /// <summary>
        /// Performs an encryption using AES/CBC/PKCS7 with an input byte array and key, with a random IV prepended using AES/ECB/None
        /// </summary>
        public static byte[] SymmetricEncryptWithIV(byte[] input, byte[] key, byte[] iv)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (iv == null)
            {
                throw new ArgumentNullException(nameof(iv));
            }

            DebugLog.Assert(key.Length == 32, "CryptoHelper", "SymmetricEncrypt used with non 32 byte key!");

            using (var aes = Aes.Create())
            {
                aes.BlockSize = 128;
                aes.KeySize   = 256;

                byte[] cryptedIv;

                // encrypt iv using ECB and provided key
                aes.Mode    = CipherMode.ECB;
                aes.Padding = PaddingMode.None;

                using (var aesTransform = aes.CreateEncryptor(key, null))
                {
                    cryptedIv = aesTransform.TransformFinalBlock(iv, 0, iv.Length);
                }

                // encrypt input plaintext with CBC using the generated (plaintext) IV and the provided key
                aes.Mode    = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;

                using (var aesTransform = aes.CreateEncryptor(key, iv))
                    using (var ms = new MemoryStream())
                        using (var cs = new CryptoStream(ms, aesTransform, CryptoStreamMode.Write))
                        {
                            cs.Write(input, 0, input.Length);
                            cs.FlushFinalBlock();

                            var cipherText = ms.ToArray();

                            // final output is 16 byte ecb crypted IV + cbc crypted plaintext
                            var output = new byte[cryptedIv.Length + cipherText.Length];

                            Array.Copy(cryptedIv, 0, output, 0, cryptedIv.Length);
                            Array.Copy(cipherText, 0, output, cryptedIv.Length, cipherText.Length);

                            return(output);
                        }
            }
        }
Beispiel #18
0
        /// <summary>
        /// Decrypts using AES/CBC/PKCS7 with an input byte array and key, using the random IV prepended using AES/ECB/None
        /// </summary>
        static byte[] SymmetricDecrypt(byte[] input, byte[] key, out byte[] iv)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            DebugLog.Assert(key.Length == 32, "CryptoHelper", "SymmetricDecrypt used with non 32 byte key!");

            using (var aes = Aes.Create())
            {
                aes.BlockSize = 128;
                aes.KeySize   = 256;

                // first 16 bytes of input is the ECB encrypted IV
                byte[] cryptedIv = new byte[16];
                iv = new byte[cryptedIv.Length];
                Array.Copy(input, 0, cryptedIv, 0, cryptedIv.Length);

                // the rest is ciphertext
                byte[] cipherText = new byte[input.Length - cryptedIv.Length];
                Array.Copy(input, cryptedIv.Length, cipherText, 0, cipherText.Length);

                // decrypt the IV using ECB
                aes.Mode    = CipherMode.ECB;
                aes.Padding = PaddingMode.None;

                using (var aesTransform = aes.CreateDecryptor(key, null))
                {
                    iv = aesTransform.TransformFinalBlock(cryptedIv, 0, cryptedIv.Length);
                }

                // decrypt the remaining ciphertext in cbc with the decrypted IV
                aes.Mode    = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;

                using (var aesTransform = aes.CreateDecryptor(key, iv))
                    using (var ms = new MemoryStream(cipherText))
                        using (var cs = new CryptoStream(ms, aesTransform, CryptoStreamMode.Read))
                        {
                            // plaintext is never longer than ciphertext
                            byte[] plaintext = new byte[cipherText.Length];

                            int len = cs.ReadAll(plaintext);

                            byte[] output = new byte[len];
                            Array.Copy(plaintext, 0, output, 0, len);

                            return(output);
                        }
            }
        }
Beispiel #19
0
        public NetFilterEncryptionWithHMAC(byte[] sessionKey, ILogContext log)
        {
            DebugLog.Assert(sessionKey.Length == 32, nameof(NetFilterEncryption), "AES session key was not 32 bytes!");

            this.sessionKey = sessionKey;
            this.log        = log ?? throw new ArgumentNullException(nameof(log));
            this.hmacSecret = new byte[16];
            Array.Copy(sessionKey, 0, hmacSecret, 0, hmacSecret.Length);
        }
Beispiel #20
0
        /// <summary>
        /// Connects and authenticates to the specified content server.
        /// </summary>
        /// <param name="csServer">The content server to connect to.</param>
        /// <exception cref="System.ArgumentNullException">csServer was null.</exception>
        public void Connect(Server csServer)
        {
            DebugLog.Assert(steamClient.IsConnected, "CDNClient", "CMClient is not connected!");

            if (csServer == null)
            {
                throw new ArgumentNullException("csServer");
            }

            byte[] pubKey = KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse);

            sessionKey = CryptoHelper.GenerateRandomBlock(32);

            byte[] cryptedSessKey = null;
            using (var rsa = new RSACrypto(pubKey))
            {
                cryptedSessKey = rsa.Encrypt(sessionKey);
            }

            string data;

            if (appTicket == null)
            {
                // no appticket, doing anonymous connection
                data = string.Format("sessionkey={0}&anonymoususer=1&steamid={1}", WebHelpers.UrlEncode(cryptedSessKey), steamClient.SteamID.ConvertToUInt64());
            }
            else
            {
                byte[] encryptedAppTicket = CryptoHelper.SymmetricEncrypt(appTicket, sessionKey);
                data = string.Format("sessionkey={0}&appticket={1}", WebHelpers.UrlEncode(cryptedSessKey), WebHelpers.UrlEncode(encryptedAppTicket));
            }

            KeyValue initKv = DoCommand(csServer, "initsession", data, WebRequestMethods.Http.Post);

            sessionId  = ( ulong )initKv["sessionid"].AsLong();
            reqCounter = initKv["req-counter"].AsLong();

            if (appTicket == null)
            {
                data = string.Format("depotid={0}", depotId);
            }
            else
            {
                byte[] encryptedAppTicket = CryptoHelper.SymmetricEncrypt(appTicket, sessionKey);
                data = string.Format("appticket={0}", WebHelpers.UrlEncode(encryptedAppTicket));
            }

            DoCommand(csServer, "authdepot", data, WebRequestMethods.Http.Post, true);

            connectedServer = csServer;
        }
Beispiel #21
0
        private void ConnectCompleted(bool success)
        {
            // Always discard result if our request was cancelled
            // If we have no cancellation token source, we were already Release()'ed
            if (cancellationToken?.IsCancellationRequested ?? true)
            {
                log.LogDebug(nameof(TcpConnection), "Connection request to {0} was cancelled", CurrentEndPoint);
                if (success)
                {
                    Shutdown();
                }
                Release(userRequestedDisconnect: true);
                return;
            }
            else if (!success)
            {
                log.LogDebug(nameof(TcpConnection), "Failed connecting to {0}", CurrentEndPoint);
                Release(userRequestedDisconnect: false);
                return;
            }

            log.LogDebug(nameof(TcpConnection), "Connected to {0}", CurrentEndPoint);
            DebugLog.Assert(socket != null, nameof(TcpConnection), "Socket should be non-null after connecting.");

            try
            {
                lock (netLock)
                {
                    netStream = new NetworkStream(socket, false);
                    netReader = new BinaryReader(netStream);
                    netWriter = new BinaryWriter(netStream);

                    netThread = new Thread(NetLoop)
                    {
                        Name = "SK2-TcpConn"
                    };

                    CurrentEndPoint = socket !.RemoteEndPoint;
                }

                netThread.Start();

                Connected?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                log.LogDebug(nameof(TcpConnection), "Exception while setting up connection to {0}: {1}", CurrentEndPoint, ex);
                Release(userRequestedDisconnect: false);
            }
        }
Beispiel #22
0
        /// <summary>
        /// Connects to the specified end point.
        /// </summary>
        /// <param name="endPoint">The end point to connect to.</param>
        /// <param name="timeout">Timeout in milliseconds</param>
        public void Connect(EndPoint endPoint, int timeout)
        {
            lock ( netLock )
            {
                DebugLog.Assert(cancellationToken == null, nameof(TcpConnection), "Connection cancellation token is not null");
                cancellationToken = new CancellationTokenSource();

                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.ReceiveTimeout = timeout;
                socket.SendTimeout    = timeout;

                CurrentEndPoint = endPoint;
                log.LogDebug(nameof(TcpConnection), "Connecting to {0}...", CurrentEndPoint);
                TryConnect(timeout);
            }
        }
Beispiel #23
0
        /// <summary>
        /// Connects this client to a UFS server.
        /// This begins the process of connecting and encrypting the data channel between the client and the UFS server.
        /// Results are returned asynchronously in a <see cref="ConnectedCallback"/>.
        /// If the UFS server that this client attempts to connect to is down, a <see cref="DisconnectedCallback"/> will be posted instead.
        /// <see cref="UFSClient"/> will not attempt to reconnect to Steam, you must handle this callback and call <see cref="Connect"/> again, preferrably after a short delay.
        /// In order to connect to the UFS server, the parent <see cref="SteamClient"/> must be connected to the CM server.
        /// </summary>
        /// <param name="ufsServer">
        /// The <see cref="System.Net.IPEndPoint"/> of the UFS server to connect to.
        /// If <c>null</c>, <see cref="UFSClient"/> will randomly select a UFS server from the <see cref="SteamClient"/>'s list of servers.
        /// </param>
        public void Connect(IPEndPoint ufsServer = null)
        {
            DebugLog.Assert(steamClient.IsConnected, "UFSClient", "CMClient is not connected!");

            Disconnect();

            if (ufsServer == null)
            {
                var serverList = steamClient.GetServersOfType(EServerType.UFS);

                Random random = new Random();
                ufsServer = serverList[random.Next(serverList.Count)];
            }

            connection.Connect(ufsServer, ( int )ConnectionTimeout.TotalMilliseconds);
        }
Beispiel #24
0
        /// <summary>
        /// Performs an encryption using AES/CBC/PKCS7 with an input byte array and key, with a random IV prepended using AES/ECB/None
        /// </summary>
        public static byte[] SymmetricEncrypt(byte[] input, byte[] key)
        {
            DebugLog.Assert(key.Length == 32, "CryptoHelper", "SymmetricEncrypt used with non 32 byte key!");

            using (var aes = new RijndaelManaged())
            {
                aes.BlockSize = 128;
                aes.KeySize   = 256;

                // generate iv
                byte[] iv        = GenerateRandomBlock(16);
                byte[] cryptedIv = new byte[16];


                // encrypt iv using ECB and provided key
                aes.Mode    = CipherMode.ECB;
                aes.Padding = PaddingMode.None;

                using (var aesTransform = aes.CreateEncryptor(key, null))
                {
                    cryptedIv = aesTransform.TransformFinalBlock(iv, 0, iv.Length);
                }

                // encrypt input plaintext with CBC using the generated (plaintext) IV and the provided key
                aes.Mode    = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;

                using (var aesTransform = aes.CreateEncryptor(key, iv))
                    using (var ms = new MemoryStream())
                        using (var cs = new CryptoStream(ms, aesTransform, CryptoStreamMode.Write))
                        {
                            cs.Write(input, 0, input.Length);
                            cs.FlushFinalBlock();

                            byte[] cipherText = ms.ToArray();

                            // final output is 16 byte ecb crypted IV + cbc crypted plaintext
                            byte[] output = new byte[cryptedIv.Length + cipherText.Length];

                            Array.Copy(cryptedIv, 0, output, 0, cryptedIv.Length);
                            Array.Copy(cipherText, 0, output, cryptedIv.Length, cipherText.Length);

                            return(output);
                        }
            }
        }
Beispiel #25
0
        /// <summary>
        /// Connects and initializes a session to the specified content server.
        /// </summary>
        /// <param name="csServer">The content server to connect to.</param>
        /// <exception cref="System.ArgumentNullException">csServer was null.</exception>
        /// <exception cref="HttpRequestException">An network error occurred when performing the request.</exception>
        /// <exception cref="SteamKitWebRequestException">A network error occurred when performing the request.</exception>
        public async Task ConnectAsync(Server csServer)
        {
            DebugLog.Assert(steamClient.IsConnected, "CDNClient", "CMClient is not connected!");

            if (csServer == null)
            {
                throw new ArgumentNullException(nameof(csServer));
            }

            // Nothing needs to be done to initialize a session to a CDN server
            if (csServer.Type == "CDN")
            {
                connectedServer = csServer;
                return;
            }

            byte[] pubKey = KeyDictionary.GetPublicKey(steamClient.Universe);

            sessionKey = CryptoHelper.GenerateRandomBlock(32);

            byte[] cryptedSessKey = null;
            using (var rsa = new RSACrypto(pubKey))
            {
                cryptedSessKey = rsa.Encrypt(sessionKey);
            }

            string data;

            if (appTicket == null)
            {
                // no appticket, doing anonymous connection
                data = string.Format("sessionkey={0}&anonymoususer=1&steamid={1}", WebHelpers.UrlEncode(cryptedSessKey), steamClient.SteamID.ConvertToUInt64());
            }
            else
            {
                byte[] encryptedAppTicket = CryptoHelper.SymmetricEncrypt(appTicket, sessionKey);
                data = string.Format("sessionkey={0}&appticket={1}", WebHelpers.UrlEncode(cryptedSessKey), WebHelpers.UrlEncode(encryptedAppTicket));
            }

            var initKv = await DoCommandAsync(csServer, HttpMethod.Post, "initsession", data).ConfigureAwait(false);

            sessionId       = initKv["sessionid"].AsUnsignedLong();
            reqCounter      = initKv["req-counter"].AsLong();
            connectedServer = csServer;
        }
Beispiel #26
0
        /// <summary>
        /// Connects this client to a UFS server.
        /// This begins the process of connecting and encrypting the data channel between the client and the UFS server.
        /// Results are returned asynchronously in a <see cref="ConnectedCallback"/>.
        /// If the UFS server that this client attempts to connect to is down, a <see cref="DisconnectedCallback"/> will be posted instead.
        /// <see cref="UFSClient"/> will not attempt to reconnect to Steam, you must handle this callback and call <see cref="Connect"/> again, preferrably after a short delay.
        /// In order to connect to the UFS server, the parent <see cref="SteamClient"/> must be connected to the CM server.
        /// </summary>
        /// <param name="ufsServer">
        /// The <see cref="System.Net.IPEndPoint"/> of the UFS server to connect to.
        /// If <c>null</c>, <see cref="UFSClient"/> will randomly select a UFS server from the <see cref="SteamClient"/>'s list of servers.
        /// </param>
        public void Connect(IPEndPoint ufsServer = null)
        {
            DebugLog.Assert(steamClient.IsConnected, "UFSClient", "CMClient is not connected!");

            this.Disconnect();

            pendingNetFilterEncryption = null;

            if (ufsServer == null)
            {
                var serverList = steamClient.GetServersOfType(EServerType.UFS);

                Random random = new Random();
                ufsServer = serverList[random.Next(serverList.Count)];
            }

            connection.Connect(Task.FromResult(ufsServer), ( int )ConnectionTimeout.TotalMilliseconds);
        }
Beispiel #27
0
        void HandleEncryptResult( IPacketMsg packetMsg )
        {
            var result = new Msg<MsgChannelEncryptResult>( packetMsg );

            log.LogDebug( nameof(EnvelopeEncryptedConnection), "Encryption result: {0}", result.Body.Result );
            DebugLog.Assert( encryption != null, nameof( EnvelopeEncryptedConnection ), "Encryption is null" );

            if ( result.Body.Result == EResult.OK && encryption != null )
            {
                state = EncryptionState.Encrypted;
                Connected?.Invoke( this, EventArgs.Empty );
            }
            else
            {
                log.LogDebug( nameof(EnvelopeEncryptedConnection), "Encryption channel setup failed" );
                Disconnect( userInitiated: false );
            }
        }
Beispiel #28
0
        /// <summary>
        /// Decrypts using AES/ECB/PKCS7
        /// </summary>
        public static byte[] SymmetricDecryptECB(byte[] input, byte[] key)
        {
            DebugLog.Assert(key.Length == 32, "CryptoHelper", "SymmetricDecryptECB used with non 32 byte key!");

            using (var aes = new RijndaelManaged())
            {
                aes.BlockSize = 128;
                aes.KeySize   = 256;
                aes.Mode      = CipherMode.ECB;
                aes.Padding   = PaddingMode.PKCS7;

                using (var aesTransform = aes.CreateDecryptor(key, null))
                {
                    byte[] output = aesTransform.TransformFinalBlock(input, 0, input.Length);

                    return(output);
                }
            }
        }
Beispiel #29
0
        /// <summary>
        /// Decrypts using AES/CBC/PKCS7 with an input byte array and key, using the IV (comprised of random bytes and the HMAC-SHA1 of the random bytes and plaintext) prepended using AES/ECB/None
        /// </summary>
        public static byte[] SymmetricDecryptHMACIV(byte[] input, byte[] key, byte[] hmacSecret)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (hmacSecret == null)
            {
                throw new ArgumentNullException(nameof(hmacSecret));
            }

            DebugLog.Assert(key.Length >= 16, "CryptoHelper", "SymmetricDecryptHMACIV used with a key smaller than 16 bytes.");
            var truncatedKeyForHmac = new byte[16];

            Array.Copy(key, 0, truncatedKeyForHmac, 0, truncatedKeyForHmac.Length);

            var plaintextData = SymmetricDecrypt(input, key, out var iv);

            // validate HMAC
            byte[] hmacBytes;
            using (var hmac = new HMACSHA1(hmacSecret))
                using (var ms = new MemoryStream())
                {
                    ms.Write(iv, iv.Length - 3, 3);
                    ms.Write(plaintextData, 0, plaintextData.Length);
                    ms.Seek(0, SeekOrigin.Begin);

                    hmacBytes = hmac.ComputeHash(ms);
                }

            if (!hmacBytes.Take(iv.Length - 3).SequenceEqual(iv.Take(iv.Length - 3)))
            {
                throw new CryptographicException(string.Format(CultureInfo.InvariantCulture, "{0} was unable to decrypt packet: HMAC from server did not match computed HMAC.", nameof(NetFilterEncryption)));
            }

            return(plaintextData);
        }
Beispiel #30
0
        /// <summary>
        /// Sends a packet immediately.
        /// </summary>
        /// <param name="packet">The packet.</param>
        private void SendPacket(UdpPacket packet)
        {
            packet.Header.SourceConnID = sourceConnId;
            packet.Header.DestConnID   = remoteConnId;
            packet.Header.SeqAck       = inSeqAcked = inSeq;

            log.LogDebug("UdpConnection", "Sent -> {0} Seq {1} Ack {2}; {3} bytes; Message: {4} bytes {5} packets",
                         packet.Header.PacketType, packet.Header.SeqThis, packet.Header.SeqAck,
                         packet.Header.PayloadSize, packet.Header.MsgSize, packet.Header.PacketsInMsg);

            byte[] data = packet.GetData();

            DebugLog.Assert(CurrentEndPoint != null, nameof(UdpConnection), "CurrentEndPoint should not be null when connected.");

            try
            {
                sock.SendTo(data, CurrentEndPoint);
            }
            catch (SocketException e)
            {
                log.LogDebug("UdpConnection", "Critical socket failure: " + e.SocketErrorCode);

                state = ( int )State.Disconnected;
                return;
            }

            // If we've been idle but completely acked for more than two seconds, the next sent
            // packet will trip the resend detection. This fixes that.
            if (outSeqSent == outSeqAcked)
            {
                nextResend = DateTime.Now.AddSeconds(RESEND_DELAY);
            }

            // Sending should generally carry on from the packet most recently sent, even if it was a
            // resend (who knows what else was lost).
            if (packet.Header.SeqThis > 0)
            {
                outSeqSent = Math.Max(outSeqSent, packet.Header.SeqThis);
            }
        }