Skip to content

Commit f89751d

Browse files
authored
Merge pull request #305 from trail-of-forks/jl/cosign
cosign/tuf: use trustroot
2 parents 7b61297 + 2f8150d commit f89751d

18 files changed

+1106
-756
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ rsa = "0.9.2"
110110
scrypt = "0.11.0"
111111
serde = { version = "1.0.136", features = ["derive"] }
112112
serde_json = "1.0.79"
113+
serde_with = { version = "3.4.0", features = ["base64"] }
113114
sha2 = { version = "0.10.6", features = ["oid"] }
114115
signature = { version = "2.0" }
115116
thiserror = "1.0.30"
@@ -120,6 +121,9 @@ url = "2.2.2"
120121
x509-cert = { version = "0.2.2", features = ["pem", "std"] }
121122
crypto_secretbox = "0.1.1"
122123
zeroize = "1.5.7"
124+
rustls-webpki = { version = "0.102.0-alpha.4", features = ["alloc"] }
125+
rustls-pki-types = { version = "0.2.1", features = ["std"] }
126+
serde_repr = "0.1.16"
123127

124128
[dev-dependencies]
125129
anyhow = { version = "1.0", features = ["backtrace"] }

examples/cosign/verify/main.rs

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct Cli {
110110

111111
async fn run_app(
112112
cli: &Cli,
113-
frd: &FulcioAndRekorData,
113+
frd: &dyn sigstore::tuf::Repository,
114114
) -> anyhow::Result<(Vec<SignatureLayer>, VerificationConstraintVec)> {
115115
// Note well: this a limitation deliberately introduced by this example.
116116
if cli.cert_email.is_some() && cli.cert_url.is_some() {
@@ -133,20 +133,13 @@ async fn run_app(
133133

134134
let mut client_builder =
135135
sigstore::cosign::ClientBuilder::default().with_oci_client_config(oci_client_config);
136-
137-
if let Some(key) = frd.rekor_pub_key.as_ref() {
138-
client_builder = client_builder.with_rekor_pub_key(key);
139-
}
136+
client_builder = client_builder.with_trust_repository(frd)?;
140137

141138
let cert_chain: Option<Vec<sigstore::registry::Certificate>> = match cli.cert_chain.as_ref() {
142139
None => None,
143140
Some(cert_chain_path) => Some(parse_cert_bundle(cert_chain_path)?),
144141
};
145142

146-
if !frd.fulcio_certs.is_empty() {
147-
client_builder = client_builder.with_fulcio_certs(&frd.fulcio_certs);
148-
}
149-
150143
if cli.enable_registry_caching {
151144
client_builder = client_builder.enable_registry_caching();
152145
}
@@ -194,7 +187,7 @@ async fn run_app(
194187
}
195188
if let Some(path_to_cert) = cli.cert.as_ref() {
196189
let cert = fs::read(path_to_cert).map_err(|e| anyhow!("Cannot read cert: {:?}", e))?;
197-
let require_rekor_bundle = if frd.rekor_pub_key.is_some() {
190+
let require_rekor_bundle = if !frd.rekor_keys()?.is_empty() {
198191
true
199192
} else {
200193
warn!("certificate based verification is weaker when Rekor integration is disabled");
@@ -235,31 +228,22 @@ async fn run_app(
235228
Ok((trusted_layers, verification_constraints))
236229
}
237230

238-
#[derive(Default)]
239-
struct FulcioAndRekorData {
240-
pub rekor_pub_key: Option<String>,
241-
pub fulcio_certs: Vec<sigstore::registry::Certificate>,
242-
}
243-
244-
async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<FulcioAndRekorData> {
245-
let mut data = FulcioAndRekorData::default();
246-
231+
async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<Box<dyn sigstore::tuf::Repository>> {
247232
if cli.use_sigstore_tuf_data {
248233
let repo: sigstore::errors::Result<SigstoreRepository> = spawn_blocking(|| {
249234
info!("Downloading data from Sigstore TUF repository");
250-
sigstore::tuf::SigstoreRepository::fetch(None)
235+
SigstoreRepository::new(None)?.prefetch()
251236
})
252237
.await
253238
.map_err(|e| anyhow!("Error spawning blocking task inside of tokio: {}", e))?;
254239

255-
let repo: SigstoreRepository = repo?;
256-
data.fulcio_certs = repo.fulcio_certs().into();
257-
data.rekor_pub_key = Some(repo.rekor_pub_key().to_string());
240+
return Ok(Box::new(repo?));
258241
};
259242

243+
let mut data = sigstore::tuf::ManualRepository::default();
260244
if let Some(path) = cli.rekor_pub_key.as_ref() {
261-
data.rekor_pub_key = Some(
262-
fs::read_to_string(path)
245+
data.rekor_key = Some(
246+
fs::read(path)
263247
.map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e))?,
264248
);
265249
}
@@ -272,10 +256,12 @@ async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<FulcioAndRekorData>
272256
encoding: sigstore::registry::CertificateEncoding::Pem,
273257
data: cert_data,
274258
};
275-
data.fulcio_certs.push(certificate);
259+
data.fulcio_certs
260+
.get_or_insert(Vec::new())
261+
.push(certificate.try_into()?);
276262
}
277263

278-
Ok(data)
264+
Ok(Box::new(data))
279265
}
280266

281267
#[tokio::main]
@@ -304,7 +290,7 @@ pub async fn main() {
304290
println!("Loop {}/{}", n + 1, cli.loops);
305291
}
306292

307-
match run_app(&cli, &frd).await {
293+
match run_app(&cli, frd.as_ref()).await {
308294
Ok((trusted_layers, verification_constraints)) => {
309295
let filter_result = sigstore::cosign::verify_constraints(
310296
&trusted_layers,

src/cosign/client.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ pub const CONFIG_DATA: &str = "{}";
3737
/// Cosign Client
3838
///
3939
/// Instances of `Client` can be built via [`sigstore::cosign::ClientBuilder`](crate::cosign::ClientBuilder).
40-
pub struct Client {
40+
pub struct Client<'a> {
4141
pub(crate) registry_client: Box<dyn crate::registry::ClientCapabilities>,
4242
pub(crate) rekor_pub_key: Option<CosignVerificationKey>,
43-
pub(crate) fulcio_cert_pool: Option<CertificatePool>,
43+
pub(crate) fulcio_cert_pool: Option<CertificatePool<'a>>,
4444
}
4545

4646
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
4747
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
48-
impl CosignCapabilities for Client {
48+
impl CosignCapabilities for Client<'_> {
4949
async fn triangulate(
5050
&mut self,
5151
image: &OciReference,
@@ -140,7 +140,7 @@ impl CosignCapabilities for Client {
140140
}
141141
}
142142

143-
impl Client {
143+
impl Client<'_> {
144144
/// Internal helper method used to fetch data from an OCI registry
145145
async fn fetch_manifest_and_layers(
146146
&mut self,
@@ -177,7 +177,7 @@ mod tests {
177177
use crate::crypto::SigningScheme;
178178
use crate::mock_client::test::MockOciClient;
179179

180-
fn build_test_client(mock_client: MockOciClient) -> Client {
180+
fn build_test_client(mock_client: MockOciClient) -> Client<'static> {
181181
let rekor_pub_key =
182182
CosignVerificationKey::from_pem(REKOR_PUB_KEY.as_bytes(), &SigningScheme::default())
183183
.expect("Cannot create CosignVerificationKey");

src/cosign/client_builder.rs

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,30 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16+
use rustls_pki_types::CertificateDer;
1617
use tracing::info;
1718

1819
use super::client::Client;
1920
use crate::crypto::SigningScheme;
2021
use crate::crypto::{certificate_pool::CertificatePool, CosignVerificationKey};
2122
use crate::errors::Result;
22-
use crate::registry::{Certificate, ClientConfig};
23+
use crate::registry::ClientConfig;
24+
use crate::tuf::Repository;
2325

2426
/// A builder that generates Client objects.
2527
///
2628
/// ## Rekor integration
2729
///
2830
/// Rekor integration can be enabled by specifying Rekor's public key.
29-
/// This can be provided via the [`ClientBuilder::with_rekor_pub_key`] method.
31+
/// This can be provided via a [`crate::tuf::ManualRepository`].
3032
///
3133
/// > Note well: the [`tuf`](crate::tuf) module provides helper structs and methods
3234
/// > to obtain this data from the official TUF repository of the Sigstore project.
3335
///
3436
/// ## Fulcio integration
3537
///
3638
/// Fulcio integration can be enabled by specifying Fulcio's certificate.
37-
/// This can be provided via the [`ClientBuilder::with_fulcio_cert`] method.
39+
/// This can be provided via a [`crate::tuf::ManualRepository`].
3840
///
3941
/// > Note well: the [`tuf`](crate::tuf) module provides helper structs and methods
4042
/// > to obtain this data from the official TUF repository of the Sigstore project.
@@ -50,63 +52,34 @@ use crate::registry::{Certificate, ClientConfig};
5052
///
5153
/// Each cached entry will automatically expire after 60 seconds.
5254
#[derive(Default)]
53-
pub struct ClientBuilder {
55+
pub struct ClientBuilder<'a> {
5456
oci_client_config: ClientConfig,
55-
rekor_pub_key: Option<String>,
56-
fulcio_certs: Vec<Certificate>,
57+
rekor_pub_key: Option<&'a [u8]>,
58+
fulcio_certs: Vec<CertificateDer<'a>>,
5759
#[cfg(feature = "cached-client")]
5860
enable_registry_caching: bool,
5961
}
6062

61-
impl ClientBuilder {
63+
impl<'a> ClientBuilder<'a> {
6264
/// Enable caching of data returned from remote OCI registries
6365
#[cfg(feature = "cached-client")]
6466
pub fn enable_registry_caching(mut self) -> Self {
6567
self.enable_registry_caching = true;
6668
self
6769
}
6870

69-
/// Specify the public key used by Rekor.
71+
/// Optional - Configures the roots of trust.
7072
///
71-
/// The public key can be obtained by using the helper methods under the
72-
/// [`tuf`](crate::tuf) module.
73-
///
74-
/// `key` is a PEM encoded public key
75-
///
76-
/// When provided, this enables Rekor's integration.
77-
pub fn with_rekor_pub_key(mut self, key: &str) -> Self {
78-
self.rekor_pub_key = Some(key.to_string());
79-
self
80-
}
81-
82-
/// Specify the certificate used by Fulcio. This method can be invoked
83-
/// multiple times to add all the certificates that Fulcio used over the
84-
/// time.
85-
///
86-
/// `cert` is a PEM encoded certificate
87-
///
88-
/// The certificates can be obtained by using the helper methods under the
89-
/// [`tuf`](crate::tuf) module.
90-
///
91-
/// When provided, this enables Fulcio's integration.
92-
pub fn with_fulcio_cert(mut self, cert: &[u8]) -> Self {
93-
let certificate = Certificate {
94-
encoding: crate::registry::CertificateEncoding::Pem,
95-
data: cert.to_owned(),
96-
};
97-
self.fulcio_certs.push(certificate);
98-
self
99-
}
73+
/// Enables Fulcio and Rekor integration with the given trust repository.
74+
/// See [crate::tuf::Repository] for more details on trust repositories.
75+
pub fn with_trust_repository<R: Repository + ?Sized>(mut self, repo: &'a R) -> Result<Self> {
76+
let rekor_keys = repo.rekor_keys()?;
77+
if !rekor_keys.is_empty() {
78+
self.rekor_pub_key = Some(rekor_keys[0]);
79+
}
80+
self.fulcio_certs = repo.fulcio_certs()?;
10081

101-
/// Specify the certificates used by Fulcio.
102-
///
103-
/// The certificates can be obtained by using the helper methods under the
104-
/// [`tuf`](crate::tuf) module.
105-
///
106-
/// When provided, this enables Fulcio's integration.
107-
pub fn with_fulcio_certs(mut self, certs: &[crate::registry::Certificate]) -> Self {
108-
self.fulcio_certs = certs.to_vec();
109-
self
82+
Ok(self)
11083
}
11184

11285
/// Optional - the configuration to be used by the OCI client.
@@ -118,14 +91,14 @@ impl ClientBuilder {
11891
self
11992
}
12093

121-
pub fn build(self) -> Result<Client> {
94+
pub fn build(self) -> Result<Client<'a>> {
12295
let rekor_pub_key = match self.rekor_pub_key {
12396
None => {
12497
info!("Rekor public key not provided. Rekor integration disabled");
12598
None
12699
}
127-
Some(data) => Some(CosignVerificationKey::from_pem(
128-
data.as_bytes(),
100+
Some(data) => Some(CosignVerificationKey::from_der(
101+
data,
129102
&SigningScheme::default(),
130103
)?),
131104
};
@@ -134,7 +107,7 @@ impl ClientBuilder {
134107
info!("No Fulcio cert has been provided. Fulcio integration disabled");
135108
None
136109
} else {
137-
let cert_pool = CertificatePool::from_certificates(&self.fulcio_certs)?;
110+
let cert_pool = CertificatePool::from_certificates(self.fulcio_certs, [])?;
138111
Some(cert_pool)
139112
};
140113

src/cosign/mod.rs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,14 @@ pub trait CosignCapabilities {
102102
/// must be satisfied:
103103
///
104104
/// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must
105-
/// have been created with Rekor integration enabled (see
106-
/// [`sigstore::cosign::ClientBuilder::with_rekor_pub_key`](crate::cosign::ClientBuilder::with_rekor_pub_key))
105+
/// have been created with Rekor integration enabled (see [`crate::tuf::ManualRepository`])
107106
/// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must
108-
/// have been created with Fulcio integration enabled (see
109-
/// [`sigstore::cosign::ClientBuilder::with_fulcio_certs`](crate::cosign::ClientBuilder::with_fulcio_certs))
107+
/// have been created with Fulcio integration enabled (see [`crate::tuf::ManualRepository])
110108
/// * The layer must include a bundle produced by Rekor
111109
///
110+
/// > Note well: the [`tuf`](crate::tuf) module provides helper structs and methods
111+
/// > to obtain this data from the official TUF repository of the Sigstore project.
112+
///
112113
/// When the embedded certificate cannot be verified, [`SignatureLayer::certificate_signature`]
113114
/// is going to be `None`.
114115
///
@@ -199,7 +200,7 @@ pub trait CosignCapabilities {
199200
/// verification.
200201
///
201202
/// Returns a `Result` with either `Ok()` for passed verification or
202-
/// [`SigstoreVerifyConstraintsError`](crate::errors::SigstoreVerifyConstraintsError),
203+
/// [`SigstoreVerifyConstraintsError`]
203204
/// which contains a vector of references to unsatisfied constraints.
204205
///
205206
/// See the documentation of the [`cosign::verification_constraint`](crate::cosign::verification_constraint) module for more
@@ -249,7 +250,7 @@ where
249250
/// passes applying constraints process.
250251
///
251252
/// Returns a `Result` with either `Ok()` for success or
252-
/// [`SigstoreApplicationConstraintsError`](crate::errors::SigstoreApplicationConstraintsError),
253+
/// [`SigstoreApplicationConstraintsError`]
253254
/// which contains a vector of references to unapplied constraints.
254255
///
255256
/// See the documentation of the [`cosign::sign_constraint`](crate::cosign::sign_constraint) module for more
@@ -282,6 +283,7 @@ where
282283

283284
#[cfg(test)]
284285
mod tests {
286+
use rustls_pki_types::CertificateDer;
285287
use serde_json::json;
286288
use std::collections::HashMap;
287289

@@ -335,18 +337,15 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
335337
#[cfg(feature = "test-registry")]
336338
const SIGNED_IMAGE: &str = "busybox:1.34";
337339

338-
pub(crate) fn get_fulcio_cert_pool() -> CertificatePool {
339-
let certificates = vec![
340-
crate::registry::Certificate {
341-
encoding: crate::registry::CertificateEncoding::Pem,
342-
data: FULCIO_CRT_1_PEM.as_bytes().to_vec(),
343-
},
344-
crate::registry::Certificate {
345-
encoding: crate::registry::CertificateEncoding::Pem,
346-
data: FULCIO_CRT_2_PEM.as_bytes().to_vec(),
347-
},
348-
];
349-
CertificatePool::from_certificates(&certificates).unwrap()
340+
pub(crate) fn get_fulcio_cert_pool() -> CertificatePool<'static> {
341+
fn pem_to_der<'a>(input: &'a str) -> CertificateDer<'a> {
342+
let pem_cert = pem::parse(input).unwrap();
343+
assert_eq!(pem_cert.tag(), "CERTIFICATE");
344+
CertificateDer::from(pem_cert.into_contents())
345+
}
346+
let certificates = vec![pem_to_der(FULCIO_CRT_1_PEM), pem_to_der(FULCIO_CRT_2_PEM)];
347+
348+
CertificatePool::from_certificates(certificates, []).unwrap()
350349
}
351350

352351
pub(crate) fn get_rekor_public_key() -> CosignVerificationKey {
@@ -645,7 +644,7 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
645644
}
646645

647646
#[cfg(feature = "test-registry")]
648-
async fn prepare_image_to_be_signed(client: &mut Client, image_ref: &OciReference) {
647+
async fn prepare_image_to_be_signed(client: &mut Client<'_>, image_ref: &OciReference) {
649648
let data = client
650649
.registry_client
651650
.pull(

0 commit comments

Comments
 (0)