src/Signatures.ps1

<#
    Signature-related functions of module 'RdpToolkit'

    RdpToolkit is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    RdpToolkit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with RdpToolkit. If not, see <https://www.gnu.org/licenses/>.
#>


Function Remove-RdcFileSignature {
    [OutputType([void])]
    Param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [Alias('File', 'Files', 'RdcFile', 'RdcFiles')]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("\.rdp$")]
        [IO.FileInfo[]] $Path,

        [Switch] $KeepBlankLines
    )

    Process {
        # The nested loops allow this cmdlet to operate on wildcards.
        ForEach ($Argument in $Path) {
            ForEach ($File in (Get-Item $Argument)) {
                $content = Get-Content -Path $File
                If (-Not $KeepBlankLines) {
                    $content = ($content | Where-Object {$_.trim() -ne ""})
                }
                $content = ($content | Select-String -NotMatch -Pattern '^sign(ature|scope):*')
                Set-Content -Path $File -Value $content -Force
            }
        }
    }
}

Function Add-RdcFileSignature {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')]
    [OutputType([void])]
    Param(
        [Parameter(Mandatory, Position=0, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("\.rdp$")]
        [IO.FileInfo[]] $Files,

        [Alias('Certificate', 'Thumbprint')]
        [AllowNull()]
        [String] $CertificateThumbprint = $null,

        [IO.FileInfo] $PathToRDPSign = (Join-Path -Path $env:WinDir -ChildPath 'System32' -AdditionalChildPath 'rdpsign.exe')
    )

    Begin {
        If (-Not $IsWindows) {
            Throw [PlatformNotSupportedException]::new('Signing .rdp files can only be done under Microsoft Windows.')
        }

        # If the user specified a certificate's thumbprint, we'll use that one.
        # Otherwise, call Get-CodeSigningCertificates to pick one automatically.
        If ("" -eq $CertificateThumbprint) {
            $CertificateThumbprint = Get-CodeSigningCertificates
        }
        Write-Verbose -Message "Signing with the certificate $CertificateThumbprint."
    }

    Process {
        # The nested loops allow this cmdlet to operate on wildcards.
        ForEach ($Argument in $Files) {
            ForEach ($File in (Get-Item $Argument)) {
                $File = Get-Item $File
                Write-Verbose "Signing the file $($File.Name)"

                If ($PSCmdlet.ShouldProcess($File, 'Add digital signature')) {
                    $result = Invoke-RdpSign -Thumbprint $CertificateThumbprint -File $File -PathToRDPSign $PathToRDPSign
                    If ($result -eq 0) {
                        Write-Information "Signed $($File.Name)"
                    } Else {
                        Write-Warning "Did not sign the file $($File.Name)."
                    }
                }
            }
        }
    }
}

Function Get-CodeSigningCertificates {
    [OutputType([String])]
    [CmdletBinding()]
    Param()

    $Thumbprint = $null
    $Certificates = Get-ChildItem (Join-Path -Path 'Cert:' -ChildPath 'CurrentUser' -AdditionalChildPath 'My') -CodeSigning -ErrorAction Stop `
                      | Where-Object {$_.NotBefore -le (Get-Date) -and $_.NotAfter -ge (Get-Date)}

    If ($Certificates.Count -gt 0) {
        $Thumbprint = $Certificates | Select-Object -First 1 -ExpandProperty Thumbprint
        Write-Verbose "Using the certificate with thumbprint $Thumbprint."
        Write-Debug ($Certificates | Select-Object -First 1 -ExpandProperty Thumbprint)
    } Else {
        Throw [PowerShell.Commands.CertificateNotFoundException]::new('A valid code signing certificate could not be found.')
    }
    Return $Thumbprint
}

Function Invoke-RdpSign {
    [OutputType([bool])]
    Param(
        [Parameter(Mandatory, Position=0)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("\.rdp$")]
        [IO.FileInfo] $File,

        [ValidateNotNullOrEmpty()]
        [String] $Thumbprint,

        [IO.FileInfo] $PathToRDPSign = (Join-Path -Path $env:WinDir -ChildPath 'System32' -AdditionalChildPath 'rdpsign.exe')
    )

    If (Test-Path -Path $PathToRDPSign -PathType Leaf) {
        $output = Start-Process -FilePath $PathToRDPSign -ArgumentList @("/sha256 $Thumbprint", "`"$($File.FullName)`"") -PassThru -Wait -WindowStyle Hidden | Out-Null
        Return ($output.ExitCode -eq 0)
    } Else {
        Throw [IO.FileNotFoundException]::new("rdpsign was not found at $PathToRdpSign")
    }
}