Add INF import and per-INF issuance

This commit is contained in:
2026-01-30 12:09:34 +13:00
parent 7b9fef1946
commit c545ae6d48

162
certy.ps1
View File

@@ -234,6 +234,62 @@ function Get-DefaultValue {
return $prop.Value
}
function Get-HostsFromInfLines {
param([string[]]$Lines)
$set = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
if (-not $Lines) { return @() }
foreach ($line in $Lines) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
if ($line.TrimStart() -match '^[;#]') { continue }
$matches = [regex]::Matches($line, '(?i)\bdns\s*=\s*([^&",\s]+)')
foreach ($match in $matches) {
$value = $match.Groups[1].Value.Trim()
if ($value) { [void]$set.Add($value) }
}
}
if ($set.Count -gt 0) { return $set | Sort-Object }
$subjectLine = $Lines | Where-Object { $_ -match '(?i)^\s*subject\s*=' } | Select-Object -First 1
if ($subjectLine -and ($subjectLine -match '(?i)\bCN\s*=\s*([^,"]+)')) {
$cn = $Matches[1].Trim()
if ($cn) { [void]$set.Add($cn) }
}
return $set | Sort-Object
}
function Remove-InfSubjectLines {
param([string[]]$Lines)
$removed = $false
$filtered = foreach ($line in $Lines) {
if ($line -match '(?i)^\s*subject\s*=') {
$removed = $true
continue
}
$line
}
return [pscustomobject]@{
Lines = $filtered
Removed = $removed
}
}
function Save-SanitizedInf {
param(
[string]$FileName,
[string[]]$Lines
)
$dir = Join-Path $env:ProgramData "Certy\\inf-sanitized"
if (-not (Test-Path -Path $dir -PathType Container)) {
New-Item -Path $dir -ItemType Directory -Force | Out-Null
}
$outPath = Join-Path $dir $FileName
Set-Content -Path $outPath -Value $Lines -Encoding ascii
return $outPath
}
function Resolve-HostEntry {
param(
[string]$Name,
@@ -712,6 +768,22 @@ $panel.Controls.Add($csrImportBtn)
Style-ButtonSecondary $csrImportBtn
$y += $rowHeight + $gap
$infLabel = Add-Label "INF folder (optional)" $xLabel $y $labelWidth $rowHeight
$infFolderBox = Add-TextBox $xInput $y ($inputWidth - ((2 * $buttonWidth) + $buttonGap)) $rowHeight $false
$infBrowseBtn = New-Object System.Windows.Forms.Button
$infBrowseBtn.Text = "Browse"
$infBrowseBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - (2 * $buttonWidth + $buttonGap)), ($y - 1))
$infBrowseBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($infBrowseBtn)
Style-ButtonSecondary $infBrowseBtn
$infImportBtn = New-Object System.Windows.Forms.Button
$infImportBtn.Text = "Import INF"
$infImportBtn.Location = [System.Drawing.Point]::new(($xInput + $inputWidth - $buttonWidth), ($y - 1))
$infImportBtn.Size = [System.Drawing.Size]::new($buttonWidth, 26)
$panel.Controls.Add($infImportBtn)
Style-ButtonSecondary $infImportBtn
$y += $rowHeight + $gap
$useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight
$y += $rowHeight + $gap
@@ -909,6 +981,8 @@ $sectionAnchors = @{
Logs = $sectionActivity
}
$script:infRequests = @()
foreach ($key in $sectionAnchors.Keys) {
$label = $navLabels[$key]
if (-not $label) { continue }
@@ -996,7 +1070,7 @@ function Show-HelpDialog {
$stepY = 28
$steps = @(
"Add hostnames (one per line, CSV, or CSR folder) then click 1) Preview.",
"Add hostnames (one per line, CSV, CSR, or INF folder) then click 1) Preview.",
"Click 2) Scan and select the DNS server (Primary is 10.106.60.1). Replication runs from there.",
"Set the replication wait time. Typical value: 15 minutes.",
"Choose output type (PEM or PFX). If PFX, supply a password.",
@@ -1192,6 +1266,9 @@ function Apply-Layout {
$csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$infFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$infBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$infImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
$zoneBox.Width = $inputWidthCalc
$ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth
@@ -1276,6 +1353,13 @@ $csrBrowseBtn.Add_Click({
}
})
$infBrowseBtn.Add_Click({
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$infFolderBox.Text = $dialog.SelectedPath
}
})
$csrImportBtn.Add_Click({
try {
$folder = $csrFolderBox.Text.Trim()
@@ -1298,6 +1382,58 @@ $csrImportBtn.Add_Click({
}
})
$infImportBtn.Add_Click({
try {
$folder = $infFolderBox.Text.Trim()
if (-not $folder) { throw "INF folder is empty." }
if (-not (Test-Path -Path $folder -PathType Container)) { throw "INF folder not found: $folder" }
$infFiles = Get-ChildItem -Path $folder -Filter *.inf -File -Recurse
if (-not $infFiles) {
& $logAction "No INF files found in $folder"
return
}
$script:infRequests = @()
$infHosts = @()
$subjectRemovedCount = 0
foreach ($infFile in $infFiles) {
$lines = Get-Content -Path $infFile.FullName
$hosts = @(Get-HostsFromInfLines -Lines $lines)
$sanitize = Remove-InfSubjectLines -Lines $lines
$sanitizedPath = Save-SanitizedInf -FileName $infFile.Name -Lines $sanitize.Lines
if ($sanitize.Removed) { $subjectRemovedCount++ }
if ($hosts.Count -eq 0) {
& $logAction "INF $($infFile.Name): no hostnames detected."
continue
}
$script:infRequests += [pscustomobject]@{
File = $infFile.FullName
Hosts = $hosts
Sanitized = $sanitizedPath
}
$infHosts += $hosts
}
$infHosts = @($infHosts | Where-Object { $_ } | Sort-Object -Unique)
if ($infHosts.Count -gt 0) {
$currentHosts = Split-List $hostsBox.Text
$merged = Merge-Hostnames -Existing $currentHosts -NewItems $infHosts
$hostsBox.Text = ($merged -join [Environment]::NewLine)
}
& $logAction "Imported $($infFiles.Count) INF file(s), added $($infHosts.Count) hostname(s)."
if ($subjectRemovedCount -gt 0) {
& $logAction "Removed Subject line from $subjectRemovedCount INF file(s) (sanitized copies saved)."
}
} catch {
& $logAction "Error: $($_.Exception.Message)"
}
})
$dnsScanBtn.Add_Click({
try {
$dnsListBox.Items.Clear()
@@ -1506,7 +1642,29 @@ $runBtn.Add_Click({
if (-not (Test-Path -Path $wacsPath -PathType Leaf)) {
throw "WACS not found at: $wacsPath"
}
if ($perHostBox.Checked) {
if ($script:infRequests -and $script:infRequests.Count -gt 0) {
& $logAction "INF requests detected; issuing one certificate per INF file."
foreach ($req in $script:infRequests) {
$reqEntries = @($req.Hosts | ForEach-Object { Resolve-HostEntry -Name $_ -Zone $zone -UseProvidedFqdn $useFqdnBox.Checked } | Where-Object { $_ })
$reqFqdns = @($reqEntries | ForEach-Object { $_.Fqdn } | Where-Object { $_ })
if ($reqFqdns.Count -eq 0) {
& $logAction "INF $([System.IO.Path]::GetFileName($req.File)) skipped (no hosts)."
continue
}
& $logAction "Requesting certificate for INF $([System.IO.Path]::GetFileName($req.File)) with $($reqFqdns.Count) hostname(s)."
Invoke-Wacs `
-WacsPath $wacsPath `
-HostFqdns $reqFqdns `
-OutputType $outputType `
-OutputPath $outputPath `
-PfxPassword ($(if ($usePfxPassword) { $pfxPasswordBox.Text } else { "" })) `
-BaseUri $baseUriBox.Text.Trim() `
-Validation $validationBox.Text.Trim() `
-ValidationPort $validationPortBox.Text.Trim() `
-Verbose $verboseBox.Checked `
-Log $logAction
}
} elseif ($perHostBox.Checked) {
foreach ($entry in $hostEntries) {
& $logAction "Requesting certificate for $($entry.Fqdn)."
Invoke-Wacs `