source/Public/Protect-GuestConfigurationPackage.ps1 (186 lines of code) (raw):

<# .SYNOPSIS Signs a Guest Configuration package using either a certificate on Windows or GPG keys on Linux. .PARAMETER Path The path of the Guest Configuration package to sign. .PARAMETER Certificate The 'Code Signing' certificate to sign the package with. This is only supported on Windows. This certificate will need to be installed on machines running this package. See examples for how to generate a test certificate. .PARAMETER PrivateGpgKeyPath The private GPG key path to sign the package with. This is only supported on Linux. See examples for how to generate this key. .PARAMETER PublicGpgKeyPath The public GPG key path to sign the package with. This is only supported on Linux. This key will need to be installed on any machines running this package at the path: /usr/local/share/ca-certificates/gc/pub_keyring.gpg See examples for how to generate this key. .EXAMPLE # Windows # Please note that self-signed certs should not be used in production, only testing # Create a code signing cert $myCert = New-SelfSignedCertificate -Type 'CodeSigningCert' -DnsName 'GCEncryptionCertificate' -HashAlgorithm 'SHA256' # Export the certificates $myPwd = ConvertTo-SecureString -String 'Password1234' -Force -AsPlainText $myCert | Export-PfxCertificate -FilePath 'C:\demo\GCPrivateKey.pfx' -Password $myPwd $myCert | Export-Certificate -FilePath 'C:\demo\GCPublicKey.cer' -Force # Import the certificate Import-PfxCertificate -FilePath 'C:\demo\GCPrivateKey.pfx' -Password $myPwd -CertStoreLocation 'Cert:\LocalMachine\My' # Sign the package $certToSignThePackage = Get-ChildItem -Path cert:\LocalMachine\My | Where-Object {($_.Subject-eq "CN=GCEncryptionCertificate") } Protect-GuestConfigurationPackage -Path C:\demo\AuditWindowsService.zip -Certificate $certToSignThePackage -Verbose .EXAMPLE # Linux # Generate gpg key gpg --gen-key # Export public key gpg --output public.gpg --export <email-id used to generate gpg key> # Export private key gpg --output private.gpg --export-secret-key <email-id used to generate gpg key> # Sign Linux policy package Protect-GuestConfigurationPackage -Path ./not_installed_application_linux.zip -PrivateGpgKeyPath ./private.gpg -PublicGpgKeyPath ./public.gpg -Verbose .OUTPUTS Returns the name of the configuration in the package and the path of the output signed Guest Configuration package. $result = [PSCustomObject]@{ Name = $configurationName Path = $signedPackageFilePath } #> function Protect-GuestConfigurationPackage { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Certificate")] [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "GpgKeys")] [ValidateNotNullOrEmpty()] [String] $Path, [Parameter(Mandatory = $true, ParameterSetName = "Certificate")] [ValidateNotNullOrEmpty()] [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Parameter(Mandatory = $true, ParameterSetName = "GpgKeys")] [ValidateNotNullOrEmpty()] [String] $PrivateGpgKeyPath, [Parameter(Mandatory = $true, ParameterSetName = "GpgKeys")] [ValidateNotNullOrEmpty()] [String] $PublicGpgKeyPath ) $os = Get-OSPlatform if ($PSCmdlet.ParameterSetName -eq 'GpgKeys' -and $os -ine 'Linux') { throw 'GPG key signing is only supported on Linux.' } elseif ($PSCmdlet.ParameterSetName -eq 'Certificate' -and $os -ine 'Windows') { throw 'Certificate signing is only supported on Windows.' } $Path = Resolve-RelativePath -Path $Path if (-not (Test-Path $Path -PathType 'Leaf')) { throw "Could not find a file at the path '$Path'" } if ($PSCmdlet.ParameterSetName -eq 'GpgKeys') { $PrivateGpgKeyPath = Resolve-RelativePath -Path $PrivateGpgKeyPath if (-not (Test-Path -Path $PrivateGpgKeyPath)) { throw "Could not find the private GPG key file at the path '$PrivateGpgKeyPath'" } $PublicGpgKeyPath = Resolve-RelativePath -Path $PublicGpgKeyPath if (-not (Test-Path -Path $PublicGpgKeyPath)) { throw "Could not find the public GPG key file at the path '$PublicGpgKeyPath'" } } $package = Get-Item -Path $Path $packageDirectory = $package.Directory $packageFileName = [System.IO.Path]::GetFileNameWithoutExtension($Path) $signedPackageName = "$($packageFileName)_signed.zip" $signedPackageFilePath = Join-Path -Path $packageDirectory -ChildPath $signedPackageName if (Test-Path -Path $signedPackageFilePath) { $null = Remove-Item -Path $signedPackageFilePath -Force } $tempDirectory = Reset-GCWorkerTempDirectory try { # Unzip policy package. $null = Expand-Archive -Path $Path -DestinationPath $tempDirectory # Find and validate the mof file $mofFilePattern = '*.mof' $mofChildItems = @( Get-ChildItem -Path $tempDirectory -Filter $mofFilePattern -File ) if ($mofChildItems.Count -eq 0) { throw "No .mof file found in the package. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package." } elseif ($mofChildItems.Count -gt 1) { throw "Found more than one .mof file in the extracted Guest Configuration package. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package." } $mofFile = $mofChildItems[0] $configurationName = $mofFile.BaseName if ($PSCmdlet.ParameterSetName -eq 'Certificate') { # Create catalog file $catalogFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.cat" if (Test-Path -Path $catalogFilePath) { $null = Remove-Item -Path $catalogFilePath -Force } Write-Verbose -Message "Creating the catalog file at '$catalogFilePath'" $null = New-FileCatalog -Path $tempDirectory -CatalogFilePath $catalogFilePath -CatalogVersion 2 # Sign catalog file Write-Verbose -Message "Signing the catalog file at '$catalogFilePath'" $codeSignOutput = Set-AuthenticodeSignature -Certificate $Certificate -FilePath $catalogFilePath # Validate that file was signed $signature = Get-AuthenticodeSignature -FilePath $catalogFilePath if ($null -eq $signature.SignerCertificate) { throw $codeSignOutput.StatusMessage } elseif ($signature.SignerCertificate.Thumbprint -ne $Certificate.Thumbprint) { throw $codeSignOutput.StatusMessage } } else { $ascFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.asc" $hashFilePath = Join-Path -Path $tempDirectory -ChildPath "$configurationName.sha256sums" Write-Verbose -Message "Creating hash file at '$hashFilePath'" Push-Location -Path $tempDirectory try { bash -c "find ./ -type f -print0 | xargs -0 sha256sum | grep -v sha256sums > $hashFilePath" } finally { Pop-Location } Write-Verbose -Message "Signing hash file at '$hashFilePath'" gpg --import $PrivateGpgKeyPath gpg --no-default-keyring --keyring $PublicGpgKeyPath --output $ascFilePath --armor --detach-sign $hashFilePath } # Zip the signed Guest Configuration package # NOTE: We are NOT using Compress-Archive here because it does not zip empty folders (like an empty Modules folder) into the package Write-Verbose -Message "Creating the signed Guest Configuration package at '$signedPackageFilePath'" $null = [System.IO.Compression.ZipFile]::CreateFromDirectory($tempDirectory, $signedPackageFilePath) $result = [PSCustomObject]@{ Name = $configurationName Path = $signedPackageFilePath } } finally { $null = Reset-GCWorkerTempDirectory } return $result }