CertificateConversion.psm1

#region Functions
#region Export-ServerCertificateFromPFX
function Export-ServerCertificateFromPFX
{
    [CmdletBinding()]
    Param
    (
        # The path to the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $PFXFilePath,

        # The passphrase for the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=1)]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $PFXPassphrase,

        # The path to the certificate file to create
        [Parameter(Position=2)]
        [string]
        $DestinationCertificateFilePath,

        # Display OpenSSL output
        [Parameter()]
        [switch]
        $OpenSSLOutput = $false
    )

    Begin
    {
        # Import the 'CPolydorou.FileSystem' module
        if((get-module).Name -notcontains "cpolydorou.security")
        {
            try
            {
                Import-Module CPolydorou.FileSystem -ErrorAction Stop -Verbose:$false -DisableNameChecking
            }
            catch
            {
                Throw "Failed to import the 'CPolydorou.FileSystem' module. This module is required and has to be installed."
            }
        }
    }

    Process
    {
        # Verbose output
        Write-Verbose "Extracting server certificate from PFX file: $PFXFilePath"

        # Test the installation of OpenSSL
        $OpenSSLPath = Get-OpenSSLPath

        # Test the OpenSSL files
        Test-OpenSSLInstallation

        Write-Verbose "Found OpenSSL binary at: $OpenSSLPath"

        # Check if the PFX file exists
        try
        {
            $pfx = (Resolve-Path -Path $PFXFilePath -ErrorAction Stop).Path
        }
        catch
        {
            Throw "Could not locate the PFX file."
        }

        # Check if the DestinationCertificateFilePath is null and create a path
        if(-Not $DestinationCertificateFilePath)
        {
            $base = (Get-Location).Path + "\"
            $filename = (Get-Item -Path $pfx).Name + ".crt"
            $DestinationCertificateFile = $base + $filename
        }
        else
        {
            $DestinationCertificateFile = (Resolve-NonExistentPath -Path $DestinationCertificateFilePath).Fullname
        }

        # Extract the passphrase
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($PFXPassphrase)
        $UnsecurePassphrase = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

        # Verbose output
        Write-Verbose "Saving exported certificate at: $DestinationCertificateFile"

        # Set the arguments
        $args = @("pkcs12", "-in", $pfx, "-clcerts", "-nokeys", "-out", $DestinationCertificateFile, "-passin", "pass:$UnsecurePassphrase")

        # Convert the file
        try
        {
            $result = Convert -OpenSSLPath $OpenSSLPath -arguments $args

            if($OpenSSLOutput)
            {
                Write-Output $result
            }

            if($result.ExitCode -ne 0)
            {
                Write-Error "OpenSSL failed to perform the requested operation."
            }
        }
        catch
        {
            Write-Error ("Failed to invoke OpenSSL. " + $_.Exception.Message)
        }
    }

    End
    {
    }
}
#endregion

#region Export-CertificateChainFromPFX
function Export-CertificateChainFromPFX
{
    [CmdletBinding()]
    Param
    (
        # The path to the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $PFXFilePath,

        # The passphrase for the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=1)]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $PFXPassphrase,


        # The path to the certificate file to create
        [Parameter(Position=2)]
        [string]
        $DestinationCertificateFilePath,

        # Display OpenSSL output
        [Parameter()]
        [switch]
        $OpenSSLOutput = $false
    )

    Begin
    {
        # Import the 'CPolydorou.FileSystem' module
        if((get-module).Name -notcontains "cpolydorou.security")
        {
            try
            {
                Import-Module CPolydorou.FileSystem -ErrorAction Stop -Verbose:$false -DisableNameChecking
            }
            catch
            {
                Throw "Failed to import the 'CPolydorou.FileSystem' module. This module is required and has to be installed."
            }
        }
    }

    Process
    {
        # Verbose output
        Write-Verbose "Extracting certificate chain from PFX file: $PFXFilePath"

        # Test the installation of OpenSSL
        $OpenSSLPath = Get-OpenSSLPath

        # Test the 7Z files
        Test-OpenSSLInstallation

        Write-Verbose "Found OpenSSL binary at: $OpenSSLPath"

        # Check if the PFX file exists
        try
        {
            $pfx = (Resolve-Path -Path $PFXFilePath -ErrorAction Stop).Path
        }
        catch
        {
            Throw "Could not locate the PFX file."
        }

        # Check if the DestinationCertificateFilePath is null and create a path
        if(-Not $DestinationCertificateFilePath)
        {
            $base = (Get-Location).Path + "\"
            $filename = (Get-Item -Path $PFXFilePath).Name + ".chain.crt"
            $DestinationCertificateFile = $base + $filename
        }
        else
        {
            $DestinationCertificateFile = (Resolve-NonExistentPath -Path $DestinationCertificateFilePath).Fullname
        }

        # Extract the passphrase
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($PFXPassphrase)
        $UnsecurePassphrase = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

        # Verbose output
        Write-Verbose "Saving exported certificate at: $DestinationCertificateFile"

        # Set the arguments
        $args = @("pkcs12", "-in", $pfx, "-cacerts", "-nokeys", "-chain", "-out", $DestinationCertificateFile, "-password", "pass:$UnsecurePassphrase")

        # Convert the file
        try
        {
            $result = Convert -OpenSSLPath $OpenSSLPath -arguments $args

            if($OpenSSLOutput)
            {
                Write-Output $result
            }

            if($result.ExitCode -ne 0)
            {
                Write-Error "OpenSSL failed to perform the requested operation."
            }

        }
        catch
        {
            Write-Error ("Failed to invoke OpenSSL. " + $_.Exception.Message)
        }
    }

    End
    {
    }
}
#endregion

