mirror of
https://github.com/Grace-Solutions/Invoke-OpenSSHConfiguration.git
synced 2026-07-26 11:38:14 +00:00
Enhanced OpenSSH configuration with certificate auth, service management, and security features
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
This commit is contained in:
@@ -5,21 +5,81 @@ Function ConvertFrom-CertificateToPEM
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A brief overview of what your function does
|
||||
|
||||
Convert X.509 certificates to PEM format with support for certificate, public key, and private key extraction.
|
||||
|
||||
.DESCRIPTION
|
||||
Slightly more detailed description of what your function does
|
||||
|
||||
.PARAMETER ParameterName
|
||||
Your parameter 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:
|
||||
|
||||
.PARAMETER ParameterName
|
||||
Your parameter description
|
||||
- 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.
|
||||
|
||||
.PARAMETER ParameterName
|
||||
Your parameter description
|
||||
|
||||
.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')
|
||||
@@ -33,15 +93,31 @@ Function ConvertFrom-CertificateToPEM
|
||||
Write-Output -InputObject ($ConvertFromCertificateToPEMResult)
|
||||
|
||||
.EXAMPLE
|
||||
[String]$RootCALocation = 'Cert:\LocalMachine\My'
|
||||
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'
|
||||
|
||||
[String]$RootCAFriendlyName = 'Self Signed Root CA Certificate'
|
||||
|
||||
$DNSHostEntry = [System.Net.Dns]::GetHostEntry('LocalHost')
|
||||
|
||||
$NewSelfSignedCertificateParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$NewSelfSignedCertificateParameters.FriendlyName = $RootCAFriendlyName
|
||||
$NewSelfSignedCertificateParameters.Subject = "$($Env:ComputerName.ToUpper())"
|
||||
$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
|
||||
@@ -52,7 +128,6 @@ Function ConvertFrom-CertificateToPEM
|
||||
$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.TextExtension.Add('2.5.29.37={text}1.3.6.1.5.5.7.3.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]'
|
||||
@@ -67,41 +142,129 @@ Function ConvertFrom-CertificateToPEM
|
||||
$ConvertFromCertificateToPEMParameters.CertificateList = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]'
|
||||
$ConvertFromCertificateToPEMParameters.CertificateList.Add($SelfSignedCertificate)
|
||||
$ConvertFromCertificateToPEMParameters.Export = $True
|
||||
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:Userprofile)\Downloads\Certificates"
|
||||
$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
|
||||
Any useful tidbits
|
||||
|
||||
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
|
||||
Place any useful link here where your function or cmdlet can be referenced
|
||||
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
|
||||
)
|
||||
@@ -207,16 +370,24 @@ Function ConvertFrom-CertificateToPEM
|
||||
{
|
||||
[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)
|
||||
@@ -227,7 +398,7 @@ Function ConvertFrom-CertificateToPEM
|
||||
|
||||
$ExistingCertificateCount = ($ExistingCertificates | Measure-Object).Count
|
||||
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Located $($ExistingCertificateCount) certificate(s) for certificate serial number `"$($SerialNumber)`"."
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Located $($ExistingCertificateCount) certificate(s) for serial number `"$($SerialNumber)`"."
|
||||
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
||||
|
||||
Switch ($ExistingCertificateCount)
|
||||
@@ -238,13 +409,62 @@ Function ConvertFrom-CertificateToPEM
|
||||
}
|
||||
|
||||
{($_ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,6 +475,9 @@ Function ConvertFrom-CertificateToPEM
|
||||
$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
|
||||
{
|
||||
@@ -281,7 +504,14 @@ Function ConvertFrom-CertificateToPEM
|
||||
$X509Certificate2ListItem = $X509Certificate2List[$X509Certificate2ListIndex]
|
||||
|
||||
$OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$OutputObjectProperties.SerialNumber = $X509Certificate2ListItem.SerialNumber
|
||||
$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
|
||||
@@ -345,8 +575,13 @@ Function ConvertFrom-CertificateToPEM
|
||||
|
||||
$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)
|
||||
|
||||
@@ -393,48 +628,74 @@ Function ConvertFrom-CertificateToPEM
|
||||
}
|
||||
}
|
||||
|
||||
Switch ($X509Certificate2.Object.HasPrivateKey)
|
||||
#region Private Key Extraction (only if -IncludePrivateKey is specified)
|
||||
Switch ($IncludePrivateKey.IsPresent)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
Switch ($X509Certificate2.OID.FriendlyName)
|
||||
{
|
||||
Switch ($X509Certificate2.Object.HasPrivateKey)
|
||||
{
|
||||
{($_ -iin @('RSA'))}
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($X509Certificate2.Object)
|
||||
$X509Certificate2.CNG = [System.Security.Cryptography.RSACNG]($X509Certificate2.DSA)
|
||||
}
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to extract private key (as requested by -IncludePrivateKey)..."
|
||||
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
||||
|
||||
{($_ -iin @('ECC'))}
|
||||
{
|
||||
$X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDSAPrivateKey($X509Certificate2.Object)
|
||||
$X509Certificate2.CNG = [System.Security.Cryptography.ECDSACNG]($X509Certificate2.DSA)
|
||||
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 key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] is unsupported for private key extracted."
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a private key."
|
||||
Write-Warning -Message ($LoggingDetails.LogMessage)
|
||||
}
|
||||
}
|
||||
|
||||
$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()
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a private key. Please ensure that the private key is exportable."
|
||||
Write-Warning -Message ($LoggingDetails.LogMessage)
|
||||
$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
|
||||
@@ -514,7 +775,8 @@ Function ConvertFrom-CertificateToPEM
|
||||
}
|
||||
}
|
||||
|
||||
Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.PrivateKey) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.PrivateKey) -eq $False))
|
||||
#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)}
|
||||
{
|
||||
@@ -531,15 +793,12 @@ Function ConvertFrom-CertificateToPEM
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$OutputObjectProperties.Exports.PrivateKey = $WriteAllTextParameters.Path
|
||||
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Private key exported successfully."
|
||||
Write-Verbose -Message ($LoggingDetails.LogMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the private key from certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"."
|
||||
Write-Warning -Message ($LoggingDetails.LogMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,21 +828,74 @@ Function ConvertFrom-CertificateToPEM
|
||||
}
|
||||
|
||||
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
|
||||
#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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user