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 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 { function Resolve-HostEntry {
param( param(
[string]$Name, [string]$Name,
@@ -712,6 +768,22 @@ $panel.Controls.Add($csrImportBtn)
Style-ButtonSecondary $csrImportBtn Style-ButtonSecondary $csrImportBtn
$y += $rowHeight + $gap $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 $useFqdnBox = Add-CheckBox "Input contains FQDNs (otherwise default zone is appended)" $xInput $y $inputWidth $rowHeight
$y += $rowHeight + $gap $y += $rowHeight + $gap
@@ -909,6 +981,8 @@ $sectionAnchors = @{
Logs = $sectionActivity Logs = $sectionActivity
} }
$script:infRequests = @()
foreach ($key in $sectionAnchors.Keys) { foreach ($key in $sectionAnchors.Keys) {
$label = $navLabels[$key] $label = $navLabels[$key]
if (-not $label) { continue } if (-not $label) { continue }
@@ -996,7 +1070,7 @@ function Show-HelpDialog {
$stepY = 28 $stepY = 28
$steps = @( $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.", "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.", "Set the replication wait time. Typical value: 15 minutes.",
"Choose output type (PEM or PFX). If PFX, supply a password.", "Choose output type (PEM or PFX). If PFX, supply a password.",
@@ -1192,6 +1266,9 @@ function Apply-Layout {
$csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap) $csrFolderBox.Width = $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap) $csrBrowseBtn.Left = $xInput + $inputWidthCalc - ((2 * $buttonWidth) + $buttonGap)
$csrImportBtn.Left = $xInput + $inputWidthCalc - $buttonWidth $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 $zoneBox.Width = $inputWidthCalc
$ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap) $ipBox.Width = $inputWidthCalc - ($buttonWidth + $buttonGap)
$ipRefreshBtn.Left = $xInput + $inputWidthCalc - $buttonWidth $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({ $csrImportBtn.Add_Click({
try { try {
$folder = $csrFolderBox.Text.Trim() $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({ $dnsScanBtn.Add_Click({
try { try {
$dnsListBox.Items.Clear() $dnsListBox.Items.Clear()
@@ -1506,7 +1642,29 @@ $runBtn.Add_Click({
if (-not (Test-Path -Path $wacsPath -PathType Leaf)) { if (-not (Test-Path -Path $wacsPath -PathType Leaf)) {
throw "WACS not found at: $wacsPath" 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) { foreach ($entry in $hostEntries) {
& $logAction "Requesting certificate for $($entry.Fqdn)." & $logAction "Requesting certificate for $($entry.Fqdn)."
Invoke-Wacs ` Invoke-Wacs `