DSCResources/ArcGIS_IIS_TLS/ArcGIS_IIS_TLS.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the ArcGIS Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'ArcGIS.Common' `
            -ChildPath 'ArcGIS.Common.psm1'))

<#
    .SYNOPSIS
        Check for an SSL Certificate. If not present Installs and Configures the SSL Certificate. If certifcate not provided, creates a self signed certificate and configures it.
    .PARAMETER Ensure
        Indicates to make sure a SSL Certificate is Installed and Configured on the Machine. Take the values Present or Absent.
        - "Present" ensures that a SSL Certificate is Installed if provided and Configured on the Machine, if not already done.
        - "Absent" ensures that a SSL Certificate is uninstalled, if present and configured - Not Implemented.
    .PARAMETER WebSiteId
        Id of the website with which the SSL Certificate needs to be Binded with.
    .PARAMETER ExternalDNSName
        Name of the ExternalDNSName with which the SSL Certificates needs to be Binded with.
    .PARAMETER CertificateFileLocation
        A Path to a Physical Location or Network Share Address for the SSL Certificate File.
    .PARAMETER CertificatePassword
        Secret key or password for the SSL Certificate.
#>


function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Int32]
        $WebSiteId,

        [parameter(Mandatory = $true)]
        [System.String]
        $ExternalDNSName
    )
    
    $null
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Int32]
        $WebSiteId,

        [parameter(Mandatory = $true)]
        [System.String]
        $ExternalDNSName,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,
        
        [System.String]
        $CertificateFileLocation,

        [System.Management.Automation.PSCredential]
        $CertificatePassword
    )

    

    $CurrVerbosePreference = $VerbosePreference # Save current preference
    $VerbosePreference = 'SilentlyContinue' # quieten it to ignore verbose output from Importing WebAdmin (bug in Powershell for this module)
    Import-Module WebAdministration | Out-Null
    $VerbosePreference = $CurrVerbosePreference # reset it back to previous preference

    Invoke-EnsureWebBindingForHTTPS -WebSiteId $WebSiteId

    $CertToInstall = $null
    if($CertificateFileLocation -and ($null -ne $CertificatePassword) -and (Test-Path $CertificateFileLocation)){
        Write-Verbose "Importing Certificate from $CertificateFileLocation"
        $CertToInstall = Import-PfxCertificateFromFile -CertificatePath $CertificateFileLocation -pfxPassword $CertificatePassword
        Write-Verbose "Installing CA Issued Certificate $($CertToInstall) for DnsName $ExternalDNSName"       
        Install-SSLCertificateIntoIIS -DnsName $ExternalDNSName -CertificateToInstall $CertToInstall
    }
    else {
        Write-Verbose "Installing Self-Signed Certificate for DnsName $ExternalDNSName"
        Install-SSLCertificateIntoIIS -DnsName $ExternalDNSName 
    }
}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Int32]
        $WebSiteId,

        [parameter(Mandatory = $true)]
        [System.String]
        $ExternalDNSName,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,
        
        [System.String]
        $CertificateFileLocation,

        [System.Management.Automation.PSCredential]
        $CertificatePassword
    )

    $result = $false
    $Port = 443
    
    if($CertificateFileLocation -and ($null -ne $CertificatePassword) -and (Test-Path $CertificateFileLocation)){
        $pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 
        if($null -ne $CertificatePassword) {
            $pfx.Import($CertificateFileLocation,$CertificatePassword.GetNetworkCredential().Password,'DefaultKeySet') 
        }
        else {
            $pfx.Import($CertificateFileLocation)
        }
        $CertRootStore = "LocalMachine"
        $CertStore = "My"
        $CertPath = "Cert:\$CertRootStore\$CertStore\$($pfx.Thumbprint)"
        if(Test-Path $CertPath)  {
            Write-Verbose "Certificate found in $CertPath"
            $result = $true
        }
        if($result){
            $result = $false
            $CurrVerbosePreference = $VerbosePreference # Save current preference
            $VerbosePreference = 'SilentlyContinue' # quieten it to ignore verbose output from Importing WebAdmin (bug in Powershell for this module)
            Import-Module WebAdministration | Out-Null
            $VerbosePreference = $CurrVerbosePreference # reset it back to previous preference
            $binding = Get-WebBinding -Protocol https -Port $Port
            if($binding)
            {
                Write-Verbose "IIS has a web binding at Port $Port. Checking for the Certificate"
                $IISCertPath = "IIS:\SslBindings\0.0.0.0!$Port"
                if(Test-Path $IISCertPath) {
                    if ($binding.certificateHash -ieq $pfx.Thumbprint) {
                        $result = $true
                    }
                }
            }
        }
    }else{
        # Self Signed option
        $CurrVerbosePreference = $VerbosePreference # Save current preference
        $VerbosePreference = 'SilentlyContinue' # quieten it to ignore verbose output from Importing WebAdmin (bug in Powershell for this module)
        Import-Module WebAdministration | Out-Null
        $VerbosePreference = $CurrVerbosePreference # reset it back to previous preference
        if(Get-WebBinding -Protocol https -Port $Port)
        {
            Write-Verbose "IIS has a web binding at Port $Port. Checking for the Certificate"
            $IISCertPath = "IIS:\SslBindings\0.0.0.0!$Port"
            if(Test-Path $IISCertPath) {                
                $result = $true
            }
        }
    }  

    if($Ensure -ieq 'Present') {
        $result   
    }
    elseif($Ensure -ieq 'Absent') {        
        (-not($result))
    }
}


function Get-CommonNameSplits
{
    [CmdletBinding()]
    param
    (
        [System.String]
        $SubjectName
    )

    if(-not($SubjectName)){
        return $SubjectName
    }
    [string[]]$Splits = $SubjectName.Split(',', [StringSplitOptions]::RemoveEmptyEntries)
    foreach($Split in $Splits)
    {
        [string[]]$SubSplits = $Split.Split('=',[StringSplitOptions]::RemoveEmptyEntries)
        if($SubSplits -and ($SubSplits.Length -gt 1) -and ('CN' -ieq $SubSplits[0])){
            [string[]]$InnerSplits = $SubSplits[1].Split('.',[StringSplitOptions]::RemoveEmptyEntries)  
            return $InnerSplits
        }
    }
    return $null
}


function Install-SSLCertificateIntoIIS
{
    [CmdletBinding()]
    param
    (
        [System.String]
        $DnsName,

        [System.Int32]
        $Port = 443,

        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $CertificateToInstall = $null
    )

    <#
    ###
    ### Ensure binding exists
    ###
    Write-Verbose "Install-SSLCertificateIntoIIS"
    $binding = Get-WebBinding -Protocol https -Port $Port
    if($null -eq $binding)
    {
        Write-Verbose 'Setting up SSL Binding with self signed certificate'
        Write-Verbose "Creating Binding on Port $Port for https"
        New-WebBinding -Name "Default Web Site" -IP "*" -Port $Port -Protocol https
        Write-Verbose "Finished Creating Binding on Port $Port for https"
    }
    #>
   
        
    ###
    ### Ensure certificate (if not create one)
    ###
    if($null -eq $CertificateToInstall) {
        Write-Verbose "Creating New-SelfSignedCertificate for DNS:- $DnsName"
        if($null -ne (Get-Command -Name 'New-SelfSignedCertificate' -ErrorAction Ignore)) {
            Write-Verbose 'Creating using New-SelfSignedCertificate'
            $Cert = New-SelfSignedCertificate -DnsName $DnsName -CertStoreLocation cert:\LocalMachine\My 
            Write-Verbose 'Finished Creating using New-SelfSignedCertificate'
        }
        else {
            throw "New-SelfSignedCertificate isn't available on the machine. Please create provide a certificate and try again."
        }
    }
    else {
        $SubjectName = $CertificateToInstall.SubjectName.Name
        if(-not($SubjectName)){
            $SubjectName = $CertificateToInstall.Subject
        }

        Write-Verbose "Installing existing certificate with SubjectName = $SubjectName and Thumbprint $($CertificateToInstall.Thumbprint)"

        $AllCerts = Get-ChildItem cert:\LocalMachine\My 
        foreach($ACert in $AllCerts){
            if($CertificateToInstall.Thumbprint -ieq $ACert.Thumbprint) 
            {
                $Cert = $ACert
            }
        }
        
        #Search based on Name as a fallback - Backward compatibility
        if(-not($Cert)) 
        {
            Write-Verbose "Installing existing certificate with SubjectName $($SubjectName)"
            $SubjectNameSplits = Get-CommonNameSplits $SubjectName
            if($null -eq $SubjectNameSplits) { throw "Unable to split $SubjectName" }        
            $AllCerts = Get-ChildItem cert:\LocalMachine\My 
            foreach($ACert in $AllCerts){
                $SubjectForCert = $ACert.Subject
                if($SubjectForCert -and $SubjectForCert.Length -gt 0) {
                    $CertSubjectNameSplits = Get-CommonNameSplits $SubjectForCert
                    if($CertSubjectNameSplits -and ($SubjectNameSplits.Length -eq $CertSubjectNameSplits.Length)) {
                        $MisMatch = $false   
                        [int]$count = $SubjectNameSplits.Length
                        for($m = 0; $m -lt $count; $m++){
                            if($SubjectNameSplits[$m] -ine $CertSubjectNameSplits[$m] -and $SubjectNameSplits[$m] -ine '*') {
                                $MisMatch = $true
                                break
                            }
                        }            
                        if($MisMatch -eq $false) {
                            $Cert = $ACert
                            break
                        } 
                    }
                }
            }
        }

        if(-not($Cert)) 
        {
            $Cert = Get-ChildItem cert:\LocalMachine\My | Where-Object { $_.Thumbprint -ieq $CertificateToInstall.Thumbprint } | Select-Object -First 1 
            if($null -eq $Cert){
                throw "Unable to find certificate with SubjectName = $SubjectName and Thumbprint $($CertificateToInstall.Thumbprint) in 'cert:\LocalMachine\My'"
            }               
        }              
    }
    
    $InstallPath = "IIS:\SslBindings\0.0.0.0!$Port"
    if(Test-Path $InstallPath) {
        Write-Verbose "Removing existing certificate at $InstallPath"
        Remove-Item -Path $InstallPath -Force
    }
    Write-Verbose "Installing Certificate with thumbprint $($Cert.Thumbprint) and subject $($Cert.Subject) into IIS Binding for Port $Port"
    New-Item  $InstallPath -Value $Cert
    Write-Verbose 'Finished Installing Certificate'
}
 
function Import-PfxCertificateFromFile
{
    [CmdletBinding()]
    param
    (
        [System.String]
        $CertificatePath,

        [System.String]
        $CertRootStore = "LocalMachine",

        [System.String]
        $CertStore = "My",

        [System.Management.Automation.PSCredential]
        $pfxPassword = $null
    )

    $pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 
    if($null -ne $pfxPassword) {
        $pfx.Import($CertificatePath,$pfxPassword.GetNetworkCredential().Password,"Exportable,PersistKeySet") 
    }
    else {
        $pfx.Import($CertificatePath)
    }
    $CertPath = "Cert:\$CertRootStore\$CertStore\$($pfx.Thumbprint)"    
    if(Test-Path $CertPath)  {        
        Remove-Item $CertPath -Force
    }
    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($CertStore,$CertRootStore)
    $store.Open("MaxAllowed")
    $store.Add($pfx)
    $store.Close()
    $pfx
}

function Invoke-EnsureWebBindingForHTTPS
{
    [CmdletBinding()]    
    param
    (
        [parameter(Mandatory = $true)]
        [System.Int32]
        $WebSiteId,

        [parameter(Mandatory = $false)]
        [System.Int32]
        $Port = 443
    )
    ###
    ### Ensure binding exists
    ###
    $binding = Get-WebBinding -Protocol https -Port $Port    
    if($null -eq $binding) 
    {      
        Write-Verbose 'Setting up SSL Binding with self signed certificate'
        Write-Verbose "Creating Binding on Port $Port for https"
        $WebSiteName = (Get-Website | Where-Object {$_.ID -eq $WebSiteId}).Name
        New-WebBinding -Name $WebSiteName -IP "*" -Port $Port -Protocol https
        Write-Verbose "Finished Creating Binding on Port $Port for https"
    }   
}

Export-ModuleMember -Function *-TargetResource