Files
Git-Skill/.cursor/skills/git-init/scripts/ensure-gpg.ps1
T
2026-08-20 23:42:02 +08:00

203 lines
5.6 KiB
PowerShell

# 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