A pleasant walk through computing

Comment for me? Send an email. I might even update the post!

Creating a Long-Lasting ClickOnce Certificate

This post’s for me, since I’ve occasionally had to look this up.

Here are the two command lines of code needed to create a long-lasting, untrusted ClickOnce certificate. You run this using the Visual Studio command prompt. I recommend putting the certificate info in a separate folder, documenting the procedure and passwords, and storing in version control. The purpose isn’t deep security, it’s letting the apps install and run with minimal fuss.

C:\MakeCertDemo> makecert -sv MyClickOnce.pvk -n "CN=MY ClickOnce" MyClickOnce.cer -b 08/01/2015 -e 12/31/2100 -r
C:\MakeCertDemo> pvk2pfx -pvk MyClickOnce.pvk -spc MyClickOnce.cer -pfx MyClickOnce.pfx -po X0PASS!

The –r switch is required to ensure the certificate’s length is greater that 1024 bits, otherwise Windows will refuse the ClickOnce installation. Older articles about using MakeCert omit that.

The development certificate created by Visual Studio when publishing a ClickOnce application lasts just a year. You can get with your network admin to create a domain-level certificate for internal publishing, if you have Windows Enterprise Server. Or buy a Verisign certificate for trusted public publishing. But often, small businesses just need to publish internal apps with little fanfare. The app developers need:

  1. A long-lasting certificate
  2. To not discover they don’t know the original certificate password (“Hey, who wrote this? Know password? OK, I’ll create a new certificate.”)
  3. Consistency

References

Certificate Expiration in ClickOnce Deployment

Stack Overflow

DigitallyCreated

More Encrypted Config Files–ClickOnce Complications

Prologue

In my previous post, I detailed a solution for encrypting sections of the app.config (the technique is similar in a web app). Since then, I ran into some issues in a Windows Form application I was developing.

  • I needed to encrypt email SMTP data.
  • Initially, I’d get an error when trying to access the smtp section.
  • When published as ClickOnce, the application threw an error and wouldn’t open.

Act I

Let’s create a simple app. First, a Windows Form app with three labels. All the code will be in the same page as the Form1 class, to keep things simple.
image

Add connectionString, appSettings and smtp sections to the app.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <connectionStrings>
    <add name="MashDb" 
         connectionString="Server=.\SQLExpress2012;Database=MASH;Trusted Connection=True;" 
         providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>
    <add key="Password" value="4077"/>
  </appSettings>
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="pierce@mash.net">
        <network
          host="ohmyseoul.kr"
          port="25"
          defaultCredentials="true"/>
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

Add reference to System.Configuration to the project.

image

The static Settings class, to get the app.config data. This is personal preference; I know you can also use the Settings designer for this, but it’s kind of hidden and finicky.

public static class Settings
    {
        public static string MashDb { get { return ConfigurationManager.ConnectionStrings["MashDb"].Name; } }
        public static string Password { get { return ConfigurationManager.AppSettings["Password"]; } }
    }

The static ConfigSettings classes. (Or whatever you’d name your general security utilities.) This is different than what I showed previously. Specifically, they don’t optionally take an executable name. Instead, they take the full path to a config file, essential to solving the ClickOnce problem.

public static class ConfigSettings
    {

        /// <summary>
        /// Encrypts/decrypts the connectionStrings, appSettings and smtp sections.
        /// </summary>
        /// <param name="encryption"></param>
        /// <param name="configPath">Full path to config file</param>
        /// <remarks>https://msdn.microsoft.com/en-us/library/ms254494(v=vs.110).aspx</remarks>
        public static void SetDefaultConfigEncryption(bool encryption, string configPath = null)
        {
            SetConfigSectionEncryption(encryption, "appSettings", configPath);
            SetConfigSectionEncryption(encryption, "connectionStrings", configPath);
            SetConfigSectionEncryption(encryption, "system.net/mailSettings/smtp", configPath);
        }

        /// <summary>
        /// Encrypts/decrypts the connectionStrings, appSettings and smtp sections.
        /// </summary>
        /// <param name="encryption"></param>
        /// <param name="sectionName"></param>
        /// <param name="configPath">Full path to config file</param>
        /// <remarks></remarks>
        public static void SetConfigSectionEncryption(bool encryption, string sectionName, string configPath = null)
        {
            //if no configPath supplied, get the currently executing exe's config.
            if (string.IsNullOrWhiteSpace(configPath))
            {
                configPath = Assembly.GetEntryAssembly().Location + ".config";
#if (DEBUG)
                AppDomain domain = System.AppDomain.CurrentDomain;
                configPath = Path.Combine(domain.SetupInformation.ApplicationBase, domain.FriendlyName + ".config");
#endif
            }

            try
            {
                //Open the configuration file and retrieve the connectionStrings section.
                ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = configPath;
                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                ConfigurationSection section = config.GetSection(sectionName);
                if (section == null)
                {
                    return; 
                }
                // Encrypt the section.
                if (encryption)
                {
                    if (!section.SectionInformation.IsProtected)
                    {
                        section.SectionInformation.ForceSave = true;
                        section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
                    }
                }
                // Remove encryption.
                if (!encryption)
                {
                    if (section.SectionInformation.IsProtected)
                    {
                        section.SectionInformation.UnprotectSection();
                    }
                }
                // Save the current configuration.
                config.Save();
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to encrypt. " + ex.GetBaseException().Message);
            }
        }
    }

In the Form1 class, add the form Load event, and also the method for encrypting the config. You might be tempted to use System.AppDomain.CurrentDomain.FriendlyName, but in a ClickOnce application this will return “DefaultDomain,” and no site I’ve seen has an answer why.

public partial class Form1 : Form
    {
        public Form1()
        {
            EncryptClickOnceConfig();
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Text = Settings.MashDb;
            label2.Text = Settings.Password;
            label3.Text = new System.Net.Mail.SmtpClient().Host;
        }

        private void EncryptClickOnceConfig()
        {
            //Encrypt config. This didn't work properly in Load. Would lead to error "Unrecognized attribute ‘configProtectionProvider’
            //after encrypting app.config when reading smtp
            //Weird stuff for ClickOnce Deployment. 
            //https://social.msdn.microsoft.com/Forums/windows/en-US/3d7ba97f-684c-46a2-9f7b-5d17e989baa8/encrypting-connection-string-with-clickonce-deployment?forum=winformssetup
            //ClickOnce writes two config files for some reason.
            {
                if (ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.IsFirstRun)
                {
                    //GetPaths
                    string exeFile = Assembly.GetEntryAssembly().Location;
                    //get directory above the executable's.
                    string parentPath = Path.GetDirectoryName(Path.GetDirectoryName(exeFile));
                    string configName = Path.GetFileName(exeFile) + ".config";
                    //get the matching files
                    foreach (string configFile in Directory.GetDirectories(parentPath, "*" + configName, SearchOption.AllDirectories))
                    {
                        ConfigSettings.SetDefaultConfigEncryption(true, configFile);
                    }
                }
                else
                {
                    //good for debugging, sets the vshost config
                    ConfigSettings.SetDefaultConfigEncryption(true);
                }
            }
        }
    }

Act II

Publish the application. This will automatically create a self-signed certificate. (In a follow up post, I’ll document (for my easy reference) how to create a certificate that lasts more than a year.) There are two important things to do here:

  • Set the project to Release.  This is critical. It should be obvious, but it’s easy to forget and leads to one of the errors.
  • It’s important to install it from a network location, which you can fake by sharing your publish folder and using the UNC path to it. I published to the solution folder. Give the share full permissions (for testing only).

image

You should also enable automatic updating.

image

Install from the UNC path. The app should open.

image

With the app open, open Task Manager, right-click the app, and choose “open file location.”

image

The folder will contain a EncryptedConfigClickOnceSample.exe.config file, which should be encrypted. But you’ll also find another folder, a sibling above or below the one you’re in, also with a EncryptedConfigClickOnceSample.exe.config, which should also be encrypted.

So, fine, it works. How were the problems fixed?

Act III

Encrypt the SMTP Info

This one’s pretty easy. Pass the SetConfigSectionEncryption() method the string “system.net/mailSettings/smtp”. What I found interesting is that you can’t submit just “system.net”, or “system.net/mailSettings” Apparently, in some  Orwellian programming community, some sections are more equal than others.

Error Accessing the SMTP Info

Set the program to Debug mode and republish. Run the app, letting it self-update. You should receive an error

Unrecognized attribute 'configProtectionProvider'

image

Some sites say to call ConfigurationManager.RefreshSection, but the problem really seems to be the behavior when in Debug mode. The error seems to be caused by how configuration info is accessed after the application starts. It’s not clear to me, and no web page mentioned debug mode. But it worked.

ClickOnce Error

In one application, when I tried to install it simply wouldn’t open. I believe this was also due to publishing in Debug mode. However, I also moved my call to configure the config file from Form_Load to the constructor. In my testing app, that’s not making any difference.

Epilogue

Encrypting the app.config could be critical for certain organizations where the highest security is preferred, even if the risk is very low.

References

http://stackoverflow.com/questions/7856951/unrecognized-attribute-configprotectionprovider-after-encrypting-app-config

https://social.msdn.microsoft.com/Forums/windows/en-US/3d7ba97f-684c-46a2-9f7b-5d17e989baa8/encrypting-connection-string-with-clickonce-deployment?forum=winformssetup

Source Code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net.Mail;
using System.Configuration;
using System.IO;
using System.Deployment.Application;
using System.Reflection;

namespace EncryptedConfigClickOnceSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            EncryptClickOnceConfig();
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Text = Settings.MashDb;
            label2.Text = Settings.Password;
            label3.Text = new System.Net.Mail.SmtpClient().Host;
        }

        private void EncryptClickOnceConfig()
        {
            //Encrypt config. This didn't work properly in Load. Would lead to error "Unrecognized attribute ‘configProtectionProvider’
            //after encrypting app.config when reading smtp
            //Weird stuff for ClickOnce Deployment. 
            //https://social.msdn.microsoft.com/Forums/windows/en-US/3d7ba97f-684c-46a2-9f7b-5d17e989baa8/encrypting-connection-string-with-clickonce-deployment?forum=winformssetup
            //ClickOnce writes two config files for some reason.
            {
                if (ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.IsFirstRun)
                {
                    //GetPaths
                    string exeFile = Assembly.GetEntryAssembly().Location;
                    //get directory above the executable's.
                    string parentPath = Path.GetDirectoryName(Path.GetDirectoryName(exeFile));
                    string configName = Path.GetFileName(exeFile) + ".config";
                    //get the matching files
                    foreach (string configFile in Directory.GetFiles(parentPath, "*" + configName, SearchOption.AllDirectories))
                    {
                        ConfigSettings.SetDefaultConfigEncryption(true, configFile);
                    }
                    
                }
                else
                {
#if (DEBUG)
                    //good for debugging, sets the vshost config
                    ConfigSettings.SetDefaultConfigEncryption(true);
#endif
                }
            }
        }
    }

    public static class Settings
    {
        public static string MashDb { get { return ConfigurationManager.ConnectionStrings["MashDb"].Name; } }
        public static string Password { get { return ConfigurationManager.AppSettings["Password"]; } }
    }


    public static class ConfigSettings
    {

        /// <summary>
        /// Encrypts/decrypts the connectionStrings, appSettings and smtp sections.
        /// </summary>
        /// <param name="encryption"></param>
        /// <param name="configPath">Full path to config file</param>
        /// <remarks>https://msdn.microsoft.com/en-us/library/ms254494(v=vs.110).aspx</remarks>
        public static void SetDefaultConfigEncryption(bool encryption, string configPath = null)
        {
            SetConfigSectionEncryption(encryption, "appSettings", configPath);
            SetConfigSectionEncryption(encryption, "connectionStrings", configPath);
            SetConfigSectionEncryption(encryption, "system.net/mailSettings/smtp", configPath);
        }

        /// <summary>
        /// Encrypts/decrypts the connectionStrings, appSettings and smtp sections.
        /// </summary>
        /// <param name="encryption"></param>
        /// <param name="sectionName"></param>
        /// <param name="configPath">Full path to config file</param>
        /// <remarks></remarks>
        public static void SetConfigSectionEncryption(bool encryption, string sectionName, string configPath = null)
        {
            //if no configPath supplied, get the currently executing exe's config.
            if (string.IsNullOrWhiteSpace(configPath))
            {
                configPath = Assembly.GetEntryAssembly().Location + ".config";
#if (DEBUG)
                AppDomain domain = System.AppDomain.CurrentDomain;
                configPath = Path.Combine(domain.SetupInformation.ApplicationBase, domain.FriendlyName + ".config");
#endif
            }

            try
            {
                //Open the configuration file and retrieve the connectionStrings section.
                ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = configPath;
                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                ConfigurationSection section = config.GetSection(sectionName);
                if (section == null)
                {
                    return; 
                }
                // Encrypt the section.
                if (encryption)
                {
                    if (!section.SectionInformation.IsProtected)
                    {
                        section.SectionInformation.ForceSave = true;
                        section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
                    }
                }
                // Remove encryption.
                if (!encryption)
                {
                    if (section.SectionInformation.IsProtected)
                    {
                        section.SectionInformation.UnprotectSection();
                    }
                }
                // Save the current configuration.
                config.Save();
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to encrypt. " + ex.GetBaseException().Message);
            }
        }
    }
}

