Functions/Certificate/New-DomainSignedCertificate.ps1

<#
    .SYNOPSIS
        Create a new certificate signed by an domain-based enterprise ca.
 
    .DESCRIPTION
        Use the tools certreq.exe and openssl.exe to request a domain signed
        certificate and export it as Windows (.cer, .pfx) and Linux (.pem, .key)
        formatted certificate.
 
    .EXAMPLE
        PS C:\> New-DomainSignedCertificate
        Create a new certificate. PowerShell will prompt for subject, dns name
        and password.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function New-DomainSignedCertificate
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        # Subject of the certificate, without the 'CN=' prefix. This subject is
        # always included as dns name in the subject alternative name.
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $Subject,

        # Add dns names to the subject alternative name.
        [Parameter(Mandatory = $true, Position = 1)]
        [AllowEmptyCollection()]
        [System.String[]]
        $DnsName,

        # Add ip addresses to the subject alternative name.
        [Parameter(Mandatory = $false, Position = 2)]
        [AllowEmptyCollection()]
        [System.String[]]
        $IPAddress,

        # Specify the key usage extensions.
        [Parameter(Mandatory = $false)]
        [ValidateSet('ServerAuthentication', 'ClientAuthentication')]
        [System.String]
        $EnhancedKeyUsage = 'ServerAuthentication',

        # Length of the private key, by default 2048.
        [Parameter(Mandatory = $false)]
        [System.Int32]
        $KeyLength = 2048,

        # Name of the CA certificate template to use.
        [Parameter(Mandatory = $false)]
        [System.String]
        $CertificateTemplate = 'InternalWebServer',

        # Password to encrypt the pfx file.
        [Parameter(Mandatory = $true)]
        [System.Security.SecureString]
        $Password,

        # Overwrite the existing files.
        [Parameter(Mandatory = $false)]
        [switch]
        $Force,

        # Path to the working folder where the files will be stored.
        [Parameter(Mandatory = $false)]
        [System.String]
        $Path = (Get-Location).Path,

        # Keep all extra files like the policy definition or request file.
        [Parameter(Mandatory = $false)]
        [switch]
        $Keep
    )

    $ErrorActionPreference = 'Stop'

    try
    {
        Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status 'Setup' -PercentComplete 0

        # Policy template definitions
        $enhancedKeyUsageOidMap = @{
            'ServerAuthentication' = '1.3.6.1.5.5.7.3.1'
            'ClientAuthentication' = '1.3.6.1.5.5.7.3.2'
        }

        # Check if the current user is admin
        if (-not (Test-AdministratorRole))
        {
            throw 'Access denied. Restart this command as administrator.'
        }

        # Check and get native commands
        $certReqCmd  = Get-CommandPath -Name 'certreq.exe'
        $openSslCmd  = Get-CommandPath -Name 'openssl.exe' -WarningMessage 'Download OpenSSL for Windows from https://slproweb.com/products/Win32OpenSSL.html'

        # Append subject to the dns name or ip address
        if ($Subject -match '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$')
        {
            if ($IPAddress -notcontains $Subject)
            {
                $IPAddress = @($Subject) + $IPAddress
            }
        }
        else
        {
            if ($DnsName -notcontains $Subject)
            {
                $DnsName = @($Subject) + $DnsName
            }
        }

        # Prepare certificate definition
        $policy = @()
        $policy += '[Version]'
        $policy += 'Signature = "$Windows NT$"'
        $policy += ''
        $policy += '[NewRequest]'
        $policy += 'Subject = "CN={0}"' -f $Subject
        $policy += 'Exportable = TRUE'
        $policy += 'KeyLength = {0}' -f $KeyLength
        $policy += 'KeySpec = 1'
        $policy += 'KeyUsage = 0xA0'
        $policy += 'MachineKeySet = True'
        $policy += 'ProviderName = "Microsoft RSA SChannel Cryptographic Provider"'
        $policy += 'RequestType = PKCS10'
        $policy += ''
        $policy += '[EnhancedKeyUsageExtension]'
        foreach ($currentEnhancedKeyUsage in $EnhancedKeyUsage)
        {
            $policy += 'OID = {0}' -f $enhancedKeyUsageOidMap[$currentEnhancedKeyUsage]
        }
        $policy += ''
        $policy += '[Extensions]'
        $policy += '2.5.29.17 = "{text}"'
        foreach ($currentDnsName in $DnsName)
        {
            $policy += '_continue_ = "DNS={0}&"' -f $currentDnsName
        }
        foreach ($currentIPAddress in $IPAddress)
        {
            $policy += '_continue_ = "IPAddress={0}&"' -f $currentIPAddress
        }
        $policy += ''
        $policy += '[RequestAttributes]'
        $policy += 'CertificateTemplate = {0}' -f $CertificateTemplate


        if ($PSCmdlet.ShouldProcess("CN=$Subject", 'Crate'))
        {
            # Trim the path and cleanup existing files
            $Path = $Path.TrimEnd('\')
            foreach ($extension in 'cer', 'inf', 'key', 'pem', 'pfx', 'req', 'rsp')
            {
                $filePath ="$Path\$Subject.$extension"
                if (Test-Path -Path $filePath)
                {
                    if ($Force.IsPresent)
                    {
                        Remove-Item -Path $filePath -Force -Confirm:$false
                    }
                    else
                    {
                        throw "The file $filePath already exists!"
                    }
                }
            }


            # Step 1
            # Store the policy file

            Write-Verbose "Create policy file $Subject.inf"

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Create policy file $Subject.inf" -PercentComplete 14

            Set-Content -Path "$Path\$Subject.inf" -Value $policy


            # Step 2
            # Create a certificate request and store the private key in the user session

            Write-Verbose "Create request file $Subject.req"
            Write-Verbose " certreq.exe -new -q -f `"$Path\$Subject.inf`" `"$Path\$Subject.req`""

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Create request file $Subject.req" -PercentComplete 28

            $result = (& $certReqCmd -new -q -f "`"$Path\$Subject.inf`"" "`"$Path\$Subject.req`"")

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to create the certificate request!`n`n$result"
            }


            # Step 3
            # Submit the certificate request to the CA

            Write-Verbose "Sign request and export to $Subject.cer"
            Write-Verbose " certreq.exe -submit -q -f `"$Path\$Subject.req`" `"$Path\$Subject.cer`""

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Sign request and export to $Subject.cer" -PercentComplete 28

            $result = (& $certReqCmd -submit -q -f "`"$Path\$Subject.req`"" "`"$Path\$Subject.cer`"")

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to submit the certificate request!`n`n$result"
            }


            # Step 4
            # Accept the request and import it into the local cert store

            Write-Verbose "Accept and import signed certificate $Subject.cer"
            Write-Verbose " certreq.exe -accept -q `"$Path\$Subject.cer`""

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Accept and import signed certificate $Subject.cer" -PercentComplete 42

            $result = (& $certReqCmd -accept -q "`"$Path\$Subject.cer`"")

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to accept the certificate request!`n`n$result"
            }


            # Step 5
            # Export certificate as PFX file

            Write-Verbose "Export the certificate as PFX to $Subject.pfx"

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Export the certificate as PFX to $Subject.pfx" -PercentComplete 56

            # Extract thumbprint from the cer file
            $thumbprint = Get-PfxCertificate -FilePath "$Path\$Subject.cer" | Select-Object -ExpandProperty 'Thumbprint'

            # Export the pfx protected by a password
            Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" | Export-PfxCertificate -FilePath "$Path\$Subject.pfx" -Password $Password | Out-Null


            # Step 6
            # Export certificate as PEM file

            Write-Verbose "Export the certificate as PEM to $Subject.pem"
            Write-Verbose " openssl.exe pkcs12 -passin pass:`"***`" -in `"$Path\$Subject.pfx`" -clcerts -nokeys -out `"$Path\$Subject.pem`""
            Write-Verbose " openssl.exe x509 -in `"$Path\$Subject.pem`" -out `"$Path\$Subject.pem`""

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Export the certificate as PEM to $Subject.pem" -PercentComplete 70

            $result = (& $openSslCmd pkcs12 -passin "pass:`"$(Unprotect-SecureString -SecureString $Password)`"" -in "`"$Path\$Subject.pfx`"" -clcerts -nokeys -out "`"$Path\$Subject.pem`"")

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to convert the certificate from pfx to pem!"
            }

            $result = (& $openSslCmd x509 -in "`"$Path\$Subject.pem`"" -out "`"$Path\$Subject.pem`"")

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to convert the certificate from pfx to pem!"
            }


            # Step 7
            # Export certificate as KEY file

            Write-Verbose "Export the certificate as KEY to $Subject.key"
            Write-Verbose " openssl.exe pkcs12 -passin pass:`"***`" -in `"$Path\$Subject.pfx`" -nocerts -out `"$Path\$Subject.key`" -nodes"
            Write-Verbose " openssl.exe rsa -in `"$Path\$Subject.key`" -out `"$Path\$Subject.key`""

            Write-Progress -Activity "Generate Certificate for CN=$Subject" -Status "Export the certificate as KEY to $Subject.key" -PercentComplete 84

            $result = (& $openSslCmd pkcs12 -passin "pass:`"$(Unprotect-SecureString -SecureString $Password)`"" -in "`"$Path\$Subject.pfx`"" -nocerts -out "`"$Path\$Subject.key`"" -nodes)

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to convert the certificate from pfx to key!"
            }

            $ErrorActionPreference = 'SilentlyContinue'
            $result = (& $openSslCmd rsa -in "`"$Path\$Subject.key`"" -out "`"$Path\$Subject.key`"" 2>&1)
            $ErrorActionPreference = 'Stop'

            if ($Global:LASTEXITCODE -ne 0)
            {
                throw "Failed to convert the certificate from pfx to key!"
            }

            if (-not $Keep.IsPresent)
            {
                Get-Item -Path "Cert:\LocalMachine\My\$thumbprint"

                Remove-Item -Path "$Path\$Subject.inf" -Force -Confirm:$false
                Remove-Item -Path "$Path\$Subject.req" -Force -Confirm:$false
                Remove-Item -Path "$Path\$Subject.rsp" -Force -Confirm:$false
            }
        }
    }
    catch
    {
        throw $_
    }
    finally
    {
        Write-Progress -Activity "Generate Certificate for CN=$Subject" -PercentComplete 100 -Completed
    }
}