wGrow - Team Notes

Sharing Expertise: Tech Insights and Case Studies

Creating a Windows Service to Automate Let's Encrypt SSL Certificate Management for IIS Websites

Let's Encrypt is a free, automated, and open Certificate Authority (CA) that provides SSL/TLS certificates for websites. The ACME (Automatic Certificate Management Environment) protocol is used to automate the process of obtaining and renewing SSL certificates. In this article, we will create a Windows Service that automates the process of obtaining, installing, and renewing Let's Encrypt SSL certificates for IIS websites using the ACME challenge.

Prerequisites

  1. Familiarity with C# and the .NET Framework
  2. Visual Studio installed on your system
  3. Administrative access to the target Windows machine running IIS
  4. A registered domain name with the ability to create and manage subdomains (e.g., SSLTest.wGrow.com)

Step 1: Install the required NuGet packages

  1. Launch Visual Studio and create a new project by selecting the "Windows Service (.NET Framework)" template.
  2. Name the project "IISLEService" and click "Create".
  3. Right-click on the project and select "Manage NuGet Packages".
  4. Install the following packages:
    • Certes
    • Microsoft.Web.Administration

Step 2: Implement the IISLEService

Open the "Service1.cs" file and rename the class to "IISLEService". Implement the required logic as follows:

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using System.Threading.Tasks;
using Certes;
using Certes.Acme;
using Certes.Acme.Resource;
using Microsoft.Web.Administration;

public partial class IISLEService : ServiceBase
{
    private const string Domain = "SSLTest.wGrow.com";
    private const string Email = "[email protected]";
    private const string SiteName = "Default Web Site"; // Change this to your IIS site name
    private const int RenewalPeriodDays = 30;

    private Timer _renewalTimer;

    public IISLEService()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        // Schedule the first renewal check
        _renewalTimer = new Timer(CheckCertificateRenewal, null, TimeSpan.Zero, TimeSpan.FromDays(RenewalPeriodDays));
    }

    protected override void OnStop()
    {
        _renewalTimer.Dispose();
    }

    private async void CheckCertificateRenewal(object state)
    {
        var cert = GetExistingCertificate();
        if (cert == null || DateTime.UtcNow >= cert.NotAfter.AddDays(-RenewalPeriodDays))
        {
            await ObtainAndInstallCertificate();
        }
    }

    private async Task ObtainAndInstallCertificate()
    {
        var acme = new AcmeContext(WellKnownServers.LetsEncryptV2);
        var account = await acme.NewAccount(Email, true);

        var order = await acme.NewOrder(new[] { Domain });
        var authz = (await order.Authorizations()).First();
        var httpChallenge = await authz.Http();

        var keyAuthz = acme.AccountKey.ComputeKeyAuthorization(httpChallenge);
        SaveChallengeFile(httpChallenge.Token, keyAuthz);

        var challenge = await httpChallenge.Validate();
        while (challenge.Status == ChallengeStatus.Pending)
        {
            await Task.Delay(2000);
            challenge = await httpChallenge.Resource();
        }

        if (challenge.Status != ChallengeStatus.Valid)
        {
            throw new InvalidOperationException("ACME challenge validation failed");
        }

        var certChain = await order.Generate(new CsrInfo
        {
            CommonName = Domain,
        });

        InstallCertificate(certChain.Certificate.ToPem(), certChain.IssuerCertificate.ToPem());
        DeleteChallengeFile(httpChallenge.Token);
    }

    private X509Certificate2 GetExistingCertificate()
    {
        using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
        {
            store.Open(OpenFlags.ReadOnly);
            return store.Certificates.Find(X509FindType.FindBySubjectName, Domain, false).OfType().FirstOrDefault();
        }
    }

    private void SaveChallengeFile(string token, string keyAuthorization)
    {
        // Replace this path with the appropriate path for your IIS configuration
        var challengeFilePath = Path.Combine("C:\\inetpub\\wwwroot\\.well-known\\acme-challenge", token);
        File.WriteAllText(challengeFilePath, keyAuthorization);
    }

    private void DeleteChallengeFile(string token)
    {
        // Replace this path with the appropriate path for your IIS configuration
        var challengeFilePath = Path.Combine("C:\\inetpub\\wwwroot\\.well-known\\acme-challenge", token);
        File.Delete(challengeFilePath);
    }

    private void InstallCertificate(string certificatePem, string issuerCertificatePem)
    {
        var certificate = new X509Certificate2(Convert.FromBase64String(Certes.Pkcs12Converter.Convert(certificatePem, issuerCertificatePem, "")));

        using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
        {
            store.Open(OpenFlags.ReadWrite);
            store.Add(certificate);
        }

        using (var serverManager = new ServerManager())
        {
            var site = serverManager.Sites[SiteName];
            var binding = site.Bindings.FirstOrDefault(b => b.Protocol == "https");

            if (binding != null)
            {
                binding.CertificateHash = certificate.GetCertHash();
                binding.CertificateStoreName = StoreName.My.ToString();
            }
            else
            {
                site.Bindings.Add($"*:{443}:{Domain}", certificate.GetCertHash(), StoreName.My.ToString(), "https");
            }

            serverManager.CommitChanges();
        }
    }
}

