/// <summary> /// Sends the outgoing message /// </summary> /// <param name="message">The message string</param> /// <param name="id">The message string</param> public async void SendMessage(string message, string id) { // encrypt the message with other user's key string encryptedMessage; { string key = (await SharedStuff.Database.Table <DatabaseHelper.Users>().Where(user => user.Username == Username) .FirstAsync()).Key; encryptedMessage = BouncyCastleHelper.AesGcmEncrypt(message, key); } // create json string json = JsonConvert.SerializeObject(new JsonTypes.SendMessage { Type = 0, Id = id, Payload = new JsonTypes.SendMessagePayload { To = Username, Message = encryptedMessage } }); // send the json SharedStuff.Websocket.SendAsync(json, ok => { SharedStuff.Database.InsertAsync(new DatabaseHelper.Messages { Date = DateTime.Now, MyMessage = true, Payload = message, Type = 0, Username = Username }).Wait(); }); // add to main window Application.Current.Dispatcher.Invoke(delegate { var u = MainChatsWindow.MessagesList.First(x => x.Username == Username); u.Message = message; u.IsLastMessageForUser = true; }); }
private async void OpenFileClicked(object sender, RoutedEventArgs e) { if (sender is Button btn) { if (btn.CommandParameter is string token) { DatabaseHelper.Files f = new DatabaseHelper.Files(); try { f = await SharedStuff.Database.Table <DatabaseHelper.Files>() .Where(file => file.Token == token).FirstAsync(); if (string.IsNullOrEmpty(f.Location)) { throw new InvalidOperationException(); } if (f.Location == " ") // null means the file is not downloaded. ' ' means it does not exists { throw new FileNotFoundException(); } if (File.Exists(f.Location)) // check if the downloaded file exists { System.Diagnostics.Process.Start(f.Location); } else { await SharedStuff.Database.ExecuteAsync( "UPDATE Files SET Location = ? WHERE Token = ?" , " ", token); throw new FileNotFoundException(); } } catch (FileNotFoundException) { // at first check if server has the file var err = new ErrorDialogSample { ErrorText = { Text = "This file does not exists on your computer. Ask other user to send this file again." }, ErrorTitle = { Text = "Cannot Open File" } }; await DialogHost.Show(err, "ChatDialogHost" + Username); } catch (InvalidOperationException) // this token does not exists in database { // download this file await Application.Current.Dispatcher.Invoke(async delegate { ProgressBar bar = ((StackPanel)((DockPanel)((Button)sender).Parent).Parent) .Children[1] as ProgressBar; try { string downloadFileUrl = $"https://{Properties.Settings.Default.ServerAddress}/download?token={token}"; // token does not contain special characters so we are good string destinationFilePath = Path.GetTempFileName(); using (var client = new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath)) { client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { bar.Value = progressPercentage ?? 0; }; await client.StartDownload(); } // get the key string key = (await SharedStuff.Database.Table <DatabaseHelper.Users>() .Where(user => user.Username == Username).FirstAsync()).Key; BouncyCastleHelper.AesGcmDecrypt(new FileInfo(destinationFilePath), new FileInfo(Path.Combine(SharedStuff.DownloadPath, f.Name)), key); await SharedStuff.Database.ExecuteAsync( "UPDATE Files SET Location = ? WHERE Token = ?" , Path.Combine(SharedStuff.DownloadPath, f.Name), token); } catch (FileNotFoundException) // This error means that the files does not exists on server. (it's too old) { await SharedStuff.Database.ExecuteAsync( "UPDATE Files SET Location = ? WHERE Token = ?" , " ", token); // this will make the File.Exists always return false MessagesList.First(msg => msg.Token == token).DownloadButtonIcon = PackIconKind.DownloadOff; var err = new ErrorDialogSample { ErrorText = { Text = "This file is too old and could not be downloaded. Ask other user to send this file again." }, ErrorTitle = { Text = "Cannot Download File" } }; await DialogHost.Show(err, "ChatDialogHost" + Username); } catch (Exception ex) { var err = new ErrorDialogSample { ErrorText = { Text = ex.Message }, ErrorTitle = { Text = "Cannot Download File" } }; await DialogHost.Show(err, "ChatDialogHost" + Username); } finally { bar.Value = 101; } }); } } } }
/// <summary> /// Encrypts and uploads a file to server /// </summary> /// <param name="guid">The ID of message</param> public static async void UploadFile(string guid) { // at first encrypt the file var encryptedFile = new FileInfo(Path.GetTempFileName()); string username = PendingMessages[guid].With; string key = (await Database.Table <DatabaseHelper.Users>() .Where(user => user.Username == username).FirstAsync()).Key; await Task.Run(() => BouncyCastleHelper.AesGcmEncrypt(new FileInfo(PendingMessages[guid].FilePath), encryptedFile, key)); // now upload the file try { using (HttpClient localClient = new HttpClient()) using (var multiForm = new MultipartFormDataContent()) { localClient.Timeout = TimeSpan.FromMinutes(15); // You may need this if you are uploading a big file var file = new ProgressableStreamContent( new StreamContent(encryptedFile.OpenRead()) , (sent, total) => { }); multiForm.Add(file, "document", Path.GetFileName(PendingMessages[guid].FilePath)); // Add the file localClient.DefaultRequestHeaders.Add("token", PendingMessages[guid].Token); var response = await localClient.PostAsync("https://" + Properties.Settings.Default.ServerAddress + "/upload", multiForm); if (response.StatusCode == HttpStatusCode.OK) { string res = await response.Content.ReadAsStringAsync(); Console.WriteLine(res); // parse result var jsonRes = JsonConvert.DeserializeObject <JsonTypes.ServerStatus>(res); if (!jsonRes.Ok) { throw new WebException("Server returned an error: " + response.StatusCode); } // submit the file Websocket.Send(JsonConvert.SerializeObject(new JsonTypes.SendMessage { Id = guid, Type = 1, Payload = new JsonTypes.SendMessagePayload { To = username, Message = JsonConvert.SerializeObject(new JsonTypes.FilePayload { FileName = Path.GetFileName(PendingMessages[guid].FilePath), Token = PendingMessages[guid].Token, FileSize = ByteSizeToString(new FileInfo(PendingMessages[guid].FilePath).Length) }) } })); // update UI PendingMessages[guid].Sent = 0; PendingMessages[guid].Progress = 101; // update database await Database.RunInTransactionAsync(tran => { tran.Insert(new DatabaseHelper.Messages { Date = DateTime.Now, MyMessage = true, Payload = PendingMessages[guid].Token, Type = 1, Username = PendingMessages[guid].With }); tran.Insert(new DatabaseHelper.Files { Token = PendingMessages[guid].Token, Location = PendingMessages[guid].FilePath, Name = Path.GetFileName(PendingMessages[guid].FilePath) }); }); } else { throw new WebException("HTTP status code is not ok: " + response.StatusCode); } } } catch (Exception ex) { Console.WriteLine("Cannot upload file: " + ex); PendingMessages[guid].Sent = 2; // show the user that the upload has been failed PendingMessages[guid].Progress = 101; // show the user that the upload has been failed } finally { encryptedFile.Delete(); PendingMessages.Remove(guid); } }
private async void WS_OnMessage(object sender, MessageEventArgs e) { // at first try to parse the message as Server status. try { var status = JsonConvert.DeserializeObject <JsonTypes.MessageStatus>(e.Data); if (status.Id != null) // a real message status { if (!status.Ok) { Console.WriteLine("Error on message " + status.Id + ": " + status.Message); } if (status.Message != "sent") // this is a file token update { SharedStuff.PendingMessages[status.Id].Token = status.Message; // now start upload SharedStuff.UploadFile(status.Id); } else { SharedStuff.PendingMessages[status.Id].Sent = status.Ok ? (byte)0 : (byte)2; // Do not remove files from here; They are removed at "finally" block of SharedStuff.UploadFile if (SharedStuff.PendingMessages[status.Id].Type != 1) { SharedStuff.PendingMessages.Remove(status.Id); } } return; } if (status.Message != null && !status.Ok) { // TODO: HANDLE THIS SHIT return; } } catch (Exception) { // ignored : the type might be something else } // try ClientUpdateTypeStruct await _mu.WaitAsync(); // get the data in order that server sends them string nameSafe = ""; try { var msg = JsonConvert.DeserializeObject <JsonTypes.Updates>(e.Data); var keyList = await SharedStuff.Database.Table <DatabaseHelper.Users>().Where(k => k.Username == msg.Payload.From) .ToArrayAsync(); // get the key of the user string key; if (keyList.Length == 0) // Key agreement { var data = await SharedStuff.GetUserData(msg.Payload.From); key = Convert.ToBase64String(SharedStuff.Curve.calculateAgreement( Convert.FromBase64String(_savedData.PrivateKey), Convert.FromBase64String(data.PublicKey))); await SharedStuff.Database.InsertAsync(new DatabaseHelper.Users // save the key in database { Username = data.Username, Name = data.Name, Key = key, }); nameSafe = data.Name; } else // get the key from database; There is only one row because Username is unique { key = keyList[0].Key; } string toShowOnMainMenu = ""; JsonTypes.FilePayload filePayload = new JsonTypes.FilePayload(); switch (msg.Type) { case 0: // Text message // decrypt the message or assign the file token try { string message = BouncyCastleHelper.AesGcmDecrypt(msg.Payload.Message, key); await SharedStuff.Database.InsertAsync(new DatabaseHelper.Messages { Username = msg.Payload.From, Type = msg.Type, Date = msg.Payload.Date, Payload = message }); toShowOnMainMenu = message; } catch (Exception) { // TODO: RE-REQUEST public key } break; case 1: // File message filePayload = JsonConvert.DeserializeObject <JsonTypes.FilePayload>(msg.Payload.Message); await SharedStuff.Database.RunInTransactionAsync(trans => { trans.Insert(new DatabaseHelper.Messages { Username = msg.Payload.From, Type = msg.Type, Date = msg.Payload.Date, Payload = filePayload.Token }); trans.Insert(new DatabaseHelper.Files { Token = filePayload.Token, Name = filePayload.FileName }); }); toShowOnMainMenu = filePayload.FileName; break; } // save the value bool inserted = false, open = OpenWindowsList.ContainsKey(msg.Payload.From); int index = -1; try { index = MessagesList.IndexOf(MessagesList.First(x => x.Username == msg.Payload.From)); // get index of the UI if (index == -1) { throw new Exception(); } } catch (Exception) { string name = nameSafe; if (name == "") { name = (await SharedStuff.GetUserData(msg.Payload.From)).Name; } Application.Current.Dispatcher.Invoke(delegate { MessagesList.Insert(0, new MainMessagesNotify { FullDate = msg.Payload.Date, Message = toShowOnMainMenu, IsLastMessageForUser = false, Name = name, NewMessages = 1, Username = msg.Payload.From }); }); inserted = true; } if (index == -1) { return; } if (!open) // increase unread messages { await SharedStuff.Database.ExecuteAsync("UPDATE Users SET UnreadMessages = ? WHERE Username = ?", MessagesList[index].NewMessages + 1, msg.Payload.From); } if (!inserted) { Application.Current.Dispatcher.Invoke(delegate { // Show the user the update MessagesList.Move(index, 0); MessagesList[0].FullDate = msg.Payload.Date; MessagesList[0].Message = toShowOnMainMenu; MessagesList[0].IsLastMessageForUser = false; if (!open) { MessagesList[0].NewMessages++; } }); } // update the open windows if (open) { if (msg.Type == 0) // add text messages { OpenWindowsList[msg.Payload.From].AddMessageText(toShowOnMainMenu, msg.Payload.Date); } else if (msg.Type == 1) // add files { OpenWindowsList[msg.Payload.From].AddMessageFile(filePayload.Token, filePayload.FileName, msg.Payload.Date); } } } catch (Exception ex) { // TODO: HANDLE THIS SHIT } finally { _mu.Release(); } }
private async void LoginButtonClicked(object sender, RoutedEventArgs e) { LoadingDialogSample loadingDialog = new LoadingDialogSample // simple dialog with a spinning indicator { LoadingTextText = { Text = "Connecting to server..." } // set the text of the dialog }; Exception error = null; await DialogHost.Show(loadingDialog, "LoginDialogHost", delegate(object sender1, DialogOpenedEventArgs args) // show the dialog { string serverAddress = ServerUrlTxt.Text, username = UsernameTxt.Text, password = PasswordTxt.Password; bool trustEveryThing = TrustSslCheckBox.IsChecked.HasValue && TrustSslCheckBox.IsChecked.Value; Task.Factory.StartNew(async() => // connect to server from another thread { // let's see to trust all certificates or not if (trustEveryThing) { ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true; } else { ServicePointManager.ServerCertificateValidationCallback = null; } // at first get server's public RSA key try { if (SharedStuff.ServerPublicKey == null) { using (var wc = new WebClient()) { string key = wc.DownloadString("https://" + serverAddress + "/publicKey"); SharedStuff.ServerPublicKey = BouncyCastleHelper.ReadAsymmetricKeyParameter(key); } } } catch (Exception ex) { error = new Exception("Can't get public key", ex); // close dialog on main thread (because it is not running from main thread) await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close())); return; } // now login the user string toSave; try { // at first generate a ecdh curve25519 key pair var curveKeys = SharedStuff.Curve.generateKeyPair(); // encrypt the password with server's public key string rsaPassword = Convert.ToBase64String(BouncyCastleHelper.RsaEncrypt(SharedStuff.ServerPublicKey, password)); // build the request var postValues = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("username", username), new KeyValuePair <string, string>("password", rsaPassword), new KeyValuePair <string, string>("key", Convert.ToBase64String(curveKeys.getPublicKey())), }); var result = await SharedStuff.Client.PostAsync("https://" + serverAddress + "/users/registerClient", postValues); string resultContent = await result.Content.ReadAsStringAsync(); // parse the server result var jsonRes = JsonConvert.DeserializeObject <JsonTypes.ServerStatus>(resultContent); if (!jsonRes.Ok) { throw new Exception("Server returned an error: " + jsonRes.Message); } // save the login details toSave = JsonConvert.SerializeObject(new JsonTypes.SavedData { Username = username, Password = password, PublicKey = Convert.ToBase64String(curveKeys.getPublicKey()), PrivateKey = Convert.ToBase64String(curveKeys.getPrivateKey()) }); Properties.Settings.Default.Name = jsonRes.Message; Properties.Settings.Default.ServerAddress = serverAddress; } catch (Exception ex) { error = new Exception("Can't login you", ex); // close dialog on main thread (because it is not running from main thread) await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close())); return; } // save the data (we had a successful login) try { byte[] encrypted = ProtectedData.Protect(Encoding.UTF8.GetBytes(toSave), null, DataProtectionScope.CurrentUser); // encrypt the data Properties.Settings.Default.LoginData = Convert.ToBase64String(encrypted); // save data to application settings Properties.Settings.Default.TrustInvalidSSL = trustEveryThing; Properties.Settings.Default.Save(); } catch (Exception ex) { error = new Exception("Cannot save data", ex); // TODO: logout the user if this fails // close dialog on main thread (because it is not running from main thread) await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close())); return; } // close dialog on main thread (because it is not running from main thread) await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => args.Session.Close())); }); }); // check for errors if (error != null) { ErrorDialogSample errorDialog = new ErrorDialogSample { ErrorTitle = { Text = error.Message }, ErrorText = { Text = error.InnerException.Message } // Inner exception is never empty }; await DialogHost.Show(errorDialog, "LoginDialogHost"); } else // login the user { Hide(); new MainChatsWindow().Show(); } }