#region Export-PrivateKeyFromPFX
function Export-PrivateKeyFromPFX
{
    [CmdletBinding()]
    Param
    (
        # The path to the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $PFXFilePath,

        # The passphrase for the PFX file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=1)]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $PFXPassphrase,

        # The path to the certificate file to create
        [Parameter(Position=2)]
        [string]
        $DestinationCertificateFilePath,

        # The passphrase for the key file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=1)]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $KeyPassphrase,

        # Display OpenSSL output
        [Parameter()]
        [switch]
        $OpenSSLOutput = $false
    )

    Begin
    {
        # Import the 'CPolydorou.FileSystem' module
        if((get-module).Name -notcontains "cpolydorou.security")
        {
            try
            {
                Import-Module CPolydorou.FileSystem -ErrorAction Stop -Verbose:$false -DisableNameChecking
            }
            catch
            {
                Throw "Failed to import the 'CPolydorou.FileSystem' module. This module is required and has to be installed."
            }
        }
    }

    Process
    {
        # Verbose output
        Write-Verbose "Extracting server certificate from PFX file: $PFXFilePath"

        # Test the installation of OpenSSL
        $OpenSSLPath = Get-OpenSSLPath

        # Test the OpenSSL files
        Test-OpenSSLInstallation

        Write-Verbose "Found OpenSSL binary at: $OpenSSLPath"

        # Check if the PFX file exists
        try
        {
            $pfx = (Resolve-Path -Path $PFXFilePath -ErrorAction Stop).Path
        }
        catch
        {
            Throw "Could not locate the PFX file."
        }

        # Check if the DestinationCertificateFilePath is null and create a path
        if(-Not $DestinationCertificateFilePath)
        {
            $base = (Get-Location).Path + "\"
            $filename = (Get-Item -Path $pfx).Name + ".key"
            $DestinationCertificateFile = $base + $filename
        }
        else
        {
            $DestinationCertificateFile = (Resolve-NonExistentPath -Path $DestinationCertificateFilePath).Fullname
        }

        # Extract the PFX passphrase
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($PFXPassphrase)
        $UnsecurePassphrase = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

        # Extract the Key passphrase
        $BSTR2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($KeyPassphrase)
        $UnsecurePassphraseKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR2)

        # Verbose output
        Write-Verbose "Saving exported private key at: $DestinationCertificateFile"

        # Set the arguments
        $args = @("pkcs12", "-in", $pfx, "-cacerts", "-nocerts", "-out", $DestinationCertificateFile, "-passin", "pass:$UnsecurePassphrase", "-passout", "pass:$UnsecurePassphraseKey")

        # Convert the file
        try
        {
            $result = Convert -OpenSSLPath $OpenSSLPath -arguments $args

            if($OpenSSLOutput)
            {
                Write-Output $result
            }

            if($result.ExitCode -ne 0)
            {
                Write-Error "OpenSSL failed to perform the requested operation."
            }
        }
        catch
        {
            Write-Error ("Failed to invoke OpenSSL. " + $_.Exception.Message)
        }
    }

    End
    {
    }
}
#endregion

