GPG in Outlook 2007 – OutlookGnuPG

A long time ago, I used PGP for signing my email, but I stopped using it. Probably because I didn’t quite get the concept yet, but that has changed now.

Today I switched to GPG for signing my mail and files, with all the required files running from my memory stick.

One thing bothered me however, no Outlook 2007 support at all. There are some plugins around for older Outlook versions, but that didn’t quite work out.

So I decided to develop my own! And after a few weeks of development and testing, it’s done and ready to be set free upon the world.

Let’s have a look at the functionality and how to set it up and use it.

Prerequisites

First of all, a working copy of GPG is required, so visit the GnuPG download page, scroll down and download the command line Windows version.

Install this somewhere on your machine and remember the path where you installed it.

If everything has gone correctly, you can now open a command prompt and type gpg –version and gpg –list-keys to see the version and your keys (which would be empty if you’re using it for the first time).

Visit the Getting Started section of the GnuPG handbook to generate your own key if this is your first encounter.

Command Line Interface

OutlookGnuPG

There are two main areas in the plugin, functionality for sending a mail (sign/encrypt) and for retrieving a mail (verify/decrypt).

At the moment only plain text emails are supported. No HTML mail or attachments, that’s for a future version :)

To install the addin, start by adding www.cumps.be to your Trusted Sites (you can do this in Internet Explorer – Tools – Internet Options – Security – Trusted Sites – Sites).

Trusted Sites

Download the OutlookGnuPG ClickOnce installer and execute it.

This will give you a prompt asking you if everything is fine. Go ahead and install it. You might have to close Outlook before installing.

ClickOnce Installer

When the addin is installed, you can open Outlook and a Settings dialog will show up. Click Browse… and select the directory where your the gpg.exe you previously installed is located.

On the second tab you can select the default key you want to use to sign your mails. You will still have a choice to change your key upon sending the actual mail.

GnuPg Location

Default Key

Click Ok when done. At this point you can use your Outlook as before, since we haven’t checked any auto-sign/encrypt functionality.

Sending Mail

When you compose a new mail, you will notice the Message ribbon has a new group on it, called OutlookGnuPG, with a Sign and Encrypt toggle button.

Compose Ribbon

Sending out a signed mail is as simply as turning on the Sign button, typing your mail and pressing Send. It will prompt you to select the private key you want to use to sign the mail, and your passphrase.

Passphrase Window

Creating an encrypted mail follows the same logic, toggle the Encrypt button and send your mail. You will have to select the intended recipients (multiple are possible) and it will encrypt the mail so only these people will be able to decrypt it.

Recipient window

To be absolutely safe, you can toggle both Sign and Encrypt button to send out an encrypted signed message.

Retrieving Mail

Reading mail can be done in two ways in Outlook, either by opening the mail item, or by using the preview pane. It only makes sense there are two ways to verify/decrypt a mail as well then.

The first is very identical to the send functionality. When you open an existing mail, you will notice a new ribbon group, OutlookGnuPG, with a Verify and Decrypt button.

Read Ribbon

Simply click Verify to check if a signed mail is valid or not. A messagebox will inform you of the status.

Valid Signature

Likewise, click Decrypt to decrypt an encrypted email. This will ask you for your private key to decrypt the message with. The message will be decrypted, and the decrypted content will be placed in the message. If the message was also signed, a messagebox will inform you of the status.

The second way is through the preview window. A new commandbar will have appeared on the preview window, with a Verify and Decrypt button, which work exactly the same as the previous buttons.

CommandBar

Credits

OutlookGnuPG is free, only supported by a donate button, so it’s only fair to give the used resources some credits.

Silk Icon Set by Mark James
OpenPGP wrapper by Starksoft
Clipboard Wrapper by Alessio Deiana

AboutBox

Feedback

Got questions? Remarks? Feel free to leave a comment :)

Update:
Since I won’t have the time to maintain this, I’m releasing the source: cc.outlookgnupg-1.0.8.0.zip

Update 2:
Philippe Teuwen and Thierry Walrant have taken it upon themselves to improve the code I posted above and made it available on github, using GPLv3 as a license. Check it out at: github.com/twalrant/OutlookGnuPG

Modified MVC AccountController for Preview 5

I just downloaded the ASP.NET MVC Preview 5 bits from Codeplex and started on my first experiment.

One of the first things I did was to modify the default AccountController to use the new Form Posting and Form Validation features of the Preview 5, somebody probably overlooked updating those :)

