@@ -0,0 +1,202 @@
|
||||
# Detect or generate a GPG signing key. Print key id only. Never export secret keys.
|
||||
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Email,
|
||||
[switch]$ForceGenerate
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. (Join-Path $PSScriptRoot 'common.ps1')
|
||||
|
||||
function Resolve-Gpg {
|
||||
$cmd = Get-Command gpg -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
|
||||
$candidates = @(
|
||||
(Join-Path $env:ProgramFiles 'GnuPG\bin\gpg.exe'),
|
||||
(Join-Path ${env:ProgramFiles(x86)} 'GnuPG\bin\gpg.exe'),
|
||||
(Join-Path $env:ProgramFiles 'Git\usr\bin\gpg.exe')
|
||||
)
|
||||
foreach ($path in $candidates) {
|
||||
if ($path -and (Test-Path -LiteralPath $path)) {
|
||||
return $path
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-SecretKeyForEmail {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$GpgPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EmailAddress
|
||||
)
|
||||
|
||||
$prevEap = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
$lines = Invoke-GitSkillsNative -FilePath $GpgPath -ArgumentList @('--batch', '--list-secret-keys', '--with-colons')
|
||||
}
|
||||
finally {
|
||||
$ErrorActionPreference = $prevEap
|
||||
}
|
||||
|
||||
$currentId = ''
|
||||
$currentFpr = ''
|
||||
$matchedId = ''
|
||||
$matchedFpr = ''
|
||||
|
||||
foreach ($line in $lines) {
|
||||
$parts = $line.Split(':')
|
||||
if ($parts.Count -lt 2) { continue }
|
||||
$type = $parts[0]
|
||||
|
||||
if ($type -eq 'sec' -or $type -eq 'ssb') {
|
||||
if ($type -eq 'sec') {
|
||||
$currentId = $parts[4]
|
||||
$currentFpr = ''
|
||||
}
|
||||
}
|
||||
elseif ($type -eq 'fpr' -and $parts.Count -ge 10) {
|
||||
$fpr = $parts[9]
|
||||
if ([string]::IsNullOrWhiteSpace($currentFpr)) {
|
||||
$currentFpr = $fpr
|
||||
if ([string]::IsNullOrWhiteSpace($currentId) -and $fpr.Length -ge 16) {
|
||||
$currentId = $fpr.Substring($fpr.Length - 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($type -eq 'uid') {
|
||||
$uid = if ($parts.Count -ge 10) { $parts[9] } else { '' }
|
||||
if ($uid -match [regex]::Escape($EmailAddress)) {
|
||||
$matchedId = $currentId
|
||||
$matchedFpr = $currentFpr
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($matchedId)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
KeyId = $matchedId
|
||||
Fingerprint = $matchedFpr
|
||||
}
|
||||
}
|
||||
|
||||
function New-BatchKey {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$GpgPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$RealName,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EmailAddress,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet('ed25519', 'rsa4096')]
|
||||
[string]$Kind
|
||||
)
|
||||
|
||||
if ($Kind -eq 'ed25519') {
|
||||
$body = @"
|
||||
%echo generating
|
||||
Key-Type: EDDSA
|
||||
Key-Curve: Ed25519
|
||||
Key-Usage: sign
|
||||
Subkey-Type: ECDH
|
||||
Subkey-Curve: Curve25519
|
||||
Subkey-Usage: encrypt
|
||||
Name-Real: $RealName
|
||||
Name-Email: $EmailAddress
|
||||
Expire-Date: 0
|
||||
%no-protection
|
||||
%commit
|
||||
%echo done
|
||||
"@
|
||||
}
|
||||
else {
|
||||
$body = @"
|
||||
%echo generating
|
||||
Key-Type: RSA
|
||||
Key-Length: 4096
|
||||
Key-Usage: sign
|
||||
Subkey-Type: RSA
|
||||
Subkey-Length: 4096
|
||||
Subkey-Usage: encrypt
|
||||
Name-Real: $RealName
|
||||
Name-Email: $EmailAddress
|
||||
Expire-Date: 0
|
||||
%no-protection
|
||||
%commit
|
||||
%echo done
|
||||
"@
|
||||
}
|
||||
|
||||
$temp = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$utf8 = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllText($temp, $body, $utf8)
|
||||
Invoke-GitSkillsNative -FilePath $GpgPath -ArgumentList @('--batch', '--generate-key', $temp) | Out-Null
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $temp) {
|
||||
Remove-Item -LiteralPath $temp -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$gitProfile = Get-GitSkillsProfile
|
||||
$defaults = Get-GitSkillsDefaults
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Name)) {
|
||||
if ($gitProfile) { $Name = [string]$gitProfile.userName }
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($Email)) {
|
||||
if ($gitProfile) { $Email = [string]$gitProfile.userEmail }
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($Name)) { $Name = [string]$defaults.userName }
|
||||
if ([string]::IsNullOrWhiteSpace($Email)) { $Email = [string]$defaults.userEmail }
|
||||
|
||||
$gpg = Resolve-Gpg
|
||||
if ($null -eq $gpg) {
|
||||
Write-StatusLine -Key 'status' -Value 'failed'
|
||||
Write-StatusLine -Key 'reason' -Value 'gpg_missing'
|
||||
Write-Output 'hint=Install Gpg4win, then re-run ensure-gpg.ps1'
|
||||
exit 1
|
||||
}
|
||||
|
||||
$existing = Get-SecretKeyForEmail -GpgPath $gpg -EmailAddress $Email
|
||||
if ($existing -and -not $ForceGenerate) {
|
||||
Write-StatusLine -Key 'status' -Value 'exists'
|
||||
Write-StatusLine -Key 'key_id' -Value $existing.KeyId
|
||||
Write-StatusLine -Key 'fingerprint' -Value $existing.Fingerprint
|
||||
Write-StatusLine -Key 'gpg' -Value $gpg
|
||||
exit 0
|
||||
}
|
||||
|
||||
$created = New-BatchKey -GpgPath $gpg -RealName $Name -EmailAddress $Email -Kind 'ed25519'
|
||||
if (-not $created) {
|
||||
$created = New-BatchKey -GpgPath $gpg -RealName $Name -EmailAddress $Email -Kind 'rsa4096'
|
||||
}
|
||||
if (-not $created) {
|
||||
Write-StatusLine -Key 'status' -Value 'failed'
|
||||
Write-StatusLine -Key 'reason' -Value 'gpg_generate_failed'
|
||||
exit 1
|
||||
}
|
||||
|
||||
$createdKey = Get-SecretKeyForEmail -GpgPath $gpg -EmailAddress $Email
|
||||
if ($null -eq $createdKey) {
|
||||
Write-StatusLine -Key 'status' -Value 'failed'
|
||||
Write-StatusLine -Key 'reason' -Value 'gpg_key_not_found_after_generate'
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-StatusLine -Key 'status' -Value 'created'
|
||||
Write-StatusLine -Key 'key_id' -Value $createdKey.KeyId
|
||||
Write-StatusLine -Key 'fingerprint' -Value $createdKey.Fingerprint
|
||||
Write-StatusLine -Key 'gpg' -Value $gpg
|
||||
Reference in New Issue
Block a user