commit 173ed891428abd11609e9dc41cb6c9a6320bef70
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-27T10:48:52Z |
| subject | Linux can now use gpg agent for kstore secret unlock |
commit 173ed891428abd11609e9dc41cb6c9a6320bef70
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-27T10:48:52Z
Linux can now use gpg agent for kstore secret unlock
---
bash/sources/15_gpg-agent | 3 +
bash/sources/16_keystore | 206 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 207 insertions(+), 2 deletions(-)
diff --git a/bash/sources/15_gpg-agent b/bash/sources/15_gpg-agent
index 181759d..6ff695a 100644
--- a/bash/sources/15_gpg-agent
+++ b/bash/sources/15_gpg-agent
@@ -21,6 +21,7 @@ function _rbash_enable_gpg_ssh_support {
}
_rbash_enable_gpg_ssh_support
+_rbash_gpg_enabled=
if command -v gpg-connect-agent >/dev/null 2>&1; then
export GPG_TTY
@@ -30,6 +31,7 @@ if command -v gpg-connect-agent >/dev/null 2>&1; then
if [ -n "$GPG_TTY" ]; then
gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1 || true
+ _rbash_gpg_enabled=yes
fi
fi
@@ -44,5 +46,6 @@ then
export SSH_AUTH_SOCK="$_rbash_gpg_ssh_sock"
fi
+ _rbash_gpg_enabled=yes
unset _rbash_gpg_ssh_sock
fi
diff --git a/bash/sources/16_keystore b/bash/sources/16_keystore
index d53014e..f96dba9 100644
--- a/bash/sources/16_keystore
+++ b/bash/sources/16_keystore
@@ -17,6 +17,8 @@ _AUTH_SECRET=
_KSTORE_DEF_PROP=${_KSTORE_DEF_PROP:-val}
_KSTORE_DEC_FAILED="$RBASH_HOME/.decypt-failed"
_KSTORE_TABLE=store
+_KSTORE_GPG_RCPT="penguin@rbash.io"
+
: "${_KSTORE_OPENSSL_ITER:=1000000}"
function kstore {
@@ -184,8 +186,208 @@ function _kstoresecret-auto {
case $OSTYPE in
darwin*) _kstoresecret-macos "$@" ;;
cygwin) _kstoresecret-cygwin "$@" ;;
+ linux*) _kstoresecret-linux "$@" ;;
+ *)
+ echo "$OSTYPE is not supported yet" >&2
+ ;;
+ esac
+}
+
+function _kstoresecret-linux-ensure-gpg-key {
+ local _GNUPGHOME _FPR
+
+ if ! command -v gpg >/dev/null 2>&1; then
+ echo "gpg is required" >&2
+ return 1
+ fi
+
+ _GNUPGHOME="${GNUPGHOME:-$HOME/.gnupg}"
+
+ mkdir -p "$_GNUPGHOME" || return 1
+ chmod 700 "$_GNUPGHOME" 2>/dev/null || true
+
+ if [ -n "${_KSTORE_GPG_RCPT:-}" ]; then
+ _FPR=$(
+ gpg --batch --with-colons --list-secret-keys "$_KSTORE_GPG_RCPT" 2>/dev/null |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+ )
+
+ if [ -n "$_FPR" ]; then
+ export _KSTORE_GPG_RCPT="$_FPR"
+ return 0
+ fi
+ fi
+
+ _FPR=$(
+ gpg --batch --with-colons --list-secret-keys 2>/dev/null |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+ )
+
+ if [ -n "$_FPR" ]; then
+ export _KSTORE_GPG_RCPT="$_FPR"
+ return 0
+ fi
+
+ return 1
+}
+
+function _kstoresecret-linux-generate-gpg-key {
+ local _SECRET _UID _TMP _FPR _CODE
+
+ _SECRET="$1"
+ _UID="${KSTORE_GPG_UID:-rbash kstore <kstore@rbash.local>}"
+
+ if [ -z "$_SECRET" ]; then
+ echo "empty local unlock secret" >&2
+ return 1
+ fi
+
+ _TMP=$(mktemp) || return 1
+ chmod 600 "$_TMP" 2>/dev/null || true
+
+ cat > "$_TMP" <<EOF
+Key-Type: eddsa
+Key-Curve: ed25519
+Key-Usage: sign
+Subkey-Type: ecdh
+Subkey-Curve: cv25519
+Subkey-Usage: encrypt
+Name-Real: rbash kstore
+Name-Email: kstore@rbash.local
+Expire-Date: 0
+Passphrase: $_SECRET
+%commit
+EOF
+
+ gpg --batch --pinentry-mode loopback --generate-key "$_TMP"
+ _CODE=$?
+
+ rm -f "$_TMP"
+
+ if [ "$_CODE" -ne 0 ]; then
+ return "$_CODE"
+ fi
+
+ _FPR=$(
+ gpg --batch --with-colons --list-secret-keys "$_UID" 2>/dev/null |
+ awk -F: '$1 == "fpr" { print $10; exit }'
+ )
+
+ if [ -z "$_FPR" ]; then
+ echo "failed to find generated GPG key fingerprint" >&2
+ return 1
+ fi
+
+ export _KSTORE_GPG_RCPT="$_FPR"
+}
+
+function _kstoresecret-linux {
+ local _SECRET_FILE _RECIPIENT _TMP _A _SECRET _SECRET2
+
+ _SECRET_FILE="${RBASH_HOME:-$HOME/.rbash}/keystore.secret.gpg"
+
+ if ! _kstoresecret-linux-ensure-gpg-key; then
+ read -rsp "Enter new local kstore unlock secret: " _SECRET
+ echo >&2
+ read -rsp "Confirm new local kstore unlock secret: " _SECRET2
+ echo >&2
+
+ if [ -z "$_SECRET" ]; then
+ echo "empty local unlock secret" >&2
+ unset _SECRET _SECRET2
+ return 1
+ fi
+
+ if [ "$_SECRET" != "$_SECRET2" ]; then
+ echo "local unlock secrets do not match" >&2
+ unset _SECRET _SECRET2
+ return 1
+ fi
+
+ _kstoresecret-linux-generate-gpg-key "$_SECRET" || {
+ unset _SECRET _SECRET2
+ return 1
+ }
+
+ unset _SECRET _SECRET2
+ fi
+
+ case "$1" in
+ get)
+ if [ ! -f "$_SECRET_FILE" ]; then
+ return 1
+ fi
+
+ _A=$(
+ gpg --quiet --decrypt "$_SECRET_FILE"
+ ) || {
+ echo "failed to decrypt $_SECRET_FILE" >&2
+ return 1
+ }
+
+ if [ -z "$_A" ]; then
+ echo "decrypted _AUTH_SECRET is empty: $_SECRET_FILE" >&2
+ return 1
+ fi
+
+ _AUTH_SECRET="$_A"
+ unset _A
+ ;;
+
+ set)
+ if [ -z "$_AUTH_SECRET" ]; then
+ echo "_AUTH_SECRET is not loaded; cannot create $_SECRET_FILE" >&2
+ return 1
+ fi
+
+ _RECIPIENT="$_KSTORE_GPG_RCPT"
+
+ mkdir -p "${RBASH_HOME:-$HOME/.rbash}" || return 1
+ chmod 700 "${RBASH_HOME:-$HOME/.rbash}" 2>/dev/null || true
+
+ _TMP=$(mktemp) || return 1
+
+ if printf "%s" "$_AUTH_SECRET" | gpg \
+ --quiet \
+ --batch \
+ --yes \
+ --encrypt \
+ --recipient "$_RECIPIENT" \
+ --output "$_TMP"
+ then
+ chmod 600 "$_TMP" 2>/dev/null || true
+ else
+ rm -f "$_TMP"
+ echo "failed to create $_SECRET_FILE" >&2
+ return 1
+ fi
+
+ _A=$(
+ gpg --quiet --decrypt "$_TMP"
+ ) || {
+ rm -f "$_TMP"
+ echo "failed to verify $_SECRET_FILE" >&2
+ return 1
+ }
+
+ if [ "$_A" != "$_AUTH_SECRET" ]; then
+ rm -f "$_TMP"
+ unset _A
+ echo "verification mismatch for $_SECRET_FILE" >&2
+ return 1
+ fi
+
+ unset _A
+ mv "$_TMP" "$_SECRET_FILE"
+ ;;
+
+ del)
+ rm -f "$_SECRET_FILE"
+ ;;
+
*)
- echo "$OSTYPE is Not supported yet" >&2
+ echo "Unknown action: $1" >&2
+ return 1
;;
esac
}
@@ -260,7 +462,7 @@ function _kstoresecret-config {
fi
case $OSTYPE in
- cygwin|darwin*)
+ cygwin|linux|darwin*)
read -p "Save this password to OS's keystore? (y/n): " _CONFIRM
if [ "$_CONFIRM" != "y" ]; then
return 0