# 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