#region Decrypt-PrivateKey
function Decrypt-PrivateKey
{
    [CmdletBinding()]
    Param
    (
        # The path to the encrypted private key file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $PrivateKeyFilePath,

        # The passphrase for the private key file
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$false,
                   ValueFromPipelineByPropertyName=$false, 
                   ValueFromRemainingArguments=$false, 
                   Position=1)]
        [ValidateNotNullOrEmpty()]
        [securestring]
        $PrivateKeyPassphrase,

        # The path to the certificate file to create
        [Parameter(Position=2)]
        [string]
        $DestinationCertificateFilePath,

        # Display OpenSSL output
        [Parameter()]
        [switch]
        $OpenSSLOutput = $false
    )

    Begin
    {
        # Import the 'CPolydorou.FileSystem' module
        if((get-module).Name -notcontains "cpolydorou.security")
        {
            try
            {
                Import-Module CPolydorou.FileSystem -ErrorAction Stop -Verbose:$false -DisableNameChecking
            }
            catch
            {
                Throw "Failed to import the 'CPolydorou.FileSystem' module. This module is required and has to be installed."
            }
        }
    }

    Process
    {
        # Verbose output
        Write-Verbose "Extracting server certificate from PFX file: $PFXFilePath"

        # Test the installation of OpenSSL
        $OpenSSLPath = Get-OpenSSLPath

        # Test the OpenSSL files
        Test-OpenSSLInstallation

        Write-Verbose "Found OpenSSL binary at: $OpenSSLPath"

        # Check if the private key file exists
        try
        {
            $privatekey = (Resolve-Path -Path $PrivateKeyFilePath -ErrorAction Stop).Path
        }
        catch
        {
            Throw "Could not locate the private key file."
        }

        # Check if the DestinationCertificateFilePath is null and create a path
        if(-Not $DestinationCertificateFilePath)
        {
            $base = (Get-Location).Path + "\"
            $filename = (Get-Item -Path $privatekey).Name + ".plainkey"
            $DestinationCertificateFile = $base + $filename
        }
        else
        {
            $DestinationCertificateFile = (Resolve-NonExistentPath -Path $DestinationCertificateFilePath).Fullname
        }

        # Extract the PFX passphrase
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($PrivateKeyPassphrase)
        $UnsecurePassphrase = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

        # Verbose output
        Write-Verbose "Saving exported key at: $DestinationCertificateFile"

        # Set the arguments
        $args = @("rsa", "-in", $privatekey, "-out", $DestinationCertificateFile, "-passin", "pass:$UnsecurePassphrase")

        # Convert the file
        try
        {
            $result = Convert -OpenSSLPath $OpenSSLPath -arguments $args

            if($OpenSSLOutput)
            {
                Write-Output $result
            }

            if($result.ExitCode -ne 0)
            {
                Write-Error "OpenSSL failed to perform the requested operation."
            }
        }
        catch
        {
            Write-Error ("Failed to invoke OpenSSL. " + $_.Exception.Message)
        }
    }

    End
    {
    }
}
#endregion
#endregion

#region Helper Functions
#region Get-OpenSSLPath
Function Get-OpenSSLPath
{
    # Get the path to the openssl.exe
    $SecurityModulePath = (Get-Module -Name CPolydorou.Security).Path
    $OpenSSLPath = $SecurityModulePath.Substring(0, $SecurityModulePath.Length - 25) + "\OpenSSL\openssl-1.0.2q-x64_86-win64\openssl.exe"

    return $OpenSSLPath
}
#endregion

#region Test-OpenSSLInstallation
Function Test-OpenSSLInstallation
{
    $OpenSSLPath = Get-OpenSSLPath

    # Test if OpenSSL binaries exist
    if( -Not (Test-Path ($OpenSSLPath)))
    {
        Write-Error "Could not locate openssl.exe!"
        return
    }
    if( -Not (Test-Path ($OpenSSLPath.ToLower().Replace("openssl.exe","libeay32.dll"))))
    {
        Write-Error "Could not locate libeay32.dll!"
        return
    }
    if( -Not (Test-Path ($OpenSSLPath.ToLower().Replace("openssl.exe","ssleay32.dll"))))
    {
        Write-Error "Could not locate ssleay32.dll!"
        return
    }
}
#endregion

#region Convert
function Convert
{
    Param
    (
        [string]$OpenSSLPath,
        [string[]]$Arguments
    )

    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $OpenSSLPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.CreateNoWindow = $true
    $pinfo.Arguments = $Arguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    New-Object -TypeName PSObject `
               -Property @{
                            "StandardOutput" = $stdout
                            "StandardError"  = $stderr
                            "ExitCode"       = $p.ExitCode
                         }
}

#endregion
#endregion

#region Exports
Export-ModuleMember -Function Export-ServerCertificateFromPFX
Export-ModuleMember -Function Export-CertificateChainFromPFX
Export-ModuleMember -Function Export-PrivateKeyFromPFX
Export-ModuleMember -Function Decrypt-PrivateKey
#endregion