Crypto¶
xcore_agent.crypto — signature verification, AES-GCM encryption/decryption,
and content digests.
xcore_agent.crypto
¶
Cryptographic primitives used by the deployment pipeline.
Key custody model: xcore-agent never generates or stores long-lived secrets.
The artifact's per-version data-encryption key (DEK) is obtained at deploy
time from XCore Hub (see agent.hub_client.HubClient.obtain_deployment_key)
and kept only in memory for the duration of one deployment.
SignatureVerificationError
¶
DecryptionError
¶
verify_hmac_sha256_hex(*, secret: bytes, signature_hex: str, payload: bytes) -> None
¶
Verify a hmac_sha256:<hex>-style signature (the real XCore Marketplace's
install endpoint — see MarketplaceClient) over payload.
This is a symmetric check: unlike verify_signature (Ed25519), whoever
holds secret can also forge a signature. Authenticity therefore rests on
the operator having obtained secret through a channel they trust (it's
the plugin developer's own xdevkeys signing secret), not on public-key
cryptography — see the "Key custody model" note in the README.
Source code in xcore_agent/crypto.py
sha256_hex(data: bytes) -> str
¶
verify_signature(*, public_key: bytes, signature: bytes, payload: bytes) -> None
¶
Verify an Ed25519 signature over payload.
Raises SignatureVerificationError on any mismatch; never returns a falsy "invalid" value, so a caller cannot accidentally ignore the result.
Source code in xcore_agent/crypto.py
decrypt_aes_gcm(*, key: bytes, nonce: bytes, ciphertext: bytes, associated_data: bytes | None = None) -> bytes
¶
Decrypt+authenticate an AES-256-GCM ciphertext.
key must be the artifact's per-version DEK (32 bytes), already unwrapped
by the Hub — xcore-agent never derives or stores it itself.
Source code in xcore_agent/crypto.py
compute_tree_digest(root: Path, *, exclude: frozenset[str] = frozenset(), skip_patterns: tuple[str, ...] = ()) -> str
¶
Deterministic digest of every file under root, as sha256("relpath:sha256\n"...).
Used both to produce manifest.json's content_sha256 at build time and,
here, to re-verify it after extraction. Hashing individual files instead
of the whole tar means a single tampered file can be pinpointed, and it
sidesteps the circular problem of a manifest hashing a tarball that
contains that same manifest (and therefore its own hash) — manifest.json
itself must always be passed in exclude.
exclude matches a file's full relative path exactly (what manifest.json
needs). skip_patterns matches by glob against every path component
(same semantics as shutil.ignore_patterns) — for packer.builder
passing its _PACKAGING_EXCLUDE_PATTERNS, so this digest is computed
over the same tree _prepare_packaging_view actually seals (.venv,
.env, ... never make it into the packaging view, so they must not
silently count toward content_sha256 either — otherwise the manifest
would describe a tree that isn't the one inside the artifact).