If anyone else wants the reworked code, feel free to copy paste.

Note this was something done during lunch break in a hurry, it seems to all work logically, but it's possible I'll have to tune it a bit later on.

Controller:

C#:
  1. [HandleError]
  2. [OutputCache(Location = OutputCacheLocation.None)]
  3. public class AccountController : Controller
  4. {
  5.     public AccountController()
  6.         : this(null, null)
  7.     {
  8.     }
  9.  
  10.     public AccountController(IFormsAuthentication formsAuth, MembershipProvider provider)
  11.     {
  12.         FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
  13.         Provider = provider ?? Membership.Provider;
  14.     }
  15.  
  16.     public IFormsAuthentication FormsAuth
  17.     {
  18.         get;
  19.         private set;
  20.     }
  21.  
  22.     public MembershipProvider Provider
  23.     {
  24.         get;
  25.         private set;
  26.     }
  27.  
  28.     [Authorize]
  29.     [AcceptVerbs("GET")]
  30.     public ActionResult ChangePassword()
  31.     {
  32.         ViewData["Title"] = "Change Password";
  33.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  34.  
  35.         return View();
  36.     }
  37.  
  38.     [Authorize]
  39.     [AcceptVerbs("POST")]
  40.     public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
  41.     {
  42.         // Basic parameter validation
  43.         if (String.IsNullOrEmpty(currentPassword))
  44.         {
  45.             ViewData.ModelState.AddModelError("currentPassword", currentPassword, "You must specify a current password.");
  46.         }
  47.         if (newPassword == null || newPassword.Length <Provider.MinRequiredPasswordLength)
  48.         {
  49.             ViewData.ModelState.AddModelError("newPassword", newPassword, String.Format(CultureInfo.InvariantCulture,
  50.                      "You must specify a new password of {0} or more characters.",
  51.                      Provider.MinRequiredPasswordLength));
  52.         }
  53.         if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
  54.         {
  55.             ViewData.ModelState.AddModelError("newPassword", newPassword, "The new password and confirmation password do not match.");
  56.         }
  57.  
  58.         if (ViewData.ModelState.IsValid)
  59.         {
  60.             // Attempt to change password
  61.             MembershipUser currentUser = Provider.GetUser(User.Identity.Name, true /* userIsOnline */);
  62.             bool changeSuccessful = false;
  63.             try
  64.             {
  65.                 changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword);
  66.             }
  67.             catch
  68.             {
  69.                 // An exception is thrown if the new password does not meet the provider's requirements
  70.             }
  71.  
  72.             if (changeSuccessful)
  73.             {
  74.                 return RedirectToAction("ChangePasswordSuccess");
  75.             }
  76.             else
  77.             {
  78.                 ViewData.ModelState.AddModelError("password", currentPassword, "The current password is incorrect or the new password is invalid.");
  79.             }
  80.         }
  81.  
  82.         // If we got this far, something failed, redisplay form
  83.         ViewData["Title"] = "Change Password";
  84.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  85.  
  86.         return View();
  87.     }
  88.  
  89.     public ActionResult ChangePasswordSuccess()
  90.     {
  91.         ViewData["Title"] = "Change Password";
  92.  
  93.         return View();
  94.     }
  95.  
  96.     [AcceptVerbs("GET")]
  97.     public ActionResult Login()
  98.     {
  99.         ViewData["Title"] = "Login";
  100.         ViewData["CurrentPage"] = "login";
  101.  
  102.         return View();
  103.     }
  104.  
  105.     [AcceptVerbs("POST")]
  106.     public ActionResult Login(string username, string password, bool? rememberMe)
  107.     {
  108.         // Basic parameter validation
  109.         if (String.IsNullOrEmpty(username))
  110.         {
  111.             ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
  112.         }
  113.  
  114.         if (ViewData.ModelState.IsValid)
  115.         {
  116.             // Attempt to login
  117.             bool loginSuccessful = Provider.ValidateUser(username, password);
  118.  
  119.             if (loginSuccessful)
  120.             {
  121.                 FormsAuth.SetAuthCookie(username, rememberMe ?? false);
  122.                 return RedirectToAction("Index", "Home");
  123.             }
  124.             else
  125.             {
  126.                 ViewData.ModelState.AddModelError("*", username, "The username or password provided is incorrect.");
  127.             }
  128.         }
  129.  
  130.         // If we got this far, something failed, redisplay form
  131.         ViewData["Title"] = "Login";
  132.         ViewData["CurrentPage"] = "login";
  133.         ViewData["username"] = username;
  134.  
  135.         return View();
  136.     }
  137.  
  138.     public ActionResult Logout()
  139.     {
  140.         FormsAuth.SignOut();
  141.         return RedirectToAction("Index", "Home");
  142.     }
  143.  
  144.     protected override void OnActionExecuting(ActionExecutingContext filterContext)
  145.     {
  146.         if (filterContext.HttpContext.User.Identity is WindowsIdentity)
  147.         {
  148.             throw new InvalidOperationException("Windows authentication is not supported.");
  149.         }
  150.     }
  151.  
  152.     [AcceptVerbs("GET")]
  153.     public ActionResult Register()
  154.     {
  155.         ViewData["Title"] = "Register";
  156.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  157.  
  158.         return View();
  159.     }
  160.  
  161.     [AcceptVerbs("POST")]
  162.     public ActionResult Register(string username, string email, string password, string confirmPassword)
  163.     {
  164.         // Basic parameter validation
  165.         if (String.IsNullOrEmpty(username))
  166.         {
  167.             ViewData.ModelState.AddModelError("username", username, "You must specify a username.");
  168.         }
  169.  
  170.         if (String.IsNullOrEmpty(email))
  171.         {
  172.             ViewData.ModelState.AddModelError("email", email, "You must specify an email address.");
  173.         }
  174.  
  175.         if (password == null || password.Length <Provider.MinRequiredPasswordLength)
  176.         {
  177.             ViewData.ModelState.AddModelError("password", password, String.Format(CultureInfo.InvariantCulture,
  178.                      "You must specify a password of {0} or more characters.",
  179.                      Provider.MinRequiredPasswordLength));
  180.         }
  181.  
  182.         if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
  183.         {
  184.             ViewData.ModelState.AddModelError("confirmPassword", confirmPassword, "The password and confirmation do not match.");
  185.         }
  186.  
  187.         if (ViewData.ModelState.IsValid)
  188.         {
  189.  
  190.             // Attempt to register the user
  191.             MembershipCreateStatus createStatus;
  192.             MembershipUser newUser = Provider.CreateUser(username, password, email, null, null, true, null, out createStatus);
  193.  
  194.             if (newUser != null)
  195.             {
  196.                 FormsAuth.SetAuthCookie(username, false /* createPersistentCookie */);
  197.                 return RedirectToAction("Index", "Home");
  198.             }
  199.             else
  200.             {
  201.                 ViewData.ModelState.AddModelError("*", username, ErrorCodeToString(createStatus));
  202.             }
  203.         }
  204.  
  205.         // If we got this far, something failed, redisplay form
  206.         ViewData["Title"] = "Register";
  207.         ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
  208.         ViewData["username"] = username;
  209.         ViewData["email"] = email;
  210.  
  211.         return View();
  212.     }
  213.  
  214.     public static string ErrorCodeToString(MembershipCreateStatus createStatus)
  215.     {
  216.         // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
  217.         // a full list of status codes.
  218.         switch (createStatus)
  219.         {
  220.             case MembershipCreateStatus.DuplicateUserName:
  221.                 return "Username already exists. Please enter a different user name.";
  222.  
  223.             case MembershipCreateStatus.DuplicateEmail:
  224.                 return "A username for that e-mail address already exists. Please enter a different e-mail address.";
  225.  
  226.             case MembershipCreateStatus.InvalidPassword:
  227.                 return "The password provided is invalid. Please enter a valid password value.";
  228.  
  229.             case MembershipCreateStatus.InvalidEmail:
  230.                 return "The e-mail address provided is invalid. Please check the value and try again.";
  231.  
  232.             case MembershipCreateStatus.InvalidAnswer:
  233.                 return "The password retrieval answer provided is invalid. Please check the value and try again.";
  234.  
  235.             case MembershipCreateStatus.InvalidQuestion:
  236.                 return "The password retrieval question provided is invalid. Please check the value and try again.";
  237.  
  238.             case MembershipCreateStatus.InvalidUserName:
  239.                 return "The user name provided is invalid. Please check the value and try again.";
  240.  
  241.             case MembershipCreateStatus.ProviderError:
  242.                 return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  243.  
  244.             case MembershipCreateStatus.UserRejected:
  245.                 return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  246.  
  247.             default:
  248.                 return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
  249.         }
  250.     }
  251. }
  252.  
  253. // The FormsAuthentication type is sealed and contains static members, so it is difficult to
  254. // unit test code that calls its members. The interface and helper class below demonstrate
  255. // how to create an abstract wrapper around such a type in order to make the AccountController
  256. // code unit testable.
  257.  
  258. public interface IFormsAuthentication
  259. {
  260.     void SetAuthCookie(string userName, bool createPersistentCookie);
  261.     void SignOut();
  262. }
  263.  
  264. public class FormsAuthenticationWrapper : IFormsAuthentication
  265. {
  266.     public void SetAuthCookie(string userName, bool createPersistentCookie)
  267.     {
  268.         FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
  269.     }
  270.     public void SignOut()
  271.     {
  272.         FormsAuthentication.SignOut();
  273.     }
  274. }

Login View:

ASP:
  1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="GuildSite.Views.Account.Login" %>
  2.  
  3. <asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
  4.     <h2>Login</h2>
  5.  
  6.     <p>
  7.         Please enter your username and password below. If you don't have an account,
  8.         please <%= Html.ActionLink("register", "Register") %>.
  9.     </p>
  10.  
  11.     <%= Html.ValidationSummary()%>
  12.  
  13.     <form method="post" action="<%= Html.AttributeEncode(Url.Action("Login")) %>">
  14.         <div class="form">
  15.             <table>
  16.                 <tr>
  17.                     <td>Username:</td>
  18.                     <td><%= Html.TextBox("username") %></td>
  19.                 </tr>
  20.                 <tr>
  21.                     <td>Password:</td>
  22.                     <td><%= Html.Password("password") %></td>
  23.                 </tr>
  24.                 <tr>
  25.                     <td></td>
  26.                     <td><input type="checkbox" name="rememberMe" value="true" /> Remember me?</td>
  27.                 </tr>
  28.                 <tr>
  29.                     <td></td>
  30.                     <td><input type="submit" value="Login" /></td>
  31.                 </tr>
  32.             </table>
  33.         </div>
  34.     </form>
  35. </asp:Content>

Register View:

ASP:
  1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="GuildSite.Views.Account.Register" %>
  2.  
  3. <asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" runat="server">
  4.     <h2>Account Creation</h2>
  5.  
  6.     <p>
  7.         Use the form below to create a new account.
  8.     </p>
  9.     <p>
  10.         Passwords are required to be a minimum of <%=Html.Encode(ViewData["PasswordLength"])%> characters in length.
  11.     </p>
  12.  
  13.     <%= Html.ValidationSummary()%>
  14.  
  15.     <form method="post" action="<%= Html.AttributeEncode(Url.Action("Register")) %>">
  16.         <div class="form">
  17.             <table>
  18.                 <tr>
  19.                     <td>Username:</td>
  20.                     <td><%= Html.TextBox("username") %></td>
  21.                 </tr>
  22.                 <tr>
  23.                     <td>Email:</td>
  24.                     <td><%= Html.TextBox("email") %></td>
  25.                 </tr>
  26.                 <tr>
  27.                     <td>Password:</td>
  28.                     <td><%= Html.Password("password") %></td>
  29.                 </tr>
  30.                 <tr>
  31.                     <td>Confirm password:</td>
  32.                     <td><%= Html.Password("confirmPassword") %></td>
  33.                 </tr>
  34.                 <tr>
  35.                     <td></td>
  36.                     <td><input type="submit" value="Register" /></td>
  37.                 </tr>
  38.             </table>
  39.         </div>
  40.     </form>
  41. </asp:Content>

C# World Of Warcraft Armory Library 0.1

Since I started playing World of Warcraft again, I've taken a bit more of a developer approach to it this time, and after founding a little casual guild, I decided to create a site for it.

However, I'm a lazy developer, I don't intend to update the site regularly whenever someone joins or leaves the guild.

Also because I'm quite geeky when it comes to statistics, and a bit of a theory crafter, I planned to populate our guild site with lots of stats.

Where else would be a better place to get them from then the Armory? It contains everything I want!

After searching a little, I found various libraries for PHP, Perl and Ruby, but nothing for the .NET world. At least nothing that fetches everything I wanted, like Reputation and Skills.

So, I decided to just write it myself! :)

Over the last week, I've been developing ArmoryLib, and decided to release it under the LGPL and use Google Code to store the source in.

You can find version 0.1 at http://code.google.com/p/armorylib/ under Downloads.

Have a look at the Documentation to see more details on the API and some example output.

Please, feel free to beta test it and leave your comments! Let me know when you use it for your projects, and remember... LGPL requires you give prominent notice about using the library! (A link to this post will do)

I will make a future post showing how I've used it to integrate into our guild website.

Design Patterns – Proxy Pattern

Welcome back for another episode in the pattern series! This will also be the last article about Design Patterns, since I've finished reading the Head First Design Patterns book :)

It's been a very interesting journey, lots of new patterns learned, lots of knowledge gained, and now it's time to apply them in real projects.

As a summary, the overview of all articles about patterns, including the one we're going to see today:

Let's get started! Make sure you're seated comfortable, it's going to be a long one today!

The definiton, as usual: "Provide a surrogate or placeholder for another object to control access to it."

A new request popped up, we need to add in a multiplayer option in our game, featuring a Lobby where users can get in touch with each other.

This lobby is going to be running on another machine, or in our case, just another console application to illustrate it.

First of all, we're going to start by adding an interface ILobby to define our lobby.

ILobby Interface

We'll place this interface in a seperate library, to better illustrate the Proxy Pattern later on. This way you can clearly see on which machine a specific piece of code is located.

Time to create our actual Lobby implementation!

As mentioned before, we will create this in a seperate project to clearly show the code for the Lobby is not located on the same machine as our main client.

Lobby Implementation

A very simple Lobby implementation, containing nothing more than a List<string> of Users.

At this stage, we can have our Lobby on one machine, but how do we add users to it from another machine?

This is where the Proxy Pattern comes looking!

Just to make one thing clear, the Proxy pattern comes in many different shapes, we're using it to give a client access to a remote object, by means of a placeholder, reminds you of the definition, doesn't it?

In our case, it's called a 'remote proxy', there is also a 'virtual proxy', a 'protection proxy' and more.

A virtual proxy can for example serve as a placeholder for an object which is expensive to create, if you want to retrieve an image over the internet, you could display an icon via a proxy while the real image is loading in the background.

So, let's create this placeholder on the client side.

LobbyProxy Implementation

By placing a reference to our previously created ILobby, this proxy allows our client to work with, what it believes to be, a real lobby object.

In reality however, their is no Lobby implementation in our client code at all, it is merely a placeholder which implements the correct interface.

You might have noticed the notion of a Socket already :)

Our proxy object might implement the correct ILobby interface, if we want it working, we will eventually need to call our real Lobby object.

In a first step this is done using sockets to connect to the server and communicate with the real object.

Before you go screaming .NET Remoting, hold your horses! This is meant to illustrate what is going on behind the scenes with a remote proxy.

When you're coming from the Java world, you might have heard people mentioning a Skeleton.

This is not something creepy, but simply a class on the server side which intercepts call from the Proxy, talks to the real object and sends the results back.

Here's a small part of our Skeleton code:

Proxy and Skeleton Code

As you can see, the Proxy talks to the Skeleton, which talks to the Real Object, after which it sends the response back over the wire.

When we put all of this in action, we see the following happening:

Testing Proxy Pattern

Our main client talked to a remote Lobby and registered some users, great!

Now that we've seen how a proxy serves as a placeholder, it's time to clean this code and get rid of all the socket stuff.

Just a note, all the socket stuff in the project is highly unstable and not meant for production! Don't use it!! It's only meant for demo purposes :)

Let's work on the server side first by removing the Skeleton code, referencing System.Runtime.Remoting, and letting our Lobby class inherit MarshalByRefObject.

Lobby Remoting Implementation

Last step needed for our server is to expose the Lobby object through Remoting, which is nothing more than having something like our previous Skeleton code hidden behind the scenes.

Exposing Lobby Remoting

The only thing left to do now is to remove the Proxy class from our main project, and use Remoting to get an instance of ILobby, which acts as a proxy behind the scenes.

Utilizing Remoting Proxy

Resulting output when we run this version? Exactly the same! But a lot less work to implement :)

And that's it, another pattern in our heads!

I've uploaded the solution again to have a look at. When you run it, make sure your run the GameServer first, unblock it on your Windows Firewall, and then run the Proxy project.

Well, that's it, the last part of my series. I hope you liked them and learned a lot from it. Be sure to keep on visiting for some other tech subjects coming up soon.

You can always subscribe to the RSS feed to stay informed.

Thanks to the people who were generous enough to donate a little bit after reading some of these articles! (If you'd like to donate, simply use the PayPal button on the left :) )

See you soon!

Some additional information on the Proxy Pattern: