Skip to content

Instantly share code, notes, and snippets.

@indented-automation
Created September 12, 2017 08:26
Show Gist options
  • Save indented-automation/3e31d12347763e6f1d264fc3219522f8 to your computer and use it in GitHub Desktop.
Save indented-automation/3e31d12347763e6f1d264fc3219522f8 to your computer and use it in GitHub Desktop.
GPG
<#
Module file content:
CmdLet Name Category Access modifier Updated
----------- -------- --------------- -------
Set-SessionKey Passphrase management Public 02/10/2012
Get-Key Keyring management Public 02/10/2012
Import-Key Keyring management Public 02/10/2012
Export-Key Keyring management Public 02/10/2012
Remove-Key Keyring management Public 02/10/2012
Protect-Key Keyring management Public 02/10/2012
Protect-File File management Public 02/10/2012
Unprotect-File File management Public 02/10/2012
#>
New-Variable -Name GPG -Scope Script -Force
if ((Get-WmiObject Win32_OperatingSystem).OSArchitecture -eq "64-bit") {
$ProgFiles = "C:\Progra~2\gnu\GnuPG"
} else {
$ProgFiles = "C:\Progra~1\gnu\GnuPG"
}
if (Test-Path "$ProgFiles\gpg2.exe") {
$GPG = "$ProgFiles\gpg2.exe"
} elseif (Test-Path "$ProgFiles\gpg.exe") {
$GPG = "$ProgFiles\gpg.exe"
}
if (-not $GPG) {
Write-Warning "Indented.GPG: Failed to locate GPG executable, aborting module load."
break
}
New-Variable -Name Key -Scope Script -Value "" -Force
#
# CmdLets
#
function Set-SessionKey {
# .SYNOPSIS
# Set and store a passphrase for a key.
# .DESCRIPTION
# Set-SessionKey populates a script-level variable with an encrypted form of the passphrase. The value is only accessible by the module (not the session).
#
# Later versions of GPG do not require this, an agent process is spawned (by GPG) to acquire a passphrase.
# .EXAMPLE
# Set-SessionKey
[CmdLetBinding()]
param( )
$Script:Key = Read-Host -Prompt "Please enter your passphrase" -AsSecureString
}
function Get-Key {
# .SYNOPSIS
# Get keys stored on a GPG keyring.
# .DESCRIPTION
# Get-Key prepares a command to pass to the GPG command line executable. The output from the command is parsed and presented as a PSObject.
#
# Filtering options are applied after all keys have been returned and processed.
# .PARAMETER Identity
# Filters the return value to the specified Identity. Matches by Fingerprint, Owner or Email.
# .PARAMETER Private
# Causes Get-Key to ask for private keys instead of public keys.
# .INPUTS
# System.String
# .OUTPUTS
# Indented.GPG.Key
# .EXAMPLE
# Get-Key "*dent*"
[CmdLetBinding()]
param(
[String]$Identity = '*',
[Switch]$Private
)
$Command = "--list-sigs"
if ($Private) { $Command = "--list-secret-keys" }
$Keys = & $GPG $Command, "--fingerprint" | Where-Object { $_ -notmatch '\w{3}ring\.gpg|^-*$|^sig\s3|^sub' }
$i = 0
$( while ($i -lt $Keys.Count) {
if ($Keys[$i] -match '^(pub|sec)') {
$Key = New-Object PsObject -Property ([Ordered]@{
Owner = "";
Email = "";
Fingerprint = "";
Type = ($Keys[$i] -replace '^(pub|sec)\s*\d*|/.*');
Length = ($Keys[$i] -replace '^(pub|sec)\s*|\w/.*');
ValidFrom = "";
Signatures = @();
})
$Key.PsObject.TypeNames.Add("Indented.GPG.Key")
if ($Keys[$i] -match '\d{4}(-\d{2}){2}') {
$Key.ValidFrom = [DateTime]::ParseExact($Matches[0], "yyyy-MM-dd", $null)
}
$i++; $Key.Fingerprint = $Keys[$i] -replace '.*=|\s'
$i++; $Key.Owner = $Keys[$i] -replace '^uid\s*|\s<.*'
$Key.Email = $Keys[$i] -replace '.*<|>'
if (-not $Private) {
$i++
while (($Keys[$i] -replace '.*<|>') -ne $Key.Email -and $i -lt $Keys.Count -and $Keys[$i] -match '^sig') {
if ($Keys[$i] -notmatch 'User ID not found') {
$Key.Signatures += $Keys[$i] -replace '.*<|>|.*\[|\]'
}
$i++
}
}
$Key
}
if ($Keys[$i] -notmatch '^pub') { $i++ }
} ) | Where-Object { $_.Owner -like $Identity -or $_.Email -like $Identity -or $_.Fingerprint -eq $Identity }
}
function Import-Key {
# .SYNOPSIS
# Import a public key into to the keyring from a file.
# .DESCRIPTION
# Import-Key allows the addition of keys from a file. Import-Key accepts pipeline input from Get-ChildItem.
# .PARAMETER Path
# The file containing the key to be imported. Wildcards are not permissible, however pipeline input from Get-ChildItem is supported.
# .INPUTS
# System.String
# .EXAMPLE
# Get-ChildItem C:\Keys\*.asc | Import-Key
[CmdLetBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("FullName")]
[ValidateScript( { Test-Path $_ -PathType Leaf } )]
[String]$Path
)
process {
& $GPG "--import", $Path
}
}
function Export-Key {
# .SYNOPSIS
# Exports a key to an ASCII Armour file.
# .DESCRIPTION
# Export-Key attempts to export keys to a file named after the key identity. For example, a key with with Identity set to "Chris Dent" will be named ChrisDent.asc.
#
# Export-Key accepts pipeline input from Get-Key.
# .PARAMETER Identity
# Identity is a value which can be used to uniquely identify the key. Both Owner and Fingerprint are acceptable as these tend to be unique.
# .PARAMETER Folder
# The key will be exported to a file name created from the key identity (automatically). The key file will be stored in the folder set using this parameter. By default, keys will be exported to the current directory.
# .PARAMETER Private
# Export a private key instead of public keys.
# .INPUTS
# System.String
# .EXAMPLE
# Get-Key name | Export-Key
[CmdLetBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("Owner", "Fingerprint")]
[String]$Identity,
[ValidateScript( { Test-Path $_ -PathType Container } )]
[String]$Folder = $PWD.Path,
[Switch]$Private
)
process {
$Command = "--export"
if ($Private) { $Command = "--export-secret-key" }
& $GPG "$Command", "-a", "$Identity" | Out-File "$Folder\$($Identity -replace '\s').asc" -Encoding ASCII
}
}
function Remove-Key {
# .SYNOPSIS
# Remove a key from the GPG keyring.
# .DESCRIPTION
# Remove-Key attempts to delete public keys from the keyring.
#
# Remove-Key accepts pipeline input from Get-Key.
# .PARAMETER Force
# Key removal will prompt for confirmation. This behaviour may be changed using the Force parameter.
# .PARAMETER Identity
# Identity is a value which can be used to uniquely identify the key. Both Owner and Fingerprint are acceptable as these tend to be unique.
# .INPUTS
# System.String
# .EXAMPLE
# Get-Key "*dent*" | Remove-Key
[CmdLetBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("Name", "Fingerprint")]
[String]$Identity,
[Switch]$Force
)
process {
if ($Force) {
& $GPG "--batch", "--yes", "--delete-keys", $Identity
} else {
& $GPG "--delete-keys", $Identity
}
}
}
function Protect-Key {
# .SYNOPSIS
# Protect-Key is used to add a signature to a public key.
# .DESCRIPTION
# Add a signature from a private key to a public key on the keyring.
# .PARAMETER Identity
# Identity is a value which can be used to uniquely identify the key. Both Owner and Fingerprint are acceptable as these tend to be unique.
# .INPUTS
# System.String
# .EXAMPLE
# Get-Key "John Doe" | Protect-Key
[CmdLetBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("Fingerprint", "Owner")]
[String]$Identity
)
process {
if ($Key -is [Security.SecureString]) {
$Passphrase = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Key))
}
if ($Passphrase) {
$Passphrase | & $GPG "--passphrase-fd", 0, "--batch", "--yes", "--sign-key", $PublicKey
} else {
& $GPG "--sign-key", $PublicKey
}
}
}
function Protect-File {
# .SYNOPSIS
# Protect a file by encrypting the content.
# .DESCRIPTION
# Protect file encrypts file content using public keys from the GPG keyring. The current users public key is included by default.
# .PARAMETER Path
# The file containing the content to be encrypted. Wildcards are not permissible, however pipeline input from Get-ChildItem is supported.
# .PARAMETER IncludeKeys
# Specify additional public keys to include when encrypting the file (to let others decrypt the file). All keys must exist on the keyring.
# .PARAMETER Group
# Additional public keys may be selected using Active Directory group membership. Multiple group names may be defined.
# .INPUTS
# System.String
# .EXAMPLE
# Protect-File C:\Temp\Test.txt -IncludeKeys "jane.doe@domain.example", "john.doe@domain.example"
# .EXAMPLE
# Protect-File C:\Temp\Test.txt -IncludeKeys "An AD Group", "Another AD Group"
[CmdLetBinding(DefaultParameterSetName = "KeyList")]
param(
[Parameter(Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateScript( { Test-Path $_ - } )]
[Alias("FullName")]
[String]$Path,
[Parameter(ParameterSetName = "KeyList")]
[String[]]$IncludeKeys,
[Parameter(ParameterSetName = "Group")]
[String[]]$Group
)
process {
if ($File -notmatch '^".+"$') {
$File = """$Path"""
} Else {
$File = $Path
}
if ($pscmdlet.ParameterSetName -eq "Group") {
# Cache a parsed copy of the keyring in memory for a while
$KeyCache = Get-Key
$IncludeKeys = $Group | ForEach-Object {
([ADSISearcher]"(&(objectClass=group)(name=$_))").FindAll() | ForEach-Object {
$DN = $_.Properties["distinguishedname"][0]
([ADSISearcher]"(&(objectClass=user)(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:=$DN))").FindAll() |
ForEach-Object {
$User = $_
# Attempt to match the user to a valid key
if ($KeyCache | Where-Object { $_.Email -match $User.Properties["mail"][0] }) {
$User.Properties["mail"][0]
} elseif ($KeyCache | Where-Object { $_.Owner -match $User.Properties["name"][0] }) {
$User.Properties["name"][0]
} elseif ($KeyCache | Where-Object { $_.Owner -match $User.Properties["samaccountname"][0] }) {
$User.Properties["samaccountname"][0]
} elseif ($KeyCache | Where-Object { $_.Owner -match ($User.Properties["name"][0] -replace ' ') }) {
$User.Properties["name"][0] -replace ' '
} else {
$LooseMatch = Get-Key "$($User.Properties["name"][0]) (*)"
if (([Array]$LooseMatch).Count -eq 1) {
$LooseMatch.Email
}
}
}
}
}
Write-Verbose "Protect-File: Retrieved keys from Active Directory:"
$IncludeKeys | ForEach-Object { Write-Verbose $_ }
}
$CmdArgs = "--encrypt", "-a", "--recipient", (Get-Key -Private).Email
If ($IncludeKeys) {
$IncludeKeys | ForEach-Object {
$CmdArgs += "--recipient", """$_"""
}
}
$CmdArgs += $File
& $GPG $CmdArgs
}
}
function Unprotect-File {
# .SYNOPSIS
# Unprotects, or decrypts, the specified file.
# .DESCRIPTION
# Unprotect-File attempts to decrypt the specified file using the current private key.
# .PARAMETER Path
# The file containing the content to be decrypted. Wildcards are not permissible, however pipeline input from Get-ChildItem is supported.
# .PARAMETER ToFile
# By default, Decrypt-File outputs to the console. ToFile forces the CmdLet to write the decrypted content to an ASCII formatted file. The file name will be preserved, the extension denoting encryption type will be removed.
# .PARAMETER AsCsv
# Converts output from CSV format when displaying to output to the console.
# .INPUTS
# System.String
# .EXAMPLE
# Unprotect-File C:\temp\somefile.txt.asc
# .EXAMPLE
# Get-ChildItem | Unprotect-File -ToFile
# .EXAMPLE
# Unprotect-File c:\temp\somefile.csv.asc -AsCsv | Format-Table
[CmdLetBinding(DefaultParameterSetName = "CSV")]
param(
[Parameter(Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateScript( { Test-Path $_ -PathType Leaf } )]
[Alias("FullName")]
[String]$Path,
[Parameter(ParameterSetName = "CSV")]
[Switch]$AsCsv,
[Parameter(ParameterSetName = "FILE")]
[Switch]$ToFile
)
process {
if ($Key -is [Security.SecureString]) {
$Passphrase = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Key))
}
if ($Passphrase) {
$Content = $Passphrase | & $GPG "--decrypt", "--passphrase-fd", 0, $Path
} else {
$Content = & $GPG "--decrypt", $Path
}
if ($Content) {
if ($ToFile) {
$Content | Out-File $($Path -replace '\.(asc)|(gpg)') -Encoding ASCII
} elseif ($AsCSV) {
$Content | ConvertFrom-Csv
} else {
$Content
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment