Private/Export-CertPfx.ps1
function Export-CertPfx { [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] [string]$CertFile, [Parameter(Mandatory,Position=1)] [string]$KeyFile, [Parameter(Mandatory,Position=2)] [string]$OutputFile, [string]$ChainFile, [string]$FriendlyName, [string]$PfxPass='', [switch]$UseModernPfxEncryption ) $CertFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CertFile) $KeyFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($KeyFile) $OutputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputFile) $ChainFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ChainFile) # read in the files as native BouncyCastle objects $key = Import-Pem -InputFile $KeyFile # [Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair] $cert = Import-Pem -InputFile $CertFile # [Org.BouncyCastle.X509.X509Certificate] # BouncyCastle won't let use use a null value for a cert/key alias in the PFX file and Windows # in some cases doesn't like the empty string default we were using previously. So we'll # use the subject CN value unless something non-empty was passed in. if ([String]::IsNullOrWhiteSpace($FriendlyName)) { $FriendlyName = $cert.SubjectDN.GetValueList([Org.BouncyCastle.Asn1.X509.X509Name]::CN)[0] } # create a new Pkcs12Store $storebuilder = [Org.BouncyCastle.Pkcs.Pkcs12StoreBuilder]::new() $storebuilder.SetCertAlgorithm( [Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::PbeWithShaAnd3KeyTripleDesCbc.Id ) | Out-Null # The private key algorithm option affects compatibility with various versions # of OpenSSL. The default, "RC2-40-CBC", works with 1.0.x and 1.1.x, but not # 3.x unless additional "legacy" parameters are used. The modern option, # "AES256 with SHA256" is not supported on 1.0.x. if ($UseModernPfxEncryption) { # Use PKCS5 Scheme 2 with AES256CBC and HMAC-SHA256 $storebuilder.SetKeyAlgorithm( [Org.BouncyCastle.Asn1.Nist.NistObjectIdentifiers]::IdAes256Cbc.Id, [Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::IdHmacWithSha256.Id ) | Out-Null } else { # Use legacy RC2-40-CBC $storebuilder.SetKeyAlgorithm( [Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::PbewithShaAnd40BitRC2Cbc.Id ) | Out-Null } $store = $storebuilder.Build() # add the private key try { $store.SetKeyEntry($FriendlyName, $key.Private, @($cert)) } catch { throw } # add the chain certs if specified if ('ChainFile' -in $PSBoundParameters.Keys) { $pems = @(Split-PemChain $ChainFile) foreach ($pem in $pems) { $ca = Import-Pem -InputString ($pem -join [Environment]::NewLine) # try to parse the subject to use as the alias if ($ca.SubjectDN -match "CN=([^,]+)") { $caName = $matches[1] } else { $caName = $ca.SerialNumber } try { $store.SetCertificateEntry($caName, $ca) } catch { throw } } } # save it $sRandom = New-Object Org.BouncyCastle.Security.SecureRandom try { $fs = New-Object IO.FileStream($OutputFile,'Create') $store.Save($fs, $PfxPass, $sRandom) } finally { if ($null -ne $fs) { $fs.Close() } } } |