Skip to main content

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}