keylime/0014-push-attestation-documentation.patch
2026-05-19 15:12:29 -04:00

1911 lines
71 KiB
Diff

From 077762aa335de0cf99e190bd5afb5b77f5403a89 Mon Sep 17 00:00:00 2001
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
Date: Tue, 17 Feb 2026 16:43:04 +0100
Subject: [PATCH] Document agent-driven (push) attestation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
---
docs/assets/push-model-architecture.svg | 86 ++++
docs/assets/push-model-sequence.svg | 122 +++++
docs/conf.py | 1 +
docs/design.rst | 1 +
docs/design/overview.rst | 11 +-
docs/design/push_model.rst | 226 +++++++++
docs/index.rst | 1 +
docs/installation.rst | 11 +
docs/man/keylime_push_model_agent.8.rst | 226 +++++++++
docs/man/keylime_verifier.8.rst | 3 +-
docs/rest_apis.rst | 30 ++
docs/rest_apis/3_0/3_0.rst | 21 +
docs/rest_apis/3_0/verifier.rst | 608 ++++++++++++++++++++++++
docs/user_guide.rst | 1 +
docs/user_guide/configuration.rst | 7 +
docs/user_guide/push_model.rst | 370 ++++++++++++++
16 files changed, 1721 insertions(+), 4 deletions(-)
create mode 100644 docs/assets/push-model-architecture.svg
create mode 100644 docs/assets/push-model-sequence.svg
create mode 100644 docs/design/push_model.rst
create mode 100644 docs/man/keylime_push_model_agent.8.rst
create mode 100644 docs/rest_apis/3_0/3_0.rst
create mode 100644 docs/rest_apis/3_0/verifier.rst
create mode 100644 docs/user_guide/push_model.rst
diff --git a/docs/assets/push-model-architecture.svg b/docs/assets/push-model-architecture.svg
new file mode 100644
index 000000000..82a5672f4
--- /dev/null
+++ b/docs/assets/push-model-architecture.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" width="800" height="400">
+ <defs>
+ <style>
+ text { font-family: Tahoma, sans-serif; font-size: 14px; }
+ .title { font-size: 16px; font-weight: bold; }
+ .subtitle { font-size: 12px; fill: #666; }
+ .box { fill: #f0f0f0; stroke: #333; stroke-width: 1.5; rx: 5; ry: 5; }
+ .arrow { stroke: #333; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead); }
+ .arrow-push { stroke: #2563eb; stroke-width: 2; fill: none; marker-end: url(#arrowhead-push); }
+ .label { font-size: 11px; fill: #555; }
+ .label-push { font-size: 11px; fill: #2563eb; }
+ .section-title { font-size: 13px; font-weight: bold; fill: #333; }
+ </style>
+ <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
+ <polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
+ </marker>
+ <marker id="arrowhead-push" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
+ <polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
+ </marker>
+ </defs>
+
+ <!-- Title -->
+ <text x="400" y="30" text-anchor="middle" class="title">Push-Model Architecture</text>
+
+ <!-- Pull Model (left side) -->
+ <text x="200" y="60" text-anchor="middle" class="section-title">Pull Model (traditional)</text>
+
+ <!-- Pull - Agent box -->
+ <rect x="30" y="120" width="120" height="50" class="box"/>
+ <text x="90" y="150" text-anchor="middle">Agent</text>
+ <text x="90" y="165" text-anchor="middle" class="subtitle">(server, port 9002)</text>
+
+ <!-- Pull - Registrar box -->
+ <rect x="230" y="80" width="120" height="50" class="box"/>
+ <text x="290" y="110" text-anchor="middle">Registrar</text>
+
+ <!-- Pull - Verifier box -->
+ <rect x="230" y="160" width="120" height="50" class="box"/>
+ <text x="290" y="190" text-anchor="middle">Verifier</text>
+
+ <!-- Pull - Agent to Registrar arrow -->
+ <path d="M 150 130 L 225 105" class="arrow"/>
+ <text x="180" y="110" class="label">register</text>
+
+ <!-- Pull - Verifier to Agent arrow -->
+ <path d="M 230 185 L 155 160" class="arrow"/>
+ <text x="170" y="185" class="label">poll quotes</text>
+
+ <!-- Divider -->
+ <line x1="400" y1="55" x2="400" y2="380" stroke="#ccc" stroke-width="1" stroke-dasharray="5,5"/>
+
+ <!-- Push Model (right side) -->
+ <text x="600" y="60" text-anchor="middle" class="section-title">Push Model (new)</text>
+
+ <!-- Push - Agent box -->
+ <rect x="430" y="120" width="120" height="50" class="box" style="stroke: #2563eb;"/>
+ <text x="490" y="150" text-anchor="middle">Agent</text>
+ <text x="490" y="165" text-anchor="middle" class="subtitle">(client, no ports)</text>
+
+ <!-- Push - Registrar box -->
+ <rect x="630" y="80" width="120" height="50" class="box"/>
+ <text x="690" y="110" text-anchor="middle">Registrar</text>
+
+ <!-- Push - Verifier box -->
+ <rect x="630" y="160" width="120" height="50" class="box"/>
+ <text x="690" y="190" text-anchor="middle">Verifier</text>
+
+ <!-- Push - Agent to Registrar arrow -->
+ <path d="M 550 130 L 625 105" class="arrow-push"/>
+ <text x="580" y="110" class="label-push">register</text>
+
+ <!-- Push - Agent to Verifier arrow -->
+ <path d="M 550 160 L 625 180" class="arrow-push"/>
+ <text x="565" y="182" class="label-push">push evidence</text>
+
+ <!-- Legend -->
+ <rect x="30" y="250" width="740" height="130" fill="#fafafa" stroke="#ddd" rx="5" ry="5"/>
+ <text x="50" y="275" class="section-title">Protocol Flow (Push Model)</text>
+
+ <text x="50" y="300" class="label">1. Agent registers with Registrar (same as pull model)</text>
+ <text x="50" y="320" class="label">2. Agent authenticates with Verifier via PoP (POST /v3/sessions)</text>
+ <text x="50" y="340" class="label">3. Agent sends capabilities to Verifier (POST /v3/agents/{agent_id}/attestations) — receives challenge nonce</text>
+ <text x="50" y="360" class="label">4. Agent sends evidence to Verifier (PATCH /v3/agents/{agent_id}/attestations/latest) — receives 202 Accepted</text>
+ <text x="50" y="375" class="label">5. Agent waits for configured interval, then repeats from step 3</text>
+</svg>
diff --git a/docs/assets/push-model-sequence.svg b/docs/assets/push-model-sequence.svg
new file mode 100644
index 000000000..d9affe1c9
--- /dev/null
+++ b/docs/assets/push-model-sequence.svg
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 580" width="700" height="580">
+ <defs>
+ <style>
+ text { font-family: Tahoma, sans-serif; font-size: 12px; }
+ .title { font-size: 16px; font-weight: bold; }
+ .state { fill: #f0f0f0; stroke: #333; stroke-width: 1.5; rx: 20; ry: 20; }
+ .state-initial { fill: #e0e7ff; stroke: #2563eb; stroke-width: 2; rx: 20; ry: 20; }
+ .state-active { fill: #dbeafe; stroke: #2563eb; stroke-width: 2; rx: 20; ry: 20; }
+ .state-error { fill: #fee2e2; stroke: #dc2626; stroke-width: 1.5; rx: 20; ry: 20; }
+ .arrow { stroke: #333; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead); }
+ .arrow-success { stroke: #16a34a; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead-success); }
+ .arrow-error { stroke: #dc2626; stroke-width: 1.5; fill: none; marker-end: url(#arrowhead-error); }
+ .arrow-retry { stroke: #f59e0b; stroke-width: 1.5; fill: none; stroke-dasharray: 5,3; marker-end: url(#arrowhead-retry); }
+ .label { font-size: 10px; fill: #555; }
+ .label-success { font-size: 10px; fill: #16a34a; }
+ .label-error { font-size: 10px; fill: #dc2626; }
+ .label-retry { font-size: 10px; fill: #f59e0b; }
+ .state-text { font-size: 13px; font-weight: bold; text-anchor: middle; }
+ .state-desc { font-size: 10px; text-anchor: middle; fill: #666; }
+ </style>
+ <marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+ <polygon points="0 0, 8 3, 0 6" fill="#333"/>
+ </marker>
+ <marker id="arrowhead-success" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+ <polygon points="0 0, 8 3, 0 6" fill="#16a34a"/>
+ </marker>
+ <marker id="arrowhead-error" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+ <polygon points="0 0, 8 3, 0 6" fill="#dc2626"/>
+ </marker>
+ <marker id="arrowhead-retry" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
+ <polygon points="0 0, 8 3, 0 6" fill="#f59e0b"/>
+ </marker>
+ </defs>
+
+ <!-- Title -->
+ <text x="350" y="30" text-anchor="middle" class="title">Push-Model Agent State Machine</text>
+
+ <!-- Unregistered state -->
+ <rect x="50" y="60" width="160" height="55" class="state-initial"/>
+ <text x="130" y="85" class="state-text">Unregistered</text>
+ <text x="130" y="100" class="state-desc">Initial state</text>
+
+ <!-- Registered state -->
+ <rect x="300" y="60" width="160" height="55" class="state-active"/>
+ <text x="380" y="85" class="state-text">Registered</text>
+ <text x="380" y="100" class="state-desc">Ready for attestation</text>
+
+ <!-- Negotiating state -->
+ <rect x="300" y="190" width="160" height="55" class="state-active"/>
+ <text x="380" y="215" class="state-text">Negotiating</text>
+ <text x="380" y="230" class="state-desc">Phase 1: capabilities</text>
+
+ <!-- Attesting state -->
+ <rect x="300" y="320" width="160" height="55" class="state-active"/>
+ <text x="380" y="345" class="state-text">Attesting</text>
+ <text x="380" y="360" class="state-desc">Phase 2: evidence</text>
+
+ <!-- RegistrationFailed state -->
+ <rect x="50" y="190" width="160" height="55" class="state-error"/>
+ <text x="130" y="215" class="state-text">Reg. Failed</text>
+ <text x="130" y="230" class="state-desc">Will retry</text>
+
+ <!-- AttestationFailed state -->
+ <rect x="540" y="260" width="160" height="55" class="state-error"/>
+ <text x="620" y="285" class="state-text">Attest. Failed</text>
+ <text x="620" y="300" class="state-desc">Will retry</text>
+
+ <!-- Unregistered -> Registered (success) -->
+ <path d="M 210 87 L 295 87" class="arrow-success"/>
+ <text x="250" y="80" class="label-success">registration OK</text>
+
+ <!-- Unregistered -> RegistrationFailed (error) -->
+ <path d="M 130 115 L 130 185" class="arrow-error"/>
+ <text x="137" y="155" class="label-error">failed</text>
+
+ <!-- RegistrationFailed -> Unregistered (retry) -->
+ <path d="M 80 190 Q 55 155 80 115" class="arrow-retry"/>
+ <text x="30" y="155" class="label-retry">retry</text>
+
+ <!-- Registered -> Negotiating -->
+ <path d="M 380 115 L 380 185" class="arrow-success"/>
+ <text x="387" y="155" class="label-success">start negotiation</text>
+
+ <!-- Negotiating -> Attesting (success) -->
+ <path d="M 380 245 L 380 315" class="arrow-success"/>
+ <text x="387" y="283" class="label-success">201 Created</text>
+
+ <!-- Negotiating -> AttestationFailed (error) -->
+ <path d="M 460 220 L 535 275" class="arrow-error"/>
+ <text x="510" y="240" class="label-error">error</text>
+
+ <!-- Attesting -> Negotiating (loop - success) -->
+ <path d="M 300 345 Q 245 280 300 220" class="arrow-success"/>
+ <text x="228" y="280" class="label-success">202 Accepted</text>
+ <text x="228" y="292" class="label-success">(wait interval)</text>
+
+ <!-- Attesting -> AttestationFailed (error) -->
+ <path d="M 460 350 L 535 295" class="arrow-error"/>
+ <text x="510" y="335" class="label-error">rejected</text>
+
+ <!-- AttestationFailed -> Negotiating (retry) -->
+ <path d="M 620 260 Q 620 215 465 215" class="arrow-retry"/>
+ <text x="530" y="210" class="label-retry">retry</text>
+
+ <!-- Legend -->
+ <rect x="50" y="430" width="600" height="130" fill="#fafafa" stroke="#ddd" rx="5" ry="5"/>
+ <text x="70" y="455" font-weight="bold" font-size="13">Legend</text>
+
+ <line x1="70" y1="475" x2="120" y2="475" stroke="#16a34a" stroke-width="2"/>
+ <text x="130" y="480" class="label">Success transition</text>
+
+ <line x1="70" y1="495" x2="120" y2="495" stroke="#dc2626" stroke-width="2"/>
+ <text x="130" y="500" class="label">Error transition</text>
+
+ <line x1="70" y1="515" x2="120" y2="515" stroke="#f59e0b" stroke-width="2" stroke-dasharray="5,3"/>
+ <text x="130" y="520" class="label">Retry (with exponential backoff)</text>
+
+ <text x="350" y="480" class="label">Phase 1: Agent POSTs capabilities, receives challenge nonce</text>
+ <text x="350" y="500" class="label">Phase 2: Agent PATCHes evidence, receives 202 Accepted</text>
+ <text x="350" y="520" class="label">The Negotiating/Attesting cycle repeats continuously</text>
+</svg>
diff --git a/docs/conf.py b/docs/conf.py
index 5543afa86..00d9735de 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -154,6 +154,7 @@
("man/keylime_registrar.8", "keylime_registrar", "Keylime registrar service", [author], 8),
("man/keylime_verifier.8", "keylime_verifier", "Keylime verifier service", [author], 8),
("man/keylime_agent.8", "keylime_agent", "Keylime agent service", [author], 8),
+ ("man/keylime_push_model_agent.8", "keylime_push_model_agent", "Keylime push-model agent service", [author], 8),
]
diff --git a/docs/design.rst b/docs/design.rst
index 522ade113..dd72fd4e7 100644
--- a/docs/design.rst
+++ b/docs/design.rst
@@ -7,6 +7,7 @@ Design of Keylime
:caption: Contents:
design/overview.rst
+ design/push_model.rst
design/security.rst
diff --git a/docs/design/overview.rst b/docs/design/overview.rst
index 4c7b52227..985cbc94b 100644
--- a/docs/design/overview.rst
+++ b/docs/design/overview.rst
@@ -51,9 +51,14 @@ Verifier
The verifier implements the actual attestation of an agent and sends revocation messages if an agent leaves the trusted
state.
-Once an agent is registered for attestation (using the tenant or the API directly) the verifier continuously pulls
-the required attestation data from the agent. This can include: a quote over the PCRs, the PCR values, NK public key,
-IMA log and UEFI event log. After that the quote is validated additional validation of the data can be configured.
+In the default **pull model**, once an agent is registered for attestation (using the tenant or the API directly)
+the verifier continuously pulls the required attestation data from the agent. This can include: a quote over the
+PCRs, the PCR values, NK public key, IMA log and UEFI event log. After that the quote is validated additional
+validation of the data can be configured.
+
+Keylime also supports a **push model** where the agent initiates connections to the verifier and proactively
+submits attestation evidence. This is useful for environments where the verifier cannot directly reach the
+agent (e.g. behind firewalls or NAT). See :doc:`push_model` for details.
Static PCR values
"""""""""""""""""
diff --git a/docs/design/push_model.rst b/docs/design/push_model.rst
new file mode 100644
index 000000000..29f9061e0
--- /dev/null
+++ b/docs/design/push_model.rst
@@ -0,0 +1,226 @@
+========================
+Push-Model Attestation
+========================
+
+.. warning::
+ Push-model attestation is currently experimental. The feature is functional
+ but the API and configuration options may change in future releases.
+ Please report issues at https://github.com/keylime/keylime/issues/?q=label:push-mode
+
+Introduction
+------------
+
+Traditional Keylime attestation uses a **pull model** where the verifier continuously
+polls agents for attestation data. The agent acts as a server and the verifier initiates
+connections to it. This model requires that the verifier can reach the agent over the
+network.
+
+The **push model** reverses this communication direction: the agent initiates connections
+to the verifier and proactively sends attestation data. The verifier never connects to
+the agent. This makes push-model attestation suitable for environments where the
+verifier cannot directly reach the agent, such as:
+
+* **Edge and IoT devices** behind firewalls or NAT
+* **Hybrid cloud environments** with restricted network policies
+* **Air-gapped networks** where inbound connections to agents are not permitted
+* **Dynamic environments** where agent IP addresses change frequently
+
+In push mode, the agent is a separate binary (``keylime-push-model-agent``) that
+implements the push attestation protocol using API version 3.0.
+
+Architectural Overview
+----------------------
+
+In pull-model attestation, the verifier runs a polling loop that periodically contacts
+each registered agent to request a TPM quote and associated evidence. The agent exposes
+an HTTPS server that responds to these requests.
+
+In push-model attestation, this relationship is inverted:
+
+* The **agent initiates** all connections to the verifier
+* The agent does **not expose any HTTP endpoints** (no listening ports)
+* The verifier accepts incoming attestation data from agents
+* Verification is performed **asynchronously** after evidence is received
+* An **event-driven timeout** system replaces the polling loop for monitoring agent
+ liveness
+
+The registrar interaction is unchanged: in both models, the agent registers itself
+with the registrar during startup.
+
+.. figure:: ../assets/push-model-architecture.svg
+ :width: 600
+ :align: center
+ :alt: Diagram showing the push-model architecture where the agent initiates
+ connections to both the registrar and the verifier, contrasted with the pull
+ model where the verifier connects to the agent.
+
+ **Figure 1:** Push-Model Architecture
+
+The Two-Phase Attestation Protocol
+-----------------------------------
+
+Push-model attestation uses a two-phase protocol for each attestation cycle.
+
+Phase 1: Capabilities Negotiation
+""""""""""""""""""""""""""""""""""
+
+The agent begins an attestation cycle by sending its capabilities to the verifier.
+This tells the verifier what types of evidence the agent can produce and what
+cryptographic algorithms it supports.
+
+1. The agent sends a ``POST /v3/agents/{agent_id}/attestations`` request to the
+ verifier containing its supported evidence types (TPM quote parameters, IMA log
+ capabilities, UEFI log capabilities) and the public attestation key (AK).
+
+2. The verifier creates an attestation resource, selects cryptographic parameters
+ (signature scheme, hash algorithm, PCRs to quote), generates a random challenge
+ nonce, and returns a ``201 Created`` response with:
+
+ * The challenge nonce for TPM quote generation
+ * The chosen cryptographic parameters
+ * The evidence types requested
+ * A deadline (``challenges_expire_at``) by which evidence must be submitted
+
+Phase 2: Evidence Submission
+"""""""""""""""""""""""""""""
+
+The agent collects the requested evidence and submits it to the verifier.
+
+1. The agent generates a TPM quote using the challenge nonce from Phase 1,
+ collects IMA and/or UEFI event logs as requested, and sends a
+ ``PATCH /v3/agents/{agent_id}/attestations/latest`` request with the evidence.
+
+2. The verifier returns a ``202 Accepted`` response immediately. The evidence is
+ then verified asynchronously in a background worker process.
+
+3. If verification succeeds, the attestation is marked as ``pass``. If it fails,
+ the attestation is marked as ``fail`` with a failure reason
+ (``broken_evidence_chain`` or ``policy_violation``).
+
+4. The response includes a ``seconds_to_next_attestation`` value in the ``meta``
+ field, indicating when the agent should start its next attestation cycle.
+
+After a configurable interval, the agent begins a new cycle from Phase 1.
+
+Agent State Machine
+"""""""""""""""""""
+
+The push-model agent operates as a state machine with the following states:
+
+.. figure:: ../assets/push-model-sequence.svg
+ :width: 600
+ :align: center
+ :alt: Sequence diagram showing the push-model agent state machine transitions
+ from Unregistered through Registered, Negotiating, and Attesting states.
+
+ **Figure 2:** Push-Model Agent State Machine
+
+* **Unregistered**: Initial state. The agent registers with the registrar.
+* **Registered**: Registration succeeded. The agent begins negotiation with the
+ verifier.
+* **Negotiating**: The agent sends capabilities to the verifier (Phase 1) and waits
+ for the challenge response.
+* **Attesting**: The agent generates and sends evidence to the verifier (Phase 2).
+ On success, the agent waits for the configured interval and transitions back to
+ Negotiating.
+* **RegistrationFailed**: Registration with the registrar failed. The agent waits
+ and retries.
+* **AttestationFailed**: An attestation attempt failed (network error or verifier
+ rejection). The agent waits and retries from Negotiating.
+
+The agent uses exponential backoff when retrying failed operations.
+
+Authentication
+--------------
+
+Push-model attestation uses **Proof of Possession (PoP)** authentication instead of
+the mTLS client certificates used in pull mode. This is necessary because the agent
+acts as a client (not a server) and does not have certificates signed by the verifier's
+trusted CA.
+
+The PoP authentication flow:
+
+1. The agent creates a session by sending ``POST /v3/sessions`` with its agent ID
+ and supported authentication methods.
+2. The verifier responds with a challenge nonce.
+3. The agent proves possession of its AK by signing the challenge using the TPM
+ (``TPM2_Certify``) and sends the result via ``PATCH /v3/sessions/{session_id}``.
+4. If the signature is valid, the verifier issues a bearer token.
+5. The agent includes this token in the ``Authorization`` header of all subsequent
+ requests.
+6. Tokens have a configurable expiration time and can be refreshed.
+
+The TLS connection uses **server verification only**: the agent verifies the verifier's
+server certificate but does not present a client certificate. The agent needs the
+verifier's CA certificate for this verification.
+
+For full details on the authorization framework, including the separation between
+agent and admin authentication, see :doc:`../user_guide/authentication`.
+
+Timeout Monitoring
+------------------
+
+In pull mode, the verifier detects unresponsive agents through its polling loop. In
+push mode, an event-driven timeout system serves this purpose.
+
+The verifier monitors push-mode agents as follows:
+
+1. When the verifier receives an attestation from an agent, it schedules a timeout
+ for that agent. The timeout duration is ``quote_interval * 5`` seconds (where
+ ``quote_interval`` is the verifier's configured quote interval).
+
+2. If the agent does not submit a new attestation before the timeout fires, the
+ verifier sets the agent's ``accept_attestations`` flag to ``False``.
+
+3. Once ``accept_attestations`` is ``False``, the verifier rejects new attestation
+ requests from that agent with a ``403 Forbidden`` response.
+
+4. The agent can recover by re-registering or by administrator intervention
+ (reactivation).
+
+Comparison with Pull Model
+---------------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 35 35
+
+ * - Aspect
+ - Pull Model
+ - Push Model
+ * - Connection direction
+ - Verifier connects to agent
+ - Agent connects to verifier
+ * - Agent binary
+ - ``keylime_agent``
+ - ``keylime_push_model_agent``
+ * - Agent network requirements
+ - Must expose HTTP port (default 9002)
+ - No listening ports required
+ * - Firewall requirements
+ - Inbound to agent from verifier
+ - Outbound from agent to verifier
+ * - Authentication method
+ - mTLS (agent as server)
+ - PoP bearer tokens (agent as client)
+ * - API version
+ - v2.x
+ - v3.0
+ * - Verification trigger
+ - Verifier polls on ``quote_interval``
+ - Agent pushes on ``attestation_interval_seconds``
+ * - Liveness detection
+ - Polling loop state machine
+ - Event-driven timeout (``quote_interval * 5``)
+ * - Verifier configuration
+ - ``mode = pull`` (default)
+ - ``mode = push``
+ * - Suitable for
+ - Controlled networks, data centers
+ - Edge, IoT, NAT, firewalled environments
+ * - Maturity
+ - Stable
+ - Experimental
+
+For deployment and configuration instructions, see :doc:`../user_guide/push_model`.
+For the v3.0 API reference, see :doc:`../rest_apis/3_0/3_0`.
diff --git a/docs/index.rst b/docs/index.rst
index 8234217fd..fd5f08bed 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -43,6 +43,7 @@ what the goals of Keylime are and how they are implemented.
man/keylime_verifier.8
man/keylime_registrar.8
man/keylime_agent.8
+ man/keylime_push_model_agent.8
man/keylime_policy.1
Indices and tables
diff --git a/docs/installation.rst b/docs/installation.rst
index 21d35a793..b96574137 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -62,6 +62,17 @@ Rust agent
Installation instructions can be found in the `README.md <https://github.com/keylime/rust-keylime>`_ for the Rust agent.
+Push-model agent
+~~~~~~~~~~~~~~~~
+.. note::
+ The push-model agent (``keylime-push-model-agent``) is a separate binary from
+ the standard Rust agent. It implements the push attestation protocol where the
+ agent initiates connections to the verifier. This feature is currently experimental.
+
+ Installation instructions are the same as for the Rust agent. The push-model
+ agent binary is built from the same repository. For configuration and deployment
+ details, see the :doc:`user_guide/push_model` user guide.
+
Keylime Bash installer
----------------------
diff --git a/docs/man/keylime_push_model_agent.8.rst b/docs/man/keylime_push_model_agent.8.rst
new file mode 100644
index 000000000..b033db801
--- /dev/null
+++ b/docs/man/keylime_push_model_agent.8.rst
@@ -0,0 +1,226 @@
+==========================
+keylime_push_model_agent
+==========================
+
+------------------------------------------------------------
+Keylime push-model agent for TPM-based remote attestation
+------------------------------------------------------------
+
+:Manual section: 8
+:Author: Keylime Developers
+:Date: February 2026
+
+SYNOPSIS
+========
+
+**keylime_push_model_agent** [*OPTIONS*]
+
+(Most operations require root privileges, use with sudo)
+
+DESCRIPTION
+===========
+
+The push-model agent is a long-running service that runs on systems to be attested.
+Unlike the standard Keylime agent which acts as a server and waits for the verifier
+to poll it, the push-model agent initiates connections to the verifier and proactively
+submits attestation evidence.
+
+The agent registers with the registrar, authenticates with the verifier using Proof of
+Possession (PoP), and performs periodic attestation cycles consisting of capabilities
+negotiation and evidence submission.
+
+This agent uses API version 3.0 and requires the verifier to be configured in push
+mode (``mode = push``).
+
+OPTIONS
+=======
+
+**--verifier-url** *URL*
+ URL of the verifier (must use HTTPS). Default: ``https://localhost:8881``
+
+**--registrar-url** *URL*
+ URL of the registrar. Default: ``http://127.0.0.1:8888``
+
+**--agent-identifier** *ID*
+ Agent UUID. Overrides the ``uuid`` configuration option.
+
+**--attestation-interval-seconds** *SECONDS*
+ Interval between attestation cycles. Default: ``60``
+
+**--ca-certificate** *PATH*
+ CA certificate file for verifying the verifier's TLS certificate. Overrides
+ ``verifier_tls_ca_cert``.
+
+**--api-version** *VERSION*
+ API version to use. Default: ``v3.0``
+
+**--timeout** *MILLISECONDS*
+ HTTP request timeout. Default: ``5000``
+
+**--insecure**
+ Accept invalid TLS certificates. For testing only.
+
+**--avoid-tpm**
+ Use a mock TPM instead of hardware TPM. For testing only.
+
+**--json-file** *FILE*
+ JSON file for payload data.
+
+**--attestation-index** *INDEX*
+ Attestation index value. Default: ``1``
+
+**--session-index** *INDEX*
+ Session index value. Default: ``1``
+
+**--message-type** *TYPE*
+ Message type (Attestation, EvidenceHandling, Session). Default: ``Attestation``
+
+**--method** *METHOD*
+ HTTP method. Default: ``POST``
+
+CONFIGURATION
+=============
+
+Primary configuration is read from ``/etc/keylime/agent.conf`` (TOML format).
+All options are under the ``[agent]`` section. Command-line arguments override
+configuration file values.
+
+Drop-in overrides: files in ``/etc/keylime/agent.conf.d/`` are applied in
+lexicographic order.
+
+Push-model specific options:
+
+**verifier_url**
+ URL of the verifier. Must use HTTPS. Default: ``https://localhost:8881``
+
+**verifier_tls_ca_cert**
+ Path to CA certificate for verifying the verifier's TLS certificate.
+ Relative paths are resolved from ``keylime_dir``. Default: ``cv_ca/cacert.crt``
+
+**attestation_interval_seconds**
+ Interval in seconds between attestation cycles. Default: ``60``
+
+**api_versions**
+ API versions to use. Default: ``3.0``
+
+**certification_keys_server_identifier**
+ Server identifier for attestation key certification. Default: ``ak``
+
+**uefi_logs_evidence_version**
+ UEFI logs evidence format version. Default: ``2.1``
+
+**exponential_backoff_initial_delay**
+ Initial retry delay in milliseconds. Default: ``10000``
+
+**exponential_backoff_max_retries**
+ Maximum number of retry attempts. Default: ``5``
+
+**exponential_backoff_max_delay**
+ Maximum retry delay in milliseconds. Default: ``300000``
+
+Shared options (same as standard agent):
+
+**uuid**
+ Agent identifier. Default: auto-generated UUID.
+
+**registrar_ip**, **registrar_port**
+ Registrar endpoint. Default: ``127.0.0.1:8890``
+
+**registrar_tls_enabled**
+ Enable TLS for registrar communication. Default: ``false``
+
+**registrar_tls_ca_cert**
+ CA certificate for registrar TLS verification. Default: ``cv_ca/cacert.crt``
+
+**tpm_hash_alg**, **tpm_encryption_alg**, **tpm_signing_alg**
+ TPM algorithms. Defaults: ``sha256``, ``rsa``, ``rsassa``
+
+**keylime_dir**
+ Working directory. Default: ``/var/lib/keylime``
+
+**run_as**
+ User:group to drop privileges to. Default: ``keylime:tss``
+
+**enable_iak_idevid**
+ Enable IAK/IDevID usage. Default: ``false``
+
+ENVIRONMENT
+===========
+
+**KEYLIME_AGENT_CONFIG**
+ Path to agent.conf (highest priority)
+
+**KEYLIME_DIR**
+ Working directory (default: ``/var/lib/keylime``)
+
+**RUST_LOG**
+ Log level configuration. Default in systemd service:
+ ``keylime_push_model_agent=info,keylime=info``
+
+All configuration options can be overridden via environment variables in the form
+``KEYLIME_AGENT_<OPTION_NAME>`` (e.g. ``KEYLIME_AGENT_VERIFIER_URL``).
+
+FILES
+=====
+
+``/etc/keylime/agent.conf``
+ TOML format configuration file (shared with standard agent)
+
+``/etc/keylime/agent.conf.d/``
+ Drop-in configuration snippets
+
+``/var/lib/keylime/cv_ca/cacert.crt``
+ Default CA certificate for verifier TLS verification
+
+``/var/lib/keylime/agent_data.json``
+ Persisted agent TPM data
+
+RUNTIME
+=======
+
+Start directly:
+
+.. code-block:: bash
+
+ sudo keylime_push_model_agent --verifier-url https://verifier.example.com:8881
+
+Start as a systemd service:
+
+.. code-block:: bash
+
+ sudo systemctl enable --now keylime_push_model_agent
+
+Check service status:
+
+.. code-block:: bash
+
+ sudo systemctl status keylime_push_model_agent
+ sudo journalctl -u keylime_push_model_agent -f
+
+PREREQUISITES
+=============
+
+- Root privileges (use sudo)
+- TPM 2.0 available (verify with ``tpm2_pcrread``)
+- Verifier configured with ``mode = push``
+- Network connectivity from agent to verifier and registrar
+- Verifier CA certificate available on agent machine
+
+NOTES
+=====
+
+- This service conflicts with ``keylime_agent.service``. Only one agent type can
+ run on a machine at a time.
+- The push-model agent does not expose any listening ports.
+- Push-model attestation is currently experimental.
+- Authentication uses PoP bearer tokens, not mTLS client certificates.
+
+SEE ALSO
+========
+
+**keylime_agent**\(8), **keylime_verifier**\(8), **keylime_registrar**\(8), **keylime_tenant**\(1)
+
+BUGS
+====
+
+Report bugs at https://github.com/keylime/rust-keylime/issues
diff --git a/docs/man/keylime_verifier.8.rst b/docs/man/keylime_verifier.8.rst
index fd7cfb941..5303a5f06 100644
--- a/docs/man/keylime_verifier.8.rst
+++ b/docs/man/keylime_verifier.8.rst
@@ -32,6 +32,7 @@ Primary configuration is read from ``/etc/keylime/verifier.conf`` (or an overrid
All options are under the ``[verifier]`` section.
Essentials:
+- **mode**: Attestation mode (``pull`` or ``push``). Default: ``pull``
- **uuid**: Unique identifier for this verifier instance
- **ip**, **port**: Bind address and HTTP port
- **registrar_ip**, **registrar_port**: Registrar endpoint
@@ -108,7 +109,7 @@ NOTES
SEE ALSO
========
-**keylime_registrar**\(8), **keylime_tenant**\(1), **keylime_agent**\(8)
+**keylime_registrar**\(8), **keylime_tenant**\(1), **keylime_agent**\(8), **keylime_push_model_agent**\(8)
BUGS
====
diff --git a/docs/rest_apis.rst b/docs/rest_apis.rst
index edfe8be1c..aba64c338 100644
--- a/docs/rest_apis.rst
+++ b/docs/rest_apis.rst
@@ -14,10 +14,40 @@ Check the :ref:`Changelog` section for the differences between versions
rest_apis/2_3/2_3.rst
rest_apis/2_4/2_4.rst
rest_apis/2_5/2_5.rst
+ rest_apis/3_0/3_0.rst
Changelog
_________
+Changes from v2.5 to v3.0
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+API version 3.0 introduces push-model attestation. Unlike previous versions where
+the verifier polls agents, in v3.0 agents initiate connections and submit
+attestation evidence to the verifier. The v3.0 endpoints are served by the
+verifier only; the push-model agent does not expose HTTP endpoints.
+
+* Added `POST /v3/agents/{agent_id}/attestations` endpoint to the verifier:
+ * Allows agents to submit attestation capabilities (Phase 1 of push protocol)
+ * Returns challenge nonce for TPM quote generation
+* Added `PATCH /v3/agents/{agent_id}/attestations/latest` endpoint:
+ * Allows agents to submit attestation evidence (Phase 2 of push protocol)
+ * Returns `202 Accepted` for asynchronous verification
+* Added `PATCH /v3/agents/{agent_id}/attestations/{index}` endpoint:
+ * Submit evidence for a specific attestation by index
+* Added `GET /v3/agents/{agent_id}/attestations` endpoint:
+ * Lists all attestation records for an agent
+* Added `GET /v3/agents/{agent_id}/attestations/latest` endpoint:
+ * Returns the most recent attestation for an agent, including verification status
+* Added `GET /v3/agents/{agent_id}/attestations/{index}` endpoint:
+ * Returns a specific attestation by its index
+* Added `POST /v3/sessions` endpoint:
+ * Creates a PoP authentication session and returns a challenge nonce for the agent
+* Added `PATCH /v3/sessions/{session_id}` endpoint:
+ * Completes PoP authentication by submitting the TPM-signed challenge response
+* Introduced PoP (Proof of Possession) bearer token authentication for
+ agent-to-verifier communication
+
Changes from v2.4 to v2.5
~~~~~~~~~~~~~~~~~~~~~~~~~
API version 2.5 was first implemented in Keylime 7.14.0.
diff --git a/docs/rest_apis/3_0/3_0.rst b/docs/rest_apis/3_0/3_0.rst
new file mode 100644
index 000000000..d6cac705d
--- /dev/null
+++ b/docs/rest_apis/3_0/3_0.rst
@@ -0,0 +1,21 @@
+RESTful API for Keylime (v3.0)
+------------------------------
+
+API version 3.0 introduces push-model attestation, where agents initiate
+connections to the verifier and proactively submit attestation evidence.
+
+Unlike previous API versions where the agent exposed HTTP endpoints for the
+verifier to poll, in v3.0 the agent acts as a client. The v3.0 endpoints are
+served by the **verifier only**. The push-model agent does not expose an API.
+
+For a conceptual overview of push-model attestation, see
+:doc:`../../design/push_model`.
+
+.. warning::
+ Push-model attestation is currently experimental. The API may change in
+ future releases.
+
+.. toctree::
+ :maxdepth: 2
+
+ verifier.rst
diff --git a/docs/rest_apis/3_0/verifier.rst b/docs/rest_apis/3_0/verifier.rst
new file mode 100644
index 000000000..3476cc7a3
--- /dev/null
+++ b/docs/rest_apis/3_0/verifier.rst
@@ -0,0 +1,608 @@
+Verifier
+~~~~~~~~
+
+Push-Model Attestation Endpoints
+"""""""""""""""""""""""""""""""""
+
+These endpoints implement the two-phase push-model attestation protocol. Agents
+use these endpoints to submit attestation capabilities and evidence. Administrators
+can use the GET endpoints to view attestation results.
+
+For details on authentication requirements, see :doc:`../../user_guide/authentication`.
+
+.. http:post:: /v3/agents/{agent_id}/attestations
+
+ Phase 1: Submit attestation capabilities and receive a challenge.
+
+ The agent sends its supported evidence types, cryptographic algorithms, and
+ attestation key. The verifier selects parameters and returns a challenge nonce
+ for TPM quote generation.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+
+ **Authentication**: PoP bearer token (agent-only)
+
+ **Example request**:
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "attestation",
+ "attributes": {
+ "evidence_supported": [
+ {
+ "evidence_class": "certification",
+ "evidence_type": "tpm_quote",
+ "capabilities": {
+ "signature_schemes": ["rsassa"],
+ "hash_algorithms": ["sha256", "sha384", "sha512"],
+ "available_subjects": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
+ "certification_keys": [
+ {
+ "key_class": "asymmetric",
+ "key_algorithm": "rsa",
+ "key_size": 2048,
+ "server_identifier": "ak",
+ "allowable_signature_schemes": ["rsassa"],
+ "allowable_hash_algorithms": ["sha256", "sha384", "sha512"],
+ "public": "<base64-encoded AK public key>"
+ }
+ ],
+ "component_version": "2.0",
+ "evidence_version": "1.0"
+ }
+ },
+ {
+ "evidence_class": "log",
+ "evidence_type": "ima_log",
+ "capabilities": {
+ "entry_count": 1024,
+ "supports_partial_access": true,
+ "appendable": true,
+ "formats": ["text/plain"],
+ "component_version": "1.0",
+ "evidence_version": "1.0"
+ }
+ }
+ ],
+ "system_info": {
+ "boot_time": "2024-01-15T10:30:00Z"
+ }
+ }
+ }
+ }
+
+ **Example response** (201 Created):
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "attestation",
+ "id": "0",
+ "attributes": {
+ "stage": "awaiting_evidence",
+ "evidence_requested": [
+ {
+ "evidence_class": "certification",
+ "evidence_type": "tpm_quote",
+ "chosen_parameters": {
+ "challenge": "<base64-encoded nonce>",
+ "signature_scheme": "rsassa",
+ "hash_algorithm": "sha256",
+ "selected_subjects": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
+ "certification_key": {
+ "key_class": "asymmetric",
+ "key_algorithm": "rsa",
+ "key_size": 2048,
+ "server_identifier": "ak"
+ }
+ }
+ },
+ {
+ "evidence_class": "log",
+ "evidence_type": "ima_log",
+ "chosen_parameters": {
+ "starting_offset": 0,
+ "entry_count": 1024,
+ "format": "text/plain"
+ }
+ }
+ ],
+ "system_info": {
+ "boot_time": "2024-01-15T10:30:00Z"
+ },
+ "capabilities_received_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:35:00.123456Z"
+ },
+ "links": {
+ "self": "/v3/agents/{agent_id}/attestations/0"
+ }
+ }
+ }
+
+ :<json string data.type: Must be ``"attestation"``
+ :<json array data.attributes.evidence_supported: List of evidence types the agent can produce
+ :<json string evidence_supported[].evidence_class: ``"certification"`` or ``"log"``
+ :<json string evidence_supported[].evidence_type: ``"tpm_quote"``, ``"ima_log"``, or ``"uefi_log"``
+ :<json object evidence_supported[].capabilities: Capabilities for this evidence type
+ :<json object data.attributes.system_info: System information (e.g. boot time)
+ :>json string data.id: Attestation index (auto-incremented per agent)
+ :>json string data.attributes.stage: ``"awaiting_evidence"``
+ :>json array data.attributes.evidence_requested: Evidence the verifier wants the agent to provide
+ :>json string evidence_requested[].chosen_parameters.challenge: Base64-encoded challenge nonce for TPM quote
+ :>json string data.attributes.capabilities_received_at: ISO 8601 timestamp
+ :>json string data.attributes.challenges_expire_at: Deadline for evidence submission
+ :>json string data.links.self: URL to this attestation resource
+
+ :statuscode 201: Attestation created, challenge issued
+ :statuscode 400: Invalid request body
+ :statuscode 403: Attestations disabled for this agent (timeout or previous failure)
+ :statuscode 404: Agent not found
+ :statuscode 409: Concurrent attestation creation attempt
+ :statuscode 422: Invalid capabilities data
+ :statuscode 429: Rate limited (attestation interval not elapsed). Includes ``Retry-After`` header
+ :statuscode 503: Previous attestation still being verified. Includes ``Retry-After`` header
+
+
+.. http:patch:: /v3/agents/{agent_id}/attestations/latest
+
+ Phase 2: Submit attestation evidence for the latest attestation.
+
+ The agent sends the TPM quote, PCR values, and event logs generated using the
+ challenge nonce from Phase 1. The verifier accepts the evidence and verifies it
+ asynchronously.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+
+ **Authentication**: PoP bearer token (agent-only)
+
+ **Example request**:
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "attestation",
+ "attributes": {
+ "evidence_collected": [
+ {
+ "evidence_class": "certification",
+ "evidence_type": "tpm_quote",
+ "data": {
+ "subject_data": {
+ "0": "<PCR 0 value>",
+ "1": "<PCR 1 value>"
+ },
+ "message": "<base64-encoded TPM quote>",
+ "signature": "<base64-encoded quote signature>"
+ }
+ },
+ {
+ "evidence_class": "log",
+ "evidence_type": "ima_log",
+ "data": {
+ "entry_count": 512,
+ "entries": "<base64-encoded or raw IMA log entries>"
+ }
+ }
+ ]
+ }
+ }
+ }
+
+ **Example response** (202 Accepted):
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "attestation",
+ "id": "0",
+ "attributes": {
+ "stage": "evaluating_evidence",
+ "evidence": [
+ {
+ "evidence_class": "certification",
+ "evidence_type": "tpm_quote",
+ "capabilities": {},
+ "chosen_parameters": {},
+ "data": {
+ "message": "<base64-encoded TPM quote>",
+ "signature": "<base64-encoded quote signature>",
+ "subject_data": {}
+ }
+ }
+ ],
+ "system_info": {
+ "boot_time": "2024-01-15T10:30:00Z"
+ },
+ "capabilities_received_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:35:00.123456Z",
+ "evidence_received_at": "2024-01-15T10:31:00.123456Z"
+ },
+ "links": {
+ "self": "/v3/agents/{agent_id}/attestations/0"
+ }
+ },
+ "meta": {
+ "seconds_to_next_attestation": 45
+ }
+ }
+
+ :<json string data.type: Must be ``"attestation"``
+ :<json array data.attributes.evidence_collected: List of evidence items
+ :<json string evidence_collected[].evidence_class: ``"certification"`` or ``"log"``
+ :<json string evidence_collected[].evidence_type: Type of evidence (must match what was requested)
+ :<json object evidence_collected[].data: Evidence data (format depends on evidence type)
+ :>json string data.attributes.stage: ``"evaluating_evidence"`` (verification in progress)
+ :>json array data.attributes.evidence: Evidence items with capabilities, parameters, and data
+ :>json string data.attributes.evidence_received_at: ISO 8601 timestamp when evidence was received
+ :>json int meta.seconds_to_next_attestation: Suggested wait before starting the next attestation cycle
+
+ :statuscode 202: Evidence accepted, verification in progress
+ :statuscode 400: Invalid evidence format
+ :statuscode 403: Evidence already submitted, attestation is not the latest, or challenges expired
+ :statuscode 404: Agent or attestation not found
+ :statuscode 410: Attestation no longer exists
+ :statuscode 503: No available worker processes. Includes ``Retry-After`` header
+
+
+.. http:patch:: /v3/agents/{agent_id}/attestations/{index}
+
+ Submit attestation evidence for a specific attestation by index.
+
+ Behaves identically to ``PATCH /v3/agents/{agent_id}/attestations/latest``
+ but targets a specific attestation index. Evidence can only be submitted for
+ the latest attestation.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+ :param index: Attestation index
+ :type index: integer
+
+ **Authentication**: PoP bearer token (agent-only)
+
+ :statuscode 202: Evidence accepted
+ :statuscode 403: Not the latest attestation, evidence already submitted, or challenges expired
+ :statuscode 404: Agent or attestation not found
+
+
+.. http:get:: /v3/agents/{agent_id}/attestations
+
+ List all attestations for an agent.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+
+ **Authentication**: mTLS (admin) or PoP bearer token (own agent only)
+
+ **Example response**:
+
+ .. sourcecode:: json
+
+ {
+ "data": [
+ {
+ "type": "attestation",
+ "id": "1",
+ "attributes": {
+ "stage": "verification_complete",
+ "evaluation": "pass",
+ "evidence": [],
+ "system_info": {
+ "boot_time": "2024-01-15T10:30:00Z"
+ },
+ "capabilities_received_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:35:00.123456Z",
+ "evidence_received_at": "2024-01-15T10:31:00.123456Z",
+ "verification_completed_at": "2024-01-15T10:32:00.123456Z"
+ },
+ "links": {
+ "self": "/v3/agents/{agent_id}/attestations/1"
+ }
+ },
+ {
+ "type": "attestation",
+ "id": "0",
+ "attributes": {
+ "stage": "verification_complete",
+ "evaluation": "pass",
+ "evidence": [],
+ "system_info": {},
+ "capabilities_received_at": "2024-01-15T10:25:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:30:00.123456Z",
+ "evidence_received_at": "2024-01-15T10:26:00.123456Z",
+ "verification_completed_at": "2024-01-15T10:27:00.123456Z"
+ },
+ "links": {
+ "self": "/v3/agents/{agent_id}/attestations/0"
+ }
+ }
+ ]
+ }
+
+ :>json array data: List of attestation resources
+ :>json string data[].id: Attestation index
+ :>json string data[].attributes.stage: ``"awaiting_evidence"``, ``"evaluating_evidence"``, or ``"verification_complete"``
+ :>json string data[].attributes.evaluation: ``"pending"``, ``"pass"``, or ``"fail"``
+ :>json string data[].attributes.failure_reason: ``"broken_evidence_chain"`` or ``"policy_violation"`` (only when evaluation is ``"fail"``)
+
+ :statuscode 200: Success
+ :statuscode 404: Agent not found
+
+
+.. http:get:: /v3/agents/{agent_id}/attestations/latest
+
+ Get the latest attestation for an agent.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+
+ **Authentication**: mTLS (admin) or PoP bearer token (own agent only)
+
+ **Example response**:
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "attestation",
+ "id": "1",
+ "attributes": {
+ "stage": "verification_complete",
+ "evaluation": "pass",
+ "failure_reason": null,
+ "evidence": [
+ {
+ "evidence_class": "certification",
+ "evidence_type": "tpm_quote",
+ "capabilities": {},
+ "chosen_parameters": {},
+ "data": {
+ "message": "<base64-encoded TPM quote>",
+ "signature": "<base64-encoded signature>",
+ "subject_data": {}
+ }
+ }
+ ],
+ "system_info": {
+ "boot_time": "2024-01-15T10:30:00Z"
+ },
+ "capabilities_received_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:35:00.123456Z",
+ "evidence_received_at": "2024-01-15T10:31:00.123456Z",
+ "verification_completed_at": "2024-01-15T10:32:00.123456Z"
+ },
+ "links": {
+ "self": "/v3/agents/{agent_id}/attestations/1"
+ }
+ }
+ }
+
+ :>json string data.attributes.stage: Current stage of the attestation
+ :>json string data.attributes.evaluation: ``"pending"``, ``"pass"``, or ``"fail"``
+ :>json string data.attributes.failure_reason: ``null``, ``"broken_evidence_chain"``, or ``"policy_violation"``
+ :>json array data.attributes.evidence: Evidence items with full data
+ :>json string data.attributes.capabilities_received_at: When capabilities were received
+ :>json string data.attributes.challenges_expire_at: When challenges expire
+ :>json string data.attributes.evidence_received_at: When evidence was received (``null`` if still awaiting)
+ :>json string data.attributes.verification_completed_at: When verification completed (``null`` if still in progress)
+
+ :statuscode 200: Success
+ :statuscode 404: Agent not found or no attestations exist
+
+
+.. http:get:: /v3/agents/{agent_id}/attestations/{index}
+
+ Get a specific attestation by index.
+
+ :param agent_id: UUID of the agent
+ :type agent_id: string
+ :param index: Attestation index
+ :type index: integer
+
+ **Authentication**: mTLS (admin) or PoP bearer token (own agent only)
+
+ Response format is identical to ``GET /v3/agents/{agent_id}/attestations/latest``.
+
+ :statuscode 200: Success
+ :statuscode 404: Agent or attestation not found
+
+
+Session Endpoints
+"""""""""""""""""
+
+These endpoints manage PoP (Proof of Possession) authentication sessions for
+push-model agents. Sessions are required before an agent can submit attestations.
+
+.. http:post:: /v3/sessions
+
+ Create a new authentication session.
+
+ The verifier generates a challenge nonce that the agent must sign using its
+ TPM attestation key to prove possession.
+
+ **Authentication**: None (public endpoint)
+
+ **Example request**:
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "session",
+ "attributes": {
+ "agent_id": "d432fbb3-d2f1-4a97-9ef7-75bd81c00000",
+ "authentication_supported": [
+ {
+ "authentication_class": "pop",
+ "authentication_type": "tpm_pop"
+ }
+ ]
+ }
+ }
+ }
+
+ **Example response** (200 OK):
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "session",
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "attributes": {
+ "agent_id": "d432fbb3-d2f1-4a97-9ef7-75bd81c00000",
+ "authentication_requested": [
+ {
+ "authentication_class": "pop",
+ "authentication_type": "tpm_pop",
+ "chosen_parameters": {
+ "challenge": "<base64-encoded nonce>"
+ }
+ }
+ ],
+ "created_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:31:00.123456Z"
+ }
+ }
+ }
+
+ :<json string data.attributes.agent_id: UUID of the agent requesting a session
+ :<json array data.attributes.authentication_supported: Supported authentication methods
+ :>json string data.id: Session UUID
+ :>json string data.attributes.challenges_expire_at: Deadline for submitting the PoP response
+
+ :statuscode 200: Session created
+ :statuscode 400: Missing or invalid agent_id
+ :statuscode 429: Rate limited. Includes ``Retry-After`` header
+
+
+.. http:patch:: /v3/sessions/{session_id}
+
+ Submit Proof of Possession response to complete authentication.
+
+ The agent signs the challenge nonce from the session creation response using
+ ``TPM2_Certify`` and submits the result. If valid, the verifier issues a bearer
+ token for subsequent API calls.
+
+ :param session_id: UUID of the session
+ :type session_id: string
+
+ **Authentication**: None (public endpoint; validates PoP internally)
+
+ **Example request**:
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "session",
+ "attributes": {
+ "agent_id": "d432fbb3-d2f1-4a97-9ef7-75bd81c00000",
+ "authentication_provided": [
+ {
+ "authentication_class": "pop",
+ "authentication_type": "tpm_pop",
+ "data": {
+ "message": "<base64-encoded AK attest structure>",
+ "signature": "<base64-encoded AK signature>"
+ }
+ }
+ ]
+ }
+ }
+ }
+
+ **Example response** (200 OK, authentication passed):
+
+ .. sourcecode:: json
+
+ {
+ "data": {
+ "type": "session",
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "attributes": {
+ "agent_id": "d432fbb3-d2f1-4a97-9ef7-75bd81c00000",
+ "evaluation": "pass",
+ "token": "550e8400-e29b-41d4-a716-446655440000.<secret>",
+ "authentication": [
+ {
+ "authentication_class": "pop",
+ "authentication_type": "tpm_pop",
+ "chosen_parameters": {
+ "challenge": "<base64-encoded nonce>"
+ },
+ "data": {
+ "message": "<base64-encoded AK attest>",
+ "signature": "<base64-encoded AK signature>"
+ }
+ }
+ ],
+ "created_at": "2024-01-15T10:30:00.123456Z",
+ "challenges_expire_at": "2024-01-15T10:31:00.123456Z",
+ "response_received_at": "2024-01-15T10:30:30.123456Z",
+ "token_expires_at": "2024-01-15T11:30:00.123456Z"
+ }
+ }
+ }
+
+ :>json string data.attributes.evaluation: ``"pass"`` or ``"fail"``
+ :>json string data.attributes.token: Bearer token for subsequent requests (only on ``"pass"``)
+ :>json string data.attributes.token_expires_at: Token expiration time (only on ``"pass"``)
+
+ :statuscode 200: PoP response processed (check ``evaluation`` field for result)
+ :statuscode 400: Missing or invalid request body
+ :statuscode 401: PoP verification failed
+ :statuscode 404: Session not found
+
+
+Attestation Stages and Evaluations
+"""""""""""""""""""""""""""""""""""
+
+Each attestation progresses through the following stages:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Stage
+ - Description
+ * - ``awaiting_evidence``
+ - Capabilities received, challenge issued, waiting for evidence
+ * - ``evaluating_evidence``
+ - Evidence received, verification in progress
+ * - ``verification_complete``
+ - Verification finished, see ``evaluation`` for result
+
+The ``evaluation`` field indicates the verification result:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 80
+
+ * - Evaluation
+ - Description
+ * - ``pending``
+ - Verification not yet complete
+ * - ``pass``
+ - Evidence verified successfully
+ * - ``fail``
+ - Evidence verification failed (see ``failure_reason``)
+
+When an attestation fails, the ``failure_reason`` field provides the cause:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Failure Reason
+ - Description
+ * - ``broken_evidence_chain``
+ - TPM quote signature invalid or evidence integrity check failed
+ * - ``policy_violation``
+ - Evidence is valid but violates the configured attestation policy
diff --git a/docs/user_guide.rst b/docs/user_guide.rst
index 9bd44c512..ed052c175 100644
--- a/docs/user_guide.rst
+++ b/docs/user_guide.rst
@@ -8,6 +8,7 @@ User Guide
user_guide/authentication.rst
user_guide/configuration.rst
+ user_guide/push_model.rst
user_guide/runtime_ima.rst
user_guide/user_selected_pcr_monitoring.rst
user_guide/use_measured_boot.rst
diff --git a/docs/user_guide/configuration.rst b/docs/user_guide/configuration.rst
index 6d8f35c88..2e50757df 100644
--- a/docs/user_guide/configuration.rst
+++ b/docs/user_guide/configuration.rst
@@ -40,6 +40,13 @@ The following components can be configured:
- ``/etc/keylime/logging.conf``
- ``/etc/keylime/logging.conf.d``
+.. note::
+ For push-model attestation, the verifier must be configured with ``mode = push``
+ in the ``[verifier]`` section. The push-model agent uses the same
+ ``/etc/keylime/agent.conf`` file (TOML format) but with additional options such
+ as ``verifier_url`` and ``attestation_interval_seconds``. See
+ :doc:`push_model` for details.
+
The next sections contain details of the configuration files
Configuration file processing order
diff --git a/docs/user_guide/push_model.rst b/docs/user_guide/push_model.rst
new file mode 100644
index 000000000..773d2aaaa
--- /dev/null
+++ b/docs/user_guide/push_model.rst
@@ -0,0 +1,370 @@
+========================
+Push-Model Attestation
+========================
+
+.. warning::
+ Push-model attestation is currently experimental. The feature is functional
+ but the API and configuration options may change in future releases.
+
+Introduction
+------------
+
+In the default pull model, the Keylime verifier continuously polls agents for
+attestation data. This requires the verifier to reach the agent over the network.
+
+The push model reverses this: the agent initiates connections to the verifier and
+proactively sends attestation evidence. This is useful when the verifier cannot
+directly reach the agent, for example behind firewalls, NAT, or in edge/IoT
+deployments.
+
+For a detailed description of how push-model attestation works, see
+:doc:`../design/push_model`.
+
+Prerequisites
+-------------
+
+* Keylime verifier and registrar installed and running
+* The ``keylime-push-model-agent`` binary installed on the target machine
+* A TPM 2.0 device (hardware or emulated for development)
+* Network connectivity **from the agent to the verifier and registrar** (the
+ reverse is not required)
+* The verifier's CA certificate available on the agent machine
+
+Configuring the Verifier for Push Mode
+--------------------------------------
+
+Set the verifier's attestation mode to ``push`` in ``/etc/keylime/verifier.conf``:
+
+.. code-block:: ini
+
+ [verifier]
+ mode = push
+
+Or use a configuration snippet in ``/etc/keylime/verifier.conf.d/``:
+
+.. code-block:: ini
+
+ # /etc/keylime/verifier.conf.d/001-push-mode.conf
+ [verifier]
+ mode = push
+
+The verifier can also be configured via environment variable:
+
+.. code-block:: bash
+
+ export KEYLIME_VERIFIER_MODE=push
+
+.. note::
+ The ``mode`` setting affects all agents on this verifier. A verifier in push
+ mode expects agents to submit attestation data; it does not poll agents. A
+ single verifier cannot operate in both modes simultaneously.
+
+Additional verifier settings relevant to push mode:
+
+* ``quote_interval``: Used to calculate the agent timeout threshold
+ (``quote_interval * 5``). Default: ``2`` seconds.
+* ``challenge_lifetime``: How long a challenge nonce remains valid for evidence
+ submission.
+* ``verification_timeout``: Maximum time allowed for evidence verification.
+
+After changing the configuration, restart the verifier:
+
+.. code-block:: bash
+
+ sudo systemctl restart keylime_verifier
+
+Configuring the Push-Model Agent
+---------------------------------
+
+The push-model agent is a separate binary from the standard Keylime agent. It is
+installed as ``keylime_push_model_agent`` (or ``keylime-push-model-agent``).
+
+The agent is configured through ``/etc/keylime/agent.conf`` (TOML format), command-line
+arguments, or environment variables.
+
+Key Configuration Options
+"""""""""""""""""""""""""
+
+The following options are specific to or particularly important for push-model
+operation:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 15 55
+
+ * - Option
+ - Default
+ - Description
+ * - ``verifier_url``
+ - ``https://localhost:8881``
+ - URL of the verifier. Must use HTTPS.
+ * - ``verifier_tls_ca_cert``
+ - ``cv_ca/cacert.crt``
+ - Path to the CA certificate for verifying the verifier's TLS certificate.
+ Relative paths are resolved from ``keylime_dir``.
+ * - ``attestation_interval_seconds``
+ - ``60``
+ - Interval in seconds between attestation cycles.
+ * - ``registrar_ip``
+ - ``127.0.0.1``
+ - IP address of the registrar.
+ * - ``registrar_port``
+ - ``8890``
+ - Port of the registrar.
+ * - ``registrar_tls_enabled``
+ - ``false``
+ - Enable TLS for registrar communication.
+ * - ``registrar_tls_ca_cert``
+ - ``cv_ca/cacert.crt``
+ - CA certificate for registrar TLS verification.
+ * - ``uuid``
+ - (generated)
+ - Agent UUID. Can be a specific UUID, ``generate`` (random), or
+ ``hash_ek`` (derived from the EK).
+ * - ``api_versions``
+ - ``3.0``
+ - API versions supported by the agent. Defaults to ``3.0`` for push model.
+ * - ``tpm_hash_alg``
+ - ``sha256``
+ - TPM hash algorithm (``sha256``, ``sha384``, ``sha512``).
+ * - ``tpm_signing_alg``
+ - ``rsassa``
+ - TPM signing algorithm (``rsassa``, ``ecdsa``).
+ * - ``keylime_dir``
+ - ``/var/lib/keylime``
+ - Working directory for certificates and data files.
+
+Example Minimal Configuration
+""""""""""""""""""""""""""""""
+
+.. code-block:: toml
+
+ # /etc/keylime/agent.conf (push-model agent)
+ [agent]
+ uuid = "d432fbb3-d2f1-4a97-9ef7-75bd81c00000"
+ verifier_url = "https://verifier.example.com:8881"
+ verifier_tls_ca_cert = "/var/lib/keylime/cv_ca/cacert.crt"
+ attestation_interval_seconds = 60
+ registrar_ip = "registrar.example.com"
+ registrar_port = 8890
+ tpm_hash_alg = "sha256"
+ tpm_signing_alg = "rsassa"
+
+Command-Line Arguments
+""""""""""""""""""""""
+
+The push-model agent accepts the following command-line arguments, which override
+configuration file values:
+
+.. code-block:: text
+
+ --verifier-url <URL> Verifier URL (required)
+ --registrar-url <URL> Registrar URL (default: http://127.0.0.1:8888)
+ --agent-identifier <ID> Agent UUID
+ --attestation-interval-seconds <SECS> Attestation interval (default: 60)
+ --ca-certificate <PATH> CA certificate for TLS verification
+ --api-version <VERSION> API version (default: v3.0)
+ --timeout <MS> Request timeout in milliseconds (default: 5000)
+ --insecure Accept invalid TLS certificates (testing only)
+ --avoid-tpm Use mock TPM (testing only)
+
+Exponential Backoff
+"""""""""""""""""""
+
+When the agent encounters errors (network failures, verifier unavailable), it uses
+exponential backoff for retries:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 15 50
+
+ * - Option
+ - Default
+ - Description
+ * - ``exponential_backoff_initial_delay``
+ - ``10000``
+ - Initial delay in milliseconds (10 seconds)
+ * - ``exponential_backoff_max_retries``
+ - ``5``
+ - Maximum number of retry attempts
+ * - ``exponential_backoff_max_delay``
+ - ``300000``
+ - Maximum delay in milliseconds (5 minutes)
+
+Systemd Service Management
+---------------------------
+
+The push-model agent is managed as a systemd service:
+
+.. code-block:: bash
+
+ # Enable the service to start on boot
+ sudo systemctl enable keylime_push_model_agent
+
+ # Start the service
+ sudo systemctl start keylime_push_model_agent
+
+ # Check service status
+ sudo systemctl status keylime_push_model_agent
+
+ # View logs
+ sudo journalctl -u keylime_push_model_agent -f
+
+.. warning::
+ The push-model agent service (``keylime_push_model_agent.service``) conflicts
+ with the standard pull-model agent service (``keylime_agent.service``). Only one
+ can run at a time on the same machine. Starting one will stop the other.
+
+The service is configured to restart on failure with a 120-second delay between
+restart attempts.
+
+Enrolling an Agent for Push-Model Attestation
+---------------------------------------------
+
+Use the ``keylime_tenant`` tool with the ``--push-model`` flag to enroll an agent
+for push-model attestation:
+
+.. code-block:: bash
+
+ # Add an agent in push mode
+ sudo keylime_tenant -c add --push-model -u <agent-uuid>
+
+ # Add with a runtime IMA policy
+ sudo keylime_tenant -c add --push-model -u <agent-uuid> \
+ --runtime-policy-name <policy-name>
+
+ # Add with a measured boot policy
+ sudo keylime_tenant -c add --push-model -u <agent-uuid> \
+ --mb-policy-name <policy-name>
+
+.. note::
+ In push mode, the ``-t`` / ``--targethost`` option is not required because the
+ verifier does not need to connect to the agent. The agent's IP and port are set
+ to ``None`` in the verifier's database.
+
+To check the status of a push-model agent:
+
+.. code-block:: bash
+
+ sudo keylime_tenant -c cvstatus -u <agent-uuid>
+
+To remove an agent:
+
+.. code-block:: bash
+
+ sudo keylime_tenant -c delete -u <agent-uuid>
+
+TLS Configuration for Push Model
+---------------------------------
+
+The push model uses TLS differently from the pull model:
+
+**Agent-to-verifier connection:**
+
+* The agent connects to the verifier over HTTPS
+* The agent verifies the verifier's server certificate using the configured CA
+ certificate (``verifier_tls_ca_cert``)
+* The agent does **not** present a client certificate (no mTLS)
+* Authentication is done via PoP bearer tokens (see :doc:`authentication`)
+
+**Agent-to-registrar connection:**
+
+* The agent connects to the registrar to register itself
+* TLS can be enabled with ``registrar_tls_enabled = true``
+* The registrar CA certificate is configured with ``registrar_tls_ca_cert``
+
+**Firewall considerations:**
+
+* No inbound ports need to be opened on the agent machine
+* The agent needs outbound access to the verifier port (default: 8881)
+* The agent needs outbound access to the registrar port (default: 8890)
+
+To set up TLS, copy the verifier's CA certificate to the agent machine:
+
+.. code-block:: bash
+
+ # On the verifier machine, the CA cert is typically at:
+ # /var/lib/keylime/cv_ca/cacert.crt
+
+ # Copy to the agent machine:
+ scp verifier:/var/lib/keylime/cv_ca/cacert.crt /var/lib/keylime/cv_ca/cacert.crt
+
+Verifying the Deployment
+-------------------------
+
+After starting both the verifier (in push mode) and the push-model agent:
+
+1. **Check agent registration** in the registrar:
+
+ .. code-block:: bash
+
+ sudo keylime_tenant -c regstatus -u <agent-uuid>
+
+2. **Check attestation status** in the verifier:
+
+ .. code-block:: bash
+
+ sudo keylime_tenant -c cvstatus -u <agent-uuid>
+
+3. **View verifier logs** for attestation activity:
+
+ .. code-block:: bash
+
+ sudo journalctl -u keylime_verifier -f
+
+ Successful attestations will show evidence receipt and verification completion
+ messages.
+
+4. **View agent logs** for attestation cycles:
+
+ .. code-block:: bash
+
+ sudo journalctl -u keylime_push_model_agent -f
+
+ The agent logs will show transitions through the state machine:
+ registration, negotiation, and attestation phases.
+
+Troubleshooting
+----------------
+
+Agent cannot connect to verifier
+"""""""""""""""""""""""""""""""""
+
+* Verify the ``verifier_url`` is correct and uses HTTPS
+* Check that the verifier is running and listening on the configured port
+* Verify network connectivity from the agent to the verifier
+* Check that the CA certificate (``verifier_tls_ca_cert``) matches the verifier's
+ server certificate
+
+Agent shows timeout failures
+"""""""""""""""""""""""""""""
+
+The verifier marks an agent as failed if it does not receive an attestation within
+``quote_interval * 5`` seconds.
+
+* Verify the ``attestation_interval_seconds`` on the agent is less than the
+ verifier's timeout threshold
+* Check for network instability between agent and verifier
+* Review agent logs for errors during attestation cycles
+
+PoP authentication errors
+""""""""""""""""""""""""""
+
+* Ensure the agent is properly registered in the registrar (the AK must be known)
+* Check that the TPM is accessible and functioning
+* Verify the agent UUID matches between agent configuration and verifier enrollment
+
+Agent state stuck in Negotiating
+"""""""""""""""""""""""""""""""""
+
+* The verifier may be rejecting capabilities. Check verifier logs for error details
+* Ensure the TPM algorithms configured on the agent are accepted by the verifier
+* Check that the ``api_versions`` setting includes ``3.0``
+
+Service fails to start
+""""""""""""""""""""""
+
+* Check that the pull-model agent service is not running
+ (``systemctl status keylime_agent``)
+* Verify the configuration file syntax (TOML format)
+* Check file permissions on TLS certificates and TPM device