Pinentry: Difference between revisions

From Omnia
Jump to navigation Jump to search
Line 394: Line 394:


unset password
unset password
</pre>
== No cached passwords ==
gpg-agent caches passphrases by cache ID
Option 1: Use a unique cache ID every time
<pre>
CACHE_ID="myprompt-$$-$(date +%s)"
gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Enter Password"
/bye
EOF
</pre>
Option 2: Clear the cache after use
<pre>
gpg-connect-agent <<EOF
CLEAR_PASSPHRASE mytest
/bye
EOF
</pre>
Option 3: Disable caching globally
~/.gnupg/gpg-agent.conf
<pre>
default-cache-ttl 0
max-cache-ttl 0
</pre>
or very short values:
<pre>
default-cache-ttl 1
max-cache-ttl 1
</pre>
gpgconf --kill gpg-agent
--
Current cache settings:
gpgconf --list-options gpg-agent | grep cache
# or
gpg-connect-agent 'GETINFO gpgconf_name' /bye
<pre>
default-cache-ttl:24:0:expire cached PINs after N seconds:3:3:N:600::
default-cache-ttl-ssh:24:1:expire SSH keys after N seconds:3:3:N:1800::
max-cache-ttl:24:2:set maximum PIN cache lifetime to N seconds:3:3:N:7200::
max-cache-ttl-ssh:24:2:set maximum SSH key lifetime to N seconds:3:3:N:7200::
ignore-cache-for-signing:8:0:do not use the PIN cache when signing:0:0::::
no-allow-external-cache:8:0:disallow the use of an external password cache:0:0::::
</pre>
-- test cache --
<pre>
export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null
# First prompt
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Password"
/bye
EOF
# Second prompt using same cache id
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Password"
/bye
EOF
</pre>
If cache is enabled it won't prompt for second.
--
sample-no-cache.sh
<pre>
#!/bin/bash
export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null
CACHE_ID="prompt-$$-$(date +%s%N)"
gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Password Required"
/bye
EOF
</pre>
---
<pre>
#!/bin/bash
export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1
CACHE_ID="pw-$$-$(date +%s%N)"
response=$(
gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Enter Password"
/bye
EOF
)
echo "$response"
</pre>
</pre>


== keywords ==
== keywords ==

Revision as of 22:00, 26 August 2026

echo -e 'SETTITLE YUBI\nSETDESC YUBI\nSETPROMPT YUBI\nSETERROR YUBI\nGETPIN\n' | /usr/bin/pinentry | grep '^D'


sample-pinentry.sh ::

#!/bin/bash

# Ensure pinentry is installed
command -v pinentry >/dev/null 2>&1 || {
    echo "ERROR: pinentry not found"
    exit 1
}

# Talk to pinentry using its text protocol
response=$(
{
    echo "SETTITLE Credential Prompt"
    echo "SETPROMPT Passphrase:"
    echo "SETDESC Please enter your passphrase."
    echo "GETPIN"
} | pinentry
)

# Extract the entered PIN/password
pin=$(echo "$response" | awk '/^D / {print substr($0,3)}')

if [[ -n "$pin" ]]; then
    echo "Passphrase was entered."
    echo "Length: ${#pin}"
    # Do something with $pin here
else
    echo "No passphrase entered or dialog cancelled."
fi

secure-sample-pinentry.sh ::

#!/bin/bash

pin=$(
{
    echo "SETTITLE Secure Prompt"
    echo "SETPROMPT Password:"
    echo "GETPIN"
} | pinentry | sed -n 's/^D //p'
)

if [[ -z "$pin" ]]; then
    echo "Cancelled"
    exit 1
fi

# Use the secret
echo "Received secret"

# Clear the variable when done
unset pin

Info

When the user enters a value, pinentry returns lines similar to:

OK Pleased to meet you
D MySecretPassword
OK

The line beginning with D contains the entered secret, which is why the script extracts it.

gpg-agent version

Example 1: Ask for a passphrase via gpg-agent

#!/bin/bash

# Establish a connection to gpg-agent
GPG_CONNECT=$(gpg-connect-agent /bye 2>/dev/null)

if [[ $? -ne 0 ]]; then
    echo "gpg-agent is not running"
    exit 1
fi

# Request a passphrase
response=$(
gpg-connect-agent <<EOF
GET_PASSPHRASE --data mycacheid X X "Enter your password"
/bye
EOF
)

password=$(echo "$response" | sed -n 's/^D //p')

if [[ -z "$password" ]]; then
    echo "User cancelled"
    exit 1
fi

echo "Password received"
unset password

This caches the password, so if you call it again, it will have it stored.

--

Example 2: Trigger the user's configured pinentry

This example lets gpg-agent display the configured Pinentry GUI:

#!/bin/bash

result=$(
gpg-connect-agent <<'EOF'
GET_PASSPHRASE myapp-login X X "Please enter your password"
/bye
EOF
)

secret=$(echo "$result" | sed -n 's/^D //p')

if [[ -n "$secret" ]]; then
    echo "Success"
else
    echo "Cancelled"
fi

--

Example 3: Cache the secret

A nice feature of gpg-agent is passphrase caching:

#!/bin/bash

CACHE_ID="my-company-tool"

secret=$(
gpg-connect-agent <<EOF |
GET_PASSPHRASE $CACHE_ID X X "Authentication Required"
/bye
EOF
sed -n 's/^D //p'
)

echo "Secret obtained"

This one didn't seem to work for me?

--- Setup --

export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye

Reload agent:

gpg-connect-agent reloadagent /bye

Configure ~/.gnupg/gpg-agent.conf

 pinentry-program /usr/bin/pinentry-tty
 pinentry-program /usr/bin/pinentry-curses

--- manually test --

gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Enter Password"
EOF

Direct Pin Entry Example

#!/bin/bash

output=$(
{
    echo "SETTITLE Login"
    echo "SETPROMPT Password:"
    echo "SETDESC Please enter your password."
    echo "GETPIN"
} | pinentry
)

secret=$(sed -n 's/^D //p' <<< "$output")

if [[ -z "$secret" ]]; then
    echo "Cancelled"
    exit 1
fi

echo "Got password (${#secret} chars)"

Normal Usage

echo "hello" > test.txt
gpg -c test.txt

will trigger:

gpg
  -> gpg-agent
      -> pinentry

reusable password prompt via gpg-agent

gpg-preset-passphrase
# or
gpg-agent --daemon

Quick and dirty

{
 echo "SETTITLE Login"
 echo "SETPROMPT Password:"
 echo "GETPIN"
} | pinentry
#!/bin/bash

output=$(
{
    echo "SETTITLE Test"
    echo "SETPROMPT Password:"
    echo "GETPIN"
} | pinentry
)

echo "----- RAW OUTPUT -----"
printf '%s\n' "$output"
echo "----------------------"

if "D secret", then:

secret=$(printf '%s\n' "$output" | sed -n 's/^D //p')

If it contains percent-encoding: "D abc%20def", then you will need to decode before use

Another Try

export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye

gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Enter Password"
EOF

Decode with:

xxd -r -p
{
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Enter Password"
EOF
} | xxd -r -p
# echo "pinentry-program /usr/bin/pinentry-tty" >> ~/.gnupg/gpg-agent.conf
echo "pinentry-program /usr/bin/pinentry-gtk" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent

---

~/.gnupg/gpg-agent.conf

# pinentry-tty renders cleanly in the Copilot CLI terminal (no curses/mouse junk)
pinentry-program /usr/bin/pinentry-tty
# Allow non-interactive passphrase paths
allow-loopback-pinentry
allow-preset-passphrase
# Keep the passphrase cached for a full workday so signing is silent after one unlock
default-cache-ttl 34200
max-cache-ttl 34200

another try

Fix #3: Use a dedicated TTY

A trick for scripts is to communicate over stdin/stdout while pinentry accesses /dev/tty:

#!/bin/bash

exec 3<> /dev/tty

output=$(
{
    echo "SETTITLE Login"
    echo "SETPROMPT Password:"
    echo "GETPIN"
} | pinentry 2>&1
)

echo "$output"

---

gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Test"
EOF

---

{
echo "GETPIN"
} | pinentry

---

export GPG_TTY=$(tty)

pinentry-curses <<EOF
SETTITLE Login
SETPROMPT Password:
GETPIN
EOF

decode version

#!/bin/bash

response=$(
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Enter Password"
EOF
)

hex=$(awk '/^OK / {print $2}' <<< "$response")

password=$(printf '%s' "$hex" | xxd -r -p)

echo "Password: $password"

reliable option gpg-agent

#!/bin/bash

# Ensure gpg-agent knows which terminal to use
export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null

response=$(
gpg-connect-agent <<'EOF'
GET_PASSPHRASE mytest X X "Enter Password"
/bye
EOF
)

echo "Raw response:"
echo "$response"

# Your system appears to return:
# OK <hex-encoded-password>

hex=$(echo "$response" | awk '/^OK / {print $2}')

if [[ -z "$hex" ]]; then
    echo "Cancelled or no password entered"
    exit 1
fi

password=$(printf '%s' "$hex" | xxd -r -p)

echo "Password length: ${#password}"

# Use password here
# ...

unset password

No cached passwords

gpg-agent caches passphrases by cache ID

Option 1: Use a unique cache ID every time

CACHE_ID="myprompt-$$-$(date +%s)"

gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Enter Password"
/bye
EOF

Option 2: Clear the cache after use

gpg-connect-agent <<EOF
CLEAR_PASSPHRASE mytest
/bye
EOF


Option 3: Disable caching globally

~/.gnupg/gpg-agent.conf

default-cache-ttl 0
max-cache-ttl 0

or very short values:

default-cache-ttl 1
max-cache-ttl 1
gpgconf --kill gpg-agent

--

Current cache settings:

gpgconf --list-options gpg-agent | grep cache
# or
gpg-connect-agent 'GETINFO gpgconf_name' /bye
default-cache-ttl:24:0:expire cached PINs after N seconds:3:3:N:600::
default-cache-ttl-ssh:24:1:expire SSH keys after N seconds:3:3:N:1800::
max-cache-ttl:24:2:set maximum PIN cache lifetime to N seconds:3:3:N:7200::
max-cache-ttl-ssh:24:2:set maximum SSH key lifetime to N seconds:3:3:N:7200::
ignore-cache-for-signing:8:0:do not use the PIN cache when signing:0:0::::
no-allow-external-cache:8:0:disallow the use of an external password cache:0:0::::

-- test cache --

export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null

# First prompt
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Password"
/bye
EOF

# Second prompt using same cache id
gpg-connect-agent <<EOF
GET_PASSPHRASE mytest X X "Password"
/bye
EOF

If cache is enabled it won't prompt for second.

--

sample-no-cache.sh

#!/bin/bash

export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null

CACHE_ID="prompt-$$-$(date +%s%N)"

gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Password Required"
/bye
EOF

---

#!/bin/bash

export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1

CACHE_ID="pw-$$-$(date +%s%N)"

response=$(
gpg-connect-agent <<EOF
GET_PASSPHRASE $CACHE_ID X X "Enter Password"
/bye
EOF
)

echo "$response"

keywords