mirror of
https://github.com/Grace-Solutions/Invoke-OpenSSHConfiguration.git
synced 2026-07-26 11:38:14 +00:00
825dfdab79
Features added: - Certificate authentication with Windows CA trust bridge - CA filtering via regex inclusion/exclusion expressions - Password authentication disable option - Service auto-start and start management - Config backup before modification (timestamped) - Config validation (sshd -t) with automatic rollback on failure - SFTP subsystem configured by default - Public key management with deduplication - Key pair generation (ed25519, rsa, ecdsa) Toolkit enhancements: - ConvertFrom-CertificateToPEM: Added -IncludePrivateKey, -ThumbprintList, -CreateCABundle - Updated coding standards in PowershellScripts.md Documentation: - Comprehensive README with examples and troubleshooting - Added .augment folder to .gitignore
915 lines
64 KiB
PowerShell
915 lines
64 KiB
PowerShell
## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx
|
|
|
|
#region Function ConvertFrom-CertificateToPEM
|
|
Function ConvertFrom-CertificateToPEM
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Convert X.509 certificates to PEM format with support for certificate, public key, and private key extraction.
|
|
|
|
.DESCRIPTION
|
|
Extracts and converts X.509 certificates from the Windows certificate store to Privacy-Enhanced Mail (PEM) format.
|
|
This function supports both RSA and ECC (ECDSA) key algorithms and can extract:
|
|
|
|
- Certificate data (-----BEGIN CERTIFICATE-----/-----END CERTIFICATE-----)
|
|
- Public keys (-----BEGIN PUBLIC KEY-----/-----END PUBLIC KEY-----)
|
|
- Private keys (-----BEGIN PRIVATE KEY-----/-----END PRIVATE KEY-----)
|
|
|
|
The function is particularly useful for:
|
|
- Setting up certificate-based authentication for OpenSSH servers and clients
|
|
- Exporting certificates for use with Linux/Unix systems
|
|
- Creating CA trust chains for SSH host or user certificate authentication
|
|
- Converting Windows certificates for cross-platform applications
|
|
- Preparing certificates for web servers, APIs, or other TLS-enabled services
|
|
|
|
Certificates can be specified by serial number, thumbprint, or passed directly as X509Certificate2 objects.
|
|
When exporting, individual PEM files are created for each component, and optionally a combined CA bundle file
|
|
can be generated containing all certificates for trust chain scenarios.
|
|
|
|
.PARAMETER SerialNumberList
|
|
One or more certificate serial numbers to locate and convert. The function searches all certificate stores
|
|
(LocalMachine and CurrentUser) to find matching certificates. Serial numbers are case-insensitive.
|
|
This parameter belongs to the 'BySerialNumber' parameter set.
|
|
|
|
.PARAMETER ThumbprintList
|
|
One or more certificate thumbprints (SHA-1 hash) to locate and convert. The function searches all certificate
|
|
stores to find matching certificates. Thumbprints are 40 hexadecimal characters and are case-insensitive.
|
|
This parameter belongs to the 'ByThumbprint' parameter set.
|
|
|
|
.PARAMETER CertificateList
|
|
One or more X509Certificate2 objects to convert. Use this when you already have certificate objects in memory,
|
|
such as from Get-ChildItem Cert:\ or New-SelfSignedCertificate.
|
|
This parameter belongs to the 'ByCertificate' parameter set.
|
|
|
|
.PARAMETER Export
|
|
When specified, exports the PEM data to files on disk. Without this switch, only the in-memory PEM blobs
|
|
are returned in the output object. Each certificate's components are exported to a subdirectory named
|
|
by serial number under the ExportRootDirectory.
|
|
|
|
.PARAMETER ExportRootDirectory
|
|
The root directory where exported PEM files will be saved. Each certificate creates a subdirectory
|
|
named by its serial number containing Certificate.pem, PublicKey.pem, and PrivateKey.pem files.
|
|
Defaults to '$Env:Public\Documents\ConvertFrom-CertificateToPEM' if not specified.
|
|
|
|
.PARAMETER CreateCABundle
|
|
When specified along with -Export, creates a combined CA bundle file (ca-bundle.pem) containing all
|
|
processed certificates. This is useful for creating trust chains for OpenSSH TrustedUserCAKeys or
|
|
TrustedHostCAKeys configuration, or for other applications that need a single file with multiple
|
|
CA certificates.
|
|
|
|
.PARAMETER CABundleFileName
|
|
The filename for the combined CA bundle when -CreateCABundle is specified.
|
|
Defaults to 'ca-bundle.pem' if not specified.
|
|
|
|
.PARAMETER IncludePrivateKey
|
|
When specified, attempts to extract and export the private key if the certificate has an exportable
|
|
private key. By default, private keys are NOT exported for security reasons.
|
|
|
|
For OpenSSH certificate authentication, private keys are typically NOT needed on the server side -
|
|
only the CA's public certificate is required for TrustedUserCAKeys or TrustedHostCAKeys.
|
|
|
|
Only use this switch when you specifically need the private key, such as:
|
|
- Backing up a CA certificate with its private key
|
|
- Migrating certificates to another system
|
|
- Setting up client-side identity keys
|
|
|
|
.PARAMETER ContinueOnError
|
|
When specified, continues processing remaining certificates if an error occurs with one certificate.
|
|
Without this switch, the first error terminates the function.
|
|
|
|
.EXAMPLE
|
|
Export a certificate by serial number for use with OpenSSH user authentication.
|
|
|
|
$ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ConvertFromCertificateToPEMParameters.SerialNumberList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
$ConvertFromCertificateToPEMParameters.SerialNumberList.Add('3d05e1f73beb5d3cd1be2b9f8d96f6c323260f78')
|
|
$ConvertFromCertificateToPEMParameters.Export = $True
|
|
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:Userprofile)\Downloads\Certificates"
|
|
$ConvertFromCertificateToPEMParameters.ContinueOnError = $False
|
|
$ConvertFromCertificateToPEMParameters.Verbose = $True
|
|
|
|
$ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters
|
|
|
|
Write-Output -InputObject ($ConvertFromCertificateToPEMResult)
|
|
|
|
.EXAMPLE
|
|
Export a certificate by thumbprint.
|
|
|
|
$ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ConvertFromCertificateToPEMParameters.ThumbprintList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
$ConvertFromCertificateToPEMParameters.ThumbprintList.Add('A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2')
|
|
$ConvertFromCertificateToPEMParameters.Export = $True
|
|
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:ProgramData)\OpenSSH\Certificates"
|
|
$ConvertFromCertificateToPEMParameters.ContinueOnError = $False
|
|
$ConvertFromCertificateToPEMParameters.Verbose = $True
|
|
|
|
$ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters
|
|
|
|
Write-Output -InputObject ($ConvertFromCertificateToPEMResult)
|
|
|
|
.EXAMPLE
|
|
Create a self-signed CA certificate and export it for OpenSSH TrustedUserCAKeys configuration.
|
|
|
|
[String]$RootCALocation = 'Cert:\LocalMachine\My'
|
|
[String]$RootCAFriendlyName = 'SSH User CA Certificate'
|
|
|
|
$DNSHostEntry = [System.Net.Dns]::GetHostEntry('LocalHost')
|
|
|
|
$NewSelfSignedCertificateParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$NewSelfSignedCertificateParameters.FriendlyName = $RootCAFriendlyName
|
|
$NewSelfSignedCertificateParameters.Subject = "CN=SSH User CA"
|
|
$NewSelfSignedCertificateParameters.DnsName = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
$NewSelfSignedCertificateParameters.DnsName.Add($DNSHostEntry.HostName)
|
|
$NewSelfSignedCertificateParameters.NotBefore = (Get-Date).Date
|
|
$NewSelfSignedCertificateParameters.NotAfter = $NewSelfSignedCertificateParameters.NotBefore.Date.AddYears(10)
|
|
$NewSelfSignedCertificateParameters.CertStoreLocation = $RootCALocation
|
|
$NewSelfSignedCertificateParameters.KeyExportPolicy = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyexportpolicy]'
|
|
$NewSelfSignedCertificateParameters.KeyExportPolicy.Add('Exportable')
|
|
$NewSelfSignedCertificateParameters.Provider = 'Microsoft Enhanced RSA and AES Cryptographic Provider'
|
|
$NewSelfSignedCertificateParameters.TextExtension = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
$NewSelfSignedCertificateParameters.TextExtension.Add('2.5.29.19={text}CA=1&pathlength=3')
|
|
$NewSelfSignedCertificateParameters.KeyUsageProperty = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyusageproperty]'
|
|
$NewSelfSignedCertificateParameters.KeyUsageProperty.Add('All')
|
|
$NewSelfSignedCertificateParameters.KeyUsage = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyusage]'
|
|
$NewSelfSignedCertificateParameters.KeyUsage.Add('CertSign')
|
|
$NewSelfSignedCertificateParameters.KeyUsage.Add('CRLSign')
|
|
$NewSelfSignedCertificateParameters.KeyUsage.Add('DigitalSignature')
|
|
$NewSelfSignedCertificateParameters.Confirm = $False
|
|
|
|
$SelfSignedCertificate = New-SelfSignedCertificate @NewSelfSignedCertificateParameters
|
|
|
|
$ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ConvertFromCertificateToPEMParameters.CertificateList = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]'
|
|
$ConvertFromCertificateToPEMParameters.CertificateList.Add($SelfSignedCertificate)
|
|
$ConvertFromCertificateToPEMParameters.Export = $True
|
|
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:ProgramData)\ssh"
|
|
$ConvertFromCertificateToPEMParameters.ContinueOnError = $False
|
|
$ConvertFromCertificateToPEMParameters.Verbose = $True
|
|
|
|
$ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters
|
|
|
|
# The Certificate.pem file can be used in sshd_config as:
|
|
# TrustedUserCAKeys C:\ProgramData\ssh\<SerialNumber>\Certificate.pem
|
|
|
|
Write-Output -InputObject ($ConvertFromCertificateToPEMResult)
|
|
|
|
.EXAMPLE
|
|
Export all unexpired Root CAs and Intermediate CAs (Sub-CAs) to the machine SSH trust location without private keys.
|
|
This is the recommended approach for configuring OpenSSH TrustedUserCAKeys or TrustedHostCAKeys.
|
|
|
|
# Collect all unexpired CA certificates from both Root and Intermediate CA stores
|
|
$CACertificateList = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]'
|
|
|
|
# Get unexpired Root CAs (Cert:\LocalMachine\Root)
|
|
$RootCAs = Get-ChildItem -Path 'Cert:\LocalMachine\Root' | Where-Object { ($_.NotAfter -gt (Get-Date)) -and ($_.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'Basic Constraints' -and $_.CertificateAuthority }) }
|
|
$CACertificateList.AddRange([System.Security.Cryptography.X509Certificates.X509Certificate2[]]$RootCAs)
|
|
|
|
# Get unexpired Intermediate/Sub CAs (Cert:\LocalMachine\CA)
|
|
$IntermediateCAs = Get-ChildItem -Path 'Cert:\LocalMachine\CA' | Where-Object { ($_.NotAfter -gt (Get-Date)) -and ($_.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'Basic Constraints' -and $_.CertificateAuthority }) }
|
|
$CACertificateList.AddRange([System.Security.Cryptography.X509Certificates.X509Certificate2[]]$IntermediateCAs)
|
|
|
|
# Export to the standard OpenSSH machine configuration directory
|
|
# Note: -IncludePrivateKey is NOT specified, so only public certificates are exported (secure default)
|
|
$ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ConvertFromCertificateToPEMParameters.CertificateList = $CACertificateList
|
|
$ConvertFromCertificateToPEMParameters.Export = $True
|
|
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:ProgramData)\ssh"
|
|
$ConvertFromCertificateToPEMParameters.CreateCABundle = $True
|
|
$ConvertFromCertificateToPEMParameters.CABundleFileName = 'OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem'
|
|
$ConvertFromCertificateToPEMParameters.ContinueOnError = $False
|
|
$ConvertFromCertificateToPEMParameters.Verbose = $True
|
|
|
|
$ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters
|
|
|
|
# The combined CA bundle can be referenced in sshd_config as:
|
|
# TrustedUserCAKeys C:\ProgramData\ssh\OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem
|
|
#
|
|
# For host certificate verification:
|
|
# TrustedHostCAKeys C:\ProgramData\ssh\OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem
|
|
|
|
Write-Output -InputObject ($ConvertFromCertificateToPEMResult)
|
|
|
|
.NOTES
|
|
OpenSSH Certificate Authentication:
|
|
- For TrustedUserCAKeys: Export the CA certificate and reference the Certificate.pem file
|
|
- For TrustedHostCAKeys: Same approach for host certificate verification
|
|
- The CA's private key is NOT needed on the SSH server - only the public certificate
|
|
- The CA bundle option (-CreateCABundle) is useful when multiple CAs can sign user/host certificates
|
|
- Machine trust location on Windows: $Env:ProgramData\ssh (typically C:\ProgramData\ssh)
|
|
|
|
Certificate Expiration and Validity:
|
|
- OpenSSH does NOT accept expired certificates for authentication
|
|
- Certificates must have valid NotBefore and NotAfter dates at the time of authentication
|
|
- If a CA certificate expires, all user/host certificates signed by that CA become invalid
|
|
- Plan certificate renewal before expiration to avoid authentication failures
|
|
- Use the output properties NotBefore and NotAfter to monitor certificate validity
|
|
|
|
Private Key Security:
|
|
- By default, private keys are NOT exported for security reasons
|
|
- Use -IncludePrivateKey only when you specifically need to export private keys
|
|
- Private keys must be marked as exportable in the certificate store to be extracted
|
|
- Access to private keys may require administrative privileges
|
|
- Keep private keys secure and never expose them unnecessarily
|
|
|
|
Key Algorithm Support:
|
|
- RSA: Fully supported for certificate, public key, and private key extraction
|
|
- ECC/ECDSA: Fully supported for certificate, public key, and private key extraction
|
|
- Other algorithms may have limited support
|
|
|
|
PEM File Encoding:
|
|
- All PEM files are written with ASCII encoding for maximum compatibility
|
|
- Line endings use the platform default (CRLF on Windows)
|
|
|
|
.LINK
|
|
https://man.openbsd.org/sshd_config#TrustedUserCAKeys
|
|
|
|
.LINK
|
|
https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement
|
|
|
|
.LINK
|
|
https://www.rfc-editor.org/rfc/rfc7468 (Textual Encodings of PKIX, PKCS, and CMS Structures)
|
|
#>
|
|
|
|
[CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'BySerialNumber')]
|
|
|
|
Param
|
|
(
|
|
[Parameter(Mandatory=$True, ParameterSetName = 'BySerialNumber')]
|
|
[ValidateNotNullOrEmpty()]
|
|
[String[]]$SerialNumberList,
|
|
|
|
[Parameter(Mandatory=$True, ParameterSetName = 'ByThumbprint')]
|
|
[ValidateNotNullOrEmpty()]
|
|
[ValidatePattern('^[A-Fa-f0-9]{40}$')]
|
|
[String[]]$ThumbprintList,
|
|
|
|
[Parameter(Mandatory=$True, ParameterSetName = 'ByCertificate')]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Security.Cryptography.X509Certificates.X509Certificate2[]]$CertificateList,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$Export,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowEmptyString()]
|
|
[AllowNull()]
|
|
[System.IO.DirectoryInfo]$ExportRootDirectory,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$CreateCABundle,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[String]$CABundleFileName,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$IncludePrivateKey,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$ContinueOnError
|
|
)
|
|
|
|
Begin
|
|
{
|
|
|
|
|
|
Try
|
|
{
|
|
$DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM###
|
|
[ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)}
|
|
$DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347###
|
|
[ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)}
|
|
$DateFileFormat = 'yyyyMMdd' ###20190403###
|
|
[ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)}
|
|
$DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354###
|
|
[ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
|
|
$TextInfo = (Get-Culture).TextInfo
|
|
$LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$LoggingDetails.Add('LogMessage', $Null)
|
|
$LoggingDetails.Add('WarningMessage', $Null)
|
|
$LoggingDetails.Add('ErrorMessage', $Null)
|
|
$CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
$CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters)
|
|
$CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters)
|
|
|
|
[ScriptBlock]$ErrorHandlingDefinition = {
|
|
Param
|
|
(
|
|
[Int16]$Severity,
|
|
[Boolean]$ContinueOnError
|
|
)
|
|
|
|
$ExceptionPropertyDictionary = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ExceptionPropertyDictionary.Message = $_.Exception.Message
|
|
$ExceptionPropertyDictionary.Category = $_.Exception.ErrorRecord.FullyQualifiedErrorID
|
|
$ExceptionPropertyDictionary.Script = [System.IO.Path]::GetFileName($_.InvocationInfo.ScriptName)
|
|
$ExceptionPropertyDictionary.LineNumber = $_.InvocationInfo.ScriptLineNumber
|
|
$ExceptionPropertyDictionary.LinePosition = $_.InvocationInfo.OffsetInLine
|
|
$ExceptionPropertyDictionary.Code = $_.InvocationInfo.Line.Trim()
|
|
|
|
$ExceptionMessageList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
ForEach ($ExceptionProperty In $ExceptionPropertyDictionary.GetEnumerator())
|
|
{
|
|
$ExceptionMessageList.Add("[$($ExceptionProperty.Key): $($ExceptionProperty.Value)]")
|
|
}
|
|
|
|
$LogMessageParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$LogMessageParameters.Message = $ExceptionMessageList -Join ' '
|
|
$LogMessageParameters.Verbose = $True
|
|
|
|
Switch ($Severity)
|
|
{
|
|
{($_ -in @(1))} {Write-Verbose @LogMessageParameters}
|
|
{($_ -in @(2))} {Write-Warning @LogMessageParameters}
|
|
{($_ -in @(3))} {Write-Error @LogMessageParameters}
|
|
}
|
|
|
|
Switch ($ContinueOnError)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
Throw
|
|
}
|
|
}
|
|
}
|
|
|
|
#Determine the date and time we executed the function
|
|
$FunctionStartTime = (Get-Date)
|
|
|
|
[String]$FunctionName = $MyInvocation.MyCommand
|
|
[System.IO.FileInfo]$InvokingScriptPath = $MyInvocation.PSCommandPath
|
|
[System.IO.DirectoryInfo]$InvokingScriptDirectory = $InvokingScriptPath.Directory.FullName
|
|
[System.IO.FileInfo]$FunctionPath = "$($InvokingScriptDirectory.FullName)\Functions\$($FunctionName).ps1"
|
|
[System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory.FullName)"
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is beginning. Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
#Define Default Action Preferences
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
[String[]]$AvailableScriptParameters = (Get-Command -Name ($FunctionName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"}
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
[String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {Try {"-$($_.Key):$($_.Value.GetType().Name)"} Catch {"-$($_.Key):Unknown"}}
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
#Create an object that will contain the functions output.
|
|
$OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
|
|
|
|
#Set default parameter value(s)
|
|
Switch ($True)
|
|
{
|
|
{([String]::IsNullOrEmpty($ExportRootDirectory) -eq $True) -or ([String]::IsNullOrWhiteSpace($ExportRootDirectory) -eq $True)}
|
|
{
|
|
[System.IO.DirectoryInfo]$ExportRootDirectory = "$($Env:Public)\Documents\$($FunctionName)"
|
|
}
|
|
|
|
{([String]::IsNullOrEmpty($CABundleFileName) -eq $True) -or ([String]::IsNullOrWhiteSpace($CABundleFileName) -eq $True)}
|
|
{
|
|
[String]$CABundleFileName = 'ca-bundle.pem'
|
|
}
|
|
}
|
|
|
|
$X509Certificate2List = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]'
|
|
|
|
#List to collect all certificate PEM blobs for CA bundle creation
|
|
$CABundleCertificateBlobs = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
|
|
Switch ($PSCmdlet.ParameterSetName)
|
|
{
|
|
{($_ -iin @('BySerialNumber'))}
|
|
{
|
|
$ExistingCertificateList = Get-ChildItem -Path @('Microsoft.PowerShell.Security\Certificate::\') -Recurse -Force -ErrorAction SilentlyContinue | Where-Object {($Null -ine $_.SerialNumber)}
|
|
|
|
ForEach ($SerialNumber In $SerialNumberList)
|
|
{
|
|
Switch ($ExistingCertificateList.SerialNumber -icontains $SerialNumber)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ExistingCertificates = $ExistingCertificateList | Where-Object {($_.SerialNumber -ieq $SerialNumber)} | Sort-Object -Property @('NotAfter') -Descending -Unique
|
|
|
|
$ExistingCertificateCount = ($ExistingCertificates | Measure-Object).Count
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Located $($ExistingCertificateCount) certificate(s) for serial number `"$($SerialNumber)`"."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
Switch ($ExistingCertificateCount)
|
|
{
|
|
{($_ -eq 1)}
|
|
{
|
|
$X509Certificate2List.Add($ExistingCertificates)
|
|
}
|
|
|
|
{($_ -gt 1)}
|
|
{
|
|
ForEach ($ExistingCertificate In $ExistingCertificates)
|
|
{
|
|
$X509Certificate2List.Add($ExistingCertificate)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - No certificate found with serial number `"$($SerialNumber)`"."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{($_ -iin @('ByThumbprint'))}
|
|
{
|
|
$ExistingCertificateList = Get-ChildItem -Path @('Microsoft.PowerShell.Security\Certificate::\') -Recurse -Force -ErrorAction SilentlyContinue | Where-Object {($Null -ine $_.Thumbprint)}
|
|
|
|
ForEach ($Thumbprint In $ThumbprintList)
|
|
{
|
|
Switch ($ExistingCertificateList.Thumbprint -icontains $Thumbprint)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ExistingCertificates = $ExistingCertificateList | Where-Object {($_.Thumbprint -ieq $Thumbprint)} | Sort-Object -Property @('NotAfter') -Descending -Unique
|
|
|
|
$ExistingCertificateCount = ($ExistingCertificates | Measure-Object).Count
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Located $($ExistingCertificateCount) certificate(s) for thumbprint `"$($Thumbprint)`"."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
Switch ($ExistingCertificateCount)
|
|
{
|
|
{($_ -eq 1)}
|
|
{
|
|
$X509Certificate2List.Add($ExistingCertificates)
|
|
}
|
|
|
|
{($_ -gt 1)}
|
|
{
|
|
ForEach ($ExistingCertificate In $ExistingCertificates)
|
|
{
|
|
$X509Certificate2List.Add($ExistingCertificate)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - No certificate found with thumbprint `"$($Thumbprint)`"."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{($_ -iin @('ByCertificate'))}
|
|
{
|
|
$CertificateList | Sort-Object -Property @('NotAfter') -Descending -Unique | ForEach-Object {($X509Certificate2List.Add($_))}
|
|
}
|
|
}
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Total certificates to process: $($X509Certificate2List.Count)"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
Catch
|
|
{
|
|
$ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent)
|
|
}
|
|
Finally
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
Process
|
|
{
|
|
Try
|
|
{
|
|
$X509Certificate2ListCounter = 1
|
|
|
|
$X509Certificate2ListCount = ($X509Certificate2List | Measure-Object).Count
|
|
|
|
For ($X509Certificate2ListIndex = 0; $X509Certificate2ListIndex -lt $X509Certificate2ListCount; $X509Certificate2ListIndex++)
|
|
{
|
|
Try
|
|
{
|
|
$X509Certificate2ListItem = $X509Certificate2List[$X509Certificate2ListIndex]
|
|
|
|
$OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$OutputObjectProperties.SerialNumber = $X509Certificate2ListItem.SerialNumber
|
|
$OutputObjectProperties.Thumbprint = $X509Certificate2ListItem.Thumbprint
|
|
$OutputObjectProperties.Subject = $X509Certificate2ListItem.Subject
|
|
$OutputObjectProperties.Issuer = $X509Certificate2ListItem.Issuer
|
|
$OutputObjectProperties.NotBefore = $X509Certificate2ListItem.NotBefore
|
|
$OutputObjectProperties.NotAfter = $X509Certificate2ListItem.NotAfter
|
|
$OutputObjectProperties.KeyAlgorithm = $Null
|
|
$OutputObjectProperties.HasPrivateKey = $X509Certificate2ListItem.HasPrivateKey
|
|
$OutputObjectProperties.Blobs = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$OutputObjectProperties.Blobs.Certificate = $Null
|
|
$OutputObjectProperties.Blobs.PublicKey = $Null
|
|
$OutputObjectProperties.Blobs.PrivateKey = $Null
|
|
$OutputObjectProperties.Exports = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$OutputObjectProperties.Exports.Certificate = $Null
|
|
$OutputObjectProperties.Exports.PublicKey = $Null
|
|
$OutputObjectProperties.Exports.PrivateKey = $Null
|
|
$OutputObjectProperties.Certificate = $X509Certificate2ListItem
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to process certificate `"$($X509Certificate2ListItem.SerialNumber)`". Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Issuer: $($X509Certificate2ListItem.Issuer)"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Thumbprint: $($X509Certificate2ListItem.Thumbprint)"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Serial Number: $($X509Certificate2ListItem.SerialNumber)"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - DNS Name List: $($X509Certificate2ListItem.DnsNameList -Join ', ')"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Not Before: $($X509Certificate2ListItem.NotBefore.ToString($DateTimeLogFormat))"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Not After: $($X509Certificate2ListItem.NotAfter.ToString($DateTimeLogFormat))"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$StringBuilders = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$StringBuilders.Certificate = New-Object -TypeName 'System.Text.StringBuilder'
|
|
$StringBuilders.PrivateKey = New-Object -TypeName 'System.Text.StringBuilder'
|
|
$StringBuilders.PublicKey = New-Object -TypeName 'System.Text.StringBuilder'
|
|
|
|
$X509KeyStorageFlags = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$X509KeyStorageFlags.Valid = [System.Enum]::GetNames('System.Security.Cryptography.X509Certificates.X509KeyStorageFlags') | Sort-Object
|
|
$X509KeyStorageFlags.Specified = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]'
|
|
$X509KeyStorageFlags.Specified.Add('Exportable')
|
|
|
|
$X509Certificate2 = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$X509Certificate2.Object = $X509Certificate2ListItem
|
|
$X509Certificate2.ObjectEncoded = $Null
|
|
$X509Certificate2.ContentType = $Null
|
|
$X509Certificate2.OID = $Null
|
|
$X509Certificate2.DSA = $Null
|
|
$X509Certificate2.CNG = $Null
|
|
$X509Certificate2.PublicKey = $Null
|
|
$X509Certificate2.PublicKeyEncoded = $Null
|
|
$X509Certificate2.PrivateKey = $Null
|
|
$X509Certificate2.PrivateKeyEncoded = $Null
|
|
|
|
$X509Certificate2.ContentType = [System.Security.Cryptography.X509Certificates.X509Certificate2]::GetCertContentType($X509Certificate2.Object.RawData)
|
|
|
|
$X509Certificate2.ObjectEncoded = [System.Convert]::ToBase64String($X509Certificate2.Object.RawData, [System.Base64FormattingOptions]::InsertLineBreaks)
|
|
|
|
$Null = $StringBuilders.Certificate.Append('-----BEGIN CERTIFICATE-----').AppendLine()
|
|
$Null = $StringBuilders.Certificate.Append($X509Certificate2.ObjectEncoded).AppendLine()
|
|
$Null = $StringBuilders.Certificate.Append('-----END CERTIFICATE-----')
|
|
|
|
$OutputObjectProperties.Blobs.Certificate = $StringBuilders.Certificate.ToString()
|
|
|
|
#Add to CA bundle collection for combined export
|
|
$CABundleCertificateBlobs.Add($OutputObjectProperties.Blobs.Certificate)
|
|
|
|
$X509Certificate2.OID = [System.Security.Cryptography.OID]::New($X509Certificate2.Object.GetKeyAlgorithm())
|
|
|
|
$OutputObjectProperties.KeyAlgorithm = $X509Certificate2.OID.FriendlyName
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] was detected."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
Switch ($Null -ine $X509Certificate2.Object.PublicKey)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Switch ($X509Certificate2.OID.FriendlyName)
|
|
{
|
|
{($_ -iin @('RSA'))}
|
|
{
|
|
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($X509Certificate2.Object)
|
|
$X509Certificate2.CNG = [System.Security.Cryptography.RSACNG]($X509Certificate2.DSA)
|
|
}
|
|
|
|
{($_ -iin @('ECC'))}
|
|
{
|
|
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.ECDSACertificateExtensions]::GetECDSAPublicKey($X509Certificate2.Object)
|
|
$X509Certificate2.CNG = [System.Security.Cryptography.ECDSACNG]($X509Certificate2.DSA)
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] is unsupported for public key extraction."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
|
|
$X509Certificate2.PublicKey = $X509Certificate2.CNG.Key
|
|
|
|
$X509Certificate2.PublicKeyEncoded = [System.Convert]::ToBase64String(($X509Certificate2.PublicKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::GenericPublicBlob)), [System.Base64FormattingOptions]::InsertLineBreaks)
|
|
|
|
$Null = $StringBuilders.PublicKey.Append('-----BEGIN PUBLIC KEY-----').AppendLine()
|
|
$Null = $StringBuilders.PublicKey.Append($X509Certificate2.PublicKeyEncoded).AppendLine()
|
|
$Null = $StringBuilders.PublicKey.Append('-----END PUBLIC KEY-----')
|
|
|
|
$OutputObjectProperties.Blobs.PublicKey = $StringBuilders.PublicKey.ToString()
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a public key."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
|
|
#region Private Key Extraction (only if -IncludePrivateKey is specified)
|
|
Switch ($IncludePrivateKey.IsPresent)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Switch ($X509Certificate2.Object.HasPrivateKey)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to extract private key (as requested by -IncludePrivateKey)..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
Switch ($X509Certificate2.OID.FriendlyName)
|
|
{
|
|
{($_ -iin @('RSA'))}
|
|
{
|
|
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($X509Certificate2.Object)
|
|
$X509Certificate2.CNG = [System.Security.Cryptography.RSACNG]($X509Certificate2.DSA)
|
|
}
|
|
|
|
{($_ -iin @('ECC'))}
|
|
{
|
|
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDSAPrivateKey($X509Certificate2.Object)
|
|
$X509Certificate2.CNG = [System.Security.Cryptography.ECDSACNG]($X509Certificate2.DSA)
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] is unsupported for private key extraction."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
|
|
Switch ($Null -ine $X509Certificate2.CNG)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$X509Certificate2.PrivateKey = $X509Certificate2.CNG.Key
|
|
|
|
$X509Certificate2.PrivateKeyEncoded = [System.Convert]::ToBase64String(($X509Certificate2.PrivateKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::PKCS8PrivateBlob)), [System.Base64FormattingOptions]::InsertLineBreaks)
|
|
|
|
$Null = $StringBuilders.PrivateKey.Append('-----BEGIN PRIVATE KEY-----').AppendLine()
|
|
$Null = $StringBuilders.PrivateKey.Append($X509Certificate2.PrivateKeyEncoded).AppendLine()
|
|
$Null = $StringBuilders.PrivateKey.Append('-----END PRIVATE KEY-----')
|
|
|
|
$OutputObjectProperties.Blobs.PrivateKey = $StringBuilders.PrivateKey.ToString()
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Private key successfully extracted."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a private key."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Skipping private key extraction (use -IncludePrivateKey to export private keys)."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$WriteAllTextParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$WriteAllTextParameters.Path = $Null
|
|
$WriteAllTextParameters.Contents = $Null
|
|
$WriteAllTextParameters.Encoding = [System.Text.Encoding]::ASCII
|
|
|
|
Switch ($Export.IsPresent)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$X509Certificate2.ExportDirectory = "$($ExportRootDirectory.FullName)\$($X509Certificate2ListItem.SerialNumber)" -As [System.IO.DirectoryInfo]
|
|
|
|
Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.Certificate) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.Certificate) -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Switch ($True)
|
|
{
|
|
{([System.IO.Directory]::Exists($X509Certificate2.ExportDirectory.FullName) -eq $False)}
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to create directory `"$($X509Certificate2.ExportDirectory.FullName)`". Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.Directory]::CreateDirectory($X509Certificate2.ExportDirectory.FullName)
|
|
}
|
|
}
|
|
|
|
$WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\Certificate.pem"
|
|
$WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.Certificate
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the certificate to `"$($WriteAllTextParameters.Path)`". Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding)
|
|
|
|
Switch ($?)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$OutputObjectProperties.Exports.Certificate = $WriteAllTextParameters.Path
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the certificate from `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
|
|
Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.PublicKey) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.PublicKey) -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\PublicKey.pem"
|
|
$WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.PublicKey
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the public key to `"$($WriteAllTextParameters.Path)`". Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding)
|
|
|
|
Switch ($?)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$OutputObjectProperties.Exports.PublicKey = $WriteAllTextParameters.Path
|
|
}
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the public key from certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"."
|
|
Write-Warning -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
|
|
#Only export private key if -IncludePrivateKey was specified and blob was successfully extracted
|
|
Switch (($IncludePrivateKey.IsPresent) -and (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.PrivateKey) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.PrivateKey) -eq $False)))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\PrivateKey.pem"
|
|
$WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.PrivateKey
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the private key to `"$($WriteAllTextParameters.Path)`". Please Wait..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding)
|
|
|
|
Switch ($?)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$OutputObjectProperties.Exports.PrivateKey = $WriteAllTextParameters.Path
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Private key exported successfully."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Catch
|
|
{
|
|
$ErrorHandlingDefinition.Invoke(2, $True)
|
|
}
|
|
Finally
|
|
{
|
|
$OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties)
|
|
|
|
$OutputObjectList.Add($OutputObject)
|
|
|
|
$X509Certificate2ListCounter++
|
|
}
|
|
}
|
|
}
|
|
Catch
|
|
{
|
|
$ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent)
|
|
}
|
|
Finally
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
End
|
|
{
|
|
Try
|
|
{
|
|
#region Create CA Bundle if requested
|
|
Switch (($Export.IsPresent) -and ($CreateCABundle.IsPresent) -and ($CABundleCertificateBlobs.Count -gt 0))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$CABundlePath = "$($ExportRootDirectory.FullName)\$($CABundleFileName)" -As [System.IO.FileInfo]
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Creating CA bundle with $($CABundleCertificateBlobs.Count) certificate(s)..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
#Ensure export directory exists
|
|
Switch ([System.IO.Directory]::Exists($ExportRootDirectory.FullName))
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Creating directory `"$($ExportRootDirectory.FullName)`"..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.Directory]::CreateDirectory($ExportRootDirectory.FullName)
|
|
}
|
|
}
|
|
|
|
#Combine all certificate blobs with line breaks between them
|
|
$CABundleContent = $CABundleCertificateBlobs -Join "`r`n`r`n"
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export CA bundle to `"$($CABundlePath.FullName)`"..."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
$Null = [System.IO.File]::WriteAllText($CABundlePath.FullName, $CABundleContent, [System.Text.Encoding]::ASCII)
|
|
|
|
Switch ($?)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - CA bundle successfully exported to `"$($CABundlePath.FullName)`"."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
|
|
|
#Add CA bundle info to output
|
|
$CABundleOutputProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$CABundleOutputProperties.Type = 'CABundle'
|
|
$CABundleOutputProperties.Path = $CABundlePath.FullName
|
|
$CABundleOutputProperties.CertificateCount = $CABundleCertificateBlobs.Count
|
|
$CABundleOutputProperties.Content = $CABundleContent
|
|
|
|
$CABundleOutput = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($CABundleOutputProperties)
|
|
|
|
$OutputObjectList.Add($CABundleOutput)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#Determine the date and time the function completed execution
|
|
$FunctionEndTime = (Get-Date)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
#Log the total script execution time
|
|
$FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime)
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)"
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
|
|
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is completed."
|
|
Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose
|
|
}
|
|
Catch
|
|
{
|
|
$ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent)
|
|
}
|
|
Finally
|
|
{
|
|
#Write the object to the powershell pipeline
|
|
$OutputObjectList = $OutputObjectList.ToArray()
|
|
|
|
Write-Output -InputObject ($OutputObjectList)
|
|
}
|
|
}
|
|
}
|
|
#endregion |