/// <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()); }
/// <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); }
/// <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); }
/// <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); } } }
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); }
/// <summary> /// Initializes a new instance of the <see cref="ClientMsg<BodyType>"/> 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()); }
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); }
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)); }
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); } }
/// <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); }
/// <summary> /// Initializes a new instance of the <see cref="JobCallback<T>"/> 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; }
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()); }
/// <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); }
/// <summary> /// Initializes a new instance of the <see cref="ClientMsg<BodyType>"/> 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()); }
// 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); }
/// <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); } } }
/// <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); } } }
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); }
/// <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; }
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); } }
/// <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); } }
/// <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); }
/// <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); } } }
/// <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; }
/// <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); }
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 ); } }
/// <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); } } }
/// <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); }
/// <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); } }