From bac472a0e64ae3fc2a1b44963808d51b8196add8 Mon Sep 17 00:00:00 2001 From: Sven Rajala Date: Tue, 7 Apr 2026 15:53:18 -0400 Subject: [PATCH] fix: windows 7 support for installing cert into both cert stores --- .../windows-cmp-enrollment/README.md | 4 +- .../windows-cmp-enrollment/cmp-enrollment.ps1 | 72 +++++++++++++------ 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps-integration/windows-cmp-enrollment/README.md b/apps-integration/windows-cmp-enrollment/README.md index f2d377d..cd40861 100644 --- a/apps-integration/windows-cmp-enrollment/README.md +++ b/apps-integration/windows-cmp-enrollment/README.md @@ -231,7 +231,7 @@ Files are prefixed with the CN (Common Name) from the Subject DN: - Installs to the certificate store(s) specified by `-CertificateStore`: - **LocalMachine** (default): installs to `LocalMachine\Personal`; private key stored in machine key store - **CurrentUser**: installs to `CurrentUser\Personal`; private key stored in user key store - - **Both**: installs to `LocalMachine\Personal` first, then copies the certificate and private key into `CurrentUser\Personal` via a temporary PFX + - **Both**: installs to `LocalMachine\Personal` first, then copies the certificate and private key into `CurrentUser\Personal` via a temporary PFX. Uses `Export/Import-PfxCertificate` on Windows 8+, or `certutil -exportPFX` / `certutil -user -importPFX` on Windows 7 - Associates certificate with private key generated in Step 1 ### Step 5: Export to PFX (Optional) @@ -252,6 +252,8 @@ The target store is controlled by the `-CertificateStore` parameter: | `CurrentUser` | `CurrentUser\Personal` | User key store (current user only) | None | | `Both` | `LocalMachine\Personal` and `CurrentUser\Personal` | Both key stores | Administrator | +When `Both` is selected, the script installs to `LocalMachine\Personal` first (via `certreq -accept`), then copies the certificate and private key into `CurrentUser\Personal` using a temporary PFX with a randomly generated password. On Windows 7, where `Export-PfxCertificate` and `Import-PfxCertificate` are unavailable, `certutil -exportPFX` and `certutil -user -importPFX` are used instead. + ## Troubleshooting ### Common Issues diff --git a/apps-integration/windows-cmp-enrollment/cmp-enrollment.ps1 b/apps-integration/windows-cmp-enrollment/cmp-enrollment.ps1 index bf87b8d..9ce62a4 100644 --- a/apps-integration/windows-cmp-enrollment/cmp-enrollment.ps1 +++ b/apps-integration/windows-cmp-enrollment/cmp-enrollment.ps1 @@ -560,30 +560,56 @@ if ($CertificateStore -eq "Both") { $bothThumbprint = $matches[1] -replace ":", "" Write-Log "Thumbprint for cross-store copy: $bothThumbprint" - $lmCert = Get-ChildItem -Path "Cert:\LocalMachine\My" | - Where-Object { $_.Thumbprint -ieq $bothThumbprint } | - Select-Object -First 1 - - if ($null -eq $lmCert) { - Write-Log "WARNING: Could not find certificate in LocalMachine\My by thumbprint. Skipping CurrentUser install." -AlwaysShow - } else { - # Export to a temporary PFX with a random password, import into CurrentUser\My, then delete - $tempPfxPath = Join-Path $BasePath "$sanitizedCN-temp-cross-store.pfx" - $tempPfxBytes = New-Object byte[] 32 - [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($tempPfxBytes) - $tempPfxPassword = [Convert]::ToBase64String($tempPfxBytes) - $tempPfxSecure = ConvertTo-SecureString -String $tempPfxPassword -AsPlainText -Force - - try { - Export-PfxCertificate -Cert $lmCert -FilePath $tempPfxPath -Password $tempPfxSecure -ChainOption BuildChain | Out-Null - Import-PfxCertificate -FilePath $tempPfxPath -CertStoreLocation "Cert:\CurrentUser\My" -Password $tempPfxSecure | Out-Null - Write-Log "Certificate and private key installed to: CurrentUser\Personal" - } catch { - Write-Log "ERROR installing certificate to CurrentUser\Personal: $_" -AlwaysShow - } finally { - if (Test-Path $tempPfxPath) { Remove-Item -Force $tempPfxPath -ErrorAction SilentlyContinue } - $tempPfxPassword = $null + $tempPfxPath = Join-Path $BasePath "$sanitizedCN-temp-cross-store.pfx" + + # Generate a cryptographically random temporary PFX password + $tempPfxBytes = New-Object byte[] 32 + [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($tempPfxBytes) + $tempPfxPassword = [Convert]::ToBase64String($tempPfxBytes) + + $bothOsVersion = [System.Environment]::OSVersion.Version + $bothIsWin7OrOlder = ($bothOsVersion.Major -lt 6) -or ($bothOsVersion.Major -eq 6 -and $bothOsVersion.Minor -lt 2) + + try { + if ($bothIsWin7OrOlder) { + # Windows 7 / older: Export-PfxCertificate and Import-PfxCertificate are not available. + # Use certutil -exportPFX / certutil -user -importPFX instead. + Write-Log "Detected Windows 7 or older - using certutil for cross-store copy" + + $exportArgs = @("-exportPFX", "-p", $tempPfxPassword, "My", $bothThumbprint, "`"$tempPfxPath`"") + $exportOut = & certutil.exe @exportArgs 2>&1 + Write-Log "certutil -exportPFX output: $exportOut" + + if (!(Test-Path $tempPfxPath)) { + Write-Log "WARNING: certutil -exportPFX did not produce a file. Skipping CurrentUser install." -AlwaysShow + } else { + $importArgs = @("-user", "-p", $tempPfxPassword, "-importPFX", "My", "`"$tempPfxPath`"") + $importOut = & certutil.exe @importArgs 2>&1 + Write-Log "certutil -importPFX output: $importOut" + Write-Log "Certificate and private key installed to: CurrentUser\Personal" + } + } else { + # Windows 8+: use the PKI cmdlets + Write-Log "Detected Windows 8 or newer - using Export/Import-PfxCertificate for cross-store copy" + + $lmCert = Get-ChildItem -Path "Cert:\LocalMachine\My" | + Where-Object { $_.Thumbprint -ieq $bothThumbprint } | + Select-Object -First 1 + + if ($null -eq $lmCert) { + Write-Log "WARNING: Could not find certificate in LocalMachine\My by thumbprint. Skipping CurrentUser install." -AlwaysShow + } else { + $tempPfxSecure = ConvertTo-SecureString -String $tempPfxPassword -AsPlainText -Force + Export-PfxCertificate -Cert $lmCert -FilePath $tempPfxPath -Password $tempPfxSecure -ChainOption BuildChain | Out-Null + Import-PfxCertificate -FilePath $tempPfxPath -CertStoreLocation "Cert:\CurrentUser\My" -Password $tempPfxSecure | Out-Null + Write-Log "Certificate and private key installed to: CurrentUser\Personal" + } } + } catch { + Write-Log "ERROR installing certificate to CurrentUser\Personal: $_" -AlwaysShow + } finally { + if (Test-Path $tempPfxPath) { Remove-Item -Force $tempPfxPath -ErrorAction SilentlyContinue } + $tempPfxPassword = $null } } else { Write-Log "WARNING: Could not parse thumbprint for CurrentUser install. Skipping." -AlwaysShow