SophosFirewall.Certificates.psm1
|
#requires -Version 5.1 #requires -Modules @{ ModuleName = 'SophosFirewall.Core'; ModuleVersion = '1.3.5' } <# .SYNOPSIS Manages the certificate store of a Sophos Firewall: certificates, certificate authorities and certificate revocation lists. .DESCRIPTION Functions for the SYSTEM > Certificates area of the Sophos Firewall XML API (SFOS 22.0). A certificate identifies a service the firewall publishes or inspects; a certificate authority is the issuer the firewall trusts when it validates one; a revocation list names the certificates an authority has withdrawn. These endpoints do not answer with XML. A read returns an archive holding the stored files and a manifest describing them, so the Get cmdlets here return objects built from that manifest, and the Export cmdlets write the files themselves to disk. Total Functions: 13 (11 exported, 2 internal helpers) - see README.md for the full cmdlet table. Connect once with Connect-SfosFirewall, then call the cmdlets in this module without repeating the connection parameters. Three limitations come from the API, not from this module: - A revocation list can be read but not uploaded. Every documented request shape was refused, including the one from the vendor's own sample, so there is no New-SfosCRL. - Generating a certificate authority on the appliance is documented, but the operation reports success without storing anything, so it is deliberately not wrapped. - A certificate is stored as PEM whatever format it arrives in, and a private key is re-encoded, so an exported file is equivalent to the uploaded one rather than identical to it. .EXAMPLE Connect-SfosFirewall -Firewall '192.0.2.1' -Credential (Get-Credential) -SkipCertificateCheck Get-SfosCertificate Connects to the firewall and lists the stored certificates. .EXAMPLE New-SfosCertificate -Name 'PortalCert' -PfxFilePath 'C:\pki\portal.pfx' -PfxPassword $pfxPassword Export-SfosCertificate -Name 'PortalCert' -Path 'C:\pki\export' Uploads a PKCS#12 bundle and writes the stored files back to disk. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/ .LINK Connect-SfosFirewall #> #region Certificate <# .SYNOPSIS Builds the Set request body and multipart file map for a Certificate upload. Internal helper, not exported. .DESCRIPTION Certificate has no partial update: every Set call - add or update alike - re-uploads the complete certificate material, so New-SfosCertificate and Set-SfosCertificate share this builder instead of doing read-modify-write on separate fields. It validates that the given file(s) exist, escapes every value that goes into the XML, and returns the inner XML together with the -MultipartFile map Invoke-SfosApi needs. Two upload shapes are supported, matching what was measured against a live firewall: a single PKCS#12/PFX file (CertificateFormat pkcs12, lower case - any other casing is refused with 501), or a separate PEM certificate and unencrypted private key (CertificateFormat pem). An encrypted PEM private key with a Password was never measured to work and is intentionally not offered here. .PARAMETER Operation The Set operation attribute, either 'add' or 'update'. .PARAMETER Name Name of the Certificate object. .PARAMETER PfxFilePath Path to a local .pfx/.p12 file. Selects the PKCS#12 upload shape. .PARAMETER PfxPassword Optional. Export password of the PFX file, as a SecureString. .PARAMETER CertificateFilePath Path to a local PEM certificate file. Selects the two-file PEM upload shape; requires -PrivateKeyFilePath as well. .PARAMETER PrivateKeyFilePath Path to a local, unencrypted PEM private key file matching -CertificateFilePath. #> function New-SfosCertificateUploadPayload { # PSUseShouldProcessForStateChangingFunctions is suppressed on purpose. This function # only assembles the request body in memory; the firewall is contacted by the caller, # which declares ShouldProcess itself. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [ValidateSet('add', 'update')] [string]$Operation, [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory, ParameterSetName = 'Pkcs12')] [string]$PfxFilePath, [Parameter(ParameterSetName = 'Pkcs12')] [SecureString]$PfxPassword, [Parameter(Mandatory, ParameterSetName = 'Pem')] [string]$CertificateFilePath, [Parameter(Mandatory, ParameterSetName = 'Pem')] [string]$PrivateKeyFilePath ) $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name if ($PSCmdlet.ParameterSetName -eq 'Pkcs12') { if (-not (Test-Path -LiteralPath $PfxFilePath -PathType Leaf)) { throw "The PFX file '$PfxFilePath' does not exist." } $pfxFile = Get-Item -LiteralPath $PfxFilePath $fileNameEsc = ConvertTo-SfosXmlEscaped -Text $pfxFile.Name $passwordXml = '' if ($PSBoundParameters.ContainsKey('PfxPassword') -and $PfxPassword) { $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($PfxPassword) try { $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) $passwordEsc = ConvertTo-SfosXmlEscaped -Text $plainPassword $passwordXml = "<Password>$passwordEsc</Password>" } finally { [Runtime.InteropServices.Marshal]::FreeBSTR($bstr) $plainPassword = $null } } $inner = @" <Set operation="$Operation"> <Certificate> <Name>$nameEsc</Name> <Action>UploadCertificate</Action> <CertificateFormat>pkcs12</CertificateFormat> $passwordXml <CertificateFile>$fileNameEsc</CertificateFile> </Certificate> </Set> "@ return @{ InnerXml = $inner MultipartFile = @{ CertificateFile = $pfxFile.FullName } } } if (-not (Test-Path -LiteralPath $CertificateFilePath -PathType Leaf)) { throw "The certificate file '$CertificateFilePath' does not exist." } if (-not (Test-Path -LiteralPath $PrivateKeyFilePath -PathType Leaf)) { throw "The private key file '$PrivateKeyFilePath' does not exist." } $certFile = Get-Item -LiteralPath $CertificateFilePath $keyFile = Get-Item -LiteralPath $PrivateKeyFilePath $certNameEsc = ConvertTo-SfosXmlEscaped -Text $certFile.Name $keyNameEsc = ConvertTo-SfosXmlEscaped -Text $keyFile.Name $inner = @" <Set operation="$Operation"> <Certificate> <Name>$nameEsc</Name> <Action>UploadCertificate</Action> <CertificateFormat>pem</CertificateFormat> <CertificateFile>$certNameEsc</CertificateFile> <PrivateKeyFile>$keyNameEsc</PrivateKeyFile> </Certificate> </Set> "@ return @{ InnerXml = $inner MultipartFile = @{ CertificateFile = $certFile.FullName; PrivateKeyFile = $keyFile.FullName } } } <# .SYNOPSIS Retrieves Certificate objects from a Sophos Firewall. .DESCRIPTION Returns Certificate objects (SYSTEM > Certificates > Certificate). A Certificate object holds a certificate and, usually, its private key, uploaded either as a single PKCS#12 file or as a separate certificate/key pair - see New-SfosCertificate. A Get that matches at least one object answers as a downloaded file (application/octet-stream, a tar archive holding Entities.xml plus the certificate/key files); a Get that matches nothing answers as plain XML with "No. of records Zero.". This cmdlet handles both shapes and always returns PowerShell objects built from Entities.xml, never the archive itself - use Export-SfosCertificate to get the actual certificate/key files onto disk. If the archive's internal structure breaks before every object was read - the firewall is known to corrupt the tar header of an object whose name has non-ASCII characters - ConvertFrom-SfosArchive (SophosFirewall.Core) already emits a warning naming the last object read cleanly. That warning is not suppressed here; Entities.xml itself, and so every object this cmdlet lists, is unaffected by that corruption. The cmdlet only reads; nothing on the firewall is changed. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly. .PARAMETER NameLike Optional. Returns only objects whose name contains the given text anywhere. This is a substring match, not a wildcard pattern, sent to the firewall as a server-side pre-filter and re-applied client-side. If omitted, the name is not used to filter. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AsXml Optional. Returns the raw XML elements from Entities.xml instead of PowerShell objects. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object per Certificate, with the properties Name, CertificateFormat (the format the firewall stored it as - an uploaded DER file is measured to come back as CertificateFormat "pem"; the firewall converts on upload), CertificateFile, PrivateKeyFile and HasPrivateKey. Returns System.Xml.XmlElement when -AsXml is used, and an empty array when no object matches. .EXAMPLE Get-SfosCertificate Lists every Certificate object on the firewall of the current connection, including the built-in ApplianceCertificate. .EXAMPLE Get-SfosCertificate -NameLike 'Portal' Lists all Certificate objects whose name contains 'Portal'. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/Certificate/Certificate.html .LINK New-SfosCertificate #> function Get-SfosCertificate { [CmdletBinding()] param( [string]$NameLike, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, [switch]$AsXml ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $filterXml = '' if ($NameLike) { $nameLikeEsc = ConvertTo-SfosXmlEscaped -Text $NameLike $filterXml = ('<Filter><key name="Name" criteria="like">{0}</key></Filter>' -f $nameLikeEsc) } $inner = @" <Get> <Certificate> $filterXml </Certificate> </Get> "@ try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error retrieving Certificate objects: $($_.Exception.Message)" } # A Get with at least one match answers application/octet-stream (a tar archive); a Get # with none answers text/xml with "No. of records Zero." - the content type depends on # whether anything matched, not on the entity. if ($response.Content -is [byte[]]) { $archive = ConvertFrom-SfosArchive -Bytes $response.Content if (-not $archive.Entities) { throw 'Error retrieving Certificate objects: the firewall returned a file response with no readable Entities.xml.' } Assert-SfosApiReturnSuccess -Xml $archive.Entities -ObjectName 'Certificate' -Action 'get' $nodes = Select-Xml -Xml $archive.Entities -XPath '/Response/Certificate[Name]' | ForEach-Object -Process { $_.Node } } else { $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'Certificate' -Action 'get' $nodes = Select-Xml -Xml $XmlResponse -XPath '/Response/Certificate[Name]' | ForEach-Object -Process { $_.Node } } $certObjects = foreach ($node in @($nodes)) { [PSCustomObject]@{ Name = [string]$node.Name CertificateFormat = [string]$node.CertificateFormat CertificateFile = [string]$node.CertificateFile PrivateKeyFile = [string]$node.PrivateKeyFile HasPrivateKey = [bool]([string]$node.PrivateKeyFile) } } $certObjects = @($certObjects) if ($NameLike) { $certObjects = @($certObjects | Where-Object -FilterScript { $_.Name -like "*$NameLike*" }) } if ($AsXml) { $keptNames = @($certObjects | ForEach-Object -Process { $_.Name }) return @($nodes | Where-Object -FilterScript { $keptNames -contains $_.Name }) } return $certObjects } <# .SYNOPSIS Creates a Certificate object on a Sophos Firewall by uploading certificate material. .DESCRIPTION Uploads a certificate to SYSTEM > Certificates > Certificate, either as a single PKCS#12 (.pfx/.p12) file or as a separate PEM certificate and unencrypted private key. Exactly one of the two shapes must be used per call; PowerShell enforces this through parameter sets. The documentation also lists Action values for generating a self-signed certificate, a CSR, or requesting a Let's Encrypt certificate. None of those were reproducible against a live firewall - every candidate Action value tried was rejected - so this cmdlet only implements the upload path, which is measured to work. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly, and an account with write permission for the Certificates area. .PARAMETER Name Required. Name of the Certificate object. The Add operation's own documentation limits this to 50 characters; the Delete operation's documentation for the same field says 60 - the two disagree, so this cmdlet uses the stricter Add limit of 50. .PARAMETER PfxFilePath Required in the PKCS#12 form. Path to a local .pfx/.p12 file containing the certificate and, usually, its private key. .PARAMETER PfxPassword Optional. Export password of the PFX file, as a SecureString. Never written to the console, help text or an error message in plain text. .PARAMETER CertificateFilePath Required in the PEM form. Path to a local PEM certificate file. Requires -PrivateKeyFilePath as well. .PARAMETER PrivateKeyFilePath Required in the PEM form. Path to a local, unencrypted PEM private key file matching -CertificateFilePath. An encrypted key was never measured to work against a live firewall and is not supported here. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts Name by property name. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the create, or if the given file(s) do not exist. .EXAMPLE New-SfosCertificate -Name 'PortalCertificate' -PfxFilePath 'C:\certs\portal.pfx' -WhatIf Shows what the call would create without sending it to the firewall. .EXAMPLE $pfxPassword = Read-Host -AsSecureString New-SfosCertificate -Name 'PortalCertificate' -PfxFilePath 'C:\certs\portal.pfx' -PfxPassword $pfxPassword Uploads a PFX file with its export password. The cmdlet asks for confirmation before it writes. .EXAMPLE New-SfosCertificate -Name 'PortalCertificate' -CertificateFilePath 'C:\certs\portal.pem' -PrivateKeyFilePath 'C:\certs\portal.key' Uploads a certificate and its unencrypted private key as two separate PEM files. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/Certificate/operations/AddCertificate%26UpdateCertificate.html .LINK Get-SfosCertificate #> function New-SfosCertificate { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateLength(1, 50)] [ValidatePattern('^[A-Za-z0-9_@.-]+$')] [string]$Name, [Parameter(Mandatory, ParameterSetName = 'Pkcs12')] [ValidateNotNullOrEmpty()] [string]$PfxFilePath, [Parameter(ParameterSetName = 'Pkcs12')] [SecureString]$PfxPassword, [Parameter(Mandatory, ParameterSetName = 'Pem')] [ValidateNotNullOrEmpty()] [string]$CertificateFilePath, [Parameter(Mandatory, ParameterSetName = 'Pem')] [ValidateNotNullOrEmpty()] [string]$PrivateKeyFilePath, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { if (-not $PSCmdlet.ShouldProcess("Certificate '$Name' on $($params.Firewall)", 'Create')) { return } $payloadParams = @{ Operation = 'add' Name = $Name } if ($PSCmdlet.ParameterSetName -eq 'Pkcs12') { $payloadParams.PfxFilePath = $PfxFilePath if ($PSBoundParameters.ContainsKey('PfxPassword')) { $payloadParams.PfxPassword = $PfxPassword } } else { $payloadParams.CertificateFilePath = $CertificateFilePath $payloadParams.PrivateKeyFilePath = $PrivateKeyFilePath } try { $payload = New-SfosCertificateUploadPayload @payloadParams } catch { throw "Failed to create Certificate object '$Name': $($_.Exception.Message)" } try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $payload.InnerXml -MultipartFile $payload.MultipartFile ` -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to create Certificate object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'Certificate' -Action 'create' -Target $Name } } <# .SYNOPSIS Replaces the certificate material of an existing Certificate object on a Sophos Firewall. .DESCRIPTION Certificate has no field-by-field update: the whole certificate (and, usually, its private key) is replaced by whatever is uploaded, so this cmdlet always sends complete new material - there is nothing to read back and merge. It first confirms the named object exists and throws a clear "was not found" error if it does not, then re-uploads with operation="update" using the same PKCS#12/PEM shapes as New-SfosCertificate. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly, and an account with write permission for the Certificates area. .PARAMETER Name Required. Name of the existing Certificate object to replace. .PARAMETER PfxFilePath Required in the PKCS#12 form. Path to a local .pfx/.p12 file containing the new certificate and, usually, its private key. .PARAMETER PfxPassword Optional. Export password of the PFX file, as a SecureString. Never written to the console, help text or an error message in plain text. .PARAMETER CertificateFilePath Required in the PEM form. Path to a local PEM certificate file. Requires -PrivateKeyFilePath as well. .PARAMETER PrivateKeyFilePath Required in the PEM form. Path to a local, unencrypted PEM private key file matching -CertificateFilePath. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts the object name by property name, for example from Get-SfosCertificate. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the update, if the named object does not exist, or if the given file(s) do not exist. .EXAMPLE Set-SfosCertificate -Name 'PortalCertificate' -CertificateFilePath 'C:\certs\renewed.pem' -PrivateKeyFilePath 'C:\certs\renewed.key' -WhatIf Shows what the call would replace without sending it to the firewall. .EXAMPLE Set-SfosCertificate -Name 'PortalCertificate' -CertificateFilePath 'C:\certs\renewed.pem' -PrivateKeyFilePath 'C:\certs\renewed.key' Replaces the certificate and key of the named object. The cmdlet asks for confirmation before it writes. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/Certificate/operations/AddCertificate%26UpdateCertificate.html .LINK Get-SfosCertificate #> function Set-SfosCertificate { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateLength(1, 50)] [ValidatePattern('^[A-Za-z0-9_@.-]+$')] [string]$Name, [Parameter(Mandatory, ParameterSetName = 'Pkcs12')] [ValidateNotNullOrEmpty()] [string]$PfxFilePath, [Parameter(ParameterSetName = 'Pkcs12')] [SecureString]$PfxPassword, [Parameter(Mandatory, ParameterSetName = 'Pem')] [ValidateNotNullOrEmpty()] [string]$CertificateFilePath, [Parameter(Mandatory, ParameterSetName = 'Pem')] [ValidateNotNullOrEmpty()] [string]$PrivateKeyFilePath, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { $existing = @(Get-SfosCertificate -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -NameLike $Name ` -SkipCertificateCheck:$params.SkipCertificateCheck | Where-Object -FilterScript { $_.Name -eq $Name }) if ($existing.Count -eq 0) { throw "The Certificate object '$Name' was not found." } if (-not $PSCmdlet.ShouldProcess("Certificate '$Name' on $($params.Firewall)", 'Update')) { return } $payloadParams = @{ Operation = 'update' Name = $Name } if ($PSCmdlet.ParameterSetName -eq 'Pkcs12') { $payloadParams.PfxFilePath = $PfxFilePath if ($PSBoundParameters.ContainsKey('PfxPassword')) { $payloadParams.PfxPassword = $PfxPassword } } else { $payloadParams.CertificateFilePath = $CertificateFilePath $payloadParams.PrivateKeyFilePath = $PrivateKeyFilePath } try { $payload = New-SfosCertificateUploadPayload @payloadParams } catch { throw "Failed to update Certificate object '$Name': $($_.Exception.Message)" } try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $payload.InnerXml -MultipartFile $payload.MultipartFile ` -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to update Certificate object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'Certificate' -Action 'edit' -Target $Name } } <# .SYNOPSIS Removes a Certificate object from a Sophos Firewall. .DESCRIPTION Removes a Certificate object. Removal is measured to work for real against a live firewall - the object is gone from a following Get, not just reported as removed. Never remove the certificate the current connection authenticates with, and never remove a certificate that is still referenced elsewhere (a Web Server, an authentication policy, ...); the firewall documents dedicated failure codes for exactly that ("child exists"). It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly, and an account with write permission for the Certificates area. .PARAMETER Name Required. Name of the object to remove. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts the object name by property name, for example from Get-SfosCertificate. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the removal. .EXAMPLE Remove-SfosCertificate -Name 'PortalCertificate' -WhatIf Shows what the call would remove without sending it to the firewall. .EXAMPLE Remove-SfosCertificate -Name 'PortalCertificate' Removes the named object. The cmdlet asks for confirmation before it writes. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/Certificate/operations/Delete%20Certificate.html .LINK Get-SfosCertificate #> function Remove-SfosCertificate { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateLength(1, 50)] [ValidatePattern('^[A-Za-z0-9_@.-]+$')] [string]$Name, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { if (-not $PSCmdlet.ShouldProcess("Certificate '$Name' on $($params.Firewall)", 'Remove')) { return } $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name $inner = "<Remove><Certificate><Name>$nameEsc</Name></Certificate></Remove>" try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to remove Certificate object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'Certificate' -Action 'remove' -Target $Name } } <# .SYNOPSIS Writes the certificate and private key files of a Certificate object to disk. .DESCRIPTION A Get on Certificate answers as a downloaded tar archive, not as fields - the actual certificate and (usually) private key bytes only exist in that archive, never in the PSCustomObject Get-SfosCertificate returns. This cmdlet is named Export- rather than following the Export-Sfos<Entity>s / bulk-CSV convention used elsewhere in this module family (see the project rules, naming section), because it exports one object's actual file content to disk, not a CSV summary of many objects. It requests the single named object with an exact-match server-side filter, extracts the archive to -Path via ConvertFrom-SfosArchive (SophosFirewall.Core), and returns the full paths of every file written. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly. .PARAMETER Name Required. Name of the Certificate object to export. .PARAMETER Path Required. Directory to write the certificate/key file(s) to. Created if it does not exist. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts the object name by property name, for example from Get-SfosCertificate. .OUTPUTS System.String. The full path of every file written to -Path. Raises an error if the named object does not exist. .EXAMPLE Export-SfosCertificate -Name 'PortalCertificate' -Path 'C:\Sfos\Export' -WhatIf Shows what the call would write without contacting the firewall. .EXAMPLE Export-SfosCertificate -Name 'PortalCertificate' -Path 'C:\Sfos\Export' Writes the certificate (and private key, if the object has one) to the given directory and returns the file paths. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/Certificate/Certificate.html .LINK Get-SfosCertificate #> function Export-SfosCertificate { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateLength(1, 50)] [ValidatePattern('^[A-Za-z0-9_@.-]+$')] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Path, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { if (-not $PSCmdlet.ShouldProcess("Certificate '$Name' on $($params.Firewall)", "Export to '$Path'")) { return } $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name $inner = "<Get><Certificate><Filter><key name=`"Name`" criteria=`"=`">$nameEsc</key></Filter></Certificate></Get>" try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to export Certificate object '$Name': $($_.Exception.Message)" } if ($response.Content -is [byte[]]) { # Unpack to a scratch folder first. The archive also carries its own manifest # and a version marker; only the certificate files belong in -Path. # The help promises the target directory is created when it is missing. if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } $staging = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $staging -Force | Out-Null try { $archive = ConvertFrom-SfosArchive -Bytes $response.Content -ExtractTo $staging if (-not $archive.Entities) { throw "Failed to export Certificate object '$Name': the firewall returned a file response with no readable Entities.xml." } Assert-SfosApiReturnSuccess -Xml $archive.Entities -ObjectName 'Certificate' -Action 'export' -Target $Name $members = @($archive.Files | Where-Object -FilterScript { $_.Name -like './Files/*' -and $_.Path }) if (-not $members.Count) { throw "The Certificate object '$Name' was not found." } $written = foreach ($member in $members) { $target = Join-Path $Path (Split-Path -Path $member.Path -Leaf) Copy-Item -LiteralPath $member.Path -Destination $target -Force $target } return @($written) } finally { Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue } } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'Certificate' -Action 'export' -Target $Name throw "The Certificate object '$Name' was not found." } } #endregion #region CertificateAuthority <# .SYNOPSIS Builds the Set request body for a CertificateAuthority entity. .DESCRIPTION Builds the inner XML for a Set operation on CertificateAuthority, so New- and Set-SfosCertificateAuthority send the same entity shape. Type is a read-only field on this entity (Built-in/Uploaded/Internal, set by the firewall itself) and is never part of the request. .PARAMETER Operation The Set operation attribute, either 'add' or 'update'. .PARAMETER Name Name of the certificate authority object. .PARAMETER Format PEM or DER - the format of the uploaded certificate file. .PARAMETER CACertFileName Base file name of the certificate, matching the multipart upload's filename. .PARAMETER CAPrivateKeyFileName Optional. Base file name of the private key, matching the multipart upload's filename. .PARAMETER Password Optional. Passphrase protecting the private key, as a SecureString. It is decrypted inside this function and never leaves it as plain text. #> function ConvertTo-SfosCertificateAuthorityEntityXml { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [ValidateSet('add', 'update')] [string]$Operation, [Parameter(Mandatory)] [string]$Name, [string]$Format, [Parameter(Mandatory)] [string]$CACertFileName, [string]$CAPrivateKeyFileName, [SecureString]$Password ) $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name $formatEsc = ConvertTo-SfosXmlEscaped -Text $Format $certFileEsc = ConvertTo-SfosXmlEscaped -Text $CACertFileName $keyXml = '' if ($CAPrivateKeyFileName) { $keyXml = "<CAPrivateKeyFile>$(ConvertTo-SfosXmlEscaped -Text $CAPrivateKeyFileName)</CAPrivateKeyFile>" } $passwordXml = '' if ($Password) { # The plain value exists only inside this block, never as a parameter or a variable # the caller could inspect. $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password) try { $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) $passwordXml = "<Password>$(ConvertTo-SfosXmlEscaped -Text $plain)</Password>" } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } return @" <Set operation="$Operation"> <CertificateAuthority> <Name>$nameEsc</Name> <Format>$formatEsc</Format> <CACertFile>$certFileEsc</CACertFile> $keyXml $passwordXml </CertificateAuthority> </Set> "@ } <# .SYNOPSIS Retrieves Certificate Authority objects from a Sophos Firewall. .DESCRIPTION Returns Certificate Authority objects (SYSTEM > Certificates > Certificate Authorities). A matching Get answers with a tar archive (application/octet-stream), never with plain XML - only a Get with zero matches answers as XML. This cmdlet reads the archive's Entities.xml for the object list; it never reads the archive's per-object certificate files, because at least one factory-shipped CA (a name with non-ASCII characters) reproducibly desyncs the tar block chain for every file stored after it on this firmware, while Entities.xml itself stays intact and complete. When that happens, a warning names the last object whose file could still be read; the object list returned by this cmdlet is unaffected by it. Use Export-SfosCertificateAuthority to retrieve one object's actual certificate file. Because an unfiltered Get on this entity can be large (several hundred built-in trust anchors) and is where the archive defect above was found, prefer -NameLike when you only need specific objects. The cmdlet only reads; nothing on the firewall is changed. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly. .PARAMETER NameLike Optional. Returns only objects whose name contains the given text anywhere. Sent to the firewall as a server-side pre-filter and re-applied client-side. If omitted, every Certificate Authority object is returned. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AsXml Optional. Returns the raw XML elements from the archive's Entities.xml instead of PowerShell objects. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object per Certificate Authority, with the properties Name, Format, CACertFile, CAPrivateKeyFile, Type and HasPrivateKey (derived from CAPrivateKeyFile). Returns System.Xml.XmlElement when -AsXml is used, and an empty array when no object matches. .EXAMPLE Get-SfosCertificateAuthority -NameLike 'CorporateRootCA' Lists every Certificate Authority object whose name contains 'CorporateRootCA'. .EXAMPLE Get-SfosCertificateAuthority | Where-Object HasPrivateKey Lists every Certificate Authority object that was uploaded with its own signing key. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateAuthority/CertificateAuthority.html .LINK New-SfosCertificateAuthority #> function Get-SfosCertificateAuthority { [CmdletBinding()] param( [string]$NameLike, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, [switch]$AsXml ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $filterXml = '' if ($NameLike) { $nameLikeEsc = ConvertTo-SfosXmlEscaped -Text $NameLike $filterXml = ('<Filter><key name="Name" criteria="like">{0}</key></Filter>' -f $nameLikeEsc) } $inner = @" <Get> <CertificateAuthority> $filterXml </CertificateAuthority> </Get> "@ try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error retrieving CertificateAuthority objects: $($_.Exception.Message)" } if ($response.Content -is [string]) { # No binary body means nothing matched - a real match always answers as a tar # archive, never as XML, on this entity. $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CertificateAuthority' -Action 'get' return @() } $archive = ConvertFrom-SfosArchive -Bytes $response.Content if (-not $archive.Entities) { throw 'CertificateAuthority Get returned a binary response whose Entities.xml could not be located or parsed.' } $nodes = Select-Xml -Xml $archive.Entities -XPath '/Response/CertificateAuthority[Name]' | ForEach-Object -Process { $_.Node } $caObjects = foreach ($node in @($nodes)) { [PSCustomObject]@{ Name = [string]$node.Name Format = [string]$node.Format CACertFile = [string]$node.CACertFile CAPrivateKeyFile = [string]$node.CAPrivateKeyFile Type = [string]$node.Type HasPrivateKey = [bool]([string]$node.CAPrivateKeyFile) } } $caObjects = @($caObjects) if ($NameLike) { $caObjects = @($caObjects | Where-Object -FilterScript { $_.Name -like "*$NameLike*" }) } if ($AsXml) { $keptNames = @($caObjects | ForEach-Object -Process { $_.Name }) return @($nodes | Where-Object -FilterScript { $keptNames -contains $_.Name }) } return $caObjects } <# .SYNOPSIS Creates a Certificate Authority object on a Sophos Firewall. .DESCRIPTION Uploads a certificate authority (trust anchor, optionally with its signing private key) as a new Certificate Authority object (SYSTEM > Certificates > Certificate Authorities). This is a true multipart file upload: the request XML references the files by name, the files themselves travel as separate parts of the same request. Both files are checked for existence before anything is sent. Type is a read-only field on this entity and is never sent - the firewall always records an uploaded object as 'Uploaded' regardless of whether a private key was supplied. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly, and an account with write permission for the Certificates area. .PARAMETER Name Required. Name of the object. .PARAMETER CACertFilePath Required. Local path to the certificate file to upload. .PARAMETER Format Optional. PEM or DER. Defaults to PEM. .PARAMETER CAPrivateKeyFilePath Optional. Local path to the private key file, if this CA is also used for signing. .PARAMETER CAPassword Optional. Passphrase protecting the private key, as a SecureString. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts Name by property name. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the create. .EXAMPLE New-SfosCertificateAuthority -Name 'CorporateRootCA' -CACertFilePath 'C:\PKI\root.pem' -WhatIf Shows what the call would create without sending it to the firewall. .EXAMPLE New-SfosCertificateAuthority -Name 'CorporateRootCA' -CACertFilePath 'C:\PKI\root.pem' Uploads a trust-anchor-only Certificate Authority. The cmdlet asks for confirmation before it writes. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateAuthority/operations/AddCertificateAuthority%26EditCertificateAuthority.html .LINK Get-SfosCertificateAuthority #> function New-SfosCertificateAuthority { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateLength(1, 255)] [ValidatePattern('^[^,]+$')] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$CACertFilePath, [ValidateSet('PEM', 'DER')] [string]$Format = 'PEM', [string]$CAPrivateKeyFilePath, [SecureString]$CAPassword, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { if (-not (Test-Path -LiteralPath $CACertFilePath -PathType Leaf)) { throw "CACertFilePath '$CACertFilePath' does not exist." } if ($CAPrivateKeyFilePath -and -not (Test-Path -LiteralPath $CAPrivateKeyFilePath -PathType Leaf)) { throw "CAPrivateKeyFilePath '$CAPrivateKeyFilePath' does not exist." } if (-not $PSCmdlet.ShouldProcess("CertificateAuthority '$Name' on $($params.Firewall)", 'Create')) { return } $certFileName = [System.IO.Path]::GetFileName($CACertFilePath) $keyFileName = $null if ($CAPrivateKeyFilePath) { $keyFileName = [System.IO.Path]::GetFileName($CAPrivateKeyFilePath) } $inner = ConvertTo-SfosCertificateAuthorityEntityXml -Operation 'add' -Name $Name -Format $Format ` -CACertFileName $certFileName -CAPrivateKeyFileName $keyFileName -Password $CAPassword $multipartFiles = @{ CACertFile = $CACertFilePath } if ($CAPrivateKeyFilePath) { $multipartFiles['CAPrivateKeyFile'] = $CAPrivateKeyFilePath } try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -MultipartFile $multipartFiles -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to create CertificateAuthority object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CertificateAuthority' -Action 'create' -Target $Name } } <# .SYNOPSIS Updates a Certificate Authority object on a Sophos Firewall. .DESCRIPTION Replaces a Certificate Authority object's certificate (and, optionally, its private key) with a freshly uploaded file. Unlike most Set-* cmdlets in this suite, this is not a read-modify-write of text fields: Get-SfosCertificateAuthority can report that an object's private key file exists, but it cannot read back the key's actual bytes, so there is nothing this cmdlet could resend on the caller's behalf. -CACertFilePath is therefore mandatory on every call, matching the only update shape measured against a live appliance (the same file, or a new one, re-uploaded in full). If the object currently has a private key and you do not pass -CAPrivateKeyFilePath again, the resulting private-key state of the object after the update is unverified - resupply the key file whenever the object has one. Format is the one field this cmdlet can preserve from the object it reads first, so it is optional here. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly, and an account with write permission for the Certificates area. .PARAMETER Name Required. Name of the target object. .PARAMETER CACertFilePath Required. Local path to the certificate file to (re-)upload. .PARAMETER Format Optional. PEM or DER. If omitted, the existing value is kept. .PARAMETER CAPrivateKeyFilePath Optional. Local path to the private key file. See .DESCRIPTION for what happens when this is omitted on an object that already has a key. .PARAMETER CAPassword Optional. Passphrase protecting the private key, as a SecureString. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts Name by property name, for example from Get-SfosCertificateAuthority. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the update, or if the named object does not exist. .EXAMPLE Set-SfosCertificateAuthority -Name 'CorporateRootCA' -CACertFilePath 'C:\PKI\root-renewed.pem' -WhatIf Shows what the call would change without sending it to the firewall. .EXAMPLE Set-SfosCertificateAuthority -Name 'CorporateRootCA' -CACertFilePath 'C:\PKI\root-renewed.pem' Replaces the stored certificate with a renewed one. The cmdlet asks for confirmation before it writes. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateAuthority/operations/AddCertificateAuthority%26EditCertificateAuthority.html .LINK Get-SfosCertificateAuthority #> function Set-SfosCertificateAuthority { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateLength(1, 255)] [ValidatePattern('^[^,]+$')] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$CACertFilePath, [ValidateSet('PEM', 'DER')] [string]$Format, [string]$CAPrivateKeyFilePath, [SecureString]$CAPassword, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { if (-not (Test-Path -LiteralPath $CACertFilePath -PathType Leaf)) { throw "CACertFilePath '$CACertFilePath' does not exist." } if ($CAPrivateKeyFilePath -and -not (Test-Path -LiteralPath $CAPrivateKeyFilePath -PathType Leaf)) { throw "CAPrivateKeyFilePath '$CAPrivateKeyFilePath' does not exist." } $existing = @(Get-SfosCertificateAuthority -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -NameLike $Name ` -SkipCertificateCheck:$params.SkipCertificateCheck | Where-Object -FilterScript { $_.Name -eq $Name }) if ($existing.Count -eq 0) { throw "The CertificateAuthority object '$Name' was not found." } $targetFormat = if ($PSBoundParameters.ContainsKey('Format')) { $Format } else { [string]$existing[0].Format } if (-not $PSCmdlet.ShouldProcess("CertificateAuthority '$Name' on $($params.Firewall)", 'Update')) { return } $certFileName = [System.IO.Path]::GetFileName($CACertFilePath) $keyFileName = $null if ($CAPrivateKeyFilePath) { $keyFileName = [System.IO.Path]::GetFileName($CAPrivateKeyFilePath) } $inner = ConvertTo-SfosCertificateAuthorityEntityXml -Operation 'update' -Name $Name -Format $targetFormat ` -CACertFileName $certFileName -CAPrivateKeyFileName $keyFileName -Password $CAPassword $multipartFiles = @{ CACertFile = $CACertFilePath } if ($CAPrivateKeyFilePath) { $multipartFiles['CAPrivateKeyFile'] = $CAPrivateKeyFilePath } try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -MultipartFile $multipartFiles -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error updating CertificateAuthority object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CertificateAuthority' -Action 'edit' -Target $Name } } <# .SYNOPSIS Removes a Certificate Authority object from a Sophos Firewall. .DESCRIPTION Removes a Certificate Authority object. The cmdlet reads the object first and throws a clear "was not found" error for a nonexistent name, rather than passing through the firewall's own raw failure text for a not-found removal. .PARAMETER Name Required. Name of the object to remove. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs write permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts the object name by property name, for example from Get-SfosCertificateAuthority. .OUTPUTS None. The cmdlet writes no output and raises an error if the firewall rejects the removal (for example, because the object is still referenced elsewhere), or if the named object does not exist. .EXAMPLE Remove-SfosCertificateAuthority -Name 'CorporateRootCA' -WhatIf Shows what the call would remove without sending it to the firewall. .EXAMPLE Remove-SfosCertificateAuthority -Name 'CorporateRootCA' Removes the named object. The cmdlet asks for confirmation before it writes. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateAuthority/operations/Delete%20Certificate%20Authority.html .LINK Get-SfosCertificateAuthority #> function Remove-SfosCertificateAuthority { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateLength(1, 255)] [ValidatePattern('^[^,]+$')] [string]$Name, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { $existing = @(Get-SfosCertificateAuthority -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -NameLike $Name ` -SkipCertificateCheck:$params.SkipCertificateCheck | Where-Object -FilterScript { $_.Name -eq $Name }) if ($existing.Count -eq 0) { throw "The CertificateAuthority object '$Name' was not found." } if (-not $PSCmdlet.ShouldProcess("CertificateAuthority '$Name' on $($params.Firewall)", 'Remove')) { return } $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name $inner = "<Remove><CertificateAuthority><Name>$nameEsc</Name></CertificateAuthority></Remove>" try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Failed to remove CertificateAuthority object '$Name': $($_.Exception.Message)" } $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CertificateAuthority' -Action 'remove' -Target $Name } } <# .SYNOPSIS Extracts one Certificate Authority object's files from a Sophos Firewall to disk. .DESCRIPTION Downloads and extracts the certificate file (and private key file, if the object has one) of a single named Certificate Authority object. The cmdlet name is singular rather than the plural, CSV-bulk convention used elsewhere in this suite (Export-SfosIPHosts and similar): this exports one named object's binary files, not a table of many objects to a CSV/JSON file, so the bulk convention does not apply. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly. .PARAMETER Name Required. Name of the object to export. .PARAMETER Path Required. Directory to write the extracted file(s) to. Created if it does not exist. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .INPUTS System.Management.Automation.PSCustomObject. Accepts Name by property name. .OUTPUTS System.String. The full path of every file written to -Path. .EXAMPLE Export-SfosCertificateAuthority -Name 'CorporateRootCA' -Path 'C:\Sfos\Export' Writes the certificate authority's certificate file (and its private key file, if present) to the given directory and returns their paths. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateAuthority/CertificateAuthority.html .LINK Get-SfosCertificateAuthority #> function Export-SfosCertificateAuthority { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateLength(1, 255)] [ValidatePattern('^[^,]+$')] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Path, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session ) begin { $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters } process { $nameEsc = ConvertTo-SfosXmlEscaped -Text $Name $inner = @" <Get> <CertificateAuthority> <Filter><key name="Name" criteria="=">$nameEsc</key></Filter> </CertificateAuthority> </Get> "@ if (-not $PSCmdlet.ShouldProcess("CertificateAuthority '$Name' on $($params.Firewall)", "Export to '$Path'")) { return } try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error retrieving CertificateAuthority object '$Name': $($_.Exception.Message)" } if ($response.Content -is [string]) { $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CertificateAuthority' -Action 'get' -Target $Name throw "The CertificateAuthority object '$Name' was not found." } # Unpack to a scratch folder first. The archive also carries its own manifest # and a version marker; only the certificate files belong in -Path. # The help promises the target directory is created when it is missing. if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } $staging = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $staging -Force | Out-Null try { $archive = ConvertFrom-SfosArchive -Bytes $response.Content -ExtractTo $staging $members = @($archive.Files | Where-Object -FilterScript { $_.Name -like './Files/*' -and $_.Path }) if ($members.Count -eq 0) { throw "The CertificateAuthority object '$Name' was found, but no file could be extracted from the archive." } $written = foreach ($member in $members) { $target = Join-Path $Path (Split-Path -Path $member.Path -Leaf) Copy-Item -LiteralPath $member.Path -Destination $target -Force $target } return @($written) } finally { Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue } } } #endregion #region CRL <# .SYNOPSIS Retrieves Certificate Revocation List (CRL) objects from a Sophos Firewall. .DESCRIPTION Returns CRL objects (SYSTEM > Certificates > Certificate Revocation Lists). A matching Get answers with a tar archive (application/octet-stream), the same file-response shape as CertificateAuthority; this cmdlet reads the object list from the archive's Entities.xml, the same way Get-SfosCertificateAuthority does. Only Get-SfosCRL is provided for this entity. Uploading a CRL through the documented Add CRL operation was measured, repeatedly and with several structurally valid CRL files (including one deliberately expired one, to check whether the content is evaluated at all), to fail with a generic firewall error regardless of content - the dedicated "CRL expired" status the API documents for that case was never reached. There is no known way to create, update or remove a CRL object through this API on the firmware this was measured against, so New-/Set-/Remove-SfosCRL are not implemented. The cmdlet only reads; nothing on the firewall is changed. It needs an open connection from Connect-SfosFirewall, or the connection parameters supplied directly. .PARAMETER NameLike Optional. Returns only objects whose name contains the given text anywhere. Sent to the firewall as a server-side pre-filter and re-applied client-side. If omitted, every CRL object is returned. .PARAMETER Firewall Optional. Host name or IP address of the firewall. If omitted, the value from the current connection is used. .PARAMETER Port Optional. TCP port of the management API, usually 4444. If omitted, the value from the current connection is used. .PARAMETER Username Optional. User name for the API login. The account needs read permission for the Certificates area. If omitted, the value from the current connection is used. .PARAMETER Password Optional. Password for the API login, as a SecureString. If omitted, the value from the current connection is used. .PARAMETER SkipCertificateCheck Optional. Accepts the firewall certificate without validating it. Use this only for appliances that still present a self-signed certificate. If omitted, the certificate is validated. .PARAMETER Session Optional. A session object from Connect-SfosFirewall, or the name of a session that was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall when you work with more than one at a time. Any connection parameter you pass explicitly still takes precedence. If omitted, the stored default connection is used. .PARAMETER AsXml Optional. Returns the raw XML elements from the archive's Entities.xml instead of PowerShell objects. .INPUTS None. This cmdlet does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. One object per CRL, with the properties Name and CRLFile. Returns System.Xml.XmlElement when -AsXml is used, and an empty array when no object matches. .EXAMPLE Get-SfosCRL Lists every Certificate Revocation List object on the firewall of the current connection. .LINK https://docs.sophos.com/nsg/sophos-firewall/22.0/api/SYSTEM/Certificates/CertificateCRL/CertificateCRL.html #> function Get-SfosCRL { [CmdletBinding()] param( [string]$NameLike, [string]$Firewall, [int]$Port, [string]$Username, [SecureString]$Password, [switch]$SkipCertificateCheck, [object]$Session, [switch]$AsXml ) $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters $filterXml = '' if ($NameLike) { $nameLikeEsc = ConvertTo-SfosXmlEscaped -Text $NameLike $filterXml = ('<Filter><key name="Name" criteria="like">{0}</key></Filter>' -f $nameLikeEsc) } $inner = @" <Get> <CRL> $filterXml </CRL> </Get> "@ try { $response = Invoke-SfosApi -Firewall $params.Firewall ` -Port $params.Port ` -Username $params.Username ` -Password $params.Password ` -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop } catch { throw "Error retrieving CRL objects: $($_.Exception.Message)" } if ($response.Content -is [string]) { $XmlResponse = [xml]$response.Content Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'CRL' -Action 'get' return @() } $archive = ConvertFrom-SfosArchive -Bytes $response.Content if (-not $archive.Entities) { throw 'CRL Get returned a binary response whose Entities.xml could not be located or parsed.' } $nodes = Select-Xml -Xml $archive.Entities -XPath '/Response/CRL[Name]' | ForEach-Object -Process { $_.Node } $crlObjects = foreach ($node in @($nodes)) { [PSCustomObject]@{ Name = [string]$node.Name CRLFile = [string]$node.CRLFile } } $crlObjects = @($crlObjects) if ($NameLike) { $crlObjects = @($crlObjects | Where-Object -FilterScript { $_.Name -like "*$NameLike*" }) } if ($AsXml) { $keptNames = @($crlObjects | ForEach-Object -Process { $_.Name }) return @($nodes | Where-Object -FilterScript { $keptNames -contains $_.Name }) } return $crlObjects } #endregion |