Skip to content

Commit 8a269a3

Browse files
jleightcaptnytown
andcommitted
cosign/tuf: init
Signed-off-by: Jack Leightcap <[email protected]> Co-authored-by: Andrew Pan <[email protected]>
1 parent 7b61297 commit 8a269a3

File tree

17 files changed

+1048
-747
lines changed

17 files changed

+1048
-747
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,20 @@ 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"
116117
tokio = { version = "1.17.0", features = ["rt"] }
117118
tough = { version = "0.14", features = ["http"], optional = true }
118119
tracing = "0.1.31"
119120
url = "2.2.2"
120-
x509-cert = { version = "0.2.2", features = ["pem", "std"] }
121+
x509-cert = { version = "0.2.2", features = ["builder", "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::FakeRepository::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 & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
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
///
@@ -34,7 +36,7 @@ use crate::registry::{Certificate, ClientConfig};
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 the [`ClientBuilder::with_fulcio_certs`] method.
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,35 @@ 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>>,
59+
// repo: Repository
5760
#[cfg(feature = "cached-client")]
5861
enable_registry_caching: bool,
5962
}
6063

61-
impl ClientBuilder {
64+
impl<'a> ClientBuilder<'a> {
6265
/// Enable caching of data returned from remote OCI registries
6366
#[cfg(feature = "cached-client")]
6467
pub fn enable_registry_caching(mut self) -> Self {
6568
self.enable_registry_caching = true;
6669
self
6770
}
6871

69-
/// Specify the public key used by Rekor.
72+
/// Optional - Configures the roots of trust.
7073
///
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-
}
74+
/// Enables Fulcio and Rekor integration with the given trust repository.
75+
/// See [crate::tuf::Repository] for more details on trust repositories.
76+
pub fn with_trust_repository<R: Repository + ?Sized>(mut self, repo: &'a R) -> Result<Self> {
77+
let rekor_keys = repo.rekor_keys()?;
78+
if !rekor_keys.is_empty() {
79+
self.rekor_pub_key = Some(rekor_keys[0]);
80+
}
81+
self.fulcio_certs = repo.fulcio_certs()?;
10082

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
83+
Ok(self)
11084
}
11185

11286
/// Optional - the configuration to be used by the OCI client.
@@ -118,14 +92,14 @@ impl ClientBuilder {
11892
self
11993
}
12094

121-
pub fn build(self) -> Result<Client> {
95+
pub fn build(self) -> Result<Client<'a>> {
12296
let rekor_pub_key = match self.rekor_pub_key {
12397
None => {
12498
info!("Rekor public key not provided. Rekor integration disabled");
12599
None
126100
}
127-
Some(data) => Some(CosignVerificationKey::from_pem(
128-
data.as_bytes(),
101+
Some(data) => Some(CosignVerificationKey::from_der(
102+
data,
129103
&SigningScheme::default(),
130104
)?),
131105
};
@@ -134,7 +108,7 @@ impl ClientBuilder {
134108
info!("No Fulcio cert has been provided. Fulcio integration disabled");
135109
None
136110
} else {
137-
let cert_pool = CertificatePool::from_certificates(&self.fulcio_certs)?;
111+
let cert_pool = CertificatePool::from_certificates(self.fulcio_certs, [])?;
138112
Some(cert_pool)
139113
};
140114

src/cosign/mod.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ where
282282

283283
#[cfg(test)]
284284
mod tests {
285+
use rustls_pki_types::CertificateDer;
285286
use serde_json::json;
286287
use std::collections::HashMap;
287288

@@ -335,18 +336,15 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
335336
#[cfg(feature = "test-registry")]
336337
const SIGNED_IMAGE: &str = "busybox:1.34";
337338

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()
339+
pub(crate) fn get_fulcio_cert_pool() -> CertificatePool<'static> {
340+
fn pem_to_der<'a>(input: &'a str) -> CertificateDer<'a> {
341+
let pem_cert = pem::parse(input).unwrap();
342+
assert_eq!(pem_cert.tag(), "CERTIFICATE");
343+
CertificateDer::from(pem_cert.into_contents())
344+
}
345+
let certificates = vec![pem_to_der(FULCIO_CRT_1_PEM), pem_to_der(FULCIO_CRT_2_PEM)];
346+
347+
CertificatePool::from_certificates(certificates, []).unwrap()
350348
}
351349

352350
pub(crate) fn get_rekor_public_key() -> CosignVerificationKey {
@@ -645,7 +643,7 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
645643
}
646644

647645
#[cfg(feature = "test-registry")]
648-
async fn prepare_image_to_be_signed(client: &mut Client, image_ref: &OciReference) {
646+
async fn prepare_image_to_be_signed(client: &mut Client<'_>, image_ref: &OciReference) {
649647
let data = client
650648
.registry_client
651649
.pull(

src/cosign/signature_layers.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,12 @@ impl CertificateSignature {
438438
let integrated_time = trusted_bundle.payload.integrated_time;
439439

440440
// ensure the certificate has been issued by Fulcio
441-
fulcio_cert_pool.verify_pem_cert(cert_pem)?;
441+
fulcio_cert_pool.verify_pem_cert(
442+
cert_pem,
443+
Some(rustls_pki_types::UnixTime::since_unix_epoch(
444+
cert.tbs_certificate.validity.not_before.to_unix_duration(),
445+
)),
446+
)?;
442447

443448
crypto::certificate::is_trusted(&cert, integrated_time)?;
444449

@@ -899,8 +904,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==
899904

900905
let issued_cert_pem = issued_cert.cert.to_pem()?;
901906

902-
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
903-
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
907+
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
908+
.unwrap()
909+
.try_into()?];
910+
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();
904911

905912
let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
906913
let bundle = Bundle {
@@ -946,8 +953,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==
946953

947954
let issued_cert_pem = issued_cert.cert.to_pem()?;
948955

949-
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
950-
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
956+
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
957+
.unwrap()
958+
.try_into()?];
959+
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();
951960

952961
let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
953962
let bundle = Bundle {
@@ -992,8 +1001,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==
9921001

9931002
let issued_cert_pem = issued_cert.cert.to_pem()?;
9941003

995-
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
996-
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
1004+
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
1005+
.unwrap()
1006+
.try_into()?];
1007+
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();
9971008

9981009
let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
9991010
let bundle = Bundle {

0 commit comments

Comments
 (0)