Encrypting app.config

Developers often use the app.config (or web.config) file to hold database connection strings and application settings. For example:

<connectionStrings>
  <add name="WonderDb" connectionString="Server=BigServe1;Database=Wonder;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
  <add key="FtpUser" value="ariadne" />
  <add key="FtpPassword" value="maze1$" />
</appSettings>

You can find several pages on how to encrypt these settings, but they don’t usually show a typical solution, which, for me, includes:

  1. Encrypting/decrypting both connectionStrings and appSettings in one step.
  2. Doing it from code.
  3. Reduced parameters.

Granted, this is a simple case, where I don’t care about selectively encrypting settings. For that, Jon Galloway has a good post. In fact, we’ll use Jon’s post as our basis.

Important: In your project, you need to add the System.Configuration assembly. You also add using System.Configuration, but that by itself doesn’t include all the methods we need without referencing the assembly.

In my version, I removed the check for whether the section is locked, because I want an error thrown.

if (!section.ElementInformation.IsLocked)
{. . .
}

I also allow either encrypting or decrypting. Finally, I wrap up the default behavior, encrypting both appSettings and connectionStrings, in a single method call. Here’s the complete Console Application code.

using System;
using System.Configuration;

namespace EncryptConfig
{
    class Program
    {
        static void Main(string[] args)
        {
            SetDefaultConfigEncryption(true);
            Console.ReadLine(); //check the file, see if it’s encrypted.
        }

        private static void SetDefaultConfigEncryption(bool encrypt)
        {
            SetConfigSectionEncryption(encrypt,"appSettings");
            SetConfigSectionEncryption(encrypt, "connectionStrings");
        }

        private static void SetConfigSectionEncryption(bool encrypt, string sectionKey)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConfigurationSection section = config.GetSection(sectionKey);
            if (section != null)
            {
                if (encrypt && !section.SectionInformation.IsProtected)
                {
                        section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
                }
                if (!encrypt && section.SectionInformation.IsProtected)
                {
                        section.SectionInformation.UnprotectSection();
                }
                section.SectionInformation.ForceSave = true;
                config.Save(ConfigurationSaveMode.Full);
            }
        }
    }
}

Assuming an executable named EncryptConfig.exe, when running the above in Debug mode, the file that’s encrypted is bin\Debug\EncryptConfig.vshost.exe.Config

That’s nice and makes sense, because that’s the executable that’s actually in use, even though the regular exe has been built, too.The vshost config file is immediately reverted back when debugging ends. Here’s the encrypted version. Whew!

