2cddf06b6c
Co-authored-by: 旅行呀~ <travelxiao@qq.com>
203 lines
5.6 KiB
PowerShell
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
|