Step 3: Install and test the service

  1. Build the IISLEService project.
  2. Open a command prompt with administrative privileges and navigate to the directory containing the compiled IISLEService.exe.
  3. Install the service using the following command: `sc create IISLEService binPath= "C:\path\to\IISLEService.exe"`
  4. Start the service using the command: `sc start IISLEService`

The IISLEService will now automatically obtain, install, and renew the SSL certificate for SSLTest.wGrow.com.

-----

Monitoring, Troubleshooting, and Maintenance

To ensure the IISLEService operates effectively, it is crucial to monitor its performance and address any potential issues. This section will discuss best practices for monitoring, troubleshooting, and maintaining the service.

1. Logging and Monitoring

Implement logging to track the IISLEService's activity and performance. Utilize the built-in Windows Event Log to log relevant information, such as when a certificate is obtained, installed, or renewed, as well as any errors or warnings that may occur.

To add logging to the IISLEService, follow these steps:

- Add a new EventLog component to the IISLEService class and name it "eventLog".
- Set the "Log" property of the eventLog component to "Application".
- Set the "Source" property of the eventLog component to "IISLEService".

Now, you can use the `eventLog.WriteEntry()` method to log messages throughout the IISLEService. For example:


private void Log(string message, EventLogEntryType entryType = EventLogEntryType.Information)
{
    eventLog.WriteEntry(message, entryType);
}

 

Call the `Log()` method in the appropriate places in your code to log relevant information.

2. Troubleshooting

If the IISLEService encounters issues, such as failing to obtain, install, or renew certificates, consult the logs to identify the root cause. Common issues may include:

- Incorrect domain or site configuration: Ensure that the domain name and site name in the IISLEService code match your IIS setup.
- Insufficient permissions: Verify that the IISLEService runs under an account with adequate permissions to access the IIS configuration, the certificate store, and the ACME challenge directory.
- Network issues: Check for connectivity issues between the IIS server and the Let's Encrypt infrastructure, as well as any firewalls or proxy settings that could block access.

3. Maintenance and Updates

Keep the IISLEService up-to-date with the latest security standards and best practices:

- Regularly update the .NET Framework and any third-party NuGet packages to the latest versions.
- Review and update the ACME protocol implementation to comply with any changes introduced by Let's Encrypt or the ACME standard.
- Periodically test the IISLEService's functionality to ensure it continues to operate correctly, especially after making changes to your IIS configuration or server environment.

By following these guidelines, you can effectively monitor, troubleshoot, and maintain the IISLEService, ensuring the secure and efficient management of SSL certificates for your IIS websites.

 


 

 

Related

Designing a Secure and High-Performance EC Sales and Report System for Property Developers

Designing a Secure and High-Performance EC Sales and Report System for Property Developers

In light of policy changes, property developers are now expected to create their own Executive Condo...

Read More >
Case Study: Building a Decentralized Voting System with Ethereum Smart Contracts and .NET C#

Case Study: Building a Decentralized Voting System with Ethereum Smart Contracts and .NET C#

In this case study, we will explore how our team developed a decentralized voting system using Ether...

Read More >
Creating a Desktop Application to Backup Gmail Emails and Restore to Gmail or Yahoo Email using C#

Creating a Desktop Application to Backup Gmail Emails and Restore to Gmail or Yahoo Email using C#

Backing up important emails from your Gmail account is essential to ensure data security and availab...

Read More >
SQL Server Hardening Checklist & Best Practices

SQL Server Hardening Checklist & Best Practices

It's essential to ensure that SQL Server is secure and protected against potential cyber threats...

Read More >
Integrating Python's Rembg Library with C# for Background Removal in .NET Applications

Integrating Python's Rembg Library with C# for Background Removal in .NET Applications

In this article, we will explore how to integrate Python's Rembg library with C# to add backgrou...

Read More >
Case Study: Setting up a Network Firewall for an Internet Application Server

Case Study: Setting up a Network Firewall for an Internet Application Server

In today's world, securing networks from possible threats and attacks is more than a necessity; ...

Read More >
Contact Us
  • Our Address:
    114 Lavender Street, #07-51, CT Hub 2, Singapore 338729
    Malaysia Johor - 99-01 Jalan Adda 3/1 Taman Adda Height 81100 Johor Bahru Johor, Malaysia
  • Phone Number:
    +65 6652 3398
  • WhatsApp:
    WhatsApp Us
  • Email:
    [email protected]