private/functions/Revoke-AxisCertificateOnCommand.ps1
|
function Revoke-AxisCertificateOnCommand { <# .SYNOPSIS Revokes a certificate by running $Authority.RevokeCommand (OpenSSL CA or any CLI), locally or over SSH. .DESCRIPTION Issuer backend for Type='Command' certificate authorities. Expands the {Serial}, {Reason} and {Device} tokens in $Authority.RevokeCommand and runs it through the platform shell (cmd.exe / /bin/sh) with Start-Process, or through "ssh <host> sh -s" when $Authority.SshHost is set. Throws with the command's stderr on a non-zero exit or when the command exceeds TimeoutSec (default 120). Returns $true. Example RevokeCommand for an OpenSSL CA (https://docs.openssl.org/master/man1/openssl-ca/): openssl ca -batch -config /etc/pki/ca/openssl.cnf -revoke /etc/pki/ca/newcerts/{Serial}.pem -crl_reason {Reason} The {Reason} token is rendered in the spelling OpenSSL expects (superseded, keyCompromise, cessationOfOperation, unspecified). .PARAMETER Authority The authority hashtable from $Config.CertificateAuthorities. .PARAMETER Serial The certificate serial number as an uppercase hex string without colons. .PARAMETER Reason Revocation reason. Default Unspecified. .PARAMETER Device Optional device name for the {Device} token. .PARAMETER TimeoutSec Overrides $Authority.TimeoutSec. #> [cmdletbinding()] Param( [Parameter(Mandatory)] [Hashtable]$Authority, [Parameter(Mandatory)] [String]$Serial, [Parameter()] [ValidateSet("Superseded","KeyCompromise","CessationOfOperation","Unspecified")] [String]$Reason="Unspecified", [Parameter()] [String]$Device="", [Parameter()] [Int]$TimeoutSec ) if(!$Authority.RevokeCommand) { Throw "Authority '$($Authority.Name)' has no RevokeCommand; revocation is not supported" } if($Serial -notmatch "^[0-9A-Fa-f]+$") { Throw "Serial must be a hex string" } if(!$TimeoutSec) { $TimeoutSec = 120 if($Authority.TimeoutSec) { $TimeoutSec = [Int]$Authority.TimeoutSec } } $IsWin = ($PSVersionTable.PSVersion.Major -lt 6) -or $IsWindows $UseSsh = [bool]$Authority.SshHost #OpenSSL -crl_reason spelling (openssl ca man page, "-crl_reason reason") $ReasonMap = @{ Superseded = "superseded" KeyCompromise = "keyCompromise" CessationOfOperation = "cessationOfOperation" Unspecified = "unspecified" } $Command = $Authority.RevokeCommand $Command = $Command.Replace("{Serial}",$Serial.ToUpper()).Replace("{Reason}",$ReasonMap[$Reason]) $Command = $Command.Replace("{Device}",$Device).Replace("{Subject}","") $Command = $Command.Replace("{CsrFile}","").Replace("{CertFile}","") $Id = [guid]::NewGuid().ToString("N") $TempDir = Join-Path ([System.IO.Path]::GetTempPath()) "AxisPowershell-$Id" $ScriptFile = Join-Path $TempDir $(if($IsWin -and !$UseSsh) { "run.cmd" } else { "run.sh" }) $StdOutFile = Join-Path $TempDir "stdout.txt" $StdErrFile = Join-Path $TempDir "stderr.txt" Try { $null = New-Item -ItemType Directory -Path $TempDir -Force $Param = @{ PassThru = $true RedirectStandardOutput = $StdOutFile RedirectStandardError = $StdErrFile } if($IsWin) { $Param.Add('WindowStyle','Hidden') } else { $Param.Add('NoNewWindow',$true) } if($UseSsh) { #Fed to "sh -s" on stdin so quoting in the template survives Start-Process and the ssh re-join $Script = "set -e`n" if($Authority.WorkingDirectory) { $Script += "cd '$($Authority.WorkingDirectory)'`n" } $Script += "$Command`n" [System.IO.File]::WriteAllText($ScriptFile, $Script, [System.Text.Encoding]::ASCII) $SshTarget = $Authority.SshHost if($Authority.SshUser) { $SshTarget = "$($Authority.SshUser)@$($Authority.SshHost)" } $Arguments = @("-o","BatchMode=yes","-o","ConnectTimeout=15") if($Authority.SshKeyFile) { $Arguments += @("-i",$Authority.SshKeyFile) } $Arguments += @("-T",$SshTarget,"sh","-s") $Param.Add('FilePath',"ssh") $Param.Add('ArgumentList',$Arguments) $Param.Add('RedirectStandardInput',$ScriptFile) $What = "Revoke command on $($Authority.SshHost)" } elseif($IsWin) { [System.IO.File]::WriteAllText($ScriptFile, "@echo off`r`n$Command`r`n", [System.Text.Encoding]::ASCII) $Shell = "$env:ComSpec" if(!$Shell) { $Shell = "cmd.exe" } $Param.Add('FilePath',$Shell) $Param.Add('ArgumentList',@("/d","/c","`"$ScriptFile`"")) if($Authority.WorkingDirectory) { $Param.Add('WorkingDirectory',$Authority.WorkingDirectory) } $What = "Revoke command" } else { [System.IO.File]::WriteAllText($ScriptFile, "set -e`n$Command`n", [System.Text.Encoding]::ASCII) $Param.Add('FilePath',"/bin/sh") $Param.Add('ArgumentList',@($ScriptFile)) if($Authority.WorkingDirectory) { $Param.Add('WorkingDirectory',$Authority.WorkingDirectory) } $What = "Revoke command" } Write-Verbose "Running: $($Param.FilePath) $($Param.ArgumentList -join ' ')" $Process = Start-Process @Param $null = $Process.Handle if(!$Process.WaitForExit($TimeoutSec * 1000)) { Try { $Process.Kill() } Catch { } Throw "$What timed out after $TimeoutSec seconds" } $StdOut = "" $StdErr = "" if(Test-Path $StdOutFile) { $StdOut = "$(Get-Content $StdOutFile -Raw)" } if(Test-Path $StdErrFile) { $StdErr = "$(Get-Content $StdErrFile -Raw)" } if($Process.ExitCode -ne 0) { $Detail = $StdErr.Trim() if(!$Detail) { $Detail = $StdOut.Trim() } Throw "$What failed with exit code $($Process.ExitCode): $Detail" } Write-Verbose "Revoked serial $Serial ($Reason)" return $true } Catch { Throw "Command authority revoke failed: $_" } Finally { if(Test-Path $TempDir) { Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue } } } |