From 4a2928246924b5c8cff59bf4372e50f73d47e4d4 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Thu, 8 Feb 2018 02:14:08 +0100 Subject: first pinentry, doesn't work - problems with setting globals with SETxxx due to the $() subshells --- pinentry.sh | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 pinentry.sh diff --git a/pinentry.sh b/pinentry.sh new file mode 100644 index 0000000..fce4404 --- /dev/null +++ b/pinentry.sh @@ -0,0 +1,353 @@ +#/bin/bash + +VERSION="0.1.0" +TIMEOUT="0" +DESCRIPTION="Enter password for GPG key" +PROMPT="Password:" +TITLE="GPG Key Credentials" +CREDENTIALPREFIX="gpgkey://" +KEYINFO="" +OKBUTTON="&OK" +CANCELBUTTON="&Cancel" +NOTOKBUTTON="&Do not do this" +PINERROR="" +EXTPASSCACHE=0 +REPEATPASSWORD=0 +REPEATDESCRIPTION="Confirm password for GPG key" +REPEATERROR="Error: Passwords did not match." + +assuan_result() { + #echo -n $(( (5 << 24) | $1 )) + case $1 in + 0) + echo -n "ERR 0 no error" + ;; + 62) + echo -n "ERR 83886142 timeout" + ;; + 99) + echo -n "ERR 83886179 cancelled" + ;; + 114) + echo -n "ERR 83886194 not confirmed" + ;; + 174) + echo -n "ERR 83886254 invalid option" + ;; + 257) + echo -n "ERR 83886337 general error" + ;; + 261) + echo -n "ERR 83886341 invalid value" + ;; + 275) + echo -n "ERR 83886355 unknown command" + ;; + esac +} + +getpassword() { + echo "$PINERROR" + echo "$DESCRIPTION" + return + local cmd_prompt=$(cat <<-DLM + \$cred = \$Host.ui.PromptForCredential("$TITLE", + "$PINERROR$DESCRIPTION", + "$KEYINFO", + "gpgkey://$KEYINFO", + "Generic", + "None,ReadOnlyUserName") + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local cmd_repeat=$(cat <<-DLM + \$cred = \$Host.ui.PromptForCredential("$TITLE", + "$REPEATDESCRIPTION", + "$KEYINFO", + "gpgkey://$KEYINFO", + "Generic", + "None,ReadOnlyUserName") + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local cmd_lookup=$(cat <<-DLM + \$cred = Get-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local credpassword + local credpasswordrepeat + local passwordfromcache=0 + PINERROR="" + if [ "$REPEATPASSWORD" -eq "0" ]; then + if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ -n "$KEYINFO" ]; then + credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" + if [ -n "$credpassword" ]; then + echo -en "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" + return + fi + fi + fi + fi + credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_prompt")" + if [ -n "$credpassword" ]; then + if [ "$REPEATPASSWORD" -eq "1" ]; then + credpasswordrepeat="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_repeat")" + if [ "$credpassword" == "$credpasswordrepeat" ]; then + echo -en "S PIN_REPEATED\nD $credpassword\nOK" + else + message "$REPEATERROR" > /dev/null + echo -n "$(assuan_result 114)" # unsure this is the correct error + fi + else + echo -en "D $credpassword\nOK" + fi + else + echo -n "$(assuan_result 99)" + fi +} + +removepassword() { + if [ -z "$1" ]; then + echo -n "$(assuan_result 261)" + return + fi + local cmd_remove=$(cat <<-DLM + try { + Remove-StoredCredential -Target "$CREDENTIALPREFIX$1" -Type GENERIC -ErrorAction Stop + } + catch { + Write-Output "$(assuan_result 261)" + return + } + Write-Output "OK" +DLM + ) + echo -n "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" +} + +message() { + local desc + if [ -n "$1" ]; then + desc="$1" + else + desc="$DESCRIPTION" + fi + local cmd=$(cat <<-DLM + \$options = [System.Management.Automation.Host.ChoiceDescription[]] @("$OKBUTTON") + [int]\$defaultchoice = 0 + \$result = \$host.UI.PromptForChoice("$TITLE", + "$desc", + \$options, + \$defaultchoice) +DLM + ) + powershell.exe -nologo -noprofile -noninteractive -command "$cmd" > /dev/null + echo -n "OK" +} + +confirm() { + PINERROR="" + if [ "$1" == "--one-button" ]; then + echo "$(message)" + return + fi + local cmd=$(cat <<-DLM + \$options = [System.Management.Automation.Host.ChoiceDescription[]] @("$OKBUTTON", "$CANCELBUTTON") + [int]\$defaultchoice = 0 + \$result = \$host.UI.PromptForChoice("$TITLE", + "$DESCRIPTION", + \$options, + \$defaultchoice) + if (\$result) { + switch(\$result) + { + 0 { Write-Output "OK"} + 1 { Write-Output "$(assuan_result 99)"} + 2 { Write-Output "$(assuan_result 114)"} + } + } + else { + Write-Output "$(assuan_result 114)" + } +DLM + ) + local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" + echo -n "$result" +} + +settimeout() { + # https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell + TIMEOUT="$1" + echo -n OK +} + +setdescription() { + DESCRIPTION="$1" + echo -n OK +} + +setprompt() { + PROMPT="$1" + echo -n OK +} + +settitle() { + TITLE="$1" + echo -n OK +} + +setpinerror() { + PINERROR="** $1 ** " + echo -n "$PINERROR" +} + +setkeyinfo() { + if [ "$1" == "--clear" ]; then + KEYINFO="" + else + KEYINFO="$1" + fi + echo -n OK +} + +setrepeatpassword() { + REPEATPASSWORD=1 + REPEATDESCRIPTION="$1" + echo -n OK +} + +setrepeaterror () { + REPEATERROR="$1" + echo -n OK +} + +setokbutton() { + OKBUTTON="${$1//_/&}" + echo -n OK +} + +setcancelbutton() { + CANCELBUTTON="${$1//_/&}" + echo -n OK +} + +setnotokbutton() { + NOTOKBUTTON="${$1//_/&}" + echo -n OK +} + +getinfo() { + if [ "$1" == "version" ]; then + echo -en "D $VERSION\nOK" + elif [ "$1" == "pid" ]; then + echo -en "D $BASHPID\nOK" + else + echo -n "$(assuan_result 275)" + fi +} + +setoption() { + local key="$(echo "$1" | cut -d'=' -s -f1)" + local value="$(echo "$1" | cut -d'=' -s -f2)" + case $key in + allow-external-password-cache) + EXTPASSCACHE=1 + echo -n "OK" + ;; + default-ok) + echo -n $(setokbutton "$value") + ;; + default-cancel) + echo -n $(setcancelbutton "$value") + ;; + default-notok) + echo -n $(setnotokbutton "$value") + ;; + default-prompt) + echo -n $(setprompt "$value") + ;; + *) + echo -n "OK" + ;; + esac +} + +echo "OK Your orders please" +while IFS= read -r line; do + #echo "$line" >> /home/dalep/tracepin.txt + action="$(echo $line | cut -d' ' -f1)" + args="$(echo $line | cut -d' ' -s -f2-)" + #echo "action:$action:" + #echo "args:$args:" + case $action in + BYE) + echo "OK closing connection" + exit 0 + ;; + GETPIN) + echo "$(getpassword)" + ;; + SETTIMEOUT) + echo "$(settimeout "$args")" + ;; + SETDESC) + echo "$(setdescription "$args")" + ;; + SETPROMPT) + echo "$(setprompt "$args")" + ;; + SETTITLE) + echo "$(settitle "$args")" + ;; + SETKEYINFO) + echo "$(setkeyinfo "$args")" + ;; + SETOK) + echo "$(setokbutton "$args")" + ;; + SETCANCEL) + echo "$(setcancelbutton "$args")" + ;; + SETNOTOK) + echo "$(setnotokbutton "$args")" + ;; + CONFIRM) + echo "$(confirm "$args")" + ;; + MESSAGE) + echo "$(message)" + ;; + SETERROR) + echo "$(setpinerror "$args")" + ;; + GETINFO) + echo "$(getinfo "$args")" + ;; + OPTION) + echo "$(setoption "$args")" + ;; + SETREPEAT) + echo "$(setrepeatpassword "$args")" + ;; + SETREPEATERROR) + echo "$(setrepeaterror "$args")" + ;; + CLEARPASSPHRASE) + echo "$(removepassword "$args")" + ;; + RESET) + echo "OK" + ;; + *) + echo "OK" + ;; + esac +done -- cgit From a324830144a2ffb1aeafafd25bd51d48bbaa3cb8 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Thu, 8 Feb 2018 04:40:52 +0100 Subject: password caching now works - bugs in MESSAGE and CONFIRM due to powershell doing text only on them --- pinentry.sh | 121 +++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/pinentry.sh b/pinentry.sh index fce4404..811bd72 100644 --- a/pinentry.sh +++ b/pinentry.sh @@ -47,9 +47,6 @@ assuan_result() { } getpassword() { - echo "$PINERROR" - echo "$DESCRIPTION" - return local cmd_prompt=$(cat <<-DLM \$cred = \$Host.ui.PromptForCredential("$TITLE", "$PINERROR$DESCRIPTION", @@ -81,16 +78,23 @@ DLM } DLM ) + local cmd_store=$(cat <<-DLM + \$pw = \$Input | Select-Object -First 1 + \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force + New-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | + out-null +DLM + ) + PINERROR="" local credpassword local credpasswordrepeat local passwordfromcache=0 - PINERROR="" if [ "$REPEATPASSWORD" -eq "0" ]; then if [ "$EXTPASSCACHE" -eq "1" ]; then if [ -n "$KEYINFO" ]; then credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" if [ -n "$credpassword" ]; then - echo -en "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" + echo -e "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" return fi fi @@ -101,22 +105,30 @@ DLM if [ "$REPEATPASSWORD" -eq "1" ]; then credpasswordrepeat="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_repeat")" if [ "$credpassword" == "$credpasswordrepeat" ]; then - echo -en "S PIN_REPEATED\nD $credpassword\nOK" + echo -e "S PIN_REPEATED\nD $credpassword\nOK" else message "$REPEATERROR" > /dev/null - echo -n "$(assuan_result 114)" # unsure this is the correct error + echo "$(assuan_result 114)" # unsure this is the correct error + return fi else - echo -en "D $credpassword\nOK" + echo -e "D $credpassword\nOK" + fi + if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ -n "$KEYINFO" ]; then + # avoid setting password on visible param + # alt is to always save on the single or last-of-repeat dialog. And if the repeat fails, then immediately delete it from the cred store + builtin echo -n "$credpassword" | powershell.exe -nologo -noprofile -noninteractive -command "$cmd_store" + fi fi else - echo -n "$(assuan_result 99)" + echo "$(assuan_result 99)" fi } removepassword() { if [ -z "$1" ]; then - echo -n "$(assuan_result 261)" + echo "$(assuan_result 261)" return fi local cmd_remove=$(cat <<-DLM @@ -130,7 +142,7 @@ removepassword() { Write-Output "OK" DLM ) - echo -n "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" + echo "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" } message() { @@ -149,14 +161,15 @@ message() { \$defaultchoice) DLM ) - powershell.exe -nologo -noprofile -noninteractive -command "$cmd" > /dev/null - echo -n "OK" + echo "$cmd" + local result="$(powershell.exe -nologo -noprofile -command "$cmd")" #> /dev/null + echo "OK" } confirm() { PINERROR="" if [ "$1" == "--one-button" ]; then - echo "$(message)" + message return fi local cmd=$(cat <<-DLM @@ -180,33 +193,33 @@ confirm() { DLM ) local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" - echo -n "$result" + echo "$result" } settimeout() { # https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell TIMEOUT="$1" - echo -n OK + echo "OK" } setdescription() { DESCRIPTION="$1" - echo -n OK + echo "OK" } setprompt() { PROMPT="$1" - echo -n OK + echo "OK" } settitle() { TITLE="$1" - echo -n OK + echo "OK" } setpinerror() { PINERROR="** $1 ** " - echo -n "$PINERROR" + echo "OK" } setkeyinfo() { @@ -215,67 +228,67 @@ setkeyinfo() { else KEYINFO="$1" fi - echo -n OK + echo "OK" } setrepeatpassword() { REPEATPASSWORD=1 REPEATDESCRIPTION="$1" - echo -n OK + echo "OK" } setrepeaterror () { REPEATERROR="$1" - echo -n OK + echo "OK" } setokbutton() { OKBUTTON="${$1//_/&}" - echo -n OK + echo "OK" } setcancelbutton() { CANCELBUTTON="${$1//_/&}" - echo -n OK + echo "OK" } setnotokbutton() { NOTOKBUTTON="${$1//_/&}" - echo -n OK + echo "OK" } getinfo() { if [ "$1" == "version" ]; then - echo -en "D $VERSION\nOK" + echo -e "D $VERSION\nOK" elif [ "$1" == "pid" ]; then - echo -en "D $BASHPID\nOK" + echo -e "D $BASHPID\nOK" else - echo -n "$(assuan_result 275)" + echo "$(assuan_result 275)" fi } setoption() { - local key="$(echo "$1" | cut -d'=' -s -f1)" - local value="$(echo "$1" | cut -d'=' -s -f2)" + local key="$(echo "$1" | cut -d'=' -f1)" + local value="$(echo "$1" | cut -d'=' -s -f2-)" case $key in allow-external-password-cache) EXTPASSCACHE=1 - echo -n "OK" + echo "OK" ;; default-ok) - echo -n $(setokbutton "$value") + setokbutton "$value" ;; default-cancel) - echo -n $(setcancelbutton "$value") + setcancelbutton "$value" ;; default-notok) - echo -n $(setnotokbutton "$value") + setnotokbutton "$value" ;; default-prompt) - echo -n $(setprompt "$value") + setprompt "$value" ;; *) - echo -n "OK" + echo "OK" ;; esac } @@ -293,55 +306,55 @@ while IFS= read -r line; do exit 0 ;; GETPIN) - echo "$(getpassword)" + getpassword ;; SETTIMEOUT) - echo "$(settimeout "$args")" + settimeout "$args" ;; SETDESC) - echo "$(setdescription "$args")" + setdescription "$args" ;; SETPROMPT) - echo "$(setprompt "$args")" + setprompt "$args" ;; SETTITLE) - echo "$(settitle "$args")" + settitle "$args" ;; SETKEYINFO) - echo "$(setkeyinfo "$args")" + setkeyinfo "$args" ;; SETOK) - echo "$(setokbutton "$args")" + setokbutton "$args" ;; SETCANCEL) - echo "$(setcancelbutton "$args")" + setcancelbutton "$args" ;; SETNOTOK) - echo "$(setnotokbutton "$args")" + setnotokbutton "$args" ;; CONFIRM) - echo "$(confirm "$args")" + confirm "$args" ;; MESSAGE) - echo "$(message)" + message "$args" ;; SETERROR) - echo "$(setpinerror "$args")" + setpinerror "$args" ;; GETINFO) - echo "$(getinfo "$args")" + getinfo "$args" ;; OPTION) - echo "$(setoption "$args")" + setoption "$args" ;; SETREPEAT) - echo "$(setrepeatpassword "$args")" + setrepeatpassword "$args" ;; SETREPEATERROR) - echo "$(setrepeaterror "$args")" + setrepeaterror "$args" ;; CLEARPASSPHRASE) - echo "$(removepassword "$args")" + removepassword "$args" ;; RESET) echo "OK" -- cgit From d9afefb3a8fa52354140035f3c18a2e826c6f43a Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Thu, 8 Feb 2018 05:17:27 +0100 Subject: replaced CONFIRM prompt with alternate 1 - host.UI.PromptForChoice() won't render GUI on command line. Impl'd alternate 1 --- pinentry.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/pinentry.sh b/pinentry.sh index 811bd72..95cad63 100644 --- a/pinentry.sh +++ b/pinentry.sh @@ -16,6 +16,82 @@ REPEATPASSWORD=0 REPEATDESCRIPTION="Confirm password for GPG key" REPEATERROR="Error: Passwords did not match." +#An alternative to the built-in PromptForChoice providing a consistent UI across different hosts +# alternate #1 https://powershellone.wordpress.com/2015/09/10/a-nicer-promptforchoice-for-the-powershell-console-host/ +# and https://gist.github.com/DBremen/73d7999094e7ac342ad6#file-get-choice-ps1 +# alternate #2 is https://social.technet.microsoft.com/Forums/scriptcenter/en-US/b2546f3c-0a79-4c5f-9044-9d9e962da79c/no-popup-window-when-i-run-the-ps-script-works-in-ise?forum=winserverpowershell + +FUNC_GETCHOICE=$(cat <<-'DLM' +function Get-Choice { + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$true,Position=0)] + $Title, + + [Parameter(Mandatory=$true,Position=1)] + [String[]] + $Options, + + [Parameter(Position=2)] + $DefaultChoice = -1 + ) + if ($DefaultChoice -ne -1 -and ($DefaultChoice -gt $Options.Count -or $DefaultChoice -lt 1)){ + Write-Warning "DefaultChoice needs to be a value between 1 and $($Options.Count) or -1 (for none)" + exit + } + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + [System.Windows.Forms.Application]::EnableVisualStyles() + $script:result = "" + $form = New-Object System.Windows.Forms.Form + $form.FormBorderStyle = [Windows.Forms.FormBorderStyle]::FixedDialog + $form.BackColor = [Drawing.Color]::White + $form.TopMost = $True + $form.Text = $Title + $form.ControlBox = $False + $form.StartPosition = [Windows.Forms.FormStartPosition]::CenterScreen + #calculate width required based on longest option text and form title + $minFormWidth = 100 + $formHeight = 44 + $minButtonWidth = 70 + $buttonHeight = 23 + $buttonY = 12 + $spacing = 10 + $buttonWidth = [Windows.Forms.TextRenderer]::MeasureText((($Options | sort Length)[-1]),$form.Font).Width + 1 + $buttonWidth = [Math]::Max($minButtonWidth, $buttonWidth) + $formWidth = [Windows.Forms.TextRenderer]::MeasureText($Title,$form.Font).Width + $spaceWidth = ($options.Count+1) * $spacing + $formWidth = ($formWidth, $minFormWidth, ($buttonWidth * $Options.Count + $spaceWidth) | Measure-Object -Maximum).Maximum + $form.ClientSize = New-Object System.Drawing.Size($formWidth,$formHeight) + $index = 0 + #create the buttons dynamically based on the options + foreach ($option in $Options){ + Set-Variable "button$index" -Value (New-Object System.Windows.Forms.Button) + $temp = Get-Variable "button$index" -ValueOnly + $temp.Size = New-Object System.Drawing.Size($buttonWidth,$buttonHeight) + $temp.UseVisualStyleBackColor = $True + $temp.Text = $option + $buttonX = ($index + 1) * $spacing + $index * $buttonWidth + $temp.Add_Click({ + $script:result = $this.Text; $form.Close() + }) + $temp.Location = New-Object System.Drawing.Point($buttonX,$buttonY) + $form.Controls.Add($temp) + $index++ + } + $shownString = '$this.Activate();' + if ($DefaultChoice -ne -1){ + $shownString += '(Get-Variable "button$($DefaultChoice-1)" -ValueOnly).Focus()' + } + $shownSB = [ScriptBlock]::Create($shownString) + $form.Add_Shown($shownSB) + [void]$form.ShowDialog() + $result +} +DLM +) + assuan_result() { #echo -n $(( (5 << 24) | $1 )) case $1 in @@ -153,16 +229,11 @@ message() { desc="$DESCRIPTION" fi local cmd=$(cat <<-DLM - \$options = [System.Management.Automation.Host.ChoiceDescription[]] @("$OKBUTTON") - [int]\$defaultchoice = 0 - \$result = \$host.UI.PromptForChoice("$TITLE", - "$desc", - \$options, - \$defaultchoice) + $FUNC_GETCHOICE + \$result = Get-Choice "$desc" (echo "$OKBUTTON") 1 DLM ) - echo "$cmd" - local result="$(powershell.exe -nologo -noprofile -command "$cmd")" #> /dev/null + local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" #> /dev/null echo "OK" } @@ -173,18 +244,14 @@ confirm() { return fi local cmd=$(cat <<-DLM - \$options = [System.Management.Automation.Host.ChoiceDescription[]] @("$OKBUTTON", "$CANCELBUTTON") - [int]\$defaultchoice = 0 - \$result = \$host.UI.PromptForChoice("$TITLE", - "$DESCRIPTION", - \$options, - \$defaultchoice) + $FUNC_GETCHOICE + \$result = Get-Choice "$DESCRIPTION" (echo "$OKBUTTON" "$CANCELBUTTON") 1 if (\$result) { switch(\$result) { - 0 { Write-Output "OK"} - 1 { Write-Output "$(assuan_result 99)"} - 2 { Write-Output "$(assuan_result 114)"} + "$OKBUTTON" { Write-Output "OK"} + "$CANCELBUTTON" { Write-Output "$(assuan_result 99)"} + # "otherbutton" { Write-Output "$(assuan_result 114)"} } } else { -- cgit From fa19ab122e934f210d812a1cbd29438bac39489d Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Thu, 8 Feb 2018 18:43:27 +0100 Subject: change MESSAGE, CONFIRM to use wscript dialogs - change to use wscript com object for MESSAGE, CONFIRM pinentry commands - looks better - change credential UI to simple 4 param sig --- pinentry.sh | 105 ++++++++---------------------------------------------------- 1 file changed, 14 insertions(+), 91 deletions(-) diff --git a/pinentry.sh b/pinentry.sh index 95cad63..bbf2f40 100644 --- a/pinentry.sh +++ b/pinentry.sh @@ -16,82 +16,6 @@ REPEATPASSWORD=0 REPEATDESCRIPTION="Confirm password for GPG key" REPEATERROR="Error: Passwords did not match." -#An alternative to the built-in PromptForChoice providing a consistent UI across different hosts -# alternate #1 https://powershellone.wordpress.com/2015/09/10/a-nicer-promptforchoice-for-the-powershell-console-host/ -# and https://gist.github.com/DBremen/73d7999094e7ac342ad6#file-get-choice-ps1 -# alternate #2 is https://social.technet.microsoft.com/Forums/scriptcenter/en-US/b2546f3c-0a79-4c5f-9044-9d9e962da79c/no-popup-window-when-i-run-the-ps-script-works-in-ise?forum=winserverpowershell - -FUNC_GETCHOICE=$(cat <<-'DLM' -function Get-Choice { - [CmdletBinding()] - Param - ( - [Parameter(Mandatory=$true,Position=0)] - $Title, - - [Parameter(Mandatory=$true,Position=1)] - [String[]] - $Options, - - [Parameter(Position=2)] - $DefaultChoice = -1 - ) - if ($DefaultChoice -ne -1 -and ($DefaultChoice -gt $Options.Count -or $DefaultChoice -lt 1)){ - Write-Warning "DefaultChoice needs to be a value between 1 and $($Options.Count) or -1 (for none)" - exit - } - Add-Type -AssemblyName System.Windows.Forms - Add-Type -AssemblyName System.Drawing - [System.Windows.Forms.Application]::EnableVisualStyles() - $script:result = "" - $form = New-Object System.Windows.Forms.Form - $form.FormBorderStyle = [Windows.Forms.FormBorderStyle]::FixedDialog - $form.BackColor = [Drawing.Color]::White - $form.TopMost = $True - $form.Text = $Title - $form.ControlBox = $False - $form.StartPosition = [Windows.Forms.FormStartPosition]::CenterScreen - #calculate width required based on longest option text and form title - $minFormWidth = 100 - $formHeight = 44 - $minButtonWidth = 70 - $buttonHeight = 23 - $buttonY = 12 - $spacing = 10 - $buttonWidth = [Windows.Forms.TextRenderer]::MeasureText((($Options | sort Length)[-1]),$form.Font).Width + 1 - $buttonWidth = [Math]::Max($minButtonWidth, $buttonWidth) - $formWidth = [Windows.Forms.TextRenderer]::MeasureText($Title,$form.Font).Width - $spaceWidth = ($options.Count+1) * $spacing - $formWidth = ($formWidth, $minFormWidth, ($buttonWidth * $Options.Count + $spaceWidth) | Measure-Object -Maximum).Maximum - $form.ClientSize = New-Object System.Drawing.Size($formWidth,$formHeight) - $index = 0 - #create the buttons dynamically based on the options - foreach ($option in $Options){ - Set-Variable "button$index" -Value (New-Object System.Windows.Forms.Button) - $temp = Get-Variable "button$index" -ValueOnly - $temp.Size = New-Object System.Drawing.Size($buttonWidth,$buttonHeight) - $temp.UseVisualStyleBackColor = $True - $temp.Text = $option - $buttonX = ($index + 1) * $spacing + $index * $buttonWidth - $temp.Add_Click({ - $script:result = $this.Text; $form.Close() - }) - $temp.Location = New-Object System.Drawing.Point($buttonX,$buttonY) - $form.Controls.Add($temp) - $index++ - } - $shownString = '$this.Activate();' - if ($DefaultChoice -ne -1){ - $shownString += '(Get-Variable "button$($DefaultChoice-1)" -ValueOnly).Focus()' - } - $shownSB = [ScriptBlock]::Create($shownString) - $form.Add_Shown($shownSB) - [void]$form.ShowDialog() - $result -} -DLM -) - assuan_result() { #echo -n $(( (5 << 24) | $1 )) case $1 in @@ -127,9 +51,7 @@ getpassword() { \$cred = \$Host.ui.PromptForCredential("$TITLE", "$PINERROR$DESCRIPTION", "$KEYINFO", - "gpgkey://$KEYINFO", - "Generic", - "None,ReadOnlyUserName") + "") if (\$cred) { Write-Output \$cred.GetNetworkCredential().Password } @@ -139,9 +61,7 @@ DLM \$cred = \$Host.ui.PromptForCredential("$TITLE", "$REPEATDESCRIPTION", "$KEYINFO", - "gpgkey://$KEYINFO", - "Generic", - "None,ReadOnlyUserName") + "") if (\$cred) { Write-Output \$cred.GetNetworkCredential().Password } @@ -229,8 +149,10 @@ message() { desc="$DESCRIPTION" fi local cmd=$(cat <<-DLM - $FUNC_GETCHOICE - \$result = Get-Choice "$desc" (echo "$OKBUTTON") 1 + \$wshShell = New-Object -ComObject WScript.Shell + \$options = 0x0 + 0x40 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 + \$result = \$wshShell.Popup("$desc", $TIMEOUT, "$TITLE", \$options) + [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null DLM ) local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" #> /dev/null @@ -244,14 +166,15 @@ confirm() { return fi local cmd=$(cat <<-DLM - $FUNC_GETCHOICE - \$result = Get-Choice "$DESCRIPTION" (echo "$OKBUTTON" "$CANCELBUTTON") 1 + \$wshShell = New-Object -ComObject WScript.Shell + \$options = 0x1 + 0x30 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 + \$result = \$wshShell.Popup("$DESCRIPTION", $TIMEOUT, "$TITLE", \$options) + [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null if (\$result) { - switch(\$result) - { - "$OKBUTTON" { Write-Output "OK"} - "$CANCELBUTTON" { Write-Output "$(assuan_result 99)"} - # "otherbutton" { Write-Output "$(assuan_result 114)"} + switch(\$result) { + 1 { Write-Output "OK" } + 2 { Write-Output "$(assuan_result 99)" } + default { Write-Output "$(assuan_result 114)" } } } else { -- cgit From c3dd89707006cb3e6fc74626e5f609c97aa775e6 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Thu, 8 Feb 2018 18:46:37 +0100 Subject: renamed pinentry file --- pinentry-wsl-ps1.sh | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pinentry.sh | 356 ---------------------------------------------------- 2 files changed, 356 insertions(+), 356 deletions(-) create mode 100644 pinentry-wsl-ps1.sh delete mode 100644 pinentry.sh diff --git a/pinentry-wsl-ps1.sh b/pinentry-wsl-ps1.sh new file mode 100644 index 0000000..bbf2f40 --- /dev/null +++ b/pinentry-wsl-ps1.sh @@ -0,0 +1,356 @@ +#/bin/bash + +VERSION="0.1.0" +TIMEOUT="0" +DESCRIPTION="Enter password for GPG key" +PROMPT="Password:" +TITLE="GPG Key Credentials" +CREDENTIALPREFIX="gpgkey://" +KEYINFO="" +OKBUTTON="&OK" +CANCELBUTTON="&Cancel" +NOTOKBUTTON="&Do not do this" +PINERROR="" +EXTPASSCACHE=0 +REPEATPASSWORD=0 +REPEATDESCRIPTION="Confirm password for GPG key" +REPEATERROR="Error: Passwords did not match." + +assuan_result() { + #echo -n $(( (5 << 24) | $1 )) + case $1 in + 0) + echo -n "ERR 0 no error" + ;; + 62) + echo -n "ERR 83886142 timeout" + ;; + 99) + echo -n "ERR 83886179 cancelled" + ;; + 114) + echo -n "ERR 83886194 not confirmed" + ;; + 174) + echo -n "ERR 83886254 invalid option" + ;; + 257) + echo -n "ERR 83886337 general error" + ;; + 261) + echo -n "ERR 83886341 invalid value" + ;; + 275) + echo -n "ERR 83886355 unknown command" + ;; + esac +} + +getpassword() { + local cmd_prompt=$(cat <<-DLM + \$cred = \$Host.ui.PromptForCredential("$TITLE", + "$PINERROR$DESCRIPTION", + "$KEYINFO", + "") + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local cmd_repeat=$(cat <<-DLM + \$cred = \$Host.ui.PromptForCredential("$TITLE", + "$REPEATDESCRIPTION", + "$KEYINFO", + "") + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local cmd_lookup=$(cat <<-DLM + \$cred = Get-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC + if (\$cred) { + Write-Output \$cred.GetNetworkCredential().Password + } +DLM + ) + local cmd_store=$(cat <<-DLM + \$pw = \$Input | Select-Object -First 1 + \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force + New-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | + out-null +DLM + ) + PINERROR="" + local credpassword + local credpasswordrepeat + local passwordfromcache=0 + if [ "$REPEATPASSWORD" -eq "0" ]; then + if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ -n "$KEYINFO" ]; then + credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" + if [ -n "$credpassword" ]; then + echo -e "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" + return + fi + fi + fi + fi + credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_prompt")" + if [ -n "$credpassword" ]; then + if [ "$REPEATPASSWORD" -eq "1" ]; then + credpasswordrepeat="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_repeat")" + if [ "$credpassword" == "$credpasswordrepeat" ]; then + echo -e "S PIN_REPEATED\nD $credpassword\nOK" + else + message "$REPEATERROR" > /dev/null + echo "$(assuan_result 114)" # unsure this is the correct error + return + fi + else + echo -e "D $credpassword\nOK" + fi + if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ -n "$KEYINFO" ]; then + # avoid setting password on visible param + # alt is to always save on the single or last-of-repeat dialog. And if the repeat fails, then immediately delete it from the cred store + builtin echo -n "$credpassword" | powershell.exe -nologo -noprofile -noninteractive -command "$cmd_store" + fi + fi + else + echo "$(assuan_result 99)" + fi +} + +removepassword() { + if [ -z "$1" ]; then + echo "$(assuan_result 261)" + return + fi + local cmd_remove=$(cat <<-DLM + try { + Remove-StoredCredential -Target "$CREDENTIALPREFIX$1" -Type GENERIC -ErrorAction Stop + } + catch { + Write-Output "$(assuan_result 261)" + return + } + Write-Output "OK" +DLM + ) + echo "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" +} + +message() { + local desc + if [ -n "$1" ]; then + desc="$1" + else + desc="$DESCRIPTION" + fi + local cmd=$(cat <<-DLM + \$wshShell = New-Object -ComObject WScript.Shell + \$options = 0x0 + 0x40 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 + \$result = \$wshShell.Popup("$desc", $TIMEOUT, "$TITLE", \$options) + [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null +DLM + ) + local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" #> /dev/null + echo "OK" +} + +confirm() { + PINERROR="" + if [ "$1" == "--one-button" ]; then + message + return + fi + local cmd=$(cat <<-DLM + \$wshShell = New-Object -ComObject WScript.Shell + \$options = 0x1 + 0x30 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 + \$result = \$wshShell.Popup("$DESCRIPTION", $TIMEOUT, "$TITLE", \$options) + [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null + if (\$result) { + switch(\$result) { + 1 { Write-Output "OK" } + 2 { Write-Output "$(assuan_result 99)" } + default { Write-Output "$(assuan_result 114)" } + } + } + else { + Write-Output "$(assuan_result 114)" + } +DLM + ) + local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" + echo "$result" +} + +settimeout() { + # https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell + TIMEOUT="$1" + echo "OK" +} + +setdescription() { + DESCRIPTION="$1" + echo "OK" +} + +setprompt() { + PROMPT="$1" + echo "OK" +} + +settitle() { + TITLE="$1" + echo "OK" +} + +setpinerror() { + PINERROR="** $1 ** " + echo "OK" +} + +setkeyinfo() { + if [ "$1" == "--clear" ]; then + KEYINFO="" + else + KEYINFO="$1" + fi + echo "OK" +} + +setrepeatpassword() { + REPEATPASSWORD=1 + REPEATDESCRIPTION="$1" + echo "OK" +} + +setrepeaterror () { + REPEATERROR="$1" + echo "OK" +} + +setokbutton() { + OKBUTTON="${$1//_/&}" + echo "OK" +} + +setcancelbutton() { + CANCELBUTTON="${$1//_/&}" + echo "OK" +} + +setnotokbutton() { + NOTOKBUTTON="${$1//_/&}" + echo "OK" +} + +getinfo() { + if [ "$1" == "version" ]; then + echo -e "D $VERSION\nOK" + elif [ "$1" == "pid" ]; then + echo -e "D $BASHPID\nOK" + else + echo "$(assuan_result 275)" + fi +} + +setoption() { + local key="$(echo "$1" | cut -d'=' -f1)" + local value="$(echo "$1" | cut -d'=' -s -f2-)" + case $key in + allow-external-password-cache) + EXTPASSCACHE=1 + echo "OK" + ;; + default-ok) + setokbutton "$value" + ;; + default-cancel) + setcancelbutton "$value" + ;; + default-notok) + setnotokbutton "$value" + ;; + default-prompt) + setprompt "$value" + ;; + *) + echo "OK" + ;; + esac +} + +echo "OK Your orders please" +while IFS= read -r line; do + #echo "$line" >> /home/dalep/tracepin.txt + action="$(echo $line | cut -d' ' -f1)" + args="$(echo $line | cut -d' ' -s -f2-)" + #echo "action:$action:" + #echo "args:$args:" + case $action in + BYE) + echo "OK closing connection" + exit 0 + ;; + GETPIN) + getpassword + ;; + SETTIMEOUT) + settimeout "$args" + ;; + SETDESC) + setdescription "$args" + ;; + SETPROMPT) + setprompt "$args" + ;; + SETTITLE) + settitle "$args" + ;; + SETKEYINFO) + setkeyinfo "$args" + ;; + SETOK) + setokbutton "$args" + ;; + SETCANCEL) + setcancelbutton "$args" + ;; + SETNOTOK) + setnotokbutton "$args" + ;; + CONFIRM) + confirm "$args" + ;; + MESSAGE) + message "$args" + ;; + SETERROR) + setpinerror "$args" + ;; + GETINFO) + getinfo "$args" + ;; + OPTION) + setoption "$args" + ;; + SETREPEAT) + setrepeatpassword "$args" + ;; + SETREPEATERROR) + setrepeaterror "$args" + ;; + CLEARPASSPHRASE) + removepassword "$args" + ;; + RESET) + echo "OK" + ;; + *) + echo "OK" + ;; + esac +done diff --git a/pinentry.sh b/pinentry.sh deleted file mode 100644 index bbf2f40..0000000 --- a/pinentry.sh +++ /dev/null @@ -1,356 +0,0 @@ -#/bin/bash - -VERSION="0.1.0" -TIMEOUT="0" -DESCRIPTION="Enter password for GPG key" -PROMPT="Password:" -TITLE="GPG Key Credentials" -CREDENTIALPREFIX="gpgkey://" -KEYINFO="" -OKBUTTON="&OK" -CANCELBUTTON="&Cancel" -NOTOKBUTTON="&Do not do this" -PINERROR="" -EXTPASSCACHE=0 -REPEATPASSWORD=0 -REPEATDESCRIPTION="Confirm password for GPG key" -REPEATERROR="Error: Passwords did not match." - -assuan_result() { - #echo -n $(( (5 << 24) | $1 )) - case $1 in - 0) - echo -n "ERR 0 no error" - ;; - 62) - echo -n "ERR 83886142 timeout" - ;; - 99) - echo -n "ERR 83886179 cancelled" - ;; - 114) - echo -n "ERR 83886194 not confirmed" - ;; - 174) - echo -n "ERR 83886254 invalid option" - ;; - 257) - echo -n "ERR 83886337 general error" - ;; - 261) - echo -n "ERR 83886341 invalid value" - ;; - 275) - echo -n "ERR 83886355 unknown command" - ;; - esac -} - -getpassword() { - local cmd_prompt=$(cat <<-DLM - \$cred = \$Host.ui.PromptForCredential("$TITLE", - "$PINERROR$DESCRIPTION", - "$KEYINFO", - "") - if (\$cred) { - Write-Output \$cred.GetNetworkCredential().Password - } -DLM - ) - local cmd_repeat=$(cat <<-DLM - \$cred = \$Host.ui.PromptForCredential("$TITLE", - "$REPEATDESCRIPTION", - "$KEYINFO", - "") - if (\$cred) { - Write-Output \$cred.GetNetworkCredential().Password - } -DLM - ) - local cmd_lookup=$(cat <<-DLM - \$cred = Get-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC - if (\$cred) { - Write-Output \$cred.GetNetworkCredential().Password - } -DLM - ) - local cmd_store=$(cat <<-DLM - \$pw = \$Input | Select-Object -First 1 - \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force - New-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | - out-null -DLM - ) - PINERROR="" - local credpassword - local credpasswordrepeat - local passwordfromcache=0 - if [ "$REPEATPASSWORD" -eq "0" ]; then - if [ "$EXTPASSCACHE" -eq "1" ]; then - if [ -n "$KEYINFO" ]; then - credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" - if [ -n "$credpassword" ]; then - echo -e "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" - return - fi - fi - fi - fi - credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_prompt")" - if [ -n "$credpassword" ]; then - if [ "$REPEATPASSWORD" -eq "1" ]; then - credpasswordrepeat="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_repeat")" - if [ "$credpassword" == "$credpasswordrepeat" ]; then - echo -e "S PIN_REPEATED\nD $credpassword\nOK" - else - message "$REPEATERROR" > /dev/null - echo "$(assuan_result 114)" # unsure this is the correct error - return - fi - else - echo -e "D $credpassword\nOK" - fi - if [ "$EXTPASSCACHE" -eq "1" ]; then - if [ -n "$KEYINFO" ]; then - # avoid setting password on visible param - # alt is to always save on the single or last-of-repeat dialog. And if the repeat fails, then immediately delete it from the cred store - builtin echo -n "$credpassword" | powershell.exe -nologo -noprofile -noninteractive -command "$cmd_store" - fi - fi - else - echo "$(assuan_result 99)" - fi -} - -removepassword() { - if [ -z "$1" ]; then - echo "$(assuan_result 261)" - return - fi - local cmd_remove=$(cat <<-DLM - try { - Remove-StoredCredential -Target "$CREDENTIALPREFIX$1" -Type GENERIC -ErrorAction Stop - } - catch { - Write-Output "$(assuan_result 261)" - return - } - Write-Output "OK" -DLM - ) - echo "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" -} - -message() { - local desc - if [ -n "$1" ]; then - desc="$1" - else - desc="$DESCRIPTION" - fi - local cmd=$(cat <<-DLM - \$wshShell = New-Object -ComObject WScript.Shell - \$options = 0x0 + 0x40 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 - \$result = \$wshShell.Popup("$desc", $TIMEOUT, "$TITLE", \$options) - [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null -DLM - ) - local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" #> /dev/null - echo "OK" -} - -confirm() { - PINERROR="" - if [ "$1" == "--one-button" ]; then - message - return - fi - local cmd=$(cat <<-DLM - \$wshShell = New-Object -ComObject WScript.Shell - \$options = 0x1 + 0x30 + 0x2000 + 0x10000 # 1 + 16 + 8192 + 65536 - \$result = \$wshShell.Popup("$DESCRIPTION", $TIMEOUT, "$TITLE", \$options) - [System.Runtime.Interopservices.Marshal]::ReleaseComObject(\$wshShell) | Out-Null - if (\$result) { - switch(\$result) { - 1 { Write-Output "OK" } - 2 { Write-Output "$(assuan_result 99)" } - default { Write-Output "$(assuan_result 114)" } - } - } - else { - Write-Output "$(assuan_result 114)" - } -DLM - ) - local result="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd")" - echo "$result" -} - -settimeout() { - # https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell - TIMEOUT="$1" - echo "OK" -} - -setdescription() { - DESCRIPTION="$1" - echo "OK" -} - -setprompt() { - PROMPT="$1" - echo "OK" -} - -settitle() { - TITLE="$1" - echo "OK" -} - -setpinerror() { - PINERROR="** $1 ** " - echo "OK" -} - -setkeyinfo() { - if [ "$1" == "--clear" ]; then - KEYINFO="" - else - KEYINFO="$1" - fi - echo "OK" -} - -setrepeatpassword() { - REPEATPASSWORD=1 - REPEATDESCRIPTION="$1" - echo "OK" -} - -setrepeaterror () { - REPEATERROR="$1" - echo "OK" -} - -setokbutton() { - OKBUTTON="${$1//_/&}" - echo "OK" -} - -setcancelbutton() { - CANCELBUTTON="${$1//_/&}" - echo "OK" -} - -setnotokbutton() { - NOTOKBUTTON="${$1//_/&}" - echo "OK" -} - -getinfo() { - if [ "$1" == "version" ]; then - echo -e "D $VERSION\nOK" - elif [ "$1" == "pid" ]; then - echo -e "D $BASHPID\nOK" - else - echo "$(assuan_result 275)" - fi -} - -setoption() { - local key="$(echo "$1" | cut -d'=' -f1)" - local value="$(echo "$1" | cut -d'=' -s -f2-)" - case $key in - allow-external-password-cache) - EXTPASSCACHE=1 - echo "OK" - ;; - default-ok) - setokbutton "$value" - ;; - default-cancel) - setcancelbutton "$value" - ;; - default-notok) - setnotokbutton "$value" - ;; - default-prompt) - setprompt "$value" - ;; - *) - echo "OK" - ;; - esac -} - -echo "OK Your orders please" -while IFS= read -r line; do - #echo "$line" >> /home/dalep/tracepin.txt - action="$(echo $line | cut -d' ' -f1)" - args="$(echo $line | cut -d' ' -s -f2-)" - #echo "action:$action:" - #echo "args:$args:" - case $action in - BYE) - echo "OK closing connection" - exit 0 - ;; - GETPIN) - getpassword - ;; - SETTIMEOUT) - settimeout "$args" - ;; - SETDESC) - setdescription "$args" - ;; - SETPROMPT) - setprompt "$args" - ;; - SETTITLE) - settitle "$args" - ;; - SETKEYINFO) - setkeyinfo "$args" - ;; - SETOK) - setokbutton "$args" - ;; - SETCANCEL) - setcancelbutton "$args" - ;; - SETNOTOK) - setnotokbutton "$args" - ;; - CONFIRM) - confirm "$args" - ;; - MESSAGE) - message "$args" - ;; - SETERROR) - setpinerror "$args" - ;; - GETINFO) - getinfo "$args" - ;; - OPTION) - setoption "$args" - ;; - SETREPEAT) - setrepeatpassword "$args" - ;; - SETREPEATERROR) - setrepeaterror "$args" - ;; - CLEARPASSPHRASE) - removepassword "$args" - ;; - RESET) - echo "OK" - ;; - *) - echo "OK" - ;; - esac -done -- cgit From 8355b07e1a87e01381676b81343c8c0c6c76ca93 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Fri, 9 Feb 2018 02:31:05 +0100 Subject: fixed cache error loops, text formatting - fixed cache loop on error - fixed line feeds on description and error text - fixed button accelerator parsing - changed back to 6 param Prompt...Cred() to prevent user format validation --- pinentry-wsl-ps1.sh | 56 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/pinentry-wsl-ps1.sh b/pinentry-wsl-ps1.sh index bbf2f40..87f8b70 100644 --- a/pinentry-wsl-ps1.sh +++ b/pinentry-wsl-ps1.sh @@ -1,11 +1,11 @@ -#/bin/bash +#!/bin/bash VERSION="0.1.0" TIMEOUT="0" DESCRIPTION="Enter password for GPG key" PROMPT="Password:" TITLE="GPG Key Credentials" -CREDENTIALPREFIX="gpgkey://" +CACHEPREFIX="gpgcache://" KEYINFO="" OKBUTTON="&OK" CANCELBUTTON="&Cancel" @@ -51,7 +51,9 @@ getpassword() { \$cred = \$Host.ui.PromptForCredential("$TITLE", "$PINERROR$DESCRIPTION", "$KEYINFO", - "") + "", + "Generic", + "None,ReadOnlyUserName") if (\$cred) { Write-Output \$cred.GetNetworkCredential().Password } @@ -61,14 +63,16 @@ DLM \$cred = \$Host.ui.PromptForCredential("$TITLE", "$REPEATDESCRIPTION", "$KEYINFO", - "") + "", + "Generic", + "None,ReadOnlyUserName") if (\$cred) { Write-Output \$cred.GetNetworkCredential().Password } DLM ) local cmd_lookup=$(cat <<-DLM - \$cred = Get-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC + \$cred = Get-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC if (\$cred) { Write-Output \$cred.GetNetworkCredential().Password } @@ -77,25 +81,27 @@ DLM local cmd_store=$(cat <<-DLM \$pw = \$Input | Select-Object -First 1 \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force - New-StoredCredential -Target "$CREDENTIALPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | + New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | out-null DLM ) - PINERROR="" local credpassword local credpasswordrepeat local passwordfromcache=0 - if [ "$REPEATPASSWORD" -eq "0" ]; then - if [ "$EXTPASSCACHE" -eq "1" ]; then - if [ -n "$KEYINFO" ]; then - credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" - if [ -n "$credpassword" ]; then - echo -e "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" - return + if [ -z "$PINERROR" ]; then + if [ "$REPEATPASSWORD" -eq "0" ]; then + if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ -n "$KEYINFO" ]; then + credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" + if [ -n "$credpassword" ]; then + echo -e "S PASSWORD_FROM_CACHE\nD $credpassword\nOK" + return + fi fi fi fi fi + PINERROR="" credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_prompt")" if [ -n "$credpassword" ]; then if [ "$REPEATPASSWORD" -eq "1" ]; then @@ -129,7 +135,7 @@ removepassword() { fi local cmd_remove=$(cat <<-DLM try { - Remove-StoredCredential -Target "$CREDENTIALPREFIX$1" -Type GENERIC -ErrorAction Stop + Remove-StoredCredential -Target "$CACHEPREFIX$1" -Type GENERIC -ErrorAction Stop } catch { Write-Output "$(assuan_result 261)" @@ -193,7 +199,10 @@ settimeout() { } setdescription() { - DESCRIPTION="$1" + local prep1="${1//%0A/%0D%0A}" # convert LF into Windows CRLF + local prep2="${prep1//%/\\x}" # convert hex encoding style + local decode="$(echo -en "$prep2")" # decode hex + DESCRIPTION="${decode//\"/\`\"}" # escape double quotes for powershell echo "OK" } @@ -208,7 +217,11 @@ settitle() { } setpinerror() { - PINERROR="** $1 ** " + local prep1="** $1 **" + local prep2="${prep1//%0A/%0D%0A}" # convert LF into Windows CRLF + local prep3="${prep2//%/\\x}" # convert hex encoding style + local decode="$(echo -e "$prep3")" # decode hex + PINERROR="${decode//\"/\`\"}"$'\r'$'\n' # escape double quotes for powershell; add CRLF to separate line echo "OK" } @@ -233,17 +246,17 @@ setrepeaterror () { } setokbutton() { - OKBUTTON="${$1//_/&}" + OKBUTTON="${1//_/&}" echo "OK" } setcancelbutton() { - CANCELBUTTON="${$1//_/&}" + CANCELBUTTON="${1//_/&}" echo "OK" } setnotokbutton() { - NOTOKBUTTON="${$1//_/&}" + NOTOKBUTTON="${1//_/&}" echo "OK" } @@ -283,13 +296,12 @@ setoption() { esac } +#rm -f /home/dalep/tracepin.txt echo "OK Your orders please" while IFS= read -r line; do #echo "$line" >> /home/dalep/tracepin.txt action="$(echo $line | cut -d' ' -f1)" args="$(echo $line | cut -d' ' -s -f2-)" - #echo "action:$action:" - #echo "args:$args:" case $action in BYE) echo "OK closing connection" -- cgit From 4c8f768494377f8d1f18623519c9c42d9eec37ca Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Fri, 9 Feb 2018 03:29:45 +0100 Subject: get keyid as user from dialog description --- pinentry-wsl-ps1.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pinentry-wsl-ps1.sh b/pinentry-wsl-ps1.sh index 87f8b70..85844ca 100644 --- a/pinentry-wsl-ps1.sh +++ b/pinentry-wsl-ps1.sh @@ -5,7 +5,8 @@ TIMEOUT="0" DESCRIPTION="Enter password for GPG key" PROMPT="Password:" TITLE="GPG Key Credentials" -CACHEPREFIX="gpgcache://" +CACHEPREFIX="gpgcache:" +CACHEUSER="" KEYINFO="" OKBUTTON="&OK" CANCELBUTTON="&Cancel" @@ -47,10 +48,15 @@ assuan_result() { } getpassword() { + if [ -n $CACHEUSER ]; then + local creduser="$CACHEUSER" + else + local creduser="$KEYINFO" + fi local cmd_prompt=$(cat <<-DLM \$cred = \$Host.ui.PromptForCredential("$TITLE", "$PINERROR$DESCRIPTION", - "$KEYINFO", + "$creduser", "", "Generic", "None,ReadOnlyUserName") @@ -62,7 +68,7 @@ DLM local cmd_repeat=$(cat <<-DLM \$cred = \$Host.ui.PromptForCredential("$TITLE", "$REPEATDESCRIPTION", - "$KEYINFO", + "$creduser", "", "Generic", "None,ReadOnlyUserName") @@ -81,7 +87,7 @@ DLM local cmd_store=$(cat <<-DLM \$pw = \$Input | Select-Object -First 1 \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force - New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$KEYINFO" -SecurePassword \$securepw -Persist LocalMachine | + New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$creduser" -SecurePassword \$securepw -Persist LocalMachine | out-null DLM ) @@ -203,6 +209,10 @@ setdescription() { local prep2="${prep1//%/\\x}" # convert hex encoding style local decode="$(echo -en "$prep2")" # decode hex DESCRIPTION="${decode//\"/\`\"}" # escape double quotes for powershell + local searchfor='ID ([[:xdigit:]]{16})' # hack to search for first gpg key id in description + if [[ "$1" =~ $searchfor ]]; then + CACHEUSER="${BASH_REMATCH[1]}" + fi echo "OK" } -- cgit From 569aaa6b75e43607d8f5325e11f10ef102cd3425 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Tue, 13 Feb 2018 00:13:30 +0100 Subject: fixed password verify; persist=Enterprise - fixed password verification - credential persistance to be Enterprise to enable sync across computers - added linefeed decoding to more pinentry commands --- pinentry-wsl-ps1.sh | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/pinentry-wsl-ps1.sh b/pinentry-wsl-ps1.sh index 85844ca..61e5a22 100644 --- a/pinentry-wsl-ps1.sh +++ b/pinentry-wsl-ps1.sh @@ -8,12 +8,13 @@ TITLE="GPG Key Credentials" CACHEPREFIX="gpgcache:" CACHEUSER="" KEYINFO="" +PERSISTANCE="Enterprise" # Session, LocalMachine, or Enterprise OKBUTTON="&OK" CANCELBUTTON="&Cancel" NOTOKBUTTON="&Do not do this" PINERROR="" -EXTPASSCACHE=0 -REPEATPASSWORD=0 +EXTPASSCACHE="0" +REPEATPASSWORD="0" REPEATDESCRIPTION="Confirm password for GPG key" REPEATERROR="Error: Passwords did not match." @@ -87,7 +88,7 @@ DLM local cmd_store=$(cat <<-DLM \$pw = \$Input | Select-Object -First 1 \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force - New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$creduser" -SecurePassword \$securepw -Persist LocalMachine | + New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$creduser" -SecurePassword \$securepw -Persist $PERSISTANCE | out-null DLM ) @@ -95,8 +96,8 @@ DLM local credpasswordrepeat local passwordfromcache=0 if [ -z "$PINERROR" ]; then - if [ "$REPEATPASSWORD" -eq "0" ]; then - if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ "$REPEATPASSWORD" == "0" ]; then + if [ "$EXTPASSCACHE" == "1" ]; then if [ -n "$KEYINFO" ]; then credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_lookup")" if [ -n "$credpassword" ]; then @@ -110,7 +111,7 @@ DLM PINERROR="" credpassword="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_prompt")" if [ -n "$credpassword" ]; then - if [ "$REPEATPASSWORD" -eq "1" ]; then + if [ "$REPEATPASSWORD" == "1" ]; then credpasswordrepeat="$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_repeat")" if [ "$credpassword" == "$credpasswordrepeat" ]; then echo -e "S PIN_REPEATED\nD $credpassword\nOK" @@ -122,7 +123,7 @@ DLM else echo -e "D $credpassword\nOK" fi - if [ "$EXTPASSCACHE" -eq "1" ]; then + if [ "$EXTPASSCACHE" == "1" ]; then if [ -n "$KEYINFO" ]; then # avoid setting password on visible param # alt is to always save on the single or last-of-repeat dialog. And if the repeat fails, then immediately delete it from the cred store @@ -204,16 +205,27 @@ settimeout() { echo "OK" } +decodegpgagentstr() { + local decode="${1//%0A/%0D%0A}" # convert hex LF into hex Windows CRLF + decode="${decode//%/\\x}" # convert hex encoding style + decode="$(echo -en "$decode")" # decode hex + echo -n "${decode//\"/\`\"}" # escape double quotes for powershell +} + setdescription() { - local prep1="${1//%0A/%0D%0A}" # convert LF into Windows CRLF - local prep2="${prep1//%/\\x}" # convert hex encoding style - local decode="$(echo -en "$prep2")" # decode hex - DESCRIPTION="${decode//\"/\`\"}" # escape double quotes for powershell + DESCRIPTION="$(decodegpgagentstr "$1")" local searchfor='ID ([[:xdigit:]]{16})' # hack to search for first gpg key id in description if [[ "$1" =~ $searchfor ]]; then CACHEUSER="${BASH_REMATCH[1]}" + echo "OK" + return + fi + local searchfor='(([[:xdigit:]][[:xdigit:]]:){15}[[:xdigit:]][[:xdigit:]])' # hack to search for ssh fingerprint in description + if [[ "$1" =~ $searchfor ]]; then + CACHEUSER="${BASH_REMATCH[1]}" + echo "OK" + return fi - echo "OK" } setprompt() { @@ -227,11 +239,7 @@ settitle() { } setpinerror() { - local prep1="** $1 **" - local prep2="${prep1//%0A/%0D%0A}" # convert LF into Windows CRLF - local prep3="${prep2//%/\\x}" # convert hex encoding style - local decode="$(echo -e "$prep3")" # decode hex - PINERROR="${decode//\"/\`\"}"$'\r'$'\n' # escape double quotes for powershell; add CRLF to separate line + PINERROR="$(decodegpgagentstr "** $1 **")"$'\r'$'\n' # decode and add CRLF to separate line echo "OK" } @@ -245,13 +253,13 @@ setkeyinfo() { } setrepeatpassword() { - REPEATPASSWORD=1 - REPEATDESCRIPTION="$1" + REPEATPASSWORD="1" + REPEATDESCRIPTION="$(decodegpgagentstr "$1")" echo "OK" } setrepeaterror () { - REPEATERROR="$1" + REPEATERROR="$(decodegpgagentstr "$1")" echo "OK" } -- cgit From 4578d0112c44e3c6c4d6fb2d784c337685207cc9 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Tue, 13 Feb 2018 18:05:33 +0100 Subject: clarified password persistence; code comments --- .gitignore | 1 + pinentry-wsl-ps1.sh | 65 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 940794e..42fa0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ bld/ # Visual Studio 2015 cache/options directory .vs/ +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/pinentry-wsl-ps1.sh b/pinentry-wsl-ps1.sh index 61e5a22..0a7ab00 100644 --- a/pinentry-wsl-ps1.sh +++ b/pinentry-wsl-ps1.sh @@ -1,5 +1,32 @@ -#!/bin/bash +#!/usr/bin/env bash +# pinentry-wsl-ps1 +# +# (c) 2018 Dale Phurrough +# Licensed under the Mozilla Public License 2.0 +# +# Allows GnuPG to prompt and read passphrases by the pinentry standard +# with a GUI when running within WSL (Windows Subsystem for Linux). +# Works for all keys managed by gpg-agent (GPG, SSH, etc). +# This is a drop-in GUI alternative to pinentry-curses, pinentry-gtk-2, etc. +# https://www.gnupg.org/software/pinentry/index.html +# +# Setup: +# 1. Save this script and set its permissions to be executable +# 2. Configure gpg-agent to use this script for pinentry using +# one of the following methods: +# a) Set pinentry-program within ~/.gnupg/gpg-agent.conf +# pinentry-program /mnt/c/repos/pinentry-wsl-ps1/pinentry-wsl-ps1.sh +# b) Set the path to this script when you launch gpg-agent +# gpg-agent --pinentry-program /mnt/c/repos/pinentry-wsl-ps1/pinentry-wsl-ps1.sh +# 3. Optionally enable persistence of passwords. +# Requires https://github.com/davotronic5000/PowerShell_Credential_Manager +# Please follow instructions there to install from the Gallery or GitHub. +# Note security perspectives like https://security.stackexchange.com/questions/119765/how-secure-is-the-windows-credential-manager +# Possible values for PERSISTENCE are: "", "Session", "LocalMachine", or "Enterprise" +PERSISTENCE="" + +# Do not casually edit the below values VERSION="0.1.0" TIMEOUT="0" DESCRIPTION="Enter password for GPG key" @@ -8,7 +35,6 @@ TITLE="GPG Key Credentials" CACHEPREFIX="gpgcache:" CACHEUSER="" KEYINFO="" -PERSISTANCE="Enterprise" # Session, LocalMachine, or Enterprise OKBUTTON="&OK" CANCELBUTTON="&Cancel" NOTOKBUTTON="&Do not do this" @@ -18,8 +44,8 @@ REPEATPASSWORD="0" REPEATDESCRIPTION="Confirm password for GPG key" REPEATERROR="Error: Passwords did not match." +# convert Assuan protocol error into an ERR number, e.g. echo -n $(( (5 << 24) | $1 )) assuan_result() { - #echo -n $(( (5 << 24) | $1 )) case $1 in 0) echo -n "ERR 0 no error" @@ -48,6 +74,7 @@ assuan_result() { esac } +# GUI dialogs for passwords; text is dynamically set by gpg-agent via protocol getpassword() { if [ -n $CACHEUSER ]; then local creduser="$CACHEUSER" @@ -88,7 +115,7 @@ DLM local cmd_store=$(cat <<-DLM \$pw = \$Input | Select-Object -First 1 \$securepw = ConvertTo-SecureString \$pw -AsPlainText -Force - New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$creduser" -SecurePassword \$securepw -Persist $PERSISTANCE | + New-StoredCredential -Target "$CACHEPREFIX$KEYINFO" -Type GENERIC -UserName "$creduser" -SecurePassword \$securepw -Persist $PERSISTENCE | out-null DLM ) @@ -135,6 +162,7 @@ DLM fi } +# remove password from persistent store removepassword() { if [ -z "$1" ]; then echo "$(assuan_result 261)" @@ -151,9 +179,14 @@ removepassword() { Write-Output "OK" DLM ) - echo "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" + if [ "$EXTPASSCACHE" == "1" ]; then + echo "$(powershell.exe -nologo -noprofile -noninteractive -command "$cmd_remove")" + else + echo "OK" + fi } +# GUI dialog box with simple message and one OK button message() { local desc if [ -n "$1" ]; then @@ -172,6 +205,7 @@ DLM echo "OK" } +# GUI dialog box with test and two buttons: OK, Cancel confirm() { PINERROR="" if [ "$1" == "--one-button" ]; then @@ -199,12 +233,15 @@ DLM echo "$result" } +# set a timeout value in seconds after which prompts/dialogs are automatically cancelled +# limited functionality in current codebase +# potential improvements at https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell settimeout() { - # https://stackoverflow.com/questions/21176487/adding-a-timeout-to-batch-powershell TIMEOUT="$1" echo "OK" } +# helper function for decoding strings from gpg-agent into Windows-compatible format decodegpgagentstr() { local decode="${1//%0A/%0D%0A}" # convert hex LF into hex Windows CRLF decode="${decode//%/\\x}" # convert hex encoding style @@ -212,6 +249,8 @@ decodegpgagentstr() { echo -n "${decode//\"/\`\"}" # escape double quotes for powershell } +# commonly used to set main text in GUI dialog boxes +# also parses for key ids to display in GUI prompts setdescription() { DESCRIPTION="$(decodegpgagentstr "$1")" local searchfor='ID ([[:xdigit:]]{16})' # hack to search for first gpg key id in description @@ -288,12 +327,15 @@ getinfo() { fi } +# often called by gpg-agent to set default values setoption() { local key="$(echo "$1" | cut -d'=' -f1)" local value="$(echo "$1" | cut -d'=' -s -f2-)" case $key in allow-external-password-cache) - EXTPASSCACHE=1 + if [ -n "$PERSISTENCE" ]; then + EXTPASSCACHE=1 + fi echo "OK" ;; default-ok) @@ -314,10 +356,15 @@ setoption() { esac } -#rm -f /home/dalep/tracepin.txt +# check that we are running within WSL +if ! cat /proc/sys/kernel/osrelease | grep -q -i Microsoft; then + echo "$(assuan_result 257)" + exit 1 +fi + +# main loop to read stdin and respond echo "OK Your orders please" while IFS= read -r line; do - #echo "$line" >> /home/dalep/tracepin.txt action="$(echo $line | cut -d' ' -f1)" args="$(echo $line | cut -d' ' -s -f2-)" case $action in -- cgit From e4648a17b0899e1ce2db5579dc1a508947faf334 Mon Sep 17 00:00:00 2001 From: Dale Phurrough Date: Tue, 13 Feb 2018 19:00:16 +0100 Subject: updated README --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e7ea4be..9421256 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ # pinentry-wsl-ps1 -pinentry GUI for Windows WSL (useful for GPG) + +GUI for GPG within Windows WSL for passwords, pins, etc. +Optional persistence of passwords into Windows Credential Manager + +(c) 2018 Dale Phurrough +Licensed under the Mozilla Public License 2.0 + +## Features + +* Allows GnuPG to prompt and read passphrases by the pinentry protocol +with a GUI when running within WSL (Windows Subsystem for Linux) +* Works for all keys managed by gpg-agent (GPG, SSH, etc) +* Drop-in replacement GUI to pinentry-curses, pinentry-gtk-2, etc. + +## Setup + +1. Save the `pinentry-wsl-ps1.sh` script and set its permissions to be executable +2. Configure gpg-agent to use this script for pinentry using + one of the following methods + * Set pinentry-program within ~/.gnupg/gpg-agent.conf to the script's path, e.g. + `pinentry-program /mnt/c/repos/pinentry-wsl-ps1/pinentry-wsl-ps1.sh` + * ... or, set the path to this script when you launch gpg-agent, e.g. + `gpg-agent --pinentry-program /mnt/c/repos/pinentry-wsl-ps1/pinentry-wsl-ps1.sh` +3. Optionally enable persistence of passwords. + 1. Follow instructions https://github.com/davotronic5000/PowerShell_Credential_Manager + to install the needed module from the Powershell Gallery or GitHub. + 2. Note security perspectives like https://security.stackexchange.com/questions/119765/how-secure-is-the-windows-credential-manager + 3. Edit the script and set `PERSISTENCE` to one of the values: + * `""` no persistence + * `"Session"` persists the password only for the current Windows login session + * `"LocalMachine"` persists the password for the current Windows login on the local Windows computer + * `"Enterprise"` persists the password for the current Windows login and requests Windows Credential Manager to synchronize it across Windows computers for that same Windows login + +## References + +* https://www.gnupg.org/software/pinentry/index.html +* https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html +* https://github.com/GPGTools/pinentry/blob/master/doc/pinentry.texi +* https://gist.github.com/mdeguzis/05d1f284f931223624834788da045c65 +* https://github.com/GPGTools/pinentry/blob/master/pinentry/pinentry.c -- cgit