<connectionStrings configProtectionProvider="DataProtectionConfigurationProvider">
    <EncryptedData>
      <CipherData>
        <CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAtX6m55SoIUyvHb37ZhfgAAQAAAACAAAAAAAQZgAAAAEAACAAAAAkgsqGn6mo3noCKO2peA8w799u8ajWVLado9goQ8esSAAAAAAOgAAAAAIAACAAAADqB7GDKmDFY9buAJlgMDyUoos81ALB/3/UKAtk3wT53TADAAAHixr6r15/5eNFgPHnXPJD950sY5F9oAjYRF4Cpfu9sfd532WpEOxFnST1W4LgX+u+d8so2rf5FqN3ikxsJY4Y1p3xpQeJEA1xa4i9L4n5arPabK4mmNn7fKTKjxri1Xktqkhkb1FLV/opfFFRnOlRy64nFOzWr4Ucwwsr8VR7d3TTt6MD2Bg3pJDZiUgjcQ+tXdD8cHkEhfKfxbMdeLfO65uG6VbyAm+ldjp7p9D6c2Z9DyBbq+rEShrMvMTL+lZ52j9H63sD+V93ESJ9FMgjOMX48TG2ZvboQE6EbiLmM0uH50BqziGUat3ft3Frc+0mQHfoqhnVs2M7mCt5mkjE3Tb0pcvjN3XvWVqo9wy5jxB0K/9yRuPTtSdVBV1q4chHGUZ4XsvBjTnt67LP2XdAYcu/Xju6Swv/dkgXMuXPzuAMgizX86CYuLebR0hMNH3Spiz9ODX98wUFCJMzJ4kQupxpdRNafNKISQWZI7119Anjehz4x3yeWJjUNoatTMWxoQ2xeYr+SlyiRoTOI3BrvfPGgpkDG9nQH2S+8dWMJyWRPHBs2GbrPqjjtLA1yR+2rqt9Uocv29oBJ6DwvnT+FA0p9P/Qp91jzEpRmcTEz+EYoIJfwSsBOIZuRXllcNJkaO1Ni/ThvGpTg0aqotXrhY0MoQlI4m/iPly2qkKVKr3O1qMwY9GrYERTrbj5pKb+hXFPgsOAEDgRBiAHsH6FuoZjG0MIXVTGg4zmJ+7rhsa8EPh61ROI6DwW1rRTA9DSKs9LS1sxeHTR/8QWaLxZuRWSwjuZJaVybPyllU1rvOFzDY9Nv96wk1n9u6s21fSf233l4nuu+7np8VfUdYUxNGfn0TwH69bh8cg1wqaJVCEoSioMHFidrKArtSCciJHW27jn3/SsRz4HWNudebTY8WZTf9YsLTuP0jC7dxuX9CIpuhcJBXitpvyVGxwC5NfmKDAsl/Dws4Rt+/YXVJ/4j5JvT35ZxYbTm/cZgvhL/z6j5odkCkIxey3ulqt1o57VNWJ18Ks0lTStRN0esejDVL6ThnqSVtlP2BCB5bThoRGXvyxhuz6Y/ON4SgeK8vBAAAAAX9I6BKGWYUcUHSscMRV2a6VGisOj9FzqTTWn0YAbeQ2hK0ZuYgK2x7hgp6iZcFJnvvuvXWTHzdvDtEliixpHQQ==</CipherValue>
      </CipherData>
    </EncryptedData>
  </connectionStrings>
  <appSettings configProtectionProvider="DataProtectionConfigurationProvider">
    <EncryptedData>
      <CipherData>
        <CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAtX6m55SoIUyvHb37ZhfgAAQAAAACAAAAAAAQZgAAAAEAACAAAAAcT/dxC/l5+9h0pizxo4Thn6rgtJlRVLwGediRA8DpywAAAAAOgAAAAAIAACAAAADHdhTfi7lUXxm6CwB3S252W+lo63eEXsNwxrnPOdfQUCABAACM0LuV5c69abxGbAY6Btx9i/lUhfqjGd8B5MvNCmkwN9hQW90sPzgwBYc7o7EJPM9M2PrgbMXmCo1fUn45ZZdoL68b9andNZe78AJAKz+HNRPBaW3UD2ruwZMKr8F/FVlFxrVnrgytRxDbNNZGfwOR8WrfLsISJdOJHjZcOhLU+hsSVeA2WeSPoH/QR7L+zGqZ15/9VmULHK/0J2KacFKyqn8d4SNIOehAR+t7tlec/qdSsFYtsIWq+c4k4mgWWOVwhCXqzc+3dXRforeEYuBHfsG5KyYsxsaZZdg2dm3wooCJCCv8tK86FuDCBym8L5R1oreRWT8WAq73UM4GJn5in5ZDeAK00lnhjB8DddOy7GjrflIIM63Rc1ClXUrfm3lAAAAA+KiFbW8vRL7rF6Kl9uKDpVp8uzCMs5JcPM9lqHz5XNNQ3m7zEk8TaVoOLC56AKu5rJ+ecX76XYWfQqvpQxDj/g==</CipherValue>
      </CipherData>
    </EncryptedData>
  </appSettings>

There’s nothing new here, I just wanted to document what I think of as a common implementation instead of the generic one. If I’ve made a security error let me know.

References

Many pages reference David Hayden’s seminal post on the subject, but I was unable to locate the original.

https://msdn.microsoft.com/en-us/library/ms254494(v=vs.110).aspx
Jon Galloway’s Post
4 Guys from Rolla