Private/Export-PACertFiles.ps1
function Export-PACertFiles { [CmdletBinding()] param( [Parameter(Position=0)] [PSTypeName('PoshACME.PAOrder')]$Order, [switch]$PfxOnly ) # Make sure we have an account configured if (!($acct = Get-PAAccount)) { throw "No ACME account configured. Run Set-PAAccount or New-PAAccount first." } # Make sure we have an order if (-not $Order -and !($Order = Get-PAOrder)) { throw "No ACME order specified and no current order selected. Run Set-PAOrder or specify an existing order object." } $orderFolder = Join-Path $script:AcctFolder $Order.MainDomain.Replace('*','!') # build output paths $certFile = Join-Path $orderFolder 'cert.cer' $keyFile = Join-Path $orderFolder 'cert.key' $chainFile = Join-Path $orderFolder 'chain.cer' $fullchainFile = Join-Path $orderFolder 'fullchain.cer' $pfxFile = Join-Path $orderFolder 'cert.pfx' $pfxFullFile = Join-Path $orderFolder 'fullchain.pfx' if (-not $PfxOnly) { # Re-download the cert/chains if the order has not expired. if ((Get-DateTimeOffsetNow) -lt [DateTimeOffset]::Parse($order.expires)) { # build the header for the Post-As-Get request $header = @{ alg = $acct.alg kid = $acct.location nonce = $script:Dir.nonce url = $Order.certificate } # download the cert+chain which is what ACMEv2 delivers by default # https://tools.ietf.org/html/rfc8555#section-7.4.2 try { $response = Invoke-ACME $header ([String]::Empty) $acct -EA Stop } catch { throw } $pems = Split-PemChain -ChainBytes $response.Content # write the lone cert Export-Pem $pems[0] $certFile # write the primary chain as chain0.cer $chain0File = Join-Path $orderFolder 'chain0.cer' Export-Pem ($pems[1..($pems.Count-1)] | ForEach-Object {$_}) $chain0File # check for alternate chain header links $links = @(Get-AlternateLinks $response.Headers) # download the alternate chains for ($i = 0; $i -lt $links.Count; $i++) { Write-Debug "Alt Chain $($i+1): $($links[$i])" $header.url = $links[$i] $header.nonce = $script:Dir.nonce try { $response = Invoke-ACME $header ([String]::Empty) $acct -EA Stop } catch {throw} $pems = Split-PemChain -ChainBytes $response.Content # write additional chain files as chain1.cer,chain2.cer,etc. $altChainFile = Join-Path $orderFolder "chain$($i+1).cer" Export-Pem ($pems[1..($pems.Count-1)] | ForEach-Object {$_}) $altChainFile } } else { Write-Warning "Order has expired. Unable to re-download cert/chain files. Using cached copies." } # try to find the chain file matching the preferred issuer if specified if (-not ([String]::IsNullOrWhiteSpace($order.PreferredChain))) { $selectedChainFile = Get-ChainIssuers $orderFolder | Where-Object { $_.issuer -eq $order.PreferredChain } | Select-Object -First 1 -Expand filePath Write-Debug "Preferred chain, $($order.PreferredChain), matched: $selectedChainFile" if (-not $selectedChainFile) { Write-Warning "The preferred chain issuer, $($order.PreferredChain), was not found. Using the default chain." $selectedChainFile = $chain0File } } else { $selectedChainFile = $chain0File } # build the appropriate chain and fullchain files Copy-Item $selectedChainFile $chainFile $fullchainLines = (Get-Content $certFile) + (Get-Content $selectedChainFile) Export-Pem $fullchainLines $fullchainFile } # When using an pre-generated CSR file, there may be no private key. # So make sure we have a one before we try to generate PFX files. if (Test-Path $keyFile -PathType Leaf) { $pfxParams = @{ CertFile = $certFile; KeyFile = $keyFile; OutputFile = $pfxFile; FriendlyName = $Order.FriendlyName; PfxPass = $Order.PfxPass; } Export-CertPfx @pfxParams $pfxParams.OutputFile = $pfxFullFile Export-CertPfx @pfxParams -ChainFile $chainFile } else { Write-Verbose "No private key available. Skipping Pfx creation." } } |