Skip to content

Commit

Permalink
Bug fixing and script polishing
Browse files Browse the repository at this point in the history
  • Loading branch information
Crypt32 committed Sep 21, 2023
1 parent a0f4524 commit 73d9962
Showing 1 changed file with 56 additions and 36 deletions.
92 changes: 56 additions & 36 deletions PSPKI/Client/Convert-PemToPfx.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
[string]$OutputPath,
[SysadminsLV.PKI.Cryptography.X509Certificates.X509KeySpecFlags]$KeySpec = "AT_KEYEXCHANGE",
[Security.SecureString]$Password,
[string]$ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider",
[string]$ProviderName = "Microsoft Software Key Storage Provider",
[Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser",
[switch]$Install
)
if ($PSBoundParameters.Verbose) {$VerbosePreference = "continue"}
if ($PSBoundParameters.Debug) {$DebugPreference = "continue"}
if ($PSBoundParameters["Verbose"]) {$VerbosePreference = "continue"}
if ($PSBoundParameters["Debug"]) {$DebugPreference = "continue"}
$ErrorActionPreference = "Stop"

# PSCore needs extra assembly import
Expand All @@ -26,6 +26,7 @@
}

# global variables
Write-Verbose "Determining key storage flags..."
$KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
if ($Install) {
if ($StoreLocation -eq "CurrentUser") {
Expand All @@ -36,112 +37,131 @@
} else {
$KeyStorageFlags = $KeyStorageFlags -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet
}
$Cert = $null
$AlgFamily = "RSA"
$AsymmetricKey
$Disposables = @()
Write-Verbose "Resulting storage flags: $KeyStorageFlags"

# returns: void
$script:AlgFamily = ""
[System.Collections.Generic.List[object]]$Disposables = @()

# returns: X509Certificate2
function __extractCert([string]$Text) {
Write-Verbose "Reading certificate.."
if ($Text -match "(?msx).*-{5}BEGIN\sCERTIFICATE-{5}(.+)-{5}END\sCERTIFICATE-{5}") {
$CertRawData = [Convert]::FromBase64String($matches[1])
$Cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList (,$CertRawData)
Write-Verbose "Public key algorithm: $($Cert.PublicKey.Oid.FriendlyName) ($($Cert.PublicKey.Oid.Value))"
switch ($Cert.PublicKey.Oid.Value) {
$([SysadminsLV.PKI.Cryptography.AlgorithmOid]::RSA) { $AlgFamily = "RSA" }
$([SysadminsLV.PKI.Cryptography.AlgorithmOid]::ECC) { $AlgFamily = "ECC" }
$([SysadminsLV.PKI.Cryptography.AlgorithmOid]::RSA) { $script:AlgFamily = "RSA" }
$([SysadminsLV.PKI.Cryptography.AlgorithmOid]::ECC) { $script:AlgFamily = "ECC" }
}

$Cert
} else {
throw "Missing certificate file."
}
}
# returns: void
# returns: AsymmetricKey (RSACng or ECDsaCng)
function __extractPrivateKey([string]$Text) {
Write-Verbose "Reading private key..."
$bin = if ($Text -match "(?msx).*-{5}BEGIN\sPRIVATE\sKEY-{5}(.+)-{5}END\sPRIVATE\sKEY-{5}") {
Write-Verbose "Found private key in PKCS#8 format."
[convert]::FromBase64String($matches[1])
} elseif ($Text -match "(?msx).*-{5}BEGIN\sRSA\sPRIVATE\sKEY-{5}(.+)-{5}END\sRSA\sPRIVATE\sKEY-{5}") {
Write-Verbose "Found RSA private key in PKCS#1 format."
[convert]::FromBase64String($matches[1])
} else {
throw "The data is invalid."
}
if ($AlgFamily -eq "RSA") {
# RSA can be in PKCS#1 format, which is not supported
Write-Verbose "Converting RSA PKCS#1 to PKCS#8..."
# RSA can be in PKCS#1 format, which is not supported by CngKey.
$rsa = New-Object SysadminsLV.PKI.Cryptography.RsaPrivateKey (,$bin)
$bin = $rsa.Export("Pkcs8")
$rsa.Dispose()
}

Write-Verbose "Using provider: $ProviderName"
$prov = [System.Security.Cryptography.CngProvider]::new($ProviderName)
$blobType = [System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob
# make it exportable
$cngProp = New-Object System.Security.Cryptography.CngProperty("Export Policy", [BitConverter]::GetBytes(3), "None")
$cng = [System.Security.Cryptography.CngKey]::Import($bin, $blobType, $prov)
$Disposables += $cng
$Disposables.Add($cng)
$cng.SetProperty($cngProp)

switch ($AlgFamily) {
"RSA" {
$AsymmetricKey = New-Object System.Security.Cryptography.RSACng $cng
New-Object System.Security.Cryptography.RSACng $cng
}
"ECC" {
$AsymmetricKey = New-Object System.Security.Cryptography.ECDsaCng $cng
New-Object System.Security.Cryptography.ECDsaCng $cng

}
default {
throw "Specified algorithm is not supported"
}
}

$Disposables += $AsymmetricKey
}
# returns: X509Certificate2 with associated private key
function __associatePrivateKey() {
function __associatePrivateKey($Cert, $AsymmetricKey) {
Write-Verbose "Merging public certificate with private key..."
switch ($AlgFamily) {
"RSA" {[System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($Cert, $AsymmetricKey)}
"ECC" {[System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::CopyWithPrivateKey($Cert, $AsymmetricKey)}
}
Write-Verbose "Public certificate and private key are successfully merged."
}
function __duplicateCertWithKey($CertWithKey) {
$PfxBytes = $CertWithKey.Export("pfx")
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $PfxBytes, "", $KeyStorageFlags
}
function __installCert($CertWithKey) {
if (!$Install) {
$CertWithKey
return
}

if ($Install) {
$store = New-Object Security.Cryptography.X509Certificates.X509Store "my", $StoreLocation
$store.Open("ReadWrite")
$Disposables += $store
$store.Add($CertWithKey)
$store.Close()
}
Write-Verbose "Installing certificate to certificate store: $StoreLocation"
# $CertWithKey cert has ephemeral key which cannot be installed into cert store as is.
# so export it into PFX in memory and re-import back with storage flags
$NewCert = __duplicateCertWithKey $CertWithKey
# dispose ephemeral cert received from params, we have a persisted cert to return
$CertWithKey.Dispose()

$store = New-Object Security.Cryptography.X509Certificates.X509Store "my", $StoreLocation
$store.Open("ReadWrite")
$Disposables.Add($store)
$store.Add($NewCert)
$store.Close()
Write-Verbose "Certificate is installed."
# dispose this temporary cert
$NewCert
}
function __exportPfx($CertWithKey) {
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
return
}

Write-Verbose "Saving PFX to a file: $OutputPath"
if (!$Password) {
$Password = Read-Host -Prompt "Enter PFX password" -AsSecureString
}
$pfxBytes = $CertWithKey.Export("pfx", $Password)
Export-Binary $OutputPath $pfxBytes
Write-Verbose "PFX is saved."
}

$File = Get-Item $InputPath -Force -ErrorAction Stop
if ($KeyPath) {
$Key = Get-Item $KeyPath -Force -ErrorAction Stop
}

# parse content
$Text = Get-Content -Path $InputPath -Raw -ErrorAction Stop
Write-Debug "Extracting certificate information..."
__extractCert $Text
if ($Key) {
$Cert = __extractCert $Text
if ($KeyPath) {
$Text = Get-Content -Path $KeyPath -Raw -ErrorAction Stop
}
__extractPrivateKey $Text
$PrivateKey = __extractPrivateKey $Text
$Disposables.Add($PrivateKey)

$PfxCert = __associatePrivateKey
__installCert $PfxCert
$PfxCert = __associatePrivateKey $Cert $PrivateKey
__exportPfx $PfxCert
$PfxCert = __installCert $PfxCert
# release unmanaged resources
$Disposables | %{$_.Dispose()}

Expand Down

0 comments on commit 73d9962

Please sign in to comment.