Certificado SSL gratuito de Let’s Encrypt en servidor IIS con renovación automática

por Manuel Diago García


Escrito el 30 de Agosto de 2018



Pasos a seguir para poder instalar un certificado gratuito SSL LE en nuestro servidor IIS y renovarlo automáticamente.

Antes de comenzar se necesita tener instalado en el servidor IIS el módulo de PowerShell ACMSharp según se explicaba en esta entrada anterior del blog. También es necesario haber inicializado el baúl de claves y realizado el registro en Let's Encrypt.


Nota: Si nos encontramos frente a un sistema Windows Server 2008 o anterior o un Windows 7 o anterior este comando no será válido. Ya que no tendremos el PowerShell actualizado y no nos reconocerá el comando Save-Module. Os adjunto un link de un foro oficial de Microsoft donde solucionan el problema. Link

Es importante tener en cuenta que todos los comandos y el script de renovación se deben ejecutar en el servidor, ya que vamos a utilizar el método de verificación de propiedadad de dominio automático para IIS que nos proporciona ACMESharp, y esta verificación se debe realizar desde el propio servidor IIS.

Una vez instalado podemos continuar.

Guía para crear un certificado SSL para IIS con ACMESharp.

Para proceder a la creación del certificado SSL para nuestro servidor IIS, se deben realizar los siguientes pasos:

  • 1. Ejecutar el Powershell como administrador.

    Ejecutamos nuestro Powershell como administrador, y ahora vamos a escribir los comandos de los siguientes pasos desde el símbolo del sistema de PowerShell.
    Una manera sencilla de abrir un Powershell con permisos de administración es la siguiente: Presiona simultáneamente la tecla "Windows" + tecla "X" para abrir el menú WinX. Haga clic en "Windows Powershell (Administrador)"

  • 2. Verificar si está instalada la extensión de ACMESharp para IIS

    En primer lugar, debemos asegurarnos de que está instalada la extensión de ACMESharp para IIS. Esto lo podemos hacer mediante el comando ‘Get-ACMEExtensionModule’, el cual lista todas las extensiones instaladas (si no devuelve ningún resultado es que no tenemos ninguna extensión instalada).

       Get-ACMEExtensionModule

  • 3. Instalar la extensión de ACMESharp para IIS

    Para instalar la extensión, primero registraremos el repositorio en la PowerShell de windows:

       Register-PSRepository -Name "acmesharp-posh-staging" -SourceLocation https://www.myget.org/F/acmesharp-posh-staging/api/v2

    Ahora instalamos la extension:

       Install-Module -Name "ACMESharp.Providers.IIS" -RequiredVersion "0.9.1.326" -Repository "acmesharp-posh-staging"

    Una vez tenemos instalado el módulo, debemos habilitarlo:

       Import-Module ACMESharp
       Enable-ACMEExtensionModule -ModuleName ACMESharp.Providers.IIS

    Una vez realizadas todas las tareas anteriores, verificamos que el módulo está instalado y habilitado con:

       Get-ACMEExtensionModule

    Una vez instalada la extensión para IIS de ACMESharp, ya podemos generar un certificado para nuestro sitio; En el ejemplo vamos a solicitar un certificado para www.easysii.irenesolutions.com alojado en un servidor IIS con la versión 8.5.9600.16384.

  • 4. Solicitud del certificado por primera vez

    Al utilizar el método automático de validación de dominio para IIS que nos proporciona ACMESharp, únicamente tendremos que validar la propiedad del dominio una vez. Para las posteriores peticiones de certificados en relación al mismo, ya no será necesaria esta validación.

    Ejecutamos el poweshell como administrador y creamos un nuevo identificador de dominio:

       New-ACMEIdentifier -Dns ‘www.easysii.irenesolutions.com’ -Alias ‘www.easysii.irenesolutions.com-2018-08-30--08-44’

    Probamos la propiedad del dominio mediante el comando:

       Complete-ACMEChallenge ‘www.easysii.irenesolutions.com-2018-08-30--08-44’ -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = 'www.easysii.irenesolutions.com' }

    Enviamos la petición de validación de propiedad del dominio:

       Submit-ACMEChallenge ‘www.easysii.irenesolutions.com-2018-08-30--08-44’ -ChallengeType http-01



    Nota: El servidor de Let's encrypt aceptará esta petición de inmediato, pero realizará la validación de forma asíncrona; por lo que no obtendremos una respuesta inmediata (puede tardar segundos e incluso minutos).

    Para revisar el estado de la petición deberemos ejecutar:

       (Update-ACMEIdentifier ‘www.easysii.irenesolutions.com-2018-08-30--08-44’ -ChallengeType http-01).Challenges | Where-Object {$_.Type -eq "http-01"}

  • 5. Pedir y descargar el certificado

    Tras probar que tenemos la propiedad del nombre de dominio, podemos crear una nueva petición de certificado PKI, y enviarla a Let's Encrypt para que lo emita.

       New-ACMECertificate ‘www.easysii.irenesolutions.com-2018-08-30--08-44’ -Generate -Alias ‘www.easysii.irenesolutions.com-2018-08-30--08-44’

    Envíamos la petición.

       Submit-ACMECertificate ‘www.easysii.irenesolutions.com-2018-08-30--08-44’

    Y finalmente, descargamos el certificado como un archivo pfx.

       Get-ACMECertificate ‘www.easysii.irenesolutions.com-2018-08-30--08-44’ -ExportPkcs12 ‘C:\Users\Manuel\Documents\Certificados\ www.easysii.irenesolutions.com-2018-08-30--08-44.pfx’ -CertificatePassword ' miContraseña '

  • 6. Configurar IIS para https con el nuevo certificado

    Una vez obtenido el certificado lo podemos importar en IIS mediante la opción ‘Certificados’ y posteriormente en la gestión de ‘Enlaces’ de nuestro sitio web agregar https para el puerto 443 seleccionando del certificado generado.

  • 7. Automatizar la renovación

    Los certificados de Let’s Encrypt tienen una validez de tres meses, por lo que es interesante programar de algún modo la renovación automática de los mismos. En nuestro caso vamos a utilizar un script de la PowerShell que ejecutaremos cada 60 días. En el realizaremos la renovación de certificado y notificaremos el resultado por mail.
    Abra PowerShell ISE en su equipo, seleccione la opción de nuevo, y pegue el siguiente script:

    import-module ACMESharp
    
    #
    # Parámetros del script
    #
    
    $domain = "www.easysii.irenesolutions.com"
    $iissitename = "www.easysii.irenesolutions.com"
    $certname = "www.easysii.irenesolutions.com-$(get-date -format yyyy-MM-dd--HH-mm)"
    
    #
    # Variables de entorno
    #
    
    $secpasswd = ConvertTo-SecureString "ClaveParaLaCuentaDeCorro" -AsPlainText -Force
    $PSEmailCredentials = New-Object System.Management.Automation.PSCredential ("maildeenvio@gmail.com", $secpasswd)
    
    $PSEmailServer = "smtp.gmail.com"
    $PSEmailServerPort = 587
    $LocalEmailAddress = "maildeenvio@gmail.com"
    $OwnerEmailAddress = "mail@receptor.com"
    $pfxfile = "C:\Users\Administrador\Desktop\z\$certname.pfx"
    $CertificatePassword = "miClaveParaElCertificado"
    # Si el servidor de envío no usa SSL borrar -UseSsl de la función del PowerShell Send-MailMessage
    
    #
    # Inicio del script
    #
    
    $ErrorActionPreference = "Stop"
    $EmailLog = @()
    
    #
    # Utilidades
    #
    
    function Write-Log {
      Write-Host $args[0]
      $script:EmailLog  += $args[0]
    }
    
    Try {
      Write-Log "Generando ACMEIdentifier para el dominio $domain con el alia $certname"
      New-ACMEIdentifier -Dns $domain -Alias $certname
      
      # Sólo necesario para la primera petición de certificado (validación propiedad del dominio)
      
      #Write-Log "Completando ACMEChallenge con http"
      #Complete-ACMEChallenge $certname -ChallengeType http-01 -Handler iis -HandlerParameters @{ WebSiteRef = $iissitename }
      
      #Write-Log "Enviando petición ACMEChallenge con http"
      #Submit-ACMEChallenge $certname -ChallengeType http-01
      
      # Verificar el estatus del identificador cada 6 segundos hasta que obtengamos respuesta o haya pasado un minuto
      $i = 0
      do {
        $identinfo = (Update-ACMEIdentifier $certname -ChallengeType http-01).Challenges | Where-Object {$_.Status -eq "valid"}
        if($identinfo -eq $null) {
          Start-Sleep 6
          $i++
        }
      } until($identinfo -ne $null -or $i -gt 10)
    
      if($identinfo -eq $null) {
        Write-Log "No se recibio la peticion de ACMEIdentifier después de 60 segundos"
        $Body = $EmailLog | out-string
        Send-MailMessage -SmtpServer $PSEmailServer -Port $PSEmailServerPort -UseSsl -Credential $PSEmailCredentials -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Intento de renovacion del certificado Let's Encrypt para el dominio $domain" -Body $Body
        Exit
      }
      
      # Ya tenemos el identificador... entonces, vamos a crear el certificado
      Write-Log "Intentando renovar el certificado Let's Encrypt para el dominio $domain"
    
      # Generamos el certificado
      Write-Log "Generando certificado para el dominio $domain"
      New-ACMECertificate $certname -Generate -Alias $certname
    
      # Envíamos la petición
      Submit-ACMECertificate $certname
    
      # Verificamos el estado de la peticion cada 6 segundos hasta obtener respuesta o que pase un minuto
      $i = 0
      do {
        $certinfo = Update-AcmeCertificate $certname
        if($certinfo.SerialNumber -eq "") {
          Start-Sleep 6
          $i++
        }
      } until($certinfo.SerialNumber -ne "" -or $i -gt 10)
    
      if($i -gt 10) {
        Write-Log "No recibimos un certificado completo despues de 60 segundos"
        $Body = $EmailLog | out-string
        Send-MailMessage -SmtpServer $PSEmailServer -Port $PSEmailServerPort -UseSsl -Credential $PSEmailCredentials -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Intento de renovacion del certificado Let's Encrypt para el dominio $domain" -Body $Body
        Exit
      }
    
      # Exportamos el certificado a un fichero PFX
      Get-ACMECertificate $certname -ExportPkcs12 $pfxfile -CertificatePassword $CertificatePassword
    
      # Importamos el certificado al almacén del equipo local
      Write-Log "Importando certificado pfx $pfxfile"
      $certRootStore = "LocalMachine"
      $certStore = "My"
      $pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
      $pfx.Import($pfxfile,$CertificatePassword,"Exportable,PersistKeySet,MachineKeySet") 
      $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore) 
      $store.Open('ReadWrite')
      $store.Add($pfx) 
      $store.Close() 
      $certThumbprint = $pfx.Thumbprint
    
      # Enlazamos el certificado con el sitio IIS de la petición
      Write-Log "Bind certificate with Thumbprint $certThumbprint"
      $obj = get-webconfiguration "//sites/site[@name='$iissitename']"
      for($i = 0; $i -lt $obj.bindings.Collection.Length; $i++) {
        $binding = $obj.bindings.Collection[$i]
        if($binding.protocol -eq "https") {
          $method = $binding.Methods["AddSslCertificate"]
          $methodInstance = $method.CreateInstance()
          $methodInstance.Input.SetAttributeValue("certificateHash", $certThumbprint)
          $methodInstance.Input.SetAttributeValue("certificateStoreName", $certStore)
          $methodInstance.Execute()
        }
      }
    
      # Eliminamos los certificados de LetsEncrypt preexistentes
      Write-Log "Eliminamos todos los certificados existentes del dominio excepto el generado"
      $certRootStore = "LocalMachine"
      $certStore = "My"
      $date = Get-Date
      $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore) 
      $store.Open('ReadWrite')
      foreach($cert in $store.Certificates) {
        if($cert.Subject -eq "CN=$domain" -And $cert.Issuer.Contains("Let's Encrypt") -And $cert.Thumbprint -ne $certThumbprint) {
          Write-Log "Removing certificate $($cert.Thumbprint)"
          $store.Remove($cert)
        }
      }
      $store.Close() 
    
      # Finalizado
      Write-Log "Finalizado"
      $Body = $EmailLog | out-string
      Send-MailMessage -SmtpServer $PSEmailServer -Port $PSEmailServerPort -UseSsl -Credential $PSEmailCredentials -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Renovado el certificado de Let's Encrypt del sitio $domain" -Body $Body
    } Catch {
      Write-Host $_.Exception
      $ErrorMessage = $_.Exception | format-list -force | out-string
      $EmailLog += "Renovación de certificado Let's Encrypt para el dominio $domain fallida con la excepción `n$ErrorMessage`r`n`r`n"
      $Body = $EmailLog | Out-String
      Send-MailMessage -SmtpServer $PSEmailServer -Port $PSEmailServerPort -UseSsl -Credential $PSEmailCredentials -From $LocalEmailAddress -To $OwnerEmailAddress -Subject "Intento de renovacion del certificado Let's Encrypt para el dominio $domain" -Body $Body
      Exit
    }
    
                                

  • Feedback

    El contenido del presente post se ha generado en base a la primera puesta en marcha de un sitio https en uno de nuestros servidores IIS. Seguramente quedarán muchas cosas por añadir, y habrá errores o problemas que resolver; por lo tanto cualquier ayuda, apreciación, consulta... será de gran ayuda para todos, por lo que espero que no dudéis en contactar conmigo en el siguiente mail manuel@irenesolutions.com.

Fuentes.

Wiki en Github del proyecto ACMESharp

https://github.com/ebekker/ACMESharp/wiki/Quick-Start

[16 de febrero de 2018]

Marc Durdin's Blog: Let’s Encrypt on Windows, redux

https://marc.durdin.net/2017/02/lets-encrypt-on-windows-redux/

[16 de febrero de 2018]