pic_continuity/trust.rs
1/*
2 * Copyright Nitro Agility S.r.l.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Host-supplied trust boundaries.
18//!
19//! The Prover and Verifier are pure: key material, trusted checkpoint state,
20//! revocation, and policy all enter through these traits. Defaults are
21//! provided where the specification makes a check optional or
22//! deployment-defined.
23
24use crate::artifacts::{PicPcaPayload, PicTransitionPayload};
25use crate::authority::indexed::IndexedAuthorityMap;
26use crate::cose::{CoseError, SigningAlgorithm};
27
28// ---------------------------------------------------------------------------
29// Key material
30// ---------------------------------------------------------------------------
31
32/// Signs PIC artifacts (COSE payloads and the JWS envelope) with one key.
33///
34/// Implemented by workloads (candidate artifacts) and by the settlement
35/// authority (checkpoints and settled artifacts).
36pub trait ArtifactSigner {
37 /// Key identifier (SPIFFE ID, DID, URL, …) placed in the COSE protected
38 /// header.
39 fn kid(&self) -> &str;
40 /// COSE algorithm this signer produces.
41 fn cose_algorithm(&self) -> SigningAlgorithm;
42 /// JOSE `alg` value for JWS signatures (for example `"EdDSA"`).
43 fn jws_algorithm(&self) -> &str;
44 /// Signs raw bytes.
45 fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoseError>;
46}
47
48/// Verifies a raw signature over raw bytes with one key.
49pub trait ArtifactVerifier {
50 /// `true` when `signature` is a valid signature over `data`.
51 fn verify(&self, data: &[u8], signature: &[u8]) -> bool;
52}
53
54#[cfg(feature = "ed25519")]
55mod ed25519_impl {
56 use super::*;
57 use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
58
59 /// Ed25519 [`ArtifactSigner`].
60 #[derive(Debug, Clone)]
61 pub struct Ed25519Signer {
62 key: SigningKey,
63 kid: String,
64 }
65
66 impl Ed25519Signer {
67 /// Wraps a signing key with its key identifier.
68 pub fn new(key: SigningKey, kid: impl Into<String>) -> Self {
69 Self {
70 key,
71 kid: kid.into(),
72 }
73 }
74
75 /// The public key matching this signer.
76 pub fn verifying_key(&self) -> VerifyingKey {
77 self.key.verifying_key()
78 }
79 }
80
81 impl ArtifactSigner for Ed25519Signer {
82 fn kid(&self) -> &str {
83 &self.kid
84 }
85 fn cose_algorithm(&self) -> SigningAlgorithm {
86 SigningAlgorithm::EdDSA
87 }
88 fn jws_algorithm(&self) -> &str {
89 "EdDSA"
90 }
91 fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CoseError> {
92 Ok(self.key.sign(data).to_bytes().to_vec())
93 }
94 }
95
96 /// Ed25519 [`ArtifactVerifier`].
97 #[derive(Debug, Clone)]
98 pub struct Ed25519Verifier {
99 key: VerifyingKey,
100 }
101
102 impl Ed25519Verifier {
103 /// Wraps a public key.
104 pub fn new(key: VerifyingKey) -> Self {
105 Self { key }
106 }
107 }
108
109 impl ArtifactVerifier for Ed25519Verifier {
110 fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
111 let Ok(sig) = Signature::from_slice(signature) else {
112 return false;
113 };
114 self.key.verify(data, &sig).is_ok()
115 }
116 }
117}
118
119#[cfg(feature = "ed25519")]
120pub use ed25519_impl::{Ed25519Signer, Ed25519Verifier};
121
122// ---------------------------------------------------------------------------
123// Settlement-side trust state
124// ---------------------------------------------------------------------------
125
126/// The settlement authority's knowledge of currently trusted checkpoints.
127///
128/// `root.pca` of a candidate must be the **exact signed bytes** of a
129/// currently trusted PIC PCA COSE checkpoint; the store answers that
130/// question. Terminating a checkpoint here is how revocation of a lineage
131/// branch is realized operationally.
132pub trait TrustedCheckpoint {
133 /// `true` when `exact_pca_bytes` are byte-for-byte a currently trusted
134 /// checkpoint.
135 fn is_current_checkpoint(&self, exact_pca_bytes: &[u8]) -> bool;
136}
137
138/// Revocation state lookup. The default accepts everything; deployments
139/// enable it per the PIC Revocation Specification.
140pub trait RevocationCheck {
141 /// `true` when the continuity state rooted at this checkpoint is revoked.
142 fn is_revoked(&self, checkpoint: &PicPcaPayload, exact_pca_bytes: &[u8]) -> bool;
143}
144
145/// No revocation configured.
146#[derive(Debug, Clone, Copy, Default)]
147pub struct NoRevocation;
148
149impl RevocationCheck for NoRevocation {
150 fn is_revoked(&self, _checkpoint: &PicPcaPayload, _exact_pca_bytes: &[u8]) -> bool {
151 false
152 }
153}
154
155/// Deployment policy hooks evaluated during settlement. Every method
156/// defaults to accepting, matching the checks the specification marks as
157/// profile- or deployment-required.
158pub trait SettlementPolicy {
159 /// Request/execution binding validation, when the deployment requires
160 /// it. Receives the whole transition (including `request_digest`).
161 fn request_binding(&self, _transition: &PicTransitionPayload) -> bool {
162 true
163 }
164
165 /// Executor evidence / execution-contract conformance validation, when
166 /// required.
167 fn conformance(&self, _checkpoint: &PicPcaPayload, _transition: &PicTransitionPayload) -> bool {
168 true
169 }
170
171 /// Local policy over the materialized successor authority.
172 fn policy(&self, _checkpoint: &PicPcaPayload, _next_authority: &IndexedAuthorityMap) -> bool {
173 true
174 }
175}
176
177/// The permissive default policy.
178#[derive(Debug, Clone, Copy, Default)]
179pub struct DefaultPolicy;
180
181impl SettlementPolicy for DefaultPolicy {}
182
183/// An in-memory [`TrustedCheckpoint`] store, useful for tests and simple
184/// single-process deployments.
185#[derive(Debug, Clone, Default)]
186pub struct InMemoryCheckpoints {
187 current: Vec<Vec<u8>>,
188}
189
190impl InMemoryCheckpoints {
191 /// An empty store: nothing is trusted yet.
192 pub fn new() -> Self {
193 Self::default()
194 }
195
196 /// Marks exact checkpoint bytes as currently trusted.
197 pub fn insert(&mut self, exact_pca_bytes: Vec<u8>) {
198 self.current.push(exact_pca_bytes);
199 }
200
201 /// Replaces a superseded checkpoint with its successor.
202 pub fn replace(&mut self, old_exact_pca_bytes: &[u8], new_exact_pca_bytes: Vec<u8>) {
203 self.current.retain(|b| b != old_exact_pca_bytes);
204 self.current.push(new_exact_pca_bytes);
205 }
206}
207
208impl TrustedCheckpoint for InMemoryCheckpoints {
209 fn is_current_checkpoint(&self, exact_pca_bytes: &[u8]) -> bool {
210 self.current.iter().any(|b| b == exact_pca_bytes)
211 }
212}