[smuxi: 18/26] Engine(-IRC), Frontend-GNOME: support CertFP (closes: #96)



commit 83a2ab1c3e64ef4438b8e901891270f65566ea95
Author: Mirco Bauer <meebey meebey net>
Date:   Sun Jan 12 08:26:40 2014 +0100

    Engine(-IRC), Frontend-GNOME: support CertFP (closes: #96)
    
    [CertFP][] is a NickServ authentication feature supported by modern IRC networks
    as an more secure alternative to the famous "/msg NickServ IDENTIFY my_password"
    command.
    
     [CertFP]: https://freenode.net/certfp/
    
    As this is an internal setting only (for now) you need to configure it using
    the /config command like this:
    
        /config Servers/IRC/$SERVER_ID/ClientCertificateFilename = mycert.pfx
        /config save
    
    The client certificate can be generated using makecert like this:
    
        makecert -eku 1.3.6.1.5.5.7.3.2 -r -cy end -n "CN=$USER" -p12 mycert.pfx ""
    
    The certificate must not use a passphrase, else it can't be loaded. Thus secure
    the file against access by other users with:
    
        chmod 400 mycert.pfx
    
    Place the certificate in ~/.config/smuxi/certs/ otherwise specify the full path
    in ClientCertificateFilename.
    
    On most IRC networks that support CertFP you can verify if the certificate was
    used using /whois on your own nickname. A line like this should show up in the
    whois reply:
    
        [276 (?) meebey3] has client certificate fingerprint a15aecab43e1d0965a2da43739a9628d790994e0
    
    Special thanks goes to An-Ivoz for finding out how client certificate selection
    works!

 src/Engine-IRC/Protocols/Irc/IrcProtocolManager.cs |   36 ++++++++++++++++++++
 src/Engine/Config/Config.cs                        |    1 +
 src/Engine/Config/ServerModel.cs                   |   13 +++++++
 src/Frontend-GNOME/Views/MenuWidget.cs             |    1 +
 4 files changed, 51 insertions(+), 0 deletions(-)
---
diff --git a/src/Engine-IRC/Protocols/Irc/IrcProtocolManager.cs 
b/src/Engine-IRC/Protocols/Irc/IrcProtocolManager.cs
index 0b2584d..d85da5d 100644
--- a/src/Engine-IRC/Protocols/Irc/IrcProtocolManager.cs
+++ b/src/Engine-IRC/Protocols/Irc/IrcProtocolManager.cs
@@ -21,6 +21,7 @@
  */
 
 using System;
+using System.IO;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Security.Cryptography.X509Certificates;
@@ -2584,6 +2585,41 @@ namespace Smuxi.Engine
             if (server != null) {
                 _IrcClient.UseSsl = server.UseEncryption;
                 _IrcClient.ValidateServerCertificate = server.ValidateServerCertificate;
+                if (String.IsNullOrEmpty(server.ClientCertificateFilename)) {
+                    _IrcClient.SslClientCertificate = null;
+                } else {
+                    var certFile = server.ClientCertificateFilename;
+                    if (!Path.IsPathRooted(certFile)) {
+                        var configPath = Environment.GetFolderPath(
+                            Environment.SpecialFolder.ApplicationData
+                        );
+                        configPath = Path.Combine(configPath, "smuxi");
+                        var certPath = Path.Combine(configPath, "certs");
+                        certFile = Path.Combine(certPath, certFile);
+                    }
+                    var certType = X509Certificate2.GetCertContentType(certFile);
+                    if (certType != X509ContentType.Unknown) {
+                        var cert = new X509Certificate2();
+                        cert.Import(certFile, "", X509KeyStorageFlags.PersistKeySet);
+                        if (cert.PublicKey == null) {
+#if LOG4NET
+                            _Logger.ErrorFormat(
+                                "ApplyConfig(): client certificate {0} does " +
+                                "not contain a public key!", certFile
+                            );
+#endif
+                        }
+                        if (cert.PrivateKey == null) {
+#if LOG4NET
+                            _Logger.ErrorFormat(
+                                "ApplyConfig(): client certificate {0} does " +
+                                "not contain a private key!", certFile
+                            );
+#endif
+                        }
+                        _IrcClient.SslClientCertificate = cert;
+                    }
+                }
             }
         }
 
diff --git a/src/Engine/Config/Config.cs b/src/Engine/Config/Config.cs
index 0441882..6509d30 100644
--- a/src/Engine/Config/Config.cs
+++ b/src/Engine/Config/Config.cs
@@ -617,6 +617,7 @@ namespace Smuxi.Engine
                     LoadEntry(sprefix+"Password", String.Empty);
                     LoadEntry(sprefix+"UseEncryption", false);
                     LoadEntry(sprefix+"ValidateServerCertificate", false);
+                    LoadEntry(sprefix+"ClientCertificateFilename", String.Empty);
                     LoadEntry(sprefix+"OnStartupConnect", false);
                     string[] commands = GetList(sprefix + "OnConnectCommands");
                     if (commands == null) {
diff --git a/src/Engine/Config/ServerModel.cs b/src/Engine/Config/ServerModel.cs
index 1cde243..1c753c1 100644
--- a/src/Engine/Config/ServerModel.cs
+++ b/src/Engine/Config/ServerModel.cs
@@ -32,6 +32,7 @@ namespace Smuxi.Engine
     {
         public bool UseEncryption { get; set; }
         public bool ValidateServerCertificate { get; set; }
+        public string ClientCertificateFilename { get; set; }
         public string Protocol { get; set; }
         public string Hostname { get; set; }
         public int Port { get; set; }
@@ -100,6 +101,9 @@ namespace Smuxi.Engine
                         ValidateServerCertificate = (bool)e.Value;
                         foundValidation = true;
                         break;
+                    case "ClientCertificateFilename":
+                        ClientCertificateFilename = (string) e.Value;
+                        break;
                 }
             }
             if (foundServerID == false) {
@@ -132,6 +136,12 @@ namespace Smuxi.Engine
             if (Realname != null) {
                 info.AddValue("_Realname", Realname);
             }
+            // HACK: skip ClientCertificateFilename if it has no value as it
+            // breaks older ServerModel implementations that relied on automatic
+            // serialization which was the case in < 0.8.11
+            if (String.IsNullOrEmpty(ClientCertificateFilename)) {
+                info.AddValue("ClientCertificateFilename", ClientCertificateFilename);
+            }
             info.AddValue("_Protocol", Protocol);
             info.AddValue("_Hostname", Hostname);
             info.AddValue("_Port", Port);
@@ -175,6 +185,7 @@ namespace Smuxi.Engine
             UseEncryption = (bool) config[ConfigKeyPrefix + "UseEncryption"];
             ValidateServerCertificate =
                 (bool) config[ConfigKeyPrefix + "ValidateServerCertificate"];
+            ClientCertificateFilename = (string) config[ConfigKeyPrefix + "ClientCertificateFilename"];
             if (config[ConfigKeyPrefix + "OnStartupConnect"] != null) {
                 OnStartupConnect = (bool) config[ConfigKeyPrefix + "OnStartupConnect"];
             }
@@ -196,6 +207,8 @@ namespace Smuxi.Engine
             config[ConfigKeyPrefix + "UseEncryption"] = UseEncryption;
             config[ConfigKeyPrefix + "ValidateServerCertificate"] =
                 ValidateServerCertificate;
+            config[ConfigKeyPrefix + "ClientCertificateFilename"] =
+                ClientCertificateFilename;
             config[ConfigKeyPrefix + "OnStartupConnect"] = OnStartupConnect;
             config[ConfigKeyPrefix + "OnConnectCommands"] = OnConnectCommands;
         }
diff --git a/src/Frontend-GNOME/Views/MenuWidget.cs b/src/Frontend-GNOME/Views/MenuWidget.cs
index 829ebab..0a2af64 100644
--- a/src/Frontend-GNOME/Views/MenuWidget.cs
+++ b/src/Frontend-GNOME/Views/MenuWidget.cs
@@ -227,6 +227,7 @@ namespace Smuxi.Frontend.Gnome
                             server.ServerID = null;
                             server.Nickname = null;
                             server.Realname = null;
+                            server.ClientCertificateFilename = null;
                         }
                         Frontend.Session.Connect(server, Frontend.FrontendManager);
                     } catch (Exception ex) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]