From b8acd00bf8a7a38288f2c579b67336b5441e5524 Mon Sep 17 00:00:00 2001 From: Jake <702703+BranchyMcBranchface@users.noreply.github.com> Date: Wed, 31 Dec 2025 13:50:13 -0600 Subject: [PATCH 1/2] feat: Add GitHub Actions OIDC token fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fetch fresh GA token with audience=sigstore when available - Fall back to provided token if GA environment unavailable - Minimal change: only reqwest dep + sign.rs GA token logic - No vendoring, no bloat—just 40 lines of code --- Cargo.lock | 1 + Cargo.toml | 1 + crates/sigstore/Cargo.toml | 1 + crates/sigstore/src/sign.rs | 75 ++++++++++++++++++++++++++++++++++--- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff43724..c6b2f4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,6 +2675,7 @@ dependencies = [ "openidconnect", "pdf-sign-core", "regex", + "reqwest", "serde", "serde_json", "sigstore", diff --git a/Cargo.toml b/Cargo.toml index 2a43b1d..7a1f2e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ sigstore_protobuf_specs = "0.5" x509-cert = "0.2" const-oid = "0.9" openidconnect = "4.0" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } webbrowser = "1.0" regex = "1.11" diff --git a/crates/sigstore/Cargo.toml b/crates/sigstore/Cargo.toml index dd5f9fc..1197bb6 100644 --- a/crates/sigstore/Cargo.toml +++ b/crates/sigstore/Cargo.toml @@ -29,4 +29,5 @@ regex.workspace = true sigstore = { workspace = true, features = ["bundle", "sign", "verify", "fulcio", "rekor", "oauth", "sigstore-trust-root", "rustls-tls", "rustls-tls-native-roots"] } tokio = { workspace = true, features = ["full"] } webbrowser.workspace = true +reqwest.workspace = true diff --git a/crates/sigstore/src/sign.rs b/crates/sigstore/src/sign.rs index 7384346..f396ca7 100644 --- a/crates/sigstore/src/sign.rs +++ b/crates/sigstore/src/sign.rs @@ -5,6 +5,10 @@ use pdf_sign_core::{DigestAlgorithm, compute_digest, suffix::SigstoreBundleBlock use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::time::Duration; +#[cfg(not(target_arch = "wasm32"))] +use std::env; +#[cfg(not(target_arch = "wasm32"))] +use reqwest; /// Sigstore service endpoints configuration. #[derive(Debug, Clone)] @@ -56,6 +60,12 @@ pub struct SignResult { pub bundle_block: SigstoreBundleBlock, } +#[cfg(not(target_arch = "wasm32"))] +#[derive(Deserialize)] +struct GitHubIdTokenResponse { + value: String, +} + /// Sign a blob using Sigstore keyless (OIDC) signing. /// /// This performs the following steps: @@ -94,11 +104,24 @@ pub async fn sign_blob(data: &[u8], options: &SignOptions) -> Result // Obtain identity token let identity_token = if let Some(token) = &options.identity_token { - tracing::debug!("Using provided identity token"); - // Parse the raw JWT token - sigstore::oauth::IdentityToken::from( - openidconnect::IdToken::from_str(token).context("Failed to parse identity token")?, - ) + // Try to fetch a fresh GitHub Actions token with audience=sigstore if available + #[cfg(not(target_arch = "wasm32"))] + { + if let Ok(fresh_token) = maybe_fetch_github_actions_token().await { + fresh_token + } else { + // Fall back to provided token + sigstore::oauth::IdentityToken::from( + openidconnect::IdToken::from_str(token).context("Failed to parse identity token")?, + ) + } + } + #[cfg(target_arch = "wasm32")] + { + sigstore::oauth::IdentityToken::from( + openidconnect::IdToken::from_str(token).context("Failed to parse identity token")?, + ) + } } else { tracing::debug!("Starting interactive OIDC flow"); obtain_identity_token(&options.endpoints).await? @@ -278,3 +301,45 @@ async fn obtain_identity_token( tracing::debug!("Identity token obtained"); Ok(sigstore::oauth::IdentityToken::from(token)) } + +/// Attempt to fetch a fresh GitHub Actions OIDC token with audience=sigstore. +/// Returns Ok(token) on success, or Err if not in GitHub Actions or fetch fails. +#[cfg(not(target_arch = "wasm32"))] +async fn maybe_fetch_github_actions_token() -> Result { + let request_url = env::var("ACTIONS_ID_TOKEN_REQUEST_URL") + .ok() + .ok_or_else(|| anyhow::anyhow!("Not in GitHub Actions"))?; + let request_token = env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN") + .ok() + .ok_or_else(|| anyhow::anyhow!("No token request credentials"))?; + + // Append audience=sigstore if not already present + let url = if request_url.contains("audience=") { + request_url + } else if request_url.contains('?') { + format!("{}&audience=sigstore", request_url) + } else { + format!("{}?audience=sigstore", request_url) + }; + + let client = reqwest::Client::new(); + let resp = client + .get(&url) + .bearer_auth(request_token) + .send() + .await + .context("Failed to request GitHub Actions ID token")?; + + if !resp.status().is_success() { + return Err(anyhow::anyhow!("GitHub Actions token request failed: {}", resp.status())); + } + + let body: GitHubIdTokenResponse = resp + .json() + .await + .context("Failed to decode GitHub Actions token response")?; + + Ok(sigstore::oauth::IdentityToken::from( + openidconnect::IdToken::from_str(&body.value).context("Failed to parse GitHub Actions token")?, + )) +} From f407d0340fecb58f131776c118553a7b49468151 Mon Sep 17 00:00:00 2001 From: Jake <702703+BranchyMcBranchface@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:31:10 -0600 Subject: [PATCH 2/2] feat: Add GitHub Actions OIDC token fallback with JWT parsing fixes - Fetch fresh GA token with audience=sigstore when available - Vendor sigstore 0.13.0 with relaxed JWT parsing (flexible audience/exp/nbf) - Fall back to provided token if GA environment unavailable - Prefer fresh GA token over supplied token in CI environments --- Cargo.lock | 2 - Cargo.toml | 3 + crates/sigstore/src/sign.rs | 127 +- vendor/CHANGELOG.md | 321 + vendor/CODEOWNERS | 9 + vendor/CODE_OF_CONDUCT.md | 74 + vendor/CONTRIBUTORS.md | 122 + vendor/COPYRIGHT.txt | 14 + vendor/Cargo.lock | 5222 +++++++++++++++++ vendor/Cargo.toml | 584 ++ vendor/Cargo.toml.orig | 320 + vendor/LICENSE | 201 + vendor/Makefile | 41 + vendor/README.md | 85 + vendor/clippy.toml | 2 + vendor/examples/README.md | 15 + vendor/examples/bundle/README.md | 29 + vendor/examples/bundle/main.rs | 163 + vendor/examples/cosign/sign/README.md | 59 + vendor/examples/cosign/sign/main.rs | 182 + vendor/examples/cosign/verify-blob/.gitignore | 3 + vendor/examples/cosign/verify-blob/README.md | 87 + vendor/examples/cosign/verify-blob/main.rs | 74 + .../examples/cosign/verify-bundle/.gitignore | 2 + .../examples/cosign/verify-bundle/README.md | 16 + vendor/examples/cosign/verify-bundle/main.rs | 85 + vendor/examples/cosign/verify-bundle/run.sh | 11 + vendor/examples/cosign/verify/README.md | 34 + vendor/examples/cosign/verify/main.rs | 338 ++ vendor/examples/fulcio/cert/main.rs | 37 + vendor/examples/key_interface/README.md | 113 + .../key_interface/key_interface.drawio.svg | 4 + .../key_pair_gen_and_export/main.rs | 49 + .../key_pair_gen_sign_verify/main.rs | 44 + .../ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key | 11 + .../ECDSA_P256_ASN1_PRIVATE_DER.key | Bin 0 -> 138 bytes .../ECDSA_P256_ASN1_PRIVATE_PEM.key | 5 + .../ECDSA_P256_ASN1_PUBLIC_DER.pub | Bin 0 -> 91 bytes .../ECDSA_P256_ASN1_PUBLIC_PEM.pub | 4 + .../key_interface/key_pair_import/main.rs | 82 + vendor/examples/openidflow/README.md | 19 + .../examples/openidflow/openidconnect/main.rs | 62 + vendor/examples/rekor/README.md | 4 + .../examples/rekor/create_log_entry/main.rs | 206 + .../rekor/get_log_entry_by_index/main.rs | 55 + .../rekor/get_log_entry_by_uuid/main.rs | 50 + vendor/examples/rekor/get_log_info/main.rs | 36 + vendor/examples/rekor/get_log_proof/main.rs | 67 + vendor/examples/rekor/get_public_key/main.rs | 64 + vendor/examples/rekor/search_index/main.rs | 117 + .../examples/rekor/search_log_query/main.rs | 140 + vendor/sigstore-0.13.0/.cargo/audit.toml | 7 + vendor/sigstore-0.13.0/.cargo_vcs_info.json | 6 + vendor/sigstore-0.13.0/.github/dependabot.yml | 27 + .../auto-publish-crates-upon-release.yml | 29 + .../.github/workflows/conformance.yml | 21 + .../.github/workflows/security-audit.yml | 27 + .../.github/workflows/tests.yml | 130 + vendor/sigstore-0.13.0/.gitignore | 16 + vendor/sigstore-0.13.0/.taplo.toml | 8 + vendor/src/bundle/mod.rs | 27 + vendor/src/bundle/models.rs | 107 + vendor/src/bundle/sign.rs | 379 ++ vendor/src/bundle/verify/mod.rs | 26 + vendor/src/bundle/verify/models.rs | 294 + vendor/src/bundle/verify/policy.rs | 295 + vendor/src/bundle/verify/verifier.rs | 304 + vendor/src/cosign/bundle.rs | 201 + vendor/src/cosign/client.rs | 213 + vendor/src/cosign/client_builder.rs | 152 + vendor/src/cosign/constants.rs | 36 + vendor/src/cosign/constraint/annotation.rs | 73 + vendor/src/cosign/constraint/mod.rs | 73 + vendor/src/cosign/constraint/signature.rs | 74 + vendor/src/cosign/mod.rs | 683 +++ vendor/src/cosign/payload/mod.rs | 21 + vendor/src/cosign/payload/simple_signing.rs | 333 ++ vendor/src/cosign/signature_layers.rs | 1035 ++++ .../annotation_verifier.rs | 29 + .../cert_subject_email_verifier.rs | 378 ++ .../cert_subject_url_verifier.rs | 131 + .../certificate_verifier.rs | 307 + .../src/cosign/verification_constraint/mod.rs | 82 + .../public_key_verifier.rs | 59 + vendor/src/crypto/certificate.rs | 516 ++ vendor/src/crypto/certificate_pool.rs | 120 + vendor/src/crypto/keyring.rs | 173 + vendor/src/crypto/mod.rs | 496 ++ vendor/src/crypto/signing_key/ecdsa/ec.rs | 563 ++ vendor/src/crypto/signing_key/ecdsa/mod.rs | 177 + vendor/src/crypto/signing_key/ed25519.rs | 458 ++ vendor/src/crypto/signing_key/kdf.rs | 295 + vendor/src/crypto/signing_key/mod.rs | 445 ++ vendor/src/crypto/signing_key/rsa/keypair.rs | 455 ++ vendor/src/crypto/signing_key/rsa/mod.rs | 264 + vendor/src/crypto/transparency.rs | 398 ++ vendor/src/crypto/verification_key.rs | 630 ++ vendor/src/errors.rs | 275 + vendor/src/fulcio/mod.rs | 290 + vendor/src/fulcio/models.rs | 127 + vendor/src/fulcio/oauth.rs | 130 + vendor/src/lib.rs | 275 + vendor/src/mock_client.rs | 135 + vendor/src/oauth/mod.rs | 19 + vendor/src/oauth/openidflow.rs | 380 ++ vendor/src/oauth/token.rs | 184 + vendor/src/registry/config.rs | 241 + vendor/src/registry/mod.rs | 91 + vendor/src/registry/oci_caching_client.rs | 322 + vendor/src/registry/oci_client.rs | 94 + vendor/src/registry/oci_reference.rs | 91 + vendor/src/rekor/apis/configuration.rs | 51 + vendor/src/rekor/apis/entries_api.rs | 220 + vendor/src/rekor/apis/index_api.rs | 62 + vendor/src/rekor/apis/mod.rs | 47 + vendor/src/rekor/apis/pubkey_api.rs | 62 + vendor/src/rekor/apis/tlog_api.rs | 116 + vendor/src/rekor/mod.rs | 91 + vendor/src/rekor/models/alpine.rs | 33 + vendor/src/rekor/models/alpine_all_of.rs | 25 + vendor/src/rekor/models/consistency_proof.rs | 26 + vendor/src/rekor/models/error.rs | 28 + vendor/src/rekor/models/hashedrekord.rs | 121 + .../src/rekor/models/hashedrekord_all_of.rs | 25 + vendor/src/rekor/models/helm.rs | 34 + vendor/src/rekor/models/helm_all_of.rs | 25 + .../rekor/models/inactive_shard_log_info.rs | 44 + vendor/src/rekor/models/inclusion_proof.rs | 44 + vendor/src/rekor/models/intoto.rs | 34 + vendor/src/rekor/models/intoto_all_of.rs | 25 + vendor/src/rekor/models/jar.rs | 33 + vendor/src/rekor/models/jar_all_of.rs | 25 + vendor/src/rekor/models/log_entry.rs | 119 + vendor/src/rekor/models/log_info.rs | 42 + vendor/src/rekor/models/mod.rs | 56 + vendor/src/rekor/models/proposed_entry.rs | 78 + vendor/src/rekor/models/rekord.rs | 33 + vendor/src/rekor/models/rekord_all_of.rs | 25 + vendor/src/rekor/models/rfc3161.rs | 33 + vendor/src/rekor/models/rfc3161_all_of.rs | 25 + vendor/src/rekor/models/rpm.rs | 34 + vendor/src/rekor/models/rpm_all_of.rs | 24 + vendor/src/rekor/models/search_index.rs | 31 + .../rekor/models/search_index_public_key.rs | 52 + vendor/src/rekor/models/search_log_query.rs | 31 + vendor/src/rekor/models/tuf.rs | 34 + vendor/src/rekor/models/tuf_all_of.rs | 25 + vendor/src/trust/mod.rs | 60 + vendor/src/trust/sigstore/constants.rs | 36 + vendor/src/trust/sigstore/mod.rs | 364 ++ ...sign_generated_encrypted_empty_private.key | 11 + .../data/keys/ecdsa_encrypted_private.key | 11 + vendor/tests/data/keys/ecdsa_private.key | 5 + .../data/keys/ed25519_encrypted_private.key | 10 + vendor/tests/data/keys/ed25519_private.key | 4 + .../tests/data/keys/rsa_encrypted_private.key | 73 + vendor/tests/data/keys/rsa_private.key | 51 + vendor/trust_root/prod/root.json | 145 + vendor/trust_root/prod/trusted_root.json | 90 + 159 files changed, 25237 insertions(+), 57 deletions(-) create mode 100644 vendor/CHANGELOG.md create mode 100644 vendor/CODEOWNERS create mode 100644 vendor/CODE_OF_CONDUCT.md create mode 100644 vendor/CONTRIBUTORS.md create mode 100644 vendor/COPYRIGHT.txt create mode 100644 vendor/Cargo.lock create mode 100644 vendor/Cargo.toml create mode 100644 vendor/Cargo.toml.orig create mode 100644 vendor/LICENSE create mode 100644 vendor/Makefile create mode 100644 vendor/README.md create mode 100644 vendor/clippy.toml create mode 100644 vendor/examples/README.md create mode 100644 vendor/examples/bundle/README.md create mode 100644 vendor/examples/bundle/main.rs create mode 100644 vendor/examples/cosign/sign/README.md create mode 100644 vendor/examples/cosign/sign/main.rs create mode 100644 vendor/examples/cosign/verify-blob/.gitignore create mode 100644 vendor/examples/cosign/verify-blob/README.md create mode 100644 vendor/examples/cosign/verify-blob/main.rs create mode 100644 vendor/examples/cosign/verify-bundle/.gitignore create mode 100644 vendor/examples/cosign/verify-bundle/README.md create mode 100644 vendor/examples/cosign/verify-bundle/main.rs create mode 100755 vendor/examples/cosign/verify-bundle/run.sh create mode 100644 vendor/examples/cosign/verify/README.md create mode 100644 vendor/examples/cosign/verify/main.rs create mode 100644 vendor/examples/fulcio/cert/main.rs create mode 100644 vendor/examples/key_interface/README.md create mode 100644 vendor/examples/key_interface/key_interface.drawio.svg create mode 100644 vendor/examples/key_interface/key_pair_gen_and_export/main.rs create mode 100644 vendor/examples/key_interface/key_pair_gen_sign_verify/main.rs create mode 100644 vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key create mode 100644 vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_DER.key create mode 100644 vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_PEM.key create mode 100644 vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_DER.pub create mode 100644 vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_PEM.pub create mode 100644 vendor/examples/key_interface/key_pair_import/main.rs create mode 100644 vendor/examples/openidflow/README.md create mode 100644 vendor/examples/openidflow/openidconnect/main.rs create mode 100644 vendor/examples/rekor/README.md create mode 100644 vendor/examples/rekor/create_log_entry/main.rs create mode 100644 vendor/examples/rekor/get_log_entry_by_index/main.rs create mode 100644 vendor/examples/rekor/get_log_entry_by_uuid/main.rs create mode 100644 vendor/examples/rekor/get_log_info/main.rs create mode 100644 vendor/examples/rekor/get_log_proof/main.rs create mode 100644 vendor/examples/rekor/get_public_key/main.rs create mode 100644 vendor/examples/rekor/search_index/main.rs create mode 100644 vendor/examples/rekor/search_log_query/main.rs create mode 100644 vendor/sigstore-0.13.0/.cargo/audit.toml create mode 100644 vendor/sigstore-0.13.0/.cargo_vcs_info.json create mode 100644 vendor/sigstore-0.13.0/.github/dependabot.yml create mode 100644 vendor/sigstore-0.13.0/.github/workflows/auto-publish-crates-upon-release.yml create mode 100644 vendor/sigstore-0.13.0/.github/workflows/conformance.yml create mode 100644 vendor/sigstore-0.13.0/.github/workflows/security-audit.yml create mode 100644 vendor/sigstore-0.13.0/.github/workflows/tests.yml create mode 100644 vendor/sigstore-0.13.0/.gitignore create mode 100644 vendor/sigstore-0.13.0/.taplo.toml create mode 100644 vendor/src/bundle/mod.rs create mode 100644 vendor/src/bundle/models.rs create mode 100644 vendor/src/bundle/sign.rs create mode 100644 vendor/src/bundle/verify/mod.rs create mode 100644 vendor/src/bundle/verify/models.rs create mode 100644 vendor/src/bundle/verify/policy.rs create mode 100644 vendor/src/bundle/verify/verifier.rs create mode 100644 vendor/src/cosign/bundle.rs create mode 100644 vendor/src/cosign/client.rs create mode 100644 vendor/src/cosign/client_builder.rs create mode 100644 vendor/src/cosign/constants.rs create mode 100644 vendor/src/cosign/constraint/annotation.rs create mode 100644 vendor/src/cosign/constraint/mod.rs create mode 100644 vendor/src/cosign/constraint/signature.rs create mode 100644 vendor/src/cosign/mod.rs create mode 100644 vendor/src/cosign/payload/mod.rs create mode 100644 vendor/src/cosign/payload/simple_signing.rs create mode 100644 vendor/src/cosign/signature_layers.rs create mode 100644 vendor/src/cosign/verification_constraint/annotation_verifier.rs create mode 100644 vendor/src/cosign/verification_constraint/cert_subject_email_verifier.rs create mode 100644 vendor/src/cosign/verification_constraint/cert_subject_url_verifier.rs create mode 100644 vendor/src/cosign/verification_constraint/certificate_verifier.rs create mode 100644 vendor/src/cosign/verification_constraint/mod.rs create mode 100644 vendor/src/cosign/verification_constraint/public_key_verifier.rs create mode 100644 vendor/src/crypto/certificate.rs create mode 100644 vendor/src/crypto/certificate_pool.rs create mode 100644 vendor/src/crypto/keyring.rs create mode 100644 vendor/src/crypto/mod.rs create mode 100644 vendor/src/crypto/signing_key/ecdsa/ec.rs create mode 100644 vendor/src/crypto/signing_key/ecdsa/mod.rs create mode 100644 vendor/src/crypto/signing_key/ed25519.rs create mode 100644 vendor/src/crypto/signing_key/kdf.rs create mode 100644 vendor/src/crypto/signing_key/mod.rs create mode 100644 vendor/src/crypto/signing_key/rsa/keypair.rs create mode 100644 vendor/src/crypto/signing_key/rsa/mod.rs create mode 100644 vendor/src/crypto/transparency.rs create mode 100644 vendor/src/crypto/verification_key.rs create mode 100644 vendor/src/errors.rs create mode 100644 vendor/src/fulcio/mod.rs create mode 100644 vendor/src/fulcio/models.rs create mode 100644 vendor/src/fulcio/oauth.rs create mode 100644 vendor/src/lib.rs create mode 100644 vendor/src/mock_client.rs create mode 100644 vendor/src/oauth/mod.rs create mode 100644 vendor/src/oauth/openidflow.rs create mode 100644 vendor/src/oauth/token.rs create mode 100644 vendor/src/registry/config.rs create mode 100644 vendor/src/registry/mod.rs create mode 100644 vendor/src/registry/oci_caching_client.rs create mode 100644 vendor/src/registry/oci_client.rs create mode 100644 vendor/src/registry/oci_reference.rs create mode 100644 vendor/src/rekor/apis/configuration.rs create mode 100644 vendor/src/rekor/apis/entries_api.rs create mode 100644 vendor/src/rekor/apis/index_api.rs create mode 100644 vendor/src/rekor/apis/mod.rs create mode 100644 vendor/src/rekor/apis/pubkey_api.rs create mode 100644 vendor/src/rekor/apis/tlog_api.rs create mode 100644 vendor/src/rekor/mod.rs create mode 100644 vendor/src/rekor/models/alpine.rs create mode 100644 vendor/src/rekor/models/alpine_all_of.rs create mode 100644 vendor/src/rekor/models/consistency_proof.rs create mode 100644 vendor/src/rekor/models/error.rs create mode 100644 vendor/src/rekor/models/hashedrekord.rs create mode 100644 vendor/src/rekor/models/hashedrekord_all_of.rs create mode 100644 vendor/src/rekor/models/helm.rs create mode 100644 vendor/src/rekor/models/helm_all_of.rs create mode 100644 vendor/src/rekor/models/inactive_shard_log_info.rs create mode 100644 vendor/src/rekor/models/inclusion_proof.rs create mode 100644 vendor/src/rekor/models/intoto.rs create mode 100644 vendor/src/rekor/models/intoto_all_of.rs create mode 100644 vendor/src/rekor/models/jar.rs create mode 100644 vendor/src/rekor/models/jar_all_of.rs create mode 100644 vendor/src/rekor/models/log_entry.rs create mode 100644 vendor/src/rekor/models/log_info.rs create mode 100644 vendor/src/rekor/models/mod.rs create mode 100644 vendor/src/rekor/models/proposed_entry.rs create mode 100644 vendor/src/rekor/models/rekord.rs create mode 100644 vendor/src/rekor/models/rekord_all_of.rs create mode 100644 vendor/src/rekor/models/rfc3161.rs create mode 100644 vendor/src/rekor/models/rfc3161_all_of.rs create mode 100644 vendor/src/rekor/models/rpm.rs create mode 100644 vendor/src/rekor/models/rpm_all_of.rs create mode 100644 vendor/src/rekor/models/search_index.rs create mode 100644 vendor/src/rekor/models/search_index_public_key.rs create mode 100644 vendor/src/rekor/models/search_log_query.rs create mode 100644 vendor/src/rekor/models/tuf.rs create mode 100644 vendor/src/rekor/models/tuf_all_of.rs create mode 100644 vendor/src/trust/mod.rs create mode 100644 vendor/src/trust/sigstore/constants.rs create mode 100644 vendor/src/trust/sigstore/mod.rs create mode 100644 vendor/tests/data/keys/cosign_generated_encrypted_empty_private.key create mode 100644 vendor/tests/data/keys/ecdsa_encrypted_private.key create mode 100644 vendor/tests/data/keys/ecdsa_private.key create mode 100644 vendor/tests/data/keys/ed25519_encrypted_private.key create mode 100644 vendor/tests/data/keys/ed25519_private.key create mode 100644 vendor/tests/data/keys/rsa_encrypted_private.key create mode 100644 vendor/tests/data/keys/rsa_private.key create mode 100644 vendor/trust_root/prod/root.json create mode 100644 vendor/trust_root/prod/trusted_root.json diff --git a/Cargo.lock b/Cargo.lock index c6b2f4d..cb486d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3876,8 +3876,6 @@ dependencies = [ [[package]] name = "sigstore" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52bba786054331bdc89e90f74373b68a6c3b63c9754cf20e3a4a629d0165fe38" dependencies = [ "aws-lc-rs", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 7a1f2e4..87abcbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,6 @@ opt-level = 3 lto = "fat" codegen-units = 1 strip = true + +[patch.crates-io] +sigstore = { path = "vendor" } diff --git a/crates/sigstore/src/sign.rs b/crates/sigstore/src/sign.rs index f396ca7..4fdbe89 100644 --- a/crates/sigstore/src/sign.rs +++ b/crates/sigstore/src/sign.rs @@ -1,13 +1,14 @@ //! Sigstore keyless signing (OIDC + Fulcio + Rekor). -use anyhow::{Context, Result, bail}; -use pdf_sign_core::{DigestAlgorithm, compute_digest, suffix::SigstoreBundleBlock}; +use anyhow::{bail, Context, Result}; +use pdf_sign_core::{compute_digest, suffix::SigstoreBundleBlock, DigestAlgorithm}; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use std::env; #[cfg(not(target_arch = "wasm32"))] +use tracing::warn; +#[cfg(not(target_arch = "wasm32"))] use reqwest; /// Sigstore service endpoints configuration. @@ -104,23 +105,29 @@ pub async fn sign_blob(data: &[u8], options: &SignOptions) -> Result // Obtain identity token let identity_token = if let Some(token) = &options.identity_token { - // Try to fetch a fresh GitHub Actions token with audience=sigstore if available + // Parse the raw JWT token directly; Fulcio validates it. + let parsed = sigstore::oauth::IdentityToken::try_from(token.as_str()) + .context("Failed to parse identity token")?; + #[cfg(not(target_arch = "wasm32"))] { - if let Ok(fresh_token) = maybe_fetch_github_actions_token().await { - fresh_token + // Prefer a fresh GitHub Actions token (audience=sigstore) when available to avoid + // mis-minted or stale tokens passed in by the caller. + if let Some(fresh) = maybe_fetch_github_actions_token().await? { + tracing::debug!("Using GitHub Actions ID token with audience=sigstore (overrides provided token)"); + fresh + } else if !parsed.has_sigstore_audience() { + bail!( + "Provided identity token is missing audience 'sigstore'. In GitHub Actions, request an ID token with audience=sigstore (set 'id-token: write' permission and fetch via ACTIONS_ID_TOKEN_REQUEST_URL)." + ); } else { - // Fall back to provided token - sigstore::oauth::IdentityToken::from( - openidconnect::IdToken::from_str(token).context("Failed to parse identity token")?, - ) + parsed } } + #[cfg(target_arch = "wasm32")] { - sigstore::oauth::IdentityToken::from( - openidconnect::IdToken::from_str(token).context("Failed to parse identity token")?, - ) + parsed } } else { tracing::debug!("Starting interactive OIDC flow"); @@ -178,6 +185,58 @@ pub async fn sign_blob(data: &[u8], options: &SignOptions) -> Result }) } +/// If running inside GitHub Actions and provided token lacks the `sigstore` audience, fetch a new token. +/// This helps avoid stale/mis-audienced tokens supplied by callers when the workflow already has `id-token: write`. +#[cfg(not(target_arch = "wasm32"))] +async fn maybe_fetch_github_actions_token( +) -> Result> { + let request_url = match env::var("ACTIONS_ID_TOKEN_REQUEST_URL") { + Ok(v) => v, + Err(_) => return Ok(None), + }; + let request_token = match env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN") { + Ok(v) => v, + Err(_) => return Ok(None), + }; + + // Append audience=sigstore; GitHub Actions requires an explicit audience parameter. + let url = if request_url.contains("audience=") { + request_url + } else if request_url.contains('?') { + format!("{}&audience=sigstore", request_url) + } else { + format!("{}?audience=sigstore", request_url) + }; + + let client = reqwest::Client::new(); + let resp = client + .get(&url) + .bearer_auth(request_token) + .send() + .await + .context("Failed to request GitHub Actions ID token")?; + + if !resp.status().is_success() { + warn!(status = resp.status().as_u16(), "GitHub Actions ID token request failed"); + return Ok(None); + } + + let body: GitHubIdTokenResponse = resp + .json() + .await + .context("Failed to decode GitHub Actions ID token response")?; + + let token = sigstore::oauth::IdentityToken::try_from(body.value.as_str()) + .context("GitHub Actions ID token malformed")?; + + if !token.has_sigstore_audience() { + warn!("GitHub Actions ID token still missing 'sigstore' audience"); + return Ok(None); + } + + Ok(Some(token)) +} + /// Extract certificate identity and issuer from bundle. fn extract_bundle_info(bundle: &sigstore::bundle::Bundle) -> Result<(String, String)> { use x509_cert::Certificate; @@ -301,45 +360,3 @@ async fn obtain_identity_token( tracing::debug!("Identity token obtained"); Ok(sigstore::oauth::IdentityToken::from(token)) } - -/// Attempt to fetch a fresh GitHub Actions OIDC token with audience=sigstore. -/// Returns Ok(token) on success, or Err if not in GitHub Actions or fetch fails. -#[cfg(not(target_arch = "wasm32"))] -async fn maybe_fetch_github_actions_token() -> Result { - let request_url = env::var("ACTIONS_ID_TOKEN_REQUEST_URL") - .ok() - .ok_or_else(|| anyhow::anyhow!("Not in GitHub Actions"))?; - let request_token = env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN") - .ok() - .ok_or_else(|| anyhow::anyhow!("No token request credentials"))?; - - // Append audience=sigstore if not already present - let url = if request_url.contains("audience=") { - request_url - } else if request_url.contains('?') { - format!("{}&audience=sigstore", request_url) - } else { - format!("{}?audience=sigstore", request_url) - }; - - let client = reqwest::Client::new(); - let resp = client - .get(&url) - .bearer_auth(request_token) - .send() - .await - .context("Failed to request GitHub Actions ID token")?; - - if !resp.status().is_success() { - return Err(anyhow::anyhow!("GitHub Actions token request failed: {}", resp.status())); - } - - let body: GitHubIdTokenResponse = resp - .json() - .await - .context("Failed to decode GitHub Actions token response")?; - - Ok(sigstore::oauth::IdentityToken::from( - openidconnect::IdToken::from_str(&body.value).context("Failed to parse GitHub Actions token")?, - )) -} diff --git a/vendor/CHANGELOG.md b/vendor/CHANGELOG.md new file mode 100644 index 0000000..7acc45a --- /dev/null +++ b/vendor/CHANGELOG.md @@ -0,0 +1,321 @@ +# v0.10.0 + +## What's Changed + +- chore(deps): Update oci-distribution requirement from 0.10 to 0.11 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/343 +- verify: init by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/311 +- chore(deps): Update rstest requirement from 0.18.1 to 0.19.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/351 +- chore(deps): Bump actions/checkout from 4.1.2 to 4.1.5 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/360 +- fix linter warning by @flavio in https://github.com/sigstore/sigstore-rs/pull/361 +- chore(deps): Update cached requirement from 0.49.2 to 0.51.3 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/362 +- chore(deps): Update webbrowser requirement from 0.8.12 to 1.0.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/359 +- chore(deps): Bump actions/checkout from 4.1.5 to 4.1.6 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/363 +- chore(deps): Update testcontainers requirement from 0.15 to 0.16 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/355 +- chore(deps): change provider of cargo-audit GH action by @flavio in https://github.com/sigstore/sigstore-rs/pull/364 +- fix docs by @flavio in https://github.com/sigstore/sigstore-rs/pull/366 +- fix: allow ManualTrustRoot to have multiple rekor keys by @flavio in https://github.com/sigstore/sigstore-rs/pull/365 +- build(deps): update testcontainers requirement from 0.16 to 0.17 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/368 +- build(deps): update rstest requirement from 0.19.0 to 0.21.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/370 +- build(deps): bump actions/checkout from 4.1.6 to 4.1.7 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/372 +- build(deps): update testcontainers requirement from 0.17 to 0.18 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/371 +- Signed Certificate Timestamp verification by @tnytown in https://github.com/sigstore/sigstore-rs/pull/326 +- transparency: pull OID constants from `const-oid` by @tnytown in https://github.com/sigstore/sigstore-rs/pull/374 +- build(deps): update testcontainers requirement from 0.18 to 0.19 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/375 +- build(deps): update cached requirement from 0.51.3 to 0.52.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/377 +- build(deps): update testcontainers requirement from 0.19 to 0.20 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/376 +- build(deps): update cached requirement from 0.52.0 to 0.53.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/379 +- build(deps): update rstest requirement from 0.21.0 to 0.22.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/383 +- build(deps): update testcontainers requirement from 0.20 to 0.21 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/382 +- build(deps): update testcontainers requirement from 0.21 to 0.22 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/386 +- fix: Allow empty passwords for encrypted pem files by @gmpinder in https://github.com/sigstore/sigstore-rs/pull/381 +- build(deps): update tough requirement from 0.17.1 to 0.18.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/389 +- dependency cleanup by @flavio in https://github.com/sigstore/sigstore-rs/pull/390 +- chore: update cargo audit ignore list by @flavio in https://github.com/sigstore/sigstore-rs/pull/387 + +## New Contributors + +- @tnytown made their first contribution in https://github.com/sigstore/sigstore-rs/pull/326 +- @gmpinder made their first contribution in https://github.com/sigstore/sigstore-rs/pull/381 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.9.0...v0.10.0 + +# v0.9.0 + +## What's Changed + +- sign: init by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/310 +- cargo audit: ignore RUSTSEC-2023-0071 by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/321 +- chore(deps): Update json-syntax requirement from 0.9.6 to 0.10.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/319 +- chore(deps): Update cached requirement from 0.46.0 to 0.47.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/323 +- chore(deps): Update serial_test requirement from 2.0.0 to 3.0.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/322 +- dep: update rustls-webpki, fold in pki_types by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/324 +- chore(deps): Update cached requirement from 0.47.0 to 0.48.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/325 +- chore(deps): Update json-syntax requirement from 0.10.0 to 0.11.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/327 +- chore(deps): Update cached requirement from 0.48.0 to 0.49.2 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/329 +- chore(deps): Update json-syntax requirement from 0.11.1 to 0.12.2 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/330 +- lint: fix lint error of chrono and tokio by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/334 +- chore(deps): Update base64 requirement from 0.21.0 to 0.22.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/332 +- The `Repository` trait and `ManualRepository` struct no longer require a feature flag by @tannaurus in https://github.com/sigstore/sigstore-rs/pull/331 +- chore(deps): Bump actions/checkout from 4.1.1 to 4.1.2 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/336 +- chore(deps): Update reqwest requirement from 0.11 to 0.12 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/341 +- update tough dep by @astoycos in https://github.com/sigstore/sigstore-rs/pull/340 +- Tag the 0.9.0 release by @flavio in https://github.com/sigstore/sigstore-rs/pull/342 + +## New Contributors + +- @tannaurus made their first contribution in https://github.com/sigstore/sigstore-rs/pull/331 +- @astoycos made their first contribution in https://github.com/sigstore/sigstore-rs/pull/340 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.8.0...v0.9.0 + +# v0.8.0 + +## What's Changed + +- chore(deps): Update rstest requirement from 0.17.0 to 0.18.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/282 +- chore(deps): do not enable default features of chrono by @flavio in https://github.com/sigstore/sigstore-rs/pull/286 +- chore(deps): Update pem requirement from 2.0 to 3.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/289 +- conformance: add conformance CLI and action by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/287 +- chore: fix clippy warnings by @flavio in https://github.com/sigstore/sigstore-rs/pull/292 +- chore(deps): Bump actions/checkout from 3.5.3 to 3.6.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/291 +- chore(deps): Update tough requirement from 0.13 to 0.14 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/290 +- chore(deps): update to latest version of picky by @flavio in https://github.com/sigstore/sigstore-rs/pull/293 +- chore(deps): Bump actions/checkout from 3.6.0 to 4.0.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/294 +- chore: add repository link to Cargo metadata by @flavio in https://github.com/sigstore/sigstore-rs/pull/297 +- chore(deps): Update cached requirement from 0.44.0 to 0.45.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/298 +- chore(deps): Bump actions/checkout from 4.0.0 to 4.1.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/302 +- chore(deps): Update cached requirement from 0.45.1 to 0.46.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/301 +- chore(deps): Update testcontainers requirement from 0.14 to 0.15 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/303 +- chore(deps): Bump actions/checkout from 4.1.0 to 4.1.1 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/304 +- cosign/tuf: use trustroot by @jleightcap in https://github.com/sigstore/sigstore-rs/pull/305 +- Fix broken tests, update deps by @flavio in https://github.com/sigstore/sigstore-rs/pull/313 + +## New Contributors + +- @jleightcap made their first contribution in https://github.com/sigstore/sigstore-rs/pull/287 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.7.2...v0.8.0 + +# v0.7.2 + +## What's Changed + +- chore(deps): Update cached requirement from 0.42.0 to 0.44.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/277 +- chore(deps): Bump actions/checkout from 3.5.2 to 3.5.3 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/278 +- chore(deps): update picky dependency by @flavio in https://github.com/sigstore/sigstore-rs/pull/279 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.7.1...v0.7.2 + +# v0.7.1 + +## What's Changed + +- fix: ensure cosign client can be sent between threads by @flavio in https://github.com/sigstore/sigstore-rs/pull/275 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.7.0...v0.7.1 + +# v0.7.0 + +## What's Changed + +- Fix typo in SignatureLayer::new doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/170 +- feat: replace example dependency docker_credential by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/172 +- Clean up readme by @lukehinds in https://github.com/sigstore/sigstore-rs/pull/173 +- chore(deps): Update rstest requirement from 0.15.0 to 0.16.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/174 +- Fix typo in simple_signing.rs by @danbev in https://github.com/sigstore/sigstore-rs/pull/175 +- Introduce SignedArtifactBundle by @danbev in https://github.com/sigstore/sigstore-rs/pull/171 +- chore(deps): Update base64 requirement from 0.13.0 to 0.20.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/177 +- chore(deps): Bump actions/checkout from 3.1.0 to 3.2.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/180 +- chore(deps): Update serial_test requirement from 0.9.0 to 0.10.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/182 +- chore(deps): Update cached requirement from 0.40.0 to 0.41.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/181 +- Fix typo in SecretBoxCipher doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/179 +- chore(deps): Update cached requirement from 0.41.0 to 0.42.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/185 +- chore(deps): Bump actions/checkout from 3.2.0 to 3.3.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/183 +- chore(deps): Update base64 requirement from 0.20.0 to 0.21.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/184 +- Add cosign verify-bundle example by @danbev in https://github.com/sigstore/sigstore-rs/pull/186 +- Fix incorrect base64_signature doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/188 +- Fix typos in tuf/mod.rs by @danbev in https://github.com/sigstore/sigstore-rs/pull/195 +- chore(deps): Update serial_test requirement from 0.10.0 to 1.0.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/200 +- fix: show actual response status field by @ctron in https://github.com/sigstore/sigstore-rs/pull/197 +- Update target -> target_name for consistency by @danbev in https://github.com/sigstore/sigstore-rs/pull/196 +- fix: make the fields accessible by @ctron in https://github.com/sigstore/sigstore-rs/pull/202 +- Add verify-bundle example to README.md by @danbev in https://github.com/sigstore/sigstore-rs/pull/203 +- fix: make fields of hash accessible by @ctron in https://github.com/sigstore/sigstore-rs/pull/205 +- Improve public key output and add file output by @Gronner in https://github.com/sigstore/sigstore-rs/pull/194 +- Add TokenProvider::Static doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/208 +- Changed the type of LogEntry.body from String to Body by @Neccolini in https://github.com/sigstore/sigstore-rs/pull/207 +- Fix errors/warnings reported by clippy by @danbev in https://github.com/sigstore/sigstore-rs/pull/210 +- Add fine-grained features to control the compilation by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/189 +- fix: bring tuf feature out of rekor and add related docs by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/211 +- chore: update crypto deps by @flavio in https://github.com/sigstore/sigstore-rs/pull/204 +- Replace `x509-parser` with `x509-cert` by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/212 +- Fix: Wrong parameter order inside documentation example. by @vembacher in https://github.com/sigstore/sigstore-rs/pull/215 +- Remove lines about timestamp in lib.rs by @naveensrinivasan in https://github.com/sigstore/sigstore-rs/pull/213 +- Fix ed25519 version conflict by @vembacher in https://github.com/sigstore/sigstore-rs/pull/223 +- Support compiling to wasm32 architectures by @lulf in https://github.com/sigstore/sigstore-rs/pull/221 +- Fix link to contributor doc in readme by @oliviacrain in https://github.com/sigstore/sigstore-rs/pull/225 +- refactor: derive `Clone` trait by @flavio in https://gitub.com/sigstore/sigstore-rs/pull/227 +- fix: correct typo in verify/main.rs by @danbev in https://github.com/sigstore/sigstore-rs/pull/228 +- chore(deps): Update tough requirement from 0.12 to 0.13 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/237 +- chore(deps): Bump actions/checkout from 3.3.0 to 3.4.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/240 +- dep: update picky version to git rid of `ring` by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/226 +- chore(deps): Bump actions/checkout from 3.4.0 to 3.5.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/245 +- fix: make LogEntry Body an enum by @danbev in https://github.com/sigstore/sigstore-rs/pull/244 +- Add verify-blob example by @danbev in https://github.com/sigstore/sigstore-rs/pull/239 +- Introduce Newtype `OciReference` into API for OCI image references. by @vembacher in https://github.com/sigstore/sigstore-rs/pull/216 +- Swap over to using CDN to fetch TUF metadata by @haydentherapper in https://github.com/sigstore/sigstore-rs/pull/251 +- chore(deps): Bump actions/checkout from 3.5.0 to 3.5.2 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/252 +- upgrade 'der' to 0.7.5 by @dmitris in https://github.com/sigstore/sigstore-rs/pull/257 +- remove unused 'clock' feature for chrono by @dmitris in https://github.com/sigstore/sigstore-rs/pull/258 +- update pkcs1 from 0.4.0 to 0.7.5 by @dmitris in https://github.com/sigstore/sigstore-rs/pull/260 +- use 2021 Rust edition by @dmitris in https://github.com/sigstore/sigstore-rs/pull/261 +- chore(deps): Update serial_test requirement from 1.0.0 to 2.0.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/264 +- update scrypt to 0.11.0, adapt for API change (fix #231) by @dmitris in https://github.com/sigstore/sigstore-rs/pull/268 +- upgrade ed25519-dalek to 2.0.0-rc.2 by @dmitris in https://github.com/sigstore/sigstore-rs/pull/263 +- chore(deps): Update openidconnect requirement from 2.3 to 3.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/265 +- chore(deps): Update rstest requirement from 0.16.0 to 0.17.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/271 +- Update crypto deps by @flavio in https://github.com/sigstore/sigstore-rs/pull/269 +- Update create_log_entry example to create key pair. by @jvanz in https://github.com/sigstore/sigstore-rs/pull/206 + +## New Contributors + +- @ctron made their first contribution in https://github.com/sigstore/sigstore-rs/pull/197 +- @Gronner made their first contribution in https://github.com/sigstore/sigstore-rs/pull/194 +- @Neccolini made their first contribution in https://github.com/sigstore/sigstore-rs/pull/207 +- @vembacher made their first contribution in https://github.com/sigstore/sigstore-rs/pull/215 +- @naveensrinivasan made their first contribution in https://github.com/sigstore/sigstore-rs/pull/213 +- @lulf made their first contribution in https://github.com/sigstore/sigstore-rs/pull/221 +- @oliviacrain made their first contribution in https://github.com/sigstore/sigstore-rs/pull/225 +- @haydentherapper made their first contribution in https://github.com/sigstore/sigstore-rs/pull/251 +- @dmitris made their first contribution in https://github.com/sigstore/sigstore-rs/pull/257 +- @jvanz made their first contribution in https://github.com/sigstore/sigstore-rs/pull/206 + +**Full Changelog**: https://github.com/sigstore/sigstore-rs/compare/v0.6.0...v0.7.0h + +# v0.6.0 + +## Fixes + +- Fix typo in cosign/mod.rs doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/148 +- Fix typo in KeyPair trait doc comment by @danbev in https://github.com/sigstore/sigstore-rs/pull/149 +- Update cached requirement from 0.39.0 to 0.40.0 by @dependabot in https://github.com/sigstore/sigstore-rs/pull/154 +- Fix typos in PublicKeyVerifier doc comments by @danbev in https://github.com/sigstore/sigstore-rs/pull/155 +- Fix: CI error for auto deref by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/160 +- Fix typo and grammar in signature_layers.rs by @danbev in https://github.com/sigstore/sigstore-rs/pull/161 +- Remove unused imports in examples/rekor by @danbev in https://github.com/sigstore/sigstore-rs/pull/162 +- Update link to verification example by @danbev in https://github.com/sigstore/sigstore-rs/pull/156 +- Fix typos in from_encrypted_pem doc comments by @danbev in https://github.com/sigstore/sigstore-rs/pull/164 +- Fix typos in doc comments by @danbev in https://github.com/sigstore/sigstore-rs/pull/163 +- Update path to fulcio-cert in verify example by @danbev in https://github.com/sigstore/sigstore-rs/pull/168 + +## Enhancements + +- Add getter functions for LogEntry fields by @lkatalin in https://github.com/sigstore/sigstore-rs/pull/147 +- Add TreeSize alias to Rekor by @avery-blanchard in https://github.com/sigstore/sigstore-rs/pull/151 +- Updates for parsing hashedrekord LogEntry by @lkatalin in https://github.com/sigstore/sigstore-rs/pull/152 +- Add certificate based verification by @flavio in https://github.com/sigstore/sigstore-rs/pull/159 +- Add support for OCI Image signing (spec v1.0) by @Xynnn007 in https://github.com/sigstore/sigstore-rs/pull/158 + +## Contributors + +- Avery Blanchard (@avery-blanchardmade) +- Daniel Bevenius (@danbev) +- Flavio Castelli (@flavio) +- Lily Sturmann (@lkatalin) +- Xynnn (@Xynnn007) + +# v0.5.3 + +## Fixes + +- rustls should not require openssl by (https://github.com/sigstore/sigstore-rs/pull/146) + +## Others + +- Rework Rekor module structure and enable doc tests (https://github.com/sigstore/sigstore-rs/pull/145) + +## Contributors + +- Flavio Castelli (@flavio) +- Lily Sturmann (@lkatalin) + +# v0.5.2 + +## Fixes + +- Address compilation error (https://github.com/sigstore/sigstore-rs/pull/143) + +## Contributors + +- Flavio Castelli (@flavio) + +# v0.5.1 + +## Fixes + +- fix verification of signatures produced with PKI11 (https://github.com/sigstore/sigstore-rs/pull/142) + +## Others + +- Update rsa dependency to stable version 0.7.0 (https://github.com/sigstore/sigstore-rs/pull/141) +- Bump actions/checkout from 3.0.2 to 3.1.0 (https://github.com/sigstore/sigstore-rs/pull/140) + +## Contributors + +- Flavio Castelli (@flavio) +- Xynnn (@Xynnn007) + +# v0.5.0 + +## Enhancements + +- update user-agent value to be specific to sigstore-rs (https://github.com/sigstore/sigstore-rs/pull/122) +- remove /api/v1/version from client by (https://github.com/sigstore/sigstore-rs/pull/121) +- crate async fulcio client (https://github.com/sigstore/sigstore-rs/pull/132) +- Removed ring dependency (https://github.com/sigstore/sigstore-rs/pull/127) + +## Others + +- Update dependencies +- Refactoring and examples for key interface (https://github.com/sigstore/sigstore-rs/pull/123) +- Fix doc test failures (https://github.com/sigstore/sigstore-rs/pull/136) + +## Contributors + +- Bob Callaway (@bobcallaway) +- Bob McWhirter (@bobmcwhirter) +- Flavio Castelli (@flavio) +- Luke Hinds (@lukehinds) +- Xynnn (@Xynnn007) + +# v0.4.0 + +## Enhancements + +- feat: from and to interface for signing and verification keys (https://github.com/sigstore/sigstore-rs/pulls/115) +- Refactor examples to support subfolder execution (https://github.com/sigstore/sigstore-rs/pulls/111) +- Integrate Rekor with Sigstore-rs (https://github.com/sigstore/sigstore-rs/pulls/88) +- feat: add example case and docs for key interface (https://github.com/sigstore/sigstore-rs/pulls/99) +- feat: add signing key module (https://github.com/sigstore/sigstore-rs/pulls/87) + +## Documention + +- Update readme to include new features (https://github.com/sigstore/sigstore-rs/pulls/113) + +## Others + +- bump crate version (https://github.com/sigstore/sigstore-rs/pulls/118) +- Add RUSTSEC-2021-0139 to audit.toml (https://github.com/sigstore/sigstore-rs/pulls/112) +- Update xsalsa20poly1305 requirement from 0.7.1 to 0.9.0 (https://github.com/sigstore/sigstore-rs/pulls/101) +- ignore derive_partial_eq_without_eq (https://github.com/sigstore/sigstore-rs/pulls/102) +- fix clippy lints (https://github.com/sigstore/sigstore-rs/pulls/98) + +## Contributors + +- Carlos Tadeu Panato Junior (@cpanato) +- Flavio Castelli (@flavio) +- Jyotsna (@jyotsna-penumaka) +- Lily Sturmann (@lkatalin) +- Luke Hinds (@lukehinds) +- Tony Arcieri (@tarcieri) +- Xynnn\_ (@Xynnn007) diff --git a/vendor/CODEOWNERS b/vendor/CODEOWNERS new file mode 100644 index 0000000..bd4a67a --- /dev/null +++ b/vendor/CODEOWNERS @@ -0,0 +1,9 @@ +# The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order): +# +# flavio +# lukehinds +# arsa +# viccuad +# Xynnn007 + +@sigstore-rs-codeowners diff --git a/vendor/CODE_OF_CONDUCT.md b/vendor/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..440768d --- /dev/null +++ b/vendor/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/vendor/CONTRIBUTORS.md b/vendor/CONTRIBUTORS.md new file mode 100644 index 0000000..eef0340 --- /dev/null +++ b/vendor/CONTRIBUTORS.md @@ -0,0 +1,122 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish +to make via an [issue](https://github.com/sigstore/sigstore-rs/issues). + +## Building and testing + +The Makefile contains useful targets for editing source code (`make lint`, `make fmt`), building executables +(`make build`) and testing (`make test`). + +Full test suite requires Docker daemon to be running (and the user must have permissions to start a container). + +## Pull Request Process + +1. Create an [issue](https://github.com/sigstore/sigstore-rs/issues) + outlining the fix or feature. +2. Fork the sigstore-rs repository to your own github account and clone it locally. +3. Hack on your changes. +4. Update the README.md with details of changes to any interface, this includes new environment + variables, exposed ports, useful file locations, CLI parameters and + new or changed configuration values. +5. Correctly format your commit message see [Commit Messages](#commit-message-guidelines) + below. +6. Ensure that CI passes, if it fails, fix the failures. +7. Every pull request requires a review from the [core sigstore-rs team](https://github.com/orgs/github.com/sigstore/teams/sigstore-rs-codeowners) + before merging. +8. If your pull request consists of more than one commit, please squash your + commits as described in [Squash Commits](#squash-commits) + +## Commit Message Guidelines + +We follow the commit formatting recommendations found on [Chris Beams' How to Write a Git Commit Message article]((https://chris.beams.io/posts/git-commit/). + +Well formed commit messages not only help reviewers understand the nature of +the Pull Request, but also assists the release process where commit messages +are used to generate release notes. + +A good example of a commit message would be as follows: + +``` +Summarize changes in around 50 characters or less + +More detailed explanatory text, if necessary. Wrap it to about 72 +characters or so. In some contexts, the first line is treated as the +subject of the commit and the rest of the text as the body. The +blank line separating the summary from the body is critical (unless +you omit the body entirely); various tools like `log`, `shortlog` +and `rebase` can get confused if you run the two together. + +Explain the problem that this commit is solving. Focus on why you +are making this change as opposed to how (the code explains that). +Are there side effects or other unintuitive consequences of this +change? Here's the place to explain them. + +Further paragraphs come after blank lines. + + - Bullet points are okay, too + + - Typically a hyphen or asterisk is used for the bullet, preceded + by a single space, with blank lines in between, but conventions + vary here + +If you use an issue tracker, put references to them at the bottom, +like this: + +Resolves: #123 +See also: #456, #789 +``` + +Note the `Resolves #123` tag, this references the issue raised and allows us to +ensure issues are associated and closed when a pull request is merged. + +Please refer to [the github help page on message types](https://help.github.com/articles/closing-issues-using-keywords/) +for a complete list of issue references. + +## Squash Commits + +Should your pull request consist of more than one commit (perhaps due to +a change being requested during the review cycle), please perform a git squash +once a reviewer has approved your pull request. + +A squash can be performed as follows. Let's say you have the following commits: + + initial commit + second commit + final commit + +Run the command below with the number set to the total commits you wish to +squash (in our case 3 commits): + + git rebase -i HEAD~3 + +You default text editor will then open up and you will see the following:: + + pick eb36612 initial commit + pick 9ac8968 second commit + pick a760569 final commit + + # Rebase eb1429f..a760569 onto eb1429f (3 commands) + +We want to rebase on top of our first commit, so we change the other two commits +to `squash`: + + pick eb36612 initial commit + squash 9ac8968 second commit + squash a760569 final commit + +After this, should you wish to update your commit message to better summarise +all of your pull request, run: + + git commit --amend + +You will then need to force push (assuming your initial commit(s) were posted +to github): + + git push origin your-branch --force + +Alternatively, a core member can squash your commits within Github. +## Code of Conduct + +sigstore-rs adheres to and enforces the [Contributor Covenant](http://contributor-covenant.org/version/1/4/) Code of Conduct. +Please take a moment to read the [CODE_OF_CONDUCT.md](https://github.com/sigstore/sigstore-rs/blob/master/CODE_OF_CONDUCT.md) document. diff --git a/vendor/COPYRIGHT.txt b/vendor/COPYRIGHT.txt new file mode 100644 index 0000000..7a01c84 --- /dev/null +++ b/vendor/COPYRIGHT.txt @@ -0,0 +1,14 @@ + +Copyright 2021 The Sigstore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/Cargo.lock b/vendor/Cargo.lock new file mode 100644 index 0000000..5004a88 --- /dev/null +++ b/vendor/Cargo.lock @@ -0,0 +1,5222 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +dependencies = [ + "backtrace", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-fips-sys" +version = "0.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede71ad84efb06d748d9af3bc500b14957a96282a69a6833b1420dcacb411cc3" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "regex", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-fips-sys", + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bollard" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06" +dependencies = [ + "async-stream", + "base64 0.22.1", + "bitflags 2.9.4", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes", + "chrono", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.2", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-buildkit-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b3e79f8bd0f25f32660e3402afca46fd91bebaf135af017326d905651f8107" +dependencies = [ + "prost 0.13.5", + "prost-types 0.13.5", + "tonic", + "ureq", +] + +[[package]] +name = "bollard-stubs" +version = "1.48.3-rc.28.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368" +dependencies = [ + "base64 0.22.1", + "bollard-buildkit-proto", + "bytes", + "chrono", + "prost 0.13.5", + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cached" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c" +dependencies = [ + "ahash 0.8.12", + "async-trait", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.15.5", + "once_cell", + "thiserror 2.0.17", + "tokio", + "web-time", +] + +[[package]] +name = "cached_proc_macro" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "decoded-char" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.106", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.11.4", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-auth" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" +dependencies = [ + "memchr", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.3", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-number" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66994b2bac615128d07a1e4527ad29e98b004dd1a1769e7b8fbc1173ccf43006" +dependencies = [ + "lexical", + "ryu-js", + "serde", + "smallvec", +] + +[[package]] +name = "json-syntax" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044a68aba3f96d712f492b72be25e10f96201eaaca3207a7d6e68d6d5105fda9" +dependencies = [ + "decoded-char", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "json-number", + "locspan", + "locspan-derive", + "ryu-js", + "serde", + "smallstr", + "smallvec", + "utf8-decode", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lexical" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc8a009b2ff1f419ccc62706f04fe0ca6e67b37460513964a3dfdb919bb37d6" +dependencies = [ + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.4", + "libc", + "redox_syscall 0.5.18", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "locspan" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33890449fcfac88e94352092944bf321f55e5deb4e289a6f51c87c55731200a0" + +[[package]] +name = "locspan-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.16", + "http", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.9.4", + "objc2", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "oci-client" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b74df13319e08bc386d333d3dc289c774c88cc543cae31f5347db07b5ec2172" +dependencies = [ + "bytes", + "chrono", + "futures-util", + "http", + "http-auth", + "jwt", + "lazy_static", + "oci-spec", + "olpc-cjson", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "oci-spec" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb4684653aeaba48dea019caa17b2773e1212e281d50b6fa759f36fe032239d" +dependencies = [ + "const_format", + "derive_builder", + "getset", + "regex", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 2.0.17", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696183c9b5fe81a7715d074fd632e8bd46f4ccc0231a3ed7fc580a80de5f7083" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openidconnect" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2" +dependencies = [ + "base64 0.21.7", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "openssl" +version = "0.10.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.106", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.11.4", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive 0.14.1", +] + +[[package]] +name = "prost-build" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.14.1", + "prost-types 0.14.1", + "regex", + "syn 2.0.106", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "prost-reflect" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a3ac73ec9a9118131a4594c9d336631a07852220a1d0ae03ee36b04503a063" +dependencies = [ + "base64 0.22.1", + "prost 0.14.1", + "prost-reflect-derive", + "prost-types 0.14.1", + "serde", + "serde-value", +] + +[[package]] +name = "prost-reflect-build" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8214ae2c30bbac390db0134d08300e770ef89b6d4e5abf855e8d300eded87e28" +dependencies = [ + "prost-build", + "prost-reflect", +] + +[[package]] +name = "prost-reflect-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b6d90e29fa6c0d13c2c19ba5e4b3fb0efbf5975d27bcf4e260b7b15455bcabe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", +] + +[[package]] +name = "prost-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +dependencies = [ + "prost 0.14.1", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.1", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.1", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.3", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.106", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.4", + "schemars 0.9.0", + "schemars 1.0.4", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "sigstore" +version = "0.13.0" +dependencies = [ + "anyhow", + "assert-json-diff", + "async-trait", + "aws-lc-rs", + "base64 0.22.1", + "cached", + "cfg-if", + "chrono", + "clap", + "const-oid", + "crypto_secretbox", + "digest", + "docker_credential", + "ecdsa", + "ed25519", + "ed25519-dalek", + "elliptic-curve", + "futures", + "futures-util", + "getrandom 0.2.16", + "hex", + "json-syntax", + "oci-client", + "olpc-cjson", + "openidconnect", + "openssl", + "p256", + "p384", + "pem", + "pkcs1", + "pkcs8", + "rand 0.8.5", + "regex", + "reqwest", + "ring", + "rsa", + "rstest", + "rustls-pki-types", + "rustls-webpki", + "scrypt", + "serde", + "serde_json", + "serde_repr", + "serde_with", + "serial_test", + "sha2", + "signature", + "sigstore_protobuf_specs", + "tempfile", + "testcontainers", + "thiserror 2.0.17", + "tls_codec", + "tokio", + "tokio-util", + "tough", + "tracing", + "tracing-subscriber", + "url", + "webbrowser", + "x509-cert", + "zeroize", +] + +[[package]] +name = "sigstore-protobuf-specs-derive" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80baa401f274093f7bb27d7a69d6139cbc11f1b97624e9a61a9b3ea32c776a35" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sigstore_protobuf_specs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4827a7a6b539af686abf27c09cb3ddc76c786c0b8b999a1b13566747b22e77c" +dependencies = [ + "anyhow", + "glob", + "prost 0.14.1", + "prost-build", + "prost-reflect", + "prost-reflect-build", + "prost-types 0.14.1", + "serde", + "serde_json", + "sigstore-protobuf-specs-derive", + "which", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallstr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862077b1e764f04c251fe82a2ef562fd78d7cadaeb072ca7c2bcaf7217b1ff3b" +dependencies = [ + "serde", + "smallvec", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "futures-core", + "pin-project", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.106", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "testcontainers" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92bce247dc9260a19808321e11b51ea6a0293d02b48ab1c6578960610cfa2a7" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "ulid", + "url", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.5", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tough" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88d0ee9525696569cc2af5d46f8a739028c0268895071e0386957195b0c9161" +dependencies = [ + "async-recursion", + "async-trait", + "aws-lc-rs", + "bytes", + "chrono", + "dyn-clone", + "futures", + "futures-core", + "globset", + "hex", + "log", + "olpc-cjson", + "pem", + "percent-encoding", + "reqwest", + "rustls", + "serde", + "serde_json", + "serde_plain", + "snafu", + "tempfile", + "tokio", + "tokio-util", + "typed-path", + "untrusted 0.7.1", + "url", + "walkdir", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.11.4", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-path" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82205ffd44a9697e34fc145491aa47310f9871540bb7909eaa9365e0a9a46607" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.2", + "web-time", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-decode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.3", +] + +[[package]] +name = "webpki-roots" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "sha1", + "signature", + "spki", + "tls_codec", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] diff --git a/vendor/Cargo.toml b/vendor/Cargo.toml new file mode 100644 index 0000000..c759e9c --- /dev/null +++ b/vendor/Cargo.toml @@ -0,0 +1,584 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "sigstore" +version = "0.13.0" +authors = ["sigstore-rs developers"] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "An experimental crate to interact with sigstore" +readme = "README.md" +license = "Apache-2.0" +repository = "https://github.com/sigstore/sigstore-rs" + +[package.metadata.docs.rs] +all-features = true + +[features] +bundle = [ + "sign", + "verify", +] +cached-client = ["dep:cached"] +cert = [ + "dep:aws-lc-rs", + "rustls-webpki/aws-lc-rs", +] +cosign = [ + "cert", + "dep:async-trait", + "dep:cfg-if", + "dep:oci-client", + "dep:olpc-cjson", + "dep:regex", + "registry", +] +default = [ + "full", + "native-tls", +] +fulcio = [ + "dep:reqwest", + "dep:serde_repr", + "dep:serde_with", + "dep:webbrowser", + "oauth", +] +full = [ + "bundle", + "cached-client", + "cosign", + "fulcio", + "mock-client", + "rekor", + "sigstore-trust-root", +] +mock-client = [ + "dep:async-trait", + "dep:oci-client", +] +native-tls = [ + "oci-client?/native-tls", + "openidconnect?/native-tls", + "reqwest?/native-tls", +] +oauth = [ + "dep:openidconnect", + "dep:reqwest", +] +registry = [ + "dep:async-trait", + "dep:oci-client", + "dep:olpc-cjson", +] +rekor = ["dep:reqwest"] +rustls-tls = [ + "oci-client?/rustls-tls", + "openidconnect?/rustls-tls", + "reqwest?/rustls-tls", +] +rustls-tls-native-roots = ["oci-client?/rustls-tls-native-roots"] +sign = [ + "cert", + "dep:hex", + "dep:json-syntax", + "dep:sigstore_protobuf_specs", + "fulcio", + "rekor", +] +sigstore-trust-root = [ + "dep:futures", + "dep:futures-util", + "dep:hex", + "dep:reqwest", + "dep:sigstore_protobuf_specs", + "dep:tough", + "tokio/sync", +] +test-registry = [] +verify = [ + "cert", + "dep:hex", + "dep:json-syntax", + "dep:sigstore_protobuf_specs", + "fulcio", + "rekor", +] +wasm = [ + "chrono/wasmbind", + "getrandom/js", + "ring?/wasm32_unknown_unknown_js", +] + +[lib] +name = "sigstore" +path = "src/lib.rs" + +[[example]] +name = "bundle" +path = "examples/bundle/main.rs" + +[[example]] +name = "create_log_entry" +path = "examples/rekor/create_log_entry/main.rs" + +[[example]] +name = "fulcio_cert" +path = "examples/fulcio/cert/main.rs" + +[[example]] +name = "get_log_entry_by_index" +path = "examples/rekor/get_log_entry_by_index/main.rs" + +[[example]] +name = "get_log_entry_by_uuid" +path = "examples/rekor/get_log_entry_by_uuid/main.rs" + +[[example]] +name = "get_log_info" +path = "examples/rekor/get_log_info/main.rs" + +[[example]] +name = "get_log_proof" +path = "examples/rekor/get_log_proof/main.rs" + +[[example]] +name = "get_public_key" +path = "examples/rekor/get_public_key/main.rs" + +[[example]] +name = "key_pair_gen_and_export" +path = "examples/key_interface/key_pair_gen_and_export/main.rs" + +[[example]] +name = "key_pair_gen_sign_verify" +path = "examples/key_interface/key_pair_gen_sign_verify/main.rs" + +[[example]] +name = "key_pair_import" +path = "examples/key_interface/key_pair_import/main.rs" + +[[example]] +name = "openidconnect" +path = "examples/openidflow/openidconnect/main.rs" + +[[example]] +name = "search_index" +path = "examples/rekor/search_index/main.rs" + +[[example]] +name = "search_log_query" +path = "examples/rekor/search_log_query/main.rs" + +[[example]] +name = "sign" +path = "examples/cosign/sign/main.rs" + +[[example]] +name = "verify" +path = "examples/cosign/verify/main.rs" + +[[example]] +name = "verify-blob" +path = "examples/cosign/verify-blob/main.rs" + +[[example]] +name = "verify-bundle" +path = "examples/cosign/verify-bundle/main.rs" + +[dependencies.async-trait] +version = "0.1" +optional = true +default-features = false + +[dependencies.base64] +version = "0.22" +features = ["std"] +default-features = false + +[dependencies.cached] +version = "0.56" +features = ["async"] +optional = true + +[dependencies.cfg-if] +version = "1.0.0" +optional = true +default-features = false + +[dependencies.chrono] +version = "0.4" +features = [ + "now", + "serde", +] +default-features = false + +[dependencies.const-oid] +version = "0.9" +features = ["db"] +default-features = false + +[dependencies.crypto_secretbox] +version = "0.1" +features = [ + "alloc", + "salsa20", +] +default-features = false + +[dependencies.digest] +version = "0.10" +default-features = false + +[dependencies.ecdsa] +version = "0.16" +features = [ + "der", + "digest", + "pkcs8", + "signing", + "std", +] +default-features = false + +[dependencies.ed25519] +version = "2.2" +features = ["alloc"] +default-features = false + +[dependencies.ed25519-dalek] +version = "2.1" +features = [ + "alloc", + "pkcs8", + "rand_core", +] +default-features = false + +[dependencies.elliptic-curve] +version = "0.13" +features = [ + "arithmetic", + "pem", + "std", +] +default-features = false + +[dependencies.futures] +version = "0.3" +optional = true +default-features = false + +[dependencies.futures-util] +version = "0.3" +optional = true +default-features = false + +[dependencies.getrandom] +version = "0.2" +optional = true +default-features = false + +[dependencies.hex] +version = "0.4" +features = ["std"] +optional = true +default-features = false + +[dependencies.json-syntax] +version = "0.12" +features = [ + "canonicalize", + "serde", +] +optional = true +default-features = false + +[dependencies.oci-client] +version = "0.15" +optional = true +default-features = false + +[dependencies.olpc-cjson] +version = "0.1" +optional = true +default-features = false + +[dependencies.openidconnect] +version = "4.0" +features = [ + "reqwest", + "reqwest-blocking", +] +optional = true +default-features = false + +[dependencies.p256] +version = "0.13" +features = [ + "arithmetic", + "ecdsa", + "pem", + "std", +] +default-features = false + +[dependencies.p384] +version = "0.13" +features = [ + "arithmetic", + "ecdsa", + "pem", + "std", +] +default-features = false + +[dependencies.pem] +version = "3.0" +features = [ + "serde", + "std", +] +default-features = false + +[dependencies.pkcs1] +version = "0.7" +features = ["std"] +default-features = false + +[dependencies.pkcs8] +version = "0.10" +features = [ + "encryption", + "pem", + "pkcs5", + "std", +] +default-features = false + +[dependencies.pki-types] +version = "1.11" +default-features = false +package = "rustls-pki-types" + +[dependencies.rand] +version = "0.8" +features = [ + "getrandom", + "std", +] +default-features = false + +[dependencies.regex] +version = "1.10" +optional = true +default-features = false + +[dependencies.reqwest] +version = "0.12" +features = [ + "json", + "multipart", +] +optional = true +default-features = false + +[dependencies.ring] +version = "0.17" +optional = true +default-features = false + +[dependencies.rsa] +version = "0.9" +features = ["std"] +default-features = false + +[dependencies.rustls-webpki] +version = "0.103" +features = ["std"] +default-features = false + +[dependencies.scrypt] +version = "0.11" +features = [ + "simple", + "std", +] +default-features = false + +[dependencies.serde] +version = "1.0" +features = ["derive"] +default-features = false + +[dependencies.serde_json] +version = "1.0" +features = ["std"] +default-features = false + +[dependencies.serde_repr] +version = "0.1" +optional = true +default-features = false + +[dependencies.serde_with] +version = "3.9" +features = [ + "base64", + "json", +] +optional = true +default-features = false + +[dependencies.sha2] +version = "0.10" +features = ["oid"] +default-features = false + +[dependencies.signature] +version = "2.2" +default-features = false + +[dependencies.sigstore_protobuf_specs] +version = "0.5" +optional = true +default-features = false + +[dependencies.thiserror] +version = "2.0" +features = ["std"] +default-features = false + +[dependencies.tls_codec] +version = "0.4" +features = ["derive"] +default-features = false + +[dependencies.tokio] +version = "1" +features = ["rt"] +default-features = false + +[dependencies.tokio-util] +version = "0.7" +features = ["io-util"] +default-features = false + +[dependencies.tough] +version = "0.21" +features = ["http"] +optional = true +default-features = false + +[dependencies.tracing] +version = "0.1" +default-features = false + +[dependencies.url] +version = "2.5" +default-features = false + +[dependencies.webbrowser] +version = "1.0" +optional = true +default-features = false + +[dependencies.x509-cert] +version = "0.2" +features = [ + "builder", + "pem", + "sct", + "std", +] +default-features = false + +[dependencies.zeroize] +version = "1.8" +default-features = false + +[dev-dependencies.anyhow] +version = "1.0" +features = [ + "backtrace", + "std", +] +default-features = false + +[dev-dependencies.assert-json-diff] +version = "2.0" +default-features = false + +[dev-dependencies.clap] +version = "4.5" +features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] +default-features = false + +[dev-dependencies.docker_credential] +version = "1.3" +default-features = false + +[dev-dependencies.openssl] +version = "0.10" +default-features = false + +[dev-dependencies.rstest] +version = "0.26" +default-features = false + +[dev-dependencies.serial_test] +version = "3.1" +default-features = false + +[dev-dependencies.tempfile] +version = "3.12" +default-features = false + +[dev-dependencies.testcontainers] +version = "0.25" +features = ["aws-lc-rs"] +default-features = false + +[dev-dependencies.tracing-subscriber] +version = "0.3" +features = [ + "ansi", + "env-filter", + "fmt", + "smallvec", + "std", + "tracing-log", +] +default-features = false + +[target.'cfg(not(target_arch = "powerpc64"))'.dependencies.aws-lc-rs] +version = "1" +optional = true + +[target.'cfg(target_arch = "powerpc64")'.dependencies.aws-lc-rs] +version = "1" +features = ["bindgen"] +optional = true diff --git a/vendor/Cargo.toml.orig b/vendor/Cargo.toml.orig new file mode 100644 index 0000000..9925928 --- /dev/null +++ b/vendor/Cargo.toml.orig @@ -0,0 +1,320 @@ +[package] +authors = ["sigstore-rs developers"] +description = "An experimental crate to interact with sigstore" +edition = "2024" +license = "Apache-2.0" +name = "sigstore" +readme = "README.md" +repository = "https://github.com/sigstore/sigstore-rs" +version = "0.13.0" + +[package.metadata.docs.rs] +all-features = true + +[features] +default = ["full", "native-tls"] + +bundle = ["sign", "verify"] +cached-client = ["dep:cached"] +cert = ["dep:aws-lc-rs", "rustls-webpki/aws-lc-rs"] +cosign = [ + "cert", + "dep:async-trait", + "dep:cfg-if", + "dep:oci-client", + "dep:olpc-cjson", + "dep:regex", + "registry", +] +fulcio = [ + "dep:reqwest", + "dep:serde_repr", + "dep:serde_with", + "dep:webbrowser", + "oauth", +] +full = [ + "bundle", + "cached-client", + "cosign", + "fulcio", + "mock-client", + "rekor", + "sigstore-trust-root", +] +mock-client = ["dep:async-trait", "dep:oci-client"] +native-tls = [ + "oci-client?/native-tls", + "openidconnect?/native-tls", + "reqwest?/native-tls", +] +oauth = ["dep:openidconnect", "dep:reqwest"] +registry = ["dep:async-trait", "dep:oci-client", "dep:olpc-cjson"] +rekor = ["dep:reqwest"] +rustls-tls = [ + "oci-client?/rustls-tls", + "openidconnect?/rustls-tls", + "reqwest?/rustls-tls", +] +# This feature flag is used to allow using the platform's native certificate store +# when using rustls suites +rustls-tls-native-roots = ["oci-client?/rustls-tls-native-roots"] +sign = [ + "cert", + "dep:hex", + "dep:json-syntax", + "dep:sigstore_protobuf_specs", + "fulcio", + "rekor", +] +sigstore-trust-root = [ + "dep:futures", + "dep:futures-util", + "dep:hex", + "dep:reqwest", + "dep:sigstore_protobuf_specs", + "dep:tough", + "tokio/sync", +] +test-registry = [] # used for testing against a test registry +verify = [ + "cert", + "dep:hex", + "dep:json-syntax", + "dep:sigstore_protobuf_specs", + "fulcio", + "rekor", +] +wasm = ["chrono/wasmbind", "getrandom/js", "ring?/wasm32_unknown_unknown_js"] + +[dependencies] +async-trait = { version = "0.1", optional = true, default-features = false } +base64 = { version = "0.22", default-features = false, features = ["std"] } +cached = { version = "0.56", optional = true, features = ["async"] } +cfg-if = { version = "1.0.0", optional = true, default-features = false } +chrono = { version = "0.4", default-features = false, features = [ + "now", + "serde", +] } +const-oid = { version = "0.9", default-features = false, features = ["db"] } +crypto_secretbox = { version = "0.1", default-features = false, features = [ + "alloc", + "salsa20", +] } +digest = { version = "0.10", default-features = false } +ecdsa = { version = "0.16", default-features = false, features = [ + "der", + "digest", + "pkcs8", + "signing", + "std", +] } +ed25519 = { version = "2.2", default-features = false, features = ["alloc"] } +ed25519-dalek = { version = "2.1", default-features = false, features = [ + "alloc", + "pkcs8", + "rand_core", +] } +elliptic-curve = { version = "0.13", default-features = false, features = [ + "arithmetic", + "pem", + "std", +] } +futures = { version = "0.3", default-features = false, optional = true } +futures-util = { version = "0.3", default-features = false, optional = true } +getrandom = { version = "0.2", default-features = false, optional = true } +hex = { version = "0.4", default-features = false, optional = true, features = [ + "std", +] } +json-syntax = { version = "0.12", default-features = false, features = [ + "canonicalize", + "serde", +], optional = true } +oci-client = { version = "0.15", default-features = false, optional = true } +olpc-cjson = { version = "0.1", default-features = false, optional = true } +openidconnect = { version = "4.0", default-features = false, features = [ + "reqwest", + "reqwest-blocking", +], optional = true } +p256 = { version = "0.13", default-features = false, features = [ + "arithmetic", + "ecdsa", + "pem", + "std", +] } +p384 = { version = "0.13", default-features = false, features = [ + "arithmetic", + "ecdsa", + "pem", + "std", +] } +pem = { version = "3.0", default-features = false, features = ["serde", "std"] } +pkcs1 = { version = "0.7", default-features = false, features = ["std"] } +pkcs8 = { version = "0.10", default-features = false, features = [ + "encryption", + "pem", + "pkcs5", + "std", +] } +pki-types = { package = "rustls-pki-types", version = "1.11", default-features = false } +rand = { version = "0.8", default-features = false, features = [ + "getrandom", + "std", +] } +regex = { version = "1.10", default-features = false, optional = true } +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "multipart", +], optional = true } +ring = { version = "0.17", default-features = false, optional = true } +rsa = { version = "0.9", default-features = false, features = ["std"] } +rustls-webpki = { version = "0.103", default-features = false, features = [ + "std", +] } +scrypt = { version = "0.11", default-features = false, features = [ + "simple", + "std", +] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false, features = ["std"] } +serde_repr = { version = "0.1", default-features = false, optional = true } +serde_with = { version = "3.9", default-features = false, features = [ + "base64", + "json", +], optional = true } +sha2 = { version = "0.10", default-features = false, features = ["oid"] } +signature = { version = "2.2", default-features = false } +sigstore_protobuf_specs = { version = "0.5", default-features = false, optional = true } +thiserror = { version = "2.0", default-features = false, features = ["std"] } +tls_codec = { version = "0.4", default-features = false, features = ["derive"] } +tokio = { version = "1", default-features = false, features = ["rt"] } +tokio-util = { version = "0.7", default-features = false, features = [ + "io-util", +] } +tough = { version = "0.21", default-features = false, features = [ + "http", +], optional = true } +tracing = { version = "0.1", default-features = false } +url = { version = "2.5", default-features = false } +webbrowser = { version = "1.0", default-features = false, optional = true } +x509-cert = { version = "0.2", default-features = false, features = [ + "builder", + "pem", + "sct", + "std", +] } +zeroize = { version = "1.8", default-features = false } + +[dev-dependencies] +anyhow = { version = "1.0", default-features = false, features = [ + "backtrace", + "std", +] } +assert-json-diff = { version = "2.0", default-features = false } +clap = { version = "4.5", default-features = false, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +docker_credential = { version = "1.3", default-features = false } +openssl = { version = "0.10", default-features = false } +rstest = { version = "0.26", default-features = false } +serial_test = { version = "3.1", default-features = false } +tempfile = { version = "3.12", default-features = false } +testcontainers = { version = "0.25", default-features = false, features = [ + "aws-lc-rs", +] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "ansi", + "env-filter", + "fmt", + "smallvec", + "std", + "tracing-log", +] } + +[target.'cfg(not(target_arch = "powerpc64"))'.dependencies] +aws-lc-rs = { version = "1", optional = true } + +[target.'cfg(target_arch = "powerpc64")'.dependencies] +aws-lc-rs = { version = "1", optional = true, features = ["bindgen"] } + +# cosign example mappings + +[[example]] +name = "verify" +path = "examples/cosign/verify/main.rs" + +[[example]] +name = "verify-blob" +path = "examples/cosign/verify-blob/main.rs" + +[[example]] +name = "verify-bundle" +path = "examples/cosign/verify-bundle/main.rs" + +[[example]] +name = "sign" +path = "examples/cosign/sign/main.rs" + +# openidconnect example mappings + +[[example]] +name = "openidconnect" +path = "examples/openidflow/openidconnect/main.rs" + +# key interface mappings + +[[example]] +name = "key_pair_gen_sign_verify" +path = "examples/key_interface/key_pair_gen_sign_verify/main.rs" + +[[example]] +name = "key_pair_gen_and_export" +path = "examples/key_interface/key_pair_gen_and_export/main.rs" + +[[example]] +name = "key_pair_import" +path = "examples/key_interface/key_pair_import/main.rs" + +# rekor example mappings + +[[example]] +name = "create_log_entry" +path = "examples/rekor/create_log_entry/main.rs" + +[[example]] +name = "get_log_entry_by_index" +path = "examples/rekor/get_log_entry_by_index/main.rs" + +[[example]] +name = "get_log_entry_by_uuid" +path = "examples/rekor/get_log_entry_by_uuid/main.rs" + +[[example]] +name = "get_log_info" +path = "examples/rekor/get_log_info/main.rs" + +[[example]] +name = "get_log_proof" +path = "examples/rekor/get_log_proof/main.rs" + +[[example]] +name = "get_public_key" +path = "examples/rekor/get_public_key/main.rs" + +[[example]] +name = "search_index" +path = "examples/rekor/search_index/main.rs" + +[[example]] +name = "search_log_query" +path = "examples/rekor/search_log_query/main.rs" + +[[example]] +name = "fulcio_cert" +path = "examples/fulcio/cert/main.rs" diff --git a/vendor/LICENSE b/vendor/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/vendor/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/vendor/Makefile b/vendor/Makefile new file mode 100644 index 0000000..38efe30 --- /dev/null +++ b/vendor/Makefile @@ -0,0 +1,41 @@ +.PHONY: build +build: + cargo build --release + +.PHONY: fmt +fmt: + cargo fmt --all -- --check + taplo fmt --check + +.PHONY: lint +lint: + cargo clippy --all-targets -- -D warnings + +.PHONY: doc +doc: + RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc --all-features --no-deps + +.PHONY: check-features +check-features: + cargo hack check --each-feature --skip cosign --skip full --skip mock-client --skip registry + +.PHONY: check-features-native-tls +check-features-native-tls: + cargo hack check --feature-powerset --features native-tls --skip wasm --skip test-registry --skip rustls-tls --skip rustls-tls-native-roots + +.PHONY: check-features-rustls-tls +check-features-rustls-tls: + cargo hack check --feature-powerset --features rustls-tls --skip wasm --skip test-registry --skip native-tls --skip rustls-tls-native-roots + +.PHONY: test +test: fmt lint doc + cargo test --workspace --no-default-features --features full,native-tls,test-registry + cargo test --workspace --no-default-features --features full,rustls-tls,test-registry + +.PHONY: clean +clean: + cargo clean + +.PHONY: coverage +coverage: + cargo tarpaulin -o Html diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 0000000..7c87ffe --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,85 @@ +Continuous integration | Docs | License | Crate version | Crate downloads + ----------------------|------|---------|---------------|----------------- + [![Continuous integration](https://github.com/sigstore/sigstore-rs/actions/workflows/tests.yml/badge.svg)](https://github.com/sigstore/sigstore-rs/actions/workflows/tests.yml) | [![Docs](https://img.shields.io/badge/docs-%20-blue)](https://docs.rs/sigstore/latest/sigstore) | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache2.0-brightgreen.svg)](https://opensource.org/licenses/Apache-2.0) | [![Crate version](https://img.shields.io/crates/v/sigstore?style=flat-square)](https://crates.io/crates/sigstore) | [![Crate downloads](https://img.shields.io/crates/d/sigstore?style=flat-square)](https://crates.io/crates/sigstore) + + +A crate to interact with [sigstore](https://sigstore.dev/). + +This crate is under active development and will not be considered +stable until the 1.0 release. + +## Features + +### Cosign Sign and Verify + +The crate implements the following verification mechanisms: + + * Sign using a cosign key and store the signature in a registry + * Verify using a given key + * Verify bundle produced by transparency log (Rekor) + * Verify signature produced in keyless mode, using Fulcio Web-PKI + +Signature annotations and certificate email can be provided at verification time. + +### Fulcio Integration + +For use with Fulcio ephemeral key signing, an OpenID connect API is available, +along with a fulcio client implementation. + +### Rekor Client + +All rekor client APIs can be leveraged to interact with the transparency log. + +### Key Interface + +Cryptographic key management with the following key interfaces: + +* Generate a key pair +* Sign data +* Verify signature +* Export public / (encrypted) private key in PEM / DER format +* Import public / (encrypted) private key in PEM / DER format + +#### Known limitations + +* The crate does not handle verification of attestations yet. + +## Examples + +The `examples` directory contains demo programs using the library. + + * [`openidflow`](examples/openidflow/README.md) + * [`key_interface`](examples/key_interface/README.md) + * [`rekor`](examples/rekor/README.md) + * [`cosign/verify`](examples/cosign/verify/README.md) + * [`cosign/verify-blob`](examples/cosign/verify-blob/README.md) + * [`cosign/verify-bundle`](examples/cosign/verify-bundle/README.md) + * [`cosign/sign`](examples/cosign/sign/README.md) + +Each example can be executed with the `cargo run --example ` command. + +For example, `openidconnect` can be run with the following command: + +```bash +cargo run --example openidconnect +``` + +## WebAssembly/WASM support + +To embedded this crate in WASM modules, build it using the `wasm` cargo feature: + +```bash +cargo build --no-default-features --features wasm --target wasm32-unknown-unknown +``` + +NOTE: The wasm32-wasi target architecture is not yet supported. + +## Contributing + +Contributions are welcome! Please see the [contributing guidelines](CONTRIBUTORS.md) +for more information. + +## Security + +Should you discover any security issues, please refer to sigstores [security +process](https://github.com/sigstore/community/security/policy) diff --git a/vendor/clippy.toml b/vendor/clippy.toml new file mode 100644 index 0000000..1e56804 --- /dev/null +++ b/vendor/clippy.toml @@ -0,0 +1,2 @@ +allow-panic-in-tests = true +allow-unwrap-in-tests = true diff --git a/vendor/examples/README.md b/vendor/examples/README.md new file mode 100644 index 0000000..63450b8 --- /dev/null +++ b/vendor/examples/README.md @@ -0,0 +1,15 @@ +# sigstore-rs code examples + +This folder contains executable examples of the sigstore-rs library. + +To run any given example, simply provide the subfolder name as an argument to the `cargo run` command. + +```bash +cargo run --example --all-features +``` + +e.g. + +```bash +cargo run --example create_log_entry --all-features +``` diff --git a/vendor/examples/bundle/README.md b/vendor/examples/bundle/README.md new file mode 100644 index 0000000..802b809 --- /dev/null +++ b/vendor/examples/bundle/README.md @@ -0,0 +1,29 @@ +This example shows how to sign and verify Sigstore signature bundles. The bundle +format used here is supported by most Sigstore clients but notably cosign requires +`--new-bundle-format` to do so. + +This example uses `sigstore::bundle` for signing and verification. The sign subcommand uses +`sigstore::oauth` for interactive OIDC authorization. In addition to the bundle format, a +notable difference compared to the "cosign" examples is that `sigstore::bundle` also handles +the Sigstore trust root update before signing or verifying. + +### Sign README.md + +```console +cargo run --example bundle \ + sign README.md +``` + +A browser window will be opened to authorize signing with an OIDC identity. +After the authorization the signature bundle is created in `README.md.sigstore.json`. + +### Verify README.md using the signature bundle + +```console +cargo run --example bundle \ + verify --identity --issuer README.md +```console + +`EMAIL` is the email address of the OIDC account and is the OIDC issuer URI that were used +during signing. As an example `cargo run --example bundle verify --identity name@example.com --issuer https://github.com/login/oauth README.md` +verifies that the bundle `README.md.sigstore.json` was signed by "name@example.com" as authenticated by GitHub. diff --git a/vendor/examples/bundle/main.rs b/vendor/examples/bundle/main.rs new file mode 100644 index 0000000..53b8031 --- /dev/null +++ b/vendor/examples/bundle/main.rs @@ -0,0 +1,163 @@ +use clap::{Parser, Subcommand}; +use std::fs; +use std::path::PathBuf; +use tracing::debug; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{EnvFilter, fmt}; + +use sigstore::bundle::sign::SigningContext; +use sigstore::bundle::verify::{blocking::Verifier, policy}; +use sigstore::oauth; + +#[derive(Parser, Debug)] +#[clap(about = "Signing and verification example for sigstore::bundle module")] +struct Cli { + /// Enable verbose mode + #[arg(short, long)] + verbose: bool, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Verify a signature for an artifact + Verify(VerifyArgs), + /// Create a signature for an artifact + Sign(SignArgs), +} + +#[derive(Parser, Debug)] +struct SignArgs { + /// Path to the artifact to sign + artifact: PathBuf, +} + +#[derive(Parser, Debug)] +struct VerifyArgs { + /// Path to the artifact to verify + artifact: PathBuf, + + /// expected signing identity (email) + #[arg(long, value_name = "EMAIL")] + identity: String, + + /// expected signing identity issuer (URI) + #[arg(long, value_name = "URI")] + issuer: String, +} + +pub fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + match cli.command { + Commands::Sign(args) => sign(&args.artifact), + Commands::Verify(args) => verify(&args.artifact, &args.identity, &args.issuer), + } +} + +fn sign(artifact_path: &PathBuf) { + let filename = artifact_path + .file_name() + .and_then(|s| s.to_str()) + .expect("Failed to parse artifact filename"); + let mut artifact = fs::File::open(artifact_path) + .unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); + + let mut bundle_path = artifact_path.clone(); + bundle_path.set_file_name(format!("{}.sigstore.json", filename)); + let bundle = fs::File::create_new(&bundle_path).unwrap_or_else(|e| { + println!( + "Failed to create signature bundle {}: {}", + bundle_path.display(), + e + ); + std::process::exit(1); + }); + + let token = authorize(); + let email = &token.unverified_claims().email.clone(); + debug!("Signing with {}", email); + + let signing_artifact = SigningContext::production().and_then(|ctx| { + ctx.blocking_signer(token) + .and_then(|session| session.sign(&mut artifact)) + }); + + match signing_artifact { + Ok(signing_artifact) => { + serde_json::to_writer(bundle, &signing_artifact.to_bundle()) + .expect("Failed to write bundle to file"); + } + Err(e) => { + panic!("Failed to sign: {}", e); + } + } + println!( + "Created signature bundle {} with identity {}", + bundle_path.display(), + email + ); +} + +fn verify(artifact_path: &PathBuf, identity: &str, issuer: &str) { + let filename = artifact_path + .file_name() + .and_then(|s| s.to_str()) + .expect("Failed to parse artifact filename"); + let mut bundle_path = artifact_path.clone(); + bundle_path.set_file_name(format!("{}.sigstore.json", filename)); + + let bundle = fs::File::open(&bundle_path) + .unwrap_or_else(|_| panic!("Failed to open signature bundle {}", &bundle_path.display())); + let mut artifact = fs::File::open(artifact_path) + .unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); + + let bundle: sigstore::bundle::Bundle = + serde_json::from_reader(bundle).expect("Failed to parse the bundle"); + let verifier = Verifier::production().expect("Failed to create a verifier"); + + debug!("Verifying with {} (issuer {})", identity, issuer); + let id_policy = policy::Identity::new(identity, issuer); + + if let Err(e) = verifier.verify(&mut artifact, bundle, &id_policy, true) { + println!("Failed to verify: {}", e); + std::process::exit(1); + } + println!("Verified") +} + +fn authorize() -> oauth::IdentityToken { + let oidc_url = oauth::openidflow::OpenIDAuthorize::new( + "sigstore", + "", + "https://oauth2.sigstore.dev/auth", + "http://localhost:8080", + ) + .auth_url() + .expect("Failed to start OIDC authorization"); + + webbrowser::open(oidc_url.0.as_ref()).expect("Failed to open browser"); + + println!("Please authorize signing in web browser."); + + let listener = oauth::openidflow::RedirectListener::new( + "127.0.0.1:8080", + oidc_url.1, // client + oidc_url.2, // nonce + oidc_url.3, // pkce_verifier + ); + let (_, token) = listener + .redirect_listener() + .expect("Failed to receive a token"); + oauth::IdentityToken::from(token) +} diff --git a/vendor/examples/cosign/sign/README.md b/vendor/examples/cosign/sign/README.md new file mode 100644 index 0000000..b714424 --- /dev/null +++ b/vendor/examples/cosign/sign/README.md @@ -0,0 +1,59 @@ +This is a simple example program that shows how perform cosign signing. + +The program allows also to use annotation, in the same way as `cosign sign -a key=value` +does. + +The program prints to the standard output all the Simple Signing objects that +have been successfully pushed. + +# Key based Signing + +The implementation is in [main.rs](./main.rs). + +Create a keypair using the official cosign client: + +```console +cosign generate-key-pair +``` + +Because the default key pair generated by cosign is `ECDSA_P256` key, +so we choose to use `ECDSA_P256_SHA256_ASN1` as the signing scheme. +Suppose the password used to encrypt the private key is `123`, and the target +image to be signed is `172.17.0.2:5000/ubuntu` + +Also, let us the annotation `a=1`. + +Sign a container image: + +```console +cargo run --example sign \ + --all-features \ + -- \ + --key cosign.key \ + --image 172.17.0.2:5000/ubuntu \ + --signing-scheme ECDSA_P256_SHA256_ASN1 \ + --password 123 \ + --verbose \ + --http \ + --annotations a=1 +``` + +Then the image will be signed. + +Let us then verify it. + +1. Using `cosign` (golang version) +```console +cosign verify --key cosign.pub \ + -a a=1 \ + 172.17.0.2:5000/ubuntu +``` + +2. Or use `sigstore-rs` +```console +cargo run --example verify -- \ + --key cosign.pub \ + --annotations a=1 \ + --http \ + 172.17.0.2:5000/ubuntu +``` \ No newline at end of file diff --git a/vendor/examples/cosign/sign/main.rs b/vendor/examples/cosign/sign/main.rs new file mode 100644 index 0000000..ca48469 --- /dev/null +++ b/vendor/examples/cosign/sign/main.rs @@ -0,0 +1,182 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use docker_credential::{CredentialRetrievalError, DockerCredential}; +use sigstore::cosign::constraint::{AnnotationMarker, PrivateKeySigner}; +use sigstore::cosign::{Constraint, CosignCapabilities, SignatureLayer}; +use sigstore::crypto::SigningScheme; +use sigstore::registry::{Auth, ClientConfig, ClientProtocol, OciReference}; +use tracing::{debug, warn}; +use zeroize::Zeroizing; + +extern crate anyhow; +use anyhow::anyhow; + +extern crate clap; +use clap::Parser; + +use std::{collections::HashMap, fs}; + +extern crate tracing_subscriber; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{EnvFilter, fmt}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Verification key + #[clap(short, long, required(false))] + key: String, + + /// Signing scheme when signing and verifying + #[clap(long, required(false))] + signing_scheme: Option, + + /// Password used to decrypt private key + #[clap(long, required(false))] + password: Option, + + /// Annotations that have to be satisfied + #[clap(short, long, required(false))] + annotations: Vec, + + /// Enable verbose mode + #[clap(short, long)] + verbose: bool, + + /// Name of the image to verify + #[clap(short, long)] + image: OciReference, + + /// Whether the registry uses HTTP + #[clap(long)] + http: bool, +} + +async fn run_app(cli: &Cli) -> anyhow::Result<()> { + let auth = &sigstore::registry::Auth::Anonymous; + + let mut oci_client_config = ClientConfig::default(); + match cli.http { + false => oci_client_config.protocol = ClientProtocol::Https, + true => oci_client_config.protocol = ClientProtocol::Http, + } + + let client_builder = + sigstore::cosign::ClientBuilder::default().with_oci_client_config(oci_client_config); + let mut client = client_builder.build()?; + + let image = &cli.image; + + let (cosign_signature_image, source_image_digest) = client.triangulate(image, auth).await?; + debug!(cosign_signature_image= ?cosign_signature_image, source_image_digest= ?source_image_digest); + + let mut signature_layer = SignatureLayer::new_unsigned(image, &source_image_digest)?; + + let auth = build_auth(&cosign_signature_image); + debug!(auth = ?auth, "use auth"); + + if !cli.annotations.is_empty() { + let mut values: HashMap = HashMap::new(); + for annotation in &cli.annotations { + let tmp: Vec<_> = annotation.splitn(2, '=').collect(); + if tmp.len() == 2 { + values.insert(String::from(tmp[0]), String::from(tmp[1])); + } + } + if !values.is_empty() { + let annotations_marker = AnnotationMarker { + annotations: values, + }; + annotations_marker + .add_constraint(&mut signature_layer) + .expect("add annotations failed"); + } + } + + let key = Zeroizing::new(fs::read(&cli.key).map_err(|e| anyhow!("Cannot read key: {:?}", e))?); + + let signing_scheme = if let Some(ss) = &cli.signing_scheme { + &ss[..] + } else { + "ECDSA_P256_SHA256_ASN1" + }; + let signing_scheme = SigningScheme::try_from(signing_scheme).map_err(anyhow::Error::msg)?; + let password = Zeroizing::new(cli.password.clone().unwrap_or_default().as_bytes().to_vec()); + + let signer = PrivateKeySigner::new_with_raw(key, password, &signing_scheme) + .map_err(|e| anyhow!("Cannot create private key signer: {}", e))?; + + signer + .add_constraint(&mut signature_layer) + .expect("sign image failed"); + + // Suppose there is only one SignatureLayer in the cosign image + client + .push_signature(None, &auth, &cosign_signature_image, vec![signature_layer]) + .await?; + Ok(()) +} + +/// This function helps to get the auth of the given image reference. +/// Now only `UsernamePassword` and `Anonymous` is supported. If an +/// `IdentityToken` is found, this function will return an `Anonymous` +/// auth. +/// +/// Any error will return an `Anonymous`. +fn build_auth(reference: &OciReference) -> Auth { + let server = reference + .resolve_registry() + .strip_suffix('/') + .unwrap_or_else(|| reference.resolve_registry()); + match docker_credential::get_credential(server) { + Err(CredentialRetrievalError::ConfigNotFound) => Auth::Anonymous, + Err(CredentialRetrievalError::NoCredentialConfigured) => Auth::Anonymous, + Err(e) => { + warn!("Error handling docker configuration file: {}", e); + Auth::Anonymous + } + Ok(DockerCredential::UsernamePassword(username, password)) => { + debug!("Found docker credentials"); + Auth::Basic(username, password) + } + Ok(DockerCredential::IdentityToken(_)) => { + warn!( + "Cannot use contents of docker config, identity token not supported. Using anonymous auth" + ); + Auth::Anonymous + } + } +} + +#[tokio::main] +pub async fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + match run_app(&cli).await { + Ok(_) => println!("Costraints successfully applied"), + Err(err) => { + eprintln!("Image signing failed: {:?}", err); + } + } +} diff --git a/vendor/examples/cosign/verify-blob/.gitignore b/vendor/examples/cosign/verify-blob/.gitignore new file mode 100644 index 0000000..9cf68f4 --- /dev/null +++ b/vendor/examples/cosign/verify-blob/.gitignore @@ -0,0 +1,3 @@ +signature +certificate +artifact.txt diff --git a/vendor/examples/cosign/verify-blob/README.md b/vendor/examples/cosign/verify-blob/README.md new file mode 100644 index 0000000..2c49237 --- /dev/null +++ b/vendor/examples/cosign/verify-blob/README.md @@ -0,0 +1,87 @@ +This example shows how to verify a blob signature that was created by the +`cosign sign-blob` command. + +### Create the artifact to be signed. +```console +cd examples/cosign/verify-blob +echo something > artifact.txt +``` + +### Sign the artifact.txt file using cosign +``` +cosign sign-blob \ + --output-signature signature \ + --output-certificate certificate \ + artifact.txt + +Using payload from: artifact.txt +Generating ephemeral keys... +Retrieving signed certificate... + + Note that there may be personally identifiable information associated with this signed artifact. + This may include the email address associated with the account with which you authenticate. + This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later. + By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs. + +Are you sure you want to continue? (y/[N]): y +Your browser will now be opened to: +https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=o2zGqxFdnIMy2n31excKZGDd25nj9bRocuCK_oSTKDk&code_challenge_method=S256&nonce=2MxS5IYq7wviqRPvAKMeSUcQiBS&redirect_uri=http%3A%2F%2Flocalhost%3A36653%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=2MxS5NQBiv0oTvB0oU88qRbaKEk +Successfully verified SCT... +using ephemeral certificate: +-----BEGIN CERTIFICATE----- +MIICqTCCAi6gAwIBAgIUc4soYChsRq4lWUu990I7GrErO9IwCgYIKoZIzj0EAwMw +NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl +cm1lZGlhdGUwHhcNMjMwMzEzMTEzOTIwWhcNMjMwMzEzMTE0OTIwWjAAMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAE7zhgP7vhI8QzXm0nMC6wvj1c/82sRx4ozvIB +6od9xfiNofmjlDJtdG+IrObrxONhAXffZWDB2N8SmjAcHVz85qOCAU0wggFJMA4G +A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU3U2j +b80jcAEZIXWnZgIjGEJ39EcwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y +ZD8wJwYDVR0RAQH/BB0wG4EZZGFuaWVsLmJldmVuaXVzQGdtYWlsLmNvbTAsBgor +BgEEAYO/MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYoGCisG +AQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynu +jgAAAYbaxJHJAAAEAwBHMEUCIFsrHqZF6pqZotjvHvvSPxk7jdtWkAPLn55APKmj +lD72AiEA3807EnFi2HLZcoP+85fmCH5awXDX1KLPUW7kibOwKpAwCgYIKoZIzj0E +AwMDaQAwZgIxAOf6C68qm6r7Rovurc7j+JQkki8hsoWd68vC+VvSazSFMpCxrvm7 +HlrW7oMAzjlCzwIxANQWgC60eNi7QNeqlMlo/UraZz8xFho2d0Fr5fa0ZfALBE82 +I9TvCXsVua7/ERp+eQ== +-----END CERTIFICATE----- + +tlog entry created with index: 15311440 +MEYCIQDSsR/enheXGrFNLtgEVNLvLFTYPa1cWOTBZBqNYv/kQQIhALFxLx27ECqtVyM3jGedhharRngiHJ4EMdfvA6Bl3+pm +``` + +The above command will have saved two files, one containing the signature +(which can also be seen as the last line of the output above), and one which +contains the certificate. + +### Verify using sigstore-rs: +To verify the blob using this example use the following command: +```console +cd examples/cosign/verify-blob +cargo run --example verify-blob -- \ + --certificate certificate \ + --signature signature \ + artifact.txt +Verification succeeded +``` + +### Verify using cosign +To verify the blob using `cosign verify-blob` we need to specify a +`--certificate-oidc-issuer` which currently can be one of: +the following: +* https://github.com/login/oauth +* https://accounts.google.com +* https://login.microsoftonline.com + + +And we also have to specify the email address we used as the +`--certificate-identity`: +```console +cosign verify-blob \ + --cert certificate \ + --signature signature \ + --certificate-identity \ + --certificate-oidc-issuer https://github.com/login/oauth \ + artifact.txt +Verified OK +``` diff --git a/vendor/examples/cosign/verify-blob/main.rs b/vendor/examples/cosign/verify-blob/main.rs new file mode 100644 index 0000000..093df49 --- /dev/null +++ b/vendor/examples/cosign/verify-blob/main.rs @@ -0,0 +1,74 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate clap; +extern crate sigstore; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use clap::Parser; +use sigstore::cosign::CosignCapabilities; +use sigstore::cosign::client::Client; + +extern crate tracing_subscriber; +use std::fs; +use std::path::PathBuf; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{EnvFilter, fmt}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// The certificate generate from the `cosign sign-blob` command + #[clap(short, long)] + certificate: PathBuf, + + /// The signature generated from the `cosign sign-blob` command + #[clap(long, required(false))] + signature: PathBuf, + + /// The blob to verify + blob: String, + + /// Enable verbose mode + #[clap(short, long)] + verbose: bool, +} + +#[tokio::main] +pub async fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + // certificate may be PEM or "double base64 encoded PEM" (cosign). + let cert_input = fs::read_to_string(&cli.certificate).expect("error reading certificate"); + let certificate = match BASE64_STD_ENGINE.decode(cert_input.clone()) { + Ok(res) => String::from_utf8(res).expect("error stringifying PEM certificate"), + Err(_) => cert_input, + }; + + let signature = fs::read_to_string(&cli.signature).expect("error reading signature"); + let blob = fs::read(cli.blob.as_str()).expect("error reading blob file"); + + match Client::verify_blob(&certificate, signature.trim(), &blob) { + Ok(_) => println!("Verification succeeded"), + Err(e) => eprintln!("Verification failed {:?}", e), + } +} diff --git a/vendor/examples/cosign/verify-bundle/.gitignore b/vendor/examples/cosign/verify-bundle/.gitignore new file mode 100644 index 0000000..4f525ae --- /dev/null +++ b/vendor/examples/cosign/verify-bundle/.gitignore @@ -0,0 +1,2 @@ +artifact.bundle +artifact.txt diff --git a/vendor/examples/cosign/verify-bundle/README.md b/vendor/examples/cosign/verify-bundle/README.md new file mode 100644 index 0000000..db8ad27 --- /dev/null +++ b/vendor/examples/cosign/verify-bundle/README.md @@ -0,0 +1,16 @@ +This example shows how to verify a blob, using a bundle that was created by the +`cosign sign-blob` command. + +### Sign README.md file using cosign +``` +cd examples/cosign/verify-bundle +cosign sign-blob --bundle=artifact.bundle README.md +``` + +### Verify using sigstore-rs: +```console +cargo run --example verify-bundle -- \ + --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ + --bundle artifact.bundle \ + README.md +``` diff --git a/vendor/examples/cosign/verify-bundle/main.rs b/vendor/examples/cosign/verify-bundle/main.rs new file mode 100644 index 0000000..35324dd --- /dev/null +++ b/vendor/examples/cosign/verify-bundle/main.rs @@ -0,0 +1,85 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use clap::Parser; +use sigstore::cosign::CosignCapabilities; +use sigstore::cosign::bundle::SignedArtifactBundle; +use sigstore::cosign::client::Client; +use sigstore::crypto::{CosignVerificationKey, SigningScheme}; +use std::fs; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{EnvFilter, fmt}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Path to bundle file + #[clap(short, long)] + bundle: String, + + /// Path to artifact to be verified + blob: String, + + /// File containing Rekor's public key (e.g.: ~/.sigstore/root/targets/rekor.pub) + #[clap(long, required = true)] + rekor_pub_key: String, + + /// Rekor public key ID + #[clap(long, required = true)] + rekor_pub_key_id: String, + + /// Enable verbose mode + #[clap(short, long)] + verbose: bool, +} + +#[tokio::main] +pub async fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + let rekor_pub_pem = + fs::read_to_string(&cli.rekor_pub_key).expect("error reading rekor's public key"); + let rekor_pub_key = + CosignVerificationKey::from_pem(rekor_pub_pem.as_bytes(), &SigningScheme::default()) + .expect("Cannot create Rekor verification key"); + let bundle_json = fs::read_to_string(&cli.bundle).expect("error reading bundle json file"); + let blob = fs::read(cli.blob.as_str()).expect("error reading blob file"); + + let rekor_pub_keys = [(cli.rekor_pub_key_id, rekor_pub_key)] + .into_iter() + .collect(); + + let bundle = SignedArtifactBundle::new_verified(&bundle_json, &rekor_pub_keys).unwrap(); + + // certificate in bundle is double base64 encoded, remove one layer: + let cert_data = BASE64_STD_ENGINE + .decode(bundle.cert) + .expect("Error decoding base64 certificate"); + let cert = String::from_utf8(cert_data).expect("error stringifying PEM certificate"); + + match Client::verify_blob(&cert, &bundle.base64_signature, &blob) { + Ok(_) => println!("Verification succeeded"), + Err(e) => eprintln!("Verification failed: {}", e), + } +} diff --git a/vendor/examples/cosign/verify-bundle/run.sh b/vendor/examples/cosign/verify-bundle/run.sh new file mode 100755 index 0000000..7a2d1a3 --- /dev/null +++ b/vendor/examples/cosign/verify-bundle/run.sh @@ -0,0 +1,11 @@ +BLOB="README.md" +BUNDLE="artifact.bundle" + +echo -e "\nSign README.md file using sign-blob" +cosign sign-blob --bundle=$BUNDLE $BLOB + +echo -e "\nRun examples/cosign/verify-bundle" +cargo run --example verify-bundle -- \ + --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ + --bundle $BUNDLE \ + $BLOB diff --git a/vendor/examples/cosign/verify/README.md b/vendor/examples/cosign/verify/README.md new file mode 100644 index 0000000..3f83975 --- /dev/null +++ b/vendor/examples/cosign/verify/README.md @@ -0,0 +1,34 @@ +This is a simple example program that shows how perform cosign verification. + +The program allows also to use annotation, in the same way as `cosign verify -a key=value` +does. + +The program prints to the standard output all the Simple Signing objects that +have been successfully verified. + +# Key based verification + +Create a keypair using the official cosign client: + +```console +cosign generate-key-pair +``` + +Sign a container image: + +```console +cosign sign --key cosign.key registry-testing.svc.lan/busybox +``` + +Verify the image signature using the example program defined in +[main.rs](./main.rs): + +```console +cargo run --example verify \ + --all-features \ + -- \ + -k cosign.pub \ + --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ + --fulcio-cert ~/.sigstore/root/targets/fulcio.crt.pem \ + registry-testing.svc.lan/busybox +``` diff --git a/vendor/examples/cosign/verify/main.rs b/vendor/examples/cosign/verify/main.rs new file mode 100644 index 0000000..63efc70 --- /dev/null +++ b/vendor/examples/cosign/verify/main.rs @@ -0,0 +1,338 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate sigstore; +use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier; +use sigstore::cosign::verification_constraint::{ + AnnotationVerifier, CertSubjectEmailVerifier, CertSubjectUrlVerifier, CertificateVerifier, + PublicKeyVerifier, VerificationConstraintVec, +}; +use sigstore::cosign::{CosignCapabilities, SignatureLayer}; +use sigstore::crypto::SigningScheme; +use sigstore::errors::SigstoreVerifyConstraintsError; +use sigstore::registry::{ClientConfig, ClientProtocol, OciReference}; +use sigstore::trust::sigstore::SigstoreTrustRoot; +use std::time::Instant; +use std::{collections::BTreeMap, fs}; + +extern crate anyhow; +use anyhow::{Result, anyhow}; + +extern crate clap; +use clap::Parser; + +extern crate tracing_subscriber; +use tracing::{info, warn}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{EnvFilter, fmt}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Verification key + #[clap(short, long, required(false))] + key: Option, + + /// Path to verification certificate + #[clap(long, required(false))] + cert: Option, + + /// Path to certificate chain bundle file + #[clap(long, required(false))] + cert_chain: Option, + + /// Signing scheme when signing and verifying + #[clap(long, required(false))] + signing_scheme: Option, + + /// Fetch Rekor and Fulcio data from Sigstore's TUF repository" + #[clap(long)] + use_sigstore_tuf_data: bool, + + /// Rekor's public key ID (in hex format) and path to the public key, separated by the ':' symbol (e.g.: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d:~/.sigstore/root/targets/rekor.pub) + #[clap(long, required(false))] + rekor_pub_keys: Vec, + + /// File containing Fulcio's certificate (e.g.: ~/.sigstore/root/targets/fulcio.crt.pem) + #[clap(long, required(false))] + fulcio_certs: Vec, + + /// The issuer of the OIDC token used by the user to authenticate against Fulcio + #[clap(long, required(false))] + cert_issuer: Option, + + /// The email expected in a valid fulcio cert + #[clap(long, required(false))] + cert_email: Option, + + /// The URL expected in a valid fulcio cert + #[clap(long, required(false))] + cert_url: Option, + + /// Annotations that have to be satisfied + #[clap(short, long, required(false))] + annotations: Vec, + + /// Enable verbose mode + #[clap(short, long)] + verbose: bool, + + /// Enable caching of registry operations + #[clap(long)] + enable_registry_caching: bool, + + /// Number of loops to be done. Useful only for testing `enable-registry-caching` + #[clap(long, default_value = "1")] + loops: u32, + + /// Name of the image to verify + image: OciReference, + + /// Whether the registry uses HTTP + #[clap(long)] + http: bool, +} + +async fn run_app( + cli: &Cli, + frd: &dyn sigstore::trust::TrustRoot, +) -> anyhow::Result<(Vec, VerificationConstraintVec)> { + // Note well: this a limitation deliberately introduced by this example. + if cli.cert_email.is_some() && cli.cert_url.is_some() { + return Err(anyhow!( + "The 'cert-email' and 'cert-url' flags cannot be used at the same time" + )); + } + + if cli.key.is_some() && cli.cert.is_some() { + return Err(anyhow!("'key' and 'cert' cannot be used at the same time")); + } + + let auth = &sigstore::registry::Auth::Anonymous; + + let mut oci_client_config = ClientConfig::default(); + match cli.http { + false => oci_client_config.protocol = ClientProtocol::Https, + true => oci_client_config.protocol = ClientProtocol::Http, + } + + let mut client_builder = + sigstore::cosign::ClientBuilder::default().with_oci_client_config(oci_client_config); + client_builder = client_builder.with_trust_repository(frd)?; + + let cert_chain: Option> = match cli.cert_chain.as_ref() { + None => None, + Some(cert_chain_path) => Some(parse_cert_bundle(cert_chain_path)?), + }; + + if cli.enable_registry_caching { + client_builder = client_builder.enable_registry_caching(); + } + + let mut client = client_builder.build()?; + + // Build verification constraints + let mut verification_constraints: VerificationConstraintVec = Vec::new(); + if let Some(cert_email) = cli.cert_email.as_ref() { + let issuer = cli + .cert_issuer + .as_ref() + .map(|i| StringVerifier::ExactMatch(i.to_string())); + + verification_constraints.push(Box::new(CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(cert_email.to_string()), + issuer, + })); + } + if let Some(cert_url) = cli.cert_url.as_ref() { + let issuer = cli.cert_issuer.as_ref().map(|i| i.to_string()); + if issuer.is_none() { + return Err(anyhow!( + "'cert-issuer' is required when 'cert-url' is specified" + )); + } + + verification_constraints.push(Box::new(CertSubjectUrlVerifier { + url: cert_url.to_string(), + issuer: issuer.unwrap(), + })); + } + if let Some(path_to_key) = cli.key.as_ref() { + let key = fs::read(path_to_key).map_err(|e| anyhow!("Cannot read key: {:?}", e))?; + + let verifier = match &cli.signing_scheme { + Some(scheme) => { + let signing_scheme = + SigningScheme::try_from(&scheme[..]).map_err(anyhow::Error::msg)?; + PublicKeyVerifier::new(&key, &signing_scheme) + .map_err(|e| anyhow!("Cannot create public key verifier: {}", e))? + } + None => PublicKeyVerifier::try_from(&key) + .map_err(|e| anyhow!("Cannot create public key verifier: {}", e))?, + }; + + verification_constraints.push(Box::new(verifier)); + } + if let Some(path_to_cert) = cli.cert.as_ref() { + let cert = fs::read(path_to_cert).map_err(|e| anyhow!("Cannot read cert: {:?}", e))?; + let require_rekor_bundle = if !frd.rekor_keys()?.is_empty() { + true + } else { + warn!("certificate based verification is weaker when Rekor integration is disabled"); + false + }; + + let verifier = + CertificateVerifier::from_pem(&cert, require_rekor_bundle, cert_chain.as_deref()) + .map_err(|e| anyhow!("Cannot create certificate verifier: {}", e))?; + + verification_constraints.push(Box::new(verifier)); + } + + if !cli.annotations.is_empty() { + let mut values: BTreeMap = BTreeMap::new(); + for annotation in &cli.annotations { + let tmp: Vec<_> = annotation.splitn(2, '=').collect(); + if tmp.len() == 2 { + values.insert(String::from(tmp[0]), String::from(tmp[1])); + } + } + if !values.is_empty() { + let annotations_verifier = AnnotationVerifier { + annotations: values, + }; + verification_constraints.push(Box::new(annotations_verifier)); + } + } + + let image = &cli.image; + + let (cosign_signature_image, source_image_digest) = client.triangulate(image, auth).await?; + + let trusted_layers = client + .trusted_signature_layers(auth, &source_image_digest, &cosign_signature_image) + .await?; + + Ok((trusted_layers, verification_constraints)) +} + +async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result> { + if cli.use_sigstore_tuf_data { + info!("Downloading data from Sigstore TUF repository"); + + let trust_root: sigstore::errors::Result = + SigstoreTrustRoot::new(None).await; + + return Ok(Box::new(trust_root?)); + }; + + let mut trust_root = sigstore::trust::ManualTrustRoot::default(); + for id_and_path in cli.rekor_pub_keys.iter() { + let (id, path) = id_and_path + .split_once(':') + .ok_or_else(|| anyhow!("Invalid format for rekor public key"))?; + trust_root.rekor_keys.insert( + id.to_string(), + fs::read(path) + .map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e))?, + ); + } + + for path in cli.fulcio_certs.iter() { + let cert_data = fs::read(path) + .map_err(|e| anyhow!("Error reading fulcio certificate from disk: {}", e))?; + + let certificate = sigstore::registry::Certificate { + encoding: sigstore::registry::CertificateEncoding::Pem, + data: cert_data, + }; + trust_root.fulcio_certs.push(certificate.try_into()?); + } + + Ok(Box::new(trust_root)) +} + +#[tokio::main] +pub async fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + let frd = match fulcio_and_rekor_data(&cli).await { + Ok(sr) => sr, + Err(e) => { + eprintln!("Cannot build sigstore repo data: {}", e); + std::process::exit(1); + } + }; + + for n in 0..(cli.loops) { + let now = Instant::now(); + if cli.loops != 1 { + println!("Loop {}/{}", n + 1, cli.loops); + } + + match run_app(&cli, frd.as_ref()).await { + Ok((trusted_layers, verification_constraints)) => { + let filter_result = sigstore::cosign::verify_constraints( + &trusted_layers, + verification_constraints.iter(), + ); + + match filter_result { + Ok(()) => { + println!("Image successfully verified"); + } + Err(SigstoreVerifyConstraintsError { + unsatisfied_constraints, + }) => { + eprintln!("Image verification failed: not all constraints satisfied."); + eprintln!("{:?}", unsatisfied_constraints); + } + } + } + Err(err) => { + eprintln!("Image verification failed: {:?}", err); + } + } + + let elapsed = now.elapsed(); + + if cli.loops != 1 { + println!("Elapsed: {:.2?}", elapsed); + println!("------"); + } + } +} + +fn parse_cert_bundle(bundle_path: &str) -> Result> { + let data = + fs::read(bundle_path).map_err(|e| anyhow!("Error reading {}: {}", bundle_path, e))?; + let pems = pem::parse_many(data)?; + + Ok(pems + .iter() + .map(|pem| sigstore::registry::Certificate { + encoding: sigstore::registry::CertificateEncoding::Der, + data: pem.contents().to_vec(), + }) + .collect()) +} diff --git a/vendor/examples/fulcio/cert/main.rs b/vendor/examples/fulcio/cert/main.rs new file mode 100644 index 0000000..bf71576 --- /dev/null +++ b/vendor/examples/fulcio/cert/main.rs @@ -0,0 +1,37 @@ +use pkcs8::der::Decode; +use sigstore::crypto::SigningScheme; +use sigstore::fulcio::oauth::OauthTokenProvider; +use sigstore::fulcio::{FULCIO_ROOT, FulcioClient, TokenProvider}; +use url::Url; +use x509_cert::Certificate; +use x509_cert::ext::pkix::SubjectAltName; + +#[tokio::main] +async fn main() { + let fulcio = FulcioClient::new( + Url::parse(FULCIO_ROOT).unwrap(), + TokenProvider::Oauth(OauthTokenProvider::default()), + ); + + if let Ok((_signer, cert)) = fulcio + .request_cert(SigningScheme::ECDSA_P256_SHA256_ASN1) + .await + { + println!("Received certificate chain"); + + let pems = pem::parse_many(cert.as_ref()).expect("parse pem failed"); + for pem in &pems { + let cert = Certificate::from_der(pem.contents()).expect("parse certificate from der"); + + let (_, san) = cert + .tbs_certificate + .get::() + .expect("get SAN failed") + .expect("No SAN found"); + + for name in &san.0 { + println!("SAN: {name:?}"); + } + } + } +} diff --git a/vendor/examples/key_interface/README.md b/vendor/examples/key_interface/README.md new file mode 100644 index 0000000..080adc8 --- /dev/null +++ b/vendor/examples/key_interface/README.md @@ -0,0 +1,113 @@ +# Example Key Interface + +This is a simple example program that shows how to use the key interfaces. +The key interfaces covers: +* Generating Asymmetric encryption key pair +* Signing with private key +* Exporting the (encrypted) private/public key +* Importing the (encrypted) private/public key +* Verifying signature with public key + +The basic implementation for key-interface can be shown in the following diagram + +![key_interface](key_interface.drawio.svg) + +The exposed interfaces (marked as `pub`) include: +* `SigStoreSigner` enum: wrapper for `Signer`s of different kinds of signing algorithm. +* `SigStoreKeyPair` enum: wrapper for `KeyPair`s of different kinds of asymmetric encryption algorithm. +* `SigningScheme` enum: Different kinds of signing algorithm. +* `CosignVerificationKey` struct: Public key types to verify signatures for different signing algorithm. + +To show the different usages for them, there will be three typical scenarios. + +## Key Pair Generation, Signing and Verification + +This example shows the following operations + +* Generating Asymmetric encryption key pair due to given `SigningScheme`. +* Signing the given test data using private key. The signature will be printed +in hex. +* Verifying the signature generated. + +The signing process is performed by `SigStoreSigner`. +The verifying process is performed by `CosignVerificationKey`. + +### Run the example case + +The following example will create a ECDSA_P256_ASN1 keypair and sign the given +data. + +```bash +cargo run --example key_pair_gen_sign_verify +``` + +This example includes the following steps: + +* Randomly generate an `ECDSA_P256_ASN1` key pair, which is represented as `signer` of type +`SigStoreSigner` and includes a private key and a public key. Here, the type of the key +pair is influenced by the given `SigningScheme`. +* Sign the given data `DATA_TO_BE_SIGNED` using the `signer`'s private key. +* Derive [`verification_key`](../../src/crypto/verification_key.rs) from the `signer`. +* Verify the signature generated before using the `verification_key`. + +## Key Pair Generation and Exporting + +This example shows the following operations + +* Generating Asymmetric encryption key pair due to given `SigningScheme`. +* Export the public key in both DER and PEM format. +* Export the private key in both DER and PEM format. +* Export the encrypted private key in PEM format. + +The key-related operations are performed by `SigStoreKeyPair`. + +### Run the example case + +The following example will create a ECDSA_P256_ASN1 keypair and sign the given +data. + +```bash +cargo run --example key_pair_gen_and_export +``` + +This example includes the following steps: + +* Randomly generate an `ECDSA_P256_ASN1` key pair, which is represented as `signer` of type +`SigStoreSigner` and includes a private key and a public key. Here, the type of the key +pair is influenced by the given `SigningScheme`. +* Export the public key in PEM format and DER format. The result +will be printed (PEM as string, DER as hex). +* Export the private key in PEM format and DER format. The result +will be printed (PEM as string, DER as hex). +* Export the encrypted private key in PEM format. The result +will be printed. + +## Key Pair Importing + +This example shows the following operations + +* Import the public key in both DER and PEM format to `CosignVerificationKey`. +* Import the private key in both DER and PEM format to `SigStoreKeyPair/ECDSAKeys`. +* Import the encrypted private key in PEM format to `SigStoreKeyPair/ECDSAKeys`. +* Convert the `SigStoreKeyPair` to `SigStoreSigner`. + +### Run the example case + +The following example will create a ECDSA_P256_ASN1 keypair and sign the given +data. + +```bash +cargo run --example key_pair_import +``` + +This example includes the following steps: + +* Import the public key `ECDSA_P256_ASN1_PUBLIC_PEM.pub` as `CosignVerificationKey`. +* Import the public key `ECDSA_P256_ASN1_PUBLIC_DER.pub` as `CosignVerificationKey`. +* Import the private key `ECDSA_P256_ASN1_PRIVATE_PEM.key` as `SigStoreKeyPair`. +* Import the private key `ECDSA_P256_ASN1_PRIVATE_PEM.key` as `ECDSAKeys`. +* Import the private key `ECDSA_P256_ASN1_PRIVATE_DER.key` as `SigStoreKeyPair`. +* Import the private key `ECDSA_P256_ASN1_PRIVATE_DER.key` as `ECDSAKeys`. +* Import the encrypted private key `ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key` as `SigStoreKeyPair`. +* Import the encrypted private key `ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key` as `ECDSAKeys`. +* Convert the last `SigStoreKeyPair` to `SigStoreSigner`. diff --git a/vendor/examples/key_interface/key_interface.drawio.svg b/vendor/examples/key_interface/key_interface.drawio.svg new file mode 100644 index 0000000..b9f0389 --- /dev/null +++ b/vendor/examples/key_interface/key_interface.drawio.svg @@ -0,0 +1,4 @@ + + + +
Signature
Signature
SigStoreKeyPair(enum)
SigStoreKeyPair(enum)
Ed25519(Ed25519Keys)
Ed25519(Ed25519Keys)
Ecdsa(ECDSAKeys)
Ecdsa(ECDSAKeys)
Rsa(RSA..)
Rsa(RSA..)
to_sigstore_keypair()
to_sigstore_keypair()
SigStoreSigner(enum)
SigStoreSigner(enum)
ED25519
ED25519
ECDSA_P256_SHA256_ASN1
ECDSA_P256_SHA256_ASN1
ECDSA_P384_SHA384_ASN1
ECDSA_P384_SHA384_ASN1
RSA.....
RSA.....
CosignVerificationKey
CosignVerificationKey
ED25519
ED25519
ECDSA_P256_SHA256_ASN1
ECDSA_P256_SHA256_ASN1
ECDSA_P384_SHA384_ASN1
ECDSA_P384_SHA384_ASN1
RSA...
RSA...
Private/Public Key File
(DER/PEM)
Private/Public Key Fil...
verify()
verify()
sign()
sign()
from/to_(encrypted)pem/der
from/to_(encrypted)pem/der
generate_key()
generate_key()
SigningScheme(enum)
SigningScheme(enum)
ED25519
ED25519
ECDSA_P256_SHA256_ASN1
ECDSA_P256_SHA256_ASN1
ECDSA_P384_SHA384_ASN1
ECDSA_P384_SHA384_ASN1
RSA.....
RSA.....
from/to_(encrypted)pem/der
from/to_(encrypted)pem/der
ECDSAKeys(enum)
ECDSAKeys(enum)
P256
P256
P384
P384
wrapper
wrapper
EcdsaKeys(trait object)
EcdsaKeys(trait object)
wrapper
wrapper
to_verification_key()
to_verification_key()
from/to_(encrypted)pem/der
from/to_(encrypted)pem/der
to_sigstore_signer()
to_sigstore_signer()
Ec25519Keys(struct)
Ec25519Keys(struct)
wrapper
wrapper
to_verification_key()
to_verification_key()
to_sigstore_signer()
to_sigstore_signer()
to_verification_key()
to_verification_key()
Text is not SVG - cannot display
\ No newline at end of file diff --git a/vendor/examples/key_interface/key_pair_gen_and_export/main.rs b/vendor/examples/key_interface/key_pair_gen_and_export/main.rs new file mode 100644 index 0000000..902acd2 --- /dev/null +++ b/vendor/examples/key_interface/key_pair_gen_and_export/main.rs @@ -0,0 +1,49 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Result; +use sigstore::crypto::SigningScheme; + +const PASSWORD: &str = "example password"; + +fn main() -> Result<()> { + let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer()?; + println!("Created a new key pair for ECDSA_P256_SHA256_ASN1.\n"); + + let key_pair = signer.to_sigstore_keypair()?; + println!("Derived `SigStoreKeyPair` from the `SigStoreSigner`.\n"); + + let pub_pem = key_pair.public_key_to_pem()?; + println!("Exported the public key in PEM format."); + println!("public key:\n {}", pub_pem); + + let pub_der = key_pair.public_key_to_der()?; + println!("Exported the public key in DER format."); + println!("public key:\n {:x?}", pub_der); + + let pri_pem = key_pair.private_key_to_pem()?; + println!("Exported the private key in PEM format."); + println!("private key:\n {}", *pri_pem); + + let pri_der = key_pair.private_key_to_der()?; + println!("Exported the private key in DER format."); + println!("private key:\n {:x?}", *pri_der); + + let encrypted_pri_pem = key_pair.private_key_to_encrypted_pem(PASSWORD.as_bytes())?; + println!("Exported the encrypted private key in PEM format."); + println!("private key:\n {}", *encrypted_pri_pem); + + Ok(()) +} diff --git a/vendor/examples/key_interface/key_pair_gen_sign_verify/main.rs b/vendor/examples/key_interface/key_pair_gen_sign_verify/main.rs new file mode 100644 index 0000000..f80a95a --- /dev/null +++ b/vendor/examples/key_interface/key_pair_gen_sign_verify/main.rs @@ -0,0 +1,44 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::{Result, anyhow}; +use sigstore::crypto::{Signature, SigningScheme}; + +const DATA_TO_BE_SIGNED: &str = "this is an example data to be signed"; + +fn main() -> Result<()> { + let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer()?; + println!("Created a new key pair for ECDSA_P256_SHA256_ASN1.\n"); + + let signature_data = signer.sign(DATA_TO_BE_SIGNED.as_bytes())?; + println!("Signed the example data."); + println!("Data: {}", DATA_TO_BE_SIGNED); + println!("Signature: {:x?}\n", &signature_data); + + let verification_key = signer.to_verification_key()?; + println!("Derive verification key from the signer.\n"); + + println!("Verifying the signature of the example data..."); + match verification_key.verify_signature( + Signature::Raw(&signature_data), + DATA_TO_BE_SIGNED.as_bytes(), + ) { + Ok(_) => { + println!("Verification Succeeded."); + Ok(()) + } + Err(e) => Err(anyhow!("Verifycation failed: {}", e)), + } +} diff --git a/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key new file mode 100644 index 0000000..461dda1 --- /dev/null +++ b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiI3eWtwR1NRNVBpVWJZb1B0eXZ4cnhHWjcrM2NtZmdM +WmwwQnhDQlVHRUdZPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiI0N2dPOVNCd0FueXVOZlQxVzlzcFVYOGxCREVnckgwZyJ9LCJj +aXBoZXJ0ZXh0IjoiRHhmenJlWis5eXNLdVlHOU02L0F1dVl1bVVFa2tHcFdWUmhK +V242dGp0VW1RTzBqNTJSa1RXZTYwVTk2bFMzelVldm9BUGU2MjhPMHFQODlkdkwv +MEJObjljdWZYUVZObnAxVWJsUVBaQm9tRFRsUFR6NnZFa3doMS9XTFhTa2NKekdW +ZnpqN3ZQSDRaVFhqRjBVRnRqYzZ6QmhqZFk5ZjhnRklzVERiYSs3S013eWxPUGhW +ditpakl3R0R5Zk43RGNlWTJzYTdNZHM5Q2c9PSJ9 +-----END ENCRYPTED COSIGN PRIVATE KEY----- diff --git a/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_DER.key b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_DER.key new file mode 100644 index 0000000000000000000000000000000000000000..2156c5f51e4ad3e16cc77cc7ba768a187d2386ee GIT binary patch literal 138 zcmV;50CoQ`frkPC05B5<2P%e0&OHJF1_&yKNX|V20S5$aFlzz<0R$ib$|UA)N7O%< zQNp>?nhwTZ^|9wAdJSm5dLS`x&83aZ1khZ|;v!cWVkl+qe>yZlEFDey;ps{HpjF#rGn literal 0 HcmV?d00001 diff --git a/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_PEM.key b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_PEM.key new file mode 100644 index 0000000..2b5ed85 --- /dev/null +++ b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_PEM.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5qt7YAIL9zSg38Pi +5DX7rHjEcQAZkDk5MulVsr6x3QehRANCAASXe5tGHMHmug4BWmGl2HtIJlG8AIEV +pWZ895mqN6Yv2X6HA1n7yxjDQdqJMmFmvQm9C7Z8HGR3kbj1LQyi+DaY +-----END PRIVATE KEY----- diff --git a/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_DER.pub b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_DER.pub new file mode 100644 index 0000000000000000000000000000000000000000..37eb31b71ac65d3e08edea061c292d56b71da4e1 GIT binary patch literal 91 zcmXqrG!SNE*J|@PXUoLM#sOw9GqN)~F|eGzzQ?3V>3H(afbg`(>5*c`_cD|_ERS91 u7ykcagYu6xng*&YzpY)@Z3;`^tzK~4=kcoW6A^8hJZZN*@8mq*@C5*+PbHH8 literal 0 HcmV?d00001 diff --git a/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_PEM.pub b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_PEM.pub new file mode 100644 index 0000000..a735be1 --- /dev/null +++ b/vendor/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_PEM.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE10MJqZ6tgxCxOvANHgfKMY90bso8 +H+Iq3rPfT6GrFbYAgckw24H69hgnTHrujYAtjhK6csqLXkgwFzYh2Hdckw== +-----END PUBLIC KEY----- diff --git a/vendor/examples/key_interface/key_pair_import/main.rs b/vendor/examples/key_interface/key_pair_import/main.rs new file mode 100644 index 0000000..bef93da --- /dev/null +++ b/vendor/examples/key_interface/key_pair_import/main.rs @@ -0,0 +1,82 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::{Result, bail}; +use sigstore::crypto::{ + CosignVerificationKey, SigningScheme, + signing_key::{SigStoreKeyPair, ecdsa::ECDSAKeys}, +}; + +const PASSWORD: &str = "password"; + +const ECDSA_P256_ASN1_PUBLIC_PEM: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PUBLIC_PEM.pub"); +const ECDSA_P256_ASN1_PUBLIC_DER: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PUBLIC_DER.pub"); +const ECDSA_P256_ASN1_PRIVATE_PEM: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PRIVATE_PEM.key"); +const ECDSA_P256_ASN1_PRIVATE_DER: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PRIVATE_DER.key"); +const ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM: &[u8] = + include_bytes!("./ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key"); + +fn main() -> Result<()> { + let _ = CosignVerificationKey::from_pem(ECDSA_P256_ASN1_PUBLIC_PEM, &SigningScheme::default())?; + println!( + "Imported PEM encoded public key as CosignVerificationKey using ECDSA_P256_ASN1_PUBLIC_PEM as verification algorithm." + ); + + let _ = CosignVerificationKey::from_der(ECDSA_P256_ASN1_PUBLIC_DER, &SigningScheme::default())?; + println!( + "Imported DER encoded public key as CosignVerificationKey using ECDSA_P256_ASN1_PUBLIC_PEM as verification algorithm." + ); + + let _ = CosignVerificationKey::try_from_pem(ECDSA_P256_ASN1_PUBLIC_PEM)?; + println!("Imported PEM encoded public key as CosignVerificationKey."); + + let _ = CosignVerificationKey::try_from_der(ECDSA_P256_ASN1_PUBLIC_DER)?; + println!("Imported DER encoded public key as CosignVerificationKey."); + + let _ = SigStoreKeyPair::from_pem(ECDSA_P256_ASN1_PRIVATE_PEM)?; + println!("Imported PEM encoded private key as SigStoreKeyPair."); + + let _ = ECDSAKeys::from_pem(ECDSA_P256_ASN1_PRIVATE_PEM)?; + println!("Imported PEM encoded private key as ECDSAKeys."); + + let _ = SigStoreKeyPair::from_der(ECDSA_P256_ASN1_PRIVATE_DER)?; + println!("Imported DER encoded private key as SigStoreKeyPair."); + + let _ = ECDSAKeys::from_der(ECDSA_P256_ASN1_PRIVATE_DER)?; + println!("Imported DER encoded private key as ECDSAKeys."); + + let key_pair = SigStoreKeyPair::from_encrypted_pem( + ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM, + PASSWORD.as_bytes(), + )?; + println!("Imported encrypted PEM encoded private key as SigStoreKeyPair."); + + let ecdsa_key_pair = + ECDSAKeys::from_encrypted_pem(ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM, PASSWORD.as_bytes())?; + println!("Imported encrypted PEM encoded private key as ECDSAKeys."); + + let _ = ecdsa_key_pair.to_sigstore_signer()?; + println!("Converted ECDSAKeys to SigStoreSigner."); + + match key_pair { + SigStoreKeyPair::ECDSA(inner) => { + inner.to_sigstore_signer()?; + println!("Converted SigStoreKeyPair to SigStoreSigner."); + } + _ => bail!("Wrong key pair type."), + } + + Ok(()) +} diff --git a/vendor/examples/openidflow/README.md b/vendor/examples/openidflow/README.md new file mode 100644 index 0000000..8ed9fd5 --- /dev/null +++ b/vendor/examples/openidflow/README.md @@ -0,0 +1,19 @@ +# Open ID Connect Flow for Fulcio Signing Certificates + +This is an example of the fulcio OpenID connect flow. + +The general idea is to return an access_token and the email via a scope. + +Both values can then be made to form a POST request to fulcio for a software +signing certificate + +`cargo run --example openidconnect --all-features` + +The implementation contains a `redirect_listener` function that will create +a local listening server to incept the ID token and scopes returned from +sigstores OIDC service. However should you prefer, you can implement your +own redirect service and simply pass along the required values: + +* client: CoreClient, +* nonce: Nonce, +* pkce_verifier: PkceCodeVerifier \ No newline at end of file diff --git a/vendor/examples/openidflow/openidconnect/main.rs b/vendor/examples/openidflow/openidconnect/main.rs new file mode 100644 index 0000000..167bfc4 --- /dev/null +++ b/vendor/examples/openidflow/openidconnect/main.rs @@ -0,0 +1,62 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Result; +use sigstore::oauth; + +fn main() -> Result<(), anyhow::Error> { + let oidc_url = oauth::openidflow::OpenIDAuthorize::new( + "sigstore", + "", + "https://oauth2.sigstore.dev/auth", + "http://localhost:8080", + ) + .auth_url(); + + match oidc_url.as_ref() { + Ok(url) => { + webbrowser::open(url.0.as_ref())?; + println!( + "Open this URL in a browser if it does not automatically open for you:\n{}\n", + url.0 + ); + } + Err(e) => println!("{}", e), + } + + let oidc_url = oidc_url?; + let result = oauth::openidflow::RedirectListener::new( + "127.0.0.1:8080", + oidc_url.1, // client + oidc_url.2, // nonce + oidc_url.3, // pkce_verifier + ) + .redirect_listener(); + + match result { + Ok((token_response, id_token)) => { + println!("Email {:?}", token_response.email().unwrap()); + println!( + "Access Token:{:?}", + token_response.access_token_hash().unwrap() + ); + println!("id_token: {:?}", id_token.to_string()); + } + Err(err) => { + println!("{}", err); + } + } + anyhow::Ok(()) +} diff --git a/vendor/examples/rekor/README.md b/vendor/examples/rekor/README.md new file mode 100644 index 0000000..d672132 --- /dev/null +++ b/vendor/examples/rekor/README.md @@ -0,0 +1,4 @@ +# Rekor Transparency Log Client + +The following examples all interface with the Rekor Transparency Log Client. + diff --git a/vendor/examples/rekor/create_log_entry/main.rs b/vendor/examples/rekor/create_log_entry/main.rs new file mode 100644 index 0000000..fc8a2f2 --- /dev/null +++ b/vendor/examples/rekor/create_log_entry/main.rs @@ -0,0 +1,206 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use base64::{Engine as _, engine::general_purpose}; +use sha2::Digest; +use sha2::Sha256; +use sigstore::crypto::SigningScheme; +use sigstore::crypto::signing_key::SigStoreSigner; +use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +use sigstore::rekor::models::{ + ProposedEntry, + hashedrekord::{AlgorithmKind, Data, Hash, PublicKey, Signature, Spec}, +}; + +use clap::{Arg, Command}; + +async fn create_signer() -> SigStoreSigner { + SigningScheme::ECDSA_P256_SHA256_ASN1 + .create_signer() + .expect("cannot create sigstore signer") +} + +// function to fetch data and generate the hash of it to be signed and upload to the transparency log +async fn get_file_sha256sum(url: String) -> Result<(Vec, String), reqwest::Error> { + let body = reqwest::get(&url).await?.bytes().await?; + let mut digester = Sha256::new(); + digester.update(body.clone()); + let digest = format!("{:x}", digester.finalize()); + Ok((body.to_vec(), digest)) +} + +#[tokio::main] +async fn main() { + /* + + Creates an entry in the transparency log. If no command line arguments is provided, + the program will generate a key pair, download the file available at URL constant, sign it + and create an entry in the transparency log. In the other hand, if the user sets the + command line flags, the program will use that info to create the entry. Therefore, + if the user use information of an entry already present in the transparency log, this + program can print an error. See an example: + + Example command : + cargo run --example create_log_entry -- \ + --hash c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413\ + --url https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data\ + --public_key LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\ + --signature MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8=\ + --api_version 0.0.1 + + When the example code is run with the default values, the following error message gets returned: + + Err( + ResponseError( + ResponseContent { + status: 409, + content: "{\"code\":409,\"message\":\"An equivalent entry already exists in the transparency log with UUID 1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf\"}\n", + entity: Some( + Status400( + Error { + code: Some( + 409, + ), + message: Some( + "An equivalent entry already exists in the transparency log with UUID 1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf", + ), + }, + ), + ), + }, + ), + ) + + This is because an equivalent entry with the provided meta data already exists in the transparency log. + When you use the example code to create a new entry with fresh set of input values or leaving the program + to generate the required data, you should be able to run the code without any errors. See an example: + + Example command : + cargo run --example create_log_entry -- + + The expected output will be something similar to: + + Ok( + LogEntry { + uuid: "24296fb24b8ad77afa01e2c1f5555326e4fc32a942b40a2d798ae72a8f10c801f6e8dee771dfbacc", + attestation: None, + body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjN2VhZDg3ZmE1YzgyZDJiMTdmZWVjZTFjMmVlMWJkYThlOTQ3ODhmNGIyMDhkZTUwNTdiMzYxN2E0MmI3NDEzIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQWh4elhWZnRyMWpyS0k3dEluWW5iR1pNMDZybFhpQ1lUMTRJbFdFazF4QkFpRUE0SGllM2l4cTRyOG9tVVgwclRDV2o3UmducVhqUEFZTmlkaDlQVllrQXFVPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjNCRmJGTlJlbGRTTUM5Sk5raEJZbm9yV21sVmFsVlhWR051WlFvdlUwUndWV1ZOVUhGR04wUXlZbU5xV2tKRlYweGhiak5XTjB3cmVHNW5jVFJHYW1wRGVtdHlLMFkwYlc5bFNEaFJTbWhNYUV0SlQzWlJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", + integrated_time: 1675277501, + log_i_d: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + log_index: 12425816, + verification: Verification { + inclusion_proof: Some( + InclusionProof { + hashes: [ + "a0b75928818e5fa302c3690a895e0385803a079391a79cf9f25b08a51eebc338", + "3a39532ac61bf4d3f9a982e38f1b3166a3222e9d8a081d31f67d0da745117dc5", + "96ff049c2d122233d7e44b49d5df16b0901dbf85523d90bd739a3a25d26a974c", + "09805d1e21e395d8b82e7269c7ddff2941564f925145196273a993c452059a85", + "37ca98bdb80bdc45768539d15117b2b57531b3ad1f051aaa4f58d030f868f86e", + "e81e08afa83961c36e2a6961f66859620c9ee4ed9be5631ace9f3c27c72f66fb", + "2f01e165e3758aba5fd53d0c03c88b84ccbed7334173d9159d87fed5930bfe03", + "d0509b1c0bde3ba0517efcc5d3e8d2007fe86e5e055cebd5e94307cd0394c22d", + "8a84151f9b8fbbf7b3e77cb658535ec46d27c1cdd1cab714558dc51114922e7a", + "5eb43eca7a763e2eaafb2bc2fc963e7802283cf1a9076638242177b1669942c0", + "5523cd019fea93d01834fc429f708b700aeb72c835a73161cdb9003f8f4e8072", + "4b6df664d9552bc24d48a4c7d5659a8270065e1fedbc39103b010ab235a87850", + "616429db6c7d20c5b0eff1a6e512ea57a0734b94ae0bc7c914679463e01a7fba", + "5a4ad1534b1e770f02bfde0de15008a6971cf1ffbfa963fc9c2a644973a8d2d1", + ], + log_index: 8262385, + root_hash: "41b3e1294d122b2190396de7de92731a378378ac2d7f620eb01d653838e88219", + tree_size: 8262387, + }, + ), + signed_entry_timestamp: "MEUCIG/vIwjuQoiVZtxw48KSMYyxXlpHA/y8kxYTJh46qbejAiEAyFAP5oQjxT6xFK7wKYW33sa/5wFQvqtKsdTLnitrzWA=", + }, + }, + ) + */ + + const URL: &str = "https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data"; + const API_VERSION: &str = "0.0.1"; + + let data_job = get_file_sha256sum(URL.to_string()); + let signer_job = create_signer(); + + let matches = Command::new("cmd") + .arg(Arg::new("hash") + .long("hash") + .value_name("HASH") + .help("hash of the artifact")) + .arg(Arg::new("url") + .long("url") + .value_name("URL") + .help("url containing the contents of the artifact (raw github url)")) + .arg(Arg::new("public_key") + .long("public_key") + .value_name("PUBLIC_KEY") + .help("base64 encoded public_key. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) + .arg(Arg::new("signature") + .long("signature") + .value_name("SIGNATURE") + .help("base64 encoded signature of the artifact. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) + .arg(Arg::new("api_version") + .long("api_version") + .value_name("API_VERSION") + .help("Rekor-rs open api version")); + + let flags = matches.get_matches(); + + let configuration = Configuration::default(); + + let (data_bytes, digest) = data_job.await.expect("Cannot get data digest"); + let signer = signer_job.await; + let public_key_base64 = general_purpose::STANDARD.encode( + signer + .to_sigstore_keypair() + .expect("Cannot get sigstore keypair") + .public_key_to_pem() + .expect("Cannot set public key"), + ); + + let sig = general_purpose::STANDARD.encode(signer.sign(&data_bytes).expect("Cannot sign data")); + + let hash = Hash::new( + AlgorithmKind::sha256, + flags + .get_one::("hash") + .unwrap_or(&digest) + .to_owned(), + ); + let data = Data::new(hash); + let public_key = PublicKey::new( + flags + .get_one::("public_key") + .unwrap_or(&public_key_base64) + .to_owned(), + ); + let signature = Signature::new( + flags.get_one("signature").unwrap_or(&sig).to_owned(), + public_key, + ); + let spec = Spec::new(signature, data); + let proposed_entry = ProposedEntry::Hashedrekord { + api_version: flags + .get_one::("api_version") + .unwrap_or(&API_VERSION.to_string()) + .to_owned(), + spec, + }; + + let log_entry = entries_api::create_log_entry(&configuration, proposed_entry).await; + println!("{:#?}", log_entry); +} diff --git a/vendor/examples/rekor/get_log_entry_by_index/main.rs b/vendor/examples/rekor/get_log_entry_by_index/main.rs new file mode 100644 index 0000000..57d85d1 --- /dev/null +++ b/vendor/examples/rekor/get_log_entry_by_index/main.rs @@ -0,0 +1,55 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +use sigstore::rekor::models::log_entry::LogEntry; +use std::str::FromStr; + +#[tokio::main] +async fn main() { + /* + + Retrieves an entry and inclusion proof from the transparency log (if it exists) by index + + Example command : + cargo run --example get_log_entry_by_index -- --log_index 99 + + */ + let matches = Command::new("cmd").arg( + Arg::new("log_index") + .long("log_index") + .value_name("LOG_INDEX") + .help("log_index of the artifact"), + ); + + // The following default value will be used if the user does not input values using cli flags + const LOG_INDEX: &str = "1"; + + let flags = matches.get_matches(); + let index = i32::from_str( + flags + .get_one::("log_index") + .unwrap_or(&LOG_INDEX.to_string()), + ) + .unwrap(); + + let configuration = Configuration::default(); + + let message: LogEntry = entries_api::get_log_entry_by_index(&configuration, index) + .await + .unwrap(); + println!("{:#?}", message); +} diff --git a/vendor/examples/rekor/get_log_entry_by_uuid/main.rs b/vendor/examples/rekor/get_log_entry_by_uuid/main.rs new file mode 100644 index 0000000..50a5d58 --- /dev/null +++ b/vendor/examples/rekor/get_log_entry_by_uuid/main.rs @@ -0,0 +1,50 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +use sigstore::rekor::models::log_entry::LogEntry; + +#[tokio::main] +async fn main() { + /* + + Get log entry and information required to generate an inclusion proof for the entry in the transparency log + + Example command : + cargo run --example get_log_entry_by_uuid -- --uuid 073970a07c978b7a9ff15b69fe15d87dfb58fd5756086e3d1fb671c2d0bd95c0 + + */ + let matches = Command::new("cmd").arg( + Arg::new("uuid") + .long("uuid") + .value_name("UUID") + .help("uuid of the artifact"), + ); + + // The following default value will be used if the user does not input values using cli flags + const UUID: &str = "073970a07c978b7a9ff15b69fe15d87dfb58fd5756086e3d1fb671c2d0bd95c0"; + + let flags = matches.get_matches(); + let uuid = flags + .get_one::("uuid") + .unwrap_or(&UUID.to_string()) + .to_owned(); + let configuration = Configuration::default(); + let message: LogEntry = entries_api::get_log_entry_by_uuid(&configuration, &uuid) + .await + .unwrap(); + println!("{:#?}", message); +} diff --git a/vendor/examples/rekor/get_log_info/main.rs b/vendor/examples/rekor/get_log_info/main.rs new file mode 100644 index 0000000..d9d9250 --- /dev/null +++ b/vendor/examples/rekor/get_log_info/main.rs @@ -0,0 +1,36 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sigstore::rekor::apis::{configuration::Configuration, tlog_api}; +use sigstore::rekor::models::LogInfo; + +#[tokio::main] +async fn main() { + /* + + Gets information about the current state of the transparency log. + Returns the current root hash and size of the merkle tree used to store the log entries. + + Example command : + cargo run --example get_log_info + + The server might return an error sometimes, + this is because the result depends on the kind of rekor object that gets returned. + + */ + let configuration = Configuration::default(); + let log_info: LogInfo = tlog_api::get_log_info(&configuration).await.unwrap(); + println!("{:#?}", log_info); +} diff --git a/vendor/examples/rekor/get_log_proof/main.rs b/vendor/examples/rekor/get_log_proof/main.rs new file mode 100644 index 0000000..900d281 --- /dev/null +++ b/vendor/examples/rekor/get_log_proof/main.rs @@ -0,0 +1,67 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, tlog_api}; +use sigstore::rekor::models::ConsistencyProof; +use std::str::FromStr; + +#[tokio::main] +async fn main() { + /* + + Get information required to generate a consistency proof for the transparency log. + Returns a list of hashes for specified tree sizes that can be used to confirm the consistency of the transparency log. + + Example command : + cargo run --example get_log_proof -- --last_size 10 + cargo run --example get_log_proof -- --last_size 10 --first_size 1 + + */ + let matches = Command::new("cmd") + .arg(Arg::new("last_size") + .long("last_size") + .value_name("LAST_SIZE") + .help("The size of the tree that you wish to prove consistency to")) + .arg(Arg::new("first_size") + .long("first_size") + .value_name("FIRST_SIZE") + .help("The size of the tree that you wish to prove consistency from (1 means the beginning of the log). Defaults to 1. To use the default value, do not input any value")) + .arg(Arg::new("tree_id") + .long("tree_id") + .value_name("TREE_ID") + .help("The tree ID of the tree that you wish to prove consistency for. To use the default value, do not input any value.")); + + let configuration = Configuration::default(); + let flags = matches.get_matches(); + + // The following default value will be used if the user does not input values using cli flags + const LAST_SIZE: &str = "10"; + + let log_proof: ConsistencyProof = tlog_api::get_log_proof( + &configuration, + i32::from_str( + flags + .get_one::("last_size") + .unwrap_or(&LAST_SIZE.to_string()), + ) + .unwrap(), + flags.get_one::("first_size").map(|s| s.as_str()), + flags.get_one::("tree_id").map(|s| s.as_str()), + ) + .await + .unwrap(); + println!("{:#?}", log_proof); +} diff --git a/vendor/examples/rekor/get_public_key/main.rs b/vendor/examples/rekor/get_public_key/main.rs new file mode 100644 index 0000000..d7e7ced --- /dev/null +++ b/vendor/examples/rekor/get_public_key/main.rs @@ -0,0 +1,64 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{fs::write, process}; + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, pubkey_api}; + +#[tokio::main] +async fn main() { + /* + Returns the public key that can be used to validate the signed tree head + + Example command : + cargo run --example get_public_key + */ + + let matches = Command::new("cmd") + .arg(Arg::new("tree_id") + .long("tree_id") + .value_name("TREE_ID") + .help("The tree ID of the tree that you wish to prove consistency for. To use the default value, do not input any value.")) + .arg(Arg::new("output") + .short('o') + .long("output") + .value_name("OUTPUT_FILE") + .num_args(0..=1) + .require_equals(true) + .default_missing_value("key.pub") + .help("The path to the output file that you wish to store the public key in. To use the default value (pub.key), do not provide OUTPUT_FILE.")); + + let flags = matches.get_matches(); + let configuration = Configuration::default(); + let pubkey = pubkey_api::get_public_key( + &configuration, + flags.get_one::("tree_id").map(|s| s.as_str()), + ) + .await + .expect("Unable to retrieve public key"); + + if let Some(out_path) = flags.get_one::("output") { + match write(out_path, pubkey) { + Ok(_) => (), + Err(e) => { + eprintln!("Could not write to {out_path}: {e}"); + process::exit(1); + } + } + } else { + print!("{}", pubkey); + } +} diff --git a/vendor/examples/rekor/search_index/main.rs b/vendor/examples/rekor/search_index/main.rs new file mode 100644 index 0000000..f2de2da --- /dev/null +++ b/vendor/examples/rekor/search_index/main.rs @@ -0,0 +1,117 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, index_api}; +use sigstore::rekor::models::{ + SearchIndex, search_index_public_key, search_index_public_key::Format, +}; + +#[tokio::main] +async fn main() { + /* + + Searches index by entry metadata + + Example command: + cargo run --example search_index -- \ + --hash e2535d638859bb63ea9ea5cf467562cba63b007eae1acd0d73a3f259c582561f \ + --public_key c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVA3M2tuT0tKYVNyVEtEa2U2OEgvRlJoODRZWU5CU0tBN1hPVWRpWmJjeG8gdGVzdEByZWtvci5kZXYK \ + --key_format ssh \ + --email jpenumak@redhat.com + + cargo run --example search_index -- \ + --public_key c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVA3M2tuT0tKYVNyVEtEa2U2OEgvRlJoODRZWU5CU0tBN1hPVWRpWmJjeG8gdGVzdEByZWtvci5kZXYK \ + --key_format ssh \ + --email jpenumak@redhat.com + + The server might return an error sometimes, + this is because the result depends on the kind of rekor object that gets returned. + + */ + + let matches = Command::new("cmd") + .arg(Arg::new("hash") + .long("hash") + .value_name("HASH") + .help("hash of the artifact")) + .arg(Arg::new("url") + .long("url") + .value_name("URL") + .help("url containing the contents of the artifact (raw github url)")) + .arg(Arg::new("public_key") + .long("public_key") + .value_name("PUBLIC_KEY") + .help("base64 encoded public_key. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) + .arg(Arg::new("key_format") + .long("key_format") + .value_name("KEY_FORMAT") + .help("Accepted formats are : pgp / x509 / minsign / ssh / tuf")) + .arg(Arg::new("email") + .long("email") + .value_name("EMAIL") + .help("Author's email")); + + let flags = matches.get_matches(); + + // The following default values will be used if the user does not input values using cli flags + const HASH: &str = "c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413"; + const PUBLIC_KEY: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="; + const KEY_FORMAT: &str = "x509"; + const EMAIL: &str = "jpenumak@redhat.com"; + + let key_format = match flags + .get_one::("key_format") + .unwrap_or(&KEY_FORMAT.to_string()) + .as_str() + { + "pgp" => Format::Pgp, + "x509" => Format::X509, + "minisign" => Format::Minisign, + "ssh" => Format::Ssh, + _ => Format::Tuf, + }; + + let public_key = search_index_public_key::SearchIndexPublicKey { + format: key_format, + content: Some( + flags + .get_one::("public_key") + .unwrap_or(&PUBLIC_KEY.to_string()) + .to_owned(), + ), + url: None, + }; + + let query = SearchIndex { + email: Some( + flags + .get_one::("email") + .unwrap_or(&EMAIL.to_string()) + .to_owned(), + ), + public_key: Some(public_key), + hash: Some( + flags + .get_one("hash") + .unwrap_or(&HASH.to_string()) + .to_owned(), + ), + }; + let configuration = Configuration::default(); + + let uuid_vec = index_api::search_index(&configuration, query).await; + println!("{:#?}", uuid_vec); +} diff --git a/vendor/examples/rekor/search_log_query/main.rs b/vendor/examples/rekor/search_log_query/main.rs new file mode 100644 index 0000000..9e65911 --- /dev/null +++ b/vendor/examples/rekor/search_log_query/main.rs @@ -0,0 +1,140 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::{Arg, Command}; +use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +use sigstore::rekor::models::{ + ProposedEntry, SearchLogQuery, + hashedrekord::{AlgorithmKind, Data, Hash, PublicKey, Signature, Spec}, +}; +use std::str::FromStr; + +#[tokio::main] +async fn main() { + /* + Searches transparency log for one or more log entries. + Returns zero or more entries from the transparency log, according to how many were included in request query. + + Example command : + cargo run --example search_log_query -- \ + --hash c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413\ + --url https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data\ + --public_key LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\ + --signature MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8=\ + --key_format x509\ + --api_version 0.0.1\ + --entry_uuids 1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf\ + --log_indexes 2922253 + */ + + // The following default values will be used if the user does not input values using cli flags + const HASH: &str = "c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413"; + const PUBLIC_KEY: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="; + const SIGNATURE: &str = "MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8="; + const API_VERSION: &str = "0.0.1"; + const ENTRY_UUIDS: &str = "1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf"; + const LOG_INDEXES: &str = "2922253"; + + let matches = Command::new("cmd") + .arg(Arg::new("hash") + .long("hash") + .value_name("HASH") + .help("hash of the artifact")) + .arg(Arg::new("url") + .long("url") + .value_name("URL") + .help("url containing the contents of the artifact (raw github url)")) + .arg(Arg::new("public_key") + .long("public_key") + .value_name("PUBLIC_KEY") + .help("base64 encoded public_key. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) + .arg(Arg::new("key_format") + .long("key_format") + .value_name("KEY_FORMAT") + .help("Accepted formats are : pgp / x509 / minsign / ssh / tuf")) + .arg(Arg::new("signature") + .long("signature") + .value_name("SIGNATURE") + .help("base64 encoded signature of the artifact. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) + .arg(Arg::new("api_version") + .long("api_version") + .value_name("API_VERSION") + .help("Rekor-rs open api version")) + .arg(Arg::new("entry_uuids") + .long("entry_uuids") + .value_name("ENTRY_UUIDS") + .help("the uuids of the entries to search for")) + .arg(Arg::new("log_indexes") + .long("log_indexes") + .value_name("LOG_INDEXES") + .help("the log_indexes of the entries to search for")); + + let flags = matches.get_matches(); + + let hash = Hash::new( + AlgorithmKind::sha256, + flags + .get_one::("hash") + .unwrap_or(&HASH.to_string()) + .to_owned(), + ); + let data = Data::new(hash); + let public_key = PublicKey::new( + flags + .get_one::("public_key") + .unwrap_or(&PUBLIC_KEY.to_string()) + .to_owned(), + ); + let signature = Signature::new( + flags + .get_one::("signature") + .unwrap_or(&SIGNATURE.to_string()) + .to_owned(), + public_key, + ); + let spec = Spec::new(signature, data); + let proposed_entry = ProposedEntry::Hashedrekord { + api_version: flags + .get_one::("api_version") + .unwrap_or(&API_VERSION.to_string()) + .to_owned(), + spec, + }; + + let query = SearchLogQuery { + entry_uuids: Some(vec![ + flags + .get_one::("entry_uuids") + .unwrap_or(&ENTRY_UUIDS.to_string()) + .to_owned(), + ]), + log_indexes: Some(vec![ + i32::from_str( + flags + .get_one::("log_indexes") + .unwrap_or(&LOG_INDEXES.to_string()), + ) + .unwrap(), + ]), + entries: Some(vec![proposed_entry]), + }; + + let configuration = Configuration::default(); + + let message = entries_api::search_log_query(&configuration, query) + .await + .unwrap(); + println!("{}", message); +} diff --git a/vendor/sigstore-0.13.0/.cargo/audit.toml b/vendor/sigstore-0.13.0/.cargo/audit.toml new file mode 100644 index 0000000..304c6ed --- /dev/null +++ b/vendor/sigstore-0.13.0/.cargo/audit.toml @@ -0,0 +1,7 @@ +[advisories] +ignore = [ + "RUSTSEC-2023-0071", # "Classic" RSA timing sidechannel attack from non-constant-time implementation. + # Okay for local use. + # https://rustsec.org/advisories/RUSTSEC-2023-0071.html + "RUSTSEC-2024-0370", # This is a warning about `proc-macro-errors` being unmaintained. It's a transitive dependency of `sigstore` and `oci-spec`. +] diff --git a/vendor/sigstore-0.13.0/.cargo_vcs_info.json b/vendor/sigstore-0.13.0/.cargo_vcs_info.json new file mode 100644 index 0000000..85d6190 --- /dev/null +++ b/vendor/sigstore-0.13.0/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "328282a94df8333c9bd5c2d3ea80782cd6863153" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/vendor/sigstore-0.13.0/.github/dependabot.yml b/vendor/sigstore-0.13.0/.github/dependabot.yml new file mode 100644 index 0000000..0c6c90a --- /dev/null +++ b/vendor/sigstore-0.13.0/.github/dependabot.yml @@ -0,0 +1,27 @@ +# +# Copyright 2022 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/vendor/sigstore-0.13.0/.github/workflows/auto-publish-crates-upon-release.yml b/vendor/sigstore-0.13.0/.github/workflows/auto-publish-crates-upon-release.yml new file mode 100644 index 0000000..5a3f3b3 --- /dev/null +++ b/vendor/sigstore-0.13.0/.github/workflows/auto-publish-crates-upon-release.yml @@ -0,0 +1,29 @@ +name: Publish-Crate-Upon-Release +on: + release: + types: [published] + +jobs: + publish-automatically: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - uses: rust-lang/crates-io-auth-action@e919bc7605cde86df457cf5b93c5e103838bd879 # v1.0.1 + id: auth + + - name: Rustup + run: | + rustup install stable + rustup override set stable + + - name: publish crates + env: + CARGO_REGISTRY_TOKEN: "${{ steps.auth.outputs.token }}" + run: cargo publish diff --git a/vendor/sigstore-0.13.0/.github/workflows/conformance.yml b/vendor/sigstore-0.13.0/.github/workflows/conformance.yml new file mode 100644 index 0000000..7ac65ac --- /dev/null +++ b/vendor/sigstore-0.13.0/.github/workflows/conformance.yml @@ -0,0 +1,21 @@ +on: [workflow_dispatch] + +name: Conformance Suite + +jobs: + conformance: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - name: Rustup + run: | + rustup install --profile minimal stable + rustup override set stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + cargo build --manifest-path=tests/conformance/Cargo.toml + - uses: sigstore/sigstore-conformance@main + with: + entrypoint: ${{ github.workspace }}/tests/conformance/target/debug/sigstore diff --git a/vendor/sigstore-0.13.0/.github/workflows/security-audit.yml b/vendor/sigstore-0.13.0/.github/workflows/security-audit.yml new file mode 100644 index 0000000..e3666b6 --- /dev/null +++ b/vendor/sigstore-0.13.0/.github/workflows/security-audit.yml @@ -0,0 +1,27 @@ +name: Security audit +on: + schedule: + - cron: "0 0 * * *" + push: + paths: + - "**/Cargo.toml" + - "**/Cargo.lock" + +permissions: {} + +jobs: + audit: + permissions: + checks: write # for rustsec/audit-check to create check + contents: read # for actions/checkout to fetch code + issues: write # for rustsec/audit-check to create issues + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - name: Generate lockfile + run: cargo generate-lockfile + - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/vendor/sigstore-0.13.0/.github/workflows/tests.yml b/vendor/sigstore-0.13.0/.github/workflows/tests.yml new file mode 100644 index 0000000..821ff95 --- /dev/null +++ b/vendor/sigstore-0.13.0/.github/workflows/tests.yml @@ -0,0 +1,130 @@ +on: [push, pull_request] + +name: Continuous integration + +jobs: + check: + name: Check features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2.62.31 + with: + tool: cargo-hack + + # cosign, full, mock-client, registry will ONLY compile with a *-tls feature + - run: | + cargo hack check --each-feature --skip cosign --skip full --skip mock-client --skip registry + + check-native-tls: + name: Check native-tls features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2.62.31 + with: + tool: cargo-hack + - run: | + cargo hack check --feature-powerset --features native-tls --skip wasm --skip test-registry --skip rustls-tls --skip rustls-tls-native-roots + + check-rustls-tls: + name: Check rusttls-tls features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2.62.31 + with: + tool: cargo-hack + - run: | + cargo hack check --feature-powerset --features rustls-tls --skip wasm --skip test-registry --skip native-tls + + test-native-tls: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + cargo test --workspace --no-default-features --features full,native-tls,test-registry + + test-rustls-tls: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + cargo test --workspace --no-default-features --features full,rustls-tls,test-registry + + doc: + name: Build Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@4f94fbe7e03939b0e674bcc9ca609a16088f63ff # nightly + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + make doc + + toml: + name: Toml format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + - uses: taiki-e/install-action@0005e0116e92d8489d8d96fbff83f061c79ba95a # v2.62.31 + with: + tool: taplo-cli + - run: | + taplo fmt --check + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + cargo fmt --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@38b70195107dddab2c7bbd522bcf763bac00963b # stable + with: + components: clippy + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 + - run: | + cargo clippy --workspace -- -D warnings diff --git a/vendor/sigstore-0.13.0/.gitignore b/vendor/sigstore-0.13.0/.gitignore new file mode 100644 index 0000000..6d548b3 --- /dev/null +++ b/vendor/sigstore-0.13.0/.gitignore @@ -0,0 +1,16 @@ +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# ide files +.idea +.vscode \ No newline at end of file diff --git a/vendor/sigstore-0.13.0/.taplo.toml b/vendor/sigstore-0.13.0/.taplo.toml new file mode 100644 index 0000000..c72b6da --- /dev/null +++ b/vendor/sigstore-0.13.0/.taplo.toml @@ -0,0 +1,8 @@ +exclude = [".cargo/audit.toml"] + +[[rule]] + +[rule.formatting] +indent_string = " " +reorder_arrays = true +reorder_keys = true diff --git a/vendor/src/bundle/mod.rs b/vendor/src/bundle/mod.rs new file mode 100644 index 0000000..e89d34f --- /dev/null +++ b/vendor/src/bundle/mod.rs @@ -0,0 +1,27 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Sigstore bundle support. + +pub use sigstore_protobuf_specs::dev::sigstore::bundle::v1::Bundle; + +mod models; + +#[cfg_attr(docsrs, doc(cfg(feature = "sign")))] +#[cfg(feature = "sign")] +pub mod sign; + +#[cfg_attr(docsrs, doc(cfg(feature = "verify")))] +#[cfg(feature = "verify")] +pub mod verify; diff --git a/vendor/src/bundle/models.rs b/vendor/src/bundle/models.rs new file mode 100644 index 0000000..6bd641c --- /dev/null +++ b/vendor/src/bundle/models.rs @@ -0,0 +1,107 @@ +use std::fmt::Display; +use std::str::FromStr; + +use base64::{Engine as _, engine::general_purpose::STANDARD as base64}; +use json_syntax::Print; + +use sigstore_protobuf_specs::dev::sigstore::{ + common::v1::LogId, + rekor::v1::{Checkpoint, InclusionPromise, InclusionProof, KindVersion, TransparencyLogEntry}, +}; + +use crate::rekor::models::{ + LogEntry as RekorLogEntry, log_entry::InclusionProof as RekorInclusionProof, +}; + +// Known Sigstore bundle media types. +#[derive(Clone, Copy, Debug)] +pub enum Version { + Bundle0_1, + Bundle0_2, +} + +impl Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match &self { + Version::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1", + Version::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2", + }) + } +} + +impl FromStr for Version { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(Version::Bundle0_1), + "application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(Version::Bundle0_2), + _ => Err(()), + } + } +} + +#[inline] +fn decode_hex>(hex: S) -> Result, ()> { + hex::decode(hex.as_ref()).or(Err(())) +} + +impl TryFrom for InclusionProof { + type Error = (); + + fn try_from(value: RekorInclusionProof) -> Result { + let hashes = value + .hashes + .iter() + .map(decode_hex) + .collect::, _>>()?; + + Ok(InclusionProof { + checkpoint: Some(Checkpoint { + envelope: value.checkpoint, + }), + hashes, + log_index: value.log_index, + root_hash: decode_hex(value.root_hash)?, + tree_size: value.tree_size, + }) + } +} + +/// Convert log entries returned from Rekor into Sigstore Bundle format entries. +impl TryFrom for TransparencyLogEntry { + type Error = (); + + fn try_from(value: RekorLogEntry) -> Result { + let canonicalized_body = { + let mut body = json_syntax::to_value(value.body).or(Err(()))?; + body.canonicalize(); + body.compact_print().to_string().into_bytes() + }; + let inclusion_promise = Some(InclusionPromise { + signed_entry_timestamp: base64 + .decode(value.verification.signed_entry_timestamp) + .or(Err(()))?, + }); + let inclusion_proof = value + .verification + .inclusion_proof + .map(|p| p.try_into()) + .transpose()?; + + Ok(TransparencyLogEntry { + canonicalized_body, + inclusion_promise, + inclusion_proof, + integrated_time: value.integrated_time, + kind_version: Some(KindVersion { + kind: "hashedrekord".to_owned(), + version: "0.0.1".to_owned(), + }), + log_id: Some(LogId { + key_id: decode_hex(value.log_i_d)?, + }), + log_index: value.log_index, + }) + } +} diff --git a/vendor/src/bundle/sign.rs b/vendor/src/bundle/sign.rs new file mode 100644 index 0000000..81a7bed --- /dev/null +++ b/vendor/src/bundle/sign.rs @@ -0,0 +1,379 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for signing artifacts and producing Sigstore bundles. + +use std::{ + io::{self, Read}, + time::SystemTime, +}; + +use base64::{Engine as _, engine::general_purpose::STANDARD as base64}; +use hex; +use p256::NistP256; +use pkcs8::der::{Encode, EncodePem}; +use sha2::{Digest, Sha256}; +use signature::DigestSigner; +use sigstore_protobuf_specs::dev::sigstore::bundle::v1::bundle; +use sigstore_protobuf_specs::dev::sigstore::bundle::v1::{ + Bundle, VerificationMaterial, verification_material, +}; +use sigstore_protobuf_specs::dev::sigstore::common::v1::{ + HashAlgorithm, HashOutput, MessageSignature, X509Certificate, X509CertificateChain, +}; +use sigstore_protobuf_specs::dev::sigstore::rekor::v1::TransparencyLogEntry; +use tokio::io::AsyncRead; +use tokio_util::io::SyncIoBridge; +use url::Url; +use x509_cert::attr::{AttributeTypeAndValue, AttributeValue}; +use x509_cert::builder::{Builder, RequestBuilder as CertRequestBuilder}; +use x509_cert::ext::pkix as x509_ext; + +use crate::bundle::models::Version; +use crate::crypto::keyring::Keyring; +use crate::crypto::transparency::{CertificateEmbeddedSCT, verify_sct}; +use crate::errors::{Result as SigstoreResult, SigstoreError}; +use crate::fulcio::oauth::OauthTokenProvider; +use crate::fulcio::{self, FULCIO_ROOT, FulcioClient}; +use crate::oauth::IdentityToken; +use crate::rekor::apis::configuration::Configuration as RekorConfiguration; +use crate::rekor::apis::entries_api::create_log_entry; +use crate::rekor::models::{hashedrekord, proposed_entry::ProposedEntry as ProposedLogEntry}; +use crate::trust::TrustRoot; + +#[cfg(feature = "sigstore-trust-root")] +use crate::trust::sigstore::SigstoreTrustRoot; + +/// An asynchronous Sigstore signing session. +/// +/// Sessions hold a provided user identity and key materials tied to that identity. A single +/// session may be used to sign multiple items. For more information, see [`SigningSession::sign`]. +/// +/// This signing session operates asynchronously. To construct a synchronous [`blocking::SigningSession`], +/// use [`SigningContext::blocking_signer()`]. +pub struct SigningSession<'ctx> { + context: &'ctx SigningContext, + identity_token: IdentityToken, + private_key: ecdsa::SigningKey, + certs: fulcio::CertificateResponse, +} + +impl<'ctx> SigningSession<'ctx> { + async fn new( + context: &'ctx SigningContext, + identity_token: IdentityToken, + ) -> SigstoreResult> { + let (private_key, certs) = Self::materials(&context.fulcio, &identity_token).await?; + Ok(Self { + context, + identity_token, + private_key, + certs, + }) + } + + async fn materials( + fulcio: &FulcioClient, + token: &IdentityToken, + ) -> SigstoreResult<(ecdsa::SigningKey, fulcio::CertificateResponse)> { + let subject = + // SEQUENCE OF RelativeDistinguishedName + vec![ + // SET OF AttributeTypeAndValue + vec![ + // AttributeTypeAndValue, `emailAddress=...` + AttributeTypeAndValue { + oid: const_oid::db::rfc3280::EMAIL_ADDRESS, + value: AttributeValue::new( + pkcs8::der::Tag::Utf8String, + token + .unverified_claims() + .email + .as_deref() + .unwrap_or("") + .as_bytes() + .to_vec(), + )?, + } + ].try_into()? + ].into(); + + let mut rng = rand::thread_rng(); + let private_key = ecdsa::SigningKey::from(p256::SecretKey::random(&mut rng)); + let mut builder = CertRequestBuilder::new(subject, &private_key)?; + builder.add_extension(&x509_ext::BasicConstraints { + ca: false, + path_len_constraint: None, + })?; + + let cert_req = builder.build::()?; + Ok((private_key, fulcio.request_cert_v2(cert_req, token).await?)) + } + + /// Check if the session's identity token or key material is expired. + /// + /// If the session is expired, it cannot be used for signing operations, and a new session + /// must be created with a fresh identity token. + pub fn is_expired(&self) -> bool { + let not_after = self + .certs + .cert + .tbs_certificate + .validity + .not_after + .to_system_time(); + + !self.identity_token.in_validity_period() || SystemTime::now() > not_after + } + + async fn sign_digest(&self, hasher: Sha256) -> SigstoreResult { + if self.is_expired() { + return Err(SigstoreError::ExpiredSigningSession()); + } + + if let Some(detached_sct) = &self.certs.detached_sct { + verify_sct(detached_sct, &self.context.ctfe_keyring)?; + } else { + let sct = CertificateEmbeddedSCT::new(&self.certs.cert, &self.certs.chain)?; + verify_sct(&sct, &self.context.ctfe_keyring)?; + } + + // Sign artifact. + let input_hash: &[u8] = &hasher.clone().finalize(); + let artifact_signature: p256::ecdsa::Signature = self.private_key.sign_digest(hasher); + let signature_bytes = artifact_signature.to_der().as_bytes().to_owned(); + + let cert = &self.certs.cert; + + // Create the transparency log entry. + let proposed_entry = ProposedLogEntry::Hashedrekord { + api_version: "0.0.1".to_owned(), + spec: hashedrekord::Spec { + signature: hashedrekord::Signature { + content: base64.encode(&signature_bytes), + public_key: hashedrekord::PublicKey::new( + base64.encode(cert.to_pem(pkcs8::LineEnding::LF)?), + ), + }, + data: hashedrekord::Data { + hash: hashedrekord::Hash { + algorithm: hashedrekord::AlgorithmKind::sha256, + value: hex::encode(input_hash), + }, + }, + }, + }; + + let log_entry = create_log_entry(&self.context.rekor_config, proposed_entry) + .await + .map_err(|err| SigstoreError::RekorClientError(err.to_string()))?; + let log_entry = log_entry + .try_into() + .or(Err(SigstoreError::RekorClientError( + "Rekor returned malformed LogEntry".into(), + )))?; + + // TODO(tnytown): Maybe run through the verification flow here? See sigstore-rs#296. + + Ok(SigningArtifact { + input_digest: input_hash.to_owned(), + cert: cert.to_der()?, + signature: signature_bytes, + log_entry, + }) + } + + /// Signs for the input with the session's identity. If the identity is expired, + /// [`SigstoreError::ExpiredSigningSession`] is returned. + pub async fn sign( + &self, + input: R, + ) -> SigstoreResult { + if self.is_expired() { + return Err(SigstoreError::ExpiredSigningSession()); + } + + let mut sync_input = SyncIoBridge::new(input); + let hasher = tokio::task::spawn_blocking(move || -> SigstoreResult<_> { + let mut hasher = Sha256::new(); + io::copy(&mut sync_input, &mut hasher)?; + Ok(hasher) + }) + .await??; + + self.sign_digest(hasher).await + } +} + +pub mod blocking { + use super::{SigningSession as AsyncSigningSession, *}; + + /// A synchronous Sigstore signing session. + /// + /// Sessions hold a provided user identity and key materials tied to that identity. A single + /// session may be used to sign multiple items. For more information, see [`SigningSession::sign`]. + /// + /// This signing session operates synchronously, thus it cannot be used in an asynchronous context. + /// To construct an asynchronous [`SigningSession`], use [`SigningContext::signer()`]. + pub struct SigningSession<'ctx> { + inner: AsyncSigningSession<'ctx>, + rt: tokio::runtime::Runtime, + } + + impl<'ctx> SigningSession<'ctx> { + pub(crate) fn new(ctx: &'ctx SigningContext, token: IdentityToken) -> SigstoreResult { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + let inner = rt.block_on(AsyncSigningSession::new(ctx, token))?; + Ok(Self { inner, rt }) + } + + /// Check if the session's identity token or key material is expired. + /// + /// If the session is expired, it cannot be used for signing operations, and a new session + /// must be created with a fresh identity token. + pub fn is_expired(&self) -> bool { + self.inner.is_expired() + } + + /// Signs for the input with the session's identity. If the identity is expired, + /// [`SigstoreError::ExpiredSigningSession`] is returned. + pub fn sign(&self, mut input: R) -> SigstoreResult { + let mut hasher = Sha256::new(); + io::copy(&mut input, &mut hasher)?; + self.rt.block_on(self.inner.sign_digest(hasher)) + } + } +} + +/// A Sigstore signing context. +/// +/// Contexts hold Fulcio (CA) and Rekor (CT) configurations which signing sessions can be +/// constructed against. Use [`SigningContext::production`] to create a context against +/// the public-good Sigstore infrastructure. +pub struct SigningContext { + fulcio: FulcioClient, + rekor_config: RekorConfiguration, + ctfe_keyring: Keyring, +} + +impl SigningContext { + /// Manually constructs a [`SigningContext`] from its constituent data. + pub fn new( + fulcio: FulcioClient, + rekor_config: RekorConfiguration, + ctfe_keyring: Keyring, + ) -> Self { + Self { + fulcio, + rekor_config, + ctfe_keyring, + } + } + + /// Returns a [`SigningContext`] configured against the public-good production Sigstore + /// infrastructure. + #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] + #[cfg(feature = "sigstore-trust-root")] + pub async fn async_production() -> SigstoreResult { + let trust_root = SigstoreTrustRoot::new(None).await?; + Ok(Self::new( + FulcioClient::new( + Url::parse(FULCIO_ROOT).expect("constant FULCIO root fails to parse!"), + crate::fulcio::TokenProvider::Oauth(OauthTokenProvider::default()), + ), + Default::default(), + Keyring::new(trust_root.ctfe_keys()?.values().copied())?, + )) + } + + /// Returns a [`SigningContext`] configured against the public-good production Sigstore + /// infrastructure. + /// + /// Async callers should use [`SigningContext::async_production`]. + #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] + #[cfg(feature = "sigstore-trust-root")] + pub fn production() -> SigstoreResult { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + + rt.block_on(Self::async_production()) + } + + /// Configures and returns a [`SigningSession`] with the held context. + pub async fn signer( + &self, + identity_token: IdentityToken, + ) -> SigstoreResult> { + SigningSession::new(self, identity_token).await + } + + /// Configures and returns a [`blocking::SigningSession`] with the held context. + /// + /// Async contexts must use [`SigningContext::signer`]. + pub fn blocking_signer( + &self, + identity_token: IdentityToken, + ) -> SigstoreResult> { + blocking::SigningSession::new(self, identity_token) + } +} + +/// A signature and its associated metadata. +pub struct SigningArtifact { + input_digest: Vec, + cert: Vec, + signature: Vec, + log_entry: TransparencyLogEntry, +} + +impl SigningArtifact { + /// Consumes the signing artifact and produces a Sigstore [`Bundle`]. + /// + /// The resulting bundle can be serialized with [`serde_json`]. + pub fn to_bundle(self) -> Bundle { + // NOTE: We explicitly only include the leaf certificate in the bundle's "chain" + // here: the specs explicitly forbid the inclusion of the root certificate, + // and discourage inclusion of any intermediates (since they're in the root of + // trust already). + let x509_certificate_chain = X509CertificateChain { + certificates: vec![X509Certificate { + raw_bytes: self.cert, + }], + }; + + let verification_material = Some(VerificationMaterial { + timestamp_verification_data: None, + tlog_entries: vec![self.log_entry], + content: Some(verification_material::Content::X509CertificateChain( + x509_certificate_chain, + )), + }); + + let message_signature = MessageSignature { + message_digest: Some(HashOutput { + algorithm: HashAlgorithm::Sha2256.into(), + digest: self.input_digest, + }), + signature: self.signature, + }; + Bundle { + media_type: Version::Bundle0_2.to_string(), + verification_material, + content: Some(bundle::Content::MessageSignature(message_signature)), + } + } +} diff --git a/vendor/src/bundle/verify/mod.rs b/vendor/src/bundle/verify/mod.rs new file mode 100644 index 0000000..a6ba643 --- /dev/null +++ b/vendor/src/bundle/verify/mod.rs @@ -0,0 +1,26 @@ +// +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for verifying Sigstore bundles with policies. + +mod models; + +pub use models::{VerificationError, VerificationResult}; + +pub mod policy; +pub use policy::{PolicyError, VerificationPolicy}; + +mod verifier; +pub use verifier::*; diff --git a/vendor/src/bundle/verify/models.rs b/vendor/src/bundle/verify/models.rs new file mode 100644 index 0000000..870d9ec --- /dev/null +++ b/vendor/src/bundle/verify/models.rs @@ -0,0 +1,294 @@ +// +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::str::FromStr; + +use crate::{ + bundle::{Bundle, models::Version as BundleVersion}, + crypto::certificate::{CertificateValidationError, is_leaf, is_root_ca}, + rekor::models as rekor, +}; + +use base64::{Engine as _, engine::general_purpose::STANDARD as base64}; +use sigstore_protobuf_specs::dev::sigstore::{ + bundle::v1::{bundle, verification_material}, + rekor::v1::{InclusionProof, TransparencyLogEntry}, +}; +use thiserror::Error; +use tracing::{debug, error, warn}; +use x509_cert::{ + Certificate, + der::{Decode, EncodePem}, +}; + +use super::policy::PolicyError; + +#[derive(Error, Debug)] +pub enum Bundle01ProfileErrorKind { + #[error("bundle must contain inclusion promise")] + InclusionPromiseMissing, +} + +#[derive(Error, Debug)] +pub enum Bundle02ProfileErrorKind { + #[error("bundle must contain inclusion proof")] + InclusionProofMissing, + + #[error("bundle must contain checkpoint")] + CheckpointMissing, +} + +#[derive(Error, Debug)] +#[error(transparent)] +pub enum BundleProfileErrorKind { + Bundle01Profile(#[from] Bundle01ProfileErrorKind), + + Bundle02Profile(#[from] Bundle02ProfileErrorKind), + + #[error("unknown bundle profile {0}")] + Unknown(String), +} + +#[derive(Error, Debug)] +pub enum BundleErrorKind { + #[error("bundle missing VerificationMaterial")] + VerificationMaterialMissing, + + #[error("bundle includes unsupported VerificationMaterial::Content")] + VerificationMaterialContentUnsupported, + + #[error("bundle's certificate(s) are malformed")] + CertificateMalformed(#[source] x509_cert::der::Error), + + #[error("bundle contains a root certificate")] + RootInChain, + + #[error("bundle does not contain the signing (leaf) certificate")] + NoLeaf(#[source] CertificateValidationError), + + #[error("bundle does not contain any certificates")] + CertificatesMissing, + + #[error("bundle does not contain signature")] + SignatureMissing, + + #[error("bundle includes unsupported DSSE signature")] + DsseUnsupported, + + #[error("bundle needs 1 tlog entry, got {0}")] + TlogEntry(usize), + + #[error(transparent)] + BundleProfile(#[from] BundleProfileErrorKind), +} + +#[derive(Error, Debug)] +pub enum CertificateErrorKind { + #[error("certificate malformed")] + Malformed(#[source] webpki::Error), + + #[error("certificate expired before time of signing")] + Expired, + + #[error("certificate SCT verification failed")] + Sct(#[source] crate::crypto::transparency::SCTError), + + #[error("certificate verification failed")] + VerificationFailed(#[source] webpki::Error), +} + +#[derive(Error, Debug)] +pub enum SignatureErrorKind { + #[error("unsupported signature algorithm")] + AlgoUnsupported(#[source] crate::errors::SigstoreError), + + #[error("signature verification failed")] + VerificationFailed(#[source] crate::errors::SigstoreError), + + #[error("signature transparency materials are inconsistent")] + Transparency, +} + +#[derive(Error, Debug)] +#[error(transparent)] +pub enum VerificationError { + #[error("unable to read input")] + Input(#[source] std::io::Error), + + Bundle(#[from] BundleErrorKind), + + Certificate(#[from] CertificateErrorKind), + + Signature(#[from] SignatureErrorKind), + + Policy(#[from] PolicyError), +} + +pub type VerificationResult = Result<(), VerificationError>; + +pub struct CheckedBundle { + pub(crate) certificate: Certificate, + pub(crate) signature: Vec, + + tlog_entry: TransparencyLogEntry, +} + +impl TryFrom for CheckedBundle { + type Error = BundleErrorKind; + + fn try_from(input: Bundle) -> Result { + let (content, mut tlog_entries) = match input.verification_material { + Some(m) => (m.content, m.tlog_entries), + _ => return Err(BundleErrorKind::VerificationMaterialMissing), + }; + + // Parse the certificates. The first entry in the chain MUST be a leaf certificate, and the + // rest of the chain MUST NOT include a root CA or any intermediate CAs that appear in an + // independent root of trust. + let certs = match content { + Some(verification_material::Content::X509CertificateChain(ch)) => ch.certificates, + Some(verification_material::Content::Certificate(cert)) => { + vec![cert] + } + _ => return Err(BundleErrorKind::VerificationMaterialContentUnsupported), + }; + let certs = certs + .iter() + .map(|c| c.raw_bytes.as_slice()) + .map(Certificate::from_der) + .collect::, _>>() + .map_err(BundleErrorKind::CertificateMalformed)?; + + let [leaf_cert, chain_certs @ ..] = &certs[..] else { + return Err(BundleErrorKind::CertificatesMissing); + }; + + is_leaf(leaf_cert).map_err(BundleErrorKind::NoLeaf)?; + + for chain_cert in chain_certs { + if is_root_ca(chain_cert).is_ok() { + return Err(BundleErrorKind::RootInChain); + } + } + + let signature = match input.content.ok_or(BundleErrorKind::SignatureMissing)? { + bundle::Content::MessageSignature(s) => s.signature, + _ => return Err(BundleErrorKind::DsseUnsupported), + }; + + if tlog_entries.len() != 1 { + return Err(BundleErrorKind::TlogEntry(tlog_entries.len())); + } + let tlog_entry = tlog_entries.remove(0); + + let (inclusion_promise, inclusion_proof) = + (&tlog_entry.inclusion_promise, &tlog_entry.inclusion_proof); + + // `inclusion_proof` is a required field in the current protobuf spec, + // but older versions of Rekor didn't provide it. Check invariants + // here and selectively allow for this case. + // + // https://github.com/sigstore/sigstore-python/pull/634#discussion_r1182769140 + let check_01_bundle = || -> Result<(), BundleProfileErrorKind> { + if inclusion_promise.is_none() { + return Err(Bundle01ProfileErrorKind::InclusionPromiseMissing)?; + } + + if matches!( + inclusion_proof, + Some(InclusionProof { + checkpoint: None, + .. + }) + ) { + debug!("0.1 bundle contains inclusion proof without checkpoint"); + } + + Ok(()) + }; + let check_02_bundle = || -> Result<(), BundleProfileErrorKind> { + if inclusion_proof.is_none() { + error!("bundle must contain inclusion proof"); + return Err(Bundle02ProfileErrorKind::InclusionProofMissing)?; + } + + if matches!( + inclusion_proof, + Some(InclusionProof { + checkpoint: None, + .. + }) + ) { + error!("bundle must contain checkpoint"); + return Err(Bundle02ProfileErrorKind::CheckpointMissing)?; + } + + Ok(()) + }; + match BundleVersion::from_str(&input.media_type) { + Ok(BundleVersion::Bundle0_1) => check_01_bundle()?, + Ok(BundleVersion::Bundle0_2) => check_02_bundle()?, + Err(_) => return Err(BundleProfileErrorKind::Unknown(input.media_type))?, + } + + Ok(Self { + certificate: leaf_cert.clone(), + signature, + tlog_entry, + }) + } +} + +impl CheckedBundle { + /// Retrieves and checks consistency of the bundle's [TransparencyLogEntry]. + pub fn tlog_entry(&self, offline: bool, input_digest: &[u8]) -> Option<&TransparencyLogEntry> { + let base64_pem_certificate = + base64.encode(self.certificate.to_pem(pkcs8::LineEnding::LF).ok()?); + + let expected_entry = rekor::Hashedrekord { + kind: "hashedrekord".to_owned(), + api_version: "0.0.1".to_owned(), + spec: rekor::hashedrekord::Spec { + signature: rekor::hashedrekord::Signature { + content: base64.encode(&self.signature), + public_key: rekor::hashedrekord::PublicKey::new(base64_pem_certificate), + }, + data: rekor::hashedrekord::Data { + hash: rekor::hashedrekord::Hash { + algorithm: rekor::hashedrekord::AlgorithmKind::sha256, + value: hex::encode(input_digest), + }, + }, + }, + }; + + let entry = if !offline && self.tlog_entry.inclusion_proof.is_none() { + warn!("online rekor fetching is not implemented yet, but is necessary for this bundle"); + return None; + } else { + &self.tlog_entry + }; + + let actual: serde_json::Value = + serde_json::from_slice(&self.tlog_entry.canonicalized_body).ok()?; + let expected: serde_json::Value = serde_json::to_value(expected_entry).ok()?; + + if actual != expected { + return None; + } + + Some(entry) + } +} diff --git a/vendor/src/bundle/verify/policy.rs b/vendor/src/bundle/verify/policy.rs new file mode 100644 index 0000000..11b5b7f --- /dev/null +++ b/vendor/src/bundle/verify/policy.rs @@ -0,0 +1,295 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Verification constraints for certificate metadata. +//! +//! + +use const_oid::ObjectIdentifier; +use thiserror::Error; +use tracing::warn; +use x509_cert::ext::pkix::{SubjectAltName, name::GeneralName}; + +macro_rules! oids { + ($($name:ident = $value:literal),+) => { + $(const $name: ObjectIdentifier = ObjectIdentifier::new_unwrap($value);)+ + }; +} + +macro_rules! impl_policy { + ($policy:ident, $oid:expr, $doc:literal) => { + #[doc = $doc] + pub struct $policy(pub String); + + impl const_oid::AssociatedOid for $policy { + const OID: ObjectIdentifier = $oid; + } + + impl SingleX509ExtPolicy for $policy { + fn new>(val: S) -> Self { + Self(val.as_ref().to_owned()) + } + + fn name() -> &'static str { + stringify!($policy) + } + + fn value(&self) -> &str { + &self.0 + } + } + }; +} + +oids! { + OIDC_ISSUER_OID = "1.3.6.1.4.1.57264.1.1", + OIDC_GITHUB_WORKFLOW_TRIGGER_OID = "1.3.6.1.4.1.57264.1.2", + OIDC_GITHUB_WORKFLOW_SHA_OID = "1.3.6.1.4.1.57264.1.3", + OIDC_GITHUB_WORKFLOW_NAME_OID = "1.3.6.1.4.1.57264.1.4", + OIDC_GITHUB_WORKFLOW_REPOSITORY_OID = "1.3.6.1.4.1.57264.1.5", + OIDC_GITHUB_WORKFLOW_REF_OID = "1.3.6.1.4.1.57264.1.6", + OTHERNAME_OID = "1.3.6.1.4.1.57264.1.7" + +} + +#[derive(Error, Debug)] +pub enum PolicyError { + #[error("did not find exactly 1 of the required extension in the certificate")] + ExtensionNotFound, + + #[error("certificate's {extension} does not match (got {actual}, expected {expected})")] + ExtensionCheckFailed { + extension: String, + expected: String, + actual: String, + }, + + #[error("{0} of {total} policies failed: {1}\n- ", + errors.len(), + errors.iter().map(|e| e.to_string()).collect::>().join("\n- ") + )] + AllOf { + total: usize, + errors: Vec, + }, + + #[error("0 of {total} policies succeeded")] + AnyOf { total: usize }, +} + +pub type PolicyResult = Result<(), PolicyError>; + +/// A policy that checks a single textual value against a X.509 extension. +pub trait SingleX509ExtPolicy { + fn new>(val: S) -> Self; + fn name() -> &'static str; + fn value(&self) -> &str; +} + +impl VerificationPolicy for T { + fn verify(&self, cert: &x509_cert::Certificate) -> PolicyResult { + let extensions = cert.tbs_certificate.extensions.as_deref().unwrap_or(&[]); + let mut extensions = extensions.iter().filter(|ext| ext.extn_id == T::OID); + + // Check for exactly one extension. + let (Some(ext), None) = (extensions.next(), extensions.next()) else { + return Err(PolicyError::ExtensionNotFound); + }; + + // Parse raw string without DER encoding. + let val = std::str::from_utf8(ext.extn_value.as_bytes()) + .or(Err(PolicyError::ExtensionNotFound))?; + + if val != self.value() { + return Err(PolicyError::ExtensionCheckFailed { + extension: T::name().to_owned(), + expected: self.value().to_owned(), + actual: val.to_owned(), + }); + } + + Ok(()) + } +} + +impl_policy!( + OIDCIssuer, + OIDC_ISSUER_OID, + "Checks the certificate's OIDC issuer." +); + +impl_policy!( + GitHubWorkflowTrigger, + OIDC_GITHUB_WORKFLOW_TRIGGER_OID, + "Checks the certificate's GitHub Actions workflow trigger." +); + +impl_policy!( + GitHubWorkflowSHA, + OIDC_GITHUB_WORKFLOW_SHA_OID, + "Checks the certificate's GitHub Actions workflow commit SHA." +); + +impl_policy!( + GitHubWorkflowName, + OIDC_GITHUB_WORKFLOW_NAME_OID, + "Checks the certificate's GitHub Actions workflow name." +); + +impl_policy!( + GitHubWorkflowRepository, + OIDC_GITHUB_WORKFLOW_REPOSITORY_OID, + "Checks the certificate's GitHub Actions workflow repository." +); + +impl_policy!( + GitHubWorkflowRef, + OIDC_GITHUB_WORKFLOW_REF_OID, + "Checks the certificate's GitHub Actions workflow ref." +); + +/// An interface that all policies must conform to. +pub trait VerificationPolicy { + fn verify(&self, cert: &x509_cert::Certificate) -> PolicyResult; +} + +/// The "any of" policy, corresponding to a logical OR between child policies. +/// +/// An empty list of child policies is considered trivially invalid. +pub struct AnyOf<'a> { + children: Vec<&'a dyn VerificationPolicy>, +} + +impl<'a> AnyOf<'a> { + pub fn new(policies: I) -> Self + where + I: IntoIterator, + { + Self { + children: policies.into_iter().collect(), + } + } +} + +impl VerificationPolicy for AnyOf<'_> { + fn verify(&self, cert: &x509_cert::Certificate) -> PolicyResult { + self.children + .iter() + .find(|policy| policy.verify(cert).is_err()) + .map_or(Ok(()), |_| { + Err(PolicyError::AnyOf { + total: self.children.len(), + }) + }) + } +} + +/// The "all of" policy, corresponding to a logical AND between child policies. +/// +/// An empty list of child policies is considered trivially invalid. +pub struct AllOf<'a> { + children: Vec<&'a dyn VerificationPolicy>, +} + +impl<'a> AllOf<'a> { + pub fn new(policies: I) -> Option + where + I: IntoIterator, + { + let children: Vec<_> = policies.into_iter().collect(); + + // Without this, we'd be able to construct an `AllOf` containing an empty list of child + // policies. This is almost certainly not what the user wants and is a potential source + // of API misuse, so we explicitly disallow it. + if children.is_empty() { + warn!("attempted to construct an AllOf with an empty list of child policies"); + return None; + } + + Some(Self { children }) + } +} + +impl VerificationPolicy for AllOf<'_> { + fn verify(&self, cert: &x509_cert::Certificate) -> PolicyResult { + let results = self.children.iter().map(|policy| policy.verify(cert).err()); + let failures: Vec<_> = results.flatten().collect(); + + if !failures.is_empty() { + return Err(PolicyError::AllOf { + total: self.children.len(), + errors: failures, + }); + } + + Ok(()) + } +} + +/// Verifies the certificate's "identity", corresponding to the X.509v3 SAN. +/// Identities are verified modulo an OIDC issuer, so the issuer's URI +/// is also required. +/// +/// Supported SAN types include emails, URIs, and Sigstore-specific "other names". +pub struct Identity { + identity: String, + issuer: OIDCIssuer, +} + +impl Identity { + pub fn new(identity: A, issuer: B) -> Self + where + A: AsRef, + B: AsRef, + { + Self { + identity: identity.as_ref().to_owned(), + issuer: OIDCIssuer::new(issuer), + } + } +} + +impl VerificationPolicy for Identity { + fn verify(&self, cert: &x509_cert::Certificate) -> PolicyResult { + self.issuer.verify(cert)?; + + let (_, san): (bool, SubjectAltName) = match cert.tbs_certificate.get() { + Ok(Some(result)) => result, + _ => return Err(PolicyError::ExtensionNotFound), + }; + + let names: Vec<_> = san + .0 + .iter() + .filter_map(|name| match name { + GeneralName::Rfc822Name(name) => Some(name.as_str()), + GeneralName::UniformResourceIdentifier(name) => Some(name.as_str()), + GeneralName::OtherName(name) if name.type_id == OTHERNAME_OID => { + std::str::from_utf8(name.value.value()).ok() + } + _ => None, + }) + .collect(); + + if !names.contains(&self.identity.as_str()) { + return Err(PolicyError::ExtensionCheckFailed { + extension: "SubjectAltName".to_owned(), + expected: self.identity.clone(), + actual: names.join(", "), + }); + } + + Ok(()) + } +} diff --git a/vendor/src/bundle/verify/verifier.rs b/vendor/src/bundle/verify/verifier.rs new file mode 100644 index 0000000..8288559 --- /dev/null +++ b/vendor/src/bundle/verify/verifier.rs @@ -0,0 +1,304 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Verifiers: async and blocking. + +use std::io::{self, Read}; + +use pki_types::{CertificateDer, UnixTime}; +use sha2::{Digest, Sha256}; +use tokio::io::{AsyncRead, AsyncReadExt}; +use tracing::debug; +use x509_cert::der::Encode; + +use crate::{ + bundle::Bundle, + crypto::{ + CertificatePool, CosignVerificationKey, Signature, + keyring::Keyring, + transparency::{CertificateEmbeddedSCT, verify_sct}, + }, + errors::Result as SigstoreResult, + rekor::apis::configuration::Configuration as RekorConfiguration, + trust::TrustRoot, +}; + +#[cfg(feature = "sigstore-trust-root")] +use crate::trust::sigstore::SigstoreTrustRoot; + +use super::{ + VerificationError, VerificationResult, + models::{CertificateErrorKind, CheckedBundle, SignatureErrorKind}, + policy::VerificationPolicy, +}; + +/// An asynchronous Sigstore verifier. +/// +/// For synchronous usage, see [`Verifier`]. +pub struct Verifier { + #[allow(dead_code)] + rekor_config: RekorConfiguration, + cert_pool: CertificatePool, + ctfe_keyring: Keyring, +} + +impl Verifier { + /// Constructs a [`Verifier`]. + /// + /// For verifications against the public-good trust root, use [`Verifier::production()`]. + pub fn new( + rekor_config: RekorConfiguration, + trust_repo: R, + ) -> SigstoreResult { + let cert_pool = CertificatePool::from_certificates(trust_repo.fulcio_certs()?, [])?; + let ctfe_keyring = Keyring::new(trust_repo.ctfe_keys()?.values().copied())?; + + Ok(Self { + rekor_config, + cert_pool, + ctfe_keyring, + }) + } + + /// Verifies an input digest against the given Sigstore Bundle, ensuring conformance to the + /// provided [`VerificationPolicy`]. + pub async fn verify_digest

( + &self, + input_digest: Sha256, + bundle: Bundle, + policy: &P, + offline: bool, + ) -> VerificationResult + where + P: VerificationPolicy, + { + let input_digest = input_digest.finalize(); + let materials: CheckedBundle = bundle.try_into()?; + + // In order to verify an artifact, we need to achieve the following: + // + // 1) Verify that the signing certificate is signed by the certificate + // chain and that the signing certificate was valid at the time + // of signing. + // 2) Verify that the signing certificate belongs to the signer. + // 3) Verify that the artifact signature was signed by the public key in the + // signing certificate. + // 4) Verify that the Rekor entry is consistent with the other signing + // materials (preventing CVE-2022-36056) + // 5) Verify the inclusion proof supplied by Rekor for this artifact, + // if we're doing online verification. + // 6) Verify the Signed Entry Timestamp (SET) supplied by Rekor for this + // artifact. + // 7) Verify that the signing certificate was valid at the time of + // signing by comparing the expiry against the integrated timestamp. + + // 1) Verify that the signing certificate is signed by the certificate + // chain and that the signing certificate was valid at the time + // of signing. + let tbs_certificate = &materials.certificate.tbs_certificate; + let issued_at = tbs_certificate.validity.not_before.to_unix_duration(); + let cert_der: CertificateDer = materials + .certificate + .to_der() + .expect("failed to DER-encode constructed Certificate!") + .into(); + let ee_cert = (&cert_der) + .try_into() + .map_err(CertificateErrorKind::Malformed)?; + + let trusted_chain = self + .cert_pool + .verify_cert_with_time(&ee_cert, UnixTime::since_unix_epoch(issued_at)) + .map_err(CertificateErrorKind::VerificationFailed)?; + + debug!("signing certificate chains back to trusted root"); + + let sct_context = + CertificateEmbeddedSCT::new_with_verified_path(&materials.certificate, &trusted_chain) + .map_err(CertificateErrorKind::Sct)?; + verify_sct(&sct_context, &self.ctfe_keyring).map_err(CertificateErrorKind::Sct)?; + debug!("signing certificate's SCT is valid"); + + // 2) Verify that the signing certificate belongs to the signer. + policy.verify(&materials.certificate)?; + debug!("signing certificate conforms to policy"); + + // 3) Verify that the signature was signed by the public key in the signing certificate + let signing_key: CosignVerificationKey = (&tbs_certificate.subject_public_key_info) + .try_into() + .map_err(SignatureErrorKind::AlgoUnsupported)?; + + let verify_sig = + signing_key.verify_prehash(Signature::Raw(&materials.signature), &input_digest); + verify_sig.map_err(SignatureErrorKind::VerificationFailed)?; + + debug!("signature corresponds to public key"); + + // 4) Verify that the Rekor entry is consistent with the other signing + // materials + let log_entry = materials + .tlog_entry(offline, &input_digest) + .ok_or(SignatureErrorKind::Transparency)?; + debug!("log entry is consistent with other materials"); + + // 5) Verify the inclusion proof supplied by Rekor for this artifact, + // if we're doing online verification. + // TODO(tnytown): Merkle inclusion; sigstore-rs#285 + + // 6) Verify the Signed Entry Timestamp (SET) supplied by Rekor for this + // artifact. + // TODO(tnytown) SET verification; sigstore-rs#285 + + // 7) Verify that the signing certificate was valid at the time of + // signing by comparing the expiry against the integrated timestamp. + let integrated_time = log_entry.integrated_time as u64; + let not_before = tbs_certificate + .validity + .not_before + .to_unix_duration() + .as_secs(); + let not_after = tbs_certificate + .validity + .not_after + .to_unix_duration() + .as_secs(); + if integrated_time < not_before || integrated_time > not_after { + return Err(CertificateErrorKind::Expired)?; + } + debug!("data signed during validity period"); + + debug!("successfully verified!"); + Ok(()) + } + + /// Verifies an input against the given Sigstore Bundle, ensuring conformance to the provided + /// [`VerificationPolicy`]. + pub async fn verify( + &self, + mut input: R, + bundle: Bundle, + policy: &P, + offline: bool, + ) -> VerificationResult + where + R: AsyncRead + Unpin + Send, + P: VerificationPolicy, + { + // arbitrary buffer size, chosen to be a multiple of the digest size. + let mut buf = [0u8; 1024]; + let mut hasher = Sha256::new(); + + loop { + match input + .read(&mut buf) + .await + .map_err(VerificationError::Input)? + { + 0 => break, + n => hasher.update(&buf[..n]), + } + } + + self.verify_digest(hasher, bundle, policy, offline).await + } +} + +impl Verifier { + /// Constructs an [`Verifier`] against the public-good trust root. + #[cfg(feature = "sigstore-trust-root")] + #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] + pub async fn production() -> SigstoreResult { + let updater = SigstoreTrustRoot::new(None).await?; + + Verifier::new(Default::default(), updater) + } +} + +pub mod blocking { + use super::{Verifier as AsyncVerifier, *}; + + /// A synchronous Sigstore verifier. + pub struct Verifier { + inner: AsyncVerifier, + rt: tokio::runtime::Runtime, + } + + impl Verifier { + /// Constructs a synchronous Sigstore verifier. + /// + /// For verifications against the public-good trust root, use [`Verifier::production()`]. + pub fn new( + rekor_config: RekorConfiguration, + trust_repo: R, + ) -> SigstoreResult { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + let inner = AsyncVerifier::new(rekor_config, trust_repo)?; + + Ok(Self { rt, inner }) + } + + /// Verifies an input digest against the given Sigstore Bundle, ensuring conformance to the + /// provided [`VerificationPolicy`]. + pub fn verify_digest

( + &self, + input_digest: Sha256, + bundle: Bundle, + policy: &P, + offline: bool, + ) -> VerificationResult + where + P: VerificationPolicy, + { + self.rt.block_on( + self.inner + .verify_digest(input_digest, bundle, policy, offline), + ) + } + + /// Verifies an input against the given Sigstore Bundle, ensuring conformance to the provided + /// [`VerificationPolicy`]. + pub fn verify( + &self, + mut input: R, + bundle: Bundle, + policy: &P, + offline: bool, + ) -> VerificationResult + where + R: Read, + P: VerificationPolicy, + { + let mut hasher = Sha256::new(); + io::copy(&mut input, &mut hasher).map_err(VerificationError::Input)?; + + self.verify_digest(hasher, bundle, policy, offline) + } + } + + impl Verifier { + /// Constructs a synchronous [`Verifier`] against the public-good trust root. + #[cfg(feature = "sigstore-trust-root")] + #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] + pub fn production() -> SigstoreResult { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + let inner = rt.block_on(AsyncVerifier::production())?; + + Ok(Verifier { inner, rt }) + } + } +} diff --git a/vendor/src/cosign/bundle.rs b/vendor/src/cosign/bundle.rs new file mode 100644 index 0000000..3a9bea1 --- /dev/null +++ b/vendor/src/cosign/bundle.rs @@ -0,0 +1,201 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{cmp::PartialEq, collections::BTreeMap}; + +use olpc_cjson::CanonicalFormatter; +use serde::{Deserialize, Serialize}; + +use crate::{ + crypto::{CosignVerificationKey, Signature}, + errors::{Result, SigstoreError}, +}; + +/// Struct that represents the signature bundle as generated by running a +/// command that accepts the '--bundle' option. For example: +/// +/// ```sh,ignore,no_run +/// cosign sign-blob --bundle=artifact.bundle artifact.txt +/// ``` +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct SignedArtifactBundle { + /// Represents the `base64Signature' field which is the signature of the + /// of the blob. + pub base64_signature: String, + /// Represents the 'cert' field which is a PEM encoded certificate. + pub cert: String, + /// Represents the 'rekorBundle' field. + pub rekor_bundle: Bundle, +} + +impl SignedArtifactBundle { + /// Create a new verified `SignedArtifactBundle`. + /// + /// **Note well:** The bundle will be returned only if it can be verified + /// using the supplied `rekor_pub_key` public key. + pub fn new_verified( + raw: &str, + rekor_pub_keys: &BTreeMap, + ) -> Result { + let bundle: SignedArtifactBundle = serde_json::from_str(raw).map_err(|e| { + SigstoreError::UnexpectedError(format!("Cannot parse bundle |{raw}|: {e:?}")) + })?; + Bundle::verify_bundle(&bundle.rekor_bundle, rekor_pub_keys).map(|_| bundle) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "PascalCase")] +pub struct Bundle { + pub signed_entry_timestamp: String, + pub payload: Payload, +} + +impl Bundle { + /// Create a new verified `Bundle` + /// + /// **Note well:** The bundle will be returned only if it can be verified + /// using the supplied `rekor_pub_key` public key. + pub(crate) fn new_verified( + raw: &str, + rekor_pub_keys: &BTreeMap, + ) -> Result { + let bundle: Bundle = serde_json::from_str(raw).map_err(|e| { + SigstoreError::UnexpectedError(format!("Cannot parse bundle |{raw}|: {e:?}")) + })?; + Self::verify_bundle(&bundle, rekor_pub_keys).map(|_| bundle) + } + + /// Verify a `Bundle`. + /// + /// **Note well:** The bundle will be returned only if it can be verified + /// using the supplied `rekor_pub_key` public key. + pub(crate) fn verify_bundle( + bundle: &Bundle, + rekor_pub_keys: &BTreeMap, + ) -> Result<()> { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new()); + bundle.payload.serialize(&mut ser).map_err(|e| { + SigstoreError::UnexpectedError(format!( + "Cannot create canonical JSON representation of bundle: {e:?}" + )) + })?; + + let rekor_pub_key = rekor_pub_keys.get(&bundle.payload.log_id).ok_or_else(|| { + SigstoreError::RekorPublicKeyNotFoundError(bundle.payload.log_id.clone()) + })?; + + rekor_pub_key.verify_signature( + Signature::Base64Encoded(bundle.signed_entry_timestamp.as_bytes()), + &buf, + )?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Payload { + pub body: String, + pub integrated_time: i64, + pub log_index: i64, + #[serde(rename = "logID")] + pub log_id: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + use crate::cosign::tests::get_rekor_public_key; + use crate::crypto::SigningScheme; + + fn build_correct_bundle() -> String { + let bundle_json = json!({ + "SignedEntryTimestamp": "MEUCIDx9M+yRpD0O47/Mzm8NAPCbtqy4uiTkLWWexW0bo4jZAiEA1wwueIW8XzJWNkut5y9snYj7UOfbMmUXp7fH3CzJmWg=", + "Payload": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoicmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzYWY0NDE0ZDIwYzllMWNiNzZjY2M3MmFhZThiMjQyMTY2ZGFiZTZhZjUzMWE0YTc5MGRiOGUyZjBlNWVlN2M5In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUURXV3hQUWEzWEZVc1BieVRZK24rYlp1LzZQd2hnNVd3eVlEUXRFZlFobzl3SWhBUGtLVzdldWI4YjdCWCtZYmJSYWM4VHd3SXJLNUt4dmR0UTZOdW9EK2l2VyIsImZvcm1hdCI6Ing1MDkiLCJwdWJsaWNLZXkiOnsiY29udGVudCI6IkxTMHRMUzFDUlVkSlRpQlFWVUpNU1VNZ1MwVlpMUzB0TFMwS1RVWnJkMFYzV1VoTGIxcEplbW93UTBGUldVbExiMXBKZW1vd1JFRlJZMFJSWjBGRlRFdG9SRGRHTlU5TGVUYzNXalU0TWxrMmFEQjFNVW96UjA1Qkt3cHJkbFZ6YURSbFMzQmtNV3gzYTBSQmVtWkdSSE0zZVZoRlJYaHpSV3RRVUhWcFVVcENaV3hFVkRZNGJqZFFSRWxYUWk5UlJWazNiWEpCUFQwS0xTMHRMUzFGVGtRZ1VGVkNURWxESUV0RldTMHRMUzB0Q2c9PSJ9fX19", + "integratedTime": 1634714179, + "logIndex": 783606, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" + } + }); + serde_json::to_string(&bundle_json).unwrap() + } + + #[test] + fn bundle_new_verified_success() { + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let bundle_json = build_correct_bundle(); + let bundle = Bundle::new_verified(&bundle_json, &rekor_pub_keys); + + assert!(bundle.is_ok()); + } + + #[test] + fn bundle_new_verified_failure_because_different_key_signed_the_bundle() { + let public_key = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENptdY/l3nB0yqkXLBWkZWQwo6+cu +OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== +-----END PUBLIC KEY-----"#; + let not_rekor_pub_key = + CosignVerificationKey::from_pem(public_key.as_bytes(), &SigningScheme::default()) + .expect("Cannot create CosignVerificationKey"); + let (key_id, _) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, not_rekor_pub_key)]); + + let bundle_json = build_correct_bundle(); + let bundle = Bundle::new_verified(&bundle_json, &rekor_pub_keys); + + assert!(bundle.is_err()); + } + + #[test] + fn bundle_new_verified_failure_because_rekor_key_id_is_unknown() { + let (_, key) = get_rekor_public_key(); + let key_id = "cf1199155bddd051268d1f16ac5c0c75c009f6fb5a63f4177f8e18d7051e3fa0".to_string(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let bundle_json = build_correct_bundle(); + let bundle = Bundle::new_verified(&bundle_json, &rekor_pub_keys); + + assert!(matches!( + bundle, + Err(SigstoreError::RekorPublicKeyNotFoundError(_)) + )); + } + + #[test] + fn signedartifactbundle_new_verified_success() { + // Bundle as generated by running the following command, and taking the + // content from the generated 'artifact.bundle` file: + // cosign sign-blob --bundle=artifact.bundle artifact.txt + let bundle_raw = r#" +{"base64Signature":"MEQCIGp1XZP5zaImosrBhDPCdXn3f8xI9FHGLsGVx6UeRPCgAiAt5GrsdQhOKnZcA3EWecvgJSHzCIjWifFBQkD7Hdsymg==","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNxRENDQWkrZ0F3SUJBZ0lVVFBXVGZPLzFOUmFTRmRlY2FBUS9wQkRHSnA4d0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJeE1USTFNRGN6TnpFeVdoY05Nakl4TVRJMU1EYzBOekV5V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUVE0Vy81WFA5bTRZYldSQlF0SEdXd245dVVoYWUzOFVwY0oKcEVNM0RPczR6VzRNSXJNZlc0V1FEMGZ3cDhQVVVSRFh2UTM5NHBvcWdHRW1Ta3J1THFPQ0FVNHdnZ0ZLTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVvM0tuCmpKUVowWGZpZ2JENWIwT1ZOTjB4cVNvd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0p3WURWUjBSQVFIL0JCMHdHNEVaWkdGdWFXVnNMbUpsZG1WdWFYVnpRR2R0WVdsc0xtTnZiVEFzQmdvcgpCZ0VFQVlPL01BRUJCQjVvZEhSd2N6b3ZMMmRwZEdoMVlpNWpiMjB2Ykc5bmFXNHZiMkYxZEdnd2dZc0dDaXNHCkFRUUIxbmtDQkFJRWZRUjdBSGtBZHdEZFBUQnF4c2NSTW1NWkhoeVpaemNDb2twZXVONDhyZitIaW5LQUx5bnUKamdBQUFZU3R1Qkh5QUFBRUF3QklNRVlDSVFETTVZU1EvR0w2S0k1UjlPZGNuL3BTaytxVkQ2YnNMODMrRXA5UgoyaFdUYXdJaEFLMWppMWxaNTZEc2Z1TGZYN2JCQzluYlIzRWx4YWxCaHYxelFYTVU3dGx3TUFvR0NDcUdTTTQ5CkJBTURBMmNBTUdRQ01CSzh0c2dIZWd1aCtZaGVsM1BpakhRbHlKMVE1SzY0cDB4cURkbzdXNGZ4Zm9BUzl4clAKczJQS1FjZG9EOWJYd2dJd1g2ekxqeWJaa05IUDV4dEJwN3ZLMkZZZVp0ME9XTFJsVWxsY1VETDNULzdKUWZ3YwpHU3E2dlZCTndKMDB3OUhSCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIC3c+21v9pk6o4BpB/dRAM9lGnyWLi3Xnc+i8LmnNJmeAiEAiqZJbZHx3Idnw+zXv6yM0ipPw/p16R28YGuCJFQ1u8U=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0YmM0NTNiNTNjYjNkOTE0YjQ1ZjRiMjUwMjk0MjM2YWRiYTJjMGUwOWZmNmYwMzc5Mzk0OWU3ZTM5ZmQ0Y2MxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJR3AxWFpQNXphSW1vc3JCaERQQ2RYbjNmOHhJOUZIR0xzR1Z4NlVlUlBDZ0FpQXQ1R3JzZFFoT0tuWmNBM0VXZWN2Z0pTSHpDSWpXaWZGQlFrRDdIZHN5bWc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnhSRU5EUVdrclowRjNTVUpCWjBsVlZGQlhWR1pQTHpGT1VtRlRSbVJsWTJGQlVTOXdRa1JIU25BNGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEplRTFVU1RGTlJHTjZUbnBGZVZkb1kwNU5ha2w0VFZSSk1VMUVZekJPZWtWNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZLVVZFMFZ5ODFXRkE1YlRSWllsZFNRbEYwU0VkWGQyNDVkVlZvWVdVek9GVndZMG9LY0VWTk0wUlBjelI2VnpSTlNYSk5abGMwVjFGRU1HWjNjRGhRVlZWU1JGaDJVVE01TkhCdmNXZEhSVzFUYTNKMVRIRlBRMEZWTkhkblowWkxUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ2TTB0dUNtcEtVVm93V0dacFoySkVOV0l3VDFaT1RqQjRjVk52ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwM1dVUldVakJTUVZGSUwwSkNNSGRITkVWYVdrZEdkV0ZYVm5OTWJVcHNaRzFXZFdGWVZucFJSMlIwV1Zkc2MweHRUblppVkVGelFtZHZjZ3BDWjBWRlFWbFBMMDFCUlVKQ1FqVnZaRWhTZDJONmIzWk1NbVJ3WkVkb01WbHBOV3BpTWpCMllrYzVibUZYTkhaaU1rWXhaRWRuZDJkWmMwZERhWE5IQ2tGUlVVSXhibXREUWtGSlJXWlJVamRCU0d0QlpIZEVaRkJVUW5GNGMyTlNUVzFOV2tob2VWcGFlbU5EYjJ0d1pYVk9ORGh5Wml0SWFXNUxRVXg1Ym5VS2FtZEJRVUZaVTNSMVFraDVRVUZCUlVGM1FrbE5SVmxEU1ZGRVRUVlpVMUV2UjB3MlMwazFVamxQWkdOdUwzQlRheXR4VmtRMlluTk1PRE1yUlhBNVVnb3lhRmRVWVhkSmFFRkxNV3BwTVd4YU5UWkVjMloxVEdaWU4ySkNRemx1WWxJelJXeDRZV3hDYUhZeGVsRllUVlUzZEd4M1RVRnZSME5EY1VkVFRUUTVDa0pCVFVSQk1tTkJUVWRSUTAxQ1N6aDBjMmRJWldkMWFDdFphR1ZzTTFCcGFraFJiSGxLTVZFMVN6WTBjREI0Y1VSa2J6ZFhOR1o0Wm05QlV6bDRjbEFLY3pKUVMxRmpaRzlFT1dKWWQyZEpkMWcyZWt4cWVXSmFhMDVJVURWNGRFSndOM1pMTWtaWlpWcDBNRTlYVEZKc1ZXeHNZMVZFVEROVUx6ZEtVV1ozWXdwSFUzRTJkbFpDVG5kS01EQjNPVWhTQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1669361833,"logIndex":7810348,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}} + "#; + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let result = SignedArtifactBundle::new_verified(bundle_raw, &rekor_pub_keys); + assert!(result.is_ok()); + let bundle = result.unwrap(); + assert_eq!(bundle.rekor_bundle.payload.log_index, 7810348); + } +} diff --git a/vendor/src/cosign/client.rs b/vendor/src/cosign/client.rs new file mode 100644 index 0000000..9d22c89 --- /dev/null +++ b/vendor/src/cosign/client.rs @@ -0,0 +1,213 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; +use std::ops::Add; + +use async_trait::async_trait; +use oci_client::manifest::OCI_IMAGE_MEDIA_TYPE; +use tracing::warn; + +use super::constants::{SIGSTORE_OCI_MEDIA_TYPE, SIGSTORE_SIGNATURE_ANNOTATION}; +use super::{CosignCapabilities, SignatureLayer}; +use crate::cosign::signature_layers::build_signature_layers; +use crate::crypto::CosignVerificationKey; +use crate::registry::{Auth, OciReference, PushResponse}; +use crate::{ + crypto::certificate_pool::CertificatePool, + errors::{Result, SigstoreError}, +}; +use tracing::debug; + +/// Used to generate an empty [OCI Configuration](https://github.com/opencontainers/image-spec/blob/v1.0.0/config.md). +pub const CONFIG_DATA: &str = "{}"; + +/// Cosign Client +/// +/// Instances of `Client` can be built via [`sigstore::cosign::ClientBuilder`](crate::cosign::ClientBuilder). +pub struct Client { + pub(crate) registry_client: Box, + pub(crate) rekor_pub_keys: Option>, + pub(crate) fulcio_cert_pool: Option, +} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl CosignCapabilities for Client { + async fn triangulate( + &mut self, + image: &OciReference, + auth: &Auth, + ) -> Result<(OciReference, String)> { + let manifest_digest = self + .registry_client + .fetch_manifest_digest(&image.oci_reference, &auth.into()) + .await?; + + let reference = OciReference::with_tag( + image.registry().to_string(), + image.repository().to_string(), + manifest_digest.replace(':', "-").add(".sig"), + ); + + Ok((reference, manifest_digest)) + } + + async fn trusted_signature_layers( + &mut self, + auth: &Auth, + source_image_digest: &str, + cosign_image: &OciReference, + ) -> Result> { + let (manifest, layers) = self.fetch_manifest_and_layers(auth, cosign_image).await?; + let image_manifest = match manifest { + oci_client::manifest::OciManifest::Image(im) => im, + oci_client::manifest::OciManifest::ImageIndex(_) => { + return Err(SigstoreError::RegistryPullManifestError { + image: cosign_image.to_string(), + error: "Found a OciImageIndex instead of a OciImageManifest".to_string(), + }); + } + }; + + let sl = build_signature_layers( + &image_manifest, + source_image_digest, + &layers, + self.rekor_pub_keys.as_ref(), + self.fulcio_cert_pool.as_ref(), + )?; + + debug!(signature_layers=?sl, ?cosign_image, "trusted signature layers"); + Ok(sl) + } + + async fn push_signature( + &mut self, + annotations: Option>, + auth: &Auth, + target_reference: &OciReference, + signature_layers: Vec, + ) -> Result { + let layers: Vec = signature_layers + .iter() + .filter_map(|sl| { + match serde_json::to_vec(&sl.simple_signing) { + Ok(data) => { + let annotations = match &sl.signature { + Some(sig) => [(SIGSTORE_SIGNATURE_ANNOTATION.into(), sig.clone())].into(), + None => BTreeMap::new(), + }; + let image_layer = oci_client::client::ImageLayer::new(data, SIGSTORE_OCI_MEDIA_TYPE.into(), Some(annotations)); + Some(image_layer) + } + Err(e) => { + warn!(error = ?e, signaturelayer = ?sl, "Skipping SignatureLayer because serialization failed"); + None + } + } + }) + .collect(); + + // TODO: Do we need to support OCI Image Configuration? + let config = oci_client::client::Config::oci_v1(CONFIG_DATA.as_bytes().to_vec(), None); + let mut manifest = + oci_client::manifest::OciImageManifest::build(&layers[..], &config, annotations); + manifest.media_type = Some(OCI_IMAGE_MEDIA_TYPE.to_string()); + self.registry_client + .push( + &target_reference.oci_reference, + &layers[..], + config, + &auth.into(), + Some(manifest), + ) + .await + .map(|r| r.into()) + } +} + +impl Client { + /// Internal helper method used to fetch data from an OCI registry + async fn fetch_manifest_and_layers( + &mut self, + auth: &Auth, + cosign_image: &OciReference, + ) -> Result<( + oci_client::manifest::OciManifest, + Vec, + )> { + let oci_auth: oci_client::secrets::RegistryAuth = auth.into(); + + let (manifest, _) = self + .registry_client + .pull_manifest(&cosign_image.oci_reference, &oci_auth) + .await?; + let image_data = self + .registry_client + .pull( + &cosign_image.oci_reference, + &oci_auth, + vec![SIGSTORE_OCI_MEDIA_TYPE], + ) + .await?; + + Ok((manifest, image_data.layers)) + } +} + +#[cfg(feature = "mock-client")] +#[cfg(test)] +mod tests { + use super::*; + + use crate::{ + cosign::tests::{get_fulcio_cert_pool, get_rekor_public_key}, + mock_client::test::MockOciClient, + }; + + fn build_test_client(mock_client: MockOciClient) -> Client { + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + Client { + registry_client: Box::new(mock_client), + rekor_pub_keys: Some(rekor_pub_keys), + fulcio_cert_pool: Some(get_fulcio_cert_pool()), + } + } + + #[tokio::test] + async fn triangulate_sigstore_object() { + let image = "docker.io/busybox:latest".parse().unwrap(); + let image_digest = + String::from("sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b"); + let expected_image = "docker.io/library/busybox:sha256-f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b.sig".parse().unwrap(); + let mock_client = MockOciClient { + fetch_manifest_digest_response: Some(Ok(image_digest.clone())), + pull_response: None, + pull_manifest_response: None, + push_response: None, + }; + let mut cosign_client = build_test_client(mock_client); + + let reference = cosign_client + .triangulate(&image, &crate::registry::Auth::Anonymous) + .await; + + assert!(reference.is_ok()); + assert_eq!(reference.unwrap(), (expected_image, image_digest)); + } +} diff --git a/vendor/src/cosign/client_builder.rs b/vendor/src/cosign/client_builder.rs new file mode 100644 index 0000000..4b1e946 --- /dev/null +++ b/vendor/src/cosign/client_builder.rs @@ -0,0 +1,152 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; + +use pki_types::CertificateDer; +use tracing::info; + +use crate::{ + cosign::client::Client, + crypto::{CosignVerificationKey, SigningScheme, certificate_pool::CertificatePool}, + errors::Result, + registry::ClientConfig, + trust::TrustRoot, +}; + +/// A builder that generates Client objects. +/// +/// ## Rekor integration +/// +/// Rekor integration can be enabled by specifying Rekor's public key. +/// This can be provided via a [`crate::trust::ManualTrustRoot`]. +/// +/// > Note well: the [`trust::sigstore`](crate::trust::sigstore) module provides helper structs and methods +/// > to obtain this data from the official TUF repository of the Sigstore project. +/// +/// ## Fulcio integration +/// +/// Fulcio integration can be enabled by specifying Fulcio's certificate. +/// This can be provided via a [`crate::trust::sigstore::ManualTrustRoot`]. +/// +/// > Note well: the [`trust::sigstore`](crate::trust::sigstore) module provides helper structs and methods +/// > to obtain this data from the official TUF repository of the Sigstore project. +/// +/// ## Registry caching +/// +/// The [`cosign::Client`](crate::cosign::Client) interacts with remote container registries to obtain +/// the data needed to perform Sigstore verification. +/// +/// By default, the client will always reach out to the remote registry. However, +/// it's possible to enable an in-memory cache. This behaviour can be enabled via +/// the [`ClientBuilder::enable_registry_caching`] method. +/// +/// Each cached entry will automatically expire after 60 seconds. +#[derive(Default)] +pub struct ClientBuilder<'a> { + oci_client_config: ClientConfig, + rekor_pub_keys: Option>, + fulcio_certs: Vec>, + #[cfg(feature = "cached-client")] + enable_registry_caching: bool, +} + +impl<'a> ClientBuilder<'a> { + /// Enable caching of data returned from remote OCI registries + #[cfg(feature = "cached-client")] + #[cfg_attr(docsrs, doc(cfg(feature = "cached-client")))] + pub fn enable_registry_caching(mut self) -> Self { + self.enable_registry_caching = true; + self + } + + /// Optional - Configures the roots of trust. + /// + /// Enables Fulcio and Rekor integration with the given trust repository. + /// See [crate::trust::sigstore::TrustRoot] for more details on trust repositories. + pub fn with_trust_repository(mut self, repo: &'a R) -> Result { + let rekor_keys = repo.rekor_keys()?; + if !rekor_keys.is_empty() { + self.rekor_pub_keys = Some(rekor_keys); + } + self.fulcio_certs = repo.fulcio_certs()?; + + Ok(self) + } + + /// Optional - the configuration to be used by the OCI client. + /// + /// This can be used when dealing with registries that are not using + /// TLS termination, or are using self-signed certificates. + pub fn with_oci_client_config(mut self, config: ClientConfig) -> Self { + self.oci_client_config = config; + self + } + + pub fn build(self) -> Result { + let rekor_pub_keys: Option> = self + .rekor_pub_keys + .map(|keys| { + keys.iter() + .filter_map(|(key_id, data)| { + match CosignVerificationKey::from_der(data, &SigningScheme::default()) { + Ok(key) => Some((key_id.clone(), key)), + Err(e) => { + info!("Cannot parse Rekor public key with id {key_id}: {e}"); + None + } + } + }) + .collect::>() + }) + .filter(|m| !m.is_empty()); + + let fulcio_cert_pool = if self.fulcio_certs.is_empty() { + info!("No Fulcio cert has been provided. Fulcio integration disabled"); + None + } else { + let cert_pool = CertificatePool::from_certificates(self.fulcio_certs, [])?; + Some(cert_pool) + }; + + let oci_client = oci_client::client::Client::new(self.oci_client_config.clone().into()); + + let registry_client: Box = { + cfg_if::cfg_if! { + if #[cfg(feature = "cached-client")] { + if self.enable_registry_caching { + Box::new(crate::registry::OciCachingClient { + registry_client: oci_client, + }) as Box + } else { + Box::new(crate::registry::OciClient { + registry_client: oci_client, + }) as Box + } + } else { + Box::new(crate::registry::OciClient { + registry_client: oci_client, + }) as Box + } + } + }; + + Ok(Client { + registry_client, + rekor_pub_keys, + fulcio_cert_pool, + }) + } +} diff --git a/vendor/src/cosign/constants.rs b/vendor/src/cosign/constants.rs new file mode 100644 index 0000000..4038827 --- /dev/null +++ b/vendor/src/cosign/constants.rs @@ -0,0 +1,36 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use const_oid::ObjectIdentifier; + +pub(crate) const SIGSTORE_ISSUER_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.1"); +pub(crate) const SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.2"); +pub(crate) const SIGSTORE_GITHUB_WORKFLOW_SHA_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.3"); +pub(crate) const SIGSTORE_GITHUB_WORKFLOW_NAME_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.4"); +pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.5"); +pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REF_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.6"); +/// OID of Ed25519, which is not included in the RustCrypto repo yet. +pub(crate) const ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); + +pub(crate) const SIGSTORE_OCI_MEDIA_TYPE: &str = "application/vnd.dev.cosign.simplesigning.v1+json"; +pub(crate) const SIGSTORE_SIGNATURE_ANNOTATION: &str = "dev.cosignproject.cosign/signature"; +pub(crate) const SIGSTORE_BUNDLE_ANNOTATION: &str = "dev.sigstore.cosign/bundle"; +pub(crate) const SIGSTORE_CERT_ANNOTATION: &str = "dev.sigstore.cosign/certificate"; diff --git a/vendor/src/cosign/constraint/annotation.rs b/vendor/src/cosign/constraint/annotation.rs new file mode 100644 index 0000000..d4c98e8 --- /dev/null +++ b/vendor/src/cosign/constraint/annotation.rs @@ -0,0 +1,73 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::{BTreeMap, HashMap}; + +use serde_json::Value; +use tracing::warn; + +use crate::{cosign::SignatureLayer, errors::Result}; + +use super::Constraint; + +/// Constraint for the annotations, which can be verified by [`crate::cosign::verification_constraint::AnnotationVerifier`] +/// +/// The [`crate::cosign::payload::SimpleSigning`] object can be enriched by a signer +/// with more annotations. +/// +/// A [`AnnotationMarker`] helps to add annotations to the [`crate::cosign::payload::SimpleSigning`] +/// of the given [`SignatureLayer`]. +/// +/// Warning: The signing step must not happen until all [`AnnotationMarker`] +/// have already performed `add_constraint`. +#[derive(Debug)] +pub struct AnnotationMarker { + pub annotations: HashMap, +} + +impl AnnotationMarker { + pub fn new(annotations: HashMap) -> Self { + Self { annotations } + } +} + +impl Constraint for AnnotationMarker { + fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result { + let mut annotations = match &signature_layer.simple_signing.optional { + Some(opt) => { + warn!(optional = ?opt, "already has an annotation field"); + opt.extra.clone() + } + None => BTreeMap::new(), + }; + + for (k, v) in &self.annotations { + if annotations.contains_key(k) && annotations[k] != *v { + warn!(key = ?k, "extra field already has a value"); + return Ok(false); + } + annotations.insert(k.to_owned(), Value::String(v.into())); + } + + let mut opt = signature_layer + .simple_signing + .optional + .clone() + .unwrap_or_default(); + opt.extra = annotations; + signature_layer.simple_signing.optional = Some(opt); + Ok(true) + } +} diff --git a/vendor/src/cosign/constraint/mod.rs b/vendor/src/cosign/constraint/mod.rs new file mode 100644 index 0000000..07e6dc2 --- /dev/null +++ b/vendor/src/cosign/constraint/mod.rs @@ -0,0 +1,73 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Structs that can be used to add constraints to [`crate::cosign::SignatureLayer`] +//! with special business logic. +//! +//! This module provides some common kinds of constraints: +//! * [`PrivateKeySigner`]: Attaching a signature +//! * [`AnnotationMarker`]: Adding extra annotations +//! +//! Developers can define ad-hoc constraint logic by creating a Struct that +//! implements the [`Constraint`] trait +//! +//! ## Warining +//! Because [`PrivateKeySigner`] will sign the whole data of a given +//! [`crate::cosign::SignatureLayer`], developers **must** ensure that +//! a [`PrivateKeySigner`] is the last constraint to be applied on a +//! [`crate::cosign::SignatureLayer`]. Before that, all constraints that +//! may modify the content of the [`crate::cosign::SignatureLayer`] should +//! have been applied already. + +use super::SignatureLayer; +use crate::errors::Result; + +pub type SignConstraintVec = Vec>; +pub type SignConstraintRefVec<'a> = Vec<&'a Box>; + +pub trait Constraint: std::fmt::Debug { + /// Given a mutable reference of [`crate::cosign::SignatureLayer`], return + /// `true` if the constraint is applied successfully. + /// + /// Developer can use the + /// [`crate::errors::SigstoreError::ApplyConstraintError`] error + /// when something goes wrong inside of the application logic. + /// + /// ``` + /// use sigstore::{ + /// cosign::constraint::Constraint, + /// cosign::signature_layers::SignatureLayer, + /// errors::{SigstoreError, Result}, + /// }; + /// + /// #[derive(Debug)] + /// struct MyConstraint{} + /// + /// impl Constraint for MyConstraint { + /// fn add_constraint(&self, _sl: &mut SignatureLayer) -> Result { + /// Err(SigstoreError::ApplyConstraintError( + /// "something went wrong!".to_string())) + /// } + /// } + /// + /// ``` + fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result; +} + +pub mod annotation; +pub use annotation::AnnotationMarker; + +pub mod signature; +pub use self::signature::PrivateKeySigner; diff --git a/vendor/src/cosign/constraint/signature.rs b/vendor/src/cosign/constraint/signature.rs new file mode 100644 index 0000000..696fe1c --- /dev/null +++ b/vendor/src/cosign/constraint/signature.rs @@ -0,0 +1,74 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Structs that can be used to sign a [`crate::cosign::SignatureLayer`] + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use tracing::warn; +use zeroize::Zeroizing; + +use crate::{ + cosign::SignatureLayer, + crypto::{SigStoreSigner, SigningScheme, signing_key::SigStoreKeyPair}, + errors::{Result, SigstoreError}, +}; + +use super::Constraint; + +/// Sign the [`SignatureLayer`] with the given [`SigStoreSigner`]. +/// This constraint must be the last one to applied to a [`SignatureLayer`], +/// since all the plaintext is defined. +#[derive(Debug)] +pub struct PrivateKeySigner { + key: SigStoreSigner, +} + +impl PrivateKeySigner { + /// Create a new [PrivateKeySigner] with given raw PEM data of a + /// private key. + pub fn new_with_raw( + key_raw: Zeroizing>, + password: Zeroizing>, + signing_scheme: &SigningScheme, + ) -> Result { + let signer = match password.is_empty() { + true => SigStoreKeyPair::from_pem(&key_raw), + false => SigStoreKeyPair::from_encrypted_pem(&key_raw, &password), + } + .map_err(|e| SigstoreError::ApplyConstraintError(e.to_string()))? + .to_sigstore_signer(signing_scheme) + .map_err(|e| SigstoreError::ApplyConstraintError(e.to_string()))?; + + Ok(Self { key: signer }) + } + + pub fn new_with_signer(signer: SigStoreSigner) -> Self { + Self { key: signer } + } +} + +impl Constraint for PrivateKeySigner { + fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result { + if signature_layer.signature.is_some() { + warn!(signature = ?signature_layer.signature, "already has signature"); + return Ok(false); + } + signature_layer.raw_data = serde_json::to_vec(&signature_layer.simple_signing)?; + let sig = self.key.sign(&signature_layer.raw_data)?; + let sig_base64 = BASE64_STD_ENGINE.encode(sig); + signature_layer.signature = Some(sig_base64); + Ok(true) + } +} diff --git a/vendor/src/cosign/mod.rs b/vendor/src/cosign/mod.rs new file mode 100644 index 0000000..551c248 --- /dev/null +++ b/vendor/src/cosign/mod.rs @@ -0,0 +1,683 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Structs providing cosign verification capabilities +//! +//! The focus of this crate is to provide the verification capabilities of cosign, +//! not the signing one. +//! +//! Sigstore verification can be done using [`sigstore::cosign::Client`](crate::cosign::client::Client). +//! Instances of this struct can be created via the [`sigstore::cosign::ClientBuilder`](crate::cosign::client_builder::ClientBuilder). +//! +//! ## What is currently supported +//! +//! The crate implements the following verification mechanisms: +//! +//! * Verify using a given key +//! * Verify bundle produced by transparency log (Rekor) +//! * Verify signature produced in keyless mode, using Fulcio Web-PKI +//! +//! Signature annotations and certificate email can be provided at verification time. +//! +//! ## Unit testing inside of our own libraries +//! +//! In case you want to mock sigstore interactions inside of your own code, you +//! can implement the [`CosignCapabilities`] trait inside of your test suite. + +use std::collections::BTreeMap; + +use async_trait::async_trait; +use tracing::warn; + +use crate::errors::{Result, SigstoreApplicationConstraintsError, SigstoreVerifyConstraintsError}; +use crate::registry::{Auth, PushResponse}; + +use crate::crypto::{CosignVerificationKey, Signature}; +use crate::errors::SigstoreError; +use pkcs8::der::Decode; +use x509_cert::Certificate; + +pub mod bundle; +pub(crate) mod constants; +pub mod signature_layers; +pub use signature_layers::SignatureLayer; + +pub mod client; +pub use self::client::Client; + +pub mod client_builder; +pub use self::client_builder::ClientBuilder; + +pub mod verification_constraint; +pub use self::constraint::{Constraint, SignConstraintRefVec}; +use self::verification_constraint::{VerificationConstraint, VerificationConstraintRefVec}; + +pub mod payload; +use crate::registry::oci_reference::OciReference; +pub use payload::simple_signing; + +pub mod constraint; + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +/// Cosign Abilities that have to be implemented by a +/// Cosign client +pub trait CosignCapabilities { + /// Calculate the cosign image reference. + /// This is the location cosign stores signatures. + async fn triangulate( + &mut self, + image: &OciReference, + auth: &Auth, + ) -> Result<(OciReference, String)>; + + /// Returns the list of [`SignatureLayer`] + /// objects that are associated with the given signature object. + /// + /// Each layer is verified, to ensure it contains legitimate data. + /// + /// ## Layers with embedded certificate + /// + /// A signature can contain a certificate, this happens when signatures + /// are produced in keyless mode or when a PKCS11 tokens are used. + /// + /// The certificate is added to [`SignatureLayer::certificate_signature`] + /// only when it can be trusted. + /// + /// In order to trust an embedded certificate, the following prerequisites + /// must be satisfied: + /// + /// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must + /// have been created with Rekor integration enabled (see [`crate::trust::sigstore::ManualTrustRoot`]) + /// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must + /// have been created with Fulcio integration enabled (see [`crate::trust::sigstore::ManualTrustRoot]) + /// * The layer must include a bundle produced by Rekor + /// + /// > Note well: the [`trust::sigstore`](crate::trust::sigstore) module provides helper structs and methods + /// > to obtain this data from the official TUF repository of the Sigstore project. + /// + /// When the embedded certificate cannot be verified, [`SignatureLayer::certificate_signature`] + /// is going to be `None`. + /// + /// ## Usage + /// + /// These returned objects can then be verified against + /// [`VerificationConstraints`](crate::cosign::verification_constraint::VerificationConstraint) + /// using the [`verify_constraints`] function. + async fn trusted_signature_layers( + &mut self, + auth: &Auth, + source_image_digest: &str, + cosign_image: &OciReference, + ) -> Result>; + + /// Push [`SignatureLayer`] objects to the registry. This function will do + /// the following steps: + /// * Generate a series of [`oci_client::client::ImageLayer`]s due to + /// the given [`Vec`]. + /// * Generate a `OciImageManifest` of [`oci_client::manifest::OciManifest`] + /// due to the given `source_image_digest` and `signature_layers`. It supports + /// to be extended when newly published + /// [Referrers API of OCI Registry v1.1.0](https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers), + /// is prepared. At that time, + /// [an artifact manifest](https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/artifact.md) + /// will be created instead of [an image manifest](https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/manifest.md). + /// * Push the generated manifest together with the layers + /// to the `target_reference`. `target_reference` contains information + /// about the registry, repository and tag. + /// + /// The parameters: + /// - `annotations`: annotations of the generated manifest + /// - `auth`: Credential used to access the registry + /// - `target_reference`: target reference to push the manifest + /// - `signature_layers`: [`SignatureLayer`] objects containing signature information + async fn push_signature( + &mut self, + annotations: Option>, + auth: &Auth, + target_reference: &OciReference, + signature_layers: Vec, + ) -> Result; + + /// Verifies the signature produced by cosign when signing the given blob via the `cosign sign-blob` command + /// + /// The parameters: + /// * `cert`: a PEM encoded x509 certificate that contains the public key used to verify the signature. + /// Note that cert is not double-base64-encoded like the output of sigstore/cosign is. + /// * `signature`: the base64 encoded signature of the blob that has to be verified + /// * `blob`: the contents of the blob + /// + /// This function returns `Ok())` when the given signature has been verified, otherwise returns an `Err`. + fn verify_blob(cert: &str, signature: &str, blob: &[u8]) -> Result<()> { + let pem = pem::parse(cert)?; + let cert = Certificate::from_der(pem.contents()).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!("parse der into cert failed: {e}")) + })?; + let spki = cert.tbs_certificate.subject_public_key_info; + let ver_key = CosignVerificationKey::try_from(&spki)?; + let signature = Signature::Base64Encoded(signature.as_bytes()); + ver_key.verify_signature(signature, blob)?; + Ok(()) + } + + /// + /// Verifies the signature produced by cosign when signing the given blob via the `cosign sign-blob` command + /// + /// The parameters: + /// * `public_key`: the public key used to verify the signature, PEM encoded + /// * `signature`: the base64 encoded signature of the blob that has to be verified + /// * `blob`: the contents of the blob + /// + /// This function returns `Ok())` when the given signature has been verified, otherwise returns an `Err`. + fn verify_blob_with_public_key(public_key: &str, signature: &str, blob: &[u8]) -> Result<()> { + let ver_key = CosignVerificationKey::try_from_pem(public_key.as_bytes())?; + let signature = Signature::Base64Encoded(signature.as_bytes()); + ver_key.verify_signature(signature, blob)?; + Ok(()) + } +} + +/// Given a list of trusted `SignatureLayer`, find all the constraints that +/// aren't satisfied by the layers. +/// +/// If there's any unsatisfied constraints it means that the image failed +/// verification. +/// If there's no unsatisfied constraints it means that the image passed +/// verification. +/// +/// Returns a `Result` with either `Ok()` for passed verification or +/// [`SigstoreVerifyConstraintsError`] +/// which contains a vector of references to unsatisfied constraints. +/// +/// See the documentation of the [`cosign::verification_constraint`](crate::cosign::verification_constraint) module for more +/// details about how to define verification constraints. +pub fn verify_constraints<'a, 'b, I>( + signature_layers: &'a [SignatureLayer], + constraints: I, +) -> std::result::Result<(), SigstoreVerifyConstraintsError<'b>> +where + I: Iterator>, +{ + let unsatisfied_constraints: VerificationConstraintRefVec = constraints.filter(|c| { + let mut is_c_unsatisfied = true; + signature_layers.iter().any( | sl | { + // iterate through all layers and find if at least one layer + // satisfies constraint. If so, we stop iterating + match c.verify(sl) { + Ok(is_sl_verified) => { + is_c_unsatisfied = !is_sl_verified; + is_sl_verified // if true, stop searching + } + Err(e) => { + warn!(error = ?e, constraint = ?c, "Skipping layer because constraint verification returned an error"); + // handle errors as verification failures + is_c_unsatisfied = true; + false // keep searching to see if other layer satisfies + } + } + }); + is_c_unsatisfied // if true, constraint gets filtered into result + }).collect(); + + if unsatisfied_constraints.is_empty() { + Ok(()) + } else { + Err(SigstoreVerifyConstraintsError { + unsatisfied_constraints, + }) + } +} + +/// Given a [`SignatureLayer`], apply all the constraints to that. +/// +/// If there's any constraints that fails to apply, it means the +/// application process fails. +/// If all constraints succeed applying, it means that this layer +/// passes applying constraints process. +/// +/// Returns a `Result` with either `Ok()` for success or +/// [`SigstoreApplicationConstraintsError`] +/// which contains a vector of references to unapplied constraints. +/// +/// See the documentation of the [`cosign::constraint`](crate::cosign::constraint) module for more +/// details about how to define constraints. +pub fn apply_constraints<'a, 'b, I>( + signature_layer: &'a mut SignatureLayer, + constraints: I, +) -> std::result::Result<(), SigstoreApplicationConstraintsError<'b>> +where + I: Iterator>, +{ + let unapplied_constraints: SignConstraintRefVec = constraints + .filter(|c| match c.add_constraint(signature_layer) { + Ok(is_applied) => !is_applied, + Err(e) => { + warn!(error = ?e, constraint = ?c, "Applying constraint failed due to error"); + true + } + }) + .collect(); + + if unapplied_constraints.is_empty() { + Ok(()) + } else { + Err(SigstoreApplicationConstraintsError { + unapplied_constraints, + }) + } +} + +#[cfg(test)] +mod tests { + use pki_types::CertificateDer; + use serde_json::json; + + use super::constraint::{AnnotationMarker, PrivateKeySigner}; + use super::verification_constraint::cert_subject_email_verifier::StringVerifier; + use super::*; + use crate::cosign::signature_layers::CertificateSubject; + use crate::cosign::signature_layers::tests::build_correct_signature_layer_with_certificate; + use crate::cosign::simple_signing::Optional; + use crate::cosign::verification_constraint::{ + AnnotationVerifier, CertSubjectEmailVerifier, VerificationConstraintVec, + }; + use crate::crypto::SigningScheme; + use crate::crypto::certificate_pool::CertificatePool; + + #[cfg(feature = "test-registry")] + use testcontainers::{core::WaitFor, runners::AsyncRunner}; + + pub(crate) const REKOR_PUB_KEY: &str = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY-----"#; + + pub(crate) const REKOR_PUB_KEY_ID: &str = + "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"; + + const FULCIO_CRT_1_PEM: &str = r#"-----BEGIN CERTIFICATE----- +MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu +ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy +A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas +taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm +MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u +Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx +Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup +Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== +-----END CERTIFICATE-----"#; + + const FULCIO_CRT_2_PEM: &str = r#"-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE-----"#; + + #[cfg(feature = "test-registry")] + const SIGNED_IMAGE: &str = "busybox:1.34"; + + pub(crate) fn get_fulcio_cert_pool() -> CertificatePool { + fn pem_to_der(input: &str) -> CertificateDer<'_> { + let pem_cert = pem::parse(input).unwrap(); + assert_eq!(pem_cert.tag(), "CERTIFICATE"); + CertificateDer::from(pem_cert.into_contents()) + } + let certificates = vec![pem_to_der(FULCIO_CRT_1_PEM), pem_to_der(FULCIO_CRT_2_PEM)]; + + CertificatePool::from_certificates(certificates, []).unwrap() + } + + pub(crate) fn get_rekor_public_key() -> (String, CosignVerificationKey) { + let key = + CosignVerificationKey::from_pem(REKOR_PUB_KEY.as_bytes(), &SigningScheme::default()) + .expect("Cannot create test REKOR_PUB_KEY"); + (REKOR_PUB_KEY_ID.to_string(), key) + } + + #[test] + fn verify_constraints_all_satisfied() { + let email = "alice@example.com".to_string(); + let issuer = "an issuer".to_string(); + + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert("key1".into(), "value1".into()); + annotations.insert("key2".into(), "value2".into()); + + let mut layers: Vec = Vec::new(); + for _ in 0..5 { + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email(email.clone()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let mut extra: BTreeMap = annotations + .iter() + .map(|(k, v)| (k.clone(), json!(v))) + .collect(); + extra.insert("something extra".into(), json!("value extra")); + + let mut simple_signing = sl.simple_signing; + let optional = Optional { + creator: Some("test".into()), + timestamp: None, + extra, + }; + simple_signing.optional = Some(optional); + sl.simple_signing = simple_signing; + + layers.push(sl); + } + + let mut constraints: VerificationConstraintVec = Vec::new(); + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email.clone()), + issuer: Some(StringVerifier::ExactMatch(issuer)), + }; + constraints.push(Box::new(vc)); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email), + issuer: None, + }; + constraints.push(Box::new(vc)); + + let vc = AnnotationVerifier { annotations }; + constraints.push(Box::new(vc)); + + verify_constraints(&layers, constraints.iter()).expect("should not return an error"); + } + + #[test] + fn verify_constraints_none_satisfied() { + let email = "alice@example.com".to_string(); + let issuer = "an issuer".to_string(); + let wrong_email = "bob@example.com".to_string(); + + let mut layers: Vec = Vec::new(); + for _ in 0..5 { + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email(email.clone()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let mut extra: BTreeMap = BTreeMap::new(); + extra.insert("something extra".into(), json!("value extra")); + + let mut simple_signing = sl.simple_signing; + let optional = Optional { + creator: Some("test".into()), + timestamp: None, + extra, + }; + simple_signing.optional = Some(optional); + sl.simple_signing = simple_signing; + + layers.push(sl); + } + + let mut constraints: VerificationConstraintVec = Vec::new(); + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(wrong_email.clone()), + issuer: Some(StringVerifier::ExactMatch(issuer)), // correct issuer + }; + constraints.push(Box::new(vc)); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(wrong_email), + issuer: None, // missing issuer, more relaxed + }; + constraints.push(Box::new(vc)); + + let err = + verify_constraints(&layers, constraints.iter()).expect_err("we should have an err"); + assert_eq!(err.unsatisfied_constraints.len(), 2); + } + + #[test] + fn verify_constraints_some_unsatisfied() { + let email = "alice@example.com".to_string(); + let issuer = "an issuer".to_string(); + let email_incorrect = "bob@example.com".to_string(); + + let mut layers: Vec = Vec::new(); + for _ in 0..5 { + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email(email.clone()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let mut extra: BTreeMap = BTreeMap::new(); + extra.insert("something extra".into(), json!("value extra")); + + let mut simple_signing = sl.simple_signing; + let optional = Optional { + creator: Some("test".into()), + timestamp: None, + extra, + }; + simple_signing.optional = Some(optional); + sl.simple_signing = simple_signing; + + layers.push(sl); + } + + let mut constraints: VerificationConstraintVec = Vec::new(); + let satisfied_constraint = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email), + issuer: Some(StringVerifier::ExactMatch(issuer)), + }; + constraints.push(Box::new(satisfied_constraint)); + + let unsatisfied_constraint = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email_incorrect), + issuer: None, + }; + constraints.push(Box::new(unsatisfied_constraint)); + + let err = + verify_constraints(&layers, constraints.iter()).expect_err("we should have an err"); + assert_eq!(err.unsatisfied_constraints.len(), 1); + } + + #[test] + fn add_constrains_all_succeed() { + let mut signature_layer = SignatureLayer::new_unsigned( + &"test_image".parse().unwrap(), + "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ) + .expect("create SignatureLayer failed"); + + let signer = SigningScheme::ECDSA_P256_SHA256_ASN1 + .create_signer() + .expect("create signer failed"); + let signer = PrivateKeySigner::new_with_signer(signer); + + let annotations = [(String::from("key"), String::from("value"))].into(); + let annotations = AnnotationMarker::new(annotations); + + let constrains: Vec> = vec![Box::new(signer), Box::new(annotations)]; + apply_constraints(&mut signature_layer, constrains.iter()).expect("no error should occur"); + } + + #[test] + fn add_constrain_some_failed() { + let mut signature_layer = SignatureLayer::new_unsigned( + &"test_image".parse().unwrap(), + "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ) + .expect("create SignatureLayer failed"); + + let signer = SigningScheme::ECDSA_P256_SHA256_ASN1 + .create_signer() + .expect("create signer failed"); + let signer = PrivateKeySigner::new_with_signer(signer); + let another_signer_of_same_layer = SigningScheme::ECDSA_P256_SHA256_ASN1 + .create_signer() + .expect("create signer failed"); + let another_signer_of_same_layer = + PrivateKeySigner::new_with_signer(another_signer_of_same_layer); + + let annotations = [(String::from("key"), String::from("value"))].into(); + let annotations = AnnotationMarker::new(annotations); + + let constrains: Vec> = vec![ + Box::new(signer), + Box::new(annotations), + Box::new(another_signer_of_same_layer), + ]; + apply_constraints(&mut signature_layer, constrains.iter()) + .expect_err("no error should occur"); + } + + #[cfg(feature = "test-registry")] + #[rstest::rstest] + #[case(SigningScheme::RSA_PSS_SHA256(2048))] + #[case(SigningScheme::RSA_PSS_SHA384(2048))] + #[case(SigningScheme::RSA_PSS_SHA512(2048))] + #[case(SigningScheme::RSA_PKCS1_SHA256(2048))] + #[case(SigningScheme::RSA_PKCS1_SHA384(2048))] + #[case(SigningScheme::RSA_PKCS1_SHA512(2048))] + #[case(SigningScheme::ECDSA_P256_SHA256_ASN1)] + #[case(SigningScheme::ECDSA_P384_SHA384_ASN1)] + #[case(SigningScheme::ED25519)] + #[tokio::test] + #[serial_test::serial] + async fn sign_verify_image(#[case] signing_scheme: SigningScheme) { + let test_container = registry_image() + .start() + .await + .expect("failed to start registry"); + let port = test_container + .get_host_port_ipv4(5000) + .await + .expect("failed to get port"); + + let mut client = ClientBuilder::default() + .enable_registry_caching() + .with_oci_client_config(crate::registry::ClientConfig { + protocol: crate::registry::ClientProtocol::HttpsExcept(vec![format!( + "localhost:{}", + port + )]), + ..Default::default() + }) + .build() + .expect("failed to create oci client"); + + let image_ref = format!("localhost:{}/{}", port, SIGNED_IMAGE) + .parse::() + .expect("failed to parse reference"); + prepare_image_to_be_signed(&mut client, &image_ref).await; + + let (cosign_signature_image, source_image_digest) = client + .triangulate(&image_ref, &crate::registry::Auth::Anonymous) + .await + .expect("get manifest failed"); + let mut signature_layer = SignatureLayer::new_unsigned(&image_ref, &source_image_digest) + .expect("create SignatureLayer failed"); + let signer = signing_scheme + .create_signer() + .expect("create signer failed"); + let pubkey = signer + .to_sigstore_keypair() + .expect("to keypair failed") + .public_key_to_pem() + .expect("derive public key failed"); + + let signer = PrivateKeySigner::new_with_signer(signer); + if !signer + .add_constraint(&mut signature_layer) + .expect("sign SignatureLayer failed") + { + panic!("failed to sign SignatureLayer"); + }; + + client + .push_signature( + None, + &Auth::Anonymous, + &cosign_signature_image, + vec![signature_layer], + ) + .await + .expect("push signature failed"); + + dbg!("start to verify"); + + let (cosign_image, manifest_digest) = client + .triangulate(&image_ref, &Auth::Anonymous) + .await + .expect("triangulate failed"); + let signature_layers = client + .trusted_signature_layers(&Auth::Anonymous, &manifest_digest, &cosign_image) + .await + .expect("get trusted signature layers failed"); + let pk_verifier = + verification_constraint::PublicKeyVerifier::new(pubkey.as_bytes(), &signing_scheme) + .expect("create PublicKeyVerifier failed"); + assert_eq!(signature_layers.len(), 1); + let res = pk_verifier + .verify(&signature_layers[0]) + .expect("failed to verify"); + assert!(res); + } + + #[cfg(feature = "test-registry")] + async fn prepare_image_to_be_signed(client: &mut Client, image_ref: &OciReference) { + let data = client + .registry_client + .pull( + &SIGNED_IMAGE.parse().expect("failed to parse image ref"), + &oci_client::secrets::RegistryAuth::Anonymous, + vec![oci_client::manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE], + ) + .await + .expect("pull test image failed"); + + client + .registry_client + .push( + &image_ref.oci_reference, + &data.layers[..], + data.config.clone(), + &oci_client::secrets::RegistryAuth::Anonymous, + None, + ) + .await + .expect("push test image failed"); + } + + #[cfg(feature = "test-registry")] + fn registry_image() -> testcontainers::GenericImage { + testcontainers::GenericImage::new("docker.io/library/registry", "2") + .with_wait_for(WaitFor::message_on_stderr("listening on ")) + } +} diff --git a/vendor/src/cosign/payload/mod.rs b/vendor/src/cosign/payload/mod.rs new file mode 100644 index 0000000..1245fb4 --- /dev/null +++ b/vendor/src/cosign/payload/mod.rs @@ -0,0 +1,21 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module defines different kinds of payload to be signed +//! in cosign. Now it supports: +//! * `SimpleSigning`: Refer to + +pub mod simple_signing; +pub use simple_signing::SimpleSigning; diff --git a/vendor/src/cosign/payload/simple_signing.rs b/vendor/src/cosign/payload/simple_signing.rs new file mode 100644 index 0000000..58e49f7 --- /dev/null +++ b/vendor/src/cosign/payload/simple_signing.rs @@ -0,0 +1,333 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module provides a series of Rust structs that implementation +//! the Container signature format described +//! [here](https://github.com/containers/image/blob/a5061e5a5f00333ea3a92e7103effd11c6e2f51d/docs/containers-signature.5.md#json-data-format). + +use crate::registry::OciReference; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::{collections::BTreeMap, fmt}; +use tracing::{debug, error, info}; + +/// Default type name of [`Critical`] when doing cosign signing +pub const CRITICAL_TYPE_NAME: &str = "cosign container image signature"; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SimpleSigning { + pub critical: Critical, + pub optional: Option, +} + +impl fmt::Display for SimpleSigning { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + serde_json::to_string_pretty(self).map_err(|e| { + error!(error=?e, simple_signing=?self, "Cannot convert to JSON"); + fmt::Error + })? + ) + } +} + +impl SimpleSigning { + /// Create a new simple signing payload due to the given image reference + /// and manifest_digest + pub fn new(image_ref: &OciReference, manifest_digest: &str) -> Self { + Self { + critical: Critical { + type_name: CRITICAL_TYPE_NAME.to_string(), + image: Image { + docker_manifest_digest: manifest_digest.to_string(), + }, + identity: Identity { + docker_reference: image_ref.to_string(), + }, + }, + optional: None, + } + } + + /// Checks whether all the provided `annotations` are satisfied + pub fn satisfies_annotations(&self, annotations: &BTreeMap) -> bool { + if annotations.is_empty() { + debug!("no annotations have been provided -> returning true"); + return true; + } + + match &self.optional { + Some(opt) => opt.satisfies_annotations(annotations), + None => { + info!( + simple_signing=?self, + ?annotations, + "annotations not satisfied because `optional` attribute is None" + ); + false + } + } + } + + /// Compares the digest given by the user with the Docker manifest digest + /// stored inside of the Critical object + pub fn satisfies_manifest_digest(&self, expected_digest: &str) -> bool { + let matches = self.critical.image.docker_manifest_digest == expected_digest; + if !matches { + info!( + simple_signing=?self, + expected_digest, + "expected digest not found" + ); + } + matches + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Critical { + //TODO: should we validate the contents of this attribute to ensure it's "cosign container image signature"? + pub identity: Identity, + pub image: Image, + #[serde(rename = "type")] + pub type_name: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct Image { + pub docker_manifest_digest: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct Identity { + pub docker_reference: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Optional { + #[serde(skip_serializing_if = "Option::is_none")] + pub creator: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp: Option, + + #[serde(flatten)] + pub extra: BTreeMap, +} + +impl Optional { + /// Checks whether all the provided `annotations` are satisfied + pub fn satisfies_annotations(&self, annotations: &BTreeMap) -> bool { + if self.extra.is_empty() { + info!( + ?annotations, + "Annotations are not satisfied, no annotations are part of the Simple Signing object" + ); + return false; + } + + for (req_key, req_val) in annotations { + match self.extra.get(req_key) { + Some(curr_val) => match curr_val { + serde_json::Value::String(s) => { + if req_val != s { + info!( + annotation = ?req_key, + expected_value = ?req_val, + current_value = ?s, + "Annotation not satisfied" + ); + return false; + } + } + serde_json::Value::Number(n) => { + let curr_val = n.to_string(); + if req_val != &curr_val { + info!( + annotation = ?req_key, + expected_value = ?req_val, + current_value = ?n, + "Annotation not satisfied" + ); + return false; + } + } + serde_json::Value::Bool(b) => { + let curr_val = if *b { "true" } else { "false" }; + if req_val != curr_val { + info!( + annotation = ?req_key, + expected_value = ?req_val, + current_value = ?curr_val, + "Annotation not satisfied" + ); + return false; + } + } + _ => { + error!( + annotation = ?req_key, + expected_value = ?req_val, + current_value = ?curr_val.to_string(), + "Annotation type not handled" + ); + return false; + } + }, + None => { + info!( + missing_annotation = ?req_key, + layer_annotations= ?self.extra, + "Annotation not satisfied"); + return false; + } + } + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn simple_signing_does_not_satisfy_annotations_when_optional_is_none() { + let ss_json = json!({ + "critical": { + "type": "type_foo", + "image": { + "docker-manifest-digest": "sha256:something" + }, + "identity": { + "docker-reference": "registry.foo.bar/busybox" + } + } + }); + let ss: SimpleSigning = serde_json::from_value(ss_json).unwrap(); + + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert(String::from("env"), String::from("prod")); + + assert!(!ss.satisfies_annotations(&annotations)); + } + + #[test] + fn simple_signing_satisfies_empty_annotations_even_when_optional_is_none() { + let ss_json = json!({ + "critical": { + "type": "type_foo", + "image": { + "docker-manifest-digest": "sha256:something" + }, + "identity": { + "docker-reference": "registry.foo.bar/busybox" + } + } + }); + let ss: SimpleSigning = serde_json::from_value(ss_json).unwrap(); + let annotations: BTreeMap = BTreeMap::new(); + + assert!(ss.satisfies_annotations(&annotations)); + } + + #[test] + fn optional_has_all_the_required_annotations() { + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert(String::from("env"), String::from("prod")); + annotations.insert(String::from("number"), String::from("1")); + annotations.insert(String::from("bool"), String::from("true")); + + let optional_json = json!({ + "env": "prod", + "number": 1, + "bool": true + }); + let optional: Optional = serde_json::from_value(optional_json).unwrap(); + + assert!(optional.satisfies_annotations(&annotations)); + } + + #[test] + fn optional_does_not_satisfy_annotations_because_one_annotation_is_missing() { + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert(String::from("env"), String::from("prod")); + annotations.insert(String::from("owner"), String::from("flavio")); + + let optional_json = json!({ + "owner": "flavio", + "team": "devops" + }); + let optional: Optional = serde_json::from_value(optional_json).unwrap(); + + assert!(!optional.satisfies_annotations(&annotations)); + } + + #[test] + fn optional_does_not_satisfy_annotations_because_one_annotation_has_different_value() { + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert(String::from("env"), String::from("prod")); + annotations.insert(String::from("owner"), String::from("flavio")); + + let optional_json = json!({ + "env": "staging", + "owner": "flavio", + "team": "devops" + }); + let optional: Optional = serde_json::from_value(optional_json).unwrap(); + + assert!(!optional.satisfies_annotations(&annotations)); + } + + #[test] + fn optional_satisfies_annotations_when_no_annotation_is_provided() { + let annotations: BTreeMap = BTreeMap::new(); + + let optional_json = json!({ + "env": "prod", + "owner": "flavio", + "team": "devops" + }); + let optional: Optional = serde_json::from_value(optional_json).unwrap(); + + assert!(optional.satisfies_annotations(&annotations)); + } + + #[test] + fn simple_signing_satisfy_manifest_digest_works_as_expected() { + let expected_digest = "sha256:something"; + let ss_json = json!({ + "critical": { + "type": "type_foo", + "image": { + "docker-manifest-digest": expected_digest + }, + "identity": { + "docker-reference": "registry.foo.bar/busybox" + } + } + }); + let ss: SimpleSigning = serde_json::from_value(ss_json).unwrap(); + + assert!(ss.satisfies_manifest_digest(expected_digest)); + assert!(!ss.satisfies_manifest_digest("something different")); + } +} diff --git a/vendor/src/cosign/signature_layers.rs b/vendor/src/cosign/signature_layers.rs new file mode 100644 index 0000000..02c2b6a --- /dev/null +++ b/vendor/src/cosign/signature_layers.rs @@ -0,0 +1,1035 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use const_oid::ObjectIdentifier; +use digest::Digest; +use oci_client::client::ImageLayer; +use serde::Serialize; +use std::collections::BTreeMap; +use std::fmt; +use tracing::{debug, info, warn}; +use x509_cert::Certificate; +use x509_cert::der::DecodePem; +use x509_cert::ext::pkix::SubjectAltName; +use x509_cert::ext::pkix::name::GeneralName; + +use super::bundle::Bundle; +use super::constants::{ + SIGSTORE_BUNDLE_ANNOTATION, SIGSTORE_CERT_ANNOTATION, SIGSTORE_GITHUB_WORKFLOW_NAME_OID, + SIGSTORE_GITHUB_WORKFLOW_REF_OID, SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID, + SIGSTORE_GITHUB_WORKFLOW_SHA_OID, SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID, SIGSTORE_ISSUER_OID, + SIGSTORE_OCI_MEDIA_TYPE, SIGSTORE_SIGNATURE_ANNOTATION, +}; +use crate::crypto::certificate_pool::CertificatePool; +use crate::registry::oci_reference::OciReference; +use crate::{ + cosign::simple_signing::SimpleSigning, + crypto::{self, CosignVerificationKey, Signature}, + errors::{Result, SigstoreError}, +}; + +/// Describe the details of a certificate produced when signing artifacts +/// using the keyless mode. +#[derive(Clone, Debug, Serialize)] +pub struct CertificateSignature { + /// The verification key embedded into the Certificate + #[serde(skip_serializing)] + pub verification_key: CosignVerificationKey, + /// The unique ID associated to the identity + pub subject: CertificateSubject, + /// The issuer used by the signer to authenticate. (e.g. GitHub, GitHub Action, Microsoft, Google,...) + pub issuer: Option, + /// The trigger of the GitHub workflow (e.g. `push`) + pub github_workflow_trigger: Option, + /// The commit ID that triggered the GitHub workflow + pub github_workflow_sha: Option, + /// The name of the GitHub workflow (e.g. `release artifact`) + pub github_workflow_name: Option, + /// The repository that owns the GitHub workflow (e.g. `octocat/example-repo`) + pub github_workflow_repository: Option, + /// The Git ref of the commit that triggered the GitHub workflow (e.g. `refs/tags/v0.9.9`) + pub github_workflow_ref: Option, +} + +impl fmt::Display for CertificateSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = format!( + r#"CertificateSignature +- issuer: {:?} +- subject: {:?} +- GitHub Workflow trigger: {:?} +- GitHub Workflow SHA: {:?} +- GitHub Workflow name: {:?} +- GitHub Workflow repository: {:?} +- GitHub Workflow ref: {:?} +---"#, + self.issuer, + self.subject, + self.github_workflow_trigger, + self.github_workflow_sha, + self.github_workflow_name, + self.github_workflow_repository, + self.github_workflow_ref, + ); + + write!(f, "{msg}") + } +} + +/// Types of identities associated with the signer. +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type", content = "value")] +pub enum CertificateSubject { + /// An email address. This is what is used when the signer authenticated himself using something like his GitHub/Google account + Email(String), + /// A URL. This is used for example by the OIDC token issued by GitHub Actions + Uri(String), +} + +/// Object that contains all the data about a `SimpleSigning` object. +/// +/// The struct provides some helper methods that can be used at verification +/// time. +/// +/// Note well, the information needed to build a SignatureLayer are spread over +/// two places: +/// * The manifest of the signature object created by cosign +/// * One or more SIGSTORE_OCI_MEDIA_TYPE layers +/// +/// End users of this library are not supposed to create this object directly. +/// `SignatureLayer` objects are instead obtained by using the +/// [`sigstore::cosign::Client::trusted_signature_layers`](crate::cosign::client::Client) +/// method. +#[derive(Clone, Debug, Serialize)] +pub struct SignatureLayer { + /// The Simple Signing object associated with this layer + pub simple_signing: SimpleSigning, + /// The digest of the layer + pub oci_digest: String, + /// The certificate holding the identity of the signer, plus his + /// verification key. This exists for signature done with keyless mode or + /// when a PKCS11 token was used. + /// + /// The value of `CertificateSignature` is `None` + /// when no certificate was embedded into the + /// layer, or when the embedded certificate could not be verified. + /// + /// Having a `None` value will rightfully cause the + /// keyless verifiers like + /// [`CertSubjectEmailVerifier`](crate::cosign::verification_constraint::CertSubjectEmailVerifier) + /// or + /// [`CertSubjectUrlVerifier`](crate::cosign::verification_constraint::CertSubjectUrlVerifier) + /// to fail verification. + /// However, it will still be possible to use the + /// [`PublicKeyVerifier`](crate::cosign::verification_constraint::PublicKeyVerifier) + /// to verify the layer. This can be useful to verify signatures produced + /// with a PKCS11 token, but with Rekor's integration disabled at + /// signature time. + pub certificate_signature: Option, + /// The bundle produced by Rekor. + pub bundle: Option, + #[serde(skip_serializing)] + pub signature: Option, + #[serde(skip_serializing)] + pub raw_data: Vec, +} + +impl fmt::Display for SignatureLayer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = format!( + r#"--- +# SignatureLayer +## digest +{} + +## signature +{:?} + +## bundle: +{:?} + +## certificate signature +{} + +## Simple Signing +{} +---"#, + self.oci_digest, + self.signature, + self.bundle, + self.certificate_signature + .clone() + .map(|cs| cs.to_string()) + .unwrap_or_else(|| "None".to_string()), + self.simple_signing, + ); + + write!(f, "{msg}") + } +} + +impl SignatureLayer { + /// Create a [`SignatureLayer`], this function will generate a [`SimpleSigning`] + /// payload due to the given reference of image and the digest of the manifest. + /// However, the resulted [`SignatureLayer`] does not have a signature, and it + /// should be manually generated. + /// + /// ## Usage + /// ```rust,no_run + /// use sigstore::cosign::{SignatureLayer, constraint::PrivateKeySigner, Constraint}; + /// use sigstore::crypto::SigningScheme; + /// + /// async fn func() { + /// let mut signature_layer = SignatureLayer::new_unsigned( + /// &"example/test".parse().unwrap(), + /// "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").expect("create SignatureLayer failed"); + /// // Now the SignatureLayer does not have a signature, we need + /// // to generate one + /// let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer().expect("create signer failed"); + /// let pk_signer = PrivateKeySigner::new_with_signer(signer); + /// if pk_signer.add_constraint(&mut signature_layer).expect("unexpected error") { + /// println!("sign succeed!"); + /// } else { + /// println!("sign failed!"); + /// } + /// } + /// + /// ``` + pub fn new_unsigned(image_ref: &OciReference, manifest_digest: &str) -> Result { + let simple_signing = SimpleSigning::new(image_ref, manifest_digest); + + let payload = serde_json::to_vec(&simple_signing)?; + let digest = format!("sha256:{:x}", sha2::Sha256::digest(&payload)); + Ok(SignatureLayer { + simple_signing, + oci_digest: digest, + certificate_signature: None, + bundle: None, + signature: None, + raw_data: payload, + }) + } + + /// Create a SignatureLayer that can be considered trusted. + /// + /// Params: + /// * `descriptor`: the metadata of the layer, taken from the OCI manifest associated + /// with the Sigstore object + /// * `layer`: the data referenced by the descriptor + /// * `source_image_digest`: the digest of the object that we're trying + /// to verify. This is **not** the digest of the signature itself. + /// * `rekor_pub_key`: the public key of Rekor, used to verify `bundle` + /// entries + /// * `fulcio_pub_key`: the public key provided by Fulcio's certificate. + /// Used to verify the `certificate` entries + /// + /// **Note well:** the certificate and bundle added to the final SignatureLayer + /// object are to be considered **trusted** and **verified**, according to + /// the parameters provided to this method. + pub(crate) fn new( + descriptor: &oci_client::manifest::OciDescriptor, + layer: &oci_client::client::ImageLayer, + source_image_digest: &str, + rekor_pub_keys: Option<&BTreeMap>, + fulcio_cert_pool: Option<&CertificatePool>, + ) -> Result { + if descriptor.media_type != SIGSTORE_OCI_MEDIA_TYPE { + return Err(SigstoreError::SigstoreMediaTypeNotFoundError); + } + + if layer.media_type != SIGSTORE_OCI_MEDIA_TYPE { + return Err(SigstoreError::SigstoreMediaTypeNotFoundError); + } + + let layer_digest = layer.clone().sha256_digest(); + if descriptor.digest != layer_digest { + return Err(SigstoreError::SigstoreLayerDigestMismatchError); + } + + let simple_signing: SimpleSigning = serde_json::from_slice(&layer.data).map_err(|e| { + SigstoreError::UnexpectedError(format!( + "Cannot convert layer data into SimpleSigning object: {e:?}" + )) + })?; + + if !simple_signing.satisfies_manifest_digest(source_image_digest) { + return Err(SigstoreError::UnexpectedError( + "Simple signing image digest mismatch".to_string(), + )); + } + + let annotations = descriptor.annotations.clone().unwrap_or_default(); + + let signature = Self::get_signature_from_annotations(&annotations)?; + let bundle = Self::get_bundle_from_annotations(&annotations, rekor_pub_keys)?; + let certificate_signature = Self::get_certificate_signature_from_annotations( + &annotations, + fulcio_cert_pool, + bundle.as_ref(), + ); + + Ok(SignatureLayer { + oci_digest: descriptor.digest.clone(), + raw_data: layer.data.clone(), + simple_signing, + signature: Some(signature), + bundle, + certificate_signature, + }) + } + + fn get_signature_from_annotations(annotations: &BTreeMap) -> Result { + let signature: String = annotations + .get(SIGSTORE_SIGNATURE_ANNOTATION) + .cloned() + .ok_or(SigstoreError::SigstoreAnnotationNotFoundError)?; + Ok(signature) + } + + fn get_bundle_from_annotations( + annotations: &BTreeMap, + rekor_pub_keys: Option<&BTreeMap>, + ) -> Result> { + let bundle = match annotations.get(SIGSTORE_BUNDLE_ANNOTATION) { + Some(value) => match rekor_pub_keys { + Some(keys) => Some(Bundle::new_verified(value, keys)?), + None => { + info!(bundle = ?value, "Ignoring bundle, rekor public key not provided to verification client"); + None + } + }, + None => None, + }; + Ok(bundle) + } + + fn get_certificate_signature_from_annotations( + annotations: &BTreeMap, + fulcio_cert_pool: Option<&CertificatePool>, + bundle: Option<&Bundle>, + ) -> Option { + let cert_raw = annotations.get(SIGSTORE_CERT_ANNOTATION)?; + + let fulcio_cert_pool = match fulcio_cert_pool { + Some(cp) => cp, + None => { + info!( + reason = "fulcio certificates not provided", + "Ignoring certificate annotation" + ); + return None; + } + }; + + let bundle = match bundle { + Some(b) => b, + None => { + info!( + reason = "rekor bundle not found", + "Ignoring certificate annotation" + ); + return None; + } + }; + + match CertificateSignature::from_certificate(cert_raw.as_bytes(), fulcio_cert_pool, bundle) + { + Ok(certificate_signature) => Some(certificate_signature), + Err(e) => { + info!(reason=?e, "Ignoring certificate annotation"); + None + } + } + } + + /// Given a Cosign public key, check whether this Signature Layer has been + /// signed by it + pub(crate) fn is_signed_by_key(&self, verification_key: &CosignVerificationKey) -> bool { + let signature = match &self.signature { + Some(sig) => sig, + None => { + warn!(signature_layer = ?self, "signature not found in the SignatureLayer"); + return false; + } + }; + match verification_key.verify_signature( + Signature::Base64Encoded(signature.as_bytes()), + &self.raw_data, + ) { + Ok(_) => true, + Err(e) => { + debug!(signature=signature.as_str(), reason=?e, "Cannot verify signature with the given key"); + false + } + } + } +} + +/// Creates a list of [`SignatureLayer`] objects by inspecting +/// the given OCI manifest and its associated layers. +/// +/// **Note well:** when Rekor and Fulcio data has been provided, the +/// returned `SignatureLayer` is guaranteed to be +/// verified using the given Rekor and Fulcio keys. +pub(crate) fn build_signature_layers( + manifest: &oci_client::manifest::OciImageManifest, + source_image_digest: &str, + layers: &[oci_client::client::ImageLayer], + rekor_pub_keys: Option<&BTreeMap>, + fulcio_cert_pool: Option<&CertificatePool>, +) -> Result> { + let mut signature_layers: Vec = Vec::new(); + + for manifest_layer in &manifest.layers { + let matching_layer: Option<&oci_client::client::ImageLayer> = layers.iter().find(|l| { + let tmp: ImageLayer = (*l).clone(); + tmp.sha256_digest() == manifest_layer.digest + }); + if let Some(layer) = matching_layer { + match SignatureLayer::new( + manifest_layer, + layer, + source_image_digest, + rekor_pub_keys, + fulcio_cert_pool, + ) { + Ok(sl) => signature_layers.push(sl), + Err(e) => { + info!(error = ?e, "Skipping OCI layer because of error"); + } + } + } + } + + if signature_layers.is_empty() { + Err(SigstoreError::SigstoreNoVerifiedLayer) + } else { + Ok(signature_layers) + } +} + +impl CertificateSignature { + /// Ensures the given certificate can be trusted, then extracts + /// its details and returns them as a `CertificateSignature` object + pub(crate) fn from_certificate( + cert_pem: &[u8], + fulcio_cert_pool: &CertificatePool, + trusted_bundle: &Bundle, + ) -> Result { + let cert = Certificate::from_pem(cert_pem) + .map_err(|e| SigstoreError::X509Error(format!("parse from pem: {e}")))?; + let integrated_time = trusted_bundle.payload.integrated_time; + + // ensure the certificate has been issued by Fulcio + fulcio_cert_pool.verify_pem_cert( + cert_pem, + Some(pki_types::UnixTime::since_unix_epoch( + cert.tbs_certificate.validity.not_before.to_unix_duration(), + )), + )?; + + crypto::certificate::is_trusted(&cert, integrated_time)?; + + let subject = CertificateSubject::from_certificate(&cert)?; + let verification_key = + CosignVerificationKey::try_from(&cert.tbs_certificate.subject_public_key_info) + .map_err(|e| { + SigstoreError::X509Error(format!( + "cannot extract public key from certificate: {e}" + )) + })?; + + let issuer = get_cert_extension_by_oid(&cert, SIGSTORE_ISSUER_OID, "Issuer")?; + + let github_workflow_trigger = get_cert_extension_by_oid( + &cert, + SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID, + "GitHub Workflow trigger", + )?; + + let github_workflow_sha = get_cert_extension_by_oid( + &cert, + SIGSTORE_GITHUB_WORKFLOW_SHA_OID, + "GitHub Workflow sha", + )?; + + let github_workflow_name = get_cert_extension_by_oid( + &cert, + SIGSTORE_GITHUB_WORKFLOW_NAME_OID, + "GitHub Workflow name", + )?; + + let github_workflow_repository = get_cert_extension_by_oid( + &cert, + SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID, + "GitHub Workflow repository", + )?; + + let github_workflow_ref = get_cert_extension_by_oid( + &cert, + SIGSTORE_GITHUB_WORKFLOW_REF_OID, + "GitHub Workflow ref", + )?; + + Ok(CertificateSignature { + verification_key, + issuer, + github_workflow_trigger, + github_workflow_sha, + github_workflow_name, + github_workflow_repository, + github_workflow_ref, + subject, + }) + } +} + +fn get_cert_extension_by_oid( + cert: &Certificate, + ext_oid: ObjectIdentifier, + ext_oid_name: &str, +) -> Result> { + cert.tbs_certificate + .extensions + .as_ref() + .ok_or(SigstoreError::X509Error( + "Certificate's extension is empty".to_string(), + ))? + .iter() + .find(|ext| ext.extn_id == ext_oid) + .map(|ext| { + String::from_utf8(ext.extn_value.clone().into_bytes()).map_err(|_| { + SigstoreError::X509Error(format!( + "Certificate's extension Sigstore {ext_oid_name} is not UTF8 compatible" + )) + }) + }) + .transpose() +} + +impl CertificateSubject { + pub fn from_certificate(certificate: &Certificate) -> Result { + let (_, san) = certificate + .tbs_certificate + .get::() + .map_err(|e| SigstoreError::PKCS8Error(format!("get SAN ext failed: {e}")))? + .ok_or(SigstoreError::PKCS8Error("No SAN ext found".to_string()))?; + + for general_name in &san.0 { + if let GeneralName::Rfc822Name(name) = general_name { + return Ok(CertificateSubject::Email(name.to_string())); + } + + if let GeneralName::UniformResourceIdentifier(uri) = general_name { + return Ok(CertificateSubject::Uri(uri.to_string())); + } + } + + Err(SigstoreError::CertificateWithIncompleteSubjectAlternativeName) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use openssl::x509::X509; + use serde_json::json; + + use crate::cosign::tests::{get_fulcio_cert_pool, get_rekor_public_key}; + + pub(crate) fn build_correct_signature_layer_without_bundle() + -> (SignatureLayer, CosignVerificationKey) { + let public_key = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENptdY/l3nB0yqkXLBWkZWQwo6+cu +OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== +-----END PUBLIC KEY-----"#; + + let signature = String::from( + "MEUCIQD6q/COgzOyW0YH1Dk+CCYSt4uAhm3FDHUwvPI55zwnlwIgE0ZK58ZOWpZw8YVmBapJhBqCfdPekIknimuO0xH8Jh8=", + ); + let verification_key = + CosignVerificationKey::from_pem(public_key.as_bytes(), &SigningScheme::default()) + .expect("Cannot create CosignVerificationKey"); + let ss_value = json!({ + "critical": { + "identity": { + "docker-reference":"registry-testing.svc.lan/busybox" + }, + "image":{ + "docker-manifest-digest":"sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b" + }, + "type":"cosign container image signature" + }, + "optional":null + }); + + ( + SignatureLayer { + simple_signing: serde_json::from_value(ss_value.clone()).unwrap(), + oci_digest: String::from("digest"), + signature: Some(signature), + bundle: None, + certificate_signature: None, + raw_data: serde_json::to_vec(&ss_value).unwrap(), + }, + verification_key, + ) + } + + pub(crate) fn build_bundle() -> Bundle { + let bundle_value = json!({ + "SignedEntryTimestamp": "MEUCIDBGJijj2FqU25yRWzlEWHqE64XKwUvychBs1bSM1PaKAiEAwcR2u81c42TLBk3lWJqhtB7SnM7Lh0OYEl6Bfa7ZA4s=", + "Payload": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoicmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlNzgwMWRlOTM1NTEyZTIyYjIzN2M3YjU3ZTQyY2E0ZDIwZTIxMzRiZGYxYjk4Zjk3NmM4ZjU1ZDljZmU0MDY3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJR3FXU2N6N3M5YVAyc0dYTkZLZXFpdnczQjZrUFJzNTZBSVRJSG52ZDVpZ0FpRUExa3piYVYyWTV5UEU4MUVOOTJOVUZPbDMxTExKU3Z3c2pGUTA3bTJYcWFBPSIsImZvcm1hdCI6Ing1MDkiLCJwdWJsaWNLZXkiOnsiY29udGVudCI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU5rZWtORFFXWjVaMEYzU1VKQlowbFVRU3RRYzJGTGFtRkZXbkZ1TjBsWk9UUmlNV1V2YWtwdWFYcEJTMEpuWjNGb2EycFBVRkZSUkVGNlFYRUtUVkpWZDBWM1dVUldVVkZMUlhkNGVtRlhaSHBrUnpsNVdsTTFhMXBZV1hoRlZFRlFRbWRPVmtKQlRWUkRTRTV3V2pOT01HSXpTbXhOUWpSWVJGUkplQXBOVkVGNVRVUkJNMDFxVlhoT2JHOVlSRlJKZUUxVVFYbE5SRUV6VGtSVmVFNVdiM2RCUkVKYVRVSk5SMEo1Y1VkVFRUUTVRV2RGUjBORGNVZFRUVFE1Q2tGM1JVaEJNRWxCUWtsT1pYZFJRbE14WmpSQmJVNUpSVTVrVEN0VkwwaEtiM1JOVTAwM1drNXVhMVJ1V1dWbWVIZFdPVlJGY25CMmJrRmFNQ3RFZWt3S2VXWkJRVlpoWlVwMFMycEdkbUpQVkdJNFJqRjVhRXBHVlRCWVdTdFNhV3BuWjBWd1RVbEpRa3BVUVU5Q1owNVdTRkU0UWtGbU9FVkNRVTFEUWpSQmR3cEZkMWxFVmxJd2JFSkJkM2REWjFsSlMzZFpRa0pSVlVoQmQwMTNSRUZaUkZaU01GUkJVVWd2UWtGSmQwRkVRV1JDWjA1V1NGRTBSVVpuVVZWTlpqRlNDazFOYzNGT1JrSnlWMko0T0cxU1RtUjRUMnRGUlZsemQwaDNXVVJXVWpCcVFrSm5kMFp2UVZWNVRWVmtRVVZIWVVwRGEzbFZVMVJ5UkdFMVN6ZFZiMGNLTUN0M2QyZFpNRWREUTNOSFFWRlZSa0ozUlVKQ1NVZEJUVWcwZDJaQldVbExkMWxDUWxGVlNFMUJTMGRqUjJnd1pFaEJOa3g1T1hkamJXd3lXVmhTYkFwWk1rVjBXVEk1ZFdSSFZuVmtRekF5VFVST2JWcFVaR3hPZVRCM1RVUkJkMHhVU1hsTmFtTjBXVzFaTTA1VE1XMU9SMWt4V2xSbmQxcEVTVFZPVkZGMUNtTXpVblpqYlVadVdsTTFibUl5T1c1aVIxWm9ZMGRzZWt4dFRuWmlVemxxV1ZSTk1sbFVSbXhQVkZsNVRrUkthVTlYV21wWmFrVXdUbWs1YWxsVE5Xb0tZMjVSZDBsQldVUldVakJTUVZGSUwwSkNXWGRHU1VWVFdtMTRhR1J0YkhaUlIwNW9Zek5TYkdKSGVIQk1iVEZzVFVGdlIwTkRjVWRUVFRRNVFrRk5SQXBCTW10QlRVZFpRMDFSUXpOWk1uVnNVRlJ6VUcxT1V6UmplbUZMWldwbE1FSnVUMUZJZWpWbE5rNUNXREJDY1hnNVdHTmhLM1F5YTA5cE1UZHpiM0JqQ2k5MkwzaElNWGhNZFZCdlEwMVJSRXRPUkRSWGFraG1TM0ZZV0U5bFZYWmFPVUU1TmtSeGNrVjNSMkZ4UjAxMGJrbDFUalJLZWxwWllWVk1Xbko0T1djS2IxaHhjVzh2UXpsUmJrOUlWSFJ2UFFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19", + "integratedTime": 1634714717, + "logIndex": 783607, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" + } + }); + let bundle: Bundle = serde_json::from_value(bundle_value).expect("Cannot parse bundle"); + bundle + } + + pub(crate) fn build_correct_signature_layer_with_certificate() -> SignatureLayer { + let ss_value = json!({ + "critical": { + "identity": { + "docker-reference": "registry-testing.svc.lan/kubewarden/disallow-service-nodeport" + }, + "image": { + "docker-manifest-digest": "sha256:5f481572d088dc4023afb35fced9530ced3d9b03bf7299c6f492163cb9f0452e" + }, + "type": "cosign container image signature" + }, + "optional": null + }); + + let bundle = build_bundle(); + + let cert_raw = r#"-----BEGIN CERTIFICATE----- +MIICdzCCAfygAwIBAgITA+PsaKjaEZqn7IY94b1e/jJnizAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MTAyMDA3MjUxNloXDTIxMTAyMDA3NDUxNVowADBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABINewQBS1f4AmNIENdL+U/HJotMSM7ZNnkTnYefxwV9TErpvnAZ0+DzL +yfAAVaeJtKjFvbOTb8F1yhJFU0XY+RijggEpMIIBJTAOBgNVHQ8BAf8EBAMCB4Aw +EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUMf1R +MMsqNFBrWbx8mRNdxOkEEYswHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG +0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl +Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu +c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j +cnQwIAYDVR0RAQH/BBYwFIESZmxhdmlvQGNhc3RlbGxpLm1lMAoGCCqGSM49BAMD +A2kAMGYCMQC3Y2ulPTsPmNS4czaKeje0BnOQHz5e6NBX0Bqx9Xca+t2kOi17sopc +/v/xH1xLuPoCMQDKND4WjHfKqXXOeUvZ9A96DqrEwGaqGMtnIuN4JzZYaULZrx9g +oXqqo/C9QnOHTto= +-----END CERTIFICATE-----"#; + + let fulcio_cert_pool = get_fulcio_cert_pool(); + let certificate_signature = + CertificateSignature::from_certificate(cert_raw.as_bytes(), &fulcio_cert_pool, &bundle) + .expect("Cannot create certificate signature"); + + SignatureLayer { + simple_signing: serde_json::from_value(ss_value.clone()).unwrap(), + oci_digest: String::from( + "sha256:5f481572d088dc4023afb35fced9530ced3d9b03bf7299c6f492163cb9f0452e", + ), + signature: Some(String::from( + "MEUCIGqWScz7s9aP2sGXNFKeqivw3B6kPRs56AITIHnvd5igAiEA1kzbaV2Y5yPE81EN92NUFOl31LLJSvwsjFQ07m2XqaA=", + )), + bundle: Some(bundle), + certificate_signature: Some(certificate_signature), + raw_data: serde_json::to_vec(&ss_value).unwrap(), + } + } + + #[test] + fn is_signed_by_key_fails_when_signature_is_not_valid() { + let (signature_layer, _) = build_correct_signature_layer_without_bundle(); + let verification_key = CosignVerificationKey::from_pem( + r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETJP9cqpUQsn2ggmJniWGjHdlsHzD +JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== +-----END PUBLIC KEY-----"# + .as_bytes(), + &SigningScheme::default(), + ) + .expect("Cannot create CosignVerificationKey"); + + let actual = signature_layer.is_signed_by_key(&verification_key); + assert!(!actual, "expected false, got true"); + } + + #[test] + fn new_signature_layer_fails_because_bad_descriptor() { + let descriptor = oci_client::manifest::OciDescriptor { + media_type: "not what you would expected".into(), + ..Default::default() + }; + let layer = oci_client::client::ImageLayer { + media_type: super::SIGSTORE_OCI_MEDIA_TYPE.to_string(), + data: Vec::new(), + annotations: None, + }; + + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let fulcio_cert_pool = get_fulcio_cert_pool(); + + let error = SignatureLayer::new( + &descriptor, + &layer, + "source_image_digest is not relevant now", + Some(&rekor_pub_keys), + Some(&fulcio_cert_pool), + ) + .expect_err("Didn't get an error"); + + let found = matches!(error, SigstoreError::SigstoreMediaTypeNotFoundError); + assert!(found, "Got a different error type: {}", error); + } + + #[test] + fn new_signature_layer_fails_because_bad_layer() { + let descriptor = oci_client::manifest::OciDescriptor { + media_type: super::SIGSTORE_OCI_MEDIA_TYPE.to_string(), + ..Default::default() + }; + let layer = oci_client::client::ImageLayer { + media_type: "not what you would expect".into(), + data: Vec::new(), + annotations: None, + }; + + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let fulcio_cert_pool = get_fulcio_cert_pool(); + + let error = SignatureLayer::new( + &descriptor, + &layer, + "source_image_digest is not relevant now", + Some(&rekor_pub_keys), + Some(&fulcio_cert_pool), + ) + .expect_err("Didn't get an error"); + + let found = matches!(error, SigstoreError::SigstoreMediaTypeNotFoundError); + assert!(found, "Got a different error type: {}", error); + } + + #[test] + fn new_signature_layer_fails_because_checksum_mismatch() { + let descriptor = oci_client::manifest::OciDescriptor { + media_type: super::SIGSTORE_OCI_MEDIA_TYPE.to_string(), + digest: "some digest".into(), + ..Default::default() + }; + let layer = oci_client::client::ImageLayer { + media_type: super::SIGSTORE_OCI_MEDIA_TYPE.to_string(), + data: "some other contents".into(), + annotations: None, + }; + + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let fulcio_cert_pool = get_fulcio_cert_pool(); + + let error = SignatureLayer::new( + &descriptor, + &layer, + "source_image_digest is not relevant now", + Some(&rekor_pub_keys), + Some(&fulcio_cert_pool), + ) + .expect_err("Didn't get an error"); + + let found = matches!(error, SigstoreError::SigstoreLayerDigestMismatchError); + assert!(found, "Got a different error type: {}", error); + } + + #[test] + fn get_signature_from_annotations_success() { + let mut annotations: BTreeMap = BTreeMap::new(); + annotations.insert(SIGSTORE_SIGNATURE_ANNOTATION.into(), "foo".into()); + + let actual = SignatureLayer::get_signature_from_annotations(&annotations); + assert!(actual.is_ok()); + } + + #[test] + fn get_signature_from_annotations_failure() { + let annotations: BTreeMap = BTreeMap::new(); + + let actual = SignatureLayer::get_signature_from_annotations(&annotations); + assert!(actual.is_err()); + } + + #[test] + fn get_bundle_from_annotations_works() { + // we are **not** going to test neither the creation from a valid bundle + // nor the fauilure because the bundle cannot be verified. These cases + // are already covered by Bundle's test suite + // + // We care only about the only case not tested: to not + // fail when no bundle is specified. + let annotations: BTreeMap = BTreeMap::new(); + let (key_id, key) = get_rekor_public_key(); + let rekor_pub_keys = BTreeMap::from([(key_id, key)]); + + let actual = + SignatureLayer::get_bundle_from_annotations(&annotations, Some(&rekor_pub_keys)); + assert!(actual.is_ok()); + assert!(actual.unwrap().is_none()); + } + + #[test] + fn get_certificate_signature_from_annotations_returns_none() { + let annotations: BTreeMap = BTreeMap::new(); + let fulcio_cert_pool = get_fulcio_cert_pool(); + + let actual = SignatureLayer::get_certificate_signature_from_annotations( + &annotations, + Some(&fulcio_cert_pool), + None, + ); + + assert!(actual.is_none()); + } + + #[test] + fn get_certificate_signature_from_annotations_fails_when_no_bundle_is_given() { + let mut annotations: BTreeMap = BTreeMap::new(); + + // add a fake cert, contents are not relevant + annotations.insert(SIGSTORE_CERT_ANNOTATION.to_string(), "a cert".to_string()); + + let fulcio_cert_pool = get_fulcio_cert_pool(); + + let cert = SignatureLayer::get_certificate_signature_from_annotations( + &annotations, + Some(&fulcio_cert_pool), + None, + ); + assert!(cert.is_none()); + } + + #[test] + fn get_certificate_signature_from_annotations_fails_when_no_fulcio_pub_key_is_given() { + let mut annotations: BTreeMap = BTreeMap::new(); + + // add a fake cert, contents are not relevant + annotations.insert(SIGSTORE_CERT_ANNOTATION.to_string(), "a cert".to_string()); + + let bundle = build_bundle(); + + let cert = SignatureLayer::get_certificate_signature_from_annotations( + &annotations, + None, + Some(&bundle), + ); + assert!(cert.is_none()); + } + + #[test] + fn is_signed_by_key() { + // a SignatureLayer created with traditional signing + let (sl, key) = build_correct_signature_layer_without_bundle(); + assert!(sl.is_signed_by_key(&key)); + + // a SignatureLayer created with keyless signing -> there's no pub key + let sl = build_correct_signature_layer_with_certificate(); + + // fail because the signature layer wasn't signed with the given key + let verification_key = CosignVerificationKey::from_pem( + r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETJP9cqpUQsn2ggmJniWGjHdlsHzD +JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== +-----END PUBLIC KEY-----"# + .as_bytes(), + &SigningScheme::default(), + ) + .expect("Cannot create CosignVerificationKey"); + assert!(!sl.is_signed_by_key(&verification_key)); + } + + // Testing CertificateSignature + use crate::cosign::bundle::Payload; + use crate::crypto::SigningScheme; + use crate::crypto::tests::{CertGenerationOptions, generate_certificate}; + use chrono::{TimeDelta, Utc}; + + impl TryFrom for crate::registry::Certificate { + type Error = anyhow::Error; + + fn try_from(value: X509) -> std::result::Result { + let data = value.to_pem()?; + let encoding = crate::registry::CertificateEncoding::Pem; + Ok(Self { data, encoding }) + } + } + + #[test] + fn certificate_signature_from_certificate_using_email() -> anyhow::Result<()> { + let expected_email = "test@sigstore.dev".to_string(); + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + subject_email: Some(expected_email.clone()), + ..Default::default() + }, + )?; + + let issued_cert_pem = issued_cert.cert.to_pem()?; + + let certs = vec![ + crate::registry::Certificate::try_from(ca_data.cert) + .unwrap() + .try_into()?, + ]; + let cert_pool = CertificatePool::from_certificates(certs, []).unwrap(); + + let integrated_time = Utc::now() + .checked_sub_signed(TimeDelta::try_minutes(1).unwrap()) + .unwrap(); + let bundle = Bundle { + signed_entry_timestamp: "not relevant".to_string(), + payload: Payload { + body: "not relevant".to_string(), + integrated_time: integrated_time.timestamp(), + log_index: 0, + log_id: "not relevant".to_string(), + }, + }; + + let certificate_signature = + CertificateSignature::from_certificate(&issued_cert_pem, &cert_pool, &bundle) + .expect("Didn't expect an error"); + + let expected_issuer = match certificate_signature.subject.clone() { + CertificateSubject::Email(mail) => mail == expected_email, + _ => false, + }; + assert!( + expected_issuer, + "Didn't get the expected subject: {:?}", + certificate_signature.subject + ); + + Ok(()) + } + + #[test] + fn certificate_signature_from_certificate_using_uri() -> anyhow::Result<()> { + let expected_url = "https://sigstore.dev/test".to_string(); + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + subject_email: None, + subject_url: Some(expected_url.clone()), + ..Default::default() + }, + )?; + + let issued_cert_pem = issued_cert.cert.to_pem()?; + + let certs = vec![ + crate::registry::Certificate::try_from(ca_data.cert) + .unwrap() + .try_into()?, + ]; + let cert_pool = CertificatePool::from_certificates(certs, []).unwrap(); + + let integrated_time = Utc::now() + .checked_sub_signed(TimeDelta::try_minutes(1).unwrap()) + .unwrap(); + let bundle = Bundle { + signed_entry_timestamp: "not relevant".to_string(), + payload: Payload { + body: "not relevant".to_string(), + integrated_time: integrated_time.timestamp(), + log_index: 0, + log_id: "not relevant".to_string(), + }, + }; + + let certificate_signature = + CertificateSignature::from_certificate(&issued_cert_pem, &cert_pool, &bundle) + .expect("Didn't expect an error"); + + let expected_issuer = match certificate_signature.subject.clone() { + CertificateSubject::Uri(url) => url == expected_url, + _ => false, + }; + assert!( + expected_issuer, + "Didn't get the expected subject: {:?}", + certificate_signature.subject + ); + + Ok(()) + } + + #[test] + fn certificate_signature_from_certificate_without_email_and_uri() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + subject_email: None, + subject_url: None, + ..Default::default() + }, + )?; + + let issued_cert_pem = issued_cert.cert.to_pem()?; + + let certs = vec![ + crate::registry::Certificate::try_from(ca_data.cert) + .unwrap() + .try_into()?, + ]; + let cert_pool = CertificatePool::from_certificates(certs, []).unwrap(); + + let integrated_time = Utc::now() + .checked_sub_signed(TimeDelta::try_minutes(1).unwrap()) + .unwrap(); + let bundle = Bundle { + signed_entry_timestamp: "not relevant".to_string(), + payload: Payload { + body: "not relevant".to_string(), + integrated_time: integrated_time.timestamp(), + log_index: 0, + log_id: "not relevant".to_string(), + }, + }; + + let error = CertificateSignature::from_certificate(&issued_cert_pem, &cert_pool, &bundle) + .expect_err("Didn't get an error"); + assert!(matches!( + error, + SigstoreError::CertificateWithoutSubjectAlternativeName + )); + + Ok(()) + } +} diff --git a/vendor/src/cosign/verification_constraint/annotation_verifier.rs b/vendor/src/cosign/verification_constraint/annotation_verifier.rs new file mode 100644 index 0000000..014b09a --- /dev/null +++ b/vendor/src/cosign/verification_constraint/annotation_verifier.rs @@ -0,0 +1,29 @@ +use std::collections::BTreeMap; + +use super::VerificationConstraint; +use crate::cosign::signature_layers::SignatureLayer; +use crate::errors::Result; + +/// Verification Constraint for the annotations added by `cosign sign` +/// +/// The `SimpleSigning` object produced at signature time can be enriched by +/// signer with so called "anntoations". +/// +/// This constraint ensures that all the annotations specified by the user are +/// found inside of the SignatureLayer. +/// +/// It's perfectly find for the SignatureLayer to have additional annotations. +/// These will be simply be ignored by the verifier. +#[derive(Default, Debug)] +pub struct AnnotationVerifier { + pub annotations: BTreeMap, +} + +impl VerificationConstraint for AnnotationVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> Result { + let verified = signature_layer + .simple_signing + .satisfies_annotations(&self.annotations); + Ok(verified) + } +} diff --git a/vendor/src/cosign/verification_constraint/cert_subject_email_verifier.rs b/vendor/src/cosign/verification_constraint/cert_subject_email_verifier.rs new file mode 100644 index 0000000..31ea7ec --- /dev/null +++ b/vendor/src/cosign/verification_constraint/cert_subject_email_verifier.rs @@ -0,0 +1,378 @@ +use regex::Regex; +use std::fmt::Debug; + +use super::VerificationConstraint; +use crate::cosign::signature_layers::{CertificateSubject, SignatureLayer}; +use crate::errors::Result; + +/// Verification Constraint for signatures produced in keyless mode. +/// +/// Keyless signatures have a x509 certificate associated to them. This +/// verifier ensures the SAN portion of the certificate has an email +/// attribute that matches the one provided by the user. +/// +/// It's also possible to specify the `Issuer`, this is the name of the +/// identity provider that was used by the user to authenticate. +/// +/// For example, `cosign` produces the following signature when the user +/// relies on GitHub to authenticate himself: +/// +/// ```hcl +/// { +/// "critical": { +/// // not relevant +/// }, +/// "optional": { +/// "Bundle": { +/// // not relevant +/// }, +/// "Issuer": "https://github.com/login/oauth", +/// "Subject": "alice@example.com" +/// } +/// } +/// ``` +/// +/// The following constraints would be able to enforce this signature to be +/// found: +/// +/// ```rust +/// use regex::Regex; +/// use sigstore::cosign::verification_constraint::CertSubjectEmailVerifier; +/// use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier; +/// +/// // This looks only for the email address of the trusted user +/// let vc_email = CertSubjectEmailVerifier{ +/// email: StringVerifier::ExactMatch("alice@example.com".to_string()), +/// issuer: None, +/// }; +/// +/// // This looks only for emails matching the a pattern +/// let vc_email_regex = CertSubjectEmailVerifier{ +/// email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), +/// issuer: None, +/// }; +/// +/// // This ensures the user authenticated via GitHub (see the issuer value), +/// // plus the email associated to his GitHub account must be the one specified. +/// let vc_email_and_issuer = CertSubjectEmailVerifier{ +/// email: StringVerifier::ExactMatch("alice@example.com".to_string()), +/// issuer: Some(StringVerifier::ExactMatch("https://github.com/login/oauth".to_string())), +/// }; +/// +/// // This ensures the user authenticated via a service that has a domain +/// // matching the regex, plus the email associated to account also matches +/// // the regex. +/// let vc_email_and_issuer_regex = CertSubjectEmailVerifier{ +/// email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), +/// issuer: Some(StringVerifier::Regex(Regex::new(r"https://github\.com/login/oauth|https://google\.com").unwrap())), +/// }; +/// ``` +/// +/// When `issuer` is `None`, the value found inside of the signature's certificate +/// is not checked. +/// +/// For example, given the following constraint: +/// ```rust +/// use sigstore::cosign::verification_constraint::CertSubjectEmailVerifier; +/// use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier; +/// +/// let constraint = CertSubjectEmailVerifier{ +/// email: StringVerifier::ExactMatch("alice@example.com".to_string()), +/// issuer: None, +/// }; +/// ``` +/// +/// Both these signatures would be trusted: +/// ```hcl +/// [ +/// { +/// "critical": { +/// // not relevant +/// }, +/// "optional": { +/// "Bundle": { +/// // not relevant +/// }, +/// "Issuer": "https://github.com/login/oauth", +/// "Subject": "alice@example.com" +/// } +/// }, +/// { +/// "critical": { +/// // not relevant +/// }, +/// "optional": { +/// "Bundle": { +/// // not relevant +/// }, +/// "Issuer": "https://example.com/login/oauth", +/// "Subject": "alice@example.com" +/// } +/// } +/// ] +/// ``` +pub struct CertSubjectEmailVerifier { + pub email: StringVerifier, + pub issuer: Option, +} + +impl Debug for CertSubjectEmailVerifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut issuer_str = String::new(); + if let Some(issuer) = &self.issuer { + issuer_str.push_str(&format!(" and {issuer}")); + } + f.write_fmt(format_args!( + "email {}{}", + &self.email.to_string(), + issuer_str + )) + } +} + +pub enum StringVerifier { + ExactMatch(String), + Regex(Regex), +} + +impl StringVerifier { + fn verify(&self, s: &str) -> bool { + match self { + StringVerifier::ExactMatch(s2) => s == *s2, + StringVerifier::Regex(r) => r.is_match(s), + } + } +} + +impl std::fmt::Display for StringVerifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StringVerifier::ExactMatch(s) => f.write_fmt(format_args!("is exactly {s}")), + StringVerifier::Regex(r) => f.write_fmt(format_args!("matches regular expression {r}")), + } + } +} + +impl VerificationConstraint for CertSubjectEmailVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> Result { + let verified = match &signature_layer.certificate_signature { + Some(signature) => { + let email_matches = match &signature.subject { + CertificateSubject::Email(e) => self.email.verify(e), + _ => false, + }; + + let issuer_matches = match &self.issuer { + Some(issuer) => { + if let Some(signature_issuer) = &signature.issuer { + issuer.verify(signature_issuer) + } else { + // if the issuer is not present in the signature, we + // consider it as a failed constriant + false + } + } + None => true, + }; + + email_matches && issuer_matches + } + _ => false, + }; + Ok(verified) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cosign::signature_layers::tests::{ + build_correct_signature_layer_with_certificate, + build_correct_signature_layer_without_bundle, + }; + use crate::cosign::verification_constraint::CertSubjectUrlVerifier; + + #[test] + fn cert_email_verifier_only_email() { + let email = "alice@example.com".to_string(); + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email(email.to_string()); + cert_signature.issuer = None; + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email), + issuer: None, + }; + assert!(vc.verify(&sl).unwrap()); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch("different@email.com".to_string()), + issuer: None, + }; + assert!(!vc.verify(&sl).unwrap()); + } + + #[test] + fn cert_email_verifier_email_and_issuer() { + let email = "alice@example.com".to_string(); + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + + // The cerificate subject doesn't have an issuer + let cert_subj = CertificateSubject::Email(email.clone()); + cert_signature.issuer = None; + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature.clone()); + + // fail because the issuer we want doesn't exist + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email.clone()), + issuer: Some(StringVerifier::ExactMatch("an issuer".to_string())), + }; + assert!(!vc.verify(&sl).unwrap()); + + // The cerificate subject has an issuer + let issuer = "the issuer".to_string(); + let cert_subj = CertificateSubject::Email(email.clone()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email.clone()), + issuer: Some(StringVerifier::ExactMatch(issuer.clone())), + }; + assert!(vc.verify(&sl).unwrap()); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch(email), + issuer: Some(StringVerifier::ExactMatch("another issuer".to_string())), + }; + assert!(!vc.verify(&sl).unwrap()); + + // another verifier should fail + let vc = CertSubjectUrlVerifier { + url: "https://sigstore.dev/test".to_string(), + issuer, + }; + assert!(!vc.verify(&sl).unwrap()); + } + + #[test] + fn cert_email_verifier_no_signature() { + let (sl, _) = build_correct_signature_layer_without_bundle(); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch("alice@example.com".to_string()), + issuer: None, + }; + assert!(!vc.verify(&sl).unwrap()); + } + + #[test] + fn cert_email_verifier_only_email_regex() { + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email("alice@example.com".to_string()); + cert_signature.issuer = None; + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), + issuer: None, + }; + assert!(vc.verify(&sl).unwrap()); + + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email("bob@example.com".to_string()); + cert_signature.issuer = None; + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + assert!(vc.verify(&sl).unwrap()); + + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch("different@email.com".to_string()), + issuer: None, + }; + assert!(!vc.verify(&sl).unwrap()); + } + + #[test] + fn cert_email_verifier_email_and_issuer_regex() { + // The cerificate subject doesn't have an issuer + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Email("alice@example.com".to_string()); + cert_signature.issuer = None; + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature.clone()); + + // fail because the issuer we want doesn't exist + let vc = CertSubjectEmailVerifier { + email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), + issuer: Some(StringVerifier::Regex( + Regex::new(r#".*\.github.com"#).unwrap(), + )), + }; + assert!(!vc.verify(&sl).unwrap()); + + // The cerificate subject has an issuer + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let issuer = "some-action.github.com".to_string(); + let cert_subj = CertificateSubject::Email("alice@example.com".to_string()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + // pass because the issuer matches the regex + let vc = CertSubjectEmailVerifier { + email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), + issuer: Some(StringVerifier::Regex( + Regex::new(r#".*\.github.com"#).unwrap(), + )), + }; + assert!(vc.verify(&sl).unwrap()); + + // The cerificate subject has an incorrect issuer + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let issuer = "invalid issuer".to_string(); + let cert_subj = CertificateSubject::Email("alice@example.com".to_string()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + // fail because the issuer doesn't matches the regex + let vc = CertSubjectEmailVerifier { + email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), + issuer: Some(StringVerifier::Regex( + Regex::new(r#".*\.github.com"#).unwrap(), + )), + }; + assert!(!vc.verify(&sl).unwrap()); + + // The cerificate subject has an invalid email + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let issuer = "some-action.github.com".to_string(); + let cert_subj = CertificateSubject::Email("alice@somedomain.com".to_string()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + // fail because the email doesn't matches the regex + let vc = CertSubjectEmailVerifier { + email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()), + issuer: Some(StringVerifier::Regex( + Regex::new(r#".*\.github.com"#).unwrap(), + )), + }; + assert!(!vc.verify(&sl).unwrap()); + } +} diff --git a/vendor/src/cosign/verification_constraint/cert_subject_url_verifier.rs b/vendor/src/cosign/verification_constraint/cert_subject_url_verifier.rs new file mode 100644 index 0000000..66442cd --- /dev/null +++ b/vendor/src/cosign/verification_constraint/cert_subject_url_verifier.rs @@ -0,0 +1,131 @@ +use super::VerificationConstraint; +use crate::cosign::signature_layers::{CertificateSubject, SignatureLayer}; +use crate::errors::Result; + +/// Verification Constraint for signatures produced in keyless mode. +/// +/// Keyless signatures have a x509 certificate associated to them. This +/// verifier ensures the SAN portion of the certificate has a URI +/// attribute that matches the one provided by the user. +/// +/// The constraints needs also the `Issuer` to be provided, this is the name +/// of the identity provider that was used by the user to authenticate. +/// +/// This verifier can be used to check keyless signatures produced in +/// non-interactive mode inside of GitHub Actions. +/// +/// For example, `cosign` produces the following signature when the +/// OIDC token is extracted from the GITHUB_TOKEN: +/// +/// ```hcl +/// { +/// "critical": { +/// // not relevant +/// }, +/// "optional": { +/// "Bundle": { +/// // not relevant +/// }, +/// "Issuer": "https://token.actions.githubusercontent.com", +/// "Subject": "https://github.com/flavio/policy-secure-pod-images/.github/workflows/release.yml@refs/heads/main" +/// } +/// } +/// ``` +/// +/// The following constraint would be able to enforce this signature to be +/// found: +/// +/// ```rust +/// use sigstore::cosign::verification_constraint::CertSubjectUrlVerifier; +/// +/// let vc = CertSubjectUrlVerifier{ +/// url: String::from("https://github.com/flavio/policy-secure-pod-images/.github/workflows/release.yml@refs/heads/main"), +/// issuer: String::from("https://token.actions.githubusercontent.com"), +/// }; +/// ``` +#[derive(Default, Debug)] +pub struct CertSubjectUrlVerifier { + pub url: String, + pub issuer: String, +} + +impl VerificationConstraint for CertSubjectUrlVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> Result { + let verified = match &signature_layer.certificate_signature { + Some(signature) => { + let url_matches = match &signature.subject { + CertificateSubject::Uri(u) => u == &self.url, + _ => false, + }; + let issuer_matches = Some(self.issuer.clone()) == signature.issuer; + + url_matches && issuer_matches + } + _ => false, + }; + Ok(verified) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cosign::{ + signature_layers::tests::{ + build_correct_signature_layer_with_certificate, + build_correct_signature_layer_without_bundle, + }, + verification_constraint::{ + CertSubjectEmailVerifier, cert_subject_email_verifier::StringVerifier, + }, + }; + + #[test] + fn cert_subject_url_verifier() { + let url = "https://sigstore.dev/test".to_string(); + let issuer = "the issuer".to_string(); + + let mut sl = build_correct_signature_layer_with_certificate(); + let mut cert_signature = sl.certificate_signature.unwrap(); + let cert_subj = CertificateSubject::Uri(url.clone()); + cert_signature.issuer = Some(issuer.clone()); + cert_signature.subject = cert_subj; + sl.certificate_signature = Some(cert_signature); + + let vc = CertSubjectUrlVerifier { + url: url.clone(), + issuer: issuer.clone(), + }; + assert!(vc.verify(&sl).unwrap()); + + let vc = CertSubjectUrlVerifier { + url: "a different url".to_string(), + issuer: issuer.clone(), + }; + assert!(!vc.verify(&sl).unwrap()); + + let vc = CertSubjectUrlVerifier { + url, + issuer: "a different issuer".to_string(), + }; + assert!(!vc.verify(&sl).unwrap()); + + // A Cert email verifier should also report a non match + let vc = CertSubjectEmailVerifier { + email: StringVerifier::ExactMatch("alice@example.com".to_string()), + issuer: Some(StringVerifier::ExactMatch(issuer)), + }; + assert!(!vc.verify(&sl).unwrap()); + } + + #[test] + fn cert_subject_verifier_no_signature() { + let (sl, _) = build_correct_signature_layer_without_bundle(); + + let vc = CertSubjectUrlVerifier { + url: "https://sigstore.dev/test".to_string(), + issuer: "an issuer".to_string(), + }; + assert!(!vc.verify(&sl).unwrap()); + } +} diff --git a/vendor/src/cosign/verification_constraint/certificate_verifier.rs b/vendor/src/cosign/verification_constraint/certificate_verifier.rs new file mode 100644 index 0000000..8035127 --- /dev/null +++ b/vendor/src/cosign/verification_constraint/certificate_verifier.rs @@ -0,0 +1,307 @@ +use chrono::{DateTime, Utc}; +use pkcs8::der::Decode; +use pki_types::CertificateDer; +use tracing::warn; +use x509_cert::Certificate; + +use super::VerificationConstraint; +use crate::cosign::signature_layers::SignatureLayer; +use crate::crypto::{CosignVerificationKey, certificate_pool::CertificatePool}; +use crate::errors::{Result, SigstoreError}; + +/// Verify signature layers using the public key defined inside of a x509 certificate +#[derive(Debug)] +pub struct CertificateVerifier { + cert_verification_key: CosignVerificationKey, + cert_validity: x509_cert::time::Validity, + require_rekor_bundle: bool, +} + +impl CertificateVerifier { + /// Create a new instance of `CertificateVerifier` using the PEM encoded + /// certificate. + /// + /// * `cert_bytes`: PEM encoded certificate + /// * `require_rekor_bundle`: require the signature layer to have a Rekor + /// bundle. Having a Rekor bundle allows further checks to be performed, + /// like ensuring the signature has been produced during the validity + /// time frame of the certificate. It is recommended to set this value + /// to `true` to have a more secure verification process. + /// * `cert_chain`: the certificate chain that is used to verify the provided + /// certificate. When not specified, the certificate is assumed to be trusted + pub fn from_pem( + cert_bytes: &[u8], + require_rekor_bundle: bool, + cert_chain: Option<&[crate::registry::Certificate]>, + ) -> Result { + let pem = pem::parse(cert_bytes)?; + Self::from_der(pem.contents(), require_rekor_bundle, cert_chain) + } + + /// Create a new instance of `CertificateVerifier` using the DER encoded + /// certificate. + /// + /// * `cert_bytes`: DER encoded certificate + /// * `require_rekor_bundle`: require the signature layer to have a Rekor + /// bundle. Having a Rekor bundle allows further checks to be performed, + /// like ensuring the signature has been produced during the validity + /// time frame of the certificate. It is recommended to set this value + /// to `true` to have a more secure verification process. + /// * `cert_chain`: the certificate chain that is used to verify the provided + /// certificate. When not specified, the certificate is assumed to be trusted + pub fn from_der( + cert_bytes: &[u8], + require_rekor_bundle: bool, + cert_chain: Option<&[crate::registry::Certificate]>, + ) -> Result { + let cert = Certificate::from_der(cert_bytes) + .map_err(|e| SigstoreError::X509Error(format!("parse from der {e}")))?; + crate::crypto::certificate::verify_key_usages(&cert)?; + crate::crypto::certificate::verify_has_san(&cert)?; + crate::crypto::certificate::verify_validity(&cert)?; + + if let Some(certs) = cert_chain { + let certs = certs + .iter() + .map(|c| CertificateDer::try_from(c.clone())) + .collect::>>()?; + let cert_pool = CertificatePool::from_certificates(certs, [])?; + cert_pool.verify_der_cert(cert_bytes, None)?; + } + + let subject_public_key_info = &cert.tbs_certificate.subject_public_key_info; + let cosign_verification_key = CosignVerificationKey::try_from(subject_public_key_info)?; + + Ok(Self { + cert_verification_key: cosign_verification_key, + cert_validity: cert.tbs_certificate.validity, + require_rekor_bundle, + }) + } +} + +impl VerificationConstraint for CertificateVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> Result { + if !signature_layer.is_signed_by_key(&self.cert_verification_key) { + return Ok(false); + } + match &signature_layer.bundle { + Some(bundle) => { + let it = DateTime::::from_naive_utc_and_offset( + DateTime::from_timestamp(bundle.payload.integrated_time, 0) + .ok_or(SigstoreError::UnexpectedError( + "timestamp is not legal".into(), + ))? + .naive_utc(), + Utc, + ); + let not_before: DateTime = + self.cert_validity.not_before.to_system_time().into(); + if it < not_before { + warn!( + integrated_time = it.to_string(), + not_before = self.cert_validity.not_before.to_string(), + "certificate verification: ignoring layer, certificate expired before signature submitted to rekor" + ); + return Ok(false); + } + + let not_after: DateTime = self.cert_validity.not_after.to_system_time().into(); + if it > not_after { + warn!( + integrated_time = it.to_string(), + not_after = self.cert_validity.not_after.to_string(), + "certificate verification: ignoring layer, certificate issued after signatured submitted to rekor" + ); + return Ok(false); + } + Ok(true) + } + None => { + if self.require_rekor_bundle { + warn!("certificate verifier: ignoring layer because rekor bundle is missing"); + Ok(false) + } else { + Ok(true) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use std::time::{Duration, SystemTime}; + + use super::*; + use crate::cosign::bundle::Bundle; + use crate::crypto::tests::*; + use crate::registry; + + use pkcs8::der::asn1::UtcTime; + use serde_json::json; + use x509_cert::time::{Time, Validity}; + + #[test] + fn verify_certificate_() -> anyhow::Result<()> { + // use the correct CA chain + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + let ca_cert = registry::Certificate { + encoding: registry::CertificateEncoding::Pem, + data: ca_data.cert.to_pem()?, + }; + let cert_chain = vec![ca_cert]; + + let issued_cert = generate_certificate(Some(&ca_data), CertGenerationOptions::default())?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + + let verifier = CertificateVerifier::from_pem(&issued_cert_pem, false, Some(&cert_chain)); + assert!(verifier.is_ok()); + + // Use a different CA chain + let another_ca_data = generate_certificate(None, CertGenerationOptions::default())?; + let another_ca_cert = registry::Certificate { + encoding: registry::CertificateEncoding::Pem, + data: another_ca_data.cert.to_pem()?, + }; + let cert_chain = vec![another_ca_cert]; + let verifier = CertificateVerifier::from_pem(&issued_cert_pem, false, Some(&cert_chain)); + assert!(verifier.is_err()); + + // No cert chain + let verifier = CertificateVerifier::from_pem(&issued_cert_pem, false, None); + assert!(verifier.is_ok()); + + Ok(()) + } + + /// Create a SignatureLayer using some hard coded value. Returns the + /// certificate that can be used to successfully verify the layer + fn test_data() -> (SignatureLayer, String) { + let ss_value = json!({ + "critical": { + "identity": { + "docker-reference": "registry-testing.svc.lan/kubewarden/pod-privileged" + }, + "image": { + "docker-manifest-digest": "sha256:f1143ec2786e13d7d3335dbb498528438d910648469d3f39647e1cde6914da8d" + }, + "type": "cosign container image signature" + }, + "optional": null + }); + + let bundle = build_bundle(); + + let cert_pem_raw = r#"-----BEGIN CERTIFICATE----- +MIICsTCCAligAwIBAgIUR8wkyvHURfBVH6K2uhfTJZItw3owCgYIKoZIzj0EAwIw +gZIxCzAJBgNVBAYTAkRFMRAwDgYDVQQIEwdCYXZhcmlhMRIwEAYDVQQHEwlOdXJl +bWJlcmcxEzARBgNVBAoTCkt1YmV3YXJkZW4xIzAhBgNVBAsTGkt1YmV3YXJkZW4g +SW50ZXJtZWRpYXRlIENBMSMwIQYDVQQDExpLdWJld2FyZGVuIEludGVybWVkaWF0 +ZSBDQTAeFw0yMjExMTAxMDM4MDBaFw0yMzExMTAxMDM4MDBaMIGFMQswCQYDVQQG +EwJERTEQMA4GA1UECBMHQmF2YXJpYTESMBAGA1UEBxMJTnVyZW1iZXJnMRMwEQYD +VQQKEwpLdWJld2FyZGVuMRgwFgYDVQQLEw9LdWJld2FyZGVuIFVzZXIxITAfBgNV +BAMTGHVzZXIxLmN1c3RvbS13aWRnZXRzLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABEKjBtYLmtwhXNV1/uBanNn5YLD/QY/lfhPleBzenCL7CC2iocu8m3WM +PMfd06tE/9HbBAITf64Oc4Mp7abrzp2jgZYwgZMwDgYDVR0PAQH/BAQDAgeAMBMG +A1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHsx7jle +7PzGarNvliop+/aTj9GsMB8GA1UdIwQYMBaAFKJu6pRjVGUXVCVkft0YQ+3o1GbQ +MB4GA1UdEQQXMBWBE3VzZXIxQGt1YmV3YXJkZW4uaW8wCgYIKoZIzj0EAwIDRwAw +RAIgPixAn47x4qLpu7Y/d0oyvbnOGtD5cY7rywdMOO7LYRsCIDsCyGUZIYMFfSrt +3K/aLG49dcv6FKBtZpF5+hYj1zKe +-----END CERTIFICATE-----"# + .to_string(); + + let signature_layer = SignatureLayer { + simple_signing: serde_json::from_value(ss_value.clone()).unwrap(), + oci_digest: String::from( + "sha256:f9b817c013972c75de8689d55c0d441c3eb84f6233ac75f6a9c722ea5db0058b", + ), + signature: Some(String::from( + "MEYCIQCIqLEe6hnjEXP/YC2P9OIwEr2yMmwPNHLzvCPaoaXFOQIhALyTouhKNKc2ZVrR0GUQ7J0U5AtlyDZDLGnasAi7XnV/", + )), + bundle: Some(bundle), + certificate_signature: None, + raw_data: serde_json::to_vec(&ss_value).unwrap(), + }; + + (signature_layer, cert_pem_raw) + } + + fn build_bundle() -> Bundle { + let bundle_value = json!({ + "SignedEntryTimestamp": "MEUCIG5TYOXkiPm7RGYgDIPHwRQW5NyoSPuwxvJe4ByB9c37AiEAyD0dVcsiJ5Lp+QY5SL80jDxfc75BtjRnticVf7SiFD0=", + "Payload": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJmOWI4MTdjMDEzOTcyYzc1ZGU4Njg5ZDU1YzBkNDQxYzNlYjg0ZjYyMzNhYzc1ZjZhOWM3MjJlYTVkYjAwNThiIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNJcUxFZTZobmpFWFAvWUMyUDlPSXdFcjJ5TW13UE5ITHp2Q1Bhb2FYRk9RSWhBTHlUb3VoS05LYzJaVnJSMEdVUTdKMFU1QXRseURaRExHbmFzQWk3WG5WLyIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnpWRU5EUVd4cFowRjNTVUpCWjBsVlVqaDNhM2wyU0ZWU1prSldTRFpMTW5Wb1psUktXa2wwZHpOdmQwTm5XVWxMYjFwSmVtb3dSVUYzU1hjS1oxcEplRU42UVVwQ1owNVdRa0ZaVkVGclVrWk5Va0YzUkdkWlJGWlJVVWxGZDJSRFdWaGFhR050YkdoTlVrbDNSVUZaUkZaUlVVaEZkMnhQWkZoS2JBcGlWMHBzWTIxamVFVjZRVkpDWjA1V1FrRnZWRU5yZERGWmJWWXpXVmhLYTFwWE5IaEpla0ZvUW1kT1ZrSkJjMVJIYTNReFdXMVdNMWxZU210YVZ6Um5DbE5YTlRCYVdFcDBXbGRTY0ZsWVVteEpSVTVDVFZOTmQwbFJXVVJXVVZGRVJYaHdUR1JYU214a01rWjVXa2RXZFVsRmJIVmtSMVo1WWxkV2EyRlhSakFLV2xOQ1JGRlVRV1ZHZHpCNVRXcEZlRTFVUVhoTlJFMDBUVVJDWVVaM01IbE5la1Y0VFZSQmVFMUVUVFJOUkVKaFRVbEhSazFSYzNkRFVWbEVWbEZSUndwRmQwcEZVbFJGVVUxQk5FZEJNVlZGUTBKTlNGRnRSakpaV0Vwd1dWUkZVMDFDUVVkQk1WVkZRbmhOU2xSdVZubGFWekZwV2xoS2JrMVNUWGRGVVZsRUNsWlJVVXRGZDNCTVpGZEtiR1F5Um5sYVIxWjFUVkpuZDBabldVUldVVkZNUlhjNVRHUlhTbXhrTWtaNVdrZFdkVWxHVm5wYVdFbDRTVlJCWmtKblRsWUtRa0ZOVkVkSVZucGFXRWw0VEcxT01XTXpVblppVXpFellWZFNibHBZVW5wTWJVNTJZbFJDV2sxQ1RVZENlWEZIVTAwME9VRm5SVWREUTNGSFUwMDBPUXBCZDBWSVFUQkpRVUpGUzJwQ2RGbE1iWFIzYUZoT1ZqRXZkVUpoYms1dU5WbE1SQzlSV1M5c1ptaFFiR1ZDZW1WdVEwdzNRME15YVc5amRUaHRNMWROQ2xCTlptUXdOblJGTHpsSVlrSkJTVlJtTmpSUFl6Uk5jRGRoWW5KNmNESnFaMXBaZDJkYVRYZEVaMWxFVmxJd1VFRlJTQzlDUVZGRVFXZGxRVTFDVFVjS1FURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0wzZFJRMDFCUVhkSVVWbEVWbEl3VDBKQ1dVVkdTSE40TjJwc1pRbzNVSHBIWVhKT2RteHBiM0FyTDJGVWFqbEhjMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRkdTMHAxTm5CU2FsWkhWVmhXUTFaclpuUXdXVkVyTTI4eFIySlJDazFDTkVkQk1WVmtSVkZSV0UxQ1YwSkZNMVo2V2xoSmVGRkhkREZaYlZZeldWaEthMXBYTkhWaFZ6aDNRMmRaU1V0dldrbDZhakJGUVhkSlJGSjNRWGNLVWtGSloxQnBlRUZ1TkRkNE5IRk1jSFUzV1M5a01HOTVkbUp1VDBkMFJEVmpXVGR5ZVhka1RVOVBOMHhaVW5ORFNVUnpRM2xIVlZwSldVMUdabE55ZEFvelN5OWhURWMwT1dSamRqWkdTMEowV25CR05TdG9XV294ZWt0bENpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", + "integratedTime": 1668077126, + "logIndex": 6821636, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" + } + }); + let bundle: Bundle = serde_json::from_value(bundle_value).expect("Cannot parse bundle"); + bundle + } + + #[test] + fn verify_correct_layer() { + let (signature_layer, cert_pem_raw) = test_data(); + + let vc = CertificateVerifier::from_pem(cert_pem_raw.as_bytes(), true, None) + .expect("cannot create verification constraint"); + assert!(vc.verify(&signature_layer).expect("error while verifying")); + } + + #[test] + fn rekor_integration() { + let (signature_layer, cert_pem_raw) = test_data(); + let signature_layer_without_rekor_bundle = SignatureLayer { + bundle: None, + ..signature_layer.clone() + }; + assert!(signature_layer_without_rekor_bundle.bundle.is_none()); + + let vc = CertificateVerifier::from_pem(cert_pem_raw.as_bytes(), true, None) + .expect("cannot create verification constraint"); + assert!(vc.verify(&signature_layer).expect("error while verifying")); + + // layer verification fails because there's no rekor bundle + assert!( + !vc.verify(&signature_layer_without_rekor_bundle) + .expect("error while verifying") + ); + + // verification constraint that does not enforce rekor integration + let vc = CertificateVerifier::from_pem(cert_pem_raw.as_bytes(), false, None) + .expect("cannot create verification constraint"); + assert!( + vc.verify(&signature_layer_without_rekor_bundle) + .expect("error while verifying") + ); + } + + #[test] + fn detect_signature_created_at_invalid_time() { + let (signature_layer, cert_pem_raw) = test_data(); + + let mut vc = CertificateVerifier::from_pem(cert_pem_raw.as_bytes(), true, None) + .expect("cannot create verification constraint"); + let not_before = UtcTime::from_system_time( + SystemTime::now() + .checked_sub(Duration::from_secs(60)) + .expect("cannot sub time by 60 seconds"), + ) + .expect("cannot create not_before timestamp"); + let not_after = UtcTime::from_system_time( + SystemTime::now() + .checked_add(Duration::from_secs(60)) + .expect("cannot add time by 60 seconds"), + ) + .expect("cannot create not_after timestamp"); + let validity = Validity { + not_before: Time::UtcTime(not_before), + not_after: Time::UtcTime(not_after), + }; + vc.cert_validity = validity; + assert!(!vc.verify(&signature_layer).expect("error while verifying")); + } +} diff --git a/vendor/src/cosign/verification_constraint/mod.rs b/vendor/src/cosign/verification_constraint/mod.rs new file mode 100644 index 0000000..e997697 --- /dev/null +++ b/vendor/src/cosign/verification_constraint/mod.rs @@ -0,0 +1,82 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Structs that can be used to verify [`crate::cosign::SignatureLayer`] +//! with special business logic. +//! +//! This module provides already the most common kind of verification constraints: +//! * [`PublicKeyVerifier`]: ensure a signature has been produced by a specific +//! cosign key +//! * [`CertSubjectEmailVerifier`]: ensure a signature has been produced in keyless mode, +//! plus the email address associated with the signer matches a specific one +//! * [`CertSubjectUrlVerifier`]: ensure a signature has been produced in keyless mode, +//! plus the certificate SAN has a specific URI inside of it. This can be used to verify +//! signatures produced by GitHub Actions. +//! +//! Developers can define ad-hoc validation logic by creating a Struct that implements +//! the [`VerificationConstraintVec`] trait. + +use super::signature_layers::SignatureLayer; +use crate::errors::Result; + +/// A list of objects implementing the [`VerificationConstraint`] trait +pub type VerificationConstraintVec = Vec>; + +/// A list of references to objects implementing the [`VerificationConstraint`] trait +pub type VerificationConstraintRefVec<'a> = Vec<&'a Box>; + +/// A trait that can be used to define verification constraints objects +/// that use a custom verification logic. +pub trait VerificationConstraint: std::fmt::Debug { + /// Given the `signature_layer` object, return `true` if the verification + /// check is satisfied. + /// + /// Developer can use the + /// [`errors::SigstoreError::VerificationConstraintError`](crate::errors::SigstoreError::VerificationConstraintError) + /// error when something goes wrong inside of the verification logic. + /// + /// ``` + /// use sigstore::{ + /// cosign::verification_constraint::VerificationConstraint, + /// cosign::signature_layers::SignatureLayer, + /// errors::{SigstoreError, Result}, + /// }; + /// + /// #[derive(Debug)] + /// struct MyVerifier{} + /// + /// impl VerificationConstraint for MyVerifier { + /// fn verify(&self, _sl: &SignatureLayer) -> Result { + /// Err(SigstoreError::VerificationConstraintError( + /// "something went wrong!".to_string())) + /// } + /// } + fn verify(&self, signature_layer: &SignatureLayer) -> Result; +} + +pub mod certificate_verifier; +pub use certificate_verifier::CertificateVerifier; + +pub mod public_key_verifier; +pub use public_key_verifier::PublicKeyVerifier; + +pub mod cert_subject_email_verifier; +pub use cert_subject_email_verifier::CertSubjectEmailVerifier; + +pub mod cert_subject_url_verifier; +pub use cert_subject_url_verifier::CertSubjectUrlVerifier; + +pub mod annotation_verifier; +pub use annotation_verifier::AnnotationVerifier; diff --git a/vendor/src/cosign/verification_constraint/public_key_verifier.rs b/vendor/src/cosign/verification_constraint/public_key_verifier.rs new file mode 100644 index 0000000..6ec37c6 --- /dev/null +++ b/vendor/src/cosign/verification_constraint/public_key_verifier.rs @@ -0,0 +1,59 @@ +use super::VerificationConstraint; +use crate::cosign::signature_layers::SignatureLayer; +use crate::crypto::{CosignVerificationKey, SigningScheme}; +use crate::errors::Result; + +/// Verification Constraint for signatures produced with public/private keys +#[derive(Debug)] +pub struct PublicKeyVerifier { + key: CosignVerificationKey, +} + +impl PublicKeyVerifier { + /// Create a new instance of `PublicKeyVerifier`. + /// The `key_raw` variable holds a PEM encoded representation of the + /// public key to be used at verification time. + pub fn new(key_raw: &[u8], signing_scheme: &SigningScheme) -> Result { + let key = CosignVerificationKey::from_pem(key_raw, signing_scheme)?; + Ok(PublicKeyVerifier { key }) + } + + /// Create a new instance of `PublicKeyVerifier`. + /// The `key_raw` variable holds a PEM encoded representation of the + /// public key to be used at verification time. The verification + /// algorithm will be derived from the public key type: + /// * `RSA public key`: `RSA_PKCS1_SHA256` + /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` + /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` + /// * `Ed25519 public key`: `Ed25519` + pub fn try_from(key_raw: &[u8]) -> Result { + let key = CosignVerificationKey::try_from_pem(key_raw)?; + Ok(PublicKeyVerifier { key }) + } +} + +impl VerificationConstraint for PublicKeyVerifier { + fn verify(&self, signature_layer: &SignatureLayer) -> Result { + Ok(signature_layer.is_signed_by_key(&self.key)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cosign::signature_layers::tests::{ + build_correct_signature_layer_with_certificate, + build_correct_signature_layer_without_bundle, + }; + + #[test] + fn pub_key_verifier() { + let (sl, key) = build_correct_signature_layer_without_bundle(); + + let vc = PublicKeyVerifier { key }; + assert!(vc.verify(&sl).unwrap()); + + let sl = build_correct_signature_layer_with_certificate(); + assert!(!vc.verify(&sl).unwrap()); + } +} diff --git a/vendor/src/crypto/certificate.rs b/vendor/src/crypto/certificate.rs new file mode 100644 index 0000000..46f8af4 --- /dev/null +++ b/vendor/src/crypto/certificate.rs @@ -0,0 +1,516 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Utc}; +use const_oid::db::rfc5912::ID_KP_CODE_SIGNING; +use thiserror::Error; +use x509_cert::{ + Certificate, + ext::pkix::{ExtendedKeyUsage, KeyUsage, KeyUsages, SubjectAltName, constraints}, +}; + +use crate::errors::{Result, SigstoreError}; + +/// Ensure the given certificate can be trusted for verifying cosign +/// signatures. +/// +/// The following checks are performed against the given certificate: +/// * The certificate has the right set of key usages +/// * The certificate cannot be used before the current time +pub(crate) fn is_trusted(certificate: &Certificate, integrated_time: i64) -> Result<()> { + verify_key_usages(certificate)?; + verify_has_san(certificate)?; + verify_validity(certificate)?; + verify_expiration(certificate, integrated_time)?; + + Ok(()) +} + +pub(crate) fn verify_key_usages(certificate: &Certificate) -> Result<()> { + let (_, key_usage) = certificate + .tbs_certificate + .get::() + .map_err(|_| SigstoreError::CertificateWithoutDigitalSignatureKeyUsage)? + .ok_or(SigstoreError::CertificateWithoutDigitalSignatureKeyUsage)?; + + if key_usage.0.bits() & KeyUsages::DigitalSignature as u16 == 1 { + return Err(SigstoreError::CertificateWithoutDigitalSignatureKeyUsage); + } + + let (_, key_ext_usage) = certificate + .tbs_certificate + .get::() + .map_err(|_| SigstoreError::CertificateWithoutCodeSigningKeyUsage)? + .ok_or(SigstoreError::CertificateWithoutCodeSigningKeyUsage)?; + + // code signing + if !key_ext_usage.0.contains(&ID_KP_CODE_SIGNING) { + return Err(SigstoreError::CertificateWithoutCodeSigningKeyUsage); + } + + Ok(()) +} + +pub(crate) fn verify_has_san(certificate: &Certificate) -> Result<()> { + if certificate + .tbs_certificate + .get::() + .map_err(|_| SigstoreError::CertificateWithoutSubjectAlternativeName)? + .is_some() + { + Ok(()) + } else { + Err(SigstoreError::CertificateWithoutSubjectAlternativeName) + } +} + +pub(crate) fn verify_validity(certificate: &Certificate) -> Result<()> { + // Comment taken from cosign verification code: + // THIS IS IMPORTANT: WE DO NOT CHECK TIMES HERE + // THE CERTIFICATE IS TREATED AS TRUSTED FOREVER + // WE CHECK THAT THE SIGNATURES WERE CREATED DURING THIS WINDOW + let validity = &certificate.tbs_certificate.validity; + if std::time::SystemTime::now() < validity.not_before.to_system_time() { + Err(SigstoreError::CertificateValidityError( + validity.not_before.to_string(), + )) + } else { + Ok(()) + } +} + +fn verify_expiration(certificate: &Certificate, integrated_time: i64) -> Result<()> { + let it = DateTime::::from_naive_utc_and_offset( + DateTime::from_timestamp(integrated_time, 0) + .ok_or(SigstoreError::X509Error("timestamp is not legal".into()))? + .naive_utc(), + Utc, + ); + let validity = &certificate.tbs_certificate.validity; + let not_before: DateTime = validity.not_before.to_system_time().into(); + if it < not_before { + return Err( + SigstoreError::CertificateExpiredBeforeSignaturesSubmittedToRekor { + integrated_time: it.to_string(), + not_before: validity.not_before.to_string(), + }, + ); + } + + let not_after: DateTime = validity.not_after.to_system_time().into(); + if it > not_after { + return Err( + SigstoreError::CertificateIssuedAfterSignaturesSubmittedToRekor { + integrated_time: it.to_string(), + not_after: validity.not_after.to_string(), + }, + ); + } + + Ok(()) +} + +#[derive(Debug, Error)] +pub enum ExtensionErrorKind { + #[error("certificate missing extension: {0}")] + Missing(&'static str), + + #[error("certificate extension bit not asserted: {0}")] + BitUnset(&'static str), + + #[error("certificate's {0} extension not marked as critical")] + NotCritical(&'static str), +} + +#[derive(Debug, Error)] +pub enum NotLeafErrorKind { + #[error("certificate is a CA: CAs are not leaves")] + IsCA, +} + +#[derive(Debug, Error)] +pub enum NotCAErrorKind { + #[error("certificate is not a CA: CAs must assert cA and keyCertSign")] + NotCA, + + #[error("certificate is not a root CA")] + NotRootCA, + + #[error("certificate in invalid state: cA={ca}, keyCertSign={key_cert_sign}")] + Invalid { ca: bool, key_cert_sign: bool }, +} + +#[derive(Debug, Error)] +#[error(transparent)] +pub enum CertificateValidationError { + #[error("only X509 V3 certificates are supported")] + VersionUnsupported, + + #[error("malformed certificate")] + Malformed(#[source] x509_cert::der::Error), + + NotLeaf(#[from] NotLeafErrorKind), + + NotCA(#[from] NotCAErrorKind), + + Extension(#[from] ExtensionErrorKind), +} + +/// Check if the given certificate is a leaf in the context of the Sigstore profile. +/// +/// * It is not a root or intermediate CA; +/// * It has `keyUsage.digitalSignature` +/// * It has `CODE_SIGNING` as an `ExtendedKeyUsage`. +/// +/// This function does not evaluate the trustworthiness of the certificate. +pub(crate) fn is_leaf( + certificate: &Certificate, +) -> core::result::Result<(), CertificateValidationError> { + // NOTE(jl): following structure of sigstore-python over the slightly different handling found + // in `verify_key_usages`. + let tbs = &certificate.tbs_certificate; + + // Only V3 certificates should appear in the context of Sigstore; earlier versions of X.509 lack + // extensions and have ambiguous CA behavior. + if tbs.version != x509_cert::Version::V3 { + Err(CertificateValidationError::VersionUnsupported)?; + } + + if is_ca(certificate).is_ok() { + Err(NotLeafErrorKind::IsCA)?; + }; + + let digital_signature = match tbs + .get::() + .map_err(CertificateValidationError::Malformed)? + { + None => Err(ExtensionErrorKind::Missing("KeyUsage"))?, + Some((_, key_usage)) => key_usage.digital_signature(), + }; + + if !digital_signature { + Err(ExtensionErrorKind::BitUnset("KeyUsage.digitalSignature"))?; + } + + // Finally, we check to make sure the leaf has an `ExtendedKeyUsages` + // extension that includes a codesigning entitlement. Sigstore should + // never issue a leaf that doesn't have this extended usage. + + let extended_key_usage = match tbs + .get::() + .map_err(CertificateValidationError::Malformed)? + { + None => Err(ExtensionErrorKind::Missing("ExtendedKeyUsage"))?, + Some((_, extended_key_usage)) => extended_key_usage, + }; + + if !extended_key_usage.0.contains(&ID_KP_CODE_SIGNING) { + Err(ExtensionErrorKind::BitUnset( + "ExtendedKeyUsage.digitalSignature", + ))?; + } + + Ok(()) +} + +/// Checks if the given `certificate` is a CA certificate. +/// +/// This does **not** indicate trustworthiness of the given `certificate`, only if it has the +/// appropriate interior state. +/// +/// This function is **not** naively invertible: users **must** use the dedicated `is_leaf` +/// utility function to determine whether a particular leaf upholds Sigstore's invariants. +pub(crate) fn is_ca( + certificate: &Certificate, +) -> core::result::Result<(), CertificateValidationError> { + let tbs = &certificate.tbs_certificate; + + // Only V3 certificates should appear in the context of Sigstore; earlier versions of X.509 lack + // extensions and have ambiguous CA behavior. + if tbs.version != x509_cert::Version::V3 { + return Err(CertificateValidationError::VersionUnsupported); + } + + // Valid CA certificates must have the following set: + // + // - `BasicKeyUsage.keyCertSign` + // - `BasicConstraints.ca` + // + // Any other combination of states is inconsistent and invalid, meaning + // that we won't treat the certificate as neither a leaf nor a CA. + + let ca = match tbs + .get::() + .map_err(CertificateValidationError::Malformed)? + { + None => Err(ExtensionErrorKind::Missing("BasicConstraints"))?, + Some((false, _)) => { + // BasicConstraints must be marked as critical, per RFC 5280 4.2.1.9. + Err(ExtensionErrorKind::NotCritical("BasicConstraints"))? + } + Some((true, v)) => v.ca, + }; + + let key_cert_sign = match tbs + .get::() + .map_err(CertificateValidationError::Malformed)? + { + None => Err(ExtensionErrorKind::Missing("KeyUsage"))?, + Some((_, v)) => v.key_cert_sign(), + }; + + // both states set, this is a CA. + if ca && key_cert_sign { + return Ok(()); + } + + if !(ca || key_cert_sign) { + Err(NotCAErrorKind::NotCA)?; + } + + // Anything else is an invalid state that should never occur. + Err(NotCAErrorKind::Invalid { ca, key_cert_sign })? +} + +/// Returns `True` if and only if the given `Certificate` indicates +/// that it's a root CA. +/// +/// This is **not** a verification function, and it does not establish +/// the trustworthiness of the given certificate. +pub(crate) fn is_root_ca( + certificate: &Certificate, +) -> core::result::Result<(), CertificateValidationError> { + // NOTE(ww): This function is obnoxiously long to make the different + // states explicit. + + let tbs = &certificate.tbs_certificate; + + // Only V3 certificates should appear in the context of Sigstore; earlier versions of X.509 lack + // extensions and have ambiguous CA behavior. + if tbs.version != x509_cert::Version::V3 { + return Err(CertificateValidationError::VersionUnsupported); + } + + // Non-CAs can't possibly be root CAs. + is_ca(certificate)?; + + // A certificate that is its own issuer and signer is considered a root CA. + if tbs.issuer != tbs.subject { + Err(NotCAErrorKind::NotRootCA)? + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::tests::*; + + use chrono::TimeDelta; + use x509_cert::der::Decode; + + #[test] + fn verify_cert_key_usages_success() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), CertGenerationOptions::default())?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + assert!(verify_key_usages(&cert).is_ok()); + + Ok(()) + } + + #[test] + fn verify_cert_key_usages_failure_because_no_digital_signature() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + digital_signature_key_usage: false, + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + let err = verify_key_usages(&cert).expect_err("Was supposed to return an error"); + let found = matches!( + err, + SigstoreError::CertificateWithoutDigitalSignatureKeyUsage + ); + assert!(found, "Didn't get expected error, got {:?} instead", err); + + Ok(()) + } + + #[test] + fn verify_cert_key_usages_failure_because_no_code_signing() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + code_signing_extended_key_usage: false, + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + let err = verify_key_usages(&cert).expect_err("Was supposed to return an error"); + let found = matches!(err, SigstoreError::CertificateWithoutCodeSigningKeyUsage); + assert!(found, "Didn't get expected error, got {:?} instead", err); + + Ok(()) + } + + #[test] + fn verify_cert_failure_because_no_san() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + subject_email: None, + subject_url: None, + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + let error = verify_has_san(&cert).expect_err("Didn't get an error"); + let found = matches!( + error, + SigstoreError::CertificateWithoutSubjectAlternativeName + ); + assert!(found, "Didn't get the expected error: {}", error); + + Ok(()) + } + + #[test] + fn verify_cert_validity_success() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), CertGenerationOptions::default())?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + assert!(verify_validity(&cert).is_ok()); + + Ok(()) + } + + #[test] + fn verify_cert_validity_failure() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + not_before: Utc::now() + .checked_add_signed(TimeDelta::try_days(5).unwrap()) + .unwrap(), + not_after: Utc::now() + .checked_add_signed(TimeDelta::try_days(6).unwrap()) + .unwrap(), + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + let err = verify_validity(&cert).expect_err("Was expecting an error"); + let found = matches!(err, SigstoreError::CertificateValidityError(_)); + assert!(found, "Didn't get expected error, got {:?} instead", err); + + Ok(()) + } + + #[test] + fn verify_cert_expiration_success() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let integrated_time = Utc::now(); + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + not_before: Utc::now() + .checked_sub_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(), + not_after: Utc::now() + .checked_add_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(), + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + assert!(verify_expiration(&cert, integrated_time.timestamp(),).is_ok()); + + Ok(()) + } + + #[test] + fn verify_cert_expiration_failure() -> anyhow::Result<()> { + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let integrated_time = Utc::now() + .checked_add_signed(TimeDelta::try_days(5).unwrap()) + .unwrap(); + + let issued_cert = generate_certificate( + Some(&ca_data), + CertGenerationOptions { + not_before: Utc::now() + .checked_sub_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(), + not_after: Utc::now() + .checked_add_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(), + ..Default::default() + }, + )?; + let issued_cert_pem = issued_cert.cert.to_pem().unwrap(); + let pem = pem::parse(issued_cert_pem)?; + let cert = x509_cert::Certificate::from_der(pem.contents())?; + + let err = verify_expiration(&cert, integrated_time.timestamp()) + .expect_err("Was expecting an error"); + let found = matches!( + err, + SigstoreError::CertificateIssuedAfterSignaturesSubmittedToRekor { + integrated_time: _, + not_after: _, + } + ); + assert!(found, "Didn't get expected error, got {:?} instead", err); + + Ok(()) + } +} diff --git a/vendor/src/crypto/certificate_pool.rs b/vendor/src/crypto/certificate_pool.rs new file mode 100644 index 0000000..2cac254 --- /dev/null +++ b/vendor/src/crypto/certificate_pool.rs @@ -0,0 +1,120 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use const_oid::db::rfc5280::ID_KP_CODE_SIGNING; +use pki_types::{CertificateDer, TrustAnchor, UnixTime}; +use webpki::{EndEntityCert, KeyUsage, VerifiedPath}; + +use crate::errors::{Result as SigstoreResult, SigstoreError}; + +/// A collection of trusted root certificates. +#[derive(Default, Debug)] +pub(crate) struct CertificatePool { + trusted_roots: Vec>, + intermediates: Vec>, +} + +impl CertificatePool { + /// Builds a `CertificatePool` instance using the provided list of [`Certificate`]. + pub(crate) fn from_certificates<'r, 'i, R, I>( + trusted_roots: R, + untrusted_intermediates: I, + ) -> SigstoreResult + where + R: IntoIterator>, + I: IntoIterator>, + { + Ok(CertificatePool { + trusted_roots: trusted_roots + .into_iter() + .map(|x| Ok(webpki::anchor_from_trusted_cert(&x)?.to_owned())) + .collect::, webpki::Error>>()?, + intermediates: untrusted_intermediates + .into_iter() + .map(|i| i.into_owned()) + .collect(), + }) + } + + /// Ensures the given certificate has been issued by one of the trusted root certificates + /// An `Err` is returned when the verification fails. + /// + /// **Note well:** certificates issued by Fulcio are, by design, valid only + /// for a really limited amount of time. + /// Because of that the validity checks performed by this method are more + /// relaxed. The validity checks are done inside of + /// [`crate::crypto::verify_validity`] and [`crate::crypto::verify_expiration`]. + pub(crate) fn verify_pem_cert( + &self, + cert_pem: &[u8], + verification_time: Option, + ) -> SigstoreResult<()> { + let cert_pem = pem::parse(cert_pem)?; + if cert_pem.tag() != "CERTIFICATE" { + return Err(SigstoreError::CertificatePoolError( + "PEM file is not a certificate".into(), + )); + } + + self.verify_der_cert(cert_pem.contents(), verification_time) + } + + /// Ensures the given certificate has been issued by one of the trusted root certificates + /// An `Err` is returned when the verification fails. + /// + /// **Note well:** certificates issued by Fulcio are, by design, valid only + /// for a really limited amount of time. + /// Because of that the validity checks performed by this method are more + /// relaxed. The validity checks are done inside of + /// [`crate::crypto::verify_validity`] and [`crate::crypto::verify_expiration`]. + pub(crate) fn verify_der_cert( + &self, + der: &[u8], + verification_time: Option, + ) -> SigstoreResult<()> { + let der = CertificateDer::from(der); + let cert = EndEntityCert::try_from(&der)?; + let time = std::time::Duration::from_secs(chrono::Utc::now().timestamp() as u64); + + self.verify_cert_with_time( + &cert, + verification_time.unwrap_or(UnixTime::since_unix_epoch(time)), + )?; + + Ok(()) + } + + pub(crate) fn verify_cert_with_time<'a, 'cert>( + &'a self, + cert: &'cert EndEntityCert<'cert>, + verification_time: UnixTime, + ) -> Result, webpki::Error> + where + 'a: 'cert, + { + let signing_algs = webpki::ALL_VERIFICATION_ALGS; + let eku_code_signing = ID_KP_CODE_SIGNING.as_bytes(); + + cert.verify_for_usage( + signing_algs, + &self.trusted_roots, + self.intermediates.as_slice(), + verification_time, + KeyUsage::required(eku_code_signing), + None, + None, + ) + } +} diff --git a/vendor/src/crypto/keyring.rs b/vendor/src/crypto/keyring.rs new file mode 100644 index 0000000..afa0d67 --- /dev/null +++ b/vendor/src/crypto/keyring.rs @@ -0,0 +1,173 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; + +use aws_lc_rs::{signature as aws_lc_rs_signature, signature::UnparsedPublicKey}; +use const_oid::db::rfc5912::{ID_EC_PUBLIC_KEY, RSA_ENCRYPTION, SECP_256_R_1}; +use digest::Digest; +use thiserror::Error; +use x509_cert::{ + der, + der::{Decode, Encode}, + spki::SubjectPublicKeyInfoOwned, +}; + +#[derive(Error, Debug)] +pub enum KeyringError { + #[error("malformed key")] + KeyMalformed(#[from] x509_cert::der::Error), + #[error("unsupported algorithm")] + AlgoUnsupported, + + #[error("requested key not in keyring")] + KeyNotFound, + #[error("verification failed")] + VerificationFailed, +} +type Result = std::result::Result; + +/// A CT signing key. +struct Key { + inner: UnparsedPublicKey>, + /// The key's RFC 6962-style "key ID". + /// + fingerprint: [u8; 32], +} + +impl Key { + /// Creates a `Key` from a DER blob containing a SubjectPublicKeyInfo object. + pub fn new(spki_bytes: &[u8]) -> Result { + let spki = SubjectPublicKeyInfoOwned::from_der(spki_bytes)?; + let (algo, params) = if let Some(params) = &spki.algorithm.parameters { + // Special-case RSA keys, which don't have SPKI parameters. + if spki.algorithm.oid == RSA_ENCRYPTION && params == &der::Any::null() { + // TODO(tnytown): Do we need to support RSA keys? + return Err(KeyringError::AlgoUnsupported); + }; + + (spki.algorithm.oid, params.decode_as()?) + } else { + return Err(KeyringError::AlgoUnsupported); + }; + + match (algo, params) { + // TODO(tnytown): should we also accept ed25519, p384, ... ? + (ID_EC_PUBLIC_KEY, SECP_256_R_1) => Ok(Key { + inner: UnparsedPublicKey::new( + &aws_lc_rs_signature::ECDSA_P256_SHA256_ASN1, + spki.subject_public_key.raw_bytes().to_owned(), + ), + fingerprint: { + let mut hasher = sha2::Sha256::new(); + spki.encode(&mut hasher).expect("failed to hash key!"); + hasher.finalize().into() + }, + }), + _ => Err(KeyringError::AlgoUnsupported), + } + } +} + +/// Represents a set of CT signing keys, each of which is potentially a valid signer for +/// Signed Certificate Timestamps (SCTs) or Signed Tree Heads (STHs). +pub struct Keyring(HashMap<[u8; 32], Key>); + +impl Keyring { + /// Creates a `Keyring` from DER encoded SPKI-format public keys. + pub fn new<'a>(keys: impl IntoIterator) -> Result { + Ok(Self( + keys.into_iter() + .flat_map(Key::new) + .map(|k| Ok((k.fingerprint, k))) + .collect::>()?, + )) + } + + /// Verifies `data` against a `signature` with a public key identified by `key_id`. + pub fn verify(&self, key_id: &[u8; 32], signature: &[u8], data: &[u8]) -> Result<()> { + let key = self.0.get(key_id).ok_or(KeyringError::KeyNotFound)?; + + key.inner + .verify(data, signature) + .or(Err(KeyringError::VerificationFailed))?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Keyring; + use crate::crypto::signing_key::ecdsa::{ECDSAKeys, EllipticCurve}; + use digest::Digest; + use std::io::Write; + + #[test] + fn verify_keyring() { + let message = b"some message"; + + // Create a key pair and a keyring containing the public key. + let key_pair = ECDSAKeys::new(EllipticCurve::P256).unwrap(); + let signer = key_pair.to_sigstore_signer().unwrap(); + let pub_key = key_pair.as_inner().public_key_to_der().unwrap(); + let keyring = Keyring::new([pub_key.as_slice()]).unwrap(); + + // Generate the signature. + let signature = signer.sign(message).unwrap(); + + // Generate the key id. + let mut hasher = sha2::Sha256::new(); + hasher.write_all(pub_key.as_slice()).unwrap(); + let key_id: [u8; 32] = hasher.finalize().into(); + + // Check for success. + assert!( + keyring + .verify(&key_id, signature.as_slice(), message) + .is_ok() + ); + + // Check for failure with incorrect key id. + assert!( + keyring + .verify(&[0; 32], signature.as_slice(), message) + .is_err() + ); + + // Check for failure with incorrect payload. + let incorrect_message = b"another message"; + + assert!( + keyring + .verify(&key_id, signature.as_slice(), incorrect_message) + .is_err() + ); + + // Check for failure with incorrect keyring. + let incorrect_key_pair = ECDSAKeys::new(EllipticCurve::P256).unwrap(); + let incorrect_keyring = Keyring::new([incorrect_key_pair + .as_inner() + .public_key_to_der() + .unwrap() + .as_slice()]) + .unwrap(); + + assert!( + incorrect_keyring + .verify(&key_id, signature.as_slice(), message) + .is_err() + ); + } +} diff --git a/vendor/src/crypto/mod.rs b/vendor/src/crypto/mod.rs new file mode 100644 index 0000000..4409e4f --- /dev/null +++ b/vendor/src/crypto/mod.rs @@ -0,0 +1,496 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Structures and constants required to perform cryptographic operations. + +use sha2::{Sha256, Sha384}; + +use crate::errors::*; + +pub use signing_key::SigStoreSigner; +pub use verification_key::CosignVerificationKey; + +/// Different digital signature algorithms. +/// * `RSA_PSS_SHA256`: RSA PSS padding using SHA-256 +/// for RSA signatures. All the `usize` member inside +/// an RSA enum represents the key size of the RSA key. +/// * `RSA_PSS_SHA384`: RSA PSS padding using SHA-384 +/// for RSA signatures. +/// * `RSA_PSS_SHA512`: RSA PSS padding using SHA-512 +/// for RSA signatures. +/// * `RSA_PKCS1_SHA256`: PKCS#1 1.5 padding using +/// SHA-256 for RSA signatures. +/// * `RSA_PKCS1_SHA384`: PKCS#1 1.5 padding using +/// SHA-384 for RSA signatures. +/// * `RSA_PKCS1_SHA512`: PKCS#1 1.5 padding using +/// SHA-512 for RSA signatures. +/// * `ECDSA_P256_SHA256_ASN1`: ASN.1 DER-encoded ECDSA +/// signatures using the P-256 curve and SHA-256. It +/// is the default signing scheme. +/// * `ECDSA_P384_SHA384_ASN1`: ASN.1 DER-encoded ECDSA +/// signatures using the P-384 curve and SHA-384. +/// * `ED25519`: ECDSA signature using SHA2-512 +/// as the digest function and curve edwards25519. The +/// signature format please refer +/// to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.6). +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum SigningScheme { + RSA_PSS_SHA256(usize), + RSA_PSS_SHA384(usize), + RSA_PSS_SHA512(usize), + RSA_PKCS1_SHA256(usize), + RSA_PKCS1_SHA384(usize), + RSA_PKCS1_SHA512(usize), + ECDSA_P256_SHA256_ASN1, + ECDSA_P384_SHA384_ASN1, + ED25519, +} + +impl std::fmt::Display for SigningScheme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SigningScheme::RSA_PSS_SHA256(_) => write!(f, "RSA_PSS_SHA256"), + SigningScheme::RSA_PSS_SHA384(_) => write!(f, "RSA_PSS_SHA384"), + SigningScheme::RSA_PSS_SHA512(_) => write!(f, "RSA_PSS_SHA512"), + SigningScheme::RSA_PKCS1_SHA256(_) => write!(f, "RSA_PKCS1_SHA256"), + SigningScheme::RSA_PKCS1_SHA384(_) => write!(f, "RSA_PKCS1_SHA384"), + SigningScheme::RSA_PKCS1_SHA512(_) => write!(f, "RSA_PKCS1_SHA512"), + SigningScheme::ECDSA_P256_SHA256_ASN1 => write!(f, "ECDSA_P256_SHA256_ASN1"), + SigningScheme::ECDSA_P384_SHA384_ASN1 => write!(f, "ECDSA_P384_SHA384_ASN1"), + SigningScheme::ED25519 => write!(f, "ED25519"), + } + } +} + +impl TryFrom<&str> for SigningScheme { + type Error = String; + + fn try_from(value: &str) -> std::result::Result { + match value { + "ECDSA_P256_SHA256_ASN1" => Ok(Self::ECDSA_P256_SHA256_ASN1), + "ECDSA_P384_SHA384_ASN1" => Ok(Self::ECDSA_P384_SHA384_ASN1), + "ED25519" => Ok(Self::ED25519), + "RSA_PSS_SHA256" => Ok(Self::RSA_PSS_SHA256(DEFAULT_KEY_SIZE)), + "RSA_PSS_SHA384" => Ok(Self::RSA_PSS_SHA384(DEFAULT_KEY_SIZE)), + "RSA_PSS_SHA512" => Ok(Self::RSA_PSS_SHA512(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA256" => Ok(Self::RSA_PKCS1_SHA256(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA384" => Ok(Self::RSA_PKCS1_SHA384(DEFAULT_KEY_SIZE)), + "RSA_PKCS1_SHA512" => Ok(Self::RSA_PKCS1_SHA512(DEFAULT_KEY_SIZE)), + unknown => Err(format!("Unsupported signing algorithm: {unknown}")), + } + } +} + +impl SigningScheme { + /// Create a key-pair due to the given signing scheme. + pub fn create_signer(&self) -> Result { + Ok(match self { + SigningScheme::ECDSA_P256_SHA256_ASN1 => SigStoreSigner::ECDSA_P256_SHA256_ASN1( + EcdsaSigner::<_, Sha256>::from_ecdsa_keys(&EcdsaKeys::::new()?)?, + ), + SigningScheme::ECDSA_P384_SHA384_ASN1 => SigStoreSigner::ECDSA_P384_SHA384_ASN1( + EcdsaSigner::<_, Sha384>::from_ecdsa_keys(&EcdsaKeys::::new()?)?, + ), + SigningScheme::ED25519 => { + SigStoreSigner::ED25519(Ed25519Signer::from_ed25519_keys(&Ed25519Keys::new()?)?) + } + SigningScheme::RSA_PSS_SHA256(bit_size) => { + SigStoreSigner::RSA_PSS_SHA256(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha256, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PSS_SHA384(bit_size) => { + SigStoreSigner::RSA_PSS_SHA384(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha384, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PSS_SHA512(bit_size) => { + SigStoreSigner::RSA_PSS_SHA512(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha512, + PaddingScheme::PSS, + )) + } + SigningScheme::RSA_PKCS1_SHA256(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA256(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha256, + PaddingScheme::PKCS1v15, + )) + } + SigningScheme::RSA_PKCS1_SHA384(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA384(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha384, + PaddingScheme::PKCS1v15, + )) + } + SigningScheme::RSA_PKCS1_SHA512(bit_size) => { + SigStoreSigner::RSA_PKCS1_SHA512(RSASigner::from_rsa_keys( + &RSAKeys::new(*bit_size)?, + DigestAlgorithm::Sha512, + PaddingScheme::PKCS1v15, + )) + } + }) + } +} + +/// The default signature verification algorithm used by Sigstore. +/// Sigstore relies on NIST P-256 +/// NIST P-256 is a Weierstrass curve specified in [FIPS 186-4: Digital Signature Standard (DSS)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf). +/// Also known as prime256v1 (ANSI X9.62) and secp256r1 (SECG) +impl Default for SigningScheme { + fn default() -> Self { + SigningScheme::ECDSA_P256_SHA256_ASN1 + } +} + +/// A signature produced by a private key +pub enum Signature<'a> { + /// Raw signature. There's no need to process the contents + Raw(&'a [u8]), + /// A base64 encoded signature + Base64Encoded(&'a [u8]), +} + +#[cfg(feature = "cert")] +pub(crate) mod certificate; +#[cfg(feature = "cert")] +pub(crate) mod certificate_pool; +#[cfg(feature = "cert")] +pub(crate) use certificate_pool::CertificatePool; +#[cfg(feature = "cert")] +pub(crate) mod keyring; + +pub mod verification_key; + +use self::signing_key::{ + ecdsa::ec::{EcdsaKeys, EcdsaSigner}, + ed25519::{Ed25519Keys, Ed25519Signer}, + rsa::{DEFAULT_KEY_SIZE, DigestAlgorithm, PaddingScheme, RSASigner, keypair::RSAKeys}, +}; + +pub mod signing_key; + +#[cfg(any(feature = "sign", feature = "verify"))] +pub(crate) mod transparency; + +#[cfg(test)] +pub(crate) mod tests { + use chrono::{DateTime, TimeDelta, Utc}; + use openssl::asn1::{Asn1Integer, Asn1Time}; + use openssl::bn::{BigNum, MsbOption}; + use openssl::conf::{Conf, ConfMethod}; + use openssl::ec::{EcGroup, EcKey}; + use openssl::hash::MessageDigest; + use openssl::nid::Nid; + use openssl::pkey::{self, Id, PKey}; + use openssl::x509::extension::{ + AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, + SubjectAlternativeName, SubjectKeyIdentifier, + }; + use openssl::x509::{X509, X509Extension, X509NameBuilder}; + + pub(crate) const PUBLIC_KEY: &str = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENptdY/l3nB0yqkXLBWkZWQwo6+cu +OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== +-----END PUBLIC KEY-----"#; + + pub(crate) struct CertData { + pub cert: X509, + pub private_key: pkey::PKey, + } + + pub(crate) struct CertGenerationOptions { + pub digital_signature_key_usage: bool, + pub code_signing_extended_key_usage: bool, + pub subject_email: Option, + pub subject_url: Option, + //TODO: remove macro once https://github.com/sfackler/rust-openssl/issues/1411 + //is fixed + #[allow(dead_code)] + pub subject_issuer: Option, + pub not_before: DateTime, + pub not_after: DateTime, + pub private_key: pkey::PKey, + pub public_key: pkey::PKey, + } + + impl Default for CertGenerationOptions { + fn default() -> Self { + let not_before = Utc::now() + .checked_sub_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(); + let not_after = Utc::now() + .checked_add_signed(TimeDelta::try_days(1).unwrap()) + .unwrap(); + + // Sigstore relies on NIST P-256 + // NIST P-256 is a Weierstrass curve specified in FIPS 186-4: Digital Signature Standard (DSS): + // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + // Also known as prime256v1 (ANSI X9.62) and secp256r1 (SECG) + let (private_key, public_key) = generate_ecdsa_p256_keypair(); + + CertGenerationOptions { + digital_signature_key_usage: true, + code_signing_extended_key_usage: true, + subject_email: Some(String::from("tests@sigstore-rs.dev")), + subject_issuer: Some(String::from("https://sigstore.dev/oauth")), + subject_url: None, + not_before, + not_after, + private_key, + public_key, + } + } + } + + pub(crate) fn generate_ecdsa_p256_keypair() + -> (pkey::PKey, pkey::PKey) { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).expect("Cannot create EcGroup"); + let ec_private_key = EcKey::generate(&group).expect("Cannot create private key"); + let ec_public_key = ec_private_key.public_key(); + let ec_pub_key = + EcKey::from_public_key(&group, ec_public_key).expect("Cannot create ec pub key"); + + let public_key = pkey::PKey::from_ec_key(ec_pub_key).expect("Cannot create pkey"); + let private_key = pkey::PKey::from_ec_key(ec_private_key).expect("Cannot create pkey"); + + (private_key, public_key) + } + + pub(crate) fn generate_ecdsa_p384_keypair() + -> (pkey::PKey, pkey::PKey) { + let group = EcGroup::from_curve_name(Nid::SECP384R1).expect("Cannot create EcGroup"); + let ec_private_key = EcKey::generate(&group).expect("Cannot create private key"); + let ec_public_key = ec_private_key.public_key(); + let ec_pub_key = + EcKey::from_public_key(&group, ec_public_key).expect("Cannot create ec pub key"); + + let public_key = pkey::PKey::from_ec_key(ec_pub_key).expect("Cannot create pkey"); + let private_key = pkey::PKey::from_ec_key(ec_private_key).expect("Cannot create pkey"); + + (private_key, public_key) + } + + pub(crate) fn generate_ed25519_keypair() -> (pkey::PKey, pkey::PKey) + { + let private_key = PKey::generate_ed25519().expect("Cannot create private key"); + let public_key = private_key + .raw_public_key() + .expect("Cannot export public key"); + let public_key = PKey::public_key_from_raw_bytes(&public_key, Id::ED25519) + .expect("Cannot create ec pub key"); + + (private_key, public_key) + } + + pub(crate) fn generate_rsa_keypair( + bits: u32, + ) -> (pkey::PKey, pkey::PKey) { + use openssl::rsa; + + let rsa_private_key = rsa::Rsa::generate(bits).expect("Cannot generate RSA key"); + let rsa_public_key_pem = rsa_private_key + .public_key_to_pem() + .expect("Cannot obtain public key"); + let rsa_public_key = rsa::Rsa::public_key_from_pem(&rsa_public_key_pem) + .expect("Cannot create rsa_public_key"); + + let private_key = pkey::PKey::from_rsa(rsa_private_key).expect("cannot create private_key"); + let public_key = pkey::PKey::from_rsa(rsa_public_key).expect("cannot create public_key"); + + (private_key, public_key) + } + + pub(crate) fn generate_dsa_keypair( + bits: u32, + ) -> (pkey::PKey, pkey::PKey) { + use openssl::dsa; + + let dsa_private_key = dsa::Dsa::generate(bits).expect("Cannot generate DSA key"); + let dsa_public_key_pem = dsa_private_key + .public_key_to_pem() + .expect("Cannot obtain public key"); + let dsa_public_key = dsa::Dsa::public_key_from_pem(&dsa_public_key_pem) + .expect("Cannot create rsa_public_key"); + + let private_key = pkey::PKey::from_dsa(dsa_private_key).expect("cannot create private_key"); + let public_key = pkey::PKey::from_dsa(dsa_public_key).expect("cannot create public_key"); + + (private_key, public_key) + } + + pub(crate) fn generate_certificate( + issuer: Option<&CertData>, + settings: CertGenerationOptions, + ) -> anyhow::Result { + let mut x509_name_builder = X509NameBuilder::new()?; + x509_name_builder.append_entry_by_text("O", "tests")?; + x509_name_builder.append_entry_by_text("CN", "sigstore.test")?; + let x509_name = x509_name_builder.build(); + + let mut x509_builder = openssl::x509::X509::builder()?; + x509_builder.set_subject_name(&x509_name)?; + x509_builder + .set_pubkey(&settings.public_key) + .expect("Cannot set public key"); + + // set serial number + let mut big = BigNum::new().expect("Cannot create BigNum"); + big.rand(152, MsbOption::MAYBE_ZERO, true)?; + let serial_number = Asn1Integer::from_bn(&big)?; + x509_builder.set_serial_number(&serial_number)?; + + // set version 3 + x509_builder.set_version(2)?; + + // x509 v3 extensions + let conf = Conf::new(ConfMethod::default())?; + let x509v3_context = match issuer { + Some(issuer_data) => x509_builder.x509v3_context(Some(&issuer_data.cert), Some(&conf)), + None => x509_builder.x509v3_context(None, Some(&conf)), + }; + + let mut extensions: Vec = Vec::new(); + + let x509_extension_subject_key_identifier = + SubjectKeyIdentifier::new().build(&x509v3_context)?; + extensions.push(x509_extension_subject_key_identifier); + + // CA usage + if issuer.is_none() { + // CA usage + let x509_basic_constraint_ca = + BasicConstraints::new().critical().ca().pathlen(1).build()?; + extensions.push(x509_basic_constraint_ca); + } else { + let x509_basic_constraint_ca = BasicConstraints::new().critical().build()?; + extensions.push(x509_basic_constraint_ca); + } + + // set key usage + if issuer.is_some() { + if settings.digital_signature_key_usage { + let key_usage = KeyUsage::new().critical().digital_signature().build()?; + extensions.push(key_usage); + } + + if settings.code_signing_extended_key_usage { + let extended_key_usage = ExtendedKeyUsage::new().code_signing().build()?; + extensions.push(extended_key_usage); + } + } else { + let key_usage = KeyUsage::new() + .critical() + .crl_sign() + .key_cert_sign() + .build()?; + extensions.push(key_usage); + } + + // extensions that diverge, based on whether we're creating the CA or + // a certificate issued by it + if issuer.is_none() { + } else { + let x509_extension_authority_key_identifier = AuthorityKeyIdentifier::new() + .keyid(true) + .build(&x509v3_context)?; + extensions.push(x509_extension_authority_key_identifier); + + if settings.subject_email.is_some() && settings.subject_url.is_some() { + panic!( + "cosign doesn't generate certificates with a SAN that has both email and url" + ); + } + if let Some(email) = settings.subject_email { + let x509_extension_san = SubjectAlternativeName::new() + .critical() + .email(&email) + .build(&x509v3_context)?; + + extensions.push(x509_extension_san); + }; + if let Some(url) = settings.subject_url { + let x509_extension_san = SubjectAlternativeName::new() + .critical() + .uri(&url) + .build(&x509v3_context)?; + + extensions.push(x509_extension_san); + } + // + // TODO: uncomment once https://github.com/sfackler/rust-openssl/issues/1411 + // is fixed. This would allow to test also the parsing of the custom fields + // added to certificate extensions + //if let Some(subject_issuer) = settings.subject_issuer { + // let sigstore_issuer_asn1_obj = Asn1Object::from_str("1.3.6.1.4.1.57264.1.1")?; //&SIGSTORE_ISSUER_OID.to_string())?; + + // let value = format!("ASN1:UTF8String:{}", subject_issuer); + + // let sigstore_subject_issuer_extension = X509Extension::new_nid( + // None, + // Some(&x509v3_context), + // sigstore_issuer_asn1_obj.nid(), + // //&subject_issuer, + // &value, + // )?; + + // extensions.push(sigstore_subject_issuer_extension); + //} + } + + for ext in extensions { + x509_builder.append_extension(ext)?; + } + + // setup validity + let not_before = Asn1Time::from_unix(settings.not_before.timestamp())?; + let not_after = Asn1Time::from_unix(settings.not_after.timestamp())?; + x509_builder.set_not_after(¬_after)?; + x509_builder.set_not_before(¬_before)?; + + // set issuer + if let Some(issuer_data) = issuer { + let issuer_name = issuer_data.cert.subject_name(); + x509_builder.set_issuer_name(issuer_name)?; + } else { + // self signed cert + x509_builder.set_issuer_name(&x509_name)?; + } + + // sign the cert + let issuer_pkey = match issuer { + Some(issuer_data) => issuer_data.private_key.clone(), + None => settings.private_key.clone(), + }; + x509_builder + .sign(&issuer_pkey, MessageDigest::sha256()) + .expect("Cannot sign certificate"); + + let x509 = x509_builder.build(); + + Ok(CertData { + cert: x509, + private_key: settings.private_key, + }) + } +} diff --git a/vendor/src/crypto/signing_key/ecdsa/ec.rs b/vendor/src/crypto/signing_key/ecdsa/ec.rs new file mode 100644 index 0000000..399da6c --- /dev/null +++ b/vendor/src/crypto/signing_key/ecdsa/ec.rs @@ -0,0 +1,563 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # ECDSA Keys in Generic Types +//! +//! This is a wrapper for Rust Crypto. Basically it +//! is implemented using generic types and traits. Generic types +//! may let the user to manually include concrete crates like +//! `p256`, `p384`, `digest`, etc. This is unfriendly to users. +//! To make it easier for an user to use, there are two wrappers: +//! * The [`EcdsaKeys`] generic struct is wrapped in an enum named [`ECDSAKeys`]. +//! * The [`EcdsaSigner`] generic struct is wrapped in an enum named [`super::SigStoreSigner`]. +//! +//! The [`ECDSAKeys`] has two enums due to their underlying elliptic curves, s.t. +//! * `P256` +//! * `P384` +//! +//! To have an uniform interface for all kinds of asymmetric keys, [`ECDSAKeys`] +//! is also wrapped in [`super::super::SigStoreKeyPair`] enum. +//! +//! The [`super::SigStoreSigner`] enum includes two enums for [`EcdsaSigner`]: +//! * `ECDSA_P256_SHA256_ASN1` +//! * `ECDSA_P384_SHA384_ASN1` +//! +//! # EC Key Pair Operations +//! +//! *Not recommend to directly use this mod. Use [`ECDSAKeys`], [`super::super::SigStoreKeyPair`] for +//! key pair and [`super::SigStoreSigner`] for signing instead* +//! +//! When to generate an EC key pair, a specific elliptic curve +//! should be chosen. Supported elliptic curves are listed +//! . +//! +//! For example, use `P256` as elliptic curve, and `ECDSA_P256_SHA256_ASN1` as +//! signing scheme +//! +//! ```rust +//! use sigstore::crypto::signing_key::{ecdsa::ec::{EcdsaKeys,EcdsaSigner}, KeyPair, Signer}; +//! +//! let ec_key_pair = EcdsaKeys::::new().unwrap(); +//! +//! // export the pem encoded public key. +//! let pubkey = ec_key_pair.public_key_to_pem().unwrap(); +//! +//! // export the private key using sigstore encryption. +//! let privkey = ec_key_pair.private_key_to_encrypted_pem(b"password").unwrap(); +//! +//! // sign with the new key, using Sha256 as the digest scheme. +//! // In fact, the signing scheme is ECDSA_P256_SHA256_ASN1 here. +//! let ec_signer = EcdsaSigner::<_, sha2::Sha256>::from_ecdsa_keys(&ec_key_pair).unwrap(); +//! +//! let signature = ec_signer.sign(b"some message"); +//! ``` + +use std::{marker::PhantomData, ops::Add}; + +use digest::{ + Digest, FixedOutput, FixedOutputReset, + core_api::BlockSizeUser, + typenum::{ + UInt, UTerm, + bit::{B0, B1}, + }, +}; +use ecdsa::{ + PrimeCurve, SignatureSize, SigningKey, + hazmat::{DigestPrimitive, SignPrimitive}, +}; +#[allow(deprecated)] +use elliptic_curve::generic_array::ArrayLength; +use elliptic_curve::{ + AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey, Scalar, SecretKey, + bigint::ArrayEncoding, + ops::{Invert, Reduce}, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + subtle::CtOption, + zeroize::Zeroizing, +}; +use pkcs8::{AssociatedOid, DecodePrivateKey, EncodePrivateKey, EncodePublicKey}; +use signature::DigestSigner; + +use crate::{ + crypto::{ + SigningScheme, + signing_key::{ + COSIGN_PRIVATE_KEY_PEM_LABEL, KeyPair, PRIVATE_KEY_PEM_LABEL, + SIGSTORE_PRIVATE_KEY_PEM_LABEL, Signer, kdf, + }, + verification_key::CosignVerificationKey, + }, + errors::*, +}; + +use super::ECDSAKeys; + +/// The generic parameter for `C` can be chosen from the following: +/// * `p256::NistP256`: `P-256`, also known as `secp256r1` or `prime256v1`. +/// * `p384::NistP384`: `P-384`, also known as `secp384r1`. +/// +/// More elliptic curves, please refer to +/// . +#[derive(Clone, Debug)] +pub struct EcdsaKeys +where + C: Curve + CurveArithmetic + pkcs8::AssociatedOid, +{ + ec_seckey: SecretKey, + public_key: PublicKey, +} + +impl EcdsaKeys +where + C: Curve + AssociatedOid + CurveArithmetic + PrimeCurve, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Create a new `EcdsaKeys` Object, the generic parameter indicates + /// the elliptic curve. Please refer to + /// for curves. + /// The secret key (private key) will be randomly + /// generated. + pub fn new() -> Result { + let ec_seckey: SecretKey = SecretKey::random(&mut rand::rngs::OsRng); + + let public_key = ec_seckey.public_key(); + Ok(EcdsaKeys { + ec_seckey, + public_key, + }) + } + + /// Builds a `EcdsaKeys` from encrypted pkcs8 PEM-encoded private key. + /// The label should be [`COSIGN_PRIVATE_KEY_PEM_LABEL`] or + /// [`SIGSTORE_PRIVATE_KEY_PEM_LABEL`]. + pub fn from_encrypted_pem(private_key: &[u8], password: &[u8]) -> Result { + let key = pem::parse(private_key)?; + match key.tag() { + COSIGN_PRIVATE_KEY_PEM_LABEL | SIGSTORE_PRIVATE_KEY_PEM_LABEL => { + let der = kdf::decrypt(key.contents(), password)?; + let pkcs8 = pkcs8::PrivateKeyInfo::try_from(&der[..]).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {e}")) + })?; + let ec_seckey = SecretKey::::from_sec1_der(pkcs8.private_key)?; + Self::from_private_key(ec_seckey) + } + PRIVATE_KEY_PEM_LABEL if password.is_empty() => Self::from_pem(private_key), + PRIVATE_KEY_PEM_LABEL if !password.is_empty() => { + Err(SigstoreError::PrivateKeyDecryptError( + "Unencrypted private key but password provided".into(), + )) + } + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `EcdsaKeys` from a pkcs8 PEM-encoded private key. + /// The label of PEM should be [`PRIVATE_KEY_PEM_LABEL`] + pub fn from_pem(pem_data: &[u8]) -> Result { + let pem_data = std::str::from_utf8(pem_data)?; + let (label, document) = pkcs8::SecretDocument::from_pem(pem_data) + .map_err(|e| SigstoreError::PKCS8DerError(e.to_string()))?; + match label { + PRIVATE_KEY_PEM_LABEL => { + let ec_seckey = + SecretKey::::from_pkcs8_der(document.as_bytes()).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to ecdsa private key failed: {e}" + )) + })?; + Self::from_private_key(ec_seckey) + } + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `EcdsaKeys` from a pkcs8 asn.1 private key. + pub fn from_der(private_key: &[u8]) -> Result { + let ec_seckey = SecretKey::::from_pkcs8_der(private_key).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 der to ecdsa private key failed: {e}" + )) + })?; + Self::from_private_key(ec_seckey) + } + + /// Builds a `EcdsaKeys` from a private key. + fn from_private_key(ec_seckey: SecretKey) -> Result { + let public_key = ec_seckey.public_key(); + Ok(Self { + ec_seckey, + public_key, + }) + } + + /// Convert the [`EcdsaKeys`] into [`ECDSAKeys`]. + pub fn to_wrapped_ecdsa_keys(&self) -> Result { + let priv_key = self.private_key_to_der()?; + ECDSAKeys::from_der(&priv_key[..]) + } +} + +impl KeyPair for EcdsaKeys +where + C: Curve + AssociatedOid + CurveArithmetic + PrimeCurve, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Return the public key in PEM-encoded SPKI format. + fn public_key_to_pem(&self) -> Result { + self.public_key + .to_public_key_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the private key in pkcs8 PEM-encoded format. + fn private_key_to_pem(&self) -> Result> { + self.ec_seckey + .to_pkcs8_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the public key in asn.1 SPKI format. + fn public_key_to_der(&self) -> Result> { + Ok(self + .public_key + .to_public_key_der() + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string()))? + .to_vec()) + } + + /// Return the private key in asn.1 pkcs8 format. + fn private_key_to_der(&self) -> Result>> { + let pkcs8 = self + .ec_seckey + .to_pkcs8_der() + .map_err(|e| SigstoreError::PKCS8Error(e.to_string()))?; + Ok(pkcs8.to_bytes()) + } + + /// Return the encrypted private key in PEM-encoded format. + fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result> { + let der = self.private_key_to_der()?; + let pem = pem::Pem::new( + SIGSTORE_PRIVATE_KEY_PEM_LABEL, + kdf::encrypt(&der, password)?, + ); + let pem = pem::encode(&pem); + Ok(zeroize::Zeroizing::new(pem)) + } + + /// Derive the relative [`CosignVerificationKey`]. + fn to_verification_key(&self, signing_scheme: &SigningScheme) -> Result { + let pem = self.public_key_to_pem()?; + CosignVerificationKey::from_pem(pem.as_bytes(), signing_scheme) + } +} + +/// `EcdsaSigner` is used to generate a ECDSA signature. +/// The generic parameter `C` here can be chosen from +/// +/// * `p256::NistP256`: `P-256`, also known as `secp256r1` or `prime256v1`. +/// * `p384::NistP384`: `P-384`, also known as `secp384r1`. +/// +/// More elliptic curves, please refer to +/// . +/// +/// And the parameter `D` indicates the digest algorithm. +/// +/// For concrete digest algorithms, please refer to +/// . +#[allow(deprecated)] +#[derive(Clone, Debug)] +pub struct EcdsaSigner +where + C: PrimeCurve + CurveArithmetic + AssociatedOid, + Scalar: Invert>> + Reduce + SignPrimitive, + C::Uint: for<'a> From<&'a Scalar>, + SignatureSize: ArrayLength, + D: Digest + BlockSizeUser + FixedOutput> + FixedOutputReset, +{ + signing_key: SigningKey, + ecdsa_keys: EcdsaKeys, + _marker: PhantomData, +} + +#[allow(deprecated)] +impl EcdsaSigner +where + C: PrimeCurve + CurveArithmetic + AssociatedOid, + Scalar: Invert>> + Reduce + SignPrimitive, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + C::Uint: for<'a> From<&'a Scalar>, + SignatureSize: ArrayLength, + D: Digest + BlockSizeUser + FixedOutput> + FixedOutputReset, +{ + /// Create a new `EcdsaSigner` from the given `EcdsaKeys` and `SignatureDigestAlgorithm` + pub fn from_ecdsa_keys(ecdsa_keys: &EcdsaKeys) -> Result { + let signing_key = ecdsa::SigningKey::::from_pkcs8_der( + &ecdsa_keys.private_key_to_der()?[..], + ) + .map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 der to ecdsa private key failed: {e}" + )) + })?; + + Ok(Self { + signing_key, + ecdsa_keys: ecdsa_keys.clone(), + _marker: PhantomData, + }) + } + + /// Return the ref to the keypair inside the signer + pub fn ecdsa_keys(&self) -> &EcdsaKeys { + &self.ecdsa_keys + } +} + +#[allow(deprecated)] +impl Signer for EcdsaSigner +where + C: PrimeCurve + CurveArithmetic + AssociatedOid + DigestPrimitive, + Scalar: Invert>> + Reduce + SignPrimitive, + SigningKey: ecdsa::signature::Signer>, + C::Uint: for<'a> From<&'a Scalar>, + <::FieldBytesSize as Add>::Output: + Add, B0>, B0>, B1>>, + <<::FieldBytesSize as Add>::Output as Add< + UInt, B0>, B0>, B1>, + >>::Output: ArrayLength, + SignatureSize: ArrayLength, + <::Uint as ArrayEncoding>::ByteSize: ModulusSize, + ::FieldBytesSize: ModulusSize, + ::AffinePoint: ToEncodedPoint, + ::AffinePoint: FromEncodedPoint, + D: Digest + BlockSizeUser + FixedOutput> + FixedOutputReset, +{ + /// Sign the given message, and generate a signature. + /// The message will firstly be hashed with the given + /// digest algorithm `D`. And then, ECDSA signature + /// algorithm will sign the digest. + /// + /// The outcome digest will be encoded in `asn.1`. + fn sign(&self, msg: &[u8]) -> Result> { + let mut hasher = D::new(); + digest::Digest::update(&mut hasher, msg); + let (sig, _recovery_id) = self.signing_key.try_sign_digest(hasher)?; + + Ok(sig.to_der().to_bytes().to_vec()) + } + + /// Return the ref to the keypair inside the signer + fn key_pair(&self) -> &dyn KeyPair { + &self.ecdsa_keys + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use rstest::rstest; + + use crate::crypto::{ + Signature, SigningScheme, + signing_key::{KeyPair, Signer, tests::MESSAGE}, + verification_key::CosignVerificationKey, + }; + + use super::{EcdsaKeys, EcdsaSigner}; + + const PASSWORD: &[u8] = b"123"; + const EMPTY_PASSWORD: &[u8] = b""; + + /// This test will try to read an unencrypted ecdsa + /// private key file, which is generated by `sigstore`. + #[test] + fn ecdsa_from_unencrypted_pem() { + let content = fs::read("tests/data/keys/ecdsa_private.key") + .expect("read tests/data/keys/ecdsa_private.key failed."); + let key = EcdsaKeys::::from_pem(&content); + assert!( + key.is_ok(), + "can not create EcdsaKeys from unencrypted PEM file." + ); + } + + /// This test will try to read an encrypted ecdsa + /// private key file, which is generated by `sigstore`. + #[rstest] + #[case("tests/data/keys/ecdsa_encrypted_private.key", PASSWORD)] + #[case::empty_password( + "tests/data/keys/cosign_generated_encrypted_empty_private.key", + EMPTY_PASSWORD + )] + #[case::empty_password_unencrypted("tests/data/keys/ecdsa_private.key", EMPTY_PASSWORD)] + fn ecdsa_from_encrypted_pem(#[case] keypath: &str, #[case] password: &[u8]) { + let content = fs::read(keypath).expect("read key failed."); + let key = EcdsaKeys::::from_encrypted_pem(&content, password); + assert!( + key.is_ok(), + "can not create EcdsaKeys from encrypted PEM file" + ); + } + + /// This test will try to encrypt a ecdsa keypair and + /// return the pem-encoded contents. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(EMPTY_PASSWORD)] + fn ecdsa_to_encrypted_pem(#[case] password: &[u8]) { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let key = key.private_key_to_encrypted_pem(password); + assert!( + key.is_ok(), + "can not export private key in encrypted PEM format." + ); + } + + /// This test will ensure that an unencrypted + /// keypair will fail to read if a non-empty + /// password is given. + #[test] + fn ecdsa_error_unencrypted_pem_password() { + let content = fs::read("tests/data/keys/ecdsa_private.key").expect("read key failed."); + let key = EcdsaKeys::::from_encrypted_pem(&content, PASSWORD); + assert!( + key.is_err_and(|e| e + .to_string() + .contains("Unencrypted private key but password provided")), + "read unencrypted key with password" + ); + } + + /// This test will generate a EcdsaKeys, encode the private key + /// it into pem, and decode a new key from the generated pem-encoded + /// private key. + #[test] + fn ecdsa_to_and_from_pem() { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let key = key + .private_key_to_pem() + .expect("export private key to PEM format failed."); + let key = EcdsaKeys::::from_pem(key.as_bytes()); + assert!(key.is_ok(), "can not create EcdsaKeys from PEM string."); + } + + /// This test will generate a EcdsaKeys, encode the private key + /// it into pem, and decode a new key from the generated pem-encoded + /// private key. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(EMPTY_PASSWORD)] + fn ecdsa_to_and_from_encrypted_pem(#[case] password: &[u8]) { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let key = key + .private_key_to_encrypted_pem(password) + .expect("export private key to PEM format failed."); + let key = EcdsaKeys::::from_encrypted_pem(key.as_bytes(), password); + assert!(key.is_ok(), "can not create EcdsaKeys from PEM string."); + } + + /// This test will generate a EcdsaKeys, encode the private key + /// it into der, and decode a new key from the generated der-encoded + /// private key. + #[test] + fn ecdsa_to_and_from_der() { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let key = key + .private_key_to_der() + .expect("export private key to DER format failed."); + let key = EcdsaKeys::::from_der(&key); + assert!(key.is_ok(), "can not create EcdsaKeys from DER bytes.") + } + + /// This test will generate a ecdsa-P256 keypair. + /// And then use the verification key interface to instantial + /// a VerificationKey object. + #[test] + fn ecdsa_generate_public_key() { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + assert!( + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::default(),).is_ok() + ); + let pubkey = key + .public_key_to_der() + .expect("export private key to DER format failed."); + assert!( + CosignVerificationKey::from_der(&pubkey, &SigningScheme::default()).is_ok(), + "can not create CosignVerificationKey from der bytes." + ); + } + + /// This test will generate a ecdsa-P256 keypair. + /// And then derive a `CosignVerificationKey` from it. + #[test] + fn ecdsa_derive_verification_key() { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + assert!( + key.to_verification_key(&SigningScheme::default()).is_ok(), + "can not create CosignVerificationKey from EcdsaKeys via `to_verification_key`." + ); + } + + /// This test will do the following things: + /// * Generate a ecdsa-P256 keypair. + /// * Sign the MESSAGE with the private key and digest algorithm SHA256, + /// then generate a signature. + /// * Verify the signature using the public key. + #[test] + fn ecdsa_sign_and_verify() { + let key = + EcdsaKeys::::new().expect("create ecdsa keys with P256 curve failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + let signer = EcdsaSigner::<_, sha2::Sha256>::from_ecdsa_keys(&key) + .expect("create EcdsaSigner from ecdsa keys failed."); + + let sig = signer + .sign(MESSAGE.as_bytes()) + .expect("signing message failed."); + let verification_key = CosignVerificationKey::from_pem( + pubkey.as_bytes(), + &SigningScheme::ECDSA_P256_SHA256_ASN1, + ) + .expect("convert CosignVerificationKey from public key failed."); + let signature = Signature::Raw(&sig); + assert!( + verification_key + .verify_signature(signature, MESSAGE.as_bytes()) + .is_ok(), + "can not verify the signature." + ); + } +} diff --git a/vendor/src/crypto/signing_key/ecdsa/mod.rs b/vendor/src/crypto/signing_key/ecdsa/mod.rs new file mode 100644 index 0000000..b7680b0 --- /dev/null +++ b/vendor/src/crypto/signing_key/ecdsa/mod.rs @@ -0,0 +1,177 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # ECDSA Key Enums +//! +//! This is a wrapper for [`EcdsaKeys`] and [`EcdsaSigner`]. Because +//! both [`EcdsaKeys`] and [`EcdsaSigner`] are generic types, they +//! may let the user to manually include concrete underlying elliptic +//! curves like `p256`, `p384`, and concrete digest algorithm crates +//! like `sha2`. To avoid this, we use [`ECDSAKeys`] enum to wrap +//! the generic type [`EcdsaKeys`]. +//! +//! # EC Key Pair Operations +//! +//! This wrapper provides two underlying elliptic curves, s.t. +//! * `P256`: `P-256`, also known as `secp256r1` or `prime256v1`. +//! * `P384`: `P-384`, also known as `secp384r1`. +//! +//! We take `P256` for example to show the operaions: +//! ```rust +//! use sigstore::crypto::signing_key::ecdsa::{ECDSAKeys, EllipticCurve}; +//! use sigstore::crypto::Signature; +//! +//! // generate a new EC-P256 key pair +//! let ec_key_pair = ECDSAKeys::new(EllipticCurve::P256).unwrap(); +//! +//! // export the pem encoded public key. +//! // here `as_inner()` will return the reference of `KeyPair` trait object +//! // underlying this `ECDSAKeys` for key pair operaions. +//! let pubkey = ec_key_pair.as_inner().public_key_to_pem().unwrap(); +//! +//! // export the private key using sigstore encryption. +//! let privkey = ec_key_pair.as_inner().private_key_to_encrypted_pem(b"password").unwrap(); +//! +//! // also, we can import an [`ECDSAKeys`] of unknown elliptic curve at compile +//! // time using functions with the prefix `ECDSAKeys::from_`. These functions +//! // will try to decode the given ecdsa private key using all [`EllipticCurve`] +//! // enums (suppose the given private key is in PKCS8 format. The PKCS8 +//! // format will carry the key algorithm and its underlying elliptic curve +//! // identity). If one of them succeeds, return the enum. If all fail, return +//! // an error. For example: +//! // let ec_key_pair_import = ECDSAKeys::from_pem(PEM_CONTENT).unwrap(); +//! +//! // convert this EC key into an [`SigStoreSigner`] enum to sign some data. +//! // Although different EC key can combine with different digest algorithms to +//! // form a signing scheme, `P256` is recommended to work with `Sha256` and +//! // `P384` is recommended to work with `Sha384`. So here we do not include +//! // extra parameter `SignatureDigestAlgorithm` for `to_sigstore_signer()`. +//! let ec_signer = ec_key_pair.to_sigstore_signer().unwrap(); +//! +//! // test message to be signed +//! let message = b"some message"; +//! +//! // sign using +//! let signature_data = ec_signer.sign(message).unwrap(); +//! +//! // export the [`CosignVerificationKey`] from the [`SigStoreSigner`], which +//! // is used to verify the signature. +//! let verification_key = ec_signer.to_verification_key().unwrap(); +//! +//! // verify +//! assert!(verification_key.verify_signature(Signature::Raw(&signature_data),message).is_ok()); +/// ``` +use crate::errors::*; + +use self::ec::{EcdsaKeys, EcdsaSigner}; + +use super::{KeyPair, SigStoreSigner}; + +pub mod ec; + +pub enum ECDSAKeys { + P256(EcdsaKeys), + P384(EcdsaKeys), +} + +impl std::fmt::Display for ECDSAKeys { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ECDSAKeys::P256(_) => write!(f, "ECDSA P256"), + ECDSAKeys::P384(_) => write!(f, "ECDSA P384"), + } + } +} + +/// The types of supported elliptic curves: +/// * `P256`: `P-256`, also known as `secp256r1` or `prime256v1`. +/// * `P384`: `P-384`, also known as `secp384r1`. +pub enum EllipticCurve { + P256, + P384, +} + +/// This macro helps to reduce duplicated code. +macro_rules! iterate_on_curves { + ($func: ident ($($args:expr),*), $errorinfo: literal) => { + if let Ok(keys) = EcdsaKeys::::$func($($args,)*) { + Ok(ECDSAKeys::P256(keys)) + } else if let Ok(keys) = EcdsaKeys::::$func($($args,)*) { + Ok(ECDSAKeys::P384(keys)) + } else { + Err(SigstoreError::KeyParseError($errorinfo.to_string())) + } + } +} + +impl ECDSAKeys { + /// Create a new [`ECDSAKeys`] due to the given [`EllipticCurve`]. + pub fn new(curve: EllipticCurve) -> Result { + Ok(match curve { + EllipticCurve::P256 => ECDSAKeys::P256(EcdsaKeys::::new()?), + EllipticCurve::P384 => ECDSAKeys::P384(EcdsaKeys::::new()?), + }) + } + + /// Return the inner `KeyPair` of the enum. This function + /// is useful in the inner interface conversion. + pub fn as_inner(&self) -> &dyn KeyPair { + match self { + ECDSAKeys::P256(inner) => inner, + ECDSAKeys::P384(inner) => inner, + } + } + + /// Builds a `EcdsaKeys` from encrypted pkcs8 PEM-encoded private key. + /// The label should be [`super::COSIGN_PRIVATE_KEY_PEM_LABEL`] or + /// [`super::SIGSTORE_PRIVATE_KEY_PEM_LABEL`]. + pub fn from_encrypted_pem(private_key: &[u8], password: &[u8]) -> Result { + iterate_on_curves!( + from_encrypted_pem(private_key, password), + "Ecdsa keys from encrypted PEM private key" + ) + } + + /// Builds a `EcdsaKeys` from a pkcs8 PEM-encoded private key. + /// The label of PEM should be [`super::PRIVATE_KEY_PEM_LABEL`] + pub fn from_pem(pem_data: &[u8]) -> Result { + iterate_on_curves!(from_pem(pem_data), "Ecdsa keys from PEM private key") + } + + /// Builds a `EcdsaKeys` from a pkcs8 asn.1 private key. + pub fn from_der(private_key: &[u8]) -> Result { + iterate_on_curves!(from_der(private_key), "Ecdsa keys from DER private key") + } + + /// `to_sigstore_signer` will create the [`SigStoreSigner`] using + /// this Ecdsa private key. This function does not receive any parameter + /// to indicate the digest algorthm, because the common signing schemes + /// for ecdsa-p256 is `ECDSA_P256_SHA256`, and for ecdsa-p384 is + /// `ECDSA_P384_SHA384`. + pub fn to_sigstore_signer(&self) -> Result { + Ok(match self { + ECDSAKeys::P256(inner) => { + SigStoreSigner::ECDSA_P256_SHA256_ASN1( + EcdsaSigner::<_, sha2::Sha256>::from_ecdsa_keys(inner)?, + ) + } + ECDSAKeys::P384(inner) => { + SigStoreSigner::ECDSA_P384_SHA384_ASN1( + EcdsaSigner::<_, sha2::Sha384>::from_ecdsa_keys(inner)?, + ) + } + }) + } +} diff --git a/vendor/src/crypto/signing_key/ed25519.rs b/vendor/src/crypto/signing_key/ed25519.rs new file mode 100644 index 0000000..3f07185 --- /dev/null +++ b/vendor/src/crypto/signing_key/ed25519.rs @@ -0,0 +1,458 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Ed25519 Keys +//! +//! This is a wrapper for Rust Crypto. There two main types in this mod: +//! * [`Ed25519Keys`]: provides basic key pair operaions +//! * [`Ed25519Signer`]: provides signing operaion +//! +//! The `signing_key` will wrap [`Ed25519Keys`] into [`super::SigStoreKeyPair`] enum, +//! and [`Ed25519Signer`] into [`SigStoreSigner`] enum. +//! +//! # Ed25519 Key Operaions +//! +//! We give an example for the mod +//! ```rust +//! use sigstore::crypto::signing_key::ed25519::Ed25519Keys; +//! use sigstore::crypto::{signing_key::KeyPair, Signature}; +//! +//! // generate a new Ed25519 key pair +//! let ed25519_key_pair = Ed25519Keys::new().unwrap(); +//! +//! // export the pem encoded public key. +//! let pubkey = ed25519_key_pair.public_key_to_pem().unwrap(); +//! +//! // export the private key using sigstore encryption. +//! let privkey = ed25519_key_pair.private_key_to_encrypted_pem(b"password").unwrap(); +//! +//! // also, we can import a Ed25519 using functions with the prefix +//! // `Ed25519Keys::from_`. These functions will treat the given +//! // data as Ed25519 private key in PKCS8 format. For example: +//! // let ed25519_key_pair_import = Ed25519Keys::from_pem(PEM_CONTENT).unwrap(); +//! +//! // convert this Ed25519 key into an [`super::SigStoreSigner`] enum to sign some data. +//! let ed25519_signer = ed25519_key_pair.to_sigstore_signer().unwrap(); +//! +//! // test message to be signed +//! let message = b"some message"; +//! +//! // sign using +//! let signature = ed25519_signer.sign(message).unwrap(); +//! +//! // export the [`CosignVerificationKey`] from the [`super::SigStoreSigner`], which +//! // is used to verify the signature. +//! let verification_key = ed25519_signer.to_verification_key().unwrap(); +//! +//! // verify +//! assert!(verification_key.verify_signature(Signature::Raw(&signature),message).is_ok()); +//! ``` + +use ed25519::pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey}; + +use ed25519::KeypairBytes; +use ed25519_dalek::{Signer as _, SigningKey}; + +use crate::{ + crypto::{SigningScheme, verification_key::CosignVerificationKey}, + errors::*, +}; + +use super::{ + COSIGN_PRIVATE_KEY_PEM_LABEL, KeyPair, PRIVATE_KEY_PEM_LABEL, SIGSTORE_PRIVATE_KEY_PEM_LABEL, + SigStoreSigner, Signer, kdf, +}; + +#[derive(Debug, Clone)] +pub struct Ed25519Keys { + signing_key: ed25519_dalek::SigningKey, + verifying_key: ed25519_dalek::VerifyingKey, +} + +impl Ed25519Keys { + /// Create a new `Ed25519Keys` Object. + /// The private key will be randomly + /// generated. + pub fn new() -> Result { + let mut csprng = rand::rngs::OsRng {}; + let signing_key = SigningKey::generate(&mut csprng); + let verifying_key = signing_key.verifying_key(); + Ok(Self { + signing_key, + verifying_key, + }) + } + + /// Create a new `Ed25519Keys` Object from given `Ed25519Keys` Object. + pub fn from_ed25519key(key: &Ed25519Keys) -> Result { + let priv_key = key.private_key_to_der()?; + Ed25519Keys::from_der(&priv_key[..]) + } + + /// Builds a `Ed25519Keys` from encrypted pkcs8 PEM-encoded private key. + /// The label should be [`COSIGN_PRIVATE_KEY_PEM_LABEL`] or + /// [`SIGSTORE_PRIVATE_KEY_PEM_LABEL`]. + pub fn from_encrypted_pem(encrypted_pem: &[u8], password: &[u8]) -> Result { + let key = pem::parse(encrypted_pem)?; + match key.tag() { + COSIGN_PRIVATE_KEY_PEM_LABEL | SIGSTORE_PRIVATE_KEY_PEM_LABEL => { + let der = kdf::decrypt(key.contents(), password)?; + let pkcs8 = + ed25519_dalek::pkcs8::PrivateKeyInfo::try_from(&der[..]).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {e}")) + })?; + let key_pair_bytes = KeypairBytes::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to ed25519 private key failed: {e}" + )) + })?; + Self::from_key_pair_bytes(key_pair_bytes) + } + PRIVATE_KEY_PEM_LABEL if password.is_empty() => Self::from_pem(encrypted_pem), + PRIVATE_KEY_PEM_LABEL if !password.is_empty() => { + Err(SigstoreError::PrivateKeyDecryptError( + "Unencrypted private key but password provided".into(), + )) + } + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `Ed25519Keys` from a pkcs8 PEM-encoded private key. + /// The label of PEM should be [`PRIVATE_KEY_PEM_LABEL`] + pub fn from_pem(pem: &[u8]) -> Result { + let pem = std::str::from_utf8(pem)?; + let (label, document) = pkcs8::SecretDocument::from_pem(pem) + .map_err(|e| SigstoreError::PKCS8DerError(e.to_string()))?; + + match label { + PRIVATE_KEY_PEM_LABEL => { + let pkcs8 = ed25519_dalek::pkcs8::PrivateKeyInfo::try_from(document.as_bytes()) + .map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {e}")) + })?; + let key_pair_bytes = KeypairBytes::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to ed25519 private key failed: {e}" + )) + })?; + Self::from_key_pair_bytes(key_pair_bytes) + } + + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `Ed25519Keys` from a pkcs8 asn.1 private key. + pub fn from_der(der_bytes: &[u8]) -> Result { + let key_pair_bytes = KeypairBytes::from_pkcs8_der(der_bytes).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 der to ed25519 private key failed: {e}" + )) + })?; + Self::from_key_pair_bytes(key_pair_bytes) + } + + /// Builds a `Ed25519Keys` from a `KeypairBytes`. + fn from_key_pair_bytes(key_pair_bytes: KeypairBytes) -> Result { + let signing_key = ed25519_dalek::SigningKey::from_keypair_bytes( + &key_pair_bytes.to_bytes().ok_or_else(|| { + SigstoreError::PKCS8SpkiError("No public key info in given key_pair_bytes.".into()) + })?, + )?; + let verifying_key = signing_key.verifying_key(); + + Ok(Self { + signing_key, + verifying_key, + }) + } + + /// `to_sigstore_signer` will create the [`SigStoreSigner`] using + /// this ed25519 private key. + pub fn to_sigstore_signer(&self) -> Result { + Ok(SigStoreSigner::ED25519(Ed25519Signer::from_ed25519_keys( + self, + )?)) + } +} + +impl KeyPair for Ed25519Keys { + /// Return the public key in PEM-encoded SPKI format. + fn public_key_to_pem(&self) -> Result { + self.verifying_key + .to_public_key_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the public key in asn.1 SPKI format. + fn public_key_to_der(&self) -> Result> { + Ok(self + .verifying_key + .to_public_key_der() + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string()))? + .to_vec()) + } + + /// Return the encrypted asn.1 pkcs8 private key. + fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result> { + let der = self.private_key_to_der()?; + let pem = pem::Pem::new( + SIGSTORE_PRIVATE_KEY_PEM_LABEL, + kdf::encrypt(&der, password)?, + ); + let pem = pem::encode(&pem); + Ok(zeroize::Zeroizing::new(pem)) + } + + /// Return the private key in pkcs8 PEM-encoded format. + fn private_key_to_pem(&self) -> Result> { + self.signing_key + .to_pkcs8_der() + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string()))? + .to_pem(PRIVATE_KEY_PEM_LABEL, pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the private key in asn.1 pkcs8 format. + fn private_key_to_der(&self) -> Result>> { + let pkcs8 = self + .signing_key + .to_pkcs8_der() + .map_err(|e| SigstoreError::PKCS8Error(e.to_string()))?; + Ok(pkcs8.to_bytes()) + } + + /// Derive the relative [`CosignVerificationKey`]. + fn to_verification_key( + &self, + _signature_digest_algorithm: &SigningScheme, + ) -> Result { + let der = self.public_key_to_der()?; + let res = CosignVerificationKey::from_der(&der, &SigningScheme::ED25519)?; + Ok(res) + } +} + +#[derive(Debug)] +pub struct Ed25519Signer { + key_pair: Ed25519Keys, +} + +impl Ed25519Signer { + pub fn from_ed25519_keys(ed25519_keys: &Ed25519Keys) -> Result { + Ok(Self { + key_pair: ed25519_keys.clone(), + }) + } + + /// Return the ref to the keypair inside the signer + pub fn ed25519_keys(&self) -> &Ed25519Keys { + &self.key_pair + } +} + +impl Signer for Ed25519Signer { + /// Return the ref to the keypair inside the signer + fn key_pair(&self) -> &dyn KeyPair { + &self.key_pair + } + + /// Sign the given message using Ed25519 + fn sign(&self, msg: &[u8]) -> Result> { + let signature = self.key_pair.signing_key.try_sign(msg)?; + Ok(signature.to_vec()) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use rstest::rstest; + + use crate::crypto::{ + Signature, SigningScheme, + signing_key::{KeyPair, Signer, tests::MESSAGE}, + verification_key::CosignVerificationKey, + }; + + use super::{Ed25519Keys, Ed25519Signer}; + + const PASSWORD: &[u8] = b"123"; + const EMPTY_PASSWORD: &[u8] = b""; + + /// This test will try to read an unencrypted ed25519 + /// private key file, which is generated by `sigstore`. + #[test] + fn ed25519_from_unencrypted_pem() { + let content = fs::read("tests/data/keys/ed25519_private.key") + .expect("read tests/data/keys/ed25519_private.key failed."); + let key = Ed25519Keys::from_pem(&content); + assert!( + key.is_ok(), + "can not create Ed25519Keys from unencrypted PEM file." + ); + } + + /// This test will try to read an encrypted ed25519 + /// private key file, which is generated by `sigstore`. + #[rstest] + #[case("tests/data/keys/ed25519_encrypted_private.key", PASSWORD)] + #[case::empty_password("tests/data/keys/ed25519_private.key", EMPTY_PASSWORD)] + fn ed25519_from_encrypted_pem(#[case] keypath: &str, #[case] password: &[u8]) { + let content = fs::read(keypath).expect("read key failed."); + let key = Ed25519Keys::from_encrypted_pem(&content, password); + assert!( + key.is_ok(), + "can not create Ed25519Keys from encrypted PEM file" + ); + } + + /// This test will try to encrypt a ed25519 keypair and + /// return the pem-encoded contents. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(EMPTY_PASSWORD)] + fn ed25519_to_encrypted_pem(#[case] password: &[u8]) { + let key = Ed25519Keys::new().expect("create Ed25519 keys failed."); + let key = key.private_key_to_encrypted_pem(password); + assert!( + key.is_ok(), + "can not export private key in encrypted PEM format." + ); + } + + /// This test will ensure that an unencrypted + /// keypair will fail to read if a non-empty + /// password is given. + #[test] + fn ed25519_error_unencrypted_pem_password() { + let content = fs::read("tests/data/keys/ed25519_private.key").expect("read key failed."); + let key = Ed25519Keys::from_encrypted_pem(&content, PASSWORD); + assert!( + key.is_err_and(|e| e + .to_string() + .contains("Unencrypted private key but password provided")), + "read unencrypted key with password" + ); + } + + /// This test will generate a Ed25519Keys, encode the private key + /// into pem, and decode a new key from the generated pem-encoded + /// private key. + #[test] + fn ed25519_to_and_from_pem() { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + let key = key + .private_key_to_pem() + .expect("export private key to PEM format failed."); + let key = Ed25519Keys::from_pem(key.as_bytes()); + assert!(key.is_ok(), "can not create Ed25519Keys from PEM string."); + } + + /// This test will generate a Ed25519Keys, encode the private key + /// into pem, and decode a new key from the generated pem-encoded + /// private key. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(EMPTY_PASSWORD)] + fn ed25519_to_and_from_encrypted_pem(#[case] password: &[u8]) { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + let key = key + .private_key_to_encrypted_pem(password) + .expect("export private key to PEM format failed."); + let key = Ed25519Keys::from_encrypted_pem(key.as_bytes(), password); + assert!(key.is_ok(), "can not create Ed25519Keys from PEM string."); + } + + /// This test will generate a Ed25519Keys, encode the private key + /// it into der, and decode a new key from the generated der-encoded + /// private key. + #[test] + fn ed25519_to_and_from_der() { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + let key = key + .private_key_to_der() + .expect("export private key to DER format failed."); + let key = Ed25519Keys::from_der(&key); + assert!(key.is_ok(), "can not create Ed25519Keys from DER bytes.") + } + + /// This test will generate a ed25519 keypair. + /// And then use the verification key interface to instantial + /// a VerificationKey object. + #[test] + fn ed25519_generate_public_key() { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + assert!( + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::ED25519).is_ok(), + "can not convert public key in PEM format into CosignVerificationKey.", + ); + let pubkey = key + .public_key_to_der() + .expect("export private key to DER format failed."); + assert!( + CosignVerificationKey::from_der(&pubkey, &SigningScheme::ED25519).is_ok(), + "can not create CosignVerificationKey from der bytes." + ); + } + + /// This test will generate a ed25519 keypair. + /// And then derive a `CosignVerificationKey` from it. + #[test] + fn ecdsa_derive_verification_key() { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + assert!( + key.to_verification_key(&SigningScheme::ED25519).is_ok(), + "can not create CosignVerificationKey from EcdsaKeys via `to_verification_key`.", + ); + } + + /// This test will do the following things: + /// * Generate a ed25519 keypair. + /// * Sign the MESSAGE with the private key then generate a signature. + /// * Verify the signature using the public key. + #[test] + fn ed25519_sign_and_verify() { + let key = Ed25519Keys::new().expect("create ed25519 keys failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + let signer = Ed25519Signer::from_ed25519_keys(&key) + .expect("create Ed25519Signer from ed25519 keys failed."); + + let sig = signer + .sign(MESSAGE.as_bytes()) + .expect("signing message failed."); + let verification_key = + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::ED25519) + .expect("convert CosignVerificationKey from public key failed."); + let signature = Signature::Raw(&sig); + assert!( + verification_key + .verify_signature(signature, MESSAGE.as_bytes()) + .is_ok(), + "can not verify the signature.", + ); + } +} diff --git a/vendor/src/crypto/signing_key/kdf.rs b/vendor/src/crypto/signing_key/kdf.rs new file mode 100644 index 0000000..3a4acc4 --- /dev/null +++ b/vendor/src/crypto/signing_key/kdf.rs @@ -0,0 +1,295 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Key Derivation Function for Sigstore +//! +//! This is the Rust version of KDF used in Sigstore. +//! Please refer to +//! for golang version. + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use crypto_secretbox::aead::{AeadMut, KeyInit}; +use rand::Rng; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::errors::*; + +/// Salt bit length used in scrypt algorithm. +pub const SALT_SIZE: u32 = 32; + +/// KDF name for scrypt. +pub const NAME_SCRYPT: &str = "scrypt"; + +/// Scrypt algorithm parameter log2(n) +pub const SCRYPT_N_LOW: u32 = 32768; + +pub const SCRYPT_N_HIGH: u32 = 65536; + +/// Scrypt algorithm parameter r +pub const SCRYPT_R: u32 = 8; + +/// Scrypt algorithm parameter p +pub const SCRYPT_P: u32 = 1; + +/// Secret box name +pub const NAME_SECRET_BOX: &str = "nacl/secretbox"; + +/// Key length for secretbox +pub const BOX_KEY_SIZE: usize = 32; + +/// Nonce length for secretbox +pub const BOX_NONCE_SIZE: u32 = 24; + +/// Parameters for scrypt algorithm. +#[derive(Serialize, Deserialize)] +pub struct ScryptParams { + #[serde(rename = "N")] + n: u32, + r: u32, + p: u32, +} + +/// Key Derivation Function. +/// Using scrypt algorithm from a password. +#[derive(Serialize, Deserialize)] +struct ScryptKDF { + name: String, + params: ScryptParams, + #[serde(serialize_with = "to_base64", deserialize_with = "from_base64")] + salt: Vec, +} + +/// Help to serialize `salt` to base64 +fn to_base64(v: &[u8], serializer: S) -> std::result::Result +where + S: Serializer, +{ + serializer.serialize_str(&BASE64_STD_ENGINE.encode(v)) +} + +/// Help to deserialize `salt` from base64 +fn from_base64<'de, D>(deserializer: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = ::deserialize(deserializer)?; + BASE64_STD_ENGINE + .decode(s) + .map_err(serde::de::Error::custom) +} + +impl Default for ScryptKDF { + /// Create a new Key derivation function object + fn default() -> Self { + let salt = generate_random(SALT_SIZE); + Self { + name: NAME_SCRYPT.into(), + params: ScryptParams { + n: SCRYPT_N_LOW, + r: SCRYPT_R, + p: SCRYPT_P, + }, + salt, + } + } +} + +impl ScryptKDF { + /// Derivate a new key from the given password + fn key(&self, password: &[u8]) -> Result> { + let log_n = (self.params.n as f64).log2() as u8; + let params = scrypt::Params::new( + log_n, + self.params.r, + self.params.p, + scrypt::Params::RECOMMENDED_LEN, + )?; + let mut res = vec![0; BOX_KEY_SIZE]; + scrypt::scrypt(password, &self.salt, ¶ms, &mut res)?; + Ok(res) + } + + /// Check whether the given params is as the default, + /// to avoid a DoS attack. + fn check_params(&self) -> Result<()> { + match (self.params.n == SCRYPT_N_LOW || self.params.n == SCRYPT_N_HIGH) + && self.params.r == SCRYPT_R + && self.params.p == SCRYPT_P + { + true => Ok(()), + false => Err(SigstoreError::PrivateKeyDecryptError( + "Unexpected kdf parameters".into(), + )), + } + } +} + +/// Secretbox is used to seal the given secret +#[derive(Serialize, Deserialize)] +struct SecretBoxCipher { + name: String, + #[serde(serialize_with = "to_base64", deserialize_with = "from_base64")] + nonce: Vec, + #[serde(skip)] + encrypted: bool, +} + +impl Default for SecretBoxCipher { + fn default() -> Self { + let nonce = generate_random(BOX_NONCE_SIZE); + Self { + name: NAME_SECRET_BOX.into(), + nonce, + encrypted: false, + } + } +} + +impl SecretBoxCipher { + /// Seal the plaintext using the key and nonce. + fn encrypt(&mut self, plaintext: &[u8], key: &[u8]) -> Result> { + if self.encrypted { + return Err(SigstoreError::PrivateKeyEncryptError( + "Encrypt must only be called once for each cipher instance".into(), + )); + } + self.encrypted = true; + + #[allow(deprecated)] + let nonce = crypto_secretbox::Nonce::from_slice(&self.nonce); + #[allow(deprecated)] + let key = crypto_secretbox::Key::from_slice(key); + + let mut cipher = crypto_secretbox::XSalsa20Poly1305::new(key); + cipher + .encrypt(nonce, plaintext) + .map_err(|e| SigstoreError::PrivateKeyEncryptError(e.to_string())) + } + + /// Unseal the ciphertext using the key + fn decrypt(&self, ciphertext: &[u8], key: &[u8]) -> Result> { + #[allow(deprecated)] + let nonce = crypto_secretbox::Nonce::from_slice(&self.nonce); + #[allow(deprecated)] + let key = crypto_secretbox::Key::from_slice(key); + + let mut cipher = crypto_secretbox::XSalsa20Poly1305::new(key); + cipher + .decrypt(nonce, ciphertext) + .map_err(|e| SigstoreError::PrivateKeyEncryptError(e.to_string())) + } +} + +/// `Data` is all content of a encrypted private key. +#[derive(Serialize, Deserialize)] +struct Data { + kdf: ScryptKDF, + cipher: SecretBoxCipher, + #[serde( + rename = "ciphertext", + serialize_with = "to_base64", + deserialize_with = "from_base64" + )] + cipher_text: Vec, +} + +/// Generate a random Vec of given length. +fn generate_random(len: u32) -> Vec { + let mut res = Vec::new(); + for _ in 0..len { + res.push(rand::thread_rng().r#gen()); + } + res +} + +/// Encrypt the given plaintext using a derived key from +/// password. In sigstore, it is used to encrypt the +/// private key. +pub fn encrypt(plaintext: &[u8], password: &[u8]) -> Result> { + let kdf = ScryptKDF::default(); + + let key = kdf.key(password)?; + let mut box_cipher = SecretBoxCipher::default(); + let cipher_text = box_cipher.encrypt(plaintext, &key)?; + + let data = Data { + kdf, + cipher: box_cipher, + cipher_text, + }; + + let res = serde_json::to_vec(&data)?; + Ok(res) +} + +/// Encrypt the given plaintext using a derived key from +/// password. In sigstore, it is used to decrypt the +/// private key. +pub fn decrypt(ciphertext: &[u8], password: &[u8]) -> Result> { + let data: Data = serde_json::from_slice(ciphertext)?; + if data.cipher.name != NAME_SECRET_BOX { + return Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unknown cipher name: {}", + data.cipher.name + ))); + } + + if data.kdf.name != NAME_SCRYPT { + return Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unknown kdf name: {}", + data.kdf.name + ))); + } + + data.kdf.check_params()?; + + let key = data.kdf.key(password)?; + data.cipher.decrypt(&data.cipher_text, &key) +} + +#[cfg(test)] +mod tests { + use assert_json_diff::assert_json_eq; + use serde_json::json; + + use crate::crypto::signing_key::kdf::Data; + + /// This test will firstly deserialize the given KDF + /// payload generated from cosign in golang, and then + /// serialize the generated object into a new string. + #[test] + fn serde_kdf() { + let input_json = json!({ + "kdf": { + "name": "scrypt", + "params": { + "N": 32768u32, + "r": 8u32, + "p": 1u32 + }, + "salt": "+QseLb/O/0j2dG201MALNSv2xLcclv6UvpXZVvXGT0k=", + }, + "cipher": { + "name": "nacl/secretbox", + "nonce": "B5zH5d9AwoPkgaPAwIgpnft2BO6HZM/j", + }, + "ciphertext": "RQPqIJtoWjlVC49xXNG+zfkGrJF3DWIhdRArI0XeTjGx04QzjAAeybGgW4T9JWKuYYe49NIZCEOD2G8cisMJ9KXHPaxT6Q/lLa8XrkavRrzkaD3xj8tc2AAntvUz8OACtH3zmimeFLr+EtecDb/UNjNFCtW1SlIh6DsfTsbBL67uQqLrFQMW8r70SvsZLkXV8mFhMsKyVryWlQ==", + }); + let data: Data = + serde_json::from_value(input_json.clone()).expect("Cannot deserialize json Data"); + let actual_json = serde_json::to_value(data).expect("Cannot serialize Data back to JSON"); + assert_json_eq!(input_json, actual_json); + } +} diff --git a/vendor/src/crypto/signing_key/mod.rs b/vendor/src/crypto/signing_key/mod.rs new file mode 100644 index 0000000..a28f512 --- /dev/null +++ b/vendor/src/crypto/signing_key/mod.rs @@ -0,0 +1,445 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Keys Interface +//! +//! This mod includes asymmetric key pair generation, exporting/importing, +//! signing and verification key derivation. All the above features are +//! given by two enums: +//! * [`SigStoreKeyPair`]: an abstraction for asymmetric encryption key pairs. +//! * [`SigStoreSigner`]: an abstraction for digital signing algorithms. +//! +//! The [`SigStoreKeyPair`] now includes the key types of the following algorithms: +//! * [`SigStoreKeyPair::RSA`]: RSA key pair +//! * [`SigStoreKeyPair::ECDSA`]: Elliptic curve key pair +//! * [`SigStoreKeyPair::ED25519`]: Edwards curve-25519 key pair +//! +//! The [`SigStoreSigner`] now includes the following signing schemes: +//! * [`SigStoreSigner::RSA_PSS_SHA256`]: RSA signatures using PSS padding and SHA-256. +//! * [`SigStoreSigner::RSA_PSS_SHA384`]: RSA signatures using PSS padding and SHA-384. +//! * [`SigStoreSigner::RSA_PSS_SHA512`]: RSA signatures using PSS padding and SHA-512. +//! * [`SigStoreSigner::RSA_PKCS1_SHA256`]: RSA signatures using PKCS#1v1.5 padding and SHA-256. +//! * [`SigStoreSigner::RSA_PKCS1_SHA384`]: RSA signatures using PKCS#1v1.5 padding and SHA-384. +//! * [`SigStoreSigner::RSA_PKCS1_SHA512`]: RSA signatures using PKCS#1v1.5 padding and SHA-512. +//! * [`SigStoreSigner::ECDSA_P256_SHA256_ASN1`]: ASN.1 DER-encoded ECDSA +//! signatures using the P-256 curve and SHA-256. +//! * [`SigStoreSigner::ECDSA_P384_SHA384_ASN1`]: ASN.1 DER-encoded ECDSA +//! signatures using the P-384 curve and SHA-384. +//! * [`SigStoreSigner::ED25519`]: ECDSA signature using SHA2-512 +//! as the digest function and curve edwards25519. +//! +//! # Simple Usages +//! +//! ```rust +//! use sigstore::crypto::signing_key::SigStoreSigner; +//! use sigstore::crypto::SigningScheme; +//! use sigstore::crypto::Signature; +//! +//! let test_data = b"test message"; +//! // generate a key pair for ECDSA_P256_SHA256_ASN1 +//! let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer().unwrap(); +//! +//! // signing some message and get the message +//! let sig = signer.sign(test_data).unwrap(); +//! +//! // get the public key to verify +//! let verification_key = signer.to_verification_key().unwrap(); +//! +//! // do verification +//! let res = verification_key.verify_signature( +//! Signature::Raw(&sig), +//! test_data, +//! ); +//! +//! assert!(res.is_ok()); +//! ``` +//! +//! More use cases please refer to <`https://github.com/sigstore/sigstore-rs/tree/main/examples/key_interface`> + +use elliptic_curve::zeroize::Zeroizing; + +use crate::errors::*; + +use self::{ + ecdsa::{ECDSAKeys, ec::EcdsaSigner}, + ed25519::{Ed25519Keys, Ed25519Signer}, + rsa::{DigestAlgorithm, PaddingScheme, RSASigner, keypair::RSAKeys}, +}; + +use super::{SigningScheme, verification_key::CosignVerificationKey}; + +pub mod ecdsa; +pub mod ed25519; +pub mod kdf; +pub mod rsa; + +/// The label for pem of cosign generated encrypted private keys. +pub const COSIGN_PRIVATE_KEY_PEM_LABEL: &str = "ENCRYPTED COSIGN PRIVATE KEY"; + +/// The label for pem of public keys. +pub const PUBLIC_KEY_PEM_LABEL: &str = "PUBLIC KEY"; + +/// The label for pem of sigstore generated encrypted private keys. +pub const SIGSTORE_PRIVATE_KEY_PEM_LABEL: &str = "ENCRYPTED SIGSTORE PRIVATE KEY"; + +/// The label for pem of private keys. +pub const PRIVATE_KEY_PEM_LABEL: &str = "PRIVATE KEY"; + +/// The label for pem of RSA private keys. +pub const RSA_PRIVATE_KEY_PEM_LABEL: &str = "RSA PRIVATE KEY"; + +/// Every signing scheme must implement this interface. +/// All private export methods using the wrapper `Zeroizing`. +/// It will tell the compiler when the +/// result der object is dropped, the relative memory will +/// be flushed to zero to avoid leaving the private key in +/// the ram. +pub trait KeyPair { + /// `public_key_to_pem` will export the PEM-encoded public key. + fn public_key_to_pem(&self) -> Result; + + /// `public_key_to_der` will export the asn.1 PKIX public key. + fn public_key_to_der(&self) -> Result>; + + /// `private_key_to_encrypted_pem` will export the encrypted asn.1 pkcs8 private key. + /// This encryption follows the go-lang version in + /// using nacl secretbox. + fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result>; + + /// `private_key_to_pem` will export the PEM-encoded pkcs8 private key. + fn private_key_to_pem(&self) -> Result>; + + /// `private_key_to_der` will export the asn.1 pkcs8 private key. + fn private_key_to_der(&self) -> Result>>; + + /// `to_verification_key` will derive the `CosignVerificationKey` from + /// the public key. + fn to_verification_key( + &self, + signature_digest_algorithm: &SigningScheme, + ) -> Result; +} + +/// Wrapper for different kinds of keys. +pub enum SigStoreKeyPair { + ECDSA(ECDSAKeys), + ED25519(Ed25519Keys), + RSA(RSAKeys), +} + +impl std::fmt::Display for SigStoreKeyPair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SigStoreKeyPair::ECDSA(_) => write!(f, "EC Key"), + SigStoreKeyPair::ED25519(_) => write!(f, "Ed25519 Key"), + SigStoreKeyPair::RSA(_) => write!(f, "RSA Key"), + } + } +} + +/// This macro helps to reduce duplicated code. +macro_rules! sigstore_keypair_from { + ($func: ident ($($args:expr),*)) => { + if let Ok(keys) = ECDSAKeys::$func($($args,)*) { + Ok(SigStoreKeyPair::ECDSA(keys)) + } else if let Ok(keys) = Ed25519Keys::$func($($args,)*) { + Ok(SigStoreKeyPair::ED25519(keys)) + } else if let Ok(keys) = RSAKeys::$func($($args,)*) { + Ok(SigStoreKeyPair::RSA(keys)) + } else { + Err(SigstoreError::KeyParseError("Unsupported key type".to_string())) + } + } +} + +/// This macro helps to reduce duplicated code. +macro_rules! sigstore_keypair_code { + ($func: ident ($($args:expr),*), $obj:ident) => { + match $obj { + SigStoreKeyPair::ECDSA(keys) => keys.as_inner().$func($($args,)*), + SigStoreKeyPair::ED25519(keys) => keys.$func($($args,)*), + SigStoreKeyPair::RSA(keys) => keys.$func($($args,)*), + } + } +} + +impl SigStoreKeyPair { + /// Builds a `SigStoreKeyPair` from pkcs8 PEM-encoded private key. + pub fn from_pem(pem_data: &[u8]) -> Result { + sigstore_keypair_from!(from_pem(pem_data)) + } + + /// Builds a `SigStoreKeyPair` from pkcs8 DER-encoded private key. + pub fn from_der(private_key: &[u8]) -> Result { + sigstore_keypair_from!(from_der(private_key)) + } + + /// Builds a `SigStoreKeyPair` from encrypted pkcs8 PEM-encoded private key. + pub fn from_encrypted_pem(pem_data: &[u8], password: &[u8]) -> Result { + sigstore_keypair_from!(from_encrypted_pem(pem_data, password)) + } + + /// `public_key_to_pem` will export the PEM-encoded public key. + pub fn public_key_to_pem(&self) -> Result { + sigstore_keypair_code!(public_key_to_pem(), self) + } + + /// `public_key_to_der` will export the asn.1 PKIX public key. + pub fn public_key_to_der(&self) -> Result> { + sigstore_keypair_code!(public_key_to_der(), self) + } + + /// `private_key_to_encrypted_pem` will export the encrypted asn.1 pkcs8 private key. + /// This encryption follows the go-lang version in + /// using nacl secretbox. + pub fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result> { + sigstore_keypair_code!(private_key_to_encrypted_pem(password), self) + } + + /// `private_key_to_pem` will export the PEM-encoded pkcs8 private key. + pub fn private_key_to_pem(&self) -> Result> { + sigstore_keypair_code!(private_key_to_pem(), self) + } + + /// `private_key_to_der` will export the asn.1 pkcs8 private key. + pub fn private_key_to_der(&self) -> Result>> { + sigstore_keypair_code!(private_key_to_der(), self) + } + + /// `to_verification_key` will derive the `CosignVerificationKey` from + /// the public key. + pub fn to_verification_key( + &self, + signing_scheme: &SigningScheme, + ) -> Result { + sigstore_keypair_code!(to_verification_key(signing_scheme), self) + } + + /// Convert this KeyPair into a [`SigStoreSigner`] due to the given + /// signing scheme. If the key type does not match the given + /// signing scheme, an error will occur. + pub fn to_sigstore_signer(&self, signing_scheme: &SigningScheme) -> Result { + match self { + SigStoreKeyPair::ECDSA(keys) => match signing_scheme { + SigningScheme::ECDSA_P256_SHA256_ASN1 => match keys { + ECDSAKeys::P256(key) => { + let signer = EcdsaSigner::from_ecdsa_keys(key)?; + Ok(SigStoreSigner::ECDSA_P256_SHA256_ASN1(signer)) + } + ECDSAKeys::P384(_) => Err(SigstoreError::UnmatchedKeyAndSigningScheme { + key_typ: keys.to_string(), + scheme: signing_scheme.to_string(), + }), + }, + SigningScheme::ECDSA_P384_SHA384_ASN1 => match keys { + ECDSAKeys::P384(key) => { + let signer = EcdsaSigner::from_ecdsa_keys(key)?; + Ok(SigStoreSigner::ECDSA_P384_SHA384_ASN1(signer)) + } + ECDSAKeys::P256(_) => Err(SigstoreError::UnmatchedKeyAndSigningScheme { + key_typ: keys.to_string(), + scheme: signing_scheme.to_string(), + }), + }, + _ => Err(SigstoreError::UnmatchedKeyAndSigningScheme { + key_typ: self.to_string(), + scheme: signing_scheme.to_string(), + }), + }, + SigStoreKeyPair::ED25519(keys) => { + if *signing_scheme != SigningScheme::ED25519 { + Err(SigstoreError::UnmatchedKeyAndSigningScheme { + key_typ: self.to_string(), + scheme: signing_scheme.to_string(), + }) + } else { + keys.to_sigstore_signer() + } + } + SigStoreKeyPair::RSA(keys) => match signing_scheme { + SigningScheme::RSA_PSS_SHA256(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha256, PaddingScheme::PSS) + } + SigningScheme::RSA_PSS_SHA384(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha384, PaddingScheme::PSS) + } + SigningScheme::RSA_PSS_SHA512(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha512, PaddingScheme::PSS) + } + SigningScheme::RSA_PKCS1_SHA256(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha256, PaddingScheme::PKCS1v15) + } + SigningScheme::RSA_PKCS1_SHA384(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha384, PaddingScheme::PKCS1v15) + } + SigningScheme::RSA_PKCS1_SHA512(_) => { + keys.to_sigstore_signer(DigestAlgorithm::Sha512, PaddingScheme::PKCS1v15) + } + _ => Err(SigstoreError::UnmatchedKeyAndSigningScheme { + key_typ: self.to_string(), + scheme: signing_scheme.to_string(), + }), + }, + } + } +} + +/// `Signer` trait is an abstraction of a specific set of asymmetric +/// private key, hash function and (if needs) padding algorithm. This +/// trait helps to construct the `SigStoreSigner` enum. +pub trait Signer { + /// Return the ref to the keypair inside the signer + fn key_pair(&self) -> &dyn KeyPair; + + /// `sign` will sign the given data, and return the signature. + fn sign(&self, msg: &[u8]) -> Result>; +} + +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum SigStoreSigner { + RSA_PSS_SHA256(RSASigner), + RSA_PSS_SHA384(RSASigner), + RSA_PSS_SHA512(RSASigner), + RSA_PKCS1_SHA256(RSASigner), + RSA_PKCS1_SHA384(RSASigner), + RSA_PKCS1_SHA512(RSASigner), + ECDSA_P256_SHA256_ASN1(EcdsaSigner), + ECDSA_P384_SHA384_ASN1(EcdsaSigner), + ED25519(Ed25519Signer), +} + +impl SigStoreSigner { + /// Return the inner `Signer` of the enum. This function + /// is useful in the inner interface conversion. + fn as_inner(&self) -> &dyn Signer { + match self { + SigStoreSigner::ECDSA_P256_SHA256_ASN1(inner) => inner, + SigStoreSigner::ECDSA_P384_SHA384_ASN1(inner) => inner, + SigStoreSigner::ED25519(inner) => inner, + SigStoreSigner::RSA_PSS_SHA256(inner) => inner, + SigStoreSigner::RSA_PSS_SHA384(inner) => inner, + SigStoreSigner::RSA_PSS_SHA512(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA256(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA384(inner) => inner, + SigStoreSigner::RSA_PKCS1_SHA512(inner) => inner, + } + } + + /// `sign` will sign the given data, and return the signature. + pub fn sign(&self, msg: &[u8]) -> Result> { + self.as_inner().sign(msg) + } + + /// `to_verification_key` will derive the verification_key for the `SigStoreSigner`. + pub fn to_verification_key(&self) -> Result { + let signing_scheme = match self { + SigStoreSigner::ECDSA_P256_SHA256_ASN1(_) => SigningScheme::ECDSA_P256_SHA256_ASN1, + SigStoreSigner::ECDSA_P384_SHA384_ASN1(_) => SigningScheme::ECDSA_P384_SHA384_ASN1, + SigStoreSigner::ED25519(_) => SigningScheme::ED25519, + SigStoreSigner::RSA_PSS_SHA256(_) => SigningScheme::RSA_PSS_SHA256(0), + SigStoreSigner::RSA_PSS_SHA384(_) => SigningScheme::RSA_PSS_SHA384(0), + SigStoreSigner::RSA_PSS_SHA512(_) => SigningScheme::RSA_PSS_SHA512(0), + SigStoreSigner::RSA_PKCS1_SHA256(_) => SigningScheme::RSA_PKCS1_SHA256(0), + SigStoreSigner::RSA_PKCS1_SHA384(_) => SigningScheme::RSA_PKCS1_SHA384(0), + SigStoreSigner::RSA_PKCS1_SHA512(_) => SigningScheme::RSA_PKCS1_SHA512(0), + }; + self.as_inner() + .key_pair() + .to_verification_key(&signing_scheme) + } + + /// `key_pair` will return the reference of the `SigStoreKeyPair` enum due to `SigStoreSigner`. + pub fn to_sigstore_keypair(&self) -> Result { + Ok(match self { + SigStoreSigner::ECDSA_P256_SHA256_ASN1(inner) => { + SigStoreKeyPair::ECDSA(inner.ecdsa_keys().to_wrapped_ecdsa_keys()?) + } + SigStoreSigner::ECDSA_P384_SHA384_ASN1(inner) => { + SigStoreKeyPair::ECDSA(inner.ecdsa_keys().to_wrapped_ecdsa_keys()?) + } + SigStoreSigner::ED25519(inner) => { + SigStoreKeyPair::ED25519(Ed25519Keys::from_ed25519key(inner.ed25519_keys())?) + } + SigStoreSigner::RSA_PSS_SHA256(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PSS_SHA384(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PSS_SHA512(inner) => SigStoreKeyPair::RSA(inner.rsa_keys().clone()), + SigStoreSigner::RSA_PKCS1_SHA256(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } + SigStoreSigner::RSA_PKCS1_SHA384(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } + SigStoreSigner::RSA_PKCS1_SHA512(inner) => { + SigStoreKeyPair::RSA(inner.rsa_keys().clone()) + } + }) + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::crypto::{Signature, SigningScheme, verification_key::CosignVerificationKey}; + + /// This is a test MESSAGE used to be signed by all signing test. + pub const MESSAGE: &str = r#"{ + "critical": { + "identity": { + "docker-reference": "registry-testing.svc.lan/busybox" + }, + "image": { + "docker-manifest-digest": "sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b" + }, + "type": "cosign container image signature" + }, + "optional": null + }"#; + + /// This test will do the following things: + /// * Randomly generate a key pair due to the given signing scheme. + /// * Signing the MESSAGE and generate a signature using + /// the private key. + /// * Derive the verification key using both `from_sigstore_signer` + /// and `to_verification_key`. + /// * Verify the signature with the public key. + #[rstest] + #[case(SigningScheme::ECDSA_P256_SHA256_ASN1)] + #[case(SigningScheme::ECDSA_P384_SHA384_ASN1)] + #[case(SigningScheme::ED25519)] + fn sigstore_signing(#[case] signing_scheme: SigningScheme) { + let signer = signing_scheme + .create_signer() + .unwrap_or_else(|_| panic!("create SigStoreSigner with {:?} failed", signing_scheme)); + let key_pair = signer + .to_sigstore_keypair() + .expect("convert SigStoreSigner to SigStoreKeypair failed."); + let _pubkey = key_pair + .public_key_to_pem() + .expect("export public key in PEM format failed."); + let sig = signer + .sign(MESSAGE.as_bytes()) + .expect("sign message failed."); + let _verification_key = signer + .to_verification_key() + .expect("derive signer into verification key failed."); + let verification_key = CosignVerificationKey::from_sigstore_signer(&signer) + .expect("derive verification key from signer failed."); + let signature = Signature::Raw(&sig); + let verify_res = verification_key.verify_signature(signature, MESSAGE.as_bytes()); + assert!(verify_res.is_ok(), "can not verify the signature."); + } +} diff --git a/vendor/src/crypto/signing_key/rsa/keypair.rs b/vendor/src/crypto/signing_key/rsa/keypair.rs new file mode 100644 index 0000000..0592b53 --- /dev/null +++ b/vendor/src/crypto/signing_key/rsa/keypair.rs @@ -0,0 +1,455 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # RSA Key Pair +//! +//! This is a wrapper for Rust Crypto. RSA Key Pair +//! is the struct [`RSAKeys`], which implements [`KeyPair`] +//! trait, and provides different exportation and importation operations +//! from/to der/pem bytes. +//! +//! # RSA Key Pair Operations +//! +//! For example, we generate an RSA key pair and export. +//! +//! ```rust +//! use sigstore::crypto::signing_key::{rsa::keypair::RSAKeys, KeyPair}; +//! +//! let rsa_keys = RSAKeys::new(2048).unwrap(); +//! +//! // export the pem encoded public key. +//! let pubkey = rsa_keys.public_key_to_pem().unwrap(); +//! +//! // export the private key using sigstore encryption. +//! let privkey_pem = rsa_keys.private_key_to_encrypted_pem(b"password").unwrap(); +//! +//! // import the key pair from the encrypted pem. +//! let rsa_keys2 = RSAKeys::from_encrypted_pem(privkey_pem.as_bytes(), b"password").unwrap(); +//! ``` + +use pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey}; +use rsa::{ + RsaPrivateKey, RsaPublicKey, pkcs1::DecodeRsaPrivateKey, pkcs1v15::SigningKey, + pss::BlindedSigningKey, +}; + +use crate::{ + crypto::{CosignVerificationKey, SigStoreSigner, SigningScheme}, + errors::*, +}; + +use crate::crypto::signing_key::{ + COSIGN_PRIVATE_KEY_PEM_LABEL, KeyPair, PRIVATE_KEY_PEM_LABEL, RSA_PRIVATE_KEY_PEM_LABEL, + SIGSTORE_PRIVATE_KEY_PEM_LABEL, kdf, +}; + +use super::{DigestAlgorithm, PaddingScheme, RSASigner}; + +#[derive(Clone, Debug)] +pub struct RSAKeys { + pub(crate) private_key: RsaPrivateKey, + public_key: RsaPublicKey, +} + +impl RSAKeys { + /// Create a new `RSAKeys` Object. + /// The private key will be randomly + /// generated. + pub fn new(bit_size: usize) -> Result { + let mut rng = rand::rngs::OsRng {}; + let private_key = RsaPrivateKey::new(&mut rng, bit_size)?; + let public_key = RsaPublicKey::from(&private_key); + Ok(Self { + private_key, + public_key, + }) + } + + /// Create a new `RSAKeys` Object from given `RSAKeys` Object. + pub fn from_rsa_privatekey_key(key: &RSAKeys) -> Result { + let priv_key = key.private_key_to_der()?; + RSAKeys::from_der(&priv_key) + } + + /// Builds a `RSAKeys` from encrypted pkcs8 PEM-encoded private key. + /// The label should be [`COSIGN_PRIVATE_KEY_PEM_LABEL`] or + /// [`SIGSTORE_PRIVATE_KEY_PEM_LABEL`]. + pub fn from_encrypted_pem(encrypted_pem: &[u8], password: &[u8]) -> Result { + let key = pem::parse(encrypted_pem)?; + match key.tag() { + COSIGN_PRIVATE_KEY_PEM_LABEL | SIGSTORE_PRIVATE_KEY_PEM_LABEL => { + let der = kdf::decrypt(key.contents(), password)?; + let pkcs8 = pkcs8::PrivateKeyInfo::try_from(&der[..]).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {e}")) + })?; + let private_key = RsaPrivateKey::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to rsa private key failed: {e}" + )) + })?; + Ok(Self::from(private_key)) + } + RSA_PRIVATE_KEY_PEM_LABEL | PRIVATE_KEY_PEM_LABEL if password.is_empty() => { + Self::from_pem(encrypted_pem) + } + RSA_PRIVATE_KEY_PEM_LABEL | PRIVATE_KEY_PEM_LABEL if !password.is_empty() => { + Err(SigstoreError::PrivateKeyDecryptError( + "Unencrypted private key but password provided".into(), + )) + } + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `RSAKeys` from a pkcs8 PEM-encoded private key. + /// The label of PEM should be [`PRIVATE_KEY_PEM_LABEL`] + pub fn from_pem(pem: &[u8]) -> Result { + let pem = std::str::from_utf8(pem)?; + let (label, document) = pkcs8::SecretDocument::from_pem(pem) + .map_err(|e| SigstoreError::PKCS8DerError(e.to_string()))?; + + match label { + PRIVATE_KEY_PEM_LABEL => { + let pkcs8 = pkcs8::PrivateKeyInfo::try_from(document.as_bytes()).map_err(|e| { + SigstoreError::PKCS8Error(format!("Read PrivateKeyInfo failed: {e}")) + })?; + let private_key = RsaPrivateKey::try_from(pkcs8).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 pem to rsa private key failed: {e}" + )) + })?; + Ok(Self::from(private_key)) + } + + RSA_PRIVATE_KEY_PEM_LABEL => { + let private_key = RsaPrivateKey::from_pkcs1_der(document.as_bytes())?; + Ok(Self::from(private_key)) + } + + tag => Err(SigstoreError::PrivateKeyDecryptError(format!( + "Unsupported pem tag {tag}" + ))), + } + } + + /// Builds a `RSAKeys` from a pkcs8 asn.1 private key. + pub fn from_der(der_bytes: &[u8]) -> Result { + let private_key = RsaPrivateKey::from_pkcs8_der(der_bytes).map_err(|e| { + SigstoreError::PKCS8Error(format!( + "Convert from pkcs8 der to rsa private key failed: {e}" + )) + })?; + Ok(Self::from(private_key)) + } + + /// `to_sigstore_signer` will create the [`SigStoreSigner`] using + /// this rsa key pair. + pub fn to_sigstore_signer( + &self, + digest_algorithm: DigestAlgorithm, + padding_scheme: PaddingScheme, + ) -> Result { + let private_key = self.private_key.clone(); + Ok(match padding_scheme { + PaddingScheme::PSS => match digest_algorithm { + DigestAlgorithm::Sha256 => { + SigStoreSigner::RSA_PSS_SHA256(RSASigner::RSA_PSS_SHA256( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha384 => { + SigStoreSigner::RSA_PSS_SHA384(RSASigner::RSA_PSS_SHA384( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha512 => { + SigStoreSigner::RSA_PSS_SHA512(RSASigner::RSA_PSS_SHA512( + BlindedSigningKey::::new(private_key), + self.clone(), + )) + } + }, + PaddingScheme::PKCS1v15 => match digest_algorithm { + DigestAlgorithm::Sha256 => { + SigStoreSigner::RSA_PKCS1_SHA256(RSASigner::RSA_PKCS1_SHA256( + SigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha384 => { + SigStoreSigner::RSA_PKCS1_SHA384(RSASigner::RSA_PKCS1_SHA384( + SigningKey::::new(private_key), + self.clone(), + )) + } + DigestAlgorithm::Sha512 => { + SigStoreSigner::RSA_PKCS1_SHA512(RSASigner::RSA_PKCS1_SHA512( + SigningKey::::new(private_key), + self.clone(), + )) + } + }, + }) + } +} + +impl From for RSAKeys { + fn from(private_key: RsaPrivateKey) -> Self { + Self { + private_key: private_key.clone(), + public_key: RsaPublicKey::from(private_key), + } + } +} + +impl KeyPair for RSAKeys { + /// Return the public key in PEM-encoded SPKI format. + fn public_key_to_pem(&self) -> Result { + self.public_key + .to_public_key_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the public key in asn.1 SPKI format. + fn public_key_to_der(&self) -> Result> { + Ok(self + .public_key + .to_public_key_der() + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string()))? + .to_vec()) + } + + /// Return the encrypted asn.1 pkcs8 private key. + fn private_key_to_encrypted_pem(&self, password: &[u8]) -> Result> { + let der = self.private_key_to_der()?; + let pem = pem::Pem::new( + SIGSTORE_PRIVATE_KEY_PEM_LABEL, + kdf::encrypt(&der, password)?, + ); + let pem = pem::encode(&pem); + Ok(zeroize::Zeroizing::new(pem)) + } + + /// Return the private key in pkcs8 PEM-encoded format. + fn private_key_to_pem(&self) -> Result> { + self.private_key + .to_pkcs8_pem(pkcs8::LineEnding::LF) + .map_err(|e| SigstoreError::PKCS8SpkiError(e.to_string())) + } + + /// Return the private key in asn.1 pkcs8 format. + fn private_key_to_der(&self) -> Result>> { + let pkcs8 = self + .private_key + .to_pkcs8_der() + .map_err(|e| SigstoreError::PKCS8Error(e.to_string()))?; + Ok(pkcs8.to_bytes()) + } + + /// Derive the relative [`CosignVerificationKey`]. + fn to_verification_key(&self, signing_scheme: &SigningScheme) -> Result { + let der = self.public_key_to_der()?; + let res = CosignVerificationKey::from_der(&der, signing_scheme)?; + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + use rstest::rstest; + + use crate::crypto::{ + Signature, SigningScheme, + signing_key::{ + KeyPair, Signer, + rsa::{DigestAlgorithm, PaddingScheme, RSASigner}, + tests::MESSAGE, + }, + verification_key::CosignVerificationKey, + }; + + use super::RSAKeys; + + const PASSWORD: &[u8] = b"123"; + const EMPTY_PASSWORD: &[u8] = b""; + const KEY_SIZE: usize = 2048; + + /// This test will try to read an unencrypted rsa + /// private key file, which is generated by `sigstore`. + #[test] + fn rsa_from_unencrypted_pem() { + let content = fs::read("tests/data/keys/rsa_private.key") + .expect("read tests/data/keys/rsa_private.key failed."); + let key = RSAKeys::from_pem(&content); + assert!( + key.is_ok(), + "can not create RSAKeys from unencrypted PEM file." + ); + } + + /// This test will try to read an encrypted rsa + /// private key file, which is generated by `sigstore`. + #[rstest] + #[case("tests/data/keys/rsa_encrypted_private.key", PASSWORD)] + #[case("tests/data/keys/rsa_private.key", EMPTY_PASSWORD)] + fn rsa_from_encrypted_pem(#[case] keypath: &str, #[case] password: &[u8]) { + let content = + fs::read(keypath).expect("read tests/data/keys/rsa_encrypted_private.key failed."); + let key = RSAKeys::from_encrypted_pem(&content, password); + assert!( + key.is_ok(), + "can not create RSAKeys from encrypted PEM file" + ); + } + + /// This test will try to encrypt a rsa keypair and + /// return the pem-encoded contents. The bit size + /// of the rsa key is [`KEY_SIZE`]. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(PASSWORD)] + fn rsa_to_encrypted_pem(#[case] password: &[u8]) { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key.private_key_to_encrypted_pem(password); + assert!( + key.is_ok(), + "can not export private key in encrypted PEM format." + ); + } + + /// This test will ensure that an unencrypted + /// keypair will fail to read if a non-empty + /// password is given. + #[test] + fn rsa_error_unencrypted_pem_password() { + let content = fs::read("tests/data/keys/rsa_private.key").expect("read key failed."); + let key = RSAKeys::from_encrypted_pem(&content, PASSWORD); + assert!( + key.is_err_and(|e| e + .to_string() + .contains("Unencrypted private key but password provided")), + "read unencrypted key with password" + ); + } + + /// This test will generate a RSAKeys, encode the private key + /// it into pem, and decode a new key from the generated pem-encoded + /// private key. + #[test] + fn rsa_to_and_from_pem() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key + .private_key_to_pem() + .expect("export private key to PEM format failed."); + let key = RSAKeys::from_pem(key.as_bytes()); + assert!(key.is_ok(), "can not create RSAKeys from PEM string."); + } + + /// This test will generate a RSAKeys, encode the private key + /// it into pem, and decode a new key from the generated pem-encoded + /// private key. + #[rstest] + #[case(PASSWORD)] + #[case::empty_password(EMPTY_PASSWORD)] + fn rsa_to_and_from_encrypted_pem(#[case] password: &[u8]) { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key + .private_key_to_encrypted_pem(password) + .expect("export private key to PEM format failed."); + let key = RSAKeys::from_encrypted_pem(key.as_bytes(), password); + assert!(key.is_ok(), "can not create RSAKeys from PEM string."); + } + + /// This test will generate a RSAKeys, encode the private key + /// it into der, and decode a new key from the generated der-encoded + /// private key. + #[test] + fn rsa_to_and_from_der() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let key = key + .private_key_to_der() + .expect("export private key to DER format failed."); + let key = RSAKeys::from_der(&key); + assert!(key.is_ok(), "can not create RSAKeys from DER bytes.") + } + + /// This test will generate a rsa keypair. + /// And then use the verification key interface to instantial + /// a VerificationKey object. + #[test] + fn rsa_generate_public_key() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let pubkey = key + .public_key_to_pem() + .expect("export private key to PEM format failed."); + assert!( + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::RSA_PSS_SHA256(0),) + .is_ok() + ); + let pubkey = key + .public_key_to_der() + .expect("export private key to DER format failed."); + assert!( + CosignVerificationKey::from_der(&pubkey, &SigningScheme::RSA_PSS_SHA256(0)).is_ok(), + "can not create CosignVerificationKey from der bytes." + ); + } + + /// This test will generate a rsa keypair. + /// And then derive a `CosignVerificationKey` from it. + #[test] + fn rsa_derive_verification_key() { + let key = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + assert!( + key.to_verification_key(&SigningScheme::RSA_PSS_SHA256(0)) + .is_ok(), + "can not create CosignVerificationKey from RSAKeys via `to_verification_key`." + ); + } + + /// This test will do the following things: + /// * Generate a rsa keypair. + /// * Sign the MESSAGE with `RSA_PSS_SHA256` + /// * Verify the signature using the public key. + #[test] + fn rsa_sign_and_verify() { + let rsa_keys = RSAKeys::new(KEY_SIZE).expect("create rsa keys failed."); + let pubkey = rsa_keys + .public_key_to_pem() + .expect("export private key to PEM format failed."); + let signer = + RSASigner::from_rsa_keys(&rsa_keys, DigestAlgorithm::Sha256, PaddingScheme::PSS); + + let sig = signer + .sign(MESSAGE.as_bytes()) + .expect("signing message failed."); + let verification_key = + CosignVerificationKey::from_pem(pubkey.as_bytes(), &SigningScheme::RSA_PSS_SHA256(0)) + .expect("convert CosignVerificationKey from public key failed."); + let signature = Signature::Raw(&sig); + assert!( + verification_key + .verify_signature(signature, MESSAGE.as_bytes()) + .is_ok(), + "can not verify the signature." + ); + } +} diff --git a/vendor/src/crypto/signing_key/rsa/mod.rs b/vendor/src/crypto/signing_key/rsa/mod.rs new file mode 100644 index 0000000..737a3cd --- /dev/null +++ b/vendor/src/crypto/signing_key/rsa/mod.rs @@ -0,0 +1,264 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # RSA Signer +//! +//! RSA Signer support the following padding schemes: +//! * `PSS` +//! * `PKCS#1 v1.5` +//! +//! And the following digest algorithms: +//! * `Sha256` +//! * `Sha384` +//! * `Sha512` +//! +//! # RSA Signer Operaion +//! +//! A [`RSASigner`] can be derived from a [`RSAKeys`] +//! ```rust +//! use sigstore::crypto::signing_key::{rsa::{RSASigner, keypair::RSAKeys, DigestAlgorithm, PaddingScheme}, KeyPair, Signer}; +//! use sigstore::crypto::Signature; +//! +//! let rsa_keys = RSAKeys::new(2048).unwrap(); +//! +//! // create a signer +//! let signer = RSASigner::from_rsa_keys(&rsa_keys, DigestAlgorithm::Sha256, PaddingScheme::PSS); +//! +//! // test message to be signed +//! let message = b"some message"; +//! +//! // sign +//! let signature_data = signer.sign(message).unwrap(); +//! +//! // export the [`CosignVerificationKey`] from the [`SigStoreSigner`], which +//! // is used to verify the signature. +//! let verification_key = signer.to_verification_key().unwrap(); +//! +//! // verify +//! assert!(verification_key.verify_signature(Signature::Raw(&signature_data),message).is_ok()); +//! ``` + +use ::rsa::{ + pkcs1v15::SigningKey, + pss::BlindedSigningKey, + signature::{Keypair, RandomizedSigner, SignatureEncoding}, +}; + +use self::keypair::RSAKeys; + +use crate::{crypto::CosignVerificationKey, errors::*}; + +use super::{KeyPair, Signer}; + +pub mod keypair; + +pub const DEFAULT_KEY_SIZE: usize = 2048; + +/// Different digest algorithms used in +/// RSA-based signing algorithm. +pub enum DigestAlgorithm { + Sha256, + Sha384, + Sha512, +} + +/// Different padding schemes used in +/// RSA-based signing algorithm. +/// * `PSS`: Probabilistic Signature Scheme, more secure than `PKCS1v15`. +/// * `PKCS1v15`: also known as simply PKCS1, is a simple padding +/// scheme developed for use with RSA keys. +pub enum PaddingScheme { + PSS, + PKCS1v15, +} + +/// Rsa signing scheme families: +/// * `PKCS1v15`: PKCS#1 1.5 padding for RSA signatures. +/// * `PSS`: RSA PSS padding for RSA signatures. +/// +/// Both schemes support the following digest algorithms: +/// * `Sha256` +/// * `Sha384` +/// * `Sha512` +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum RSASigner { + RSA_PSS_SHA256(BlindedSigningKey, RSAKeys), + RSA_PSS_SHA384(BlindedSigningKey, RSAKeys), + RSA_PSS_SHA512(BlindedSigningKey, RSAKeys), + RSA_PKCS1_SHA256(SigningKey, RSAKeys), + RSA_PKCS1_SHA384(SigningKey, RSAKeys), + RSA_PKCS1_SHA512(SigningKey, RSAKeys), +} + +/// helper to generate match arms +macro_rules! iter_on_rsa { + ($domain: ident, $match_item: expr, $signer: ident, $key: ident, $func: expr) => { + match $match_item { + $domain::RSA_PSS_SHA256($signer, $key) => $func, + $domain::RSA_PSS_SHA384($signer, $key) => $func, + $domain::RSA_PSS_SHA512($signer, $key) => $func, + $domain::RSA_PKCS1_SHA256($signer, $key) => $func, + $domain::RSA_PKCS1_SHA384($signer, $key) => $func, + $domain::RSA_PKCS1_SHA512($signer, $key) => $func, + } + }; +} + +impl RSASigner { + pub fn from_rsa_keys( + rsa_keys: &RSAKeys, + digest_algorithm: DigestAlgorithm, + padding_scheme: PaddingScheme, + ) -> Self { + let private_key = rsa_keys.private_key.clone(); + match padding_scheme { + PaddingScheme::PSS => match digest_algorithm { + DigestAlgorithm::Sha256 => RSASigner::RSA_PSS_SHA256( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha384 => RSASigner::RSA_PSS_SHA384( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha512 => RSASigner::RSA_PSS_SHA512( + BlindedSigningKey::::new(private_key), + rsa_keys.clone(), + ), + }, + PaddingScheme::PKCS1v15 => match digest_algorithm { + DigestAlgorithm::Sha256 => RSASigner::RSA_PKCS1_SHA256( + SigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha384 => RSASigner::RSA_PKCS1_SHA384( + SigningKey::::new(private_key), + rsa_keys.clone(), + ), + DigestAlgorithm::Sha512 => RSASigner::RSA_PKCS1_SHA512( + SigningKey::::new(private_key), + rsa_keys.clone(), + ), + }, + } + } + + /// Return the ref to the [`RSAKeys`] inside the RSASigner + pub fn rsa_keys(&self) -> &RSAKeys { + iter_on_rsa!(RSASigner, self, _signer, key, key) + } + + /// Return the related [`CosignVerificationKey`] of this RSASigner + pub fn to_verification_key(&self) -> Result { + Ok(match self { + RSASigner::RSA_PSS_SHA256(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA256(signer.verifying_key()) + } + RSASigner::RSA_PSS_SHA384(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA384(signer.verifying_key()) + } + RSASigner::RSA_PSS_SHA512(signer, _) => { + CosignVerificationKey::RSA_PSS_SHA512(signer.verifying_key()) + } + RSASigner::RSA_PKCS1_SHA256(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA256(signer.verifying_key()) + } + RSASigner::RSA_PKCS1_SHA384(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA384(signer.verifying_key()) + } + RSASigner::RSA_PKCS1_SHA512(signer, _) => { + CosignVerificationKey::RSA_PKCS1_SHA512(signer.verifying_key()) + } + }) + } +} + +impl Signer for RSASigner { + /// `sign` will sign the given data, and return the signature. + fn sign(&self, msg: &[u8]) -> Result> { + let mut rng = rand::thread_rng(); + Ok(iter_on_rsa!( + RSASigner, + self, + signer, + _key, + signer.sign_with_rng(&mut rng, msg).to_vec() + )) + } + + /// Return the ref to the [`KeyPair`] trait object inside the RSASigner + fn key_pair(&self) -> &dyn KeyPair { + iter_on_rsa!(RSASigner, self, _signer, key, key) + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::{DEFAULT_KEY_SIZE, DigestAlgorithm, PaddingScheme, RSASigner, keypair::RSAKeys}; + use crate::crypto::{ + Signature, SigningScheme, + signing_key::{KeyPair, Signer, tests::MESSAGE}, + }; + + #[rstest] + #[case( + DigestAlgorithm::Sha256, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA256(0) + )] + #[case( + DigestAlgorithm::Sha384, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA384(0) + )] + #[case( + DigestAlgorithm::Sha512, + PaddingScheme::PKCS1v15, + SigningScheme::RSA_PKCS1_SHA512(0) + )] + #[case( + DigestAlgorithm::Sha256, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA256(0) + )] + #[case( + DigestAlgorithm::Sha384, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA384(0) + )] + #[case( + DigestAlgorithm::Sha512, + PaddingScheme::PSS, + SigningScheme::RSA_PSS_SHA512(0) + )] + fn rsa_schemes( + #[case] digest_algorithm: DigestAlgorithm, + #[case] padding_scheme: PaddingScheme, + #[case] signing_scheme: SigningScheme, + ) { + let rsa_keys = RSAKeys::new(DEFAULT_KEY_SIZE).expect("RSA keys generated failed."); + let signer = RSASigner::from_rsa_keys(&rsa_keys, digest_algorithm, padding_scheme); + let sig = signer.sign(MESSAGE.as_bytes()).expect("sign failed."); + let vk = rsa_keys + .to_verification_key(&signing_scheme) + .expect("derive CosignVerificationKey failed."); + let signature = Signature::Raw(&sig); + vk.verify_signature(signature, MESSAGE.as_bytes()) + .expect("can not verify the signature."); + } +} diff --git a/vendor/src/crypto/transparency.rs b/vendor/src/crypto/transparency.rs new file mode 100644 index 0000000..ed11847 --- /dev/null +++ b/vendor/src/crypto/transparency.rs @@ -0,0 +1,398 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for Certificate Transparency validation. + +use const_oid::db::rfc6962::{CT_PRECERT_SCTS, CT_PRECERT_SIGNING_CERT}; +use digest::Digest; +use thiserror::Error; +use tls_codec::{SerializeBytes, TlsByteVecU16, TlsByteVecU24, TlsSerializeBytes, TlsSize}; +use tracing::debug; +use x509_cert::{ + Certificate, der, + der::{Decode, Encode}, + ext::pkix::{ + ExtendedKeyUsage, SignedCertificateTimestamp, SignedCertificateTimestampList, sct::Version, + }, +}; + +use super::{ + certificate, + keyring::{Keyring, KeyringError}, +}; +use crate::fulcio::SigningCertificateDetachedSCT; + +fn cert_is_preissuer(cert: &Certificate) -> bool { + let eku: ExtendedKeyUsage = match cert.tbs_certificate.get() { + Ok(Some((_, ext))) => ext, + _ => return false, + }; + + eku.0.contains(&CT_PRECERT_SIGNING_CERT) +} + +// +fn find_issuer_cert(chain: &[Certificate]) -> Option<&Certificate> { + let cert = if cert_is_preissuer(&chain[0]) { + &chain[1] + } else { + &chain[0] + }; + + certificate::is_ca(cert).ok()?; + Some(cert) +} + +#[derive(Debug, Error)] +pub enum CertificateErrorKind { + #[error("SCT list extension missing from leaf certificate")] + LeafSCTMissing, + + #[error("cannot find leaf certificate's issuer")] + IssuerMissing, + + #[error("cannot decode leaf certificate's issuer")] + IssuerMalformed, + + #[error("cannot decode SCT")] + LeafSCTMalformed, + + #[error(transparent)] + Der(#[from] der::Error), + + #[error(transparent)] + Tls(#[from] tls_codec::Error), +} + +impl From for CertificateErrorKind { + fn from(value: x509_cert::ext::pkix::Error) -> Self { + match value { + x509_cert::ext::pkix::Error::Der(e) => CertificateErrorKind::Der(e), + x509_cert::ext::pkix::Error::Tls(e) => CertificateErrorKind::Tls(e), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum SCTError { + #[error("failed to extract SCT from certificate")] + Parsing(#[from] CertificateErrorKind), + + #[error("failed to reconstruct signed payload")] + Serialization(#[source] tls_codec::Error), + + #[error("failed to verify SCT")] + Verification(#[from] KeyringError), +} + +#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)] +#[repr(u8)] +enum SignatureType { + CertificateTimestamp = 0, + TreeHash = 1, +} + +#[derive(PartialEq, Debug)] +#[repr(u16)] +enum LogEntryType { + X509Entry = 0, + PrecertEntry = 1, +} + +#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)] +struct PreCert { + // opaque issuer_key_hash[32]; + issuer_key_hash: [u8; 32], + // opaque TBSCertificate<1..2^24-1>; + tbs_certificate: TlsByteVecU24, +} + +#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)] +#[repr(u16)] +enum SignedEntry { + // opaque ASN.1Cert<1..2^24-1>; + #[tls_codec(discriminant = "LogEntryType::X509Entry")] + X509Entry(TlsByteVecU24), + #[tls_codec(discriminant = "LogEntryType::PrecertEntry")] + PrecertEntry(PreCert), +} + +#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)] +pub struct DigitallySigned { + version: Version, + signature_type: SignatureType, + timestamp: u64, + signed_entry: SignedEntry, + // opaque CtExtensions<0..2^16-1>; + extensions: TlsByteVecU16, + + // XX(tnytown): pass in some useful context. These fields will not be encoded into the + // TLS DigitallySigned blob, but we need them to properly verify the reconstructed + // message. + #[tls_codec(skip)] + log_id: [u8; 32], + #[tls_codec(skip)] + signature: Vec, +} + +#[derive(Debug)] +pub struct CertificateEmbeddedSCT<'a> { + cert: &'a Certificate, + sct: SignedCertificateTimestamp, + issuer_id: [u8; 32], +} + +impl<'a> CertificateEmbeddedSCT<'a> { + fn new_with_spki(cert: &'a Certificate, spki: &[u8]) -> Result { + let scts: SignedCertificateTimestampList = match cert.tbs_certificate.get() { + Ok(Some((_, ext))) => ext, + _ => return Err(SCTError::Parsing(CertificateErrorKind::LeafSCTMissing))?, + }; + + // Parse SCT structures. + let sct = match scts + .parse_timestamps() + .map_err(CertificateErrorKind::from)? + .as_slice() + { + [e] => e, + // We expect exactly one element here. Fail if there are more or less. + _ => return Err(CertificateErrorKind::LeafSCTMissing)?, + } + .parse_timestamp() + .map_err(CertificateErrorKind::from)?; + + let issuer_id = { + let mut hasher = sha2::Sha256::new(); + hasher.update(spki); + hasher.finalize().into() + }; + + Ok(Self { + cert, + sct, + issuer_id, + }) + } + + pub fn new(leaf: &'a Certificate, chain: &[Certificate]) -> Result { + // Traverse chain to find the issuer we're verifying against. + let issuer = find_issuer_cert(chain); + let spki = issuer + .ok_or(CertificateErrorKind::IssuerMissing)? + .tbs_certificate + .subject_public_key_info + .to_der() + .map_err(CertificateErrorKind::from)?; + + Self::new_with_spki(leaf, &spki) + } + + pub fn new_with_verified_path( + leaf: &'a Certificate, + chain: &webpki::VerifiedPath, + ) -> Result { + let issuer_spki = if let Some(issuer) = chain.intermediate_certificates().next() { + debug!("intermediate is the leaf's issuer"); + + let issuer = Certificate::from_der(&issuer.der()) + .map_err(CertificateErrorKind::from)? + .tbs_certificate; + issuer + .subject_public_key_info + .to_der() + .map_err(CertificateErrorKind::from)? + } else { + debug!("anchor is the leaf's issuer"); + + // Prefix the SPKI with the DER SEQUENCE tag and a short definite-form length. + let body = &chain.anchor().subject_public_key_info[..]; + let body_len = body + .len() + .try_into() + .or(Err(CertificateErrorKind::IssuerMalformed))?; + let prefix = &[0x30u8, body_len]; + + [prefix, body].concat() + }; + + Self::new_with_spki(leaf, &issuer_spki) + } +} + +impl From<&CertificateEmbeddedSCT<'_>> for DigitallySigned { + fn from(value: &CertificateEmbeddedSCT) -> Self { + // Construct the precert by filtering out the SCT extension. + let mut tbs_precert = value.cert.tbs_certificate.clone(); + tbs_precert.extensions = tbs_precert.extensions.map(|exts| { + exts.iter() + .filter(|v| v.extn_id != CT_PRECERT_SCTS) + .cloned() + .collect() + }); + + let mut tbs_precert_der = Vec::new(); + tbs_precert + .encode_to_vec(&mut tbs_precert_der) + .expect("failed to re-encode Precertificate!"); + + DigitallySigned { + // XX(tnytown): This match is needed because `sct::Version` does not implement Copy. + version: match value.sct.version { + Version::V1 => Version::V1, + }, + signature_type: SignatureType::CertificateTimestamp, + timestamp: value.sct.timestamp, + signed_entry: SignedEntry::PrecertEntry(PreCert { + issuer_key_hash: value.issuer_id, + tbs_certificate: tbs_precert_der.as_slice().into(), + }), + extensions: value.sct.extensions.clone(), + + log_id: value.sct.log_id.key_id, + signature: value.sct.signature.signature.clone().into(), + } + } +} + +impl From<&SigningCertificateDetachedSCT> for DigitallySigned { + fn from(value: &SigningCertificateDetachedSCT) -> Self { + let sct = &value.signed_certificate_timestamp; + + DigitallySigned { + version: Version::V1, + signature_type: SignatureType::CertificateTimestamp, + timestamp: sct.timestamp, + signed_entry: SignedEntry::X509Entry(value.chain.certificates[0].contents().into()), + extensions: sct.extensions.clone().into(), + + log_id: sct.id, + signature: sct.signature.clone(), + } + } +} + +/// Verifies a given signing certificate's Signed Certificate Timestamp. +/// +/// SCT verification as defined by [RFC 6962] guarantees that a given certificate has been submitted +/// to a Certificate Transparency log. Verification should be performed on the signing certificate +/// in Sigstore verify and sign flows. Certificates that fail SCT verification are misissued and +/// MUST NOT be trusted. +/// +/// For more information on Certificate Transparency and the guarantees it provides, see . +/// +/// [RFC 6962]: https://datatracker.ietf.org/doc/html/rfc6962 +pub fn verify_sct(sct: S, keyring: &Keyring) -> Result<(), SCTError> +where + S: Into, +{ + let sct: DigitallySigned = sct.into(); + let serialized = sct.tls_serialize().map_err(SCTError::Serialization)?; + + keyring.verify(&sct.log_id, &sct.signature, &serialized)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{CertificateEmbeddedSCT, verify_sct}; + use crate::crypto::keyring::Keyring; + use crate::fulcio::SigningCertificateDetachedSCT; + use p256::ecdsa::VerifyingKey; + use std::str::FromStr; + use x509_cert::Certificate; + use x509_cert::der::DecodePem; + use x509_cert::spki::EncodePublicKey; + + #[test] + fn verify_embedded_sct() { + let cert_pem = r#"-----BEGIN CERTIFICATE----- +MIICzDCCAlGgAwIBAgIUF96OLbM9/tDVHKCJliXLTFvnfjAwCgYIKoZIzj0EAwMw +NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl +cm1lZGlhdGUwHhcNMjMxMjEzMDU1MDU1WhcNMjMxMjEzMDYwMDU1WjAAMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEmir+Lah2291zCsLkmREQNLzf99z571BNB+fa +rerSLGzcwLFK7GRLTGYcO0oStxCYavxRQPMo3JvB8vGtZbn/76OCAXAwggFsMA4G +A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU8U9M +t9GMrRm8+gifPtc63nlP3OIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y +ZD8wGwYDVR0RAQH/BBEwD4ENYXNjQHRldHN1by5zaDAsBgorBgEEAYO/MAEBBB5o +dHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwLgYKKwYBBAGDvzABCAQgDB5o +dHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYkGCisGAQQB1nkCBAIEewR5 +AHcAdQDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAYxhumYsAAAE +AwBGMEQCIHRRe20lRrNM4xd07mpjTtgaE6FGS3jjF++zW8ZMnth3AiAd6LVAAeVW +hSW4T0XJRw9lGU6/EK9+ELZpEjrY03dJ1zAKBggqhkjOPQQDAwNpADBmAjEAiHqK +W9PQ/5h7VROVIWPaxUo3LhrL2sZanw4bzTDBDY0dRR19ZFzjtAph1RzpQqppAjEA +plAvxwkAIR2jurboJZ4Zm9rNAx8KvA+A5yQFzNkGgKDLjTJrKmSKoIcWV3j7WfdL +-----END CERTIFICATE-----"#; + + let chain_pem = [ + r#"-----BEGIN CERTIFICATE----- +MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7 +7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS +0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB +BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp +KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI +zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR +nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP +mygUY7Ii2zbdCdliiow= +-----END CERTIFICATE-----"#, + r#"-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE-----"#, + ]; + + let ctfe_pem = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNK +AaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw== +-----END PUBLIC KEY-----"#; + + let cert = Certificate::from_pem(cert_pem).unwrap(); + let chain = chain_pem.map(|c| Certificate::from_pem(c).unwrap()); + let sct = CertificateEmbeddedSCT::new(&cert, &chain).unwrap(); + let ctfe_key: VerifyingKey = VerifyingKey::from_str(ctfe_pem).unwrap(); + let keyring = Keyring::new([ctfe_key.to_public_key_der().unwrap().as_bytes()]).unwrap(); + + assert!(verify_sct(&sct, &keyring).is_ok()); + } + + #[test] + fn verify_detached_sct() { + let sct_json = r#"{"chain": {"certificates": ["-----BEGIN CERTIFICATE-----\nMIICUTCCAfigAwIBAgIUAafXe40Q5jthWJMo+JsJJCq09IAwCgYIKoZIzj0EAwIw\naDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQx\nFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoT\nCHNpZ3N0b3JlMB4XDTIzMTIxNDA3MDkzMFoXDTIzMTIxNDA3MTkzMFowADBZMBMG\nByqGSM49AgEGCCqGSM49AwEHA0IABDQT+qfW/VnHts0GSqI3kOc2z1lygSUWia3y\nIOx5qyWpXS1PwVcTbJnkcQEy1mnAES76NyfN5LsHHW2m53hF4WGjgecwgeQwDgYD\nVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBRpKUIe\nAqDxiw/GzKGRLFAvbaCnujAfBgNVHSMEGDAWgBTjGF7/fiITblnp3yIv3G1DETbS\ncTAbBgNVHREBAf8EETAPgQ1hc2NAdGV0c3VvLnNoMC4GCisGAQQBg78wAQEEIGh0\ndHBzOi8vb2F1dGgyLnNpZ3N0b3JlLmRldi9hdXRoMDAGCisGAQQBg78wAQgEIgwg\naHR0cHM6Ly9vYXV0aDIuc2lnc3RvcmUuZGV2L2F1dGgwCgYIKoZIzj0EAwIDRwAw\nRAIgOW+tCrt44rjWDCMSWhwC0zJRWpqH/qWRgSw2ndK7w3ICIGz0DDAXhvl6JFAz\nQp+40dnoUGKr+y0MF1zVaDOb1y+q\n-----END CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\nMIICFzCCAb2gAwIBAgIUbPNC2sKGpw8cOQfpv8yJii7c7TEwCgYIKoZIzj0EAwIw\naDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQx\nFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoT\nCHNpZ3N0b3JlMB4XDTIzMTIxNDA2NDIzNloXDTMzMTIxNDA2NDIzNlowaDEMMAoG\nA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNV\nBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0\nb3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfe1ZllZHky68F3jRhY4Hxx7o\nPBoBaD1i9UJtyE8xfIYGVpD1+jSHctZRmiv2ZsDEE6WN3k5lc2O2GyemHJwULqNF\nMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\nFOMYXv9+IhNuWenfIi/cbUMRNtJxMAoGCCqGSM49BAMCA0gAMEUCIDj5wbYN3ym8\nwY+Uy+FkKASpBQodXdgF+JR9tWhNDlc/AiEAwqMTyLa6Yr+5t1DvnUsR4lQNoXD7\nz8XmxcUnJTenEh4=\n-----END CERTIFICATE-----"]}, "signedCertificateTimestamp": "eyJzY3RfdmVyc2lvbiI6MCwiaWQiOiJla0ppei9acEcrVUVuNXcvR2FJcjYrYXdJK1JLZmtwdC9WOVRldTd2YTFrPSIsInRpbWVzdGFtcCI6MTcwMjUzNzc3MDQyNiwiZXh0ZW5zaW9ucyI6IiIsInNpZ25hdHVyZSI6IkJBTUFSakJFQWlBT28vdDZ4RDY0RkV2TWpGcGFsMUhVVkZxQU5nOXJ3ZEttd3NQU2wxNm5FZ0lnZmFNTlJHMTBxQVY1Z280MzU1WkxVNVVvdHRvWTAwK0l0YXhZYjRkZmV0Zz0ifQ=="}"#; + + let ctfe_pem = r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/ +mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA== +-----END PUBLIC KEY-----"#; + + let sct: SigningCertificateDetachedSCT = serde_json::from_str(sct_json).unwrap(); + let ctfe_key: VerifyingKey = VerifyingKey::from_str(ctfe_pem).unwrap(); + let keyring = Keyring::new([ctfe_key.to_public_key_der().unwrap().as_bytes()]).unwrap(); + + assert!(verify_sct(&sct, &keyring).is_ok()); + } +} diff --git a/vendor/src/crypto/verification_key.rs b/vendor/src/crypto/verification_key.rs new file mode 100644 index 0000000..a51c347 --- /dev/null +++ b/vendor/src/crypto/verification_key.rs @@ -0,0 +1,630 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use const_oid::db::rfc5912::{ID_EC_PUBLIC_KEY, RSA_ENCRYPTION}; +use ed25519::pkcs8::DecodePublicKey as ED25519DecodePublicKey; +use rsa::{pkcs1v15, pss}; +use sha2::{Digest, Sha256, Sha384}; +use signature::{DigestVerifier, Verifier, hazmat::PrehashVerifier}; +use x509_cert::{der::referenced::OwnedToRef, spki::SubjectPublicKeyInfoOwned}; + +use super::{ + Signature, SigningScheme, + signing_key::{KeyPair, SigStoreSigner}, +}; + +use crate::errors::*; + +#[cfg(feature = "cosign")] +use crate::cosign::constants::ED25519; + +/// A key that can be used to verify signatures. +/// +/// Currently the following key formats are supported: +/// +/// * RSA keys, using PSS padding and SHA-256 as the digest algorithm +/// * RSA keys, using PSS padding and SHA-384 as the digest algorithm +/// * RSA keys, using PSS padding and SHA-512 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-256 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-384 as the digest algorithm +/// * RSA keys, using PKCS1 padding and SHA-512 as the digest algorithm +/// * Ed25519 keys, and SHA-512 as the digest algorithm +/// * ECDSA keys, ASN.1 DER-encoded, using the P-256 curve and SHA-256 as digest algorithm +/// * ECDSA keys, ASN.1 DER-encoded, using the P-384 curve and SHA-384 as digest algorithm +#[allow(non_camel_case_types)] +#[derive(Debug, Clone)] +pub enum CosignVerificationKey { + RSA_PSS_SHA256(pss::VerifyingKey), + RSA_PSS_SHA384(pss::VerifyingKey), + RSA_PSS_SHA512(pss::VerifyingKey), + RSA_PKCS1_SHA256(pkcs1v15::VerifyingKey), + RSA_PKCS1_SHA384(pkcs1v15::VerifyingKey), + RSA_PKCS1_SHA512(pkcs1v15::VerifyingKey), + ECDSA_P256_SHA256_ASN1(ecdsa::VerifyingKey), + ECDSA_P384_SHA384_ASN1(ecdsa::VerifyingKey), + ED25519(ed25519_dalek::VerifyingKey), +} + +/// Attempts to convert a [x509 Subject Public Key Info](x509_cert::spki::SubjectPublicKeyInfo) object into +/// a `CosignVerificationKey` one. +/// +/// Currently can convert only the following types of keys: +/// * ECDSA P-256: assumes the SHA-256 digest algorithm is used +/// * ECDSA P-384: assumes the SHA-384 digest algorithm is used +/// * RSA: assumes PKCS1 padding is used +impl TryFrom<&SubjectPublicKeyInfoOwned> for CosignVerificationKey { + type Error = SigstoreError; + + fn try_from(subject_pub_key_info: &SubjectPublicKeyInfoOwned) -> Result { + let algorithm = subject_pub_key_info.algorithm.oid; + let public_key_der = &subject_pub_key_info.subject_public_key; + match algorithm { + ID_EC_PUBLIC_KEY => { + match public_key_der.raw_bytes().len() { + 65 => Ok(CosignVerificationKey::ECDSA_P256_SHA256_ASN1( + ecdsa::VerifyingKey::try_from(subject_pub_key_info.owned_to_ref()) + .map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "Ecdsa-P256 from der bytes to public key failed: {e}" + )) + })?, + )), + 97 => Ok(CosignVerificationKey::ECDSA_P384_SHA384_ASN1( + ecdsa::VerifyingKey::try_from(subject_pub_key_info.owned_to_ref()) + .map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "Ecdsa-P384 from der bytes to public key failed: {e}" + )) + })?, + )), + _ => Err(SigstoreError::PublicKeyUnsupportedAlgorithmError(format!( + "EC with size {} is not supported", + // asn.1 encode caused different length + (public_key_der.raw_bytes().len() - 1) * 4 + ))), + } + } + RSA_ENCRYPTION => { + let pubkey = rsa::RsaPublicKey::try_from(subject_pub_key_info.owned_to_ref()) + .map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "RSA from der bytes to public key failed: {e}" + )) + })?; + Ok(CosignVerificationKey::RSA_PKCS1_SHA256( + pkcs1v15::VerifyingKey::::from(pubkey), + )) + } + // + #[cfg(feature = "cosign")] + ED25519 => Ok(CosignVerificationKey::ED25519( + ed25519_dalek::VerifyingKey::try_from(subject_pub_key_info.owned_to_ref())?, + )), + _ => Err(SigstoreError::PublicKeyUnsupportedAlgorithmError(format!( + "Key with algorithm OID {algorithm} is not supported" + ))), + } + } +} + +impl CosignVerificationKey { + /// Builds a [`CosignVerificationKey`] from DER-encoded data. The methods takes care + /// of extracting the SubjectPublicKeyInfo from the DER-encoded data. + pub fn from_der(der_data: &[u8], signing_scheme: &SigningScheme) -> Result { + Ok(match signing_scheme { + SigningScheme::RSA_PSS_SHA256(_) => { + CosignVerificationKey::RSA_PSS_SHA256(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::RSA_PSS_SHA384(_) => { + CosignVerificationKey::RSA_PSS_SHA384(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::RSA_PSS_SHA512(_) => { + CosignVerificationKey::RSA_PSS_SHA512(pss::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA256(_) => { + CosignVerificationKey::RSA_PKCS1_SHA256(pkcs1v15::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA384(_) => { + CosignVerificationKey::RSA_PKCS1_SHA384(pkcs1v15::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::RSA_PKCS1_SHA512(_) => { + CosignVerificationKey::RSA_PKCS1_SHA512(pkcs1v15::VerifyingKey::new( + rsa::RsaPublicKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "read rsa public key from der failed: {e}" + )) + })?, + )) + } + SigningScheme::ECDSA_P256_SHA256_ASN1 => CosignVerificationKey::ECDSA_P256_SHA256_ASN1( + ecdsa::VerifyingKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "Ecdsa-P256 from der bytes to public key failed: {e}" + )) + })?, + ), + SigningScheme::ECDSA_P384_SHA384_ASN1 => CosignVerificationKey::ECDSA_P384_SHA384_ASN1( + ecdsa::VerifyingKey::from_public_key_der(der_data).map_err(|e| { + SigstoreError::PKCS8SpkiError(format!( + "Ecdsa-P384 from der bytes to public key failed: {e}" + )) + })?, + ), + SigningScheme::ED25519 => CosignVerificationKey::ED25519( + ed25519_dalek::VerifyingKey::from_public_key_der(der_data)?, + ), + }) + } + + /// Builds a [`CosignVerificationKey`] from DER-encoded public key data. This function will + /// set the verification algorithm due to the public key type, s.t. + /// * `RSA public key`: `RSA_PKCS1_SHA256` + /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` + /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` + /// * `Ed25519 public key`: `Ed25519` + pub fn try_from_der(der_data: &[u8]) -> Result { + if let Ok(p256vk) = ecdsa::VerifyingKey::from_public_key_der(der_data) { + Ok(Self::ECDSA_P256_SHA256_ASN1(p256vk)) + } else if let Ok(p384vk) = ecdsa::VerifyingKey::from_public_key_der(der_data) { + Ok(Self::ECDSA_P384_SHA384_ASN1(p384vk)) + } else if let Ok(ed25519bytes) = + ed25519::pkcs8::PublicKeyBytes::from_public_key_der(der_data) + { + Ok(Self::ED25519(ed25519_dalek::VerifyingKey::from_bytes( + ed25519bytes.as_ref(), + )?)) + } else { + match rsa::RsaPublicKey::from_public_key_der(der_data) { + Ok(rsapk) => Ok(Self::RSA_PKCS1_SHA256(pkcs1v15::VerifyingKey::new(rsapk))), + _ => Err(SigstoreError::InvalidKeyFormat { + error: "Failed to parse the public key.".to_string(), + }), + } + } + } + + /// Builds a [`CosignVerificationKey`] from PEM-encoded data. The methods takes care + /// of decoding the PEM-encoded data and then extracting the SubjectPublicKeyInfo + /// from the DER-encoded bytes. + pub fn from_pem(pem_data: &[u8], signing_scheme: &SigningScheme) -> Result { + let key_pem = pem::parse(pem_data)?; + Self::from_der(key_pem.contents(), signing_scheme) + } + + /// Builds a [`CosignVerificationKey`] from PEM-encoded public key data. This function will + /// set the verification algorithm due to the public key type, s.t. + /// * `RSA public key`: `RSA_PKCS1_SHA256` + /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` + /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` + /// * `Ed25519 public key`: `Ed25519` + pub fn try_from_pem(pem_data: &[u8]) -> Result { + let key_pem = pem::parse(pem_data)?; + Self::try_from_der(key_pem.contents()) + } + + /// Builds a `CosignVerificationKey` from [`SigStoreSigner`]. The methods will derive + /// a `CosignVerificationKey` from the given [`SigStoreSigner`]'s public key. + pub fn from_sigstore_signer(signer: &SigStoreSigner) -> Result { + signer.to_verification_key() + } + + /// Builds a `CosignVerificationKey` from [`KeyPair`]. The methods will derive + /// a `CosignVerificationKey` from the given [`KeyPair`]'s public key. + pub fn from_key_pair(signer: &dyn KeyPair, signing_scheme: &SigningScheme) -> Result { + signer.to_verification_key(signing_scheme) + } + + /// Verify the signature provided has been actually generated by the given key + /// when signing the provided message. + pub fn verify_signature(&self, signature: Signature, msg: &[u8]) -> Result<()> { + let sig = match signature { + Signature::Raw(data) => data.to_owned(), + Signature::Base64Encoded(data) => BASE64_STD_ENGINE.decode(data)?, + }; + + match self { + CosignVerificationKey::RSA_PSS_SHA256(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA384(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA512(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA256(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA384(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA512(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + // ECDSA signatures are encoded in der. + CosignVerificationKey::ECDSA_P256_SHA256_ASN1(inner) => { + let mut hasher = Sha256::new(); + digest::Digest::update(&mut hasher, msg); + let sig = ecdsa::Signature::from_der(&sig)?; + inner + .verify_digest(hasher, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::ECDSA_P384_SHA384_ASN1(inner) => { + let mut hasher = Sha384::new(); + digest::Digest::update(&mut hasher, msg); + let sig = ecdsa::Signature::from_der(&sig)?; + inner + .verify_digest(hasher, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::ED25519(inner) => { + let sig = ed25519::Signature::from_slice(sig.as_slice()) + .map_err(|_| SigstoreError::PublicKeyVerificationError)?; + inner + .verify(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + } + } + + /// Verify the signature provided has been actually generated by the given key + /// when signing the provided prehashed message. + pub(crate) fn verify_prehash(&self, signature: Signature, msg: &[u8]) -> Result<()> { + let sig = match signature { + Signature::Raw(data) => data.to_owned(), + Signature::Base64Encoded(data) => BASE64_STD_ENGINE.decode(data)?, + }; + + match self { + CosignVerificationKey::RSA_PSS_SHA256(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA384(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PSS_SHA512(inner) => { + let sig = pss::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA256(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA384(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::RSA_PKCS1_SHA512(inner) => { + let sig = pkcs1v15::Signature::try_from(sig.as_slice())?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + // ECDSA signatures are encoded in der. + CosignVerificationKey::ECDSA_P256_SHA256_ASN1(inner) => { + let sig = ecdsa::Signature::from_der(&sig)?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::ECDSA_P384_SHA384_ASN1(inner) => { + let sig = ecdsa::Signature::from_der(&sig)?; + inner + .verify_prehash(msg, &sig) + .map_err(|_| SigstoreError::PublicKeyVerificationError) + } + CosignVerificationKey::ED25519(_) => { + unimplemented!("Ed25519 doesn't implement verify_prehash") + } + } + } +} + +#[cfg(test)] +mod tests { + use x509_cert::Certificate; + use x509_cert::der::Decode; + + use super::*; + use crate::crypto::tests::*; + + #[test] + fn verify_signature_success() { + let signature = Signature::Base64Encoded(b"MEUCIQD6q/COgzOyW0YH1Dk+CCYSt4uAhm3FDHUwvPI55zwnlwIgE0ZK58ZOWpZw8YVmBapJhBqCfdPekIknimuO0xH8Jh8="); + let verification_key = + CosignVerificationKey::from_pem(PUBLIC_KEY.as_bytes(), &SigningScheme::default()) + .expect("Cannot create CosignVerificationKey"); + let msg = r#"{"critical":{"identity":{"docker-reference":"registry-testing.svc.lan/busybox"},"image":{"docker-manifest-digest":"sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b"},"type":"cosign container image signature"},"optional":null}"#; + + let outcome = verification_key.verify_signature(signature, msg.as_bytes()); + assert!(outcome.is_ok()); + } + + #[test] + fn verify_signature_failure_because_wrong_msg() { + let signature = Signature::Base64Encoded(b"MEUCIQD6q/COgzOyW0YH1Dk+CCYSt4uAhm3FDHUwvPI55zwnlwIgE0ZK58ZOWpZw8YVmBapJhBqCfdPekIknimuO0xH8Jh8="); + let verification_key = + CosignVerificationKey::from_pem(PUBLIC_KEY.as_bytes(), &SigningScheme::default()) + .expect("Cannot create CosignVerificationKey"); + let msg = "hello world"; + + let err = verification_key + .verify_signature(signature, msg.as_bytes()) + .expect_err("Was expecting an error"); + let found = matches!(err, SigstoreError::PublicKeyVerificationError); + assert!(found, "Didn't get expected error, got {:?} instead", err); + } + + #[test] + fn verify_signature_failure_because_wrong_signature() { + let signature = Signature::Base64Encoded(b"this is a signature"); + let verification_key = + CosignVerificationKey::from_pem(PUBLIC_KEY.as_bytes(), &SigningScheme::default()) + .expect("Cannot create CosignVerificationKey"); + let msg = r#"{"critical":{"identity":{"docker-reference":"registry-testing.svc.lan/busybox"},"image":{"docker-manifest-digest":"sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b"},"type":"cosign container image signature"},"optional":null}"#; + + let err = verification_key + .verify_signature(signature, msg.as_bytes()) + .expect_err("Was expecting an error"); + let found = matches!(err, SigstoreError::Base64DecodeError(_)); + assert!(found, "Didn't get expected error, got {:?} instead", err); + } + + #[test] + fn verify_signature_failure_because_wrong_verification_key() { + let signature = Signature::Base64Encoded(b"MEUCIQD6q/COgzOyW0YH1Dk+CCYSt4uAhm3FDHUwvPI55zwnlwIgE0ZK58ZOWpZw8YVmBapJhBqCfdPekIknimuO0xH8Jh8="); + + let verification_key = CosignVerificationKey::from_pem( + r#"-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETJP9cqpUQsn2ggmJniWGjHdlsHzD +JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== +-----END PUBLIC KEY-----"# + .as_bytes(), + &SigningScheme::default(), + ) + .expect("Cannot create CosignVerificationKey"); + let msg = r#"{"critical":{"identity":{"docker-reference":"registry-testing.svc.lan/busybox"},"image":{"docker-manifest-digest":"sha256:f3cfc9d0dbf931d3db4685ec659b7ac68e2a578219da4aae65427886e649b06b"},"type":"cosign container image signature"},"optional":null}"#; + + let err = verification_key + .verify_signature(signature, msg.as_bytes()) + .expect_err("Was expecting an error"); + let found = matches!(err, SigstoreError::PublicKeyVerificationError); + assert!(found, "Didn't get expected error, got {:?} instead", err); + } + + #[test] + fn verify_rsa_signature() { + let signature = Signature::Base64Encoded(b"umasnfYJyLbYPjiq1wIy086Ns+CrgiMoQUSGqPqlUmtWsY0hbngJ73hPfJFrppviPKdBeuUiiwgKagBKIXLEXjwxQp4eE3szwqkKoAnR/lByb7ahLgVQ4MB6xDQaHD53MYtj7aOvd4O7FqJltVVjEn7nM/Du2tL5y3jf6lD7VfHZE8uRocRlyppt8SfTc5L12mVlZ0YlfKYkd334A4y/reCy3Yws0j356Wj7GLScMU5uR11Y2y41rSyYm5uXhTerwNFXsRcPMAmenMarCdCmt4Lf4wpcJBCU172xiK+rIhbMgkLjjA772+auSYf1E8CySVah5CD0Td5YC3y8vIIYaA=="); + + let verification_key = CosignVerificationKey::from_pem( + r#"-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvM/dHoi6nSy7hbKHLYUr +Xy6Bv35JbdoIzny5vSFiRXApr0KS56U8PugdGmh+vd7H8YNlx2YOJxzv02Blsrcm +WDZcXjE3Xpsi/IHFfRZLOdwwR+u8MNFxwRUVzxyIzKGtbREVVfXPfb2Xc6FL5/tE +vQtUKuR6XdzSaav2RnV5IybCB09s0Np0AUbdi5EfSe4INuqgY+VFYLjvM5onbAQL +N3bFLS4Quk66Dhv93Zi6NwopwL1F07UPC5uadkyePStP3PA0OAOemj9vZADOWx5a +dsGCKISs8iphNC5mDVoLy8Ry49Ms3eQXRjVQOMco3YNf8AhsIdxDNBVN8VTDKVkE +DwIDAQAB +-----END PUBLIC KEY-----"# + .as_bytes(), + &SigningScheme::RSA_PKCS1_SHA256(0), + ) + .expect("Cannot create CosignVerificationKey"); + let msg = r#"{"critical":{"identity":{"docker-reference":"registry.suse.com/suse/sle-micro/5.0/toolbox"},"image":{"docker-manifest-digest":"sha256:356631f7603526a0af827741f5fe005acf19b7ef7705a34241a91c2d47a6db5e"},"type":"cosign container image signature"},"optional":{"creator":"OBS"}}"#; + + assert!( + verification_key + .verify_signature(signature, msg.as_bytes()) + .is_ok() + ); + } + + #[test] + fn convert_ecdsa_p256_subject_public_key_to_cosign_verification_key() -> anyhow::Result<()> { + let (private_key, public_key) = generate_ecdsa_p256_keypair(); + let issued_cert_generation_options = CertGenerationOptions { + private_key, + public_key, + ..Default::default() + }; + + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), issued_cert_generation_options)?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = Certificate::from_der(pem.contents())?; + let spki = cert.tbs_certificate.subject_public_key_info; + + let cosign_verification_key = + CosignVerificationKey::try_from(&spki).expect("conversion failed"); + + assert!(matches!( + cosign_verification_key, + CosignVerificationKey::ECDSA_P256_SHA256_ASN1(_) + )); + Ok(()) + } + + #[test] + fn convert_ecdsa_p384_subject_public_key_to_cosign_verification_key() -> anyhow::Result<()> { + let (private_key, public_key) = generate_ecdsa_p384_keypair(); + let issued_cert_generation_options = CertGenerationOptions { + private_key, + public_key, + ..Default::default() + }; + + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), issued_cert_generation_options)?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = Certificate::from_der(pem.contents())?; + let spki = cert.tbs_certificate.subject_public_key_info; + + let cosign_verification_key = + CosignVerificationKey::try_from(&spki).expect("conversion failed"); + + assert!(matches!( + cosign_verification_key, + CosignVerificationKey::ECDSA_P384_SHA384_ASN1(_) + )); + Ok(()) + } + + #[test] + fn convert_rsa_subject_public_key_to_cosign_verification_key() -> anyhow::Result<()> { + let (private_key, public_key) = generate_rsa_keypair(2048); + let issued_cert_generation_options = CertGenerationOptions { + private_key, + public_key, + ..Default::default() + }; + + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), issued_cert_generation_options)?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = Certificate::from_der(pem.contents())?; + let spki = cert.tbs_certificate.subject_public_key_info; + + let cosign_verification_key = + CosignVerificationKey::try_from(&spki).expect("conversion failed"); + + assert!(matches!( + cosign_verification_key, + CosignVerificationKey::RSA_PKCS1_SHA256(_) + )); + Ok(()) + } + + #[test] + fn convert_ed25519_subject_public_key_to_cosign_verification_key() -> anyhow::Result<()> { + let (private_key, public_key) = generate_ed25519_keypair(); + let issued_cert_generation_options = CertGenerationOptions { + private_key, + public_key, + ..Default::default() + }; + + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), issued_cert_generation_options)?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = Certificate::from_der(pem.contents())?; + let spki = cert.tbs_certificate.subject_public_key_info; + + let cosign_verification_key = + CosignVerificationKey::try_from(&spki).expect("conversion failed"); + + assert!(matches!( + cosign_verification_key, + CosignVerificationKey::ED25519(_) + )); + Ok(()) + } + + #[test] + fn convert_unsupported_curve_subject_public_key_to_cosign_verification_key() + -> anyhow::Result<()> { + let (private_key, public_key) = generate_dsa_keypair(2048); + let issued_cert_generation_options = CertGenerationOptions { + private_key, + public_key, + ..Default::default() + }; + + let ca_data = generate_certificate(None, CertGenerationOptions::default())?; + + let issued_cert = generate_certificate(Some(&ca_data), issued_cert_generation_options)?; + let issued_cert_pem = issued_cert.cert.to_pem()?; + let pem = pem::parse(issued_cert_pem)?; + let cert = Certificate::from_der(pem.contents())?; + let spki = cert.tbs_certificate.subject_public_key_info; + + let err = CosignVerificationKey::try_from(&spki); + assert!(matches!( + err, + Err(SigstoreError::PublicKeyUnsupportedAlgorithmError(_)) + )); + + Ok(()) + } +} diff --git a/vendor/src/errors.rs b/vendor/src/errors.rs new file mode 100644 index 0000000..f1ae8f6 --- /dev/null +++ b/vendor/src/errors.rs @@ -0,0 +1,275 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The errors that can be raised by sigstore-rs + +use thiserror::Error; + +#[cfg(feature = "cosign")] +use crate::cosign::{ + constraint::SignConstraintRefVec, verification_constraint::VerificationConstraintRefVec, +}; + +#[cfg(feature = "cosign")] +#[cfg_attr(docsrs, doc(cfg(feature = "cosign")))] +#[derive(Error, Debug)] +#[error("Several Signature Layers failed verification")] +pub struct SigstoreVerifyConstraintsError<'a> { + pub unsatisfied_constraints: VerificationConstraintRefVec<'a>, +} + +#[cfg(feature = "cosign")] +#[cfg_attr(docsrs, doc(cfg(feature = "cosign")))] +#[derive(Error, Debug)] +#[error("Several Constraints failed to apply on the SignatureLayer")] +pub struct SigstoreApplicationConstraintsError<'a> { + pub unapplied_constraints: SignConstraintRefVec<'a>, +} + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum SigstoreError { + #[error("failed to parse URL: {0}")] + UrlParseError(#[from] url::ParseError), + + #[error("failed to construct redirect URL")] + RedirectUrlRequestLineError, + + #[error("failed to construct oauth code pair")] + CodePairError, + + #[error("invalid key format: {error}")] + InvalidKeyFormat { error: String }, + + #[error("Unable to parse identity token: {0}")] + IdentityTokenError(String), + + #[error("unmatched key type {key_typ} and signing scheme {scheme}")] + UnmatchedKeyAndSigningScheme { key_typ: String, scheme: String }, + + #[error("x509 error: {0}")] + X509Error(String), + + #[error(transparent)] + FromPEMError(#[from] pem::PemError), + + #[error(transparent)] + Base64DecodeError(#[from] base64::DecodeError), + + #[error("Public key with unsupported algorithm: {0}")] + PublicKeyUnsupportedAlgorithmError(String), + + #[error("Public key verification error")] + PublicKeyVerificationError, + + #[error("X.509 certificate version is not V3")] + CertificateUnsupportedVersionError, + + #[error("Certificate validity check failed: cannot be used before {0}")] + CertificateValidityError(String), + + #[error("Certificate has not been issued for {0}")] + CertificateInvalidEmail(String), + + #[error( + "Certificate expired before signatures were entered in log: {integrated_time} is before {not_before}" + )] + CertificateExpiredBeforeSignaturesSubmittedToRekor { + integrated_time: String, + not_before: String, + }, + + #[error( + "Certificate was issued after signatures were entered in log: {integrated_time} is after {not_after}" + )] + CertificateIssuedAfterSignaturesSubmittedToRekor { + integrated_time: String, + not_after: String, + }, + + #[error("Bundled certificate does not have digital signature key usage")] + CertificateWithoutDigitalSignatureKeyUsage, + + #[error("Bundled certificate does not have code signing extended key usage")] + CertificateWithoutCodeSigningKeyUsage, + + #[error("Certificate without Subject Alternative Name")] + CertificateWithoutSubjectAlternativeName, + + #[error("Certificate with incomplete Subject Alternative Name")] + CertificateWithIncompleteSubjectAlternativeName, + + #[error("Certificate pool error: {0}")] + CertificatePoolError(String), + + #[error("Signing session expired")] + ExpiredSigningSession(), + + #[error("Fulcio request unsuccessful: {0}")] + FulcioClientError(String), + + #[error("Cannot fetch manifest of {image}: {error}")] + RegistryFetchManifestError { image: String, error: String }, + + #[error("Cannot pull manifest of {image}: {error}")] + RegistryPullManifestError { image: String, error: String }, + + #[error("Cannot pull {image}: {error}")] + RegistryPullError { image: String, error: String }, + + #[error("Cannot push {image}: {error}")] + RegistryPushError { image: String, error: String }, + + #[error("Rekor request unsuccessful: {0}")] + RekorClientError(String), + + #[error("Rekor public key not found for key id {0}")] + RekorPublicKeyNotFoundError(String), + + #[error(transparent)] + JoinError(#[from] tokio::task::JoinError), + + #[cfg(feature = "cert")] + #[error(transparent)] + KeyringError(#[from] crate::crypto::keyring::KeyringError), + + #[cfg(any(feature = "sign", feature = "verify"))] + #[error(transparent)] + SCTError(#[from] crate::crypto::transparency::SCTError), + + // HACK(tnytown): Remove when we rework the Fulcio V2 endpoint. + #[cfg(any(feature = "fulcio", feature = "oauth"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "fulcio", feature = "oauth"))))] + #[error(transparent)] + ReqwestError(#[from] reqwest::Error), + + #[error("OCI reference not valid: {reference}")] + OciReferenceNotValidError { reference: String }, + + #[error("Sigstore bundle malformed: {0}")] + SigstoreBundleMalformedError(String), + + #[error("Layer doesn't have Sigstore media type")] + SigstoreMediaTypeNotFoundError, + + #[error("Layer digest mismatch")] + SigstoreLayerDigestMismatchError, + + #[error("Missing signature annotation")] + SigstoreAnnotationNotFoundError, + + #[error("Rekor bundle missing")] + SigstoreRekorBundleNotFoundError, + + #[error("Fulcio certificates not provided")] + SigstoreFulcioCertificatesNotProvidedError, + + #[error("No Signature Layer passed verification")] + SigstoreNoVerifiedLayer, + + #[cfg(feature = "sigstore-trust-root")] + #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] + #[error(transparent)] + TufError(#[from] Box), + + #[error("TUF target {0} not found inside of repository")] + TufTargetNotFoundError(String), + + #[error("{0}")] + TufMetadataError(String), + + #[error(transparent)] + IOError(#[from] std::io::Error), + + #[error("{0}")] + UnexpectedError(String), + + #[error("{0}")] + VerificationConstraintError(String), + + #[error("{0}")] + VerificationMaterialError(String), + + #[error("{0}")] + ApplyConstraintError(String), + + #[error("Verification of OIDC claims received from OpenIdProvider failed")] + ClaimsVerificationError, + + #[cfg(feature = "oauth")] + #[error("Claims configuration error: {0}")] + ClaimsConfigurationError(#[from] openidconnect::ConfigurationError), + + #[error("Failed to access token endpoint")] + ClaimsAccessPointError, + + #[error("Failed to get id_token")] + NoIDToken, + + #[error("Pkcs8 error : {0}")] + PKCS8Error(String), + + #[error("Pkcs8 spki error : {0}")] + PKCS8SpkiError(String), + + #[error("Pkcs8 der encoding/decoding error : {0}")] + PKCS8DerError(String), + + #[error(transparent)] + ECDSAError(#[from] ecdsa::Error), + + #[error(transparent)] + ECError(#[from] elliptic_curve::Error), + + #[error(transparent)] + ScryptKDFInvalidParamsError(#[from] scrypt::errors::InvalidParams), + + #[error(transparent)] + ScryptKDFInvalidOutputLenError(#[from] scrypt::errors::InvalidOutputLen), + + #[error("Failed to encrypt the private key: {0}")] + PrivateKeyEncryptError(String), + + #[error("Failed to decrypt the private key: {0}")] + PrivateKeyDecryptError(String), + + #[error(transparent)] + SerdeJsonError(#[from] serde_json::error::Error), + + #[error(transparent)] + Utf8Error(#[from] std::str::Utf8Error), + + #[error(transparent)] + WebPKIError(#[from] webpki::Error), + + #[error("Failed to parse the key: {0}")] + KeyParseError(String), + + #[error(transparent)] + RSAError(#[from] rsa::errors::Error), + + #[error(transparent)] + PKCS1Error(#[from] pkcs1::Error), + + #[error(transparent)] + Ed25519PKCS8Error(#[from] ed25519_dalek::pkcs8::spki::Error), + + #[error(transparent)] + X509ParseError(#[from] x509_cert::der::Error), + + #[error(transparent)] + X509BuilderError(#[from] x509_cert::builder::Error), +} diff --git a/vendor/src/fulcio/mod.rs b/vendor/src/fulcio/mod.rs new file mode 100644 index 0000000..15f196a --- /dev/null +++ b/vendor/src/fulcio/mod.rs @@ -0,0 +1,290 @@ +pub(crate) mod models; + +pub mod oauth; + +use crate::crypto::SigningScheme; +use crate::crypto::signing_key::SigStoreSigner; +use crate::errors::{Result, SigstoreError}; +use crate::fulcio::models::{CreateSigningCertificateRequest, SigningCertificate}; +use crate::fulcio::oauth::OauthTokenProvider; +use crate::oauth::IdentityToken; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use openidconnect::core::CoreIdToken; +use reqwest::{Body, header}; +use serde::ser::SerializeStruct; +use serde::{Serialize, Serializer}; +use serde_json; +use std::fmt::{Debug, Display, Formatter}; +use url::Url; +use x509_cert::{Certificate, der::Decode}; + +pub use models::{CertificateResponse, SigningCertificateDetachedSCT}; + +/// Default public Fulcio server root. +pub const FULCIO_ROOT: &str = "https://fulcio.sigstore.dev/"; + +/// Path within Fulcio to obtain a signing certificate. +pub const SIGNING_CERT_PATH: &str = "api/v1/signingCert"; +pub const SIGNING_CERT_V2_PATH: &str = "api/v2/signingCert"; + +const CONTENT_TYPE_HEADER_NAME: &str = "content-type"; + +/// Fulcio certificate signing request +/// +/// Used to present a public key and signed challenge/proof-of-key in exchange +/// for a signed X509 certificate in return. +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Csr { + public_key: Option, + signed_email_address: Option, +} + +impl TryFrom for Body { + type Error = serde_json::Error; + + fn try_from(csr: Csr) -> std::result::Result { + Ok(Body::from(serde_json::to_string(&csr)?)) + } +} + +/// Internal newtype to control serde jsonification. +#[derive(Debug)] +struct PublicKey(String, SigningScheme); + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + let mut pk = serializer.serialize_struct("PublicKey", 2)?; + pk.serialize_field("content", &self.0)?; + pk.serialize_field( + "algorithm", + match self.1 { + SigningScheme::ECDSA_P256_SHA256_ASN1 | SigningScheme::ECDSA_P384_SHA384_ASN1 => { + "ecdsa" + } + SigningScheme::ED25519 => "ed25519", + SigningScheme::RSA_PSS_SHA256(_) + | SigningScheme::RSA_PSS_SHA384(_) + | SigningScheme::RSA_PSS_SHA512(_) + | SigningScheme::RSA_PKCS1_SHA256(_) + | SigningScheme::RSA_PKCS1_SHA384(_) + | SigningScheme::RSA_PKCS1_SHA512(_) => "rsa", + }, + )?; + pk.end() + } +} + +/// The PEM-encoded certificate chain returned by Fulcio. +pub struct FulcioCert(String); + +impl AsRef<[u8]> for FulcioCert { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Display for FulcioCert { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +/// Provider for Fulcio token. +#[allow(clippy::large_enum_variant)] +pub enum TokenProvider { + /// A Static provider consists of a tuple where the first value is a + /// OIDC token. The second is the value of the challenge. + /// + /// To figure out the correct value for the challenge one can list the + /// issuers available: + /// ```console + /// $ curl -Ls https://fulcio.sigstore.dev/api/v2/configuration | jq + /// ``` + /// Find the issuer of the token, and then find the value of the + /// `challengeClaim` which will specify which value of the OIDC token's + /// claims to use. + /// + /// For example, if the token was issued from + /// `https://token.actions.githubusercontent.com`: + /// ```json + /// { + /// "issuerUrl": "https://token.actions.githubusercontent.com", + /// "audience": "sigstore", + /// "challengeClaim": "sub", + /// "spiffeTrustDomain": "" + /// } + /// ``` + /// In this case the value of the challenge should be the value of the + /// `sub` (`subject`) claim of the token. + /// + Static((CoreIdToken, String)), + Oauth(OauthTokenProvider), +} + +impl TokenProvider { + /// Retrieve a token and the challenge-to-sign from the provider. + pub async fn get_token(&self) -> Result<(CoreIdToken, String)> { + match self { + TokenProvider::Static(inner) => Ok(inner.clone()), + TokenProvider::Oauth(auth) => auth.get_token().await, + } + } +} + +/// Client for creating and holding ephemeral key pairs, and easily +/// getting a Fulcio-signed certificate chain. +pub struct FulcioClient { + root_url: Url, + token_provider: TokenProvider, +} + +impl FulcioClient { + /// Create a new Fulcio client. + /// + /// * root_url: The root Fulcio server URL. + /// * token_provider: Provider capable of providing a CoreIdToken and the challenge to sign. + /// + /// Returns a configured Fulcio client. + pub fn new(root_url: Url, token_provider: TokenProvider) -> Self { + Self { + root_url, + token_provider, + } + } + + /// Request a certificate from Fulcio + /// + /// * signing_scheme: The signing scheme to use. + /// + /// Returns a tuple of the appropriately-configured sigstore signer and the Fulcio-issued certificate chain. + pub async fn request_cert( + self, + signing_scheme: SigningScheme, + ) -> Result<(SigStoreSigner, FulcioCert)> { + let (token, challenge) = self.token_provider.get_token().await?; + + let signer = signing_scheme.create_signer()?; + let signature = signer.sign(challenge.as_bytes())?; + let signature = BASE64_STD_ENGINE.encode(signature); + + let key_pair = signer.to_sigstore_keypair()?; + let public_key = key_pair.public_key_to_der()?; + let public_key = BASE64_STD_ENGINE.encode(public_key); + + let csr = Csr { + public_key: Some(PublicKey(public_key, signing_scheme)), + signed_email_address: Some(signature), + }; + + let csr = TryInto::::try_into(csr)?; + + let client = reqwest::Client::new(); + let response = client + .post(self.root_url.join(SIGNING_CERT_PATH)?) + .header(CONTENT_TYPE_HEADER_NAME, "application/json") + .bearer_auth(token.to_string()) + .body(csr) + .send() + .await + .map_err(|_| SigstoreError::SigstoreFulcioCertificatesNotProvidedError)?; + + let cert = response + .text() + .await + .map_err(|_| SigstoreError::SigstoreFulcioCertificatesNotProvidedError)?; + + Ok((signer, FulcioCert(cert))) + } + + /// Request a certificate from Fulcio with the V2 endpoint. + /// + /// TODO(tnytown): This (and other API clients) should be autogenerated. See sigstore-rs#209. + /// + /// + /// + /// Additionally, it might not be reasonable to expect callers to correctly construct and pass + /// in an X509 CSR. + pub async fn request_cert_v2( + &self, + request: x509_cert::request::CertReq, + identity: &IdentityToken, + ) -> Result { + let client = reqwest::Client::new(); + + macro_rules! headers { + ($($key:expr => $val:expr),+) => { + { + let mut map = reqwest::header::HeaderMap::new(); + $( map.insert($key, $val.parse().unwrap()); )+ + map + } + } + } + let headers = headers!( + header::AUTHORIZATION => format!("Bearer {}", identity.to_string()), + header::CONTENT_TYPE => "application/json", + header::ACCEPT => "application/pem-certificate-chain" + ); + + let response = client + .post(self.root_url.join(SIGNING_CERT_V2_PATH)?) + .headers(headers) + .json(&CreateSigningCertificateRequest { + certificate_signing_request: request, + }) + .send() + .await?; + let status = response.status(); + let body = response.bytes().await?; + let body_text = String::from_utf8(body.clone().into_iter().collect()) + .unwrap_or_else(|_| format!("non-utf8 body ({} bytes)", body.len())); + let body_snippet = body_text.chars().take(2048).collect::(); + + if !status.is_success() { + return Err(SigstoreError::FulcioClientError(format!( + "Fulcio returned status {} with body: {}", + status.as_u16(), + body_snippet, + ))); + } + + let response: SigningCertificate = + serde_json::from_slice(&body).map_err(|err| SigstoreError::FulcioClientError(format!( + "Failed to decode Fulcio response (status {}): {}; body: {}", + status.as_u16(), + err, + body_snippet, + )))?; + + let (certs, detached_sct) = match response { + SigningCertificate::SignedCertificateDetachedSct(ref sc) => { + (&sc.chain.certificates, Some(sc.clone())) + } + SigningCertificate::SignedCertificateEmbeddedSct(ref sc) => { + (&sc.chain.certificates, None) + } + }; + + if certs.len() < 2 { + return Err(SigstoreError::FulcioClientError( + "Certificate chain too short: certs.len() < 2".into(), + )); + } + + let cert = Certificate::from_der(certs[0].contents())?; + let chain = certs[1..] + .iter() + .map(|pem| Certificate::from_der(pem.contents())) + .collect::, _>>()?; + + Ok(CertificateResponse { + cert, + chain, + detached_sct, + }) + } +} diff --git a/vendor/src/fulcio/models.rs b/vendor/src/fulcio/models.rs new file mode 100644 index 0000000..28f6b2c --- /dev/null +++ b/vendor/src/fulcio/models.rs @@ -0,0 +1,127 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Models for interfacing with Fulcio. +//! +//! + +use pem::Pem; +use pkcs8::der::EncodePem; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_repr::Deserialize_repr; +use serde_with::{ + DeserializeAs, SerializeAs, + base64::{Base64, Standard}, + formats::Padded, + serde_as, +}; +use x509_cert::Certificate; + +fn serialize_x509_csr( + input: &x509_cert::request::CertReq, + ser: S, +) -> std::result::Result +where + S: Serializer, +{ + let encoded = input + .to_pem(pkcs8::LineEnding::LF) + .map_err(serde::ser::Error::custom)?; + + Base64::::serialize_as(&encoded, ser) +} + +fn deserialize_inner_detached_sct<'de, D>(de: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + let buf: Vec = Base64::::deserialize_as(de)?; + serde_json::from_slice(&buf).map_err(serde::de::Error::custom) +} + +fn deserialize_inner_detached_sct_signature<'de, D>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let buf: Vec = Base64::::deserialize_as(de)?; + + // The first two bytes indicate the signature and hash algorithms so let's skip those. + // The next two bytes indicate the size of the signature. + let signature_size = u16::from_be_bytes(buf[2..4].try_into().expect("unexpected length")); + + // This should be equal to the length of the remainder of the signature buffer. + let signature = buf[4..].to_vec(); + if signature_size as usize != signature.len() { + return Err(serde::de::Error::custom("signature size mismatch")); + } + Ok(signature) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateSigningCertificateRequest { + #[serde(serialize_with = "serialize_x509_csr")] + pub certificate_signing_request: x509_cert::request::CertReq, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum SigningCertificate { + SignedCertificateDetachedSct(SigningCertificateDetachedSCT), + SignedCertificateEmbeddedSct(SigningCertificateEmbeddedSCT), +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SigningCertificateDetachedSCT { + pub chain: CertificateChain, + #[serde(deserialize_with = "deserialize_inner_detached_sct")] + pub signed_certificate_timestamp: InnerDetachedSCT, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SigningCertificateEmbeddedSCT { + pub chain: CertificateChain, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct CertificateChain { + pub certificates: Vec, +} + +#[serde_as] +#[derive(Deserialize, Debug, Clone)] +pub struct InnerDetachedSCT { + pub sct_version: SCTVersion, + #[serde_as(as = "Base64")] + pub id: [u8; 32], + pub timestamp: u64, + #[serde(deserialize_with = "deserialize_inner_detached_sct_signature")] + pub signature: Vec, + #[serde_as(as = "Base64")] + pub extensions: Vec, +} + +#[derive(Deserialize_repr, PartialEq, Debug, Clone)] +#[repr(u8)] +pub enum SCTVersion { + V1 = 0, +} + +pub struct CertificateResponse { + pub cert: Certificate, + pub chain: Vec, + pub detached_sct: Option, +} diff --git a/vendor/src/fulcio/oauth.rs b/vendor/src/fulcio/oauth.rs new file mode 100644 index 0000000..1cd2939 --- /dev/null +++ b/vendor/src/fulcio/oauth.rs @@ -0,0 +1,130 @@ +use crate::errors::Result; +use crate::errors::SigstoreError; +use crate::oauth::openidflow::{OpenIDAuthorize, RedirectListener}; +use openidconnect::core::CoreIdToken; + +/// Default client id ("sigstore"). +pub const DEFAULT_CLIENT_ID: &str = "sigstore"; + +/// Default client secret (the empty string) +pub const DEFAULT_CLIENT_SECRET: &str = ""; + +/// Default issuer (Oauth provider at sigstore.dev) +pub const DEFAULT_ISSUER: &str = "https://oauth2.sigstore.dev/auth"; + +/// Default local redirect port (8080) +pub const DEFAULT_REDIRECT_PORT: u32 = 8080; + +/// Token provider that performs a human-involved OIDC flow to acquire a token id. +#[derive(Default)] +pub struct OauthTokenProvider { + client_id: Option, + client_secret: Option, + issuer: Option, + redirect_port: Option, +} + +impl OauthTokenProvider { + /// Set a non-default client-id. + pub fn with_client_id(self, client_id: &str) -> Self { + Self { + client_id: Some(client_id.to_string()), + client_secret: self.client_secret, + issuer: self.issuer, + redirect_port: self.redirect_port, + } + } + + /// Set a non-default client secret. + pub fn with_client_secret(self, client_secret: &str) -> Self { + Self { + client_id: self.client_id, + client_secret: Some(client_secret.to_string()), + issuer: self.issuer, + redirect_port: self.redirect_port, + } + } + + /// Set a non-default issuer. + pub fn with_issuer(self, issuer: &str) -> Self { + Self { + client_id: self.client_id, + client_secret: self.client_secret, + issuer: Some(issuer.to_string()), + redirect_port: self.redirect_port, + } + } + + /// Set a non-default redirect port. + pub fn with_redirect_port(self, port: u32) -> Self { + Self { + client_id: self.client_id, + client_secret: self.client_secret, + issuer: self.issuer, + redirect_port: Some(port), + } + } + + fn redirect_url(&self) -> String { + format!( + "http://localhost:{}", + self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT) + ) + } + + /// Perform human-involved OIDC flow to acquire an id token, along with + /// the extracted email claim value for use in signed challenge with Fulcio. + pub async fn get_token(&self) -> Result<(CoreIdToken, String)> { + let oidc_url = OpenIDAuthorize::new( + self.client_id + .as_ref() + .unwrap_or(&DEFAULT_CLIENT_ID.to_string()), + self.client_secret + .as_ref() + .unwrap_or(&DEFAULT_CLIENT_SECRET.to_string()), + self.issuer.as_ref().unwrap_or(&DEFAULT_ISSUER.to_string()), + &self.redirect_url(), + ) + .auth_url_async() + .await; + + match oidc_url.as_ref() { + Ok(url) => { + webbrowser::open(url.0.as_ref())?; + println!( + "Open this URL in a browser if it does not automatically open for you:\n{}\n", + url.0, + ); + } + Err(e) => println!("{e}"), + } + + let oidc_url = oidc_url?; + let result = RedirectListener::new( + &format!( + "127.0.0.1:{}", + self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT) + ), + oidc_url.1.clone(), // client + oidc_url.2.clone(), // nonce + oidc_url.3, // pkce_verifier + ) + .redirect_listener_async() + .await; + + if let Ok((_, id_token)) = result { + let verifier = oidc_url.1.id_token_verifier(); + let nonce = &oidc_url.2; + + let claims = id_token.claims(&verifier, nonce); + if let Ok(claims) = claims + && let Some(email) = claims.email() + { + let email = &**email; + return Ok((id_token.clone(), email.clone())); + } + } + + Err(SigstoreError::NoIDToken) + } +} diff --git a/vendor/src/lib.rs b/vendor/src/lib.rs new file mode 100644 index 0000000..1cbc03f --- /dev/null +++ b/vendor/src/lib.rs @@ -0,0 +1,275 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate aims to provide [Sigstore](https://www.sigstore.dev/) capabilities to Rust developers. +//! +//! Currently, the main focus of the crate is to provide the verification +//! capabilities offered by the official `cosign` tool. +//! +//! **Warning:** this library is still experimental. Its API can change at any time. +//! +//! # Security +//! +//! Should you discover any security issues, please refer to +//! Sigstore's [security process](https://github.com/sigstore/community/blob/main/SECURITY.md). +//! +//! # Verification +//! +//! Sigstore verification is done using the [`cosign::Client`] +//! struct. +//! +//! ## Triangulation of Sigstore signature +//! +//! Given a container image/oci artifact, calculate the location of +//! its cosign signature inside of a registry: +//! +//! ```rust,no_run +//! use crate::sigstore::cosign::CosignCapabilities; +//! use std::fs; +//! +//! #[tokio::main] +//! pub async fn main() { +//! let auth = &sigstore::registry::Auth::Anonymous; +//! +//! let mut client = sigstore::cosign::ClientBuilder::default() +//! .build() +//! .expect("Unexpected failure while building Client"); +//! let image = "registry-testing.svc.lan/kubewarden/disallow-service-nodeport:v0.1.0".parse().unwrap(); +//! let (cosign_signature_image, source_image_digest) = client.triangulate( +//! &image, +//! auth +//! ).await.expect("Unexpected failure while using triangulate"); +//! } +//! ``` +//! +//! ## Signature verification +//! +//! Verify the signature of a container image/oci artifact: +//! +//!```rust,no_run +//! use std::{boxed::Box, collections::BTreeMap, fs}; +//! +//! use sigstore::{ +//! cosign::{ +//! CosignCapabilities, +//! verification_constraint::{ +//! AnnotationVerifier, PublicKeyVerifier, VerificationConstraintVec, +//! }, +//! verify_constraints, +//! }, +//! crypto::SigningScheme, +//! errors::SigstoreVerifyConstraintsError, +//! trust::sigstore::SigstoreTrustRoot, +//! }; +//! +//! #[tokio::main] +//! pub async fn main() { +//! let auth = &sigstore::registry::Auth::Anonymous; +//! +//! // Initialize a Sigstore trust root. This will fetch the latest trust +//! // materials from the Sigstore TUF repository. +//! let repo = SigstoreTrustRoot::new(None) +//! .await +//! .expect("Could not initialize Sigstore trust root"); +//! +//! let mut client = sigstore::cosign::ClientBuilder::default() +//! .with_trust_repository(&repo) +//! .expect("Cannot construct cosign client from given materials") +//! .build() +//! .expect("Unexpected failure while building Client"); +//! +//! // Obtained via `triangulate` +//! let cosign_image = "registry-testing.svc.lan/kubewarden/disallow-service-nodeport:sha256-5f481572d088dc4023afb35fced9530ced3d9b03bf7299c6f492163cb9f0452e.sig" +//! .parse().unwrap(); +//! // Obtained via `triangulate` +//! let source_image_digest = +//! "sha256-5f481572d088dc4023afb35fced9530ced3d9b03bf7299c6f492163cb9f0452e"; +//! +//! // Obtain the list of associated signature layers that can be trusted +//! let signature_layers = client +//! .trusted_signature_layers(auth, source_image_digest, &cosign_image) +//! .await +//! .expect("Could not obtain signature layers"); +//! +//! // Define verification constraints +//! let mut annotations: BTreeMap = BTreeMap::new(); +//! annotations.insert("env".to_string(), "prod".to_string()); +//! let annotation_verifier = AnnotationVerifier { annotations }; +//! +//! let verification_key = +//! fs::read("~/cosign.pub").expect("Cannot read contents of cosign public key"); +//! let pub_key_verifier = PublicKeyVerifier::new(&verification_key, &SigningScheme::default()) +//! .expect("Could not create verifier"); +//! +//! let verification_constraints: VerificationConstraintVec = +//! vec![Box::new(annotation_verifier), Box::new(pub_key_verifier)]; +//! +//! // Use the given list of constraints to verify the trusted +//! // `signature_layers`. This will raise an error if one or more verification +//! // constraints are not satisfied. +//! let result = verify_constraints(&signature_layers, verification_constraints.iter()); +//! +//! match result { +//! Ok(()) => { +//! println!("Image successfully verified"); +//! } +//! Err(SigstoreVerifyConstraintsError { +//! unsatisfied_constraints, +//! }) => { +//! println!("{:?}", unsatisfied_constraints); +//! panic!("Image verification failed") +//! } +//! } +//! } +//! ``` +//! # Rekor integration +//! The examples folder contains code that shows users how to make Rekor API calls. +//! It also provides a clean interface with step-by-step instructions that other developers can copy and paste. +//! +//! ```rust,no_run +//! use clap::{Arg, Command}; +//! use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +//! use sigstore::rekor::models::log_entry::LogEntry; +//! use std::str::FromStr; +//! #[tokio::main] +//! async fn main() { +//! /* +//! Retrieves an entry and inclusion proof from the transparency log (if it exists) by index +//! Example command : +//! cargo run --example get_log_entry_by_index -- --log_index 99 +//! */ +//! let matches = Command::new("cmd").arg( +//! Arg::new("log_index") +//! .long("log_index") +//! .value_name("LOG_INDEX") +//! .help("log_index of the artifact"), +//! ); +//! +//! let flags = matches.get_matches(); +//! let index = ::from_str( +//! flags.get_one::("log_index") +//! .unwrap_or(&"1".to_string()) +//! ).unwrap(); +//! +//! let configuration = Configuration::default(); +//! +//! let message: LogEntry = entries_api::get_log_entry_by_index(&configuration, index) +//! .await +//! .unwrap(); +//! println!("{:#?}", message); +//! } +//! ``` +//! +//! The following comment in the code tells the user how to provide the required values to the API calls using cli flags. +//! +//! In the example below, the user can retrieve different entries by inputting a different value for the log_index flag. +//! +//! +//!/* +//!Retrieves an entry and inclusion proof from the transparency log (if it exists) by index +//!Example command : +//!cargo run --example get_log_entry_by_index -- --log_index 99 +//!*/ +//! +//! # The example code is provided for the following API calls: +//! +//!- create_log_entry +//!- get_log_entry_by_index +//!- get_log_entry_by_uuid +//!- get_log_info +//!- get_log_proof +//!- get_public_key +//!- search_index +//!- search_log_query +//! +//! +//! # Examples +//! +//! Additional examples can be found inside of the [`examples`](https://github.com/sigstore/sigstore-rs/tree/main/examples/) +//! directory. +//! +//! ## Fulcio and Rekor integration +//! +//! [`cosign::Client`] integration with Fulcio and Rekor +//! requires the following data to work: Fulcio's certificate and Rekor's public key. +//! +//! These files are safely distributed by the Sigstore project via a TUF repository. +//! The [`sigstore::trust::sigstore`](crate::trust::sigstore) module provides the helper structures to deal +//! with it. +//! +//! # Feature Flags +//! +//! Sigstore-rs uses a set of [feature flags] to reduce the amount of compiled code. +//! By default, all features are enabled, and `native-tls` is used for TLS. +//! It is recommended to use `default-features = false` in your `Cargo.toml` +//! and only enable the features you need. +//! +//! ## TLS (Required) +//! +//! One of these features must be enabled: +//! +//! - `native-tls`: Enables support for `native-tls` as the underlying tls for all the features. +//! - `rustls-tls`: Enables support for `rustls-tls` as the underlying tls for all the features. +//! +//! ## Features +//! +//! - `full`: Enables all features documented below. +//! - `cosign`: Enables support for `cosign`. +//! - `cached-client`: Enables an in-memory cache for the cosign registry client. +//! - `fulcio`: Enables support for `fulcio`. +//! - `oauth`: Enables support for `oauth`. +//! - `registry`: Enables support for `registry`. +//! - `rekor`: Enables support for `rekor`. +//! - `sign`: Enables support for signing. +//! - `verify`: Enables support for verification. +//! - `bundle`: Includes both `sign` and `verify`. +//! - `sigstore-trust-root`: Enables support for Sigstore trust root. + +#![forbid(unsafe_code)] +#![warn(clippy::unwrap_used, clippy::panic)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub mod crypto; +pub mod trust; + +#[cfg_attr(docsrs, doc(cfg(feature = "mock-client")))] +#[cfg(feature = "mock-client")] +mod mock_client; + +#[cfg_attr(docsrs, doc(cfg(feature = "cosign")))] +#[cfg(feature = "cosign")] +pub mod cosign; + +pub mod errors; + +#[cfg_attr(docsrs, doc(cfg(feature = "fulcio")))] +#[cfg(feature = "fulcio")] +pub mod fulcio; + +#[cfg_attr(docsrs, doc(cfg(feature = "oauth")))] +#[cfg(feature = "oauth")] +pub mod oauth; + +#[cfg_attr(docsrs, doc(cfg(feature = "registry")))] +#[cfg(feature = "registry")] +pub mod registry; + +#[cfg_attr(docsrs, doc(cfg(feature = "rekor")))] +#[cfg(feature = "rekor")] +pub mod rekor; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "sign", feature = "verify"))))] +#[cfg(any(feature = "sign", feature = "verify"))] +pub mod bundle; diff --git a/vendor/src/mock_client.rs b/vendor/src/mock_client.rs new file mode 100644 index 0000000..90cca3d --- /dev/null +++ b/vendor/src/mock_client.rs @@ -0,0 +1,135 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +pub(crate) mod test { + use crate::errors::{Result, SigstoreError}; + + use async_trait::async_trait; + use oci_client::{ + Reference, + client::{ImageData, PushResponse}, + manifest::OciManifest, + secrets::RegistryAuth, + }; + + #[derive(Default)] + pub struct MockOciClient { + pub fetch_manifest_digest_response: Option>, + pub pull_response: Option>, + pub pull_manifest_response: Option>, + pub push_response: Option>, + } + + impl crate::registry::ClientCapabilitiesDeps for MockOciClient {} + + #[cfg_attr(not(target_arch = "wasm32"), async_trait)] + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] + impl crate::registry::ClientCapabilities for MockOciClient { + async fn fetch_manifest_digest( + &mut self, + image: &Reference, + _auth: &RegistryAuth, + ) -> Result { + let mock_response = self + .fetch_manifest_digest_response + .as_ref() + .ok_or_else(|| SigstoreError::RegistryFetchManifestError { + image: image.whole(), + error: String::from("No fetch_manifest_digest_response provided!"), + })?; + + match mock_response { + Ok(r) => Ok(r.clone()), + Err(e) => Err(SigstoreError::RegistryFetchManifestError { + image: image.whole(), + error: e.to_string(), + }), + } + } + + async fn pull( + &mut self, + image: &Reference, + _auth: &RegistryAuth, + _accepted_media_types: Vec<&str>, + ) -> Result { + let mock_response = + self.pull_response + .as_ref() + .ok_or_else(|| SigstoreError::RegistryPullError { + image: image.whole(), + error: String::from("No pull_response provided!"), + })?; + + match mock_response { + Ok(r) => Ok(r.clone()), + Err(e) => Err(SigstoreError::RegistryPullError { + image: image.whole(), + error: e.to_string(), + }), + } + } + + async fn pull_manifest( + &mut self, + image: &Reference, + _auth: &RegistryAuth, + ) -> Result<(OciManifest, String)> { + let mock_response = self.pull_manifest_response.as_ref().ok_or_else(|| { + SigstoreError::RegistryPullError { + image: image.whole(), + error: String::from("No pull_manifest_response provided!"), + } + })?; + + match mock_response { + Ok(r) => Ok(r.clone()), + Err(e) => Err(SigstoreError::RegistryPullError { + image: image.whole(), + error: e.to_string(), + }), + } + } + + async fn push( + &mut self, + image_ref: &oci_client::Reference, + _layers: &[oci_client::client::ImageLayer], + _config: oci_client::client::Config, + _auth: &oci_client::secrets::RegistryAuth, + _manifest: Option, + ) -> Result { + let mock_response = + self.push_response + .as_ref() + .ok_or_else(|| SigstoreError::RegistryPushError { + image: image_ref.whole(), + error: String::from("No push_response provided!"), + })?; + + match mock_response { + Ok(r) => Ok(PushResponse { + config_url: r.config_url.clone(), + manifest_url: r.manifest_url.clone(), + }), + Err(e) => Err(SigstoreError::RegistryPushError { + image: image_ref.whole(), + error: e.to_string(), + }), + } + } + } +} diff --git a/vendor/src/oauth/mod.rs b/vendor/src/oauth/mod.rs new file mode 100644 index 0000000..b78bd78 --- /dev/null +++ b/vendor/src/oauth/mod.rs @@ -0,0 +1,19 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod openidflow; + +pub mod token; +pub use token::IdentityToken; diff --git a/vendor/src/oauth/openidflow.rs b/vendor/src/oauth/openidflow.rs new file mode 100644 index 0000000..7cce30f --- /dev/null +++ b/vendor/src/oauth/openidflow.rs @@ -0,0 +1,380 @@ +// +// Copyright 2022 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This provides a method for retreiving a OpenID Connect ID Token and scope from the sigstore project. +//! +//! The main entry point is the [`OpenIDAuthorize::auth_url`](OpenIDAuthorize::auth_url) function. +//! This requires four parameters: +//! - `client_id`: the client ID of the application +//! - `client_secret`: the client secret of the application +//! - `issuer`: the URL of the OpenID Connect server +//! - `redirect_uri`: the URL of the callback endpoint +//! +//! The `auth_url` function returns the following: +//! +//! - `authorize_url` is a URL that can be opened in a browser. The user will be +//! prompted to login and authorize the application. The user will be redirected to +//! the `redirect_uri` URL with a code parameter. +//! +//! - `client` is a client object that can be used to make requests to the OpenID +//! Connect server. +//! +//! - `nonce` is a random value that is used to prevent replay attacks. +//! +//! - `pkce_verifier` is a PKCE verifier that can be used to generate the code_verifier +//! value. +//! +//! Once you have recieved the above tuple, you can use the [`RedirectListener::redirect_listener`](RedirectListener::redirect_listener) +//! function to get the ID Token and scope. +//! +//! The `redirect_listener` function requires the following parameters: +//! - `client_redirect_host`: the address for callback. +//! - `client`: the client object +//! - `nonce`: the nonce value +//! - `pkce_verifier`: the PKCE verifier +//! +//! The `IdTokenClaims` this contains params such as `email` and the `access_token`. +//! +//! It maybe prefered to instead develop your own listener. If so bypass using the +//! [`RedirectListener::redirect_listener`](RedirectListener::redirect_listener) function and +//! simply send the values retrieved from the [`OpenIDAuthorize::auth_url`](OpenIDAuthorize::auth_url) +//! to your own listener. +//! +//! +//! **Warning:** one of the dependencies of the [`OpenIDAuthorize::auth_url`](OpenIDAuthorize::auth_url) performs +//! blocking operations. Because of that it can cause panics at runtime if invoked inside of `async` code. +//! If you need to use this function inside of an async code you must wrap it inside of a `spawn_blocking` instruction: +//! +//! ```rust,ignore +//! use tokio::task::spawn_blocking; +//! +//! async fn my_async_function() { +//! // ... your code +//! +//! let oidc_url = spawn_blocking(|| +//! oauth::openidflow::OpenIDAuthorize::new( +//! "sigstore", +//! "", +//! "https://oauth2.sigstore.dev/auth", +//! "http://localhost:8080", +//! ) +//! .auth_url() +//! ) +//! .await +//! .expect("Error spawning blocking task"); +//! +//! // ... your code +//! } +//! ``` +//! This of course has a performance hit when used inside of an async function. + +use std::{ + io::{BufRead, BufReader, Write}, + net::TcpListener, +}; + +use openidconnect::{ + AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, PkceCodeChallenge, + PkceCodeVerifier, RedirectUrl, Scope, + core::{ + CoreAuthenticationFlow, CoreClient, CoreIdToken, CoreIdTokenClaims, CoreIdTokenVerifier, + CoreProviderMetadata, CoreTokenResponse, + }, + reqwest, +}; +use tracing::error; +use url::Url; + +use crate::errors::{Result, SigstoreError}; + +pub(crate) type OpenIdClient = openidconnect::core::CoreClient< + openidconnect::EndpointSet, // HasAuthUrl + openidconnect::EndpointNotSet, // HasDeviceAuthUrl + openidconnect::EndpointNotSet, // HasIntrospectionUrl + openidconnect::EndpointNotSet, // HasRevocationUrl + openidconnect::EndpointMaybeSet, // HasTokenUrl + openidconnect::EndpointMaybeSet, // HasUserInfoUrl +>; + +#[derive(Debug)] +pub struct OpenIDAuthorize { + oidc_cliend_id: String, + oidc_client_secret: String, + oidc_issuer: String, + redirect_url: String, +} + +impl OpenIDAuthorize { + //! Create a new OpenIDAuthorize struct + //! + //! # Arguments + //! + //! * `client_id` - the client ID of the application + //! * `client_secret` - the client secret of the application + //! * `issuer` - the URL of the OpenID Connect server + //! * `redirect_url` - client redirect URL + //! # Example + //! + //! ```rust,ignore + //! use sigstore::oauth::openidflow::OpenIDAuthorize; + //! + //! let oidc = OpenIDAuthorize::new("client_id", "client_secret", "https://example.com", "http://localhost:8080").auth_url(); + //! ``` + pub fn new(client_id: &str, client_secret: &str, issuer: &str, redirect_url: &str) -> Self { + Self { + oidc_cliend_id: client_id.to_string(), + oidc_client_secret: client_secret.to_string(), + oidc_issuer: issuer.to_string(), + redirect_url: redirect_url.to_string(), + } + } + + fn auth_url_internal( + &self, + provider_metadata: CoreProviderMetadata, + ) -> Result<(Url, OpenIdClient, Nonce, PkceCodeVerifier)> { + let client_id = ClientId::new(self.oidc_cliend_id.to_owned()); + let client_secret = ClientSecret::new(self.oidc_client_secret.to_owned()); + + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + + let client = + CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) + .set_redirect_uri( + RedirectUrl::new(self.redirect_url.to_owned()).expect("Invalid redirect URL"), + ); + + let (authorize_url, _, nonce) = client + .authorize_url( + CoreAuthenticationFlow::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .set_pkce_challenge(pkce_challenge) + .url(); + Ok((authorize_url, client, nonce, pkce_verifier)) + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn auth_url(&self) -> Result<(Url, OpenIdClient, Nonce, PkceCodeVerifier)> { + let http_client = reqwest::blocking::ClientBuilder::new() + // Following redirects opens the client up to SSRF vulnerabilities. + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let issuer = IssuerUrl::new(self.oidc_issuer.to_owned()).expect("Missing the OIDC_ISSUER."); + + let provider_metadata = + CoreProviderMetadata::discover(&issuer, &http_client).map_err(|err| { + error!("Error is: {:?}", err); + SigstoreError::ClaimsVerificationError + })?; + + self.auth_url_internal(provider_metadata) + } + + pub async fn auth_url_async(&self) -> Result<(Url, OpenIdClient, Nonce, PkceCodeVerifier)> { + let async_http_client = reqwest::ClientBuilder::new() + // Following redirects opens the client up to SSRF vulnerabilities. + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let issuer = IssuerUrl::new(self.oidc_issuer.to_owned()).expect("Missing the OIDC_ISSUER."); + + let provider_metadata = CoreProviderMetadata::discover_async(issuer, &async_http_client) + .await + .map_err(|_| SigstoreError::ClaimsVerificationError)?; + + self.auth_url_internal(provider_metadata) + } +} + +pub struct RedirectListener { + client_redirect_host: String, + client: OpenIdClient, + nonce: Nonce, + pkce_verifier: PkceCodeVerifier, +} + +impl RedirectListener { + //! Create a new RedirectListener struct + //! + //! # Arguments + //! + //! * `client_redirect_host` - The client callback host IP:PORT + //! * `client` - CoreClient instance (returned from OpenIDAuthorize) + //! * `nonce` - Nonce (returned from OpenIDAuthorize) + //! * `pkce_verifier` - client redirect URL + //! # Example + //! + //! ```rust,ignore + //! use sigstore::oauth::openidflow::RedirectListener; + //! + //! let oidc = RedirectListener::new("127.0.0.1:8080", client, nonce, pkce_verifier).redirect_listener(); + //! ``` + pub fn new( + client_redirect_host: &str, + client: OpenIdClient, + nonce: Nonce, + pkce_verifier: PkceCodeVerifier, + ) -> Self { + Self { + client_redirect_host: client_redirect_host.to_string(), + client, + nonce, + pkce_verifier, + } + } + + fn redirect_listener_internal(&self) -> Result { + let listener = TcpListener::bind(self.client_redirect_host.clone())?; + #[allow(clippy::manual_flatten)] + for stream in listener.incoming() { + if let Ok(mut stream) = stream { + let code; + { + let mut reader = BufReader::new(&stream); + + let mut request_line = String::new(); + reader.read_line(&mut request_line)?; + + let client_redirect_host = request_line + .split_whitespace() + .nth(1) + .ok_or(SigstoreError::RedirectUrlRequestLineError)?; + let url = + Url::parse(format!("http://localhost{client_redirect_host}").as_str())?; + + let code_pair = url + .query_pairs() + .find(|pair| { + let (key, _) = pair; + key == "code" + }) + .ok_or(SigstoreError::CodePairError)?; + + let (_, value) = code_pair; + code = AuthorizationCode::new(value.into_owned()); + } + + let html_page = r#" + Sigstore Auth + +

Sigstore Auth Successful

+

You may now close this page.

+ + "#; + let response = format!( + "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", + html_page.len(), + html_page + ); + stream.write_all(response.as_bytes())?; + + return Ok(code); + } + } + Err(SigstoreError::CodePairError) + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn redirect_listener(self) -> Result<(CoreIdTokenClaims, CoreIdToken)> { + use openidconnect::reqwest::blocking::ClientBuilder; + let http_client = ClientBuilder::new() + // Following redirects opens the client up to SSRF vulnerabilities. + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let code = self.redirect_listener_internal()?; + + let token_response = self + .client + .exchange_code(code)? + .set_pkce_verifier(self.pkce_verifier) + .request(&http_client) + .map_err(|_| SigstoreError::ClaimsAccessPointError)?; + + Self::extract_token_and_claims( + &token_response, + &self.client.id_token_verifier(), + self.nonce, + ) + } + + pub async fn redirect_listener_async(self) -> Result<(CoreIdTokenClaims, CoreIdToken)> { + use openidconnect::reqwest::ClientBuilder; + let async_http_client = ClientBuilder::new() + // Following redirects opens the client up to SSRF vulnerabilities. + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let code = self.redirect_listener_internal()?; + + let token_response = self + .client + .exchange_code(code)? + .set_pkce_verifier(self.pkce_verifier) + .request_async(&async_http_client) + .await + .map_err(|_| SigstoreError::ClaimsAccessPointError)?; + + Self::extract_token_and_claims( + &token_response, + &self.client.id_token_verifier(), + self.nonce, + ) + } + + fn extract_token_and_claims( + token_response: &CoreTokenResponse, + id_token_verifier: &CoreIdTokenVerifier, + nonce: Nonce, + ) -> Result<(CoreIdTokenClaims, CoreIdToken)> { + let id_token = token_response + .extra_fields() + .id_token() + .ok_or(SigstoreError::NoIDToken)?; + + let id_token_claims: &CoreIdTokenClaims = token_response + .extra_fields() + .id_token() + .expect("Server did not return an ID token") + .claims(id_token_verifier, &nonce) + .map_err(|_| SigstoreError::ClaimsVerificationError)?; + Ok((id_token_claims.clone(), id_token.clone())) + } +} + +#[test] +fn test_auth_url() { + let oidc_url = OpenIDAuthorize::new( + "sigstore", + "some_secret", + "https://oauth2.sigstore.dev/auth", + "http://localhost:8080", + ) + .auth_url(); + let oidc_url = oidc_url.unwrap(); + assert!( + oidc_url + .0 + .to_string() + .contains("https://oauth2.sigstore.dev/auth") + ); + assert!(oidc_url.0.to_string().contains("response_type=code")); + assert!(oidc_url.0.to_string().contains("client_id=sigstore")); + assert!(oidc_url.0.to_string().contains("scope=openid+email")); +} diff --git a/vendor/src/oauth/token.rs b/vendor/src/oauth/token.rs new file mode 100644 index 0000000..354ee34 --- /dev/null +++ b/vendor/src/oauth/token.rs @@ -0,0 +1,184 @@ +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Utc}; +use openidconnect::core::CoreIdToken; +use serde::Deserialize; +use serde_json::Value; + +use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD as base64}; + +use crate::errors::SigstoreError; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum Audience { + Single(String), + Multiple(Vec), +} + +impl Audience { + fn contains_sigstore(&self) -> bool { + match self { + Audience::Single(aud) => aud == "sigstore", + Audience::Multiple(list) => list.iter().any(|aud| aud == "sigstore"), + } + } +} + +/// Flexible timestamp deserializer that accepts seconds, RFC3339, or null. +mod flexible_timestamp { + use chrono::{DateTime, Utc}; + use serde::{Deserialize, Deserializer}; + + pub mod option { + use super::*; + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Timestamp { + Seconds(i64), + String(String), + Null, + } + + match Option::::deserialize(deserializer)? { + None | Some(Timestamp::Null) => Ok(None), + Some(Timestamp::Seconds(secs)) => Ok(DateTime::::from_timestamp(secs, 0)), + Some(Timestamp::String(s)) => DateTime::parse_from_rfc3339(&s) + .map(|dt| dt.with_timezone(&Utc)) + .map(Some) + .map_err(serde::de::Error::custom), + } + } + } +} + +#[derive(Deserialize)] +pub struct Claims { + #[serde(default)] + pub aud: Option, + #[serde(default, deserialize_with = "flexible_timestamp::option::deserialize")] + pub exp: Option>, + #[serde(default, deserialize_with = "flexible_timestamp::option::deserialize")] + pub nbf: Option>, + #[serde(default)] + pub email: Option, + #[serde(default)] + pub raw: Value, +} + +pub type UnverifiedClaims = Claims; + +/// A Sigstore token. +pub struct IdentityToken { + original_token: String, + claims: UnverifiedClaims, +} + +impl IdentityToken { + /// Returns the **unverified** claim set for the token. + /// + /// The [UnverifiedClaims] returned from this method should not be used to enforce security + /// invariants. + pub fn unverified_claims(&self) -> &UnverifiedClaims { + &self.claims + } + + /// Returns whether or not this token is within its self-stated validity period. + pub fn in_validity_period(&self) -> bool { + let now = Utc::now(); + + if let Some(nbf) = self.claims.nbf { + if now < nbf { + return false; + } + } + + if let Some(exp) = self.claims.exp { + now < exp + } else { + true + } + } + + /// Returns whether the `aud` claim includes "sigstore". + pub fn has_sigstore_audience(&self) -> bool { + match &self.claims.aud { + Some(aud) => aud.contains_sigstore(), + None => false, + } + } +} + +impl TryFrom<&str> for IdentityToken { + type Error = SigstoreError; + + fn try_from(value: &str) -> Result { + let parts: [&str; 3] = value.split('.').collect::>().try_into().or(Err( + SigstoreError::IdentityTokenError("Malformed JWT".into()), + ))?; + + let claims_bytes = base64 + .decode(parts[1]) + .or(Err(SigstoreError::IdentityTokenError( + "Malformed JWT: Unable to decode claims".into(), + )))?; + + let claims_str = String::from_utf8_lossy(&claims_bytes); + tracing::debug!("JWT claims payload (raw): {}", claims_str); + + let claims: Claims = serde_json::from_slice(&claims_bytes).or_else(|e| { + tracing::error!("Failed to parse claims: {}", e); + tracing::error!("Claims JSON: {}", claims_str); + Err(SigstoreError::IdentityTokenError(format!( + "Malformed JWT: claims JSON malformed - {}", + e + ))) + })?; + + if let Some(aud) = &claims.aud { + if !aud.contains_sigstore() { + return Err(SigstoreError::IdentityTokenError( + "Not a Sigstore JWT".into(), + )); + } + } + + Ok(IdentityToken { + original_token: value.to_owned(), + claims, + }) + } +} + +impl From for IdentityToken { + fn from(value: CoreIdToken) -> Self { + value + .to_string() + .as_str() + .try_into() + .expect("Token conversion failed") + } +} + +impl std::fmt::Display for IdentityToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.original_token.clone()) + } +} diff --git a/vendor/src/registry/config.rs b/vendor/src/registry/config.rs new file mode 100644 index 0000000..18811ab --- /dev/null +++ b/vendor/src/registry/config.rs @@ -0,0 +1,241 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Set of structs and enums used to define how to interact with OCI registries + +use pki_types::CertificateDer; +use serde::Serialize; +use std::cmp::Ordering; + +use crate::errors; + +/// A method for authenticating to a registry +#[derive(Serialize, Debug)] +pub enum Auth { + /// Access the registry anonymously + Anonymous, + /// Access the registry using HTTP Basic authentication + Basic(String, String), + /// Access the registry using a bearer token + Bearer(String), +} + +impl From<&Auth> for oci_client::secrets::RegistryAuth { + fn from(auth: &Auth) -> Self { + match auth { + Auth::Anonymous => oci_client::secrets::RegistryAuth::Anonymous, + Auth::Basic(username, pass) => { + oci_client::secrets::RegistryAuth::Basic(username.clone(), pass.clone()) + } + Auth::Bearer(token) => oci_client::secrets::RegistryAuth::Bearer(token.clone()), + } + } +} + +impl From<&oci_client::secrets::RegistryAuth> for Auth { + fn from(auth: &oci_client::secrets::RegistryAuth) -> Self { + match auth { + oci_client::secrets::RegistryAuth::Anonymous => Auth::Anonymous, + oci_client::secrets::RegistryAuth::Basic(username, pass) => { + Auth::Basic(username.clone(), pass.clone()) + } + oci_client::secrets::RegistryAuth::Bearer(token) => Auth::Bearer(token.clone()), + } + } +} + +/// The protocol that the client should use to connect +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum ClientProtocol { + #[allow(missing_docs)] + Http, + #[allow(missing_docs)] + #[default] + Https, + #[allow(missing_docs)] + HttpsExcept(Vec), +} + +impl From for oci_client::client::ClientProtocol { + fn from(cp: ClientProtocol) -> Self { + match cp { + ClientProtocol::Http => oci_client::client::ClientProtocol::Http, + ClientProtocol::Https => oci_client::client::ClientProtocol::Https, + ClientProtocol::HttpsExcept(exceptions) => { + oci_client::client::ClientProtocol::HttpsExcept(exceptions) + } + } + } +} + +/// The encoding of the certificate +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CertificateEncoding { + #[allow(missing_docs)] + Der, + #[allow(missing_docs)] + Pem, +} + +impl From for oci_client::client::CertificateEncoding { + fn from(ce: CertificateEncoding) -> Self { + match ce { + CertificateEncoding::Der => oci_client::client::CertificateEncoding::Der, + CertificateEncoding::Pem => oci_client::client::CertificateEncoding::Pem, + } + } +} + +/// A x509 certificate +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Certificate { + /// Which encoding is used by the certificate + pub encoding: CertificateEncoding, + + /// Actual certificate + pub data: Vec, +} + +impl Ord for Certificate { + fn cmp(&self, other: &Self) -> Ordering { + self.data.cmp(&other.data) + } +} + +impl PartialOrd for Certificate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From<&Certificate> for oci_client::client::Certificate { + fn from(cert: &Certificate) -> Self { + oci_client::client::Certificate { + encoding: cert.encoding.clone().into(), + data: cert.data.clone(), + } + } +} + +impl<'a> TryFrom for CertificateDer<'a> { + type Error = errors::SigstoreError; + fn try_from(value: Certificate) -> errors::Result> { + #[inline] + fn to_der(pem: &[u8]) -> errors::Result> { + Ok(pem::parse(pem)?.into_contents()) + } + + match &value.encoding { + CertificateEncoding::Der => Ok(CertificateDer::from(value.data)), + CertificateEncoding::Pem => Ok(CertificateDer::from(to_der(&value.data)?)), + } + } +} + +/// A client configuration +#[derive(Debug, Clone)] +pub struct ClientConfig { + /// Which protocol the client should use + pub protocol: ClientProtocol, + + /// Accept invalid hostname. Defaults to false + #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] + #[cfg(feature = "native-tls")] + pub accept_invalid_hostnames: bool, + + /// Accept invalid certificates. Defaults to false + pub accept_invalid_certificates: bool, + + /// A list of extra root certificate to trust. This can be used to connect + /// to servers using self-signed certificates + pub extra_root_certificates: Vec, + + /// Set the `HTTPS PROXY` used by the client. + /// + /// This defaults to `None`. + pub https_proxy: Option, + + /// Set the `HTTP PROXY` used by the client. + /// + /// This defaults to `None`. + pub http_proxy: Option, + + /// Set the `NO PROXY` used by the client. + /// + /// This defaults to `None`. + pub no_proxy: Option, +} + +impl Default for ClientConfig { + fn default() -> Self { + ClientConfig { + protocol: ClientProtocol::Https, + #[cfg(feature = "native-tls")] + accept_invalid_hostnames: false, + accept_invalid_certificates: false, + extra_root_certificates: Vec::new(), + https_proxy: None, + http_proxy: None, + no_proxy: None, + } + } +} + +impl From for oci_client::client::ClientConfig { + fn from(config: ClientConfig) -> Self { + oci_client::client::ClientConfig { + protocol: config.protocol.into(), + accept_invalid_certificates: config.accept_invalid_certificates, + #[cfg(feature = "native-tls")] + accept_invalid_hostnames: config.accept_invalid_hostnames, + extra_root_certificates: config + .extra_root_certificates + .iter() + .map(|c| c.into()) + .collect(), + https_proxy: config.https_proxy, + http_proxy: config.http_proxy, + no_proxy: config.no_proxy, + ..Default::default() + } + } +} + +/// A client configuration +#[derive(Debug, Clone)] +pub struct PushResponse { + /// Pullable url for the config. + pub config_url: String, + /// Pullable url for the manifest. + pub manifest_url: String, +} + +impl From for oci_client::client::PushResponse { + fn from(pr: PushResponse) -> Self { + oci_client::client::PushResponse { + config_url: pr.config_url, + manifest_url: pr.manifest_url, + } + } +} + +impl From for PushResponse { + fn from(pr: oci_client::client::PushResponse) -> Self { + PushResponse { + config_url: pr.config_url, + manifest_url: pr.manifest_url, + } + } +} diff --git a/vendor/src/registry/mod.rs b/vendor/src/registry/mod.rs new file mode 100644 index 0000000..10461cf --- /dev/null +++ b/vendor/src/registry/mod.rs @@ -0,0 +1,91 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod config; +pub use config::*; + +#[cfg(feature = "cosign")] +pub(crate) mod oci_client; +#[cfg(feature = "cosign")] +pub(crate) use oci_client::*; + +#[cfg(feature = "cosign")] +pub mod oci_reference; +#[cfg(feature = "cosign")] +pub use oci_reference::OciReference; + +#[cfg(all(feature = "cosign", feature = "cached-client"))] +pub(crate) mod oci_caching_client; +#[cfg(all(feature = "cosign", feature = "cached-client"))] +pub(crate) use oci_caching_client::*; + +use crate::errors::Result; + +use async_trait::async_trait; + +use ::oci_client as oci_client_dep; + +/// Workaround to ensure the `Send + Sync` supertraits are +/// required by ClientCapabilities only when the target +/// architecture is NOT wasm32. +/// +/// This intermediate trait has been created to avoid +/// to define ClientCapabilities twice (one with `#[cfg(target_arch = "wasm32")]`, +/// the other with `#[cfg(not(target_arch = "wasm32"))]` +#[cfg(not(target_arch = "wasm32"))] +pub(crate) trait ClientCapabilitiesDeps: Send + Sync {} + +/// Workaround to ensure the `Send + Sync` supertraits are +/// required by ClientCapabilities only when the target +/// architecture is NOT wasm32. +/// +/// This intermediate trait has been created to avoid +/// to define ClientCapabilities twice (one with `#[cfg(target_arch = "wasm32")]`, +/// the other with `#[cfg(not(target_arch = "wasm32"))]` +#[cfg(target_arch = "wasm32")] +pub(crate) trait ClientCapabilitiesDeps {} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +/// Capabilities that are expected to be provided by a registry client +pub(crate) trait ClientCapabilities: ClientCapabilitiesDeps { + async fn fetch_manifest_digest( + &mut self, + image: &oci_client_dep::Reference, + auth: &oci_client_dep::secrets::RegistryAuth, + ) -> Result; + + async fn pull( + &mut self, + image: &oci_client_dep::Reference, + auth: &oci_client_dep::secrets::RegistryAuth, + accepted_media_types: Vec<&str>, + ) -> Result; + + async fn pull_manifest( + &mut self, + image: &oci_client_dep::Reference, + auth: &oci_client_dep::secrets::RegistryAuth, + ) -> Result<(oci_client_dep::manifest::OciManifest, String)>; + + async fn push( + &mut self, + image_ref: &oci_client_dep::Reference, + layers: &[oci_client_dep::client::ImageLayer], + config: oci_client_dep::client::Config, + auth: &oci_client_dep::secrets::RegistryAuth, + manifest: Option, + ) -> Result; +} diff --git a/vendor/src/registry/oci_caching_client.rs b/vendor/src/registry/oci_caching_client.rs new file mode 100644 index 0000000..0cf275a --- /dev/null +++ b/vendor/src/registry/oci_caching_client.rs @@ -0,0 +1,322 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::time::Duration; + +use super::{ClientCapabilities, ClientCapabilitiesDeps}; +use crate::errors::{Result, SigstoreError}; + +use async_trait::async_trait; +use cached::proc_macro::cached; +use olpc_cjson::CanonicalFormatter; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use tracing::{debug, error}; + +/// Internal client for an OCI Registry. This performs actual +/// calls against the remote registry and caches the results +/// for 60 seconds. +/// +/// For testing purposes, use instead the client inside of the +/// `mock_client` module. +pub(crate) struct OciCachingClient { + pub registry_client: oci_client::Client, +} + +#[cached( + time = 60, + result = true, + sync_writes = "default", + key = "String", + convert = r#"{ format!("{}", image) }"#, + with_cached_flag = true +)] +async fn fetch_manifest_digest_cached( + client: &mut oci_client::Client, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, +) -> Result> { + client + .fetch_manifest_digest(image, auth) + .await + .map_err(|e| SigstoreError::RegistryFetchManifestError { + image: image.whole(), + error: e.to_string(), + }) + .map(cached::Return::new) +} + +/// Internal struct, used to calculate a unique hash of the pull +/// settings. This is required to cache pull results. +#[derive(Serialize, Debug)] +struct PullSettings<'a> { + image: String, + auth: super::config::Auth, + pub accepted_media_types: Vec<&'a str>, +} + +impl<'a> PullSettings<'a> { + fn new( + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + accepted_media_types: Vec<&'a str>, + ) -> PullSettings<'a> { + let image_str = image.whole(); + let auth_sigstore: super::config::Auth = From::from(auth); + + PullSettings { + image: image_str, + auth: auth_sigstore, + accepted_media_types, + } + } + + #[allow(clippy::unwrap_used)] + pub fn image(&self) -> oci_client::Reference { + // we can use `unwrap` here, because this will never fail + let reference: oci_client::Reference = self.image.parse().unwrap(); + reference + } + + pub fn auth(&self) -> oci_client::secrets::RegistryAuth { + let internal_auth: &super::config::Auth = &self.auth; + let a: oci_client::secrets::RegistryAuth = internal_auth.into(); + a + } + + // This function returns a hash of the PullSettings struct. + // The has is computed by doing a canonical JSON representation of + // the struct. + // + // This method cannot error, because its value is used by the `cached` + // macro, which doesn't allow error handling. + // Because of that the method will return the '0' value when something goes + // wrong during the serialization operation. This is very unlikely to happen + pub fn hash(&self) -> String { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new()); + if let Err(e) = self.serialize(&mut ser) { + error!(err=?e, settings=?self, "Cannot perform canonical serialization"); + return "0".to_string(); + } + + let mut hasher = Sha256::new(); + hasher.update(&buf); + let result = hasher.finalize(); + result + .iter() + .map(|v| format!("{v:x}")) + .collect::>() + .join("") + } +} + +// Pulls an OCI artifact. +// Details about this cache: +// * the cache is time bound: cached values are purged after 60 seconds +// * only successful results are cached +#[cached( + time = 60, + result = true, + sync_writes = "default", + key = "String", + convert = r#"{ settings.hash() }"#, + with_cached_flag = true +)] +async fn pull_cached( + client: &mut oci_client::Client, + settings: PullSettings<'_>, +) -> Result> { + let auth = settings.auth(); + let image = settings.image(); + + client + .pull(&image, &auth, settings.accepted_media_types) + .await + .map_err(|e| SigstoreError::RegistryPullError { + image: image.whole(), + error: e.to_string(), + }) + .map(cached::Return::new) +} + +/// Internal struct, used to calculate a unique hash of the pull manifest +/// settings. This is required to cache pull manifest results. +#[derive(Serialize, Debug)] +struct PullManifestSettings { + image: String, + auth: super::config::Auth, +} + +impl PullManifestSettings { + fn new( + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + ) -> PullManifestSettings { + let image_str = image.whole(); + let auth_sigstore: super::config::Auth = From::from(auth); + + PullManifestSettings { + image: image_str, + auth: auth_sigstore, + } + } + + #[allow(clippy::unwrap_used)] + pub fn image(&self) -> oci_client::Reference { + // we can use `unwrap` here, because this will never fail + let reference: oci_client::Reference = self.image.parse().unwrap(); + reference + } + + pub fn auth(&self) -> oci_client::secrets::RegistryAuth { + let internal_auth: &super::config::Auth = &self.auth; + let a: oci_client::secrets::RegistryAuth = internal_auth.into(); + a + } + + // This function returns a hash of the PullManifestSettings struct. + // The has is computed by doing a canonical JSON representation of + // the struct. + // + // This method cannot error, because its value is used by the `cached` + // macro, which doesn't allow error handling. + // Because of that the method will return the '0' value when something goes + // wrong during the serialization operation. This is very unlikely to happen + pub fn hash(&self) -> String { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new()); + if let Err(e) = self.serialize(&mut ser) { + error!(err=?e, settings=?self, "Cannot perform canonical serialization"); + return "0".to_string(); + } + + let mut hasher = Sha256::new(); + hasher.update(&buf); + let result = hasher.finalize(); + result + .iter() + .map(|v| format!("{v:x}")) + .collect::>() + .join("") + } +} + +// Pulls an OCI manifest. +// Details about this cache: +// * the cache is time bound: cached values are purged after 60 seconds +// * only successful results are cached +#[cached( + time = 60, + result = true, + sync_writes = "default", + key = "String", + convert = r#"{ settings.hash() }"#, + with_cached_flag = true +)] +async fn pull_manifest_cached( + client: &mut oci_client::Client, + settings: PullManifestSettings, +) -> Result> { + let image = settings.image(); + let auth = settings.auth(); + client + .pull_manifest(&image, &auth) + .await + .map_err(|e| SigstoreError::RegistryPullManifestError { + image: image.whole(), + error: e.to_string(), + }) + .map(cached::Return::new) +} + +impl ClientCapabilitiesDeps for OciCachingClient {} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl ClientCapabilities for OciCachingClient { + async fn fetch_manifest_digest( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + ) -> Result { + fetch_manifest_digest_cached(&mut self.registry_client, image, auth) + .await + .map(|digest| { + if digest.was_cached { + debug!(?image, "Got image digest from cache"); + } else { + debug!(?image, "Got image digest by querying remote registry"); + } + digest.value + }) + } + + async fn pull( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + accepted_media_types: Vec<&str>, + ) -> Result { + let pull_settings = PullSettings::new(image, auth, accepted_media_types); + + pull_cached(&mut self.registry_client, pull_settings) + .await + .map(|data| { + if data.was_cached { + debug!(?image, "Got image data from cache"); + } else { + debug!(?image, "Got image data by querying remote registry"); + } + data.value + }) + } + + async fn pull_manifest( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + ) -> Result<(oci_client::manifest::OciManifest, String)> { + let pull_manifest_settings = PullManifestSettings::new(image, auth); + + pull_manifest_cached(&mut self.registry_client, pull_manifest_settings) + .await + .map(|data| { + if data.was_cached { + debug!(?image, "Got image manifest from cache"); + } else { + debug!(?image, "Got image manifest by querying remote registry"); + } + data.value + }) + } + + async fn push( + &mut self, + image_ref: &oci_client::Reference, + layers: &[oci_client::client::ImageLayer], + config: oci_client::client::Config, + auth: &oci_client::secrets::RegistryAuth, + manifest: Option, + ) -> Result { + self.registry_client + .push(image_ref, layers, config, auth, manifest) + .await + .map_err(|e| SigstoreError::RegistryPushError { + image: image_ref.whole(), + error: e.to_string(), + }) + } +} diff --git a/vendor/src/registry/oci_client.rs b/vendor/src/registry/oci_client.rs new file mode 100644 index 0000000..aaf6bda --- /dev/null +++ b/vendor/src/registry/oci_client.rs @@ -0,0 +1,94 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ClientCapabilities, ClientCapabilitiesDeps}; +use crate::errors::{Result, SigstoreError}; + +use async_trait::async_trait; + +/// Internal client for an OCI Registry. This performs actual +/// calls against the remote registry.OciClient +/// +/// For testing purposes, use instead the client inside of the +/// `mock_client` module. +pub(crate) struct OciClient { + pub registry_client: oci_client::Client, +} + +impl ClientCapabilitiesDeps for OciClient {} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl ClientCapabilities for OciClient { + async fn fetch_manifest_digest( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + ) -> Result { + self.registry_client + .fetch_manifest_digest(image, auth) + .await + .map_err(|e| SigstoreError::RegistryFetchManifestError { + image: image.whole(), + error: e.to_string(), + }) + } + + async fn pull( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + accepted_media_types: Vec<&str>, + ) -> Result { + self.registry_client + .pull(image, auth, accepted_media_types) + .await + .map_err(|e| SigstoreError::RegistryPullError { + image: image.whole(), + error: e.to_string(), + }) + } + + async fn pull_manifest( + &mut self, + image: &oci_client::Reference, + auth: &oci_client::secrets::RegistryAuth, + ) -> Result<(oci_client::manifest::OciManifest, String)> { + self.registry_client + .pull_manifest(image, auth) + .await + .map_err(|e| SigstoreError::RegistryPullManifestError { + image: image.whole(), + error: e.to_string(), + }) + } + + async fn push( + &mut self, + image_ref: &oci_client::Reference, + layers: &[oci_client::client::ImageLayer], + config: oci_client::client::Config, + auth: &oci_client::secrets::RegistryAuth, + manifest: Option, + ) -> Result { + self.registry_client + .push(image_ref, layers, config, auth, manifest) + .await + .map_err(|e| SigstoreError::RegistryPushError { + image: image_ref.whole(), + error: e.to_string(), + }) + } +} diff --git a/vendor/src/registry/oci_reference.rs b/vendor/src/registry/oci_reference.rs new file mode 100644 index 0000000..47573ec --- /dev/null +++ b/vendor/src/registry/oci_reference.rs @@ -0,0 +1,91 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::errors::SigstoreError; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +/// `OciReference` provides a general type to represent any way of referencing images within an OCI registry. +#[derive(Debug, Clone, PartialEq)] +pub struct OciReference { + pub(crate) oci_reference: oci_client::Reference, +} + +impl FromStr for OciReference { + type Err = SigstoreError; + + fn from_str(s: &str) -> Result { + s.parse::() + .map_err(|_| SigstoreError::OciReferenceNotValidError { + reference: s.to_string(), + }) + .map(|oci_reference| OciReference { oci_reference }) + } +} + +impl OciReference { + /// Create a Reference with a registry, repository and tag. + pub fn with_tag(registry: String, repository: String, tag: String) -> Self { + OciReference { + oci_reference: oci_client::Reference::with_tag(registry, repository, tag), + } + } + + /// Create a Reference with a registry, repository and digest. + pub fn with_digest(registry: String, repository: String, digest: String) -> Self { + OciReference { + oci_reference: oci_client::Reference::with_digest(registry, repository, digest), + } + } + + /// Resolve the registry address of a given Reference. + /// + /// Some registries, such as docker.io, uses a different address for the actual + /// registry. This function implements such redirection. + pub fn resolve_registry(&self) -> &str { + self.oci_reference.resolve_registry() + } + + /// registry returns the name of the registry. + pub fn registry(&self) -> &str { + self.oci_reference.registry() + } + + /// repository returns the name of the repository + pub fn repository(&self) -> &str { + self.oci_reference.repository() + } + + /// digest returns the object's digest, if present. + pub fn digest(&self) -> Option<&str> { + self.oci_reference.digest() + } + + /// tag returns the object's tag, if present. + pub fn tag(&self) -> Option<&str> { + self.oci_reference.tag() + } + + /// whole returns the whole reference. + pub fn whole(&self) -> String { + self.oci_reference.whole() + } +} + +impl Display for OciReference { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.oci_reference.fmt(f) + } +} diff --git a/vendor/src/rekor/apis/configuration.rs b/vendor/src/rekor/apis/configuration.rs new file mode 100644 index 0000000..a878be3 --- /dev/null +++ b/vendor/src/rekor/apis/configuration.rs @@ -0,0 +1,51 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: reqwest::Client, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, + // TODO: take an oauth2 token source, similar to the go one +} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "https://rekor.sigstore.dev".to_owned(), + user_agent: Some(format!("sigstore-rs/{}", VERSION.unwrap_or("unknown"))), + client: reqwest::Client::new(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + } + } +} diff --git a/vendor/src/rekor/apis/entries_api.rs b/vendor/src/rekor/apis/entries_api.rs new file mode 100644 index 0000000..ddce67b --- /dev/null +++ b/vendor/src/rekor/apis/entries_api.rs @@ -0,0 +1,220 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{Error, configuration}; +use crate::rekor::apis::ResponseContent; +use crate::rekor::models::log_entry::LogEntry; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +/// struct for typed errors of method [`create_log_entry`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CreateLogEntryError { + Status400(crate::rekor::models::Error), + Status409(crate::rekor::models::Error), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_log_entry_by_index`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetLogEntryByIndexError { + Status404(), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_log_entry_by_uuid`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetLogEntryByUuidError { + Status404(), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`search_log_query`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SearchLogQueryError { + Status400(crate::rekor::models::Error), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LogEntries { + entries: Vec, +} + +// TEMPORARY: Formats the returned response such that it can be read into a struct +// TODO: Remove once upstream issue around dynamic top level key is resolved: +// https://github.com/sigstore/rekor/issues/808 +pub fn parse_response(local_var_content: String) -> String { + let uuid: &str = &local_var_content[1..82]; + let rest: &str = &local_var_content[85..local_var_content.len() - 2]; + + "{\"uuid\":".to_string() + uuid + "\"," + rest +} + +/// Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified. +// Change the return value of the function to LogEntry from ::std::collections::HashMap +pub async fn create_log_entry( + configuration: &configuration::Configuration, + proposed_entry: crate::rekor::models::ProposedEntry, +) -> Result> { + let local_var_client = &configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log/entries", configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + local_var_req_builder = local_var_req_builder.json(&proposed_entry); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + LogEntry::from_str(&(parse_response(local_var_content))).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +/// Fetches the specified entry from the transparency log using the log index +pub async fn get_log_entry_by_index( + configuration: &configuration::Configuration, + log_index: i32, +) -> Result> { + let local_var_client = &configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log/entries", configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + local_var_req_builder = local_var_req_builder.query(&[("logIndex", &log_index.to_string())]); + if let Some(ref local_var_user_agent) = configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + LogEntry::from_str(&(parse_response(local_var_content))).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +/// Returns the entry, root hash, tree size, and a list of hashes that can be used to calculate proof of an entry being included in the transparency log +pub async fn get_log_entry_by_uuid( + configuration: &configuration::Configuration, + entry_uuid: &str, +) -> Result> { + let local_var_client = &configuration.client; + + let local_var_uri_str = format!( + "{}/api/v1/log/entries/{entryUUID}", + configuration.base_path, + entryUUID = crate::rekor::apis::urlencode(entry_uuid) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + LogEntry::from_str(&(parse_response(local_var_content))).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +// Returns the vector of Log Entries as a String +pub async fn search_log_query( + configuration: &configuration::Configuration, + entry: crate::rekor::models::SearchLogQuery, +) -> Result> { + let local_var_client = &configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log/entries/retrieve", configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + local_var_req_builder = local_var_req_builder.json(&entry); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(local_var_content) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/vendor/src/rekor/apis/index_api.rs b/vendor/src/rekor/apis/index_api.rs new file mode 100644 index 0000000..87d3ba7 --- /dev/null +++ b/vendor/src/rekor/apis/index_api.rs @@ -0,0 +1,62 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{Error, configuration}; +use crate::rekor::apis::ResponseContent; +use serde::{Deserialize, Serialize}; + +/// struct for typed errors of method [`search_index`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SearchIndexError { + Status400(crate::rekor::models::Error), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +pub async fn search_index( + configuration: &configuration::Configuration, + query: crate::rekor::models::SearchIndex, +) -> Result, Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/api/v1/index/retrieve", + local_var_configuration.base_path + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + local_var_req_builder = local_var_req_builder.json(&query); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/vendor/src/rekor/apis/mod.rs b/vendor/src/rekor/apis/mod.rs new file mode 100644 index 0000000..fb1bcb3 --- /dev/null +++ b/vendor/src/rekor/apis/mod.rs @@ -0,0 +1,47 @@ +use thiserror::Error; + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("error in reqwest: {source:?}")] + Reqwest { + #[from] + source: reqwest::Error, + }, + + #[error("error in serde: {source:?}")] + Serde { + #[from] + source: serde_json::Error, + }, + + #[error("error in IO: {source:?}")] + Io { + #[from] + source: std::io::Error, + }, + + #[error("error in response: status code {:?}", error_status(.0))] + ResponseError(ResponseContent), +} + +#[inline] +fn error_status(response: &ResponseContent) -> reqwest::StatusCode { + response.status +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub mod configuration; +pub mod entries_api; +pub mod index_api; +pub mod pubkey_api; +pub mod tlog_api; diff --git a/vendor/src/rekor/apis/pubkey_api.rs b/vendor/src/rekor/apis/pubkey_api.rs new file mode 100644 index 0000000..fc5b037 --- /dev/null +++ b/vendor/src/rekor/apis/pubkey_api.rs @@ -0,0 +1,62 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{Error, configuration}; +use crate::rekor::apis::ResponseContent; +use serde::{Deserialize, Serialize}; + +/// struct for typed errors of method [`get_public_key`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetPublicKeyError { + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// Returns the public key that can be used to validate the signed tree head +pub async fn get_public_key( + configuration: &configuration::Configuration, + tree_id: Option<&str>, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log/publicKey", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_str) = tree_id { + local_var_req_builder = + local_var_req_builder.query(&[("treeID", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(local_var_content) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/vendor/src/rekor/apis/tlog_api.rs b/vendor/src/rekor/apis/tlog_api.rs new file mode 100644 index 0000000..fa1017a --- /dev/null +++ b/vendor/src/rekor/apis/tlog_api.rs @@ -0,0 +1,116 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{Error, configuration}; +use crate::rekor::apis::ResponseContent; +use serde::{Deserialize, Serialize}; + +/// struct for typed errors of method [`get_log_info`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetLogInfoError { + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_log_proof`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetLogProofError { + Status400(crate::rekor::models::Error), + DefaultResponse(crate::rekor::models::Error), + UnknownValue(serde_json::Value), +} + +/// Returns the current root hash and size of the merkle tree used to store the log entries. +pub async fn get_log_info( + configuration: &configuration::Configuration, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +/// Returns a list of hashes for specified tree sizes that can be used to confirm the consistency of the transparency log +pub async fn get_log_proof( + configuration: &configuration::Configuration, + last_size: i32, + first_size: Option<&str>, + tree_id: Option<&str>, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/api/v1/log/proof", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_str) = first_size { + local_var_req_builder = + local_var_req_builder.query(&[("firstSize", &local_var_str.to_string())]); + } + local_var_req_builder = local_var_req_builder.query(&[("lastSize", &last_size.to_string())]); + if let Some(ref local_var_str) = tree_id { + local_var_req_builder = + local_var_req_builder.query(&[("treeID", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/vendor/src/rekor/mod.rs b/vendor/src/rekor/mod.rs new file mode 100644 index 0000000..74397fe --- /dev/null +++ b/vendor/src/rekor/mod.rs @@ -0,0 +1,91 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This crate aims to provide Rust API client for Rekor to Rust developers. +//! +//! Rekor is a cryptographically secure, immutable transparency log for signed software releases. +//! +//! **Warning:** this crate is still experimental. Its API can change at any time. +//! +//! # Security +//! +//! Should you discover any security issues, please refer to +//! Sigstore's [security process](https://github.com/sigstore/community/blob/main/SECURITY.md). +//! +//! # How to use this crate +//! The examples folder contains code that shows users how to make API calls. +//! It also provides a clean interface with step-by-step instructions that other developers can copy and paste. +//! +//! ``` +//! use clap::{Arg, Command}; +//! use sigstore::rekor::apis::{configuration::Configuration, entries_api}; +//! use sigstore::rekor::models::log_entry::LogEntry; +//! use std::str::FromStr; +//! #[tokio::main] +//! async fn main() { +//! /* +//! Retrieves an entry and inclusion proof from the transparency log (if it exists) by index +//! Example command : +//! cargo run --example get_log_entry_by_index -- --log_index 99 +//! */ +//! let matches = Command::new("cmd").arg( +//! Arg::new("log_index") +//! .long("log_index") +//! .num_args(1) +//! .value_parser(clap::value_parser!(i32)) +//! .default_value("1") +//! .help("log_index of the artifact"), +//! ); +//! +//! let flags = matches.get_matches(); +//! let index: &i32 = flags.get_one("log_index").unwrap(); +//! +//! let configuration = Configuration::default(); +//! +//! let message: LogEntry = entries_api::get_log_entry_by_index(&configuration, *index) +//! .await +//! .unwrap(); +//! println!("{:#?}", message); +//! } +//! ``` +//! +//! The following comment in the code tells the user how to provide the required values to the API calls using cli flags. +//! +//! In the example below, the user can retrieve different entries by inputting a different value for the log_index flag. +//! +//! +//!/* +//!Retrieves an entry and inclusion proof from the transparency log (if it exists) by index +//!Example command : +//!cargo run --example get_log_entry_by_index -- --log_index 99 +//!*/ +//! +//! # The example code is provided for the following API calls: +//! +//!- create_log_entry +//!- get_log_entry_by_index +//!- get_log_entry_by_uuid +//!- get_log_info +//!- get_log_proof +//!- get_public_key +//!- get_timestamp_cert_chain +//!- get_timestamp_response +//!- search_index +//!- search_log_query +//! + +pub mod apis; +pub mod models; +type TreeSize = i64; diff --git a/vendor/src/rekor/models/alpine.rs b/vendor/src/rekor/models/alpine.rs new file mode 100644 index 0000000..a9fca26 --- /dev/null +++ b/vendor/src/rekor/models/alpine.rs @@ -0,0 +1,33 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Alpine : Alpine package +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Alpine { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Alpine { + /// Alpine package + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Alpine { + Alpine { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/alpine_all_of.rs b/vendor/src/rekor/models/alpine_all_of.rs new file mode 100644 index 0000000..d64cb38 --- /dev/null +++ b/vendor/src/rekor/models/alpine_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct AlpineAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl AlpineAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> AlpineAllOf { + AlpineAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/consistency_proof.rs b/vendor/src/rekor/models/consistency_proof.rs new file mode 100644 index 0000000..f819675 --- /dev/null +++ b/vendor/src/rekor/models/consistency_proof.rs @@ -0,0 +1,26 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct ConsistencyProof { + /// The hash value stored at the root of the merkle tree at the time the proof was generated + #[serde(rename = "rootHash")] + pub root_hash: String, + #[serde(rename = "hashes")] + pub hashes: Vec, +} + +impl ConsistencyProof { + pub fn new(root_hash: String, hashes: Vec) -> ConsistencyProof { + ConsistencyProof { root_hash, hashes } + } +} diff --git a/vendor/src/rekor/models/error.rs b/vendor/src/rekor/models/error.rs new file mode 100644 index 0000000..a146f63 --- /dev/null +++ b/vendor/src/rekor/models/error.rs @@ -0,0 +1,28 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Error { + #[serde(rename = "code", skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde(rename = "message", skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +impl Error { + pub fn new() -> Error { + Error { + code: None, + message: None, + } + } +} diff --git a/vendor/src/rekor/models/hashedrekord.rs b/vendor/src/rekor/models/hashedrekord.rs new file mode 100644 index 0000000..3e919fc --- /dev/null +++ b/vendor/src/rekor/models/hashedrekord.rs @@ -0,0 +1,121 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; +use serde::{Deserialize, Serialize}; + +use crate::errors::SigstoreError; + +/// Hashedrekord : Hashed Rekord object + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Hashedrekord { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: Spec, +} + +impl Hashedrekord { + /// Hashed Rekord object + pub fn new(kind: String, api_version: String, spec: Spec) -> Hashedrekord { + Hashedrekord { + kind, + api_version, + spec, + } + } +} + +/// Stores the Signature and Data struct +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Spec { + pub signature: Signature, + pub data: Data, +} + +// Design a SPEC struct +impl Spec { + pub fn new(signature: Signature, data: Data) -> Spec { + Spec { signature, data } + } +} + +/// Stores the signature format, signature of the artifact and the PublicKey struct +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Signature { + pub content: String, + pub public_key: PublicKey, +} + +impl Signature { + pub fn new(content: String, public_key: PublicKey) -> Signature { + Signature { + content, + public_key, + } + } +} + +/// Stores the public key used to sign the artifact +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKey { + content: String, +} + +impl PublicKey { + pub fn new(content: String) -> PublicKey { + PublicKey { content } + } + + pub fn decode(&self) -> Result { + let decoded = BASE64_STD_ENGINE.decode(&self.content)?; + String::from_utf8(decoded).map_err(|e| SigstoreError::from(e.utf8_error())) + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Data { + pub hash: Hash, +} + +impl Data { + pub fn new(hash: Hash) -> Data { + Data { hash } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[allow(non_camel_case_types)] +pub enum AlgorithmKind { + #[default] + sha256, + sha1, +} + +/// Stores the algorithm used to hash the artifact and the value of the hash +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Hash { + pub algorithm: AlgorithmKind, + pub value: String, +} + +impl Hash { + pub fn new(algorithm: AlgorithmKind, value: String) -> Hash { + Hash { algorithm, value } + } +} diff --git a/vendor/src/rekor/models/hashedrekord_all_of.rs b/vendor/src/rekor/models/hashedrekord_all_of.rs new file mode 100644 index 0000000..818cc95 --- /dev/null +++ b/vendor/src/rekor/models/hashedrekord_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct HashedrekordAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl HashedrekordAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> HashedrekordAllOf { + HashedrekordAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/helm.rs b/vendor/src/rekor/models/helm.rs new file mode 100644 index 0000000..64e682a --- /dev/null +++ b/vendor/src/rekor/models/helm.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Helm : Helm chart + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Helm { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Helm { + /// Helm chart + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Helm { + Helm { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/helm_all_of.rs b/vendor/src/rekor/models/helm_all_of.rs new file mode 100644 index 0000000..e925c02 --- /dev/null +++ b/vendor/src/rekor/models/helm_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct HelmAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl HelmAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> HelmAllOf { + HelmAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/inactive_shard_log_info.rs b/vendor/src/rekor/models/inactive_shard_log_info.rs new file mode 100644 index 0000000..f94406b --- /dev/null +++ b/vendor/src/rekor/models/inactive_shard_log_info.rs @@ -0,0 +1,44 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::rekor::TreeSize; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct InactiveShardLogInfo { + /// The current hash value stored at the root of the merkle tree + #[serde(rename = "rootHash")] + pub root_hash: String, + /// The current number of nodes in the merkle tree + #[serde(rename = "treeSize")] + pub tree_size: TreeSize, + /// The current signed tree head + #[serde(rename = "signedTreeHead")] + pub signed_tree_head: String, + /// The current treeID + #[serde(rename = "treeID")] + pub tree_id: String, +} + +impl InactiveShardLogInfo { + pub fn new( + root_hash: String, + tree_size: TreeSize, + signed_tree_head: String, + tree_id: String, + ) -> InactiveShardLogInfo { + InactiveShardLogInfo { + root_hash, + tree_size, + signed_tree_head, + tree_id, + } + } +} diff --git a/vendor/src/rekor/models/inclusion_proof.rs b/vendor/src/rekor/models/inclusion_proof.rs new file mode 100644 index 0000000..3dc6653 --- /dev/null +++ b/vendor/src/rekor/models/inclusion_proof.rs @@ -0,0 +1,44 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::rekor::TreeSize; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct InclusionProof { + /// The index of the entry in the transparency log + #[serde(rename = "logIndex")] + pub log_index: i64, + /// The hash value stored at the root of the merkle tree at the time the proof was generated + #[serde(rename = "rootHash")] + pub root_hash: String, + /// The size of the merkle tree at the time the inclusion proof was generated + #[serde(rename = "treeSize")] + pub tree_size: TreeSize, + /// A list of hashes required to compute the inclusion proof, sorted in order from leaf to root + #[serde(rename = "hashes")] + pub hashes: Vec, +} + +impl InclusionProof { + pub fn new( + log_index: i64, + root_hash: String, + tree_size: TreeSize, + hashes: Vec, + ) -> InclusionProof { + InclusionProof { + log_index, + root_hash, + tree_size, + hashes, + } + } +} diff --git a/vendor/src/rekor/models/intoto.rs b/vendor/src/rekor/models/intoto.rs new file mode 100644 index 0000000..31d6f2a --- /dev/null +++ b/vendor/src/rekor/models/intoto.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Intoto : Intoto object + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Intoto { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Intoto { + /// Intoto object + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Intoto { + Intoto { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/intoto_all_of.rs b/vendor/src/rekor/models/intoto_all_of.rs new file mode 100644 index 0000000..0954898 --- /dev/null +++ b/vendor/src/rekor/models/intoto_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct IntotoAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl IntotoAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> IntotoAllOf { + IntotoAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/jar.rs b/vendor/src/rekor/models/jar.rs new file mode 100644 index 0000000..14132fd --- /dev/null +++ b/vendor/src/rekor/models/jar.rs @@ -0,0 +1,33 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +/// Jar : Java Archive (JAR) +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Jar { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Jar { + /// Java Archive (JAR) + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Jar { + Jar { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/jar_all_of.rs b/vendor/src/rekor/models/jar_all_of.rs new file mode 100644 index 0000000..7615dfd --- /dev/null +++ b/vendor/src/rekor/models/jar_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct JarAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl JarAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> JarAllOf { + JarAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/log_entry.rs b/vendor/src/rekor/models/log_entry.rs new file mode 100644 index 0000000..712333c --- /dev/null +++ b/vendor/src/rekor/models/log_entry.rs @@ -0,0 +1,119 @@ +// +// Copyright 2023 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::errors::SigstoreError; +use crate::rekor::TreeSize; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Error, Value, json}; +use std::collections::HashMap; +use std::str::FromStr; + +use super::{ + AlpineAllOf, HashedrekordAllOf, HelmAllOf, IntotoAllOf, JarAllOf, RekordAllOf, Rfc3161AllOf, + RpmAllOf, TufAllOf, +}; + +/// Stores the response returned by Rekor after making a new entry +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct LogEntry { + pub uuid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation: Option, + pub body: Body, + pub integrated_time: i64, + pub log_i_d: String, + pub log_index: i64, + pub verification: Verification, +} + +impl FromStr for LogEntry { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut log_entry_map = serde_json::from_str::>(s)?; + log_entry_map.entry("body").and_modify(|body| { + let decoded_body = serde_json::to_value( + decode_body(body.as_str().expect("Failed to parse Body")) + .expect("Failed to decode Body"), + ) + .expect("Serialization failed"); + *body = json!(decoded_body); + }); + let log_entry_str = serde_json::to_string(&log_entry_map)?; + Ok(serde_json::from_str::(&log_entry_str).expect("Serialization failed")) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind")] +#[allow(non_camel_case_types)] +pub enum Body { + alpine(AlpineAllOf), + helm(HelmAllOf), + jar(JarAllOf), + rfc3161(Rfc3161AllOf), + rpm(RpmAllOf), + tuf(TufAllOf), + intoto(IntotoAllOf), + hashedrekord(HashedrekordAllOf), + rekord(RekordAllOf), +} + +impl Default for Body { + fn default() -> Self { + Self::hashedrekord(Default::default()) + } +} + +fn decode_body(s: &str) -> Result { + let decoded = BASE64_STD_ENGINE.decode(s)?; + serde_json::from_slice(&decoded).map_err(SigstoreError::from) +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Attestation { + // This field is just a place holder + // Not sure what is stored inside the Attestation struct, it is empty for now + #[serde(skip_serializing_if = "Option::is_none")] + dummy: Option, +} + +/// Stores the signature over the artifact's logID, logIndex, body and integratedTime. +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Verification { + #[serde(skip_serializing_if = "Option::is_none")] + pub inclusion_proof: Option, + pub signed_entry_timestamp: String, +} + +/// Stores the signature over the artifact's logID, logIndex, body and integratedTime. +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InclusionProof { + pub hashes: Vec, + pub log_index: i64, + pub root_hash: String, + pub tree_size: TreeSize, + + /// A snapshot of the transparency log's state at a specific point in time, + /// in [Signed Note format]. + /// + /// [Signed Note format]: https://github.com/transparency-dev/formats/blob/main/log/README.md + pub checkpoint: String, +} diff --git a/vendor/src/rekor/models/log_info.rs b/vendor/src/rekor/models/log_info.rs new file mode 100644 index 0000000..12e2ce7 --- /dev/null +++ b/vendor/src/rekor/models/log_info.rs @@ -0,0 +1,42 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::rekor::TreeSize; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct LogInfo { + /// The current hash value stored at the root of the merkle tree + #[serde(rename = "rootHash")] + pub root_hash: String, + /// The current number of nodes in the merkle tree + #[serde(rename = "treeSize")] + pub tree_size: TreeSize, + /// The current signed tree head + #[serde(rename = "signedTreeHead")] + pub signed_tree_head: String, + /// The current treeID + #[serde(rename = "treeID")] + pub tree_id: Option, + #[serde(rename = "inactiveShards", skip_serializing_if = "Option::is_none")] + pub inactive_shards: Option>, +} + +impl LogInfo { + pub fn new(root_hash: String, tree_size: TreeSize, signed_tree_head: String) -> LogInfo { + LogInfo { + root_hash, + tree_size, + signed_tree_head, + tree_id: None, + inactive_shards: None, + } + } +} diff --git a/vendor/src/rekor/models/mod.rs b/vendor/src/rekor/models/mod.rs new file mode 100644 index 0000000..67ba961 --- /dev/null +++ b/vendor/src/rekor/models/mod.rs @@ -0,0 +1,56 @@ +pub mod alpine; +pub use self::alpine::Alpine; +pub mod alpine_all_of; +pub use self::alpine_all_of::AlpineAllOf; +pub mod consistency_proof; +pub use self::consistency_proof::ConsistencyProof; +pub mod error; +pub use self::error::Error; +pub mod hashedrekord; +pub use self::hashedrekord::Hashedrekord; +pub mod hashedrekord_all_of; +pub use self::hashedrekord_all_of::HashedrekordAllOf; +pub mod helm; +pub use self::helm::Helm; +pub mod helm_all_of; +pub use self::helm_all_of::HelmAllOf; +pub mod inactive_shard_log_info; +pub use self::inactive_shard_log_info::InactiveShardLogInfo; +pub mod inclusion_proof; +pub use self::inclusion_proof::InclusionProof; +pub mod intoto; +pub use self::intoto::Intoto; +pub mod intoto_all_of; +pub use self::intoto_all_of::IntotoAllOf; +pub mod jar; +pub use self::jar::Jar; +pub mod jar_all_of; +pub use self::jar_all_of::JarAllOf; +pub mod log_info; +pub use self::log_info::LogInfo; +pub mod proposed_entry; +pub use self::proposed_entry::ProposedEntry; +pub mod rekord; +pub use self::rekord::Rekord; +pub mod rekord_all_of; +pub use self::rekord_all_of::RekordAllOf; +pub mod rfc3161; +pub use self::rfc3161::Rfc3161; +pub mod rfc3161_all_of; +pub use self::rfc3161_all_of::Rfc3161AllOf; +pub mod rpm; +pub use self::rpm::Rpm; +pub mod rpm_all_of; +pub use self::rpm_all_of::RpmAllOf; +pub mod search_index; +pub use self::search_index::SearchIndex; +pub mod search_index_public_key; +pub use self::search_index_public_key::SearchIndexPublicKey; +pub mod search_log_query; +pub use self::search_log_query::SearchLogQuery; +pub mod tuf; +pub use self::tuf::Tuf; +pub mod tuf_all_of; +pub use self::tuf_all_of::TufAllOf; +pub mod log_entry; +pub use self::log_entry::LogEntry; diff --git a/vendor/src/rekor/models/proposed_entry.rs b/vendor/src/rekor/models/proposed_entry.rs new file mode 100644 index 0000000..a395d49 --- /dev/null +++ b/vendor/src/rekor/models/proposed_entry.rs @@ -0,0 +1,78 @@ +/* +* Rekor +* +* Rekor is a cryptographically secure, immutable transparency log for signed software releases. +* +* The version of the OpenAPI document: 0.0.1 +* +* Generated by: https://openapi-generator.tech +*/ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum ProposedEntry { + #[serde(rename = "alpine")] + Alpine { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "hashedrekord")] + Hashedrekord { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: super::hashedrekord::Spec, + }, + #[serde(rename = "helm")] + Helm { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "intoto")] + Intoto { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "jar")] + Jar { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "rekord")] + Rekord { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "rfc3161")] + Rfc3161 { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "rpm")] + Rpm { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, + #[serde(rename = "tuf")] + Tuf { + #[serde(rename = "apiVersion")] + api_version: String, + #[serde(rename = "spec")] + spec: serde_json::Value, + }, +} diff --git a/vendor/src/rekor/models/rekord.rs b/vendor/src/rekor/models/rekord.rs new file mode 100644 index 0000000..a5bd4db --- /dev/null +++ b/vendor/src/rekor/models/rekord.rs @@ -0,0 +1,33 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +/// Rekord : Rekord object +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Rekord { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Rekord { + /// Rekord object + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rekord { + Rekord { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/rekord_all_of.rs b/vendor/src/rekor/models/rekord_all_of.rs new file mode 100644 index 0000000..c390117 --- /dev/null +++ b/vendor/src/rekor/models/rekord_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct RekordAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl RekordAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> RekordAllOf { + RekordAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/rfc3161.rs b/vendor/src/rekor/models/rfc3161.rs new file mode 100644 index 0000000..4c86173 --- /dev/null +++ b/vendor/src/rekor/models/rfc3161.rs @@ -0,0 +1,33 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +/// Rfc3161 : RFC3161 Timestamp +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Rfc3161 { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Rfc3161 { + /// RFC3161 Timestamp + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rfc3161 { + Rfc3161 { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/rfc3161_all_of.rs b/vendor/src/rekor/models/rfc3161_all_of.rs new file mode 100644 index 0000000..98880eb --- /dev/null +++ b/vendor/src/rekor/models/rfc3161_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Rfc3161AllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Rfc3161AllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> Rfc3161AllOf { + Rfc3161AllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/rpm.rs b/vendor/src/rekor/models/rpm.rs new file mode 100644 index 0000000..a7a9998 --- /dev/null +++ b/vendor/src/rekor/models/rpm.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Rpm : RPM package + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Rpm { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Rpm { + /// RPM package + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rpm { + Rpm { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/rpm_all_of.rs b/vendor/src/rekor/models/rpm_all_of.rs new file mode 100644 index 0000000..fc7499d --- /dev/null +++ b/vendor/src/rekor/models/rpm_all_of.rs @@ -0,0 +1,24 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct RpmAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl RpmAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> RpmAllOf { + RpmAllOf { api_version, spec } + } +} diff --git a/vendor/src/rekor/models/search_index.rs b/vendor/src/rekor/models/search_index.rs new file mode 100644 index 0000000..63586fd --- /dev/null +++ b/vendor/src/rekor/models/search_index.rs @@ -0,0 +1,31 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct SearchIndex { + #[serde(rename = "email", skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] + pub public_key: Option, + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, +} + +impl SearchIndex { + pub fn new() -> SearchIndex { + SearchIndex { + email: None, + public_key: None, + hash: None, + } + } +} diff --git a/vendor/src/rekor/models/search_index_public_key.rs b/vendor/src/rekor/models/search_index_public_key.rs new file mode 100644 index 0000000..7f2fb3a --- /dev/null +++ b/vendor/src/rekor/models/search_index_public_key.rs @@ -0,0 +1,52 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct SearchIndexPublicKey { + #[serde(rename = "format")] + pub format: Format, + #[serde(rename = "content", skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(rename = "url", skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +impl SearchIndexPublicKey { + pub fn new(format: Format) -> SearchIndexPublicKey { + SearchIndexPublicKey { + format, + content: None, + url: None, + } + } +} + +/// The supported pluggable types to sign and upload data +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum Format { + #[serde(rename = "pgp")] + Pgp, + #[serde(rename = "x509")] + X509, + #[serde(rename = "minisign")] + Minisign, + #[serde(rename = "ssh")] + Ssh, + #[serde(rename = "tuf")] + Tuf, +} + +impl Default for Format { + fn default() -> Format { + Self::Pgp + } +} diff --git a/vendor/src/rekor/models/search_log_query.rs b/vendor/src/rekor/models/search_log_query.rs new file mode 100644 index 0000000..55fac17 --- /dev/null +++ b/vendor/src/rekor/models/search_log_query.rs @@ -0,0 +1,31 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct SearchLogQuery { + #[serde(rename = "entryUUIDs", skip_serializing_if = "Option::is_none")] + pub entry_uuids: Option>, + #[serde(rename = "logIndexes", skip_serializing_if = "Option::is_none")] + pub log_indexes: Option>, + #[serde(rename = "entries", skip_serializing_if = "Option::is_none")] + pub entries: Option>, +} + +impl SearchLogQuery { + pub fn new() -> SearchLogQuery { + SearchLogQuery { + entry_uuids: None, + log_indexes: None, + entries: None, + } + } +} diff --git a/vendor/src/rekor/models/tuf.rs b/vendor/src/rekor/models/tuf.rs new file mode 100644 index 0000000..a512149 --- /dev/null +++ b/vendor/src/rekor/models/tuf.rs @@ -0,0 +1,34 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +/// Tuf : TUF metadata + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct Tuf { + #[serde(rename = "kind")] + pub kind: String, + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl Tuf { + /// TUF metadata + pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Tuf { + Tuf { + kind, + api_version, + spec, + } + } +} diff --git a/vendor/src/rekor/models/tuf_all_of.rs b/vendor/src/rekor/models/tuf_all_of.rs new file mode 100644 index 0000000..7ec6e5a --- /dev/null +++ b/vendor/src/rekor/models/tuf_all_of.rs @@ -0,0 +1,25 @@ +/* + * Rekor + * + * Rekor is a cryptographically secure, immutable transparency log for signed software releases. + * + * The version of the OpenAPI document: 0.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct TufAllOf { + #[serde(rename = "apiVersion")] + pub api_version: String, + #[serde(rename = "spec")] + pub spec: serde_json::Value, +} + +impl TufAllOf { + pub fn new(api_version: String, spec: serde_json::Value) -> TufAllOf { + TufAllOf { api_version, spec } + } +} diff --git a/vendor/src/trust/mod.rs b/vendor/src/trust/mod.rs new file mode 100644 index 0000000..67a510e --- /dev/null +++ b/vendor/src/trust/mod.rs @@ -0,0 +1,60 @@ +// +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::BTreeMap; + +use pki_types::CertificateDer; + +#[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] +#[cfg(feature = "sigstore-trust-root")] +pub mod sigstore; + +/// A `TrustRoot` owns all key material necessary for establishing a root of trust. +pub trait TrustRoot { + fn fulcio_certs(&self) -> crate::errors::Result>>; + fn rekor_keys(&self) -> crate::errors::Result>; + fn ctfe_keys(&self) -> crate::errors::Result>; +} + +/// A `ManualTrustRoot` is a [TrustRoot] with out-of-band trust materials. +/// As it does not establish a trust root with TUF, users must initialize its materials themselves. +#[derive(Debug, Default)] +pub struct ManualTrustRoot<'a> { + pub fulcio_certs: Vec>, + pub rekor_keys: BTreeMap>, + pub ctfe_keys: BTreeMap>, +} + +impl<'a> TrustRoot for ManualTrustRoot<'a> { + fn fulcio_certs(&self) -> crate::errors::Result>> { + Ok(self.fulcio_certs.clone()) + } + + fn rekor_keys(&self) -> crate::errors::Result> { + Ok(self + .rekor_keys + .iter() + .map(|(k, v)| (k.clone(), v.as_slice())) + .collect()) + } + + fn ctfe_keys(&self) -> crate::errors::Result> { + Ok(self + .ctfe_keys + .iter() + .map(|(k, v)| (k.clone(), v.as_slice())) + .collect()) + } +} diff --git a/vendor/src/trust/sigstore/constants.rs b/vendor/src/trust/sigstore/constants.rs new file mode 100644 index 0000000..d4630aa --- /dev/null +++ b/vendor/src/trust/sigstore/constants.rs @@ -0,0 +1,36 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) const SIGSTORE_METADATA_BASE: &str = "https://tuf-repo-cdn.sigstore.dev"; +pub(crate) const SIGSTORE_TARGET_BASE: &str = "https://tuf-repo-cdn.sigstore.dev/targets"; + +macro_rules! impl_static_resource { + {$($name:literal,)+} => { + #[inline] + pub(crate) fn static_resource(name: N) -> Option<&'static [u8]> where N: AsRef { + match name.as_ref() { + $( + $name => Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/trust_root/prod/", $name))) + ),+, + _ => None, + } + } + }; +} + +impl_static_resource! { + "root.json", + "trusted_root.json", +} diff --git a/vendor/src/trust/sigstore/mod.rs b/vendor/src/trust/sigstore/mod.rs new file mode 100644 index 0000000..4d0d75d --- /dev/null +++ b/vendor/src/trust/sigstore/mod.rs @@ -0,0 +1,364 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Helper Structs to interact with the Sigstore TUF repository. +//! +//! The main interaction point is [`SigstoreTrustRoot`], which fetches Rekor's +//! public key and Fulcio's certificate. +//! +//! These can later be given to [`cosign::ClientBuilder`](crate::cosign::ClientBuilder) +//! to enable Fulcio and Rekor integrations. +use std::{collections::BTreeMap, path::Path}; + +use futures_util::TryStreamExt; +use pki_types::CertificateDer; +use sha2::{Digest, Sha256}; +use sigstore_protobuf_specs::dev::sigstore::{ + common::v1::TimeRange, + trustroot::v1::{CertificateAuthority, TransparencyLogInstance, TrustedRoot}, +}; +use tokio_util::bytes::BytesMut; +use tough::TargetName; +use tracing::debug; + +mod constants; + +use crate::errors::{Result, SigstoreError}; +pub use crate::trust::{ManualTrustRoot, TrustRoot}; + +/// Securely fetches Rekor public key and Fulcio certificates from Sigstore's TUF repository. +#[derive(Debug)] +pub struct SigstoreTrustRoot { + trusted_root: TrustedRoot, +} + +impl SigstoreTrustRoot { + /// Constructs a new trust root from a [`tough::Repository`]. + async fn from_tough( + repository: &tough::Repository, + checkout_dir: Option<&Path>, + ) -> Result { + let trusted_root = { + let data = Self::fetch_target(repository, checkout_dir, "trusted_root.json").await?; + serde_json::from_slice(&data[..])? + }; + + Ok(Self { trusted_root }) + } + + /// Constructs a new trust root backed by the Sigstore Public Good Instance. + pub async fn new(cache_dir: Option<&Path>) -> Result { + // These are statically defined and should always parse correctly. + let metadata_base = url::Url::parse(constants::SIGSTORE_METADATA_BASE)?; + let target_base = url::Url::parse(constants::SIGSTORE_TARGET_BASE)?; + + let repository = tough::RepositoryLoader::new( + &constants::static_resource("root.json").expect("Failed to fetch embedded TUF root!"), + metadata_base, + target_base, + ) + .expiration_enforcement(tough::ExpirationEnforcement::Safe) + .load() + .await + .map_err(Box::new)?; + + Self::from_tough(&repository, cache_dir).await + } + + /// Constructs a new trust root from a JSON object containing a + /// [`TrustedRoot`](https://github.com/sigstore/protobuf-specs). + /// + /// # Warning + /// + /// This constructor does not perform any validation of the provided data. + /// The caller must ensure that the data is trustworthy. + /// Using untrusted data may lead to security vulnerabilities. + pub fn from_trusted_root_json_unchecked(data: &[u8]) -> Result { + let trusted_root: TrustedRoot = serde_json::from_slice(data)?; + Ok(Self { trusted_root }) + } + + async fn fetch_target( + repository: &tough::Repository, + checkout_dir: Option<&Path>, + name: N, + ) -> Result> + where + N: TryInto, + { + let name: TargetName = name.try_into().map_err(Box::new)?; + let local_path = checkout_dir.as_ref().map(|d| d.join(name.raw())); + + let read_remote_target = || async { + match repository.read_target(&name).await { + Ok(Some(s)) => Ok(s.try_collect::().await.map_err(Box::new)?), + _ => Err(SigstoreError::TufTargetNotFoundError(name.raw().to_owned())), + } + }; + + // First, try reading the target from disk cache. + let data = if let Some(Ok(local_data)) = local_path.as_ref().map(std::fs::read) { + debug!("{}: reading from disk cache", name.raw()); + local_data.to_vec() + // Try reading the target embedded into the binary. + } else if let Some(embedded_data) = constants::static_resource(name.raw()) { + debug!("{}: reading from embedded resources", name.raw()); + embedded_data.to_vec() + // If all else fails, read the data from the TUF repo. + } else { + match read_remote_target().await { + Ok(remote_data) => { + debug!("{}: reading from remote", name.raw()); + remote_data.to_vec() + } + _ => { + return Err(SigstoreError::TufTargetNotFoundError(name.raw().to_owned())); + } + } + }; + + // Get metadata (hash) of the target and update the disk copy if it doesn't match. + let Some(target) = repository.targets().signed.targets.get(&name) else { + return Err(SigstoreError::TufMetadataError(format!( + "couldn't get metadata for {}", + name.raw() + ))); + }; + + let data = if Sha256::digest(&data)[..] != target.hashes.sha256[..] { + debug!("{}: out of date", name.raw()); + read_remote_target().await?.to_vec() + } else { + data + }; + + // Write our updated data back to the disk. + if let Some(local_path) = local_path { + std::fs::write(local_path, &data)?; + } + + Ok(data) + } + + #[inline] + fn tlog_keys(tlogs: &[TransparencyLogInstance]) -> impl Iterator { + tlogs + .iter() + .filter(|tlog| { + if let Some(public_key) = tlog.public_key.as_ref() { + is_timerange_valid(public_key.valid_for.as_ref(), false) + } else { + false + } + }) + .filter_map(|tlog| { + let key_id = tlog + .log_id + .as_ref() + .map(|log_id| hex::encode(log_id.key_id.as_slice())); + let public_key_raw = tlog + .public_key + .as_ref() + .and_then(|pk| pk.raw_bytes.as_ref()); + match (key_id, public_key_raw) { + (Some(id), Some(key)) => Some((id, key.as_slice())), + _ => None, + } + }) + } + + #[inline] + fn ca_keys( + cas: &[CertificateAuthority], + allow_expired: bool, + ) -> impl Iterator { + cas.iter() + .filter(move |ca| is_timerange_valid(ca.valid_for.as_ref(), allow_expired)) + .flat_map(|ca| ca.cert_chain.as_ref()) + .flat_map(|chain| chain.certificates.iter()) + .map(|cert| cert.raw_bytes.as_slice()) + } +} + +impl crate::trust::TrustRoot for SigstoreTrustRoot { + /// Fetch Fulcio certificates from the given TUF repository or reuse + /// the local cache if its contents are not outdated. + /// + /// The contents of the local cache are updated when they are outdated. + fn fulcio_certs(&self) -> Result>> { + // Allow expired certificates: they may have been active when the + // certificate was used to sign. + let certs = Self::ca_keys(&self.trusted_root.certificate_authorities, true); + let certs: Vec<_> = certs + .map(|c| CertificateDer::from(c).into_owned()) + .collect(); + + if certs.is_empty() { + Err(SigstoreError::TufMetadataError( + "Fulcio certificates not found".into(), + )) + } else { + Ok(certs) + } + } + + /// Fetch Rekor public keys from the given TUF repository or reuse + /// the local cache if it's not outdated. + /// + /// The contents of the local cache are updated when they are outdated. + fn rekor_keys(&self) -> Result> { + let keys: BTreeMap = Self::tlog_keys(&self.trusted_root.tlogs).collect(); + + Ok(keys) + } + + /// Fetch CTFE public keys from the given TUF repository or reuse + /// the local cache if it's not outdated. + /// + /// The contents of the local cache are updated when they are outdated. + fn ctfe_keys(&self) -> Result> { + let keys: BTreeMap = Self::tlog_keys(&self.trusted_root.ctlogs).collect(); + + if keys.is_empty() { + Err(SigstoreError::TufMetadataError( + "CTFE keys not found".into(), + )) + } else { + Ok(keys) + } + } +} + +/// Given a `range`, checks that the the current time is not before `start`. If +/// `allow_expired` is `false`, also checks that the current time is not after +/// `end`. +fn is_timerange_valid(range: Option<&TimeRange>, allow_expired: bool) -> bool { + let now = chrono::Utc::now().timestamp(); + + let start = range.and_then(|r| r.start.as_ref()).map(|t| t.seconds); + let end = range.and_then(|r| r.end.as_ref()).map(|t| t.seconds); + + match (start, end) { + // If there was no validity period specified, the key is always valid. + (None, _) => true, + // Active: if the current time is before the starting period, we are not yet valid. + (Some(start), _) if now < start => false, + // If we want Expired keys, then we don't need to check the end. + _ if allow_expired => true, + // If there is no expiry date, the key is valid. + (_, None) => true, + // If we have an expiry date, check it. + (_, Some(end)) => now <= end, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::{fixture, rstest}; + use std::fs; + use std::path::Path; + use std::time::SystemTime; + use tempfile::TempDir; + + fn verify(root: &SigstoreTrustRoot, cache_dir: Option<&Path>) { + if let Some(cache_dir) = cache_dir { + assert!( + cache_dir.join("trusted_root.json").exists(), + "the trusted root was not cached" + ); + } + + assert!( + root.fulcio_certs().is_ok_and(|v| !v.is_empty()), + "no Fulcio certs established" + ); + assert!( + root.rekor_keys().is_ok_and(|v| !v.is_empty()), + "no Rekor keys established" + ); + assert!( + root.ctfe_keys().is_ok_and(|v| !v.is_empty()), + "no CTFE keys established" + ); + } + + #[fixture] + fn cache_dir() -> TempDir { + TempDir::new().expect("cannot create temp cache dir") + } + + async fn trust_root(cache: Option<&Path>) -> SigstoreTrustRoot { + SigstoreTrustRoot::new(cache) + .await + .expect("failed to construct SigstoreTrustRoot") + } + + #[rstest] + #[tokio::test] + async fn trust_root_fetch(#[values(None, Some(cache_dir()))] cache: Option) { + let cache = cache.as_ref().map(|t| t.path()); + let root = trust_root(cache).await; + + verify(&root, cache); + } + + #[rstest] + #[tokio::test] + async fn trust_root_outdated(cache_dir: TempDir) { + let trusted_root_path = cache_dir.path().join("trusted_root.json"); + let outdated_data = b"fake trusted root"; + fs::write(&trusted_root_path, outdated_data) + .expect("failed to write to trusted root cache"); + + let cache = Some(cache_dir.path()); + let root = trust_root(cache).await; + verify(&root, cache); + + let data = fs::read(&trusted_root_path).expect("failed to read from trusted root cache"); + assert_ne!(data, outdated_data, "TUF cache was not properly updated"); + } + + #[test] + fn test_is_timerange_valid() { + fn range_from(start: i64, end: i64) -> TimeRange { + let base = chrono::Utc::now(); + let start: SystemTime = (base + chrono::TimeDelta::seconds(start)).into(); + let end: SystemTime = (base + chrono::TimeDelta::seconds(end)).into(); + + TimeRange { + start: Some(start.into()), + end: Some(end.into()), + } + } + + assert!(is_timerange_valid(None, true)); + assert!(is_timerange_valid(None, false)); + + // Test lower bound conditions + + // Valid: 1 ago, 1 from now + assert!(is_timerange_valid(Some(&range_from(-1, 1)), false)); + // Invalid: 1 from now, 1 from now + assert!(!is_timerange_valid(Some(&range_from(1, 1)), false)); + + // Test upper bound conditions + + // Invalid: 1 ago, 1 ago + assert!(!is_timerange_valid(Some(&range_from(-1, -1)), false)); + // Valid: 1 ago, 1 ago + assert!(is_timerange_valid(Some(&range_from(-1, -1)), true)) + } +} diff --git a/vendor/tests/data/keys/cosign_generated_encrypted_empty_private.key b/vendor/tests/data/keys/cosign_generated_encrypted_empty_private.key new file mode 100644 index 0000000..44246c1 --- /dev/null +++ b/vendor/tests/data/keys/cosign_generated_encrypted_empty_private.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 +OCwicCI6MX0sInNhbHQiOiJFY3pJR3Z0bnpFcCsxaEpudERnbGI1UnoxN3gwQVMy +YklvWGRNcDQrU1NJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJtSVVKbG1OMzNINExvZ0dhaG5MamdmK3R4SmJwci93MCJ9LCJj +aXBoZXJ0ZXh0IjoiM3FtS2FidXRuYm1WT1IvTkZGM2NDaUErTXp1WWQ5L0VERDVI +MVlUSGhQVzZsSzAvdjVqdDZCQzlsME12NGV5am9qZkl0d3B6a2JJOXQrZGx5VXZ3 +VUgvMWZjbkFZT2dEdXRLQzkvSkNiOE02SVY2VHpVaDF5c0ZFWDFzeG9xb1FzeUpL +bjM0UVlldFNlaVdDaGtmUHUyZXByQjFEV0pwekdzTGZIakxNVTkzOEdmNk1xM1A0 +N0ZSd2syZzY1cFZnSGMwVWV1L1N1OUs0Zmc9PSJ9 +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/vendor/tests/data/keys/ecdsa_encrypted_private.key b/vendor/tests/data/keys/ecdsa_encrypted_private.key new file mode 100644 index 0000000..f73f46b --- /dev/null +++ b/vendor/tests/data/keys/ecdsa_encrypted_private.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiIrRFJrTXNRZmNUNTNKNCs1Y3VkUnlnWTUyWUMrK0RW +TmxaSTN4VnlwWFlvPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJnNTFORnBqQUxlemhvbGVWTWVjUFJUNUErMVhMcDAxeiJ9LCJj +aXBoZXJ0ZXh0Ijoib0YvN09mOGlQRDlkNlc2Mk5SWGVibEszWXZmNFFucTZORm16 +Q0VyV2FTVUJOZTNXSFJiRVNYK2dJOCszejg4TDBDVXlWdm9LTHhuR0xhVnhnczVD +YUdyekcrMWxFTEI1cGRMVmZXd1VlQzlLelNab0hxZHZLaUsvUmNodVhpS0VqYmdv +ajNGd2pMcmhEYkY5a2lVWk93REpuT01kUkoxNnI3emxjRHdwT3htOXFrbWFDWDlB +VjQ4b1RXVG0rQXExOHlDZE50MDYybGdudVE9PSJ9 +-----END ENCRYPTED COSIGN PRIVATE KEY----- diff --git a/vendor/tests/data/keys/ecdsa_private.key b/vendor/tests/data/keys/ecdsa_private.key new file mode 100644 index 0000000..c2e3ce8 --- /dev/null +++ b/vendor/tests/data/keys/ecdsa_private.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAbUSDapDt/yShCq1 +rzJwhGj9fMQd21E5SXmln12o8J+hRANCAAQfFCADQhM36xItBLLsGZmMDe5hqtPc +gRx8+8Zf40O4VAyyv3KO5HePY23r/kVZ+YkXwS55sYSpF5F++AQml0PP +-----END PRIVATE KEY----- diff --git a/vendor/tests/data/keys/ed25519_encrypted_private.key b/vendor/tests/data/keys/ed25519_encrypted_private.key new file mode 100644 index 0000000..54cfa81 --- /dev/null +++ b/vendor/tests/data/keys/ed25519_encrypted_private.key @@ -0,0 +1,10 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiJ1elF5Snc4b1FjWCt1T3RlWDljSHpVOEhycWlJVzJV +ck9YbnpJb1ljaUtJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJKeUJDMS81K2E3emtxdnE5dVpTaHIvZDRwNXVjQW1WQSJ9LCJj +aXBoZXJ0ZXh0IjoiZC9TMndRaVJ0YjcwazFmZEd0ZzRFa0RXZ0ZJcU9NRWd3TDNI +bUtzWjBpdDlJcTFRSVplUGJoYWRTKzJORUZzWVA4TlhVOGpoT1dtb0QreWZwbkMz +dTBYQTRvcFBib2JwbEpleWJEUUtQdW1sa0tiRUc5MnRCa3pUMFJwNnA1dDdFeVVk +In0= +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/vendor/tests/data/keys/ed25519_private.key b/vendor/tests/data/keys/ed25519_private.key new file mode 100644 index 0000000..1e51643 --- /dev/null +++ b/vendor/tests/data/keys/ed25519_private.key @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEILZ7IMwQHwlrU+a5l0WnujhvsmQXrWsStjtbAj8n/EiJ +gSEAXtg9yVbH8JADtC6sn4NBh1CSDWeCv0ABIj4TtoJj9ak= +-----END PRIVATE KEY----- diff --git a/vendor/tests/data/keys/rsa_encrypted_private.key b/vendor/tests/data/keys/rsa_encrypted_private.key new file mode 100644 index 0000000..0f20fe6 --- /dev/null +++ b/vendor/tests/data/keys/rsa_encrypted_private.key @@ -0,0 +1,73 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiJMelBIYTM2VkllY01YZ1NkeXMyelIreHdpRWxTcGF0 +OXBUZFlLVXVOK3VVPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJkUncvUXMvOGFXUzdhZzJjS2JUM1pVMDhBdVR1QmswQiJ9LCJj +aXBoZXJ0ZXh0IjoiZDBRZUlIWmlpYVlCenlodXB5R0FFRmZtVzJCTFFxemJTRm9Y +VnRDek5JK2VJTm8xOU9RTW1VNHo0Z2NQY1FHbkE3ay9yc3h0NWRuWjFHOHpsZ0Fl +UzlUVFVMbWdSZjU5Y1oyRGRrOHRYVSs3R3R3YkNCTWFPY1hCSTBCUDc1VGJJK0F6 +MEUvT1drZjNhQlhOSE5veU5Ecklyc05obEluUTZ3ODIwZ3pPK0xUYmxVbnJ0UDVm +OFV6V0hDc09hazN1M0hIOXE4bytOZkJ2ZXV1cUp5MUNtckhYamJJWjRtaHR6b2tv +VnROeGQwTFpQN3c3aXJFbXBQaExLMHBWRHpnTlg1OTdGVXlNUzFGcVh1YnBDbFR6 +QVBQNUZmNG9kaXJMZVdGL3djUkxFNlhkcDkxeVRVZHoxZ2tQRnN3UmZPVmJYbWUz +d3VwNGhrMXQxeWpMZkdYbUYzWjdWWEFyRDhyenV5aDhsZUpaYlZ0ekRXSzlvVmw3 +STNIYmx6Qmx5eGplRjZZeVAvZ1JqUjBCOGZxN1F2a1RjdHlvcHgxdnNsM1E2S3ly +YitiWE5mMGdMSkxRQ01Id3h2TE0yaHFoek5jcDYxendoT1hoNjdpK2grbUtweXBk +a1FMOGZ2NEI1RTd0Y0prTVg2cTJoeHpMQVdWRXFwTUVqY1k0dmszd0dkLzhPckZQ +aFd3NEpMY1c5VTdSRUx3ZTM2RFpzbk9DNW0zK3VRZDJEYUNQSUhyUEw3eVFYOUhv +Qnp1UnFXQ3R3TXNXNWZmMFgycEhpY3l4MVdlY3JMc2crc2Uwck1QRUJNT3hXL3VZ +VjRKTnhBa0FuMTRsbGZqRmlDZlorUGNMcVhLOXVON3k4dU9VeGVYN0hCSmRCdmVT +emRiYXZoRVExbm4rU0NUK21YQjF0WXJBNFl5a2VMa2RzRWtXdGtHZFhnVjNFQmtM +TDByVzlWUENWdkEwTS9mU1ZScGpmQ3g3TkZiZ21GSkl0T1dWMmczWUxxN3h0WU1i +VFU5YWdpbzFBTktMeUtlYkFNdDJOUmw1T0sxSXdya29rUXo0WjZwZWlqeWJlMDBT +ekZ0akQ2cTVQK3VxTHZwT0NwMEphdTdScGRsOXJqN2lmMTRRRWozNFhvaml4Sm9a +ZXprY1k5SEp6QllHSWMyVnpQaEF0TjdIcnZoUDVlVHFaa2ozek5aZnBiT3Nja3BB +eFk5TTRNYWhjd3g4a0VGdy8vZFZKZWREVGx2WDc2ODAvL2t3RmROYmNTcmlZYnVD +UEkyR3Y2YXVnZ2RweCtWWTY2SmtZbzNYUVdmM255Z0xOUk0xMUVTTWVkVHFuL3NN +WnMrNGRDaG84b0tqbGhzZU9URkdDOEpWeENaV2IraHh2RUNNZ0RLTExZbW1PS2J1 +cC8vWjg3cVZtUGZjUjc5RVhmN2k1bUZ6QnpzRUFOSXYvaVNpTStSM1R4NXVIUEpr +eXRScTVJNkhvOWEzKzZPV3Q2V2dUbGd5aDdmZmZDWGJXamFGNVJHNE1TcDdQV2FD +QlBvZ3lnZ0ZFazRIWjUyRWt1S1B4TGZsc200QnpkT0FBdmZhaTloSmt0N2t4WUpS +YnVMb3cvMWZxaXNmNHJwcTU3WlZCelJBR09UYW1mT0Y3RnE1a1JNL0FEWStYZWlU +UVJ3ZVRFVk4wOWh1cHdhVm9Uc2lubmNJQlIrcUZMU2pERlJWOUI4M1BBdlNSU1hM +ZnpwbWgrdU4zY1dWL2k4N2xOb25wTlJUSXR3bUFvZjNRU2NPR0lsOUhiR2tWRDM1 +bUgvTDhQWVJMMXZlME0yUjdYbXBsWUY5NkhkU3NML3BiYlVLeGFpa0FwTmJCM2VW +U0FTYXdUUEdnYmhBWEtkNEE2YXRkSU5IRHRVbXVqWGxhYTlBL0JhN2h3SU1Ic1kr +R3RpL1RzQzhacmN2ZGUzQkh3cHZJdkpHdnRRUS9IZzVhSTMvRE5yazlUYjhQWUpH +RlBPTGU4ajU1YUtWYVdmeFZNZnBZYW9FL3pwN09ibEVFWnpLY3A4aFZ4S3p3eFJx +Vk1ueGJwYm9TbXBwVzd0Zjc1RXZiUVFyUEUwd2Mzc3EzSm9kMkRYUGlFQmFNOVhB +eGZvSTM5YmZ2L2tNUU9ES3p6U2x6SnV1cTFQZUFMVEh0SmVUQld0Wk1jdytFV3p4 +dTNkMndSMWJBQWtpdFVHNkxhRjdZN1BDR2dQWDdSelNVL3Y5bE9Qa0VFMUZhbUxM +TFJCZVU3NDU1WU9HSlFJb09TOVRsbGJyMWRVRi91cmRJbitDS2JhUHRWQ0k1TjNK +SWNFYzdiYTZsd3dBMkszQVNwY2lXM1MzUmxkcVV0NFdUbXBpcU9BVytETm1qcmts +eElFZ0YxOEZ0MzQ5MVBJMUlBWU05UVYrcDVEWnBzV28ycThTSWs3ZC9QL3hpVCtN +QkVxMDNNRjA0cWtPeDczeEplN0NqbWJ5UCtBbzluUWpocWpwWmxxWmJoc1RkS3FR +M252NXpMTmFDdUMwMFhiYUIvTmRjTGl3VEt4WnV3WHp6MFhmcE9ORnVheG9SWU9R +Z01WZlhSSTZ0RXNGaWdUVWgvQm5LaWNjWm5nYWVwNHNIL0Y5MjdES1FIdCtXalc5 +OHpHT24rZGFRVWxOV1hOOXlRNERoWFk1WmcremJHZ3hEZi90d1hMMjIzeS9zT0Rt +WXlJb2ZUWDNYRWN4YWdOOHBhSStkODNOWEJHdE4vRkNlNmpKejh2R2Fzci8wdnJy +c2w4RWozV0FaMXFLWlZsd1hYMTA3V2hzZXV5bEFVaVFZZWVKZ2NmSXRPVHpSYWs4 +WHVSekw1eVloR2gxU1hMTURPemNBTE5FWEY5cDg5TmFLSGFNcHUwU1hKWUZidlFS +UFNFUk42U2dnSkFLZXMxeDQxMk42STh2MXczdzJPUldDNEUya1RMUk5oNUhReWxp +MjhNMnUzNDNtVzlUU2NrN3ZPTWkza2ZuN0VxbUQ3bWl2TElTaENhR3I3TFpJY2R3 +bVFoNWtYdDN4NXVzdVZYMGhnMEtIZHo1TWpMenR3bnhxK1M4M3h6eVlHQlFmbnVa +Yk0vZ21OVlNiQVd3cm5pYmZuUi9aR2dZOGZFOG8vcFBiU295dWo0RE1GU0pQWmN6 +WHNSY004N21ZTWExd0RZdHl5ckYwSnFEQ3FZUmhQYmlSL25XSXhCckFZZW1wZXpu +V2RucjBJNjBaYnZ5VDhIc3M0dVQwNU0xN1VwZVdNTkxReUFtZHVqVE1FQUF4NWVx +N3llYmFGRWFvbVVFdWVFK3FmaWU0d3FjSEdJTlpVbmNFWlNqTXdBcXIwOFNGM0w4 +Z1M3ejk4bUtoYVIwM1p3QmVpLzJGa204SnZEbExRY1o4K2JPaEtwUGtXRTU1MjUr +Y082ZjRkUXd4cXpsL0YwYzRDWDZvSmJaVWNoM3BXN1JicE1oaU1vOTFuSFlEMnp2 +SFhnclhWMHBIRFlsaHFZVkJlWVRaZkQ5dTE1djluMDRwWHREbXltZXdlS2JIeWtH +Q0V1aG81WlI4T0JRRUl2czdaZGVtSFJrR2J1SENyMHpvVjFPam9MUk1xMmE2UzJI +ZWt0ZlJ0MlkyRHlIRzUzNDQrTmZzcGJYYWkxdVBhd1pkL1Y2eXRFMUJtLzdMYklQ +WVZRaHdINzY3aFFpUHY3bGZJaTQ4WGJoQ3RNakxDTmtaK1RHblBrQWw1Rmwxd0RC +eUNFMk13dlBUUlN5aEJHZlMzREJiM3JaSEFTL1Z1VFRmRHRnZFJJY2lyUzJBeWI4 +REE0S1lNdDlVQW5qOUxSc0NRdUNUdVVvTXhPRXdIVThRZm5MM3YvY1MrZy9vUG5L +U0crZ1hqVXozWWkyWFd6OHpWaHF1OHZJQWJoTVV2REtsWGtwTTJQVm9XTE5zQUtW +SVpKTTEvNlMxeDhnNkp2Y3ZNK1poQjd2eGdQVE1iZFVOZ24wRFd0em1wUHZtVWc2 +NHREOW00WFdlczJFODZXQ1JBY0lOY2FFYk1QaVFNeUlaUUQ5U2V0dXVQVDdUV3lq +SkRUQjNYWU0zc2lLN3ZiZ242N1cxRlZLamhZWGRCQm5tY2wyNnkzbWsyU0VPcmd4 +UVU5RDZCakRJOHhDQklTNWxnd212dXA0WWc3dCtSaUgwbG9XUE93RHEyRHBvSnJO +MEN3K0loMk4rRVNTNmgyaEpOTjFxTmN5c0pPK0NqR3U4d3ZIeG90OVlRaWptVXNE +YXJmQ3A0ZXRrZjF4Y0VkcVpnb1pqZ2M0K3k4M3Jhb1FEdz09In0= +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/vendor/tests/data/keys/rsa_private.key b/vendor/tests/data/keys/rsa_private.key new file mode 100644 index 0000000..82fb5a7 --- /dev/null +++ b/vendor/tests/data/keys/rsa_private.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAzk4HyifDz3BhYUj4YDk6SjwpZgCh1dkEaL/E1/k8VUFvDH53 +iJ/dwbIotd920y86nqA1P8JoKbTLuDXMubyCDC7Cx/zBxwVnH2F1qeB50mun2lR0 +BEWP2cp8xSIl7Ns0rDFjAX6/vfJcYrzlD/xmGNtkdeterVxHb272R63kVXOfteiQ +Xw4pvGUNvtjk4eeCp7n8U34zPLSzVa9C7gAMtDAeuWL4sQvTZCkb5dJ5HePbpdQ1 +uEHakxiCQ25x+AqvGkvcbDxad9dUm6JAwKAcfSIWsy0Ie7Pp9KeXbGxQv0Xb1kYa +6hsKtvGlKOrvu33zgwM4dfJjUXT8pkDxHdZAhT+MYfKVl1ANVSgzypezvGQY7fAc +j+LXcsVtv6q0SS7e6XyddrI/mQSZdeHD5pT5cO0uLCTJwukDD1Q800xcCz98EW4w +VPZGU33vB3MEKflt/FFsxmlMEjw62dqh7JOn4Mcmk684npOjffx13Dacrxn76k7H +dLXu0EO56qkg00gx2s7+AVAyf0MD3YvbH3rCtD0aMctfK3CSRuQZYtqXUVjsgzm3 +nHPCq8iFCGKfBdk3DADp1NlTMqCMSMuUjAk2/2BL5iCfGYEJ9+TycaLDNpBQdJVJ +vzrBsKngdy50X4kXTLIx2vss54Lk4CWs0+TX0RN7wiVJBjmZgHc5u5yJIJcCAwEA +AQKCAgB+R08HU43MxLompVa692yRkf+5Gvv0fNDxGSjxFfLzMIk7uZGLRGelr1qx +8KW4ILmd7OyLKYE+vhbQm8XDjvp/YIQDi9hE7S6xC6PNJsUKorDsuDMHhljF8+ap +d/yE3ayBFf3HJYFSUC5ylbMUNOd9oZT9hOO/87MaJ26Cc5NHJu4El+T++hlb4vMl +9XcsO9xCtFoZ9S6Bow3+jbfHHKqqBKZZzZXyMQ3kyjD0XP+b5yREff+f2FdlIGRj +yA/kxw1laDf03IB3yItWdFt0TM0DX0FLzW3a4kZ7ZbYPPMG0QpuMrf69e230izcQ +M7YoKrFKaUc/Eu3uJ1CapzevjryQePLUJdWldwVUipZeCQNKVmeu7G/a59rFTwsw +gwghk/eUh+gssSbgUVk1J7qybx1njkiAdSOSjRxLNUGkdQFpxwUiOx0WyXysIffv +qVYvYJBCD2gf9hYb0qQcdd4nud1PyECjXVS95SorvEJDCisuoBzAvMj5iHduJimm +VnUqEI5qv6ZXE9mUKZW6IISlsf7EuM6QBUKuF4VXF+ynvMGTctHqXW/eoGxnugmj +NrXMzvqOGRn5kXs5e3NM9UdyzOZqAh/L8wB5BQKnIoJm2sAZeLQaxOYdo+ntKIsh +NcFI0+KagSKGUMF2rLxOf1BOw/dE0Ir5u0avE+YgKAk7XXG0wQKCAQEA+nT0Ql2/ +PT3GqfoHyWpvcmttDPY6yTpy9TFltFABZplPpsHaWfn4wWQ3FT4aBmYF962MXgaf +LlbQMHed2F3thhTyeEJUrj/L8dK7tB9OjqGQJFlstEKXQZh+eaWlxkikY2khjao8 +B9TFcPcMmCz0EFWVIrOIDEvgbwgp5nzopAsfqz9TRadnOOgL7x8PjVWzRA/SHuEv +FcTZuTAQyXI8oRpKYLtg2q7rptKjH/tkxf7IAmCow7vk4g3Cj/ZXfXzCD6CMPuTf +X4WFqJUnBYuwlhfHLcT6vOiNLhIq7sV3wle5UGkh4GQ6qWP3/r1TKVtyW7oduIJs +m/8duw6b9Ct99wKCAQEA0t7rFwjFY98wJbGMB5dsKkA/WnTGBNSOkNf257DPyZeE +3ljqwq6yarnpB6fu989yG8KUhX81d0BtNJL+T71CwRL6LNxlDoYdKTuWZYHUFHNh +u0BRz80d7gSiPai5ZBzgFpIFaDTwMaJjX2RLGEEgcLYFD1LtsZNSyY9FC38Vr+9k +zUQ5W5rzxYGw4rRaEL0ovvjYLFz75ns2zPZapCt0c/z/Qd9kyibgGDE3bnL6gkvo +aDCkQr4Xc3qy+jJzVsv0n+lcdHBfP/qlcpEWyapXM3/a9OK/KAYsaJa/RBpA+l+H +g4YQVwwYODugSmzYKme1JU4h804EN6lHtSvzYkNKYQKCAQAjhpNfFo0Z0rlrQtv3 +5fEI+dPuEr8j6/aCcQ9MFE0ekICL1tNyD9MJG330tWpbnf0atLNEYwwRNp8xQMZS ++n/GlRIPnNkGHmZ/VrTpR8eM073uagDRUODDnS3Tc3ugNI2czDzGK294bOXUsDZJ +H5c++eS9l1mk5N5g4XeQCge1vR4w3Dqjlqs9lyyaLn22PoG/Fb9oQei73cBEVF0N +NfcDowcJ0YpbepRShW4+CxqwOwOD0tIdcXl11x3R7c9bLWcZcFx0T2Kf2gCrePyf +/MB/ib/m7hni0dm0vz73v2rNVkQi88aqXY00mcmDiLdTFnWSLUQp99YQCo/dCKV2 +bPThAoIBAQCuRH3CpoQCmoN+0zEnYPOKI1h4GANCIKvFdkVdipjeQDMVUiSJSbi3 +TPcRVa6+65ig6ni1rsBv0jWt+kDjg0S0rUtFYcq+awWUeuM69kVftU8yYeB6vEgc +2YV/MX4tB1QGMxz21rEeQ9aeEhOhcsktfK/Hz0ASve7wFk/4RUmWAWCr5tMEKpWF +Rz34zRWVuc3/rUVxvFKNUoyibIHSJPtzk8UcGlOAYQpX0+y8gZcXsUXbPT+yzMgy +rldVP/Zj5+A9e6zlqax+AlVSzicn+HdiXyqDsRRLLnbq5JIi5ROIFwS2JEhCuAMY +DebVOwiWWuiwcNbL7VC881AIoM7eCUBhAoIBAC4ZPQ0qHhKz4DUHNevoIje9aor1 +8711SwQuUpjJ3iVmhLwLPplZZaMq6dNgjPTBosZmtnjKsrR8Kwn/ZPq8siR9Ii41 +TRdvT5fiChuaJCT+cUKgvb/vri+hihxT6Sd7YCGvrN/e76Vwz1Bes1DZW7bJN6hU +3Ha8N23Juv0Sb/2zTi2snVClX76l2ftKUUHcuUPCHvhpf/T8XYSIbVxJYBSWVtTv +oxQ0q2S/Rle1u/WE0qFfZhfJscdm07Oq+OeXFa6ZiAJ1xjumi4YxpYOdBZPguVlh +E5vzvuF5lXGTDVZWBnnI1PZTc96d3NKY86K6w9dQfPhEqTM8QfR7YNqzhw8= +-----END RSA PRIVATE KEY----- diff --git a/vendor/trust_root/prod/root.json b/vendor/trust_root/prod/root.json new file mode 100644 index 0000000..2a373bd --- /dev/null +++ b/vendor/trust_root/prod/root.json @@ -0,0 +1,145 @@ +{ + "signatures": [ + { + "keyid": "6f260089d5923daf20166ca657c543af618346ab971884a99962b01988bbe0c3", + "sig": "" + }, + { + "keyid": "e71a54d543835ba86adad9460379c7641fb8726d164ea766801a1c522aba7ea2", + "sig": "3045022100b0bcf189ce1b93e7db9649d5be512a1880c0e358870e3933e426c5afb8a4061002206d214bd79b09f458ccc521a290aa960c417014fc16e606f82091b5e31814886a" + }, + { + "keyid": "22f4caec6d8e6f9555af66b3d4c3cb06a3bb23fdc7e39c916c61f462e6f52b06", + "sig": "" + }, + { + "keyid": "61643838125b440b40db6942f5cb5a31c0dc04368316eb2aaa58b95904a58222", + "sig": "3045022100a9b9e294ec21b62dfca6a16a19d084182c12572e33d9c4dcab5317fa1e8a459d022069f68e55ea1f95c5a367aac7a61a65757f93da5a006a5f4d1cf995be812d7602" + }, + { + "keyid": "a687e5bf4fab82b0ee58d46e05c9535145a2c9afb458f43d42b45ca0fdce2a70", + "sig": "30440220781178ec3915cb16aca757d40e28435ac5378d6b487acb111d1eeb339397f79a0220781cce48ae46f9e47b97a8414fcf466a986726a5896c72a0e4aba3162cb826dd" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": true, + "expires": "2025-08-19T14:33:09Z", + "keys": { + "0c87432c3bf09fd99189fdc32fa5eaedf4e4a5fac7bab73fa04a2e0fc64af6f5": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-online-uri": "gcpkms:projects/sigstore-root-signing/locations/global/keyRings/root/cryptoKeys/timestamp/cryptoKeyVersions/1" + }, + "22f4caec6d8e6f9555af66b3d4c3cb06a3bb23fdc7e39c916c61f462e6f52b06": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@santiagotorres" + }, + "61643838125b440b40db6942f5cb5a31c0dc04368316eb2aaa58b95904a58222": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@bobcallaway" + }, + "6f260089d5923daf20166ca657c543af618346ab971884a99962b01988bbe0c3": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@dlorenc" + }, + "a687e5bf4fab82b0ee58d46e05c9535145a2c9afb458f43d42b45ca0fdce2a70": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@joshuagl" + }, + "e71a54d543835ba86adad9460379c7641fb8726d164ea766801a1c522aba7ea2": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa", + "keyval": { + "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" + }, + "scheme": "ecdsa-sha2-nistp256", + "x-tuf-on-ci-keyowner": "@mnm678" + } + }, + "roles": { + "root": { + "keyids": [ + "6f260089d5923daf20166ca657c543af618346ab971884a99962b01988bbe0c3", + "e71a54d543835ba86adad9460379c7641fb8726d164ea766801a1c522aba7ea2", + "22f4caec6d8e6f9555af66b3d4c3cb06a3bb23fdc7e39c916c61f462e6f52b06", + "61643838125b440b40db6942f5cb5a31c0dc04368316eb2aaa58b95904a58222", + "a687e5bf4fab82b0ee58d46e05c9535145a2c9afb458f43d42b45ca0fdce2a70" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "0c87432c3bf09fd99189fdc32fa5eaedf4e4a5fac7bab73fa04a2e0fc64af6f5" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 3650, + "x-tuf-on-ci-signing-period": 365 + }, + "targets": { + "keyids": [ + "6f260089d5923daf20166ca657c543af618346ab971884a99962b01988bbe0c3", + "e71a54d543835ba86adad9460379c7641fb8726d164ea766801a1c522aba7ea2", + "22f4caec6d8e6f9555af66b3d4c3cb06a3bb23fdc7e39c916c61f462e6f52b06", + "61643838125b440b40db6942f5cb5a31c0dc04368316eb2aaa58b95904a58222", + "a687e5bf4fab82b0ee58d46e05c9535145a2c9afb458f43d42b45ca0fdce2a70" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "0c87432c3bf09fd99189fdc32fa5eaedf4e4a5fac7bab73fa04a2e0fc64af6f5" + ], + "threshold": 1, + "x-tuf-on-ci-expiry-period": 7, + "x-tuf-on-ci-signing-period": 6 + } + }, + "spec_version": "1.0", + "version": 12, + "x-tuf-on-ci-expiry-period": 197, + "x-tuf-on-ci-signing-period": 46 + } +} \ No newline at end of file diff --git a/vendor/trust_root/prod/trusted_root.json b/vendor/trust_root/prod/trusted_root.json new file mode 100644 index 0000000..b8706cb --- /dev/null +++ b/vendor/trust_root/prod/trusted_root.json @@ -0,0 +1,90 @@ +{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ] +}