Files
bootstrap/nbcrypt

704 lines
23 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
# nbcrypt - SSH Agent ベースの暗号化/復号ツール
# Ed25519 署名で決定的な鍵導出を行う
#
# 重要: 単体動作を最優先する原則
# - このスクリプトは extras/bootstrap の仕組みの中で単体処理として使われる
# - nbconf などの他の nbase2 ツールへの依存は避ける
# - 環境変数や kernel keyring などの標準的な仕組みのみを使用する
SCRIPT_NAME="$(basename "$0")"
KEY_SEED_MESSAGE="nbase2-secret-key-seed-v1"
KEY_IDENTITY="id_ed25519"
# 出力用の色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # リセット
error() {
echo -e "${RED}❌ Error: $*${NC}" >&2
exit 1
}
info() {
echo -e "${GREEN} $*${NC}" >&2
}
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:
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/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 with id_ed25519 key, or ~/.ssh/id_ed25519 file, or BWS_ACCESS_TOKEN
- ssh-keygen and openssl commands must be available
Examples:
$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
EOF
exit 0
}
check_dependencies() {
local missing=()
for cmd in ssh-keygen openssl ssh-add; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
error "Missing required commands: ${missing[*]}"
fi
}
get_bws_token() {
# 1. nbconf が利用可能な場合、nbconf から取得を試みるkeyring キャッシュも含む)
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
# 2. すでに環境変数にある
if [ -n "${BWS_ACCESS_TOKEN:-}" ]; then
echo "$BWS_ACCESS_TOKEN"
return 0
fi
return 1
}
check_ssh_agent() {
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
# ssh-add で agent に接続できるか
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
# ここに来た場合: (1) env ファイルがない (2) あっても ssh-add 失敗 (3) Ed25519 がない
# いずれも BWS から bootstrap を試す
info "No Ed25519 key found in SSH Agent. Attempting BWS bootstrap..."
# 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
if [ -z "$bws_token" ]; then
error "BWS_ACCESS_TOKEN is required when SSH Agent has no Ed25519 key"
fi
fi
# BWS からセットアップスクリプトを取得して実行
load_bws_setup "$bws_token"
# 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; 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
error "No Ed25519 key found in SSH Agent. Please add id_ed25519 with: ssh-add ~/.ssh/id_ed25519"
}
load_bws_setup() {
local token="$1"
local secret_id="6e70094b-6888-4fde-85f9-b3d6007fd68e"
info "Loading setup script from BWS (secret ID: $secret_id)..."
# 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
# bws 用にトークンを一時 export
export BWS_ACCESS_TOKEN="$token"
# 秘密 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
# フォールバック: grep/sed で value を取り出し(単純 JSON 用)
loader_script=$(bws secret get "$secret_id" 2>&1 | grep -o '"value": "[^"]*"' | sed 's/"value": "//;s/"$//' | head -1)
fi
if [ -z "$loader_script" ]; then
error "Failed to retrieve secret from BWS. Check your token and secret ID."
fi
# ローダースクリプトを実行
info "Executing BWS setup script..."
eval "$loader_script"
# 作成されていれば agent 用 env を読む
local env_file="/tmp/.nb_agent_env_${USER:-$(id -un)}"
if [ -f "$env_file" ]; then
source "$env_file"
info "SSH Agent environment loaded from $env_file"
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
}
check_install_bws() {
# アーキテクチャチェック
local arch
arch=$(uname -m)
case "$arch" in
x86_64|aarch64|arm64)
;;
*)
return 1 # サポートされていないアーキテクチャ
;;
esac
# wget または curl が利用可能か
if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
return 1
fi
# unzip が利用可能か、またはインストール可能か
if ! command -v unzip >/dev/null 2>&1; then
if ! command -v apt-get >/dev/null 2>&1 && ! command -v yum >/dev/null 2>&1; then
return 1
fi
fi
return 0
}
install_bws() {
local arch
arch=$(uname -m)
local bws_version="1.0.0"
local bws_bin_dir="${HOME}/.local/bin"
local bws_path="${bws_bin_dir}/bws"
mkdir -p "$bws_bin_dir"
export PATH="$bws_bin_dir:$PATH"
# アーキテクチャ判定
case "$arch" in
x86_64)
arch="x86_64-unknown-linux-gnu"
;;
aarch64|arm64)
arch="aarch64-unknown-linux-gnu"
;;
*)
error "Unsupported architecture: $arch"
;;
esac
local zip_name="bws-${arch}-${bws_version}.zip"
local url="https://github.com/bitwarden/sdk-sm/releases/download/bws-v${bws_version}/${zip_name}"
info "Downloading bws v${bws_version} for ${arch}..."
# 必要なら 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
elif command -v yum >/dev/null 2>&1; then
sudo yum install -y unzip >/dev/null 2>&1
fi
fi
# ダウンロードして展開
local temp_zip="/tmp/${zip_name}"
if command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$temp_zip" || return 1
elif command -v curl >/dev/null 2>&1; then
curl -sL "$url" -o "$temp_zip" || return 1
else
error "wget or curl is required to download bws"
fi
unzip -o "$temp_zip" -d "$bws_bin_dir" >/dev/null 2>&1 || return 1
chmod +x "$bws_path"
rm -f "$temp_zip"
info "bws installed successfully at $bws_path"
return 0
}
derive_key() {
# Ed25519 署名から暗号鍵を導出(同じメッセージなら同じ署名で決定的)
# 優先順位: 1. SSH Agent, 2. 秘密鍵ファイル, 3. BWS (check_ssh_agent で処理)
local check_mode=false
if [ "${1:-}" = "--check" ]; then
check_mode=true
fi
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' '$temp_allowed'" EXIT
# 署名するメッセージを作成
echo -n "$KEY_SEED_MESSAGE" > "$temp_message"
# 1. SSH Agent から Ed25519 の公開鍵を取得を試みる
ssh-add -L 2>/dev/null | grep "ssh-ed25519" | head -1 | awk '{print $1 " " $2}' > "$temp_pubkey" 2>/dev/null
if [ -s "$temp_pubkey" ]; then
# Agent から取得できた場合
# key1 オプションなしで試すssh-keygen -Y sign は allowed_keys 形式のファイルを期待するが、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
if [ "$check_mode" = "true" ]; then
info "Signature obtained from: SSH Agent"
fi
# 署名を抜き出してハッシュし 256bit 鍵に
cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64
return 0
else
if [ "$check_mode" = "true" ]; then
warn "Failed to sign with SSH Agent"
fi
fi
else
if [ "$check_mode" = "true" ]; then
warn "Failed to get Ed25519 key from SSH Agent"
fi
fi
# 2. 秘密鍵ファイルから直接署名を試みる
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
# 秘密鍵ファイルを使って署名(-f で秘密鍵ファイルを直接指定)
if ssh-keygen -Y sign -f "$key_file" -n "nbase2" < "$temp_message" > "$temp_sig" 2>/dev/null; then
if [ "$check_mode" = "true" ]; then
info "Signature obtained from: id_ed25519"
fi
# 署名を抜き出してハッシュし 256bit 鍵に
cat "$temp_sig" | base64 -d 2>/dev/null | sha256sum | head -c 64
return 0
else
if [ "$check_mode" = "true" ]; then
warn "Failed to sign with $key_file"
fi
fi
else
if [ "$check_mode" = "true" ]; then
warn "Failed to extract public key from $key_file"
fi
fi
else
if [ "$check_mode" = "true" ]; then
warn "Private key file not found: $key_file"
fi
fi
# 3. どれも失敗した場合、エラーBWS は check_ssh_agent で処理される)
if [ "$check_mode" = "true" ]; then
warn "Could not derive key from SSH Agent or private key file. Ensure SSH Agent has Ed25519, or set NB_KEY_FILE."
return 1
else
error "Could not derive key from SSH Agent or private key file. Ensure SSH Agent has Ed25519, or set NB_KEY_FILE."
fi
}
encrypt_file() {
local input="$1"
local output="$2"
if [ ! -f "$input" ]; then
error "Input file not found: $input"
fi
# 秘密鍵ファイルがある場合は 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
info "Encrypting $input -> $output..."
# OpenSSL で AES-256-CBC 暗号化(鍵は hex で渡す)
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$input" -out "$output" -pass "pass:$key"
if [ $? -eq 0 ]; then
info "✅ Encryption successful: $output"
else
error "Encryption failed"
fi
}
decrypt_file() {
local force_mode="$1"
local input="$2"
local output="$3"
if [ ! -f "$input" ]; then
error "Input file not found: $input"
fi
# force: SSH Agent チェックを飛ばし BWS から直接読む
if [ "$force_mode" = "true" ]; then
info "Force mode: Loading setup script from BWS..."
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 in force mode"
fi
fi
load_bws_setup "$bws_token"
# BWS セットアップ後に agent を再確認
if [ -z "${SSH_AUTH_SOCK:-}" ] || [ ! -S "${SSH_AUTH_SOCK}" ]; then
error "SSH Agent not available after BWS setup"
fi
if ! ssh-add -l >/dev/null 2>&1 || ! ssh-add -l 2>/dev/null | grep -q "ED25519"; then
error "Ed25519 key not found in SSH Agent after BWS setup"
fi
else
# 秘密鍵ファイルがある場合は 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..."
local key=$(derive_key)
if [ -z "$key" ]; then
error "Failed to derive decryption key"
fi
info "Decrypting $input -> $output..."
# 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
error "Decryption failed (wrong key or corrupted file)"
fi
}
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
fi
local command="$1"
shift
# install-bws, keychain の場合は check_dependencies をスキップ
if [ "$command" != "install-bws" ] && [ "$command" != "keychain" ] && [ "$command" != "help" ] && [ "$command" != "--help" ] && [ "$command" != "-h" ]; then
check_dependencies
fi
case "$command" in
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 "encfile requires 2 arguments: <input> <output>"
fi
encrypt_file "$1" "$2"
;;
decfile|decrypt)
local force_mode=false
local input=""
local output=""
# 引数解析
while [ $# -gt 0 ]; do
case "$1" in
-f|--force)
force_mode=true
shift
;;
-*)
error "Unknown option: $1"
;;
*)
if [ -z "$input" ]; then
input="$1"
elif [ -z "$output" ]; then
output="$1"
else
error "Too many arguments for decrypt"
fi
shift
;;
esac
done
if [ -z "$input" ] || [ -z "$output" ]; then
error "decfile requires 2 arguments: <input> <output>"
fi
decrypt_file "$force_mode" "$input" "$output"
;;
check)
info "Testing signature derivation..."
if derive_key --check >/dev/null; then
echo "✅ Signature derivation successful"
else
warn "All signature methods failed"
info "Checking BWS availability..."
# bws コマンドが利用可能か
if command -v bws >/dev/null 2>&1; then
info "bws command is available"
else
if check_install_bws; then
info "bws can be installed (run 'nbcrypt install-bws')"
else
warn "bws installation is not possible"
fi
fi
# BWS_ACCESS_TOKEN の取得を試みる
local bws_token
if bws_token=$(get_bws_token 2>/dev/null); then
info "BWS_ACCESS_TOKEN is available"
if command -v bws >/dev/null 2>&1; then
info "BWS setup is available. Run 'nbcrypt keychain' to set up SSH Agent."
else
info "BWS_ACCESS_TOKEN is available but bws command is not installed."
fi
else
warn "BWS_ACCESS_TOKEN is not available (checked: nbconf, environment variable)"
fi
fi
;;
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
;;
help|--help|-h)
usage
;;
*)
error "Unknown command: $command (use 'help' for usage)"
;;
esac
}
main "$@"