[tomboy-list] more encryption code
Alex Graveley
alex at beatniksoftware.com
Mon Dec 4 17:14:53 PST 2006
Cool! Great work Roger. I haven't given this close review yet, but I
think the encrypt button should probably be listed in the Tools menu in
the toolbar. I don't think it's a common enough action to warrant being
constantly visible.
Again, I'd prefer to leave password maintenence outside of Tomboy.
Also, given that Tomboy is now an official Gnome app, we're sure to get
some push-back for not using the Gnome technologies intended to solve
exactly this problem.
-Alex
Roger Nesbitt wrote:
> Hi all,
>
> Here's the latest code. I've implemented AES encryption, and the only
> thing left to do is how we get passwords from the user. At the moment
> all notes are encrypted with the password "test".
>
> Anyone want to stick up for gnome-keyring as a method of storing and
> retrieving passwords? Otherwise I'll quickly stick in the two dialogs
> required to have passwords entered from the app itself. I still can't
> really see the benefit in gnome-keyring, but don't want to cause Alex
> any maintenance headaches...
>
> Also, any graphical artists out there? We need a version of the tomboy
> yellow note paper icon that has a little lock in the middle. I haven't
> found any suitable stock Gnome icon for the "encrypt" button either.
>
> Cheers,
> Roger
>
>
> ------------------------------------------------------------------------
>
> ? .EncryptionManager.cs.swp
> ? .Note.cs.swp
> ? EncryptionManager.cs
> Index: Makefile.am
> ===================================================================
> RCS file: /cvs/gnome/tomboy/Tomboy/Makefile.am,v
> retrieving revision 1.49
> diff -u -8 -p -r1.49 Makefile.am
> --- Makefile.am 22 Nov 2006 09:58:06 -0000 1.49
> +++ Makefile.am 5 Dec 2006 00:16:35 -0000
> @@ -40,16 +40,17 @@ CSFILES = \
> $(srcdir)/Preferences.cs \
> $(srcdir)/RecentChanges.cs \
> $(srcdir)/Tray.cs \
> $(srcdir)/Trie.cs \
> $(srcdir)/Undo.cs \
> $(srcdir)/Utils.cs \
> $(srcdir)/Watchers.cs \
> $(srcdir)/XKeybinder.cs \
> + $(srcdir)/EncryptionManager.cs \
> \
> $(srcdir)/panelapplet/*.cs \
> \
> $(DBUS_CSFILES)
>
>
> ASSEMBLIES = \
> $(TOMBOY_LIBS) \
> Index: Note.cs
> ===================================================================
> RCS file: /cvs/gnome/tomboy/Tomboy/Note.cs,v
> retrieving revision 1.37
> diff -u -8 -p -r1.37 Note.cs
> --- Note.cs 15 Nov 2006 19:41:30 -0000 1.37
> +++ Note.cs 5 Dec 2006 00:16:36 -0000
> @@ -5,35 +5,46 @@ using System.Diagnostics;
> using System.IO;
> using System.Xml;
> using Mono.Unix;
>
> namespace Tomboy
> {
> public delegate void NoteRenameHandler (Note sender, string old_title);
>
> + public class DecryptionRequiredException: Exception
> + {
> + }
> +
> // Contains all pure note data, like the note title and note text.
> public class NoteData
> {
> readonly string uri;
> string title;
> string text;
> + byte[] encrypted_text;
> + byte[] encryption_key; // The hashed password used to encrypt this note
> + bool encrypted; // Set true if the note is encrypted or to be encrypted
> + bool text_changed; // Set true if the encrypted text needs to be re-encrypted
> DateTime create_date;
> DateTime change_date;
>
> int cursor_pos;
> int width, height;
> int x, y;
>
> const int noPosition = -1;
>
> public NoteData (string uri)
> {
> this.uri = uri;
> this.text = "";
> + encrypted_text = null;
> + encrypted = false;
> + text_changed = false;
> x = noPosition;
> y = noPosition;
> }
>
> public string Uri
> {
> get { return uri; }
> }
> @@ -41,18 +52,75 @@ namespace Tomboy
> public string Title
> {
> get { return title; }
> set { title = value; }
> }
>
> public string Text
> {
> - get { return text; }
> - set { text = value; }
> + get
> + {
> + // May throw DecryptionRequiredException
> + if (Locked)
> + text = EncryptionManager.Instance.HandleEncryptedNote (this);
> + return text;
> + }
> + set
> + {
> + text = value;
> + encrypted_text = null;
> + text_changed = true;
> + }
> + }
> +
> + public byte[] EncryptedText
> + {
> + get
> + {
> + if (!encrypted) return null;
> + if ((text_changed || encrypted_text == null) && encryption_key != null)
> + {
> + encrypted_text = EncryptionManager.Instance.Encrypt (text, encryption_key);
> + text_changed = false;
> + }
> +
> + return encrypted_text;
> + }
> +
> + set
> + {
> + encrypted_text = value;
> + text = "";
> + encrypted = true;
> + }
> + }
> +
> + public byte[] EncryptionKey
> + {
> + get { return encryption_key; }
> + set { encryption_key = value; }
> + }
> +
> + public bool Encrypted
> + {
> + get { return encrypted; }
> + set
> + {
> + if (value == true && encryption_key == null && encrypted_text == null)
> + throw new ArgumentException ("EncryptionKey or EncryptedText must be specified before Encrypted is set to true");
> + if (value == false && Locked)
> + throw new ArgumentException ("Note must be decrypted before Encrypted is set to false");
> + encrypted = value;
> + }
> + }
> +
> + public bool Locked
> + {
> + get { return text == "" && encrypted_text != null; }
> }
>
> public DateTime CreateDate
> {
> get { return create_date; }
> set { create_date = value; }
> }
>
> @@ -681,16 +749,21 @@ namespace Tomboy
> case "title":
> note.Title = xml.ReadString ();
> break;
> case "text":
> // <text> is just a wrapper around <note-content>
> // NOTE: Use .text here to avoid triggering a save.
> note.Text = xml.ReadInnerXml ();
> break;
> + case "encrypted-text":
> + if (xml.GetAttribute ("type") != "AES-128")
> + throw new Exception("unknown encryption text algorithm");
> + note.EncryptedText = Convert.FromBase64String (xml.ReadString ());
> + break;
> case "last-change-date":
> note.ChangeDate =
> XmlConvert.ToDateTime (xml.ReadString ());
> break;
> case "create-date":
> note.CreateDate =
> XmlConvert.ToDateTime (xml.ReadString ());
> break;
> @@ -788,21 +861,31 @@ namespace Tomboy
> "size",
> null,
> "http://beatniksoftware.com/tomboy/size");
>
> xml.WriteStartElement (null, "title", null);
> xml.WriteString (note.Title);
> xml.WriteEndElement ();
>
> - xml.WriteStartElement (null, "text", null);
> - xml.WriteAttributeString ("xml", "space", null, "preserve");
> - // Insert <note-content> blob...
> - xml.WriteRaw (note.Text);
> - xml.WriteEndElement ();
> + if (note.Encrypted)
> + {
> + xml.WriteStartElement (null, "encrypted-text", null);
> + xml.WriteAttributeString (null, "type", null, "AES-128");
> + xml.WriteBase64 (note.EncryptedText, 0, note.EncryptedText.Length);
> + xml.WriteEndElement ();
> + }
> + else
> + {
> + xml.WriteStartElement (null, "text", null);
> + xml.WriteAttributeString ("xml", "space", null, "preserve");
> + // Insert <note-content> blob...
> + xml.WriteRaw (note.Text);
> + xml.WriteEndElement ();
> + }
>
> xml.WriteStartElement (null, "last-change-date", null);
> xml.WriteString (XmlConvert.ToString (note.ChangeDate));
> xml.WriteEndElement ();
>
> if (note.CreateDate != DateTime.MinValue) {
> xml.WriteStartElement (null, "create-date", null);
> xml.WriteString (XmlConvert.ToString (note.CreateDate));
> Index: NoteWindow.cs
> ===================================================================
> RCS file: /cvs/gnome/tomboy/Tomboy/NoteWindow.cs,v
> retrieving revision 1.54
> diff -u -8 -p -r1.54 NoteWindow.cs
> --- NoteWindow.cs 22 Nov 2006 09:58:06 -0000 1.54
> +++ NoteWindow.cs 5 Dec 2006 00:16:36 -0000
> @@ -464,17 +464,17 @@ namespace Tomboy
>
> args.Menu.Append (close_all);
> args.Menu.Append (close_window);
> }
>
> //
> // Toolbar
> //
> - // Add Link button, Font menu, Delete button to the window's
> + // Add Link button, Font menu, Encrypt and Delete buttons to the window's
> // toolbar.
> //
>
> Gtk.Toolbar MakeToolbar ()
> {
> Gtk.Toolbar toolbar = new Gtk.Toolbar ();
> toolbar.Tooltips = true;
>
> @@ -523,30 +523,42 @@ namespace Tomboy
> Gtk.Stock.Execute,
> Catalog.GetString ("T_ools"),
> plugin_menu);
> plugin_button.Show ();
> toolbar.AppendWidget (plugin_button,
> Catalog.GetString ("Use tools on this note"),
> null);
>
> + Gtk.ToggleButton encrypt = (Gtk.ToggleButton)
> + toolbar.AppendElement (
> + Gtk.ToolbarChildType.Togglebutton,
> + null,
> + Catalog.GetString ("Encrypt"),
> + Catalog.GetString ("Encrypt this note"),
> + null,
> + new Gtk.Image (Gtk.Stock.Convert, toolbar.IconSize), // TODO : change
> + null);
> + encrypt.Toggled += EncryptButtonToggled;
> + encrypt.Active = note.Data.Encrypted;
> +
> toolbar.AppendSpace ();
>
> Gtk.Widget delete =
> toolbar.AppendItem (
> Catalog.GetString ("Delete"),
> Catalog.GetString ("Delete this note"),
> null,
> new Gtk.Image (Gtk.Stock.Delete, toolbar.IconSize),
> new Gtk.SignalFunc (DeleteButtonClicked));
>
> // Don't allow deleting the "Start Here" note...
> if (note.IsSpecial)
> delete.Sensitive = false;
> -
> +
> return toolbar;
> }
>
> //
> // Plugin toolbar menu
> //
> // Prefixed with Open Plugins Folder action, the rest being
> // populated by individual plugins using
> @@ -730,16 +742,32 @@ namespace Tomboy
> search.SearchText = note.Buffer.Selection;
> }
> search.Present ();
> }
>
> void SearchActivate (object sender, EventArgs args)
> {
> SearchButtonClicked ();
> + }
> +
> + void EncryptButtonToggled (object sender, EventArgs args)
> + {
> + Gtk.ToggleButton encrypt = (Gtk.ToggleButton)sender;
> + if (note.Data.EncryptionKey == null && encrypt.Active)
> + {
> + note.Data.EncryptionKey = EncryptionManager.Instance.GetEncryptionKey (this);
> + if (note.Data.EncryptionKey == null)
> + {
> + encrypt.Active = false;
> + return;
> + }
> + }
> + note.Data.Encrypted = encrypt.Active;
> + note.QueueSave (false);
> }
> }
>
> public class NoteFindBar : Gtk.HBox
> {
> private Note note;
>
> Gtk.Entry entry;
>
>
> ------------------------------------------------------------------------
>
>
> using System;
> using System.IO;
> using System.Text;
> using System.Security.Cryptography;
>
> namespace Tomboy
> {
> public class InvalidKeyException: Exception
> {
> }
>
> public class EncryptionManager
> {
> static EncryptionManager instance = null;
> static readonly object lock_ = new object();
>
> System.Collections.ArrayList key_cache;
>
> protected EncryptionManager ()
> {
> key_cache = new System.Collections.ArrayList ();
> }
>
> public static EncryptionManager Instance
> {
> get
> {
> lock (lock_)
> {
> if (instance == null)
> instance = new EncryptionManager ();
> return instance;
> }
> }
> set {
> lock (lock_)
> {
> instance = value;
> }
> }
> }
>
> public string HandleEncryptedNote (NoteData data)
> {
> if (data.EncryptionKey != null)
> {
> try
> {
> return Decrypt (data);
> }
> catch (Exception)
> {
> // We couldn't decrypt the data using the key; it must have
> // changed. We'll prompt the user instead, or try a cached key.
> data.EncryptionKey = null;
> }
> }
>
> // Try decrypting note with the keys in the key cache first.
> foreach (byte[] key in key_cache)
> {
> data.EncryptionKey = key;
> try
> {
> return Decrypt (data);
> }
> catch (Exception)
> {
> }
> }
>
> // We don't have the key cached; ask the user for it.
> data.EncryptionKey = null;
> data.EncryptionKey = GetDecryptionKey (null);
>
> if (data.EncryptionKey != null)
> {
> try
> {
> return Decrypt (data);
> }
> catch (Exception)
> {
> }
> }
>
> // We couldn't decrypt it; throw an exception to our caller.
> throw new DecryptionRequiredException ();
> }
>
> public byte[] Encrypt (string data, byte[] key)
> {
> Rijndael rijndael = Rijndael.Create ();
>
> MemoryStream m = new MemoryStream ();
> m.Write(rijndael.IV, 0, 16);
>
> CryptoStream cs = new CryptoStream (m,
> rijndael.CreateEncryptor (key, rijndael.IV), CryptoStreamMode.Write);
> StreamWriter sw = new StreamWriter (cs);
> sw.Write(data);
> sw.Close();
> cs.Close();
>
> return m.ToArray();
> }
>
> public string Decrypt (NoteData data)
> {
> byte[] enc = data.EncryptedText;
>
> byte[] iv = new byte[16];
> Array.Copy(enc, iv, 16);
>
> MemoryStream m = new MemoryStream (enc, 16, enc.Length - 16);
> CryptoStream cs = new CryptoStream (m,
> Rijndael.Create ().CreateDecryptor (data.EncryptionKey, iv),
> CryptoStreamMode.Read);
>
> StreamReader sr = new StreamReader (cs);
> return sr.ReadToEnd ();
> }
>
> public byte[] GetEncryptionKey (Gtk.Window parent)
> {
> return HashPassword ("test");
> }
>
> public byte[] GetDecryptionKey (Gtk.Window parent)
> {
> return HashPassword ("test");
> }
>
> public byte[] HashPassword (string password)
> {
> return new MD5CryptoServiceProvider ().ComputeHash (
> Encoding.ASCII.GetBytes("*Tomboy*PasswordSalt*" + password));
> }
>
> public void ClearKeyCache ()
> {
> key_cache.Clear ();
> }
> }
> }
>
>
> ------------------------------------------------------------------------
>
> _______________________________________________
> tomboy-list mailing list
> tomboy-list at lists.beatniksoftware.com
> http://lists.beatniksoftware.com/listinfo.cgi/tomboy-list-beatniksoftware.com
More information about the tomboy-list
mailing list