Files
PlantAI-rag-new/tooling/legacy_plantai_server_package/issue_client.sh
T
jerryandClaude Fable 5 27946edc2b P0/M1: 收编 encryption-test 工具链入 tooling/
- legacy_plantai_server_package/: phase_a–f + compile_* + bin/plantai_admin.py
  + tools/(audit/elf_visibility/smoke/build_delivery_manifest) 完整基线
- legacy_docker_packager/: protected 形态参考
- crypto/client/: plantai_core.py(AES-GCM 原语) + install_plantai.sh 参考
- secrets/provision 严格排除(gitignore),入 git 前已脱敏清理
- LEGACY_MAPPING.md 记录源与改造方向

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 12:59:29 +08:00

130 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Run this on your server, once per customer device, AFTER that customer has
# sent you their device_fingerprint.json (produced by the client package's
# install_plantai.sh fingerprint step).
#
# Usage:
# ./issue_client.sh \
# --fingerprint /path/to/device_fingerprint.json \
# --release-version 1.2.0 \
# --customer-id CUS-0001 \
# --customer-name "Some Customer Co" \
# --serial-no PlantAI-0001
#
# Produces: out/provision/<device_id>/license.json and asset.key.enc,
# packaged into out/provision/plantai-provision-<device_id>.tar.gz
# which is the ONLY thing you send back to that customer.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for cfg in "$SCRIPT_DIR/server.env"; do
[ -f "$cfg" ] && . "$cfg"
done
SECRETS_DIR="${SECRETS_DIR:-$SCRIPT_DIR/secrets}"
ADMIN="$SCRIPT_DIR/bin/plantai_admin.py"
PYTHON="${PLANTAI_PYTHON:-python3}"
KEY_PASSPHRASE_ARGS=()
if [ -n "${SUPPLIER_KEY_PASSPHRASE_ENV:-}" ] && [ -n "${SUPPLIER_KEY_PASSPHRASE_FILE:-}" ]; then
echo "Set only one of SUPPLIER_KEY_PASSPHRASE_ENV or SUPPLIER_KEY_PASSPHRASE_FILE" >&2
exit 1
elif [ -n "${SUPPLIER_KEY_PASSPHRASE_ENV:-}" ]; then
KEY_PASSPHRASE_ARGS=(--key-passphrase-env "$SUPPLIER_KEY_PASSPHRASE_ENV")
elif [ -n "${SUPPLIER_KEY_PASSPHRASE_FILE:-}" ]; then
KEY_PASSPHRASE_ARGS=(--key-passphrase-file "$SUPPLIER_KEY_PASSPHRASE_FILE")
fi
FINGERPRINT=""
RELEASE_VERSION=""
CUSTOMER_ID=""
CUSTOMER_NAME=""
SERIAL_NO=""
EDITION="${EDITION:-standard}"
MIN_MATCH_SCORE="${MIN_MATCH_SCORE:-60}"
ALLOW_WARNING_SCORE="${ALLOW_WARNING_SCORE:-85}"
ALLOWED_ASSETS="${ALLOWED_ASSETS:-runtime-assets}"
ALLOW_UNVERIFIED_TPM_KEY=""
while [ $# -gt 0 ]; do
case "$1" in
--fingerprint) FINGERPRINT="$2"; shift 2 ;;
--release-version) RELEASE_VERSION="$2"; shift 2 ;;
--customer-id) CUSTOMER_ID="$2"; shift 2 ;;
--customer-name) CUSTOMER_NAME="$2"; shift 2 ;;
--serial-no) SERIAL_NO="$2"; shift 2 ;;
--edition) EDITION="$2"; shift 2 ;;
--min-match-score) MIN_MATCH_SCORE="$2"; shift 2 ;;
--allow-warning-score) ALLOW_WARNING_SCORE="$2"; shift 2 ;;
--allowed-assets) ALLOWED_ASSETS="$2"; shift 2 ;;
--allow-unverified-tpm-key) ALLOW_UNVERIFIED_TPM_KEY="--allow-unverified-tpm-key"; shift ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done
for req in FINGERPRINT RELEASE_VERSION CUSTOMER_ID CUSTOMER_NAME SERIAL_NO; do
if [ -z "${!req}" ]; then
echo "Missing required --${req,,}" >&2
exit 1
fi
done
MASTER_KEY="$SECRETS_DIR/master_keys/$RELEASE_VERSION.key"
if [ ! -f "$MASTER_KEY" ]; then
echo "No master key found for release $RELEASE_VERSION at $MASTER_KEY -- run build_release.sh first." >&2
exit 1
fi
DEVICE_ID="$("$PYTHON" -c "import json,sys; print(json.load(open(sys.argv[1]))['device_id'])" "$FINGERPRINT")"
OUT_DIR="$SCRIPT_DIR/out/provision/$DEVICE_ID"
mkdir -p "$OUT_DIR"
echo "[1/3] Log device registration"
mkdir -p "$SCRIPT_DIR/out/registry"
"$PYTHON" - "$FINGERPRINT" "$CUSTOMER_ID" "$DEVICE_ID" "$RELEASE_VERSION" >> "$SCRIPT_DIR/out/registry/devices.jsonl" <<'PYEOF'
import json, sys, datetime
fp_path, customer_id, device_id, release_version = sys.argv[1:5]
fp = json.load(open(fp_path))
print(json.dumps({
"at": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
"device_id": device_id,
"customer_id": customer_id,
"release_version": release_version,
"core_id_anchor": fp.get("core_id_anchor"),
"core_id_hash": fp.get("core_id_hash"),
}, ensure_ascii=False))
PYEOF
# IMPORTANT: wrap-key-for-device runs FIRST, then issue-license with
# --asset-key-envelope so the license contains a cryptographic binding
# (asset_key_sha256) to the exact asset.key.enc. The client will
# verify this hash at runtime to detect tampering or mismatched files.
echo "[2/3] Wrap this release's K_master for this device"
"$PYTHON" "$ADMIN" wrap-key-for-device \
--fingerprint "$FINGERPRINT" \
--master-key "$MASTER_KEY" \
--output "$OUT_DIR/asset.key.enc" \
$ALLOW_UNVERIFIED_TPM_KEY
echo "[3/3] Issue signed license (bound to asset.key.enc hash)"
"$PYTHON" "$ADMIN" issue-license \
--fingerprint "$FINGERPRINT" \
--private-key "$SECRETS_DIR/supplier_private_key.pem" \
--output "$OUT_DIR/license.json" \
--customer-id "$CUSTOMER_ID" \
--customer-name "$CUSTOMER_NAME" \
--serial-no "$SERIAL_NO" \
--edition "$EDITION" \
--release-version "$RELEASE_VERSION" \
--min-match-score "$MIN_MATCH_SCORE" \
--allow-warning-score "$ALLOW_WARNING_SCORE" \
--allowed-assets $ALLOWED_ASSETS \
--asset-key-envelope "$OUT_DIR/asset.key.enc" \
"${KEY_PASSPHRASE_ARGS[@]}"
tar -C "$OUT_DIR" -czf "$SCRIPT_DIR/out/provision/plantai-provision-$DEVICE_ID.tar.gz" license.json asset.key.enc
echo "Done."
echo "Send this file back to the customer: $SCRIPT_DIR/out/provision/plantai-provision-$DEVICE_ID.tar.gz"
echo "It contains only license.json + asset.key.enc -- no keys, no master key."