feat!: genesis-pinned operator set, server identity with an encrypted control link, key-less join codes; release key continuity
Join codes need no passkey touch and carry only public values (genesis fingerprint, owner credentials, installer hashes). Nodes pin the genesis and owners at enrollment and only follow passkey-signed operator set versions. The panel's server key is listed in the set; pinned nodes require its signature and run the control link under AES-256-GCM. Password sessions carry no capabilities. The updater adopts a next_key from a signed manifest. Bump version to 0.2.0. [CI SKIP]
This commit is contained in:
1 parent
9bb6841dce
commit
a2f88a344b
89 files changed
+2145
-1195
No files matched your search
@@ -78,6 +78,7 @@ else
|
||||
chmod 755 "$STATUS_DIR"
|
||||
fi
|
||||
INSTALLED="$STATE_DIR/$COMPONENT.installed" # 两行:seq、digest
|
||||
ADOPTED="$STATE_DIR/$COMPONENT.key.pem" # 发布密钥换新之后采用的公钥;有它就只认它,配置里的旧公钥不再用
|
||||
mkdir -p "$STATE_DIR"
|
||||
# Serialize check/apply path events for this component.
|
||||
exec 9>"$STATE_DIR/$COMPONENT.lock"
|
||||
@@ -169,7 +170,18 @@ verify_offer() {
|
||||
[ "$(wc -c < "$OFFER_MANIFEST")" -le 4096 ] || { ERR="manifest too large"; return 1; }
|
||||
sigbin="$WORK/sig.bin"
|
||||
base64 -d "$OFFER_SIG" > "$sigbin" 2>/dev/null || { ERR="bad signature encoding"; return 1; }
|
||||
openssl dgst -sha256 -verify "$PUBKEY" -signature "$sigbin" "$OFFER_MANIFEST" >/dev/null 2>&1 || { ERR="signature check failed"; return 1; }
|
||||
key="$PUBKEY"
|
||||
[ -s "$ADOPTED" ] && key="$ADOPTED"
|
||||
openssl dgst -sha256 -verify "$key" -signature "$sigbin" "$OFFER_MANIFEST" >/dev/null 2>&1 || { ERR="signature check failed"; return 1; }
|
||||
# 密钥延续:这份清单由当前密钥签名,里面的 next_key(SPKI DER 的 base64)就是以后要认的密钥;写不成合法的公钥就整份拒绝
|
||||
M_NEXT=""
|
||||
next="$(field next_key)"
|
||||
if [ -n "$next" ]; then
|
||||
printf '%s' "$next" | base64 -d > "$WORK/next.der" 2>/dev/null \
|
||||
&& openssl pkey -pubin -inform DER -in "$WORK/next.der" -outform PEM -out "$WORK/next.pem" >/dev/null 2>&1 \
|
||||
&& [ -s "$WORK/next.pem" ] || { ERR="next_key is not a public key"; return 1; }
|
||||
M_NEXT="$WORK/next.pem"
|
||||
fi
|
||||
[ "$(field component)" = "$COMPONENT" ] || { ERR="manifest is for another component"; return 1; }
|
||||
[ "$(field image)" = "$IMAGE_REPO" ] || { ERR="manifest names another repository"; return 1; }
|
||||
if [ -n "$CHANNEL" ]; then
|
||||
@@ -183,6 +195,14 @@ verify_offer() {
|
||||
[ "$M_SEQ" -gt "$(installed_seq)" ] || { ERR="stale manifest (seq $M_SEQ <= $(installed_seq))"; return 1; }
|
||||
}
|
||||
|
||||
# 验过签的清单带了 next_key 就改认它(写临时文件再换名;已经是它就不动)
|
||||
adopt_next_key() {
|
||||
[ -n "$M_NEXT" ] || return 0
|
||||
if [ -s "$ADOPTED" ] && cmp -s "$M_NEXT" "$ADOPTED"; then return 0; fi
|
||||
cp "$M_NEXT" "$ADOPTED.new" && mv -f "$ADOPTED.new" "$ADOPTED"
|
||||
echo "release key replaced by the one in the signed manifest"
|
||||
}
|
||||
|
||||
available_json() { printf '{"version":"%s","tag":"%s","digest":"%s","seq":%s}' "$(printf '%s' "$M_VERSION" | tr -d '\042\134')" "$(printf '%s' "$M_TAG" | tr -d '\042\134')" "$M_DIGEST" "$M_SEQ"; }
|
||||
|
||||
do_check() {
|
||||
@@ -195,6 +215,7 @@ do_check() {
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
adopt_next_key
|
||||
if [ "$M_DIGEST" = "$(current_digest)" ]; then
|
||||
write_status up_to_date null ""
|
||||
return 0
|
||||
@@ -215,6 +236,7 @@ do_apply() {
|
||||
fi
|
||||
fetch_offer
|
||||
verify_offer || { write_status invalid null "$ERR"; echo "refusing to apply: $ERR" >&2; return 1; }
|
||||
adopt_next_key
|
||||
if [ "$COMPONENT" = backend ]; then
|
||||
case "$requested_seq" in ''|*[!0-9]*) write_status failed "$(available_json)" "a confirmed release sequence is required"; return 1 ;; esac
|
||||
[ "$requested_seq" = "$M_SEQ" ] || { write_status available "$(available_json)" "release changed; confirm again"; return 1; }
|
||||
|
||||
Reference in new issue
Block a user