Update bootstrap: 2026-01-20 16:40:01

This commit is contained in:
2026-01-20 16:40:01 +09:00
parent 3050323ff6
commit 664f12656b
3 changed files with 309 additions and 91 deletions

360
nbcrypt
View File

@@ -1,18 +1,18 @@
#!/bin/bash
set -euo pipefail
# nbcrypt - SSH Agent based encryption/decryption tool
# Uses Ed25519 signature for deterministic key derivation
# nbcrypt - SSH Agent ベースの暗号化/復号ツール
# Ed25519 署名で決定的な鍵導出を行う
SCRIPT_NAME="$(basename "$0")"
KEY_SEED_MESSAGE="nbase2-secret-key-seed-v1"
KEY_IDENTITY="id_ed25519"
# Colors for output
# 出力用の色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
NC='\033[0m' # リセット
error() {
echo -e "${RED}❌ Error: $*${NC}" >&2
@@ -27,29 +27,50 @@ warn() {
echo -e "${YELLOW}⚠️ $*${NC}" >&2
}
print_source_agent_hint() {
local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}"
echo "" >&2
info "この agent を現在のシェルで使うには、次を実行:"
echo " source $env_file" >&2
}
usage() {
cat <<EOF
Usage: $SCRIPT_NAME <command> [arguments]
Commands:
encrypt <input> <output> Encrypt a file using SSH Agent key
decrypt [options] <input> <output> Decrypt a file using SSH Agent key
enc <text> Encrypt a text string (or use enctext)
dec <text> Decrypt a text string (or use dectext)
enctext <text> Encrypt a text string (alias for enc)
dectext <text> Decrypt a text string (alias for dec)
encfile <input> <output> Encrypt a file (or use encrypt)
decfile <input> <output> Decrypt a file (or use decrypt)
encrypt <input> <output> Encrypt a file (alias for encfile)
decrypt [options] <input> <output> Decrypt a file (alias for decfile)
keychain [-k|--keep] Fetch SSH keys from BWS and run ssh-add.
Default: remove key files after ssh-add. Use -k/--keep to keep.
install-bws Install Bitwarden Secrets Manager CLI (bws)
check Check if SSH Agent has required key
help Show this help message
Options for decrypt:
Options for decrypt/decfile:
-f, --force Force download nbloader from BWS and setup SSH Agent
(skips SSH Agent check, requires BWS_ACCESS_TOKEN)
Text commands support stdin:
echo "secret" | $SCRIPT_NAME enc -
echo "encrypted" | $SCRIPT_NAME dec -
Requirements:
- SSH Agent must be running with id_ed25519 key loaded
- SSH Agent with id_ed25519 key, or ~/.ssh/id_ed25519 file, or BWS_ACCESS_TOKEN
- ssh-keygen and openssl commands must be available
Examples:
$SCRIPT_NAME encrypt secrets.txt secrets.enc
$SCRIPT_NAME decrypt secrets.enc secrets.txt
$SCRIPT_NAME decrypt -f secrets.enc secrets.txt # Force BWS download
$SCRIPT_NAME enc "secret text"
$SCRIPT_NAME dec "ENC:U2FsdGVkX1..."
$SCRIPT_NAME encfile secrets.txt secrets.enc
$SCRIPT_NAME decfile secrets.enc secrets.txt
$SCRIPT_NAME keychain
$SCRIPT_NAME install-bws
$SCRIPT_NAME check
@@ -71,12 +92,52 @@ check_dependencies() {
fi
}
get_bws_token() {
# 1. すでに環境変数にある
if [ -n "${BWS_ACCESS_TOKEN:-}" ]; then
echo "$BWS_ACCESS_TOKEN"
return 0
fi
# 2. nbconf から取得を試みる
local nbase_root="${NBASE_HOME:-$(cd "$(dirname "$0")/.." && pwd)}"
local nbconf_cmd="${nbase_root}/bin/nbconf"
if [ -f "$nbconf_cmd" ] && [ -x "$nbconf_cmd" ]; then
local val
val=$("$nbconf_cmd" get BWS_ACCESS_TOKEN -d 2>/dev/null) || true
if [ -n "$val" ]; then
echo "$val"
return 0
fi
fi
return 1
}
check_ssh_agent() {
# Check if SSH_AUTH_SOCK is set and valid
local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}"
# まず、以前の BWS セットアップで作られた env ファイルがあれば読む
# SSH_AUTH_SOCK の検査より前にやるenv にソケットパスが入っているため)
if [ -f "$env_file" ]; then
source "$env_file"
info "SSH Agent environment loaded from $env_file"
# 読んだあと ssh-add が動くか確認
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then
if ssh-add -l >/dev/null 2>&1; then
# Ed25519 が載っているか確認
if ssh-add -l 2>/dev/null | grep -q "ED25519"; then
info "SSH Agent check passed (Ed25519 key found)"
return 0
fi
fi
fi
fi
# 現在の環境の SSH_AUTH_SOCK がセットかつ有効か(すでにシェルに Agent がある場合)
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then
# Check if ssh-add can connect to agent
# ssh-add で agent に接続できるか
if ssh-add -l >/dev/null 2>&1; then
# Check if Ed25519 key is loaded
# Ed25519 が載っているか確認
if ssh-add -l 2>/dev/null | grep -q "ED25519"; then
info "SSH Agent check passed (Ed25519 key found)"
return 0
@@ -84,11 +145,13 @@ check_ssh_agent() {
fi
fi
# No valid agent or no Ed25519 key - try BWS bootstrap
# ここに来た場合: (1) env ファイルがない (2) あっても ssh-add 失敗 (3) Ed25519 がない
# いずれも BWS から bootstrap を試す
info "No Ed25519 key found in SSH Agent. Attempting BWS bootstrap..."
# Get BWS access token
local bws_token="${BWS_ACCESS_TOKEN:-}"
# BWS トークン取得環境変数、nbconf、または入力
local bws_token
bws_token=$(get_bws_token) || true
if [ -z "$bws_token" ]; then
echo -n "Enter BWS_ACCESS_TOKEN: " >&2
read bws_token
@@ -97,14 +160,29 @@ check_ssh_agent() {
fi
fi
# Try to load setup script from BWS
# BWS からセットアップスクリプトを取得して実行
load_bws_setup "$bws_token"
# Re-check agent after BWS setup
# BWS セットアップ後に agent を再確認env を読み直す)
if [ -f "$env_file" ]; then
source "$env_file"
fi
if [ -n "${SSH_AUTH_SOCK:-}" ] && [ -S "${SSH_AUTH_SOCK}" ]; then
if ssh-add -l >/dev/null 2>&1 && ssh-add -l 2>/dev/null | grep -q "ED25519"; then
info "SSH Agent check passed (Ed25519 key found after BWS setup)"
return 0
if ssh-add -l >/dev/null 2>&1; then
if ssh-add -l 2>/dev/null | grep -q "ED25519"; then
info "SSH Agent check passed (Ed25519 key found after BWS setup)"
return 0
fi
else
# BWS セットアップ後に ssh-add が失敗
if [ -f "$env_file" ]; then
warn "SSH Agent communication failed after BWS setup."
warn "Please run: source $env_file"
error "SSH Agent communication failed. Run 'source $env_file' and try again."
else
error "SSH Agent communication failed after BWS setup."
fi
fi
fi
@@ -117,23 +195,23 @@ load_bws_setup() {
info "Loading setup script from BWS (secret ID: $secret_id)..."
# Check if bws command exists, if not try to install it
# bws が無ければインストールを試す
if ! command -v bws >/dev/null 2>&1; then
warn "bws command not found. Attempting to install..."
install_bws || error "Failed to install bws CLI"
fi
# Export token temporarily for bws command
# bws 用にトークンを一時 export
export BWS_ACCESS_TOKEN="$token"
# Get the secret from BWS using secret ID
# 秘密 ID で BWS から secret を取得
local loader_script
if command -v jq >/dev/null 2>&1; then
loader_script=$(bws secret get "$secret_id" 2>&1 | jq -r '.value // empty')
elif command -v python3 >/dev/null 2>&1; then
loader_script=$(bws secret get "$secret_id" 2>&1 | python3 -c "import sys, json; print(json.load(sys.stdin).get('value', ''))" 2>/dev/null)
else
# Fallback: try to extract value with grep/sed (fragile but works for simple JSON)
# フォールバック: grep/sed で value を取り出し(単純 JSON 用)
loader_script=$(bws secret get "$secret_id" 2>&1 | grep -o '"value": "[^"]*"' | sed 's/"value": "//;s/"$//' | head -1)
fi
@@ -141,11 +219,11 @@ load_bws_setup() {
error "Failed to retrieve secret from BWS. Check your token and secret ID."
fi
# Execute the loader script
# ローダースクリプトを実行
info "Executing BWS setup script..."
eval "$loader_script"
# Load agent environment if it was created
# 作成されていれば agent env を読む
local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}"
if [ -f "$env_file" ]; then
source "$env_file"
@@ -153,6 +231,32 @@ load_bws_setup() {
fi
}
keychain_cmd() {
local keep_mode="$1"
info "Fetching SSH keys from BWS and running ssh-add..."
local bws_token
bws_token=$(get_bws_token) || true
if [ -z "$bws_token" ]; then
echo -n "Enter BWS_ACCESS_TOKEN: " >&2
read bws_token
if [ -z "$bws_token" ]; then
error "BWS_ACCESS_TOKEN is required"
fi
fi
load_bws_setup "$bws_token"
if [ "$keep_mode" != "true" ]; then
info "Removing key files from disk (use -k/--keep to keep)..."
rm -f ~/.ssh/id_rsa ~/.ssh/id_ecdsa ~/.ssh/id_ed25519
fi
info "SSH agent setup complete."
ssh-add -l
print_source_agent_hint
}
install_bws() {
local arch
arch=$(uname -m)
@@ -163,7 +267,7 @@ install_bws() {
mkdir -p "$bws_bin_dir"
export PATH="$bws_bin_dir:$PATH"
# Determine architecture
# アーキテクチャ判定
case "$arch" in
x86_64)
arch="x86_64-unknown-linux-gnu"
@@ -181,7 +285,7 @@ install_bws() {
info "Downloading bws v${bws_version} for ${arch}..."
# Install dependencies if needed
# 必要なら unzip 等を入れる
if ! command -v unzip >/dev/null 2>&1; then
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y unzip >/dev/null 2>&1
@@ -190,7 +294,7 @@ install_bws() {
fi
fi
# Download and extract
# ダウンロードして展開
local temp_zip="/tmp/${zip_name}"
if command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$temp_zip" || return 1
@@ -209,52 +313,50 @@ install_bws() {
}
derive_key() {
# Derive encryption key from Ed25519 signature
# Ed25519 signatures are deterministic, so same message = same signature
# Ed25519 署名から暗号鍵を導出(同じメッセージなら同じ署名で決定的)
# 優先順位: 1. SSH Agent, 2. 秘密鍵ファイル, 3. BWS (check_ssh_agent で処理)
local temp_message=$(mktemp)
local temp_sig=$(mktemp)
local temp_pubkey=$(mktemp)
local temp_allowed=$(mktemp)
trap "rm -f '$temp_message' '$temp_sig' '$temp_pubkey'" EXIT
trap "rm -f '$temp_message' '$temp_sig' '$temp_pubkey' '$temp_allowed'" EXIT
# Create message to sign
# 署名するメッセージを作成
echo -n "$KEY_SEED_MESSAGE" > "$temp_message"
# Get public key from agent for the Ed25519 key
ssh-add -L 2>/dev/null | grep "ssh-ed25519" | head -1 > "$temp_pubkey"
if [ ! -s "$temp_pubkey" ]; then
error "Could not extract Ed25519 public key from SSH Agent"
# 1. SSH Agent から Ed25519 の公開鍵を取得を試みる
if ssh-add -L 2>/dev/null | grep "ssh-ed25519" | head -1 | awk '{print $1 " " $2}' > "$temp_pubkey" 2>/dev/null && [ -s "$temp_pubkey" ]; then
# Agent から取得できた場合
echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed"
# メッセージに署名Agent経由
if ssh-keygen -Y sign -f "$temp_allowed" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then
# 署名を抜き出してハッシュし 256bit 鍵に
cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64
return 0
fi
fi
# Sign the message using ssh-keygen
# Note: ssh-keygen -Y sign requires the public key in "allowed signers" format
local temp_allowed=$(mktemp)
echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed"
# Sign the message
if ! ssh-keygen -Y sign -f "$temp_allowed" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then
# If -Y sign doesn't work (older ssh-keygen), try alternative method
# We'll use ssh-agent's signing capability directly through a workaround
# Extract just the signature data using ssh-agent protocol
# Since we can't easily do that in pure bash, we'll use a deterministic approach
# based on the public key itself as a fallback
warn "ssh-keygen -Y sign not available, using fallback key derivation"
# Fallback: Use public key hash as seed
cat "$temp_pubkey" | sha256sum | head -c 64
rm -f "$temp_allowed"
return 0
# 2. SSH Agent がない場合、秘密鍵ファイルから直接署名を試みる
local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}"
if [ -f "$key_file" ]; then
# 公開鍵を抽出
if ssh-keygen -y -f "$key_file" 2>/dev/null | awk '{print $1 " " $2}' > "$temp_pubkey" 2>/dev/null && [ -s "$temp_pubkey" ]; then
echo "key1 $(cat "$temp_pubkey")" > "$temp_allowed"
# 秘密鍵ファイルを使って署名
if ssh-keygen -Y sign -f "$key_file" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then
# 署名を抜き出してハッシュし 256bit 鍵に
cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64
return 0
fi
fi
fi
rm -f "$temp_allowed"
# Extract signature and hash it to create 256-bit key
# The signature file contains base64-encoded signature data
cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64
# 3. どちらも失敗した場合、エラーBWS は check_ssh_agent で処理される)
error "Could not derive key from SSH Agent or private key file. Please ensure SSH Agent is running with Ed25519 key, or set NB_KEY_FILE to a valid private key path."
}
encrypt_file() {
@@ -265,9 +367,13 @@ encrypt_file() {
error "Input file not found: $input"
fi
check_ssh_agent
# 秘密鍵ファイルがある場合は check_ssh_agent をスキップ
local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}"
if [ ! -f "$key_file" ]; then
check_ssh_agent
fi
info "Deriving encryption key from SSH Agent..."
info "Deriving encryption key..."
local key=$(derive_key)
if [ -z "$key" ]; then
@@ -276,8 +382,7 @@ encrypt_file() {
info "Encrypting $input -> $output..."
# Use OpenSSL to encrypt with AES-256-CBC
# Pass the hex key directly to openssl
# OpenSSL で AES-256-CBC 暗号化(鍵は hex で渡す)
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$input" -out "$output" -pass "pass:$key"
if [ $? -eq 0 ]; then
@@ -296,10 +401,11 @@ decrypt_file() {
error "Input file not found: $input"
fi
# Force mode: skip SSH Agent check and directly load from BWS
# force: SSH Agent チェックを飛ばし BWS から直接読む
if [ "$force_mode" = "true" ]; then
info "Force mode: Loading setup script from BWS..."
local bws_token="${BWS_ACCESS_TOKEN:-}"
local bws_token
bws_token=$(get_bws_token) || true
if [ -z "$bws_token" ]; then
echo -n "Enter BWS_ACCESS_TOKEN: " >&2
read bws_token
@@ -308,7 +414,7 @@ decrypt_file() {
fi
fi
load_bws_setup "$bws_token"
# After BWS setup, check SSH Agent again
# BWS セットアップ後に agent を再確認
if [ -z "${SSH_AUTH_SOCK:-}" ] || [ ! -S "${SSH_AUTH_SOCK}" ]; then
error "SSH Agent not available after BWS setup"
fi
@@ -316,10 +422,14 @@ decrypt_file() {
error "Ed25519 key not found in SSH Agent after BWS setup"
fi
else
check_ssh_agent
# 秘密鍵ファイルがある場合は check_ssh_agent をスキップ
local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}"
if [ ! -f "$key_file" ]; then
check_ssh_agent
fi
fi
info "Deriving decryption key from SSH Agent..."
info "Deriving decryption key..."
local key=$(derive_key)
if [ -z "$key" ]; then
@@ -328,7 +438,7 @@ decrypt_file() {
info "Decrypting $input -> $output..."
# Use OpenSSL to decrypt with AES-256-CBC
# OpenSSL AES-256-CBC 復号
if openssl enc -aes-256-cbc -d -salt -pbkdf2 -in "$input" -out "$output" -pass "pass:$key" 2>/dev/null; then
info "✅ Decryption successful: $output"
else
@@ -336,7 +446,61 @@ decrypt_file() {
fi
}
# Main command dispatcher
encrypt_text() {
local input_text="$1"
# 秘密鍵ファイルがある場合は check_ssh_agent をスキップ
local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}"
if [ ! -f "$key_file" ]; then
check_ssh_agent
fi
info "Deriving encryption key..."
local key=$(derive_key)
if [ -z "$key" ]; then
error "Failed to derive encryption key"
fi
# 標準入力から読み取る場合
if [ "$input_text" = "-" ]; then
local temp_input=$(mktemp)
cat > "$temp_input"
input_text=$(cat "$temp_input")
rm -f "$temp_input"
fi
# OpenSSL で暗号化して Base64 エンコード
echo -n "$input_text" | openssl enc -aes-256-cbc -salt -pbkdf2 -pass "pass:$key" | base64 -w 0
echo "" # 改行を追加
}
decrypt_text() {
local input_text="$1"
# 秘密鍵ファイルがある場合は check_ssh_agent をスキップ
local key_file="${NB_KEY_FILE:-${HOME}/.ssh/id_ed25519}"
if [ ! -f "$key_file" ]; then
check_ssh_agent
fi
info "Deriving decryption key..."
local key=$(derive_key)
if [ -z "$key" ]; then
error "Failed to derive decryption key"
fi
# 標準入力から読み取る場合
if [ "$input_text" = "-" ]; then
input_text=$(cat)
fi
# Base64 デコードして OpenSSL で復号
echo -n "$input_text" | base64 -d 2>/dev/null | openssl enc -aes-256-cbc -d -salt -pbkdf2 -pass "pass:$key" 2>/dev/null || error "Decryption failed (wrong key or corrupted data)"
}
# メインのコマンド分岐
main() {
if [ $# -eq 0 ]; then
usage
@@ -345,24 +509,36 @@ main() {
local command="$1"
shift
# install-bws コマンドの場合は check_dependencies をスキップ
if [ "$command" != "install-bws" ] && [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then
# install-bws, keychain の場合は check_dependencies をスキップ
if [ "$command" != "install-bws" ] && [ "$command" != "keychain" ] && [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then
check_dependencies
fi
case "$command" in
encrypt)
enc|enctext)
if [ $# -lt 1 ]; then
error "enc requires 1 argument: <text> or '-' for stdin"
fi
encrypt_text "${1:-}"
;;
dec|dectext)
if [ $# -lt 1 ]; then
error "dec requires 1 argument: <text> or '-' for stdin"
fi
decrypt_text "${1:-}"
;;
encfile|encrypt)
if [ $# -ne 2 ]; then
error "encrypt requires 2 arguments: <input> <output>"
error "encfile requires 2 arguments: <input> <output>"
fi
encrypt_file "$1" "$2"
;;
decrypt)
decfile|decrypt)
local force_mode=false
local input=""
local output=""
# Parse arguments
# 引数解析
while [ $# -gt 0 ]; do
case "$1" in
-f|--force)
@@ -386,7 +562,7 @@ main() {
done
if [ -z "$input" ] || [ -z "$output" ]; then
error "decrypt requires 2 arguments: <input> <output>"
error "decfile requires 2 arguments: <input> <output>"
fi
decrypt_file "$force_mode" "$input" "$output"
@@ -395,6 +571,24 @@ main() {
check_ssh_agent
echo "✅ All checks passed"
;;
keychain)
local keep_mode=false
while [ $# -gt 0 ]; do
case "$1" in
-k|--keep)
keep_mode=true
shift
;;
-*)
error "Unknown option: $1"
;;
*)
error "keychain accepts only -k/--keep"
;;
esac
done
keychain_cmd "$keep_mode"
;;
install-bws)
install_bws
;;