├── clippy.toml ├── examples ├── cosign │ ├── verify-bundle │ │ ├── .gitignore │ │ ├── run.sh │ │ ├── README.md │ │ └── main.rs │ ├── verify-blob │ │ ├── .gitignore │ │ ├── main.rs │ │ └── README.md │ ├── verify │ │ └── README.md │ └── sign │ │ └── README.md ├── rekor │ ├── README.md │ ├── get_log_info │ │ └── main.rs │ ├── get_log_entry_by_index │ │ └── main.rs │ ├── get_log_entry_by_uuid │ │ └── main.rs │ ├── get_public_key │ │ └── main.rs │ ├── get_log_proof │ │ └── main.rs │ └── search_index │ │ └── main.rs ├── key_interface │ ├── key_pair_import │ │ ├── ECDSA_P256_ASN1_PRIVATE_DER.key │ │ ├── ECDSA_P256_ASN1_PUBLIC_DER.pub │ │ ├── ECDSA_P256_ASN1_PUBLIC_PEM.pub │ │ ├── ECDSA_P256_ASN1_PRIVATE_PEM.key │ │ ├── ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key │ │ └── main.rs │ ├── key_pair_gen_sign_verify │ │ └── main.rs │ ├── key_pair_gen_and_export │ │ └── main.rs │ └── README.md ├── README.md ├── openidflow │ ├── README.md │ └── openidconnect │ │ └── main.rs ├── fulcio │ └── cert │ │ └── main.rs └── bundle │ ├── README.md │ └── main.rs ├── .taplo.toml ├── tests ├── data │ └── keys │ │ ├── ed25519_private.key │ │ ├── ecdsa_private.key │ │ ├── ed25519_encrypted_private.key │ │ ├── ecdsa_encrypted_private.key │ │ ├── cosign_generated_encrypted_empty_private.key │ │ ├── rsa_private.key │ │ └── rsa_encrypted_private.key └── conformance │ ├── Cargo.toml │ └── conformance.rs ├── CODEOWNERS ├── rust-toolchain.toml ├── .cargo └── audit.toml ├── .gitignore ├── COPYRIGHT.txt ├── src ├── rekor │ ├── models │ │ ├── rpm_all_of.rs │ │ ├── jar_all_of.rs │ │ ├── tuf_all_of.rs │ │ ├── helm_all_of.rs │ │ ├── alpine_all_of.rs │ │ ├── intoto_all_of.rs │ │ ├── rekord_all_of.rs │ │ ├── rfc3161_all_of.rs │ │ ├── hashedrekord_all_of.rs │ │ ├── error.rs │ │ ├── consistency_proof.rs │ │ ├── rpm.rs │ │ ├── helm.rs │ │ ├── tuf.rs │ │ ├── jar.rs │ │ ├── rekord.rs │ │ ├── alpine.rs │ │ ├── intoto.rs │ │ ├── rfc3161.rs │ │ ├── search_index.rs │ │ ├── search_log_query.rs │ │ ├── inactive_shard_log_info.rs │ │ ├── log_info.rs │ │ ├── inclusion_proof.rs │ │ ├── search_index_public_key.rs │ │ ├── mod.rs │ │ ├── proposed_entry.rs │ │ ├── hashedrekord.rs │ │ └── log_entry.rs │ ├── apis │ │ ├── mod.rs │ │ ├── configuration.rs │ │ ├── index_api.rs │ │ ├── pubkey_api.rs │ │ └── tlog_api.rs │ └── mod.rs ├── oauth │ ├── mod.rs │ └── token.rs ├── bundle │ ├── verify │ │ └── mod.rs │ ├── mod.rs │ └── models.rs ├── cosign │ ├── payload │ │ └── mod.rs │ ├── verification_constraint │ │ ├── annotation_verifier.rs │ │ ├── public_key_verifier.rs │ │ ├── mod.rs │ │ └── cert_subject_url_verifier.rs │ ├── constants.rs │ └── constraint │ │ ├── annotation.rs │ │ ├── signature.rs │ │ └── mod.rs ├── trust │ ├── sigstore │ │ └── constants.rs │ └── mod.rs ├── registry │ ├── oci_reference.rs │ ├── oci_client.rs │ └── mod.rs ├── fulcio │ ├── models.rs │ └── oauth.rs ├── crypto │ └── certificate_pool.rs └── mock_client.rs ├── .github ├── workflows │ ├── zizmor.yml │ ├── conformance.yml │ ├── security-audit.yml │ └── auto-publish-crates-upon-release.yml └── dependabot.yml ├── Makefile ├── CODE_OF_CONDUCT.md ├── README.md ├── trust_root └── prod │ └── trusted_root.json └── CONTRIBUTORS.md /clippy.toml: -------------------------------------------------------------------------------- 1 | allow-panic-in-tests = true 2 | allow-unwrap-in-tests = true 3 | -------------------------------------------------------------------------------- /examples/cosign/verify-bundle/.gitignore: -------------------------------------------------------------------------------- 1 | artifact.bundle 2 | artifact.txt 3 | -------------------------------------------------------------------------------- /examples/cosign/verify-blob/.gitignore: -------------------------------------------------------------------------------- 1 | signature 2 | certificate 3 | artifact.txt 4 | -------------------------------------------------------------------------------- /examples/rekor/README.md: -------------------------------------------------------------------------------- 1 | # Rekor Transparency Log Client 2 | 3 | The following examples all interface with the Rekor Transparency Log Client. 4 | 5 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = [".cargo/audit.toml"] 2 | 3 | [[rule]] 4 | 5 | [rule.formatting] 6 | indent_string = " " 7 | reorder_arrays = true 8 | reorder_keys = true 9 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_DER.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-rs/HEAD/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_DER.key -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_DER.pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-rs/HEAD/examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_DER.pub -------------------------------------------------------------------------------- /tests/data/keys/ed25519_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MFECAQEwBQYDK2VwBCIEILZ7IMwQHwlrU+a5l0WnujhvsmQXrWsStjtbAj8n/EiJ 3 | gSEAXtg9yVbH8JADtC6sn4NBh1CSDWeCv0ABIj4TtoJj9ak= 4 | -----END PRIVATE KEY----- 5 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order): 2 | # 3 | # flavio 4 | # lukehinds 5 | # arsa 6 | # viccuad 7 | # Xynnn007 8 | 9 | @sigstore-rs-codeowners 10 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PUBLIC_PEM.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE10MJqZ6tgxCxOvANHgfKMY90bso8 3 | H+Iq3rPfT6GrFbYAgckw24H69hgnTHrujYAtjhK6csqLXkgwFzYh2Hdckw== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /tests/data/keys/ecdsa_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgAbUSDapDt/yShCq1 3 | rzJwhGj9fMQd21E5SXmln12o8J+hRANCAAQfFCADQhM36xItBLLsGZmMDe5hqtPc 4 | gRx8+8Zf40O4VAyyv3KO5HePY23r/kVZ+YkXwS55sYSpF5F++AQml0PP 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # Pin to a specific stable version for consistent clippy/fmt across contributors 3 | # Update this deliberately when ready to adopt new Rust features or fix new warnings 4 | # Minimum 1.89.0 required for edition 2024 support 5 | channel = "1.89.0" 6 | components = ["clippy", "rustfmt"] 7 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/ECDSA_P256_ASN1_PRIVATE_PEM.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5qt7YAIL9zSg38Pi 3 | 5DX7rHjEcQAZkDk5MulVsr6x3QehRANCAASXe5tGHMHmug4BWmGl2HtIJlG8AIEV 4 | pWZ895mqN6Yv2X6HA1n7yxjDQdqJMmFmvQm9C7Z8HGR3kbj1LQyi+DaY 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /examples/cosign/verify-bundle/run.sh: -------------------------------------------------------------------------------- 1 | BLOB="README.md" 2 | BUNDLE="artifact.bundle" 3 | 4 | echo -e "\nSign README.md file using sign-blob" 5 | cosign sign-blob --bundle=$BUNDLE $BLOB 6 | 7 | echo -e "\nRun examples/cosign/verify-bundle" 8 | cargo run --example verify-bundle -- \ 9 | --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ 10 | --bundle $BUNDLE \ 11 | $BLOB 12 | -------------------------------------------------------------------------------- /.cargo/audit.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | ignore = [ 3 | "RUSTSEC-2023-0071", # "Classic" RSA timing sidechannel attack from non-constant-time implementation. 4 | # Okay for local use. 5 | # https://rustsec.org/advisories/RUSTSEC-2023-0071.html 6 | "RUSTSEC-2024-0370", # This is a warning about `proc-macro-errors` being unmaintained. It's a transitive dependency of `sigstore` and `oci-spec`. 7 | ] 8 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # sigstore-rs code examples 2 | 3 | This folder contains executable examples of the sigstore-rs library. 4 | 5 | To run any given example, simply provide the subfolder name as an argument to the `cargo run` command. 6 | 7 | ```bash 8 | cargo run --example --all-features 9 | ``` 10 | 11 | e.g. 12 | 13 | ```bash 14 | cargo run --example create_log_entry --all-features 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | 4 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 5 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 6 | Cargo.lock 7 | 8 | # These are backup files generated by rustfmt 9 | **/*.rs.bk 10 | 11 | # MSVC Windows builds of rustc generate these, which store debugging information 12 | *.pdb 13 | 14 | # ide files 15 | .idea 16 | .vscode -------------------------------------------------------------------------------- /examples/cosign/verify-bundle/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to verify a blob, using a bundle that was created by the 2 | `cosign sign-blob` command. 3 | 4 | ### Sign README.md file using cosign 5 | ``` 6 | cd examples/cosign/verify-bundle 7 | cosign sign-blob --bundle=artifact.bundle README.md 8 | ``` 9 | 10 | ### Verify using sigstore-rs: 11 | ```console 12 | cargo run --example verify-bundle -- \ 13 | --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ 14 | --bundle artifact.bundle \ 15 | README.md 16 | ``` 17 | -------------------------------------------------------------------------------- /tests/conformance/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["sigstore-rs developers"] 3 | description = "sigstore conformance testing workflow" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | name = "sigstore-conformance" 7 | version = "0.0.1" 8 | 9 | [dependencies] 10 | anyhow = "1.0.75" 11 | clap = { version = "4.0.8", features = ["derive"] } 12 | serde_json = "1.0.107" 13 | sigstore = { path = "../../", default-features = false, features = [ 14 | "full", 15 | "native-tls", 16 | ] } 17 | tracing-subscriber = "0.3" 18 | 19 | [[bin]] 20 | name = "sigstore" 21 | path = "conformance.rs" 22 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2021 The Sigstore Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /tests/data/keys/ed25519_encrypted_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiJ1elF5Snc4b1FjWCt1T3RlWDljSHpVOEhycWlJVzJV 4 | ck9YbnpJb1ljaUtJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJKeUJDMS81K2E3emtxdnE5dVpTaHIvZDRwNXVjQW1WQSJ9LCJj 6 | aXBoZXJ0ZXh0IjoiZC9TMndRaVJ0YjcwazFmZEd0ZzRFa0RXZ0ZJcU9NRWd3TDNI 7 | bUtzWjBpdDlJcTFRSVplUGJoYWRTKzJORUZzWVA4TlhVOGpoT1dtb0QreWZwbkMz 8 | dTBYQTRvcFBib2JwbEpleWJEUUtQdW1sa0tiRUc5MnRCa3pUMFJwNnA1dDdFeVVk 9 | In0= 10 | -----END ENCRYPTED SIGSTORE PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /tests/data/keys/ecdsa_encrypted_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiIrRFJrTXNRZmNUNTNKNCs1Y3VkUnlnWTUyWUMrK0RW 4 | TmxaSTN4VnlwWFlvPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJnNTFORnBqQUxlemhvbGVWTWVjUFJUNUErMVhMcDAxeiJ9LCJj 6 | aXBoZXJ0ZXh0Ijoib0YvN09mOGlQRDlkNlc2Mk5SWGVibEszWXZmNFFucTZORm16 7 | Q0VyV2FTVUJOZTNXSFJiRVNYK2dJOCszejg4TDBDVXlWdm9LTHhuR0xhVnhnczVD 8 | YUdyekcrMWxFTEI1cGRMVmZXd1VlQzlLelNab0hxZHZLaUsvUmNodVhpS0VqYmdv 9 | ajNGd2pMcmhEYkY5a2lVWk93REpuT01kUkoxNnI3emxjRHdwT3htOXFrbWFDWDlB 10 | VjQ4b1RXVG0rQXExOHlDZE50MDYybGdudVE9PSJ9 11 | -----END ENCRYPTED COSIGN PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /src/rekor/models/rpm_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 13 | pub struct RpmAllOf { 14 | #[serde(rename = "apiVersion")] 15 | pub api_version: String, 16 | #[serde(rename = "spec")] 17 | pub spec: serde_json::Value, 18 | } 19 | 20 | impl RpmAllOf { 21 | pub fn new(api_version: String, spec: serde_json::Value) -> RpmAllOf { 22 | RpmAllOf { api_version, spec } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/data/keys/cosign_generated_encrypted_empty_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 3 | OCwicCI6MX0sInNhbHQiOiJFY3pJR3Z0bnpFcCsxaEpudERnbGI1UnoxN3gwQVMy 4 | YklvWGRNcDQrU1NJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJtSVVKbG1OMzNINExvZ0dhaG5MamdmK3R4SmJwci93MCJ9LCJj 6 | aXBoZXJ0ZXh0IjoiM3FtS2FidXRuYm1WT1IvTkZGM2NDaUErTXp1WWQ5L0VERDVI 7 | MVlUSGhQVzZsSzAvdjVqdDZCQzlsME12NGV5am9qZkl0d3B6a2JJOXQrZGx5VXZ3 8 | VUgvMWZjbkFZT2dEdXRLQzkvSkNiOE02SVY2VHpVaDF5c0ZFWDFzeG9xb1FzeUpL 9 | bjM0UVlldFNlaVdDaGtmUHUyZXByQjFEV0pwekdzTGZIakxNVTkzOEdmNk1xM1A0 10 | N0ZSd2syZzY1cFZnSGMwVWV1L1N1OUs0Zmc9PSJ9 11 | -----END ENCRYPTED SIGSTORE PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /src/rekor/models/jar_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct JarAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl JarAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> JarAllOf { 23 | JarAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rekor/models/tuf_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct TufAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl TufAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> TufAllOf { 23 | TufAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/oauth/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | pub mod openidflow; 17 | 18 | pub mod token; 19 | pub use token::IdentityToken; 20 | -------------------------------------------------------------------------------- /src/rekor/models/helm_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct HelmAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl HelmAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> HelmAllOf { 23 | HelmAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiI3eWtwR1NRNVBpVWJZb1B0eXZ4cnhHWjcrM2NtZmdM 4 | WmwwQnhDQlVHRUdZPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiI0N2dPOVNCd0FueXVOZlQxVzlzcFVYOGxCREVnckgwZyJ9LCJj 6 | aXBoZXJ0ZXh0IjoiRHhmenJlWis5eXNLdVlHOU02L0F1dVl1bVVFa2tHcFdWUmhK 7 | V242dGp0VW1RTzBqNTJSa1RXZTYwVTk2bFMzelVldm9BUGU2MjhPMHFQODlkdkwv 8 | MEJObjljdWZYUVZObnAxVWJsUVBaQm9tRFRsUFR6NnZFa3doMS9XTFhTa2NKekdW 9 | ZnpqN3ZQSDRaVFhqRjBVRnRqYzZ6QmhqZFk5ZjhnRklzVERiYSs3S013eWxPUGhW 10 | ditpakl3R0R5Zk43RGNlWTJzYTdNZHM5Q2c9PSJ9 11 | -----END ENCRYPTED COSIGN PRIVATE KEY----- 12 | -------------------------------------------------------------------------------- /src/rekor/models/alpine_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct AlpineAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl AlpineAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> AlpineAllOf { 23 | AlpineAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rekor/models/intoto_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct IntotoAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl IntotoAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> IntotoAllOf { 23 | IntotoAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rekor/models/rekord_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct RekordAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl RekordAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> RekordAllOf { 23 | RekordAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rekor/models/rfc3161_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct Rfc3161AllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl Rfc3161AllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> Rfc3161AllOf { 23 | Rfc3161AllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/openidflow/README.md: -------------------------------------------------------------------------------- 1 | # Open ID Connect Flow for Fulcio Signing Certificates 2 | 3 | This is an example of the fulcio OpenID connect flow. 4 | 5 | The general idea is to return an access_token and the email via a scope. 6 | 7 | Both values can then be made to form a POST request to fulcio for a software 8 | signing certificate 9 | 10 | `cargo run --example openidconnect --all-features` 11 | 12 | The implementation contains a `redirect_listener` function that will create 13 | a local listening server to incept the ID token and scopes returned from 14 | sigstores OIDC service. However should you prefer, you can implement your 15 | own redirect service and simply pass along the required values: 16 | 17 | * client: CoreClient, 18 | * nonce: Nonce, 19 | * pkce_verifier: PkceCodeVerifier -------------------------------------------------------------------------------- /src/rekor/models/hashedrekord_all_of.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct HashedrekordAllOf { 15 | #[serde(rename = "apiVersion")] 16 | pub api_version: String, 17 | #[serde(rename = "spec")] 18 | pub spec: serde_json::Value, 19 | } 20 | 21 | impl HashedrekordAllOf { 22 | pub fn new(api_version: String, spec: serde_json::Value) -> HashedrekordAllOf { 23 | HashedrekordAllOf { api_version, spec } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rekor/models/error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct Error { 15 | #[serde(rename = "code", skip_serializing_if = "Option::is_none")] 16 | pub code: Option, 17 | #[serde(rename = "message", skip_serializing_if = "Option::is_none")] 18 | pub message: Option, 19 | } 20 | 21 | impl Error { 22 | pub fn new() -> Error { 23 | Error { 24 | code: None, 25 | message: None, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/rekor/models/consistency_proof.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct ConsistencyProof { 15 | /// The hash value stored at the root of the merkle tree at the time the proof was generated 16 | #[serde(rename = "rootHash")] 17 | pub root_hash: String, 18 | #[serde(rename = "hashes")] 19 | pub hashes: Vec, 20 | } 21 | 22 | impl ConsistencyProof { 23 | pub fn new(root_hash: String, hashes: Vec) -> ConsistencyProof { 24 | ConsistencyProof { root_hash, hashes } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: 'zizmor: GitHub Actions Security Analysis' 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | zizmor: 18 | name: Zizmor 19 | runs-on: ubuntu-24.04 20 | permissions: 21 | security-events: write # needed to create vulnerability alerts 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 25 | with: 26 | persist-credentials: false 27 | 28 | - name: Run zizmor 🌈 29 | uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0 30 | with: 31 | persona: pedantic 32 | -------------------------------------------------------------------------------- /src/rekor/models/rpm.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// Rpm : RPM package 14 | 15 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 16 | pub struct Rpm { 17 | #[serde(rename = "kind")] 18 | pub kind: String, 19 | #[serde(rename = "apiVersion")] 20 | pub api_version: String, 21 | #[serde(rename = "spec")] 22 | pub spec: serde_json::Value, 23 | } 24 | 25 | impl Rpm { 26 | /// RPM package 27 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rpm { 28 | Rpm { 29 | kind, 30 | api_version, 31 | spec, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/rekor/models/helm.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// Helm : Helm chart 14 | 15 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 16 | pub struct Helm { 17 | #[serde(rename = "kind")] 18 | pub kind: String, 19 | #[serde(rename = "apiVersion")] 20 | pub api_version: String, 21 | #[serde(rename = "spec")] 22 | pub spec: serde_json::Value, 23 | } 24 | 25 | impl Helm { 26 | /// Helm chart 27 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Helm { 28 | Helm { 29 | kind, 30 | api_version, 31 | spec, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/rekor/models/tuf.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// Tuf : TUF metadata 14 | 15 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 16 | pub struct Tuf { 17 | #[serde(rename = "kind")] 18 | pub kind: String, 19 | #[serde(rename = "apiVersion")] 20 | pub api_version: String, 21 | #[serde(rename = "spec")] 22 | pub spec: serde_json::Value, 23 | } 24 | 25 | impl Tuf { 26 | /// TUF metadata 27 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Tuf { 28 | Tuf { 29 | kind, 30 | api_version, 31 | spec, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/rekor/models/jar.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | /// Jar : Java Archive (JAR) 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct Jar { 16 | #[serde(rename = "kind")] 17 | pub kind: String, 18 | #[serde(rename = "apiVersion")] 19 | pub api_version: String, 20 | #[serde(rename = "spec")] 21 | pub spec: serde_json::Value, 22 | } 23 | 24 | impl Jar { 25 | /// Java Archive (JAR) 26 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Jar { 27 | Jar { 28 | kind, 29 | api_version, 30 | spec, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/rekor/models/rekord.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | /// Rekord : Rekord object 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct Rekord { 16 | #[serde(rename = "kind")] 17 | pub kind: String, 18 | #[serde(rename = "apiVersion")] 19 | pub api_version: String, 20 | #[serde(rename = "spec")] 21 | pub spec: serde_json::Value, 22 | } 23 | 24 | impl Rekord { 25 | /// Rekord object 26 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rekord { 27 | Rekord { 28 | kind, 29 | api_version, 30 | spec, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/rekor/models/alpine.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// Alpine : Alpine package 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct Alpine { 16 | #[serde(rename = "kind")] 17 | pub kind: String, 18 | #[serde(rename = "apiVersion")] 19 | pub api_version: String, 20 | #[serde(rename = "spec")] 21 | pub spec: serde_json::Value, 22 | } 23 | 24 | impl Alpine { 25 | /// Alpine package 26 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Alpine { 27 | Alpine { 28 | kind, 29 | api_version, 30 | spec, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/rekor/models/intoto.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// Intoto : Intoto object 14 | 15 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 16 | pub struct Intoto { 17 | #[serde(rename = "kind")] 18 | pub kind: String, 19 | #[serde(rename = "apiVersion")] 20 | pub api_version: String, 21 | #[serde(rename = "spec")] 22 | pub spec: serde_json::Value, 23 | } 24 | 25 | impl Intoto { 26 | /// Intoto object 27 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Intoto { 28 | Intoto { 29 | kind, 30 | api_version, 31 | spec, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/rekor/models/rfc3161.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | /// Rfc3161 : RFC3161 Timestamp 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct Rfc3161 { 16 | #[serde(rename = "kind")] 17 | pub kind: String, 18 | #[serde(rename = "apiVersion")] 19 | pub api_version: String, 20 | #[serde(rename = "spec")] 21 | pub spec: serde_json::Value, 22 | } 23 | 24 | impl Rfc3161 { 25 | /// RFC3161 Timestamp 26 | pub fn new(kind: String, api_version: String, spec: serde_json::Value) -> Rfc3161 { 27 | Rfc3161 { 28 | kind, 29 | api_version, 30 | spec, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/bundle/verify/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Types for verifying Sigstore bundles with policies. 17 | 18 | mod models; 19 | 20 | pub use models::{VerificationError, VerificationResult}; 21 | 22 | pub mod policy; 23 | pub use policy::{PolicyError, VerificationPolicy}; 24 | 25 | mod verifier; 26 | pub use verifier::*; 27 | -------------------------------------------------------------------------------- /examples/cosign/verify/README.md: -------------------------------------------------------------------------------- 1 | This is a simple example program that shows how perform cosign verification. 2 | 3 | The program allows also to use annotation, in the same way as `cosign verify -a key=value` 4 | does. 5 | 6 | The program prints to the standard output all the Simple Signing objects that 7 | have been successfully verified. 8 | 9 | # Key based verification 10 | 11 | Create a keypair using the official cosign client: 12 | 13 | ```console 14 | cosign generate-key-pair 15 | ``` 16 | 17 | Sign a container image: 18 | 19 | ```console 20 | cosign sign --key cosign.key registry-testing.svc.lan/busybox 21 | ``` 22 | 23 | Verify the image signature using the example program defined in 24 | [main.rs](./main.rs): 25 | 26 | ```console 27 | cargo run --example verify \ 28 | --all-features \ 29 | -- \ 30 | -k cosign.pub \ 31 | --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ 32 | --fulcio-cert ~/.sigstore/root/targets/fulcio.crt.pem \ 33 | registry-testing.svc.lan/busybox 34 | ``` 35 | -------------------------------------------------------------------------------- /src/bundle/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Sigstore Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Sigstore bundle support. 16 | 17 | pub use sigstore_protobuf_specs::dev::sigstore::bundle::v1::Bundle; 18 | 19 | mod models; 20 | 21 | #[cfg_attr(docsrs, doc(cfg(feature = "sign")))] 22 | #[cfg(feature = "sign")] 23 | pub mod sign; 24 | 25 | #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] 26 | #[cfg(feature = "verify")] 27 | pub mod verify; 28 | -------------------------------------------------------------------------------- /.github/workflows/conformance.yml: -------------------------------------------------------------------------------- 1 | on: [workflow_dispatch] 2 | 3 | name: Conformance Suite 4 | 5 | permissions: 6 | contents: read 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | conformance: 14 | name: Check sigstore conformance 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 18 | with: 19 | persist-credentials: false 20 | - name: Rustup 21 | run: | 22 | rustup install --profile minimal stable 23 | rustup override set stable 24 | - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 25 | - run: | 26 | cargo build --manifest-path=tests/conformance/Cargo.toml 27 | - uses: sigstore/sigstore-conformance@main 28 | with: 29 | entrypoint: ${{ github.workspace }}/tests/conformance/target/debug/sigstore 30 | -------------------------------------------------------------------------------- /src/cosign/payload/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! This module defines different kinds of payload to be signed 17 | //! in cosign. Now it supports: 18 | //! * `SimpleSigning`: Refer to 19 | 20 | pub mod simple_signing; 21 | pub use simple_signing::SimpleSigning; 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 The Sigstore Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: 2 17 | updates: 18 | - package-ecosystem: cargo 19 | directory: "/" 20 | schedule: 21 | interval: weekly 22 | open-pull-requests-limit: 10 23 | cooldown: 24 | default-days: 7 25 | - package-ecosystem: "github-actions" 26 | directory: "/" 27 | schedule: 28 | interval: "weekly" 29 | open-pull-requests-limit: 10 30 | cooldown: 31 | default-days: 7 32 | -------------------------------------------------------------------------------- /src/rekor/models/search_index.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct SearchIndex { 15 | #[serde(rename = "email", skip_serializing_if = "Option::is_none")] 16 | pub email: Option, 17 | #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] 18 | pub public_key: Option, 19 | #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] 20 | pub hash: Option, 21 | } 22 | 23 | impl SearchIndex { 24 | pub fn new() -> SearchIndex { 25 | SearchIndex { 26 | email: None, 27 | public_key: None, 28 | hash: None, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | push: 6 | paths: 7 | - "**/Cargo.toml" 8 | - "**/Cargo.lock" 9 | 10 | permissions: {} 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | audit: 18 | name: Audit for vulnerable crates 19 | permissions: 20 | checks: write # for rustsec/audit-check to create check 21 | contents: read # for actions/checkout to fetch code 22 | issues: write # for rustsec/audit-check to create issues 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 26 | with: 27 | persist-credentials: false 28 | - name: Generate lockfile 29 | run: cargo generate-lockfile 30 | - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /src/rekor/models/search_log_query.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] 14 | pub struct SearchLogQuery { 15 | #[serde(rename = "entryUUIDs", skip_serializing_if = "Option::is_none")] 16 | pub entry_uuids: Option>, 17 | #[serde(rename = "logIndexes", skip_serializing_if = "Option::is_none")] 18 | pub log_indexes: Option>, 19 | #[serde(rename = "entries", skip_serializing_if = "Option::is_none")] 20 | pub entries: Option>, 21 | } 22 | 23 | impl SearchLogQuery { 24 | pub fn new() -> SearchLogQuery { 25 | SearchLogQuery { 26 | entry_uuids: None, 27 | log_indexes: None, 28 | entries: None, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/auto-publish-crates-upon-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish-Crate-Upon-Release 2 | on: 3 | release: 4 | types: [published] 5 | 6 | permissions: 7 | contents: read 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | publish-automatically: 15 | name: Publish crates 16 | runs-on: ubuntu-latest 17 | permissions: 18 | id-token: write # needed to get OpenID Connect token for authentication 19 | 20 | steps: 21 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 22 | with: 23 | persist-credentials: false 24 | 25 | - uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1.0.3 26 | id: auth 27 | 28 | - name: Rustup 29 | run: | 30 | rustup install stable 31 | rustup override set stable 32 | 33 | - name: publish crates 34 | env: 35 | CARGO_REGISTRY_TOKEN: "${{ steps.auth.outputs.token }}" 36 | run: cargo publish 37 | -------------------------------------------------------------------------------- /src/cosign/verification_constraint/annotation_verifier.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use super::VerificationConstraint; 4 | use crate::cosign::signature_layers::SignatureLayer; 5 | use crate::errors::Result; 6 | 7 | /// Verification Constraint for the annotations added by `cosign sign` 8 | /// 9 | /// The `SimpleSigning` object produced at signature time can be enriched by 10 | /// signer with so called "anntoations". 11 | /// 12 | /// This constraint ensures that all the annotations specified by the user are 13 | /// found inside of the SignatureLayer. 14 | /// 15 | /// It's perfectly find for the SignatureLayer to have additional annotations. 16 | /// These will be simply be ignored by the verifier. 17 | #[derive(Default, Debug)] 18 | pub struct AnnotationVerifier { 19 | pub annotations: BTreeMap, 20 | } 21 | 22 | impl VerificationConstraint for AnnotationVerifier { 23 | fn verify(&self, signature_layer: &SignatureLayer) -> Result { 24 | let verified = signature_layer 25 | .simple_signing 26 | .satisfies_annotations(&self.annotations); 27 | Ok(verified) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/rekor/apis/mod.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct ResponseContent { 5 | pub status: reqwest::StatusCode, 6 | pub content: String, 7 | pub entity: Option, 8 | } 9 | 10 | #[derive(Error, Debug)] 11 | pub enum Error { 12 | #[error("error in reqwest: {source:?}")] 13 | Reqwest { 14 | #[from] 15 | source: reqwest::Error, 16 | }, 17 | 18 | #[error("error in serde: {source:?}")] 19 | Serde { 20 | #[from] 21 | source: serde_json::Error, 22 | }, 23 | 24 | #[error("error in IO: {source:?}")] 25 | Io { 26 | #[from] 27 | source: std::io::Error, 28 | }, 29 | 30 | #[error("error in response: status code {:?}", error_status(.0))] 31 | ResponseError(ResponseContent), 32 | } 33 | 34 | #[inline] 35 | fn error_status(response: &ResponseContent) -> reqwest::StatusCode { 36 | response.status 37 | } 38 | 39 | pub fn urlencode>(s: T) -> String { 40 | ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() 41 | } 42 | 43 | pub mod configuration; 44 | pub mod entries_api; 45 | pub mod index_api; 46 | pub mod pubkey_api; 47 | pub mod tlog_api; 48 | -------------------------------------------------------------------------------- /examples/fulcio/cert/main.rs: -------------------------------------------------------------------------------- 1 | use pkcs8::der::Decode; 2 | use sigstore::crypto::SigningScheme; 3 | use sigstore::fulcio::oauth::OauthTokenProvider; 4 | use sigstore::fulcio::{FULCIO_ROOT, FulcioClient, TokenProvider}; 5 | use url::Url; 6 | use x509_cert::Certificate; 7 | use x509_cert::ext::pkix::SubjectAltName; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | let fulcio = FulcioClient::new( 12 | Url::parse(FULCIO_ROOT).unwrap(), 13 | TokenProvider::Oauth(OauthTokenProvider::default()), 14 | ); 15 | 16 | if let Ok((_signer, cert)) = fulcio 17 | .request_cert(SigningScheme::ECDSA_P256_SHA256_ASN1) 18 | .await 19 | { 20 | println!("Received certificate chain"); 21 | 22 | let pems = pem::parse_many(cert.as_ref()).expect("parse pem failed"); 23 | for pem in &pems { 24 | let cert = Certificate::from_der(pem.contents()).expect("parse certificate from der"); 25 | 26 | let (_, san) = cert 27 | .tbs_certificate 28 | .get::() 29 | .expect("get SAN failed") 30 | .expect("No SAN found"); 31 | 32 | for name in &san.0 { 33 | println!("SAN: {name:?}"); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/rekor/models/inactive_shard_log_info.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use crate::rekor::TreeSize; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct InactiveShardLogInfo { 16 | /// The current hash value stored at the root of the merkle tree 17 | #[serde(rename = "rootHash")] 18 | pub root_hash: String, 19 | /// The current number of nodes in the merkle tree 20 | #[serde(rename = "treeSize")] 21 | pub tree_size: TreeSize, 22 | /// The current signed tree head 23 | #[serde(rename = "signedTreeHead")] 24 | pub signed_tree_head: String, 25 | /// The current treeID 26 | #[serde(rename = "treeID")] 27 | pub tree_id: String, 28 | } 29 | 30 | impl InactiveShardLogInfo { 31 | pub fn new( 32 | root_hash: String, 33 | tree_size: TreeSize, 34 | signed_tree_head: String, 35 | tree_id: String, 36 | ) -> InactiveShardLogInfo { 37 | InactiveShardLogInfo { 38 | root_hash, 39 | tree_size, 40 | signed_tree_head, 41 | tree_id, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | cargo build --release 4 | 5 | .PHONY: fmt 6 | fmt: 7 | cargo fmt --all -- --check 8 | taplo fmt --check 9 | 10 | .PHONY: lint 11 | lint: 12 | cargo clippy --all-targets -- -D warnings 13 | 14 | .PHONY: doc 15 | doc: 16 | RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc --all-features --no-deps 17 | 18 | .PHONY: check-features 19 | check-features: 20 | cargo hack check --each-feature --skip cosign --skip full --skip mock-client --skip registry 21 | 22 | .PHONY: check-features-native-tls 23 | check-features-native-tls: 24 | cargo hack check --feature-powerset --features native-tls --skip wasm --skip test-registry --skip rustls-tls --skip rustls-tls-native-roots 25 | 26 | .PHONY: check-features-rustls-tls 27 | check-features-rustls-tls: 28 | cargo hack check --feature-powerset --features rustls-tls --skip wasm --skip test-registry --skip native-tls --skip rustls-tls-native-roots 29 | 30 | .PHONY: check-wasm 31 | check-wasm: 32 | cargo check --no-default-features --features wasm --target wasm32-unknown-unknown 33 | 34 | .PHONY: test 35 | test: fmt lint doc 36 | cargo test --workspace --no-default-features --features full,native-tls,test-registry 37 | cargo test --workspace --no-default-features --features full,rustls-tls,test-registry 38 | 39 | .PHONY: clean 40 | clean: 41 | cargo clean 42 | 43 | .PHONY: coverage 44 | coverage: 45 | cargo tarpaulin -o Html 46 | -------------------------------------------------------------------------------- /examples/bundle/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to sign and verify Sigstore signature bundles. The bundle 2 | format used here is supported by most Sigstore clients but notably cosign requires 3 | `--new-bundle-format` to do so. 4 | 5 | This example uses `sigstore::bundle` for signing and verification. The sign subcommand uses 6 | `sigstore::oauth` for interactive OIDC authorization. In addition to the bundle format, a 7 | notable difference compared to the "cosign" examples is that `sigstore::bundle` also handles 8 | the Sigstore trust root update before signing or verifying. 9 | 10 | ### Sign README.md 11 | 12 | ```console 13 | cargo run --example bundle \ 14 | sign README.md 15 | ``` 16 | 17 | A browser window will be opened to authorize signing with an OIDC identity. 18 | After the authorization the signature bundle is created in `README.md.sigstore.json`. 19 | 20 | ### Verify README.md using the signature bundle 21 | 22 | ```console 23 | cargo run --example bundle \ 24 | verify --identity --issuer README.md 25 | ```console 26 | 27 | `EMAIL` is the email address of the OIDC account and is the OIDC issuer URI that were used 28 | during signing. As an example `cargo run --example bundle verify --identity name@example.com --issuer https://github.com/login/oauth README.md` 29 | verifies that the bundle `README.md.sigstore.json` was signed by "name@example.com" as authenticated by GitHub. 30 | -------------------------------------------------------------------------------- /src/trust/sigstore/constants.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | pub(crate) const SIGSTORE_METADATA_BASE: &str = "https://tuf-repo-cdn.sigstore.dev"; 17 | pub(crate) const SIGSTORE_TARGET_BASE: &str = "https://tuf-repo-cdn.sigstore.dev/targets"; 18 | 19 | macro_rules! impl_static_resource { 20 | {$($name:literal,)+} => { 21 | #[inline] 22 | pub(crate) fn static_resource(name: N) -> Option<&'static [u8]> where N: AsRef { 23 | match name.as_ref() { 24 | $( 25 | $name => Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/trust_root/prod/", $name))) 26 | ),+, 27 | _ => None, 28 | } 29 | } 30 | }; 31 | } 32 | 33 | impl_static_resource! { 34 | "root.json", 35 | "trusted_root.json", 36 | } 37 | -------------------------------------------------------------------------------- /examples/rekor/get_log_info/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use sigstore::rekor::apis::{configuration::Configuration, tlog_api}; 17 | use sigstore::rekor::models::LogInfo; 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | /* 22 | 23 | Gets information about the current state of the transparency log. 24 | Returns the current root hash and size of the merkle tree used to store the log entries. 25 | 26 | Example command : 27 | cargo run --example get_log_info 28 | 29 | The server might return an error sometimes, 30 | this is because the result depends on the kind of rekor object that gets returned. 31 | 32 | */ 33 | let configuration = Configuration::default(); 34 | let log_info: LogInfo = tlog_api::get_log_info(&configuration).await.unwrap(); 35 | println!("{:#?}", log_info); 36 | } 37 | -------------------------------------------------------------------------------- /src/rekor/models/log_info.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use crate::rekor::TreeSize; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct LogInfo { 16 | /// The current hash value stored at the root of the merkle tree 17 | #[serde(rename = "rootHash")] 18 | pub root_hash: String, 19 | /// The current number of nodes in the merkle tree 20 | #[serde(rename = "treeSize")] 21 | pub tree_size: TreeSize, 22 | /// The current signed tree head 23 | #[serde(rename = "signedTreeHead")] 24 | pub signed_tree_head: String, 25 | /// The current treeID 26 | #[serde(rename = "treeID")] 27 | pub tree_id: Option, 28 | #[serde(rename = "inactiveShards", skip_serializing_if = "Option::is_none")] 29 | pub inactive_shards: Option>, 30 | } 31 | 32 | impl LogInfo { 33 | pub fn new(root_hash: String, tree_size: TreeSize, signed_tree_head: String) -> LogInfo { 34 | LogInfo { 35 | root_hash, 36 | tree_size, 37 | signed_tree_head, 38 | tree_id: None, 39 | inactive_shards: None, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/rekor/models/inclusion_proof.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use crate::rekor::TreeSize; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 15 | pub struct InclusionProof { 16 | /// The index of the entry in the transparency log 17 | #[serde(rename = "logIndex")] 18 | pub log_index: i64, 19 | /// The hash value stored at the root of the merkle tree at the time the proof was generated 20 | #[serde(rename = "rootHash")] 21 | pub root_hash: String, 22 | /// The size of the merkle tree at the time the inclusion proof was generated 23 | #[serde(rename = "treeSize")] 24 | pub tree_size: TreeSize, 25 | /// A list of hashes required to compute the inclusion proof, sorted in order from leaf to root 26 | #[serde(rename = "hashes")] 27 | pub hashes: Vec, 28 | } 29 | 30 | impl InclusionProof { 31 | pub fn new( 32 | log_index: i64, 33 | root_hash: String, 34 | tree_size: TreeSize, 35 | hashes: Vec, 36 | ) -> InclusionProof { 37 | InclusionProof { 38 | log_index, 39 | root_hash, 40 | tree_size, 41 | hashes, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/rekor/models/search_index_public_key.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 14 | pub struct SearchIndexPublicKey { 15 | #[serde(rename = "format")] 16 | pub format: Format, 17 | #[serde(rename = "content", skip_serializing_if = "Option::is_none")] 18 | pub content: Option, 19 | #[serde(rename = "url", skip_serializing_if = "Option::is_none")] 20 | pub url: Option, 21 | } 22 | 23 | impl SearchIndexPublicKey { 24 | pub fn new(format: Format) -> SearchIndexPublicKey { 25 | SearchIndexPublicKey { 26 | format, 27 | content: None, 28 | url: None, 29 | } 30 | } 31 | } 32 | 33 | /// The supported pluggable types to sign and upload data 34 | #[derive( 35 | Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, 36 | )] 37 | pub enum Format { 38 | #[serde(rename = "pgp")] 39 | #[default] 40 | Pgp, 41 | #[serde(rename = "x509")] 42 | X509, 43 | #[serde(rename = "minisign")] 44 | Minisign, 45 | #[serde(rename = "ssh")] 46 | Ssh, 47 | #[serde(rename = "tuf")] 48 | Tuf, 49 | } 50 | -------------------------------------------------------------------------------- /src/rekor/apis/configuration.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct Configuration { 15 | pub base_path: String, 16 | pub user_agent: Option, 17 | pub client: reqwest::Client, 18 | pub basic_auth: Option, 19 | pub oauth_access_token: Option, 20 | pub bearer_access_token: Option, 21 | pub api_key: Option, 22 | // TODO: take an oauth2 token source, similar to the go one 23 | } 24 | 25 | pub type BasicAuth = (String, Option); 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct ApiKey { 29 | pub prefix: Option, 30 | pub key: String, 31 | } 32 | 33 | impl Configuration { 34 | pub fn new() -> Configuration { 35 | Configuration::default() 36 | } 37 | } 38 | 39 | impl Default for Configuration { 40 | fn default() -> Self { 41 | Configuration { 42 | base_path: "https://rekor.sigstore.dev".to_owned(), 43 | user_agent: Some(format!("sigstore-rs/{}", VERSION.unwrap_or("unknown"))), 44 | client: reqwest::Client::new(), 45 | basic_auth: None, 46 | oauth_access_token: None, 47 | bearer_access_token: None, 48 | api_key: None, 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/cosign/sign/README.md: -------------------------------------------------------------------------------- 1 | This is a simple example program that shows how perform cosign signing. 2 | 3 | The program allows also to use annotation, in the same way as `cosign sign -a key=value` 4 | does. 5 | 6 | The program prints to the standard output all the Simple Signing objects that 7 | have been successfully pushed. 8 | 9 | # Key based Signing 10 | 11 | The implementation is in [main.rs](./main.rs). 12 | 13 | Create a keypair using the official cosign client: 14 | 15 | ```console 16 | cosign generate-key-pair 17 | ``` 18 | 19 | Because the default key pair generated by cosign is `ECDSA_P256` key, 20 | so we choose to use `ECDSA_P256_SHA256_ASN1` as the signing scheme. 21 | Suppose the password used to encrypt the private key is `123`, and the target 22 | image to be signed is `172.17.0.2:5000/ubuntu` 23 | 24 | Also, let us the annotation `a=1`. 25 | 26 | Sign a container image: 27 | 28 | ```console 29 | cargo run --example sign \ 30 | --all-features \ 31 | -- \ 32 | --key cosign.key \ 33 | --image 172.17.0.2:5000/ubuntu \ 34 | --signing-scheme ECDSA_P256_SHA256_ASN1 \ 35 | --password 123 \ 36 | --verbose \ 37 | --http \ 38 | --annotations a=1 39 | ``` 40 | 41 | Then the image will be signed. 42 | 43 | Let us then verify it. 44 | 45 | 1. Using `cosign` (golang version) 46 | ```console 47 | cosign verify --key cosign.pub \ 48 | -a a=1 \ 49 | 172.17.0.2:5000/ubuntu 50 | ``` 51 | 52 | 2. Or use `sigstore-rs` 53 | ```console 54 | cargo run --example verify -- \ 55 | --key cosign.pub \ 56 | --annotations a=1 \ 57 | --http \ 58 | 172.17.0.2:5000/ubuntu 59 | ``` -------------------------------------------------------------------------------- /examples/key_interface/key_pair_gen_sign_verify/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::{Result, anyhow}; 17 | use sigstore::crypto::{Signature, SigningScheme}; 18 | 19 | const DATA_TO_BE_SIGNED: &str = "this is an example data to be signed"; 20 | 21 | fn main() -> Result<()> { 22 | let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer()?; 23 | println!("Created a new key pair for ECDSA_P256_SHA256_ASN1.\n"); 24 | 25 | let signature_data = signer.sign(DATA_TO_BE_SIGNED.as_bytes())?; 26 | println!("Signed the example data."); 27 | println!("Data: {}", DATA_TO_BE_SIGNED); 28 | println!("Signature: {:x?}\n", &signature_data); 29 | 30 | let verification_key = signer.to_verification_key()?; 31 | println!("Derive verification key from the signer.\n"); 32 | 33 | println!("Verifying the signature of the example data..."); 34 | match verification_key.verify_signature( 35 | Signature::Raw(&signature_data), 36 | DATA_TO_BE_SIGNED.as_bytes(), 37 | ) { 38 | Ok(_) => { 39 | println!("Verification Succeeded."); 40 | Ok(()) 41 | } 42 | Err(e) => Err(anyhow!("Verifycation failed: {}", e)), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/rekor/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod alpine; 2 | pub use self::alpine::Alpine; 3 | pub mod alpine_all_of; 4 | pub use self::alpine_all_of::AlpineAllOf; 5 | pub mod consistency_proof; 6 | pub use self::consistency_proof::ConsistencyProof; 7 | pub mod error; 8 | pub use self::error::Error; 9 | pub mod hashedrekord; 10 | pub use self::hashedrekord::Hashedrekord; 11 | pub mod hashedrekord_all_of; 12 | pub use self::hashedrekord_all_of::HashedrekordAllOf; 13 | pub mod helm; 14 | pub use self::helm::Helm; 15 | pub mod helm_all_of; 16 | pub use self::helm_all_of::HelmAllOf; 17 | pub mod inactive_shard_log_info; 18 | pub use self::inactive_shard_log_info::InactiveShardLogInfo; 19 | pub mod inclusion_proof; 20 | pub use self::inclusion_proof::InclusionProof; 21 | pub mod intoto; 22 | pub use self::intoto::Intoto; 23 | pub mod intoto_all_of; 24 | pub use self::intoto_all_of::IntotoAllOf; 25 | pub mod jar; 26 | pub use self::jar::Jar; 27 | pub mod jar_all_of; 28 | pub use self::jar_all_of::JarAllOf; 29 | pub mod log_info; 30 | pub use self::log_info::LogInfo; 31 | pub mod proposed_entry; 32 | pub use self::proposed_entry::ProposedEntry; 33 | pub mod rekord; 34 | pub use self::rekord::Rekord; 35 | pub mod rekord_all_of; 36 | pub use self::rekord_all_of::RekordAllOf; 37 | pub mod rfc3161; 38 | pub use self::rfc3161::Rfc3161; 39 | pub mod rfc3161_all_of; 40 | pub use self::rfc3161_all_of::Rfc3161AllOf; 41 | pub mod rpm; 42 | pub use self::rpm::Rpm; 43 | pub mod rpm_all_of; 44 | pub use self::rpm_all_of::RpmAllOf; 45 | pub mod search_index; 46 | pub use self::search_index::SearchIndex; 47 | pub mod search_index_public_key; 48 | pub use self::search_index_public_key::SearchIndexPublicKey; 49 | pub mod search_log_query; 50 | pub use self::search_log_query::SearchLogQuery; 51 | pub mod tuf; 52 | pub use self::tuf::Tuf; 53 | pub mod tuf_all_of; 54 | pub use self::tuf_all_of::TufAllOf; 55 | pub mod log_entry; 56 | pub use self::log_entry::LogEntry; 57 | -------------------------------------------------------------------------------- /examples/rekor/get_log_entry_by_index/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use clap::{Arg, Command}; 17 | use sigstore::rekor::apis::{configuration::Configuration, entries_api}; 18 | use sigstore::rekor::models::log_entry::LogEntry; 19 | use std::str::FromStr; 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | /* 24 | 25 | Retrieves an entry and inclusion proof from the transparency log (if it exists) by index 26 | 27 | Example command : 28 | cargo run --example get_log_entry_by_index -- --log_index 99 29 | 30 | */ 31 | let matches = Command::new("cmd").arg( 32 | Arg::new("log_index") 33 | .long("log_index") 34 | .value_name("LOG_INDEX") 35 | .help("log_index of the artifact"), 36 | ); 37 | 38 | // The following default value will be used if the user does not input values using cli flags 39 | const LOG_INDEX: &str = "1"; 40 | 41 | let flags = matches.get_matches(); 42 | let index = i32::from_str( 43 | flags 44 | .get_one::("log_index") 45 | .unwrap_or(&LOG_INDEX.to_string()), 46 | ) 47 | .unwrap(); 48 | 49 | let configuration = Configuration::default(); 50 | 51 | let message: LogEntry = entries_api::get_log_entry_by_index(&configuration, index) 52 | .await 53 | .unwrap(); 54 | println!("{:#?}", message); 55 | } 56 | -------------------------------------------------------------------------------- /examples/rekor/get_log_entry_by_uuid/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use clap::{Arg, Command}; 17 | use sigstore::rekor::apis::{configuration::Configuration, entries_api}; 18 | use sigstore::rekor::models::log_entry::LogEntry; 19 | 20 | #[tokio::main] 21 | async fn main() { 22 | /* 23 | 24 | Get log entry and information required to generate an inclusion proof for the entry in the transparency log 25 | 26 | Example command : 27 | cargo run --example get_log_entry_by_uuid -- --uuid 073970a07c978b7a9ff15b69fe15d87dfb58fd5756086e3d1fb671c2d0bd95c0 28 | 29 | */ 30 | let matches = Command::new("cmd").arg( 31 | Arg::new("uuid") 32 | .long("uuid") 33 | .value_name("UUID") 34 | .help("uuid of the artifact"), 35 | ); 36 | 37 | // The following default value will be used if the user does not input values using cli flags 38 | const UUID: &str = "073970a07c978b7a9ff15b69fe15d87dfb58fd5756086e3d1fb671c2d0bd95c0"; 39 | 40 | let flags = matches.get_matches(); 41 | let uuid = flags 42 | .get_one::("uuid") 43 | .unwrap_or(&UUID.to_string()) 44 | .to_owned(); 45 | let configuration = Configuration::default(); 46 | let message: LogEntry = entries_api::get_log_entry_by_uuid(&configuration, &uuid) 47 | .await 48 | .unwrap(); 49 | println!("{:#?}", message); 50 | } 51 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_gen_and_export/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::Result; 17 | use sigstore::crypto::SigningScheme; 18 | 19 | const PASSWORD: &str = "example password"; 20 | 21 | fn main() -> Result<()> { 22 | let signer = SigningScheme::ECDSA_P256_SHA256_ASN1.create_signer()?; 23 | println!("Created a new key pair for ECDSA_P256_SHA256_ASN1.\n"); 24 | 25 | let key_pair = signer.to_sigstore_keypair()?; 26 | println!("Derived `SigStoreKeyPair` from the `SigStoreSigner`.\n"); 27 | 28 | let pub_pem = key_pair.public_key_to_pem()?; 29 | println!("Exported the public key in PEM format."); 30 | println!("public key:\n {}", pub_pem); 31 | 32 | let pub_der = key_pair.public_key_to_der()?; 33 | println!("Exported the public key in DER format."); 34 | println!("public key:\n {:x?}", pub_der); 35 | 36 | let pri_pem = key_pair.private_key_to_pem()?; 37 | println!("Exported the private key in PEM format."); 38 | println!("private key:\n {}", *pri_pem); 39 | 40 | let pri_der = key_pair.private_key_to_der()?; 41 | println!("Exported the private key in DER format."); 42 | println!("private key:\n {:x?}", *pri_der); 43 | 44 | let encrypted_pri_pem = key_pair.private_key_to_encrypted_pem(PASSWORD.as_bytes())?; 45 | println!("Exported the encrypted private key in PEM format."); 46 | println!("private key:\n {}", *encrypted_pri_pem); 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /src/cosign/constants.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use const_oid::ObjectIdentifier; 17 | 18 | pub(crate) const SIGSTORE_ISSUER_OID: ObjectIdentifier = 19 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.1"); 20 | pub(crate) const SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID: ObjectIdentifier = 21 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.2"); 22 | pub(crate) const SIGSTORE_GITHUB_WORKFLOW_SHA_OID: ObjectIdentifier = 23 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.3"); 24 | pub(crate) const SIGSTORE_GITHUB_WORKFLOW_NAME_OID: ObjectIdentifier = 25 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.4"); 26 | pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID: ObjectIdentifier = 27 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.5"); 28 | pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REF_OID: ObjectIdentifier = 29 | ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.6"); 30 | /// OID of Ed25519, which is not included in the RustCrypto repo yet. 31 | pub(crate) const ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); 32 | 33 | pub(crate) const SIGSTORE_OCI_MEDIA_TYPE: &str = "application/vnd.dev.cosign.simplesigning.v1+json"; 34 | pub(crate) const SIGSTORE_SIGNATURE_ANNOTATION: &str = "dev.cosignproject.cosign/signature"; 35 | pub(crate) const SIGSTORE_BUNDLE_ANNOTATION: &str = "dev.sigstore.cosign/bundle"; 36 | pub(crate) const SIGSTORE_CERT_ANNOTATION: &str = "dev.sigstore.cosign/certificate"; 37 | -------------------------------------------------------------------------------- /examples/openidflow/openidconnect/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::Result; 17 | use sigstore::oauth; 18 | 19 | fn main() -> Result<(), anyhow::Error> { 20 | let oidc_url = oauth::openidflow::OpenIDAuthorize::new( 21 | "sigstore", 22 | "", 23 | "https://oauth2.sigstore.dev/auth", 24 | "http://localhost:8080", 25 | ) 26 | .auth_url(); 27 | 28 | match oidc_url.as_ref() { 29 | Ok(url) => { 30 | webbrowser::open(url.0.as_ref())?; 31 | println!( 32 | "Open this URL in a browser if it does not automatically open for you:\n{}\n", 33 | url.0 34 | ); 35 | } 36 | Err(e) => println!("{}", e), 37 | } 38 | 39 | let oidc_url = oidc_url?; 40 | let result = oauth::openidflow::RedirectListener::new( 41 | "127.0.0.1:8080", 42 | oidc_url.1, // client 43 | oidc_url.2, // nonce 44 | oidc_url.3, // pkce_verifier 45 | ) 46 | .redirect_listener(); 47 | 48 | match result { 49 | Ok((token_response, id_token)) => { 50 | println!("Email {:?}", token_response.email().unwrap()); 51 | println!( 52 | "Access Token:{:?}", 53 | token_response.access_token_hash().unwrap() 54 | ); 55 | println!("id_token: {:?}", id_token.to_string()); 56 | } 57 | Err(err) => { 58 | println!("{}", err); 59 | } 60 | } 61 | anyhow::Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /src/trust/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2024 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::collections::BTreeMap; 17 | 18 | use pki_types::CertificateDer; 19 | 20 | #[cfg_attr(docsrs, doc(cfg(feature = "sigstore-trust-root")))] 21 | #[cfg(feature = "sigstore-trust-root")] 22 | pub mod sigstore; 23 | 24 | /// A `TrustRoot` owns all key material necessary for establishing a root of trust. 25 | pub trait TrustRoot { 26 | fn fulcio_certs(&self) -> crate::errors::Result>>; 27 | fn rekor_keys(&self) -> crate::errors::Result>; 28 | fn ctfe_keys(&self) -> crate::errors::Result>; 29 | } 30 | 31 | /// A `ManualTrustRoot` is a [TrustRoot] with out-of-band trust materials. 32 | /// As it does not establish a trust root with TUF, users must initialize its materials themselves. 33 | #[derive(Debug, Default)] 34 | pub struct ManualTrustRoot<'a> { 35 | pub fulcio_certs: Vec>, 36 | pub rekor_keys: BTreeMap>, 37 | pub ctfe_keys: BTreeMap>, 38 | } 39 | 40 | impl<'a> TrustRoot for ManualTrustRoot<'a> { 41 | fn fulcio_certs(&self) -> crate::errors::Result>> { 42 | Ok(self.fulcio_certs.clone()) 43 | } 44 | 45 | fn rekor_keys(&self) -> crate::errors::Result> { 46 | Ok(self 47 | .rekor_keys 48 | .iter() 49 | .map(|(k, v)| (k.clone(), v.as_slice())) 50 | .collect()) 51 | } 52 | 53 | fn ctfe_keys(&self) -> crate::errors::Result> { 54 | Ok(self 55 | .ctfe_keys 56 | .iter() 57 | .map(|(k, v)| (k.clone(), v.as_slice())) 58 | .collect()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cosign/verification_constraint/public_key_verifier.rs: -------------------------------------------------------------------------------- 1 | use super::VerificationConstraint; 2 | use crate::cosign::signature_layers::SignatureLayer; 3 | use crate::crypto::{CosignVerificationKey, SigningScheme}; 4 | use crate::errors::Result; 5 | 6 | /// Verification Constraint for signatures produced with public/private keys 7 | #[derive(Debug)] 8 | pub struct PublicKeyVerifier { 9 | key: CosignVerificationKey, 10 | } 11 | 12 | impl PublicKeyVerifier { 13 | /// Create a new instance of `PublicKeyVerifier`. 14 | /// The `key_raw` variable holds a PEM encoded representation of the 15 | /// public key to be used at verification time. 16 | pub fn new(key_raw: &[u8], signing_scheme: &SigningScheme) -> Result { 17 | let key = CosignVerificationKey::from_pem(key_raw, signing_scheme)?; 18 | Ok(PublicKeyVerifier { key }) 19 | } 20 | 21 | /// Create a new instance of `PublicKeyVerifier`. 22 | /// The `key_raw` variable holds a PEM encoded representation of the 23 | /// public key to be used at verification time. The verification 24 | /// algorithm will be derived from the public key type: 25 | /// * `RSA public key`: `RSA_PKCS1_SHA256` 26 | /// * `EC public key with P-256 curve`: `ECDSA_P256_SHA256_ASN1` 27 | /// * `EC public key with P-384 curve`: `ECDSA_P384_SHA384_ASN1` 28 | /// * `Ed25519 public key`: `Ed25519` 29 | pub fn try_from(key_raw: &[u8]) -> Result { 30 | let key = CosignVerificationKey::try_from_pem(key_raw)?; 31 | Ok(PublicKeyVerifier { key }) 32 | } 33 | } 34 | 35 | impl VerificationConstraint for PublicKeyVerifier { 36 | fn verify(&self, signature_layer: &SignatureLayer) -> Result { 37 | Ok(signature_layer.is_signed_by_key(&self.key)) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | use crate::cosign::signature_layers::tests::{ 45 | build_correct_signature_layer_with_certificate, 46 | build_correct_signature_layer_without_bundle, 47 | }; 48 | 49 | #[test] 50 | fn pub_key_verifier() { 51 | let (sl, key) = build_correct_signature_layer_without_bundle(); 52 | 53 | let vc = PublicKeyVerifier { key }; 54 | assert!(vc.verify(&sl).unwrap()); 55 | 56 | let sl = build_correct_signature_layer_with_certificate(); 57 | assert!(!vc.verify(&sl).unwrap()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/rekor/models/proposed_entry.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 13 | #[serde(tag = "kind")] 14 | pub enum ProposedEntry { 15 | #[serde(rename = "alpine")] 16 | Alpine { 17 | #[serde(rename = "apiVersion")] 18 | api_version: String, 19 | #[serde(rename = "spec")] 20 | spec: serde_json::Value, 21 | }, 22 | #[serde(rename = "hashedrekord")] 23 | Hashedrekord { 24 | #[serde(rename = "apiVersion")] 25 | api_version: String, 26 | #[serde(rename = "spec")] 27 | spec: super::hashedrekord::Spec, 28 | }, 29 | #[serde(rename = "helm")] 30 | Helm { 31 | #[serde(rename = "apiVersion")] 32 | api_version: String, 33 | #[serde(rename = "spec")] 34 | spec: serde_json::Value, 35 | }, 36 | #[serde(rename = "intoto")] 37 | Intoto { 38 | #[serde(rename = "apiVersion")] 39 | api_version: String, 40 | #[serde(rename = "spec")] 41 | spec: serde_json::Value, 42 | }, 43 | #[serde(rename = "jar")] 44 | Jar { 45 | #[serde(rename = "apiVersion")] 46 | api_version: String, 47 | #[serde(rename = "spec")] 48 | spec: serde_json::Value, 49 | }, 50 | #[serde(rename = "rekord")] 51 | Rekord { 52 | #[serde(rename = "apiVersion")] 53 | api_version: String, 54 | #[serde(rename = "spec")] 55 | spec: serde_json::Value, 56 | }, 57 | #[serde(rename = "rfc3161")] 58 | Rfc3161 { 59 | #[serde(rename = "apiVersion")] 60 | api_version: String, 61 | #[serde(rename = "spec")] 62 | spec: serde_json::Value, 63 | }, 64 | #[serde(rename = "rpm")] 65 | Rpm { 66 | #[serde(rename = "apiVersion")] 67 | api_version: String, 68 | #[serde(rename = "spec")] 69 | spec: serde_json::Value, 70 | }, 71 | #[serde(rename = "tuf")] 72 | Tuf { 73 | #[serde(rename = "apiVersion")] 74 | api_version: String, 75 | #[serde(rename = "spec")] 76 | spec: serde_json::Value, 77 | }, 78 | } 79 | -------------------------------------------------------------------------------- /src/rekor/apis/index_api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use super::{Error, configuration}; 12 | use crate::rekor::apis::ResponseContent; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// struct for typed errors of method [`search_index`] 16 | #[derive(Debug, Clone, Serialize, Deserialize)] 17 | #[serde(untagged)] 18 | pub enum SearchIndexError { 19 | Status400(crate::rekor::models::Error), 20 | DefaultResponse(crate::rekor::models::Error), 21 | UnknownValue(serde_json::Value), 22 | } 23 | 24 | pub async fn search_index( 25 | configuration: &configuration::Configuration, 26 | query: crate::rekor::models::SearchIndex, 27 | ) -> Result, Error> { 28 | let local_var_configuration = configuration; 29 | 30 | let local_var_client = &local_var_configuration.client; 31 | 32 | let local_var_uri_str = format!( 33 | "{}/api/v1/index/retrieve", 34 | local_var_configuration.base_path 35 | ); 36 | let mut local_var_req_builder = 37 | local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); 38 | 39 | if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { 40 | local_var_req_builder = 41 | local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); 42 | } 43 | local_var_req_builder = local_var_req_builder.json(&query); 44 | 45 | let local_var_req = local_var_req_builder.build()?; 46 | let local_var_resp = local_var_client.execute(local_var_req).await?; 47 | 48 | let local_var_status = local_var_resp.status(); 49 | let local_var_content = local_var_resp.text().await?; 50 | if !local_var_status.is_client_error() && !local_var_status.is_server_error() { 51 | serde_json::from_str(&local_var_content).map_err(Error::from) 52 | } else { 53 | let local_var_entity: Option = 54 | serde_json::from_str(&local_var_content).ok(); 55 | let local_var_error = ResponseContent { 56 | status: local_var_status, 57 | content: local_var_content, 58 | entity: local_var_entity, 59 | }; 60 | Err(Error::ResponseError(local_var_error)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/rekor/get_public_key/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::{fs::write, process}; 17 | 18 | use clap::{Arg, Command}; 19 | use sigstore::rekor::apis::{configuration::Configuration, pubkey_api}; 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | /* 24 | Returns the public key that can be used to validate the signed tree head 25 | 26 | Example command : 27 | cargo run --example get_public_key 28 | */ 29 | 30 | let matches = Command::new("cmd") 31 | .arg(Arg::new("tree_id") 32 | .long("tree_id") 33 | .value_name("TREE_ID") 34 | .help("The tree ID of the tree that you wish to prove consistency for. To use the default value, do not input any value.")) 35 | .arg(Arg::new("output") 36 | .short('o') 37 | .long("output") 38 | .value_name("OUTPUT_FILE") 39 | .num_args(0..=1) 40 | .require_equals(true) 41 | .default_missing_value("key.pub") 42 | .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.")); 43 | 44 | let flags = matches.get_matches(); 45 | let configuration = Configuration::default(); 46 | let pubkey = pubkey_api::get_public_key( 47 | &configuration, 48 | flags.get_one::("tree_id").map(|s| s.as_str()), 49 | ) 50 | .await 51 | .expect("Unable to retrieve public key"); 52 | 53 | if let Some(out_path) = flags.get_one::("output") { 54 | match write(out_path, pubkey) { 55 | Ok(_) => (), 56 | Err(e) => { 57 | eprintln!("Could not write to {out_path}: {e}"); 58 | process::exit(1); 59 | } 60 | } 61 | } else { 62 | print!("{}", pubkey); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/rekor/apis/pubkey_api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use super::{Error, configuration}; 12 | use crate::rekor::apis::ResponseContent; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// struct for typed errors of method [`get_public_key`] 16 | #[derive(Debug, Clone, Serialize, Deserialize)] 17 | #[serde(untagged)] 18 | pub enum GetPublicKeyError { 19 | DefaultResponse(crate::rekor::models::Error), 20 | UnknownValue(serde_json::Value), 21 | } 22 | 23 | /// Returns the public key that can be used to validate the signed tree head 24 | pub async fn get_public_key( 25 | configuration: &configuration::Configuration, 26 | tree_id: Option<&str>, 27 | ) -> Result> { 28 | let local_var_configuration = configuration; 29 | 30 | let local_var_client = &local_var_configuration.client; 31 | 32 | let local_var_uri_str = format!("{}/api/v1/log/publicKey", local_var_configuration.base_path); 33 | let mut local_var_req_builder = 34 | local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); 35 | 36 | if let Some(ref local_var_str) = tree_id { 37 | local_var_req_builder = 38 | local_var_req_builder.query(&[("treeID", &local_var_str.to_string())]); 39 | } 40 | if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { 41 | local_var_req_builder = 42 | local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); 43 | } 44 | 45 | let local_var_req = local_var_req_builder.build()?; 46 | let local_var_resp = local_var_client.execute(local_var_req).await?; 47 | 48 | let local_var_status = local_var_resp.status(); 49 | let local_var_content = local_var_resp.text().await?; 50 | if !local_var_status.is_client_error() && !local_var_status.is_server_error() { 51 | Ok(local_var_content) 52 | } else { 53 | let local_var_entity: Option = 54 | serde_json::from_str(&local_var_content).ok(); 55 | let local_var_error = ResponseContent { 56 | status: local_var_status, 57 | content: local_var_content, 58 | entity: local_var_entity, 59 | }; 60 | Err(Error::ResponseError(local_var_error)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/cosign/constraint/annotation.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::collections::{BTreeMap, HashMap}; 17 | 18 | use serde_json::Value; 19 | use tracing::warn; 20 | 21 | use crate::{cosign::SignatureLayer, errors::Result}; 22 | 23 | use super::Constraint; 24 | 25 | /// Constraint for the annotations, which can be verified by [`crate::cosign::verification_constraint::AnnotationVerifier`] 26 | /// 27 | /// The [`crate::cosign::payload::SimpleSigning`] object can be enriched by a signer 28 | /// with more annotations. 29 | /// 30 | /// A [`AnnotationMarker`] helps to add annotations to the [`crate::cosign::payload::SimpleSigning`] 31 | /// of the given [`SignatureLayer`]. 32 | /// 33 | /// Warning: The signing step must not happen until all [`AnnotationMarker`] 34 | /// have already performed `add_constraint`. 35 | #[derive(Debug)] 36 | pub struct AnnotationMarker { 37 | pub annotations: HashMap, 38 | } 39 | 40 | impl AnnotationMarker { 41 | pub fn new(annotations: HashMap) -> Self { 42 | Self { annotations } 43 | } 44 | } 45 | 46 | impl Constraint for AnnotationMarker { 47 | fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result { 48 | let mut annotations = match &signature_layer.simple_signing.optional { 49 | Some(opt) => { 50 | warn!(optional = ?opt, "already has an annotation field"); 51 | opt.extra.clone() 52 | } 53 | None => BTreeMap::new(), 54 | }; 55 | 56 | for (k, v) in &self.annotations { 57 | if annotations.contains_key(k) && annotations[k] != *v { 58 | warn!(key = ?k, "extra field already has a value"); 59 | return Ok(false); 60 | } 61 | annotations.insert(k.to_owned(), Value::String(v.into())); 62 | } 63 | 64 | let mut opt = signature_layer 65 | .simple_signing 66 | .optional 67 | .clone() 68 | .unwrap_or_default(); 69 | opt.extra = annotations; 70 | signature_layer.simple_signing.optional = Some(opt); 71 | Ok(true) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/cosign/verify-blob/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | extern crate clap; 17 | extern crate sigstore; 18 | use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; 19 | use clap::Parser; 20 | use sigstore::cosign::CosignCapabilities; 21 | use sigstore::cosign::client::Client; 22 | 23 | extern crate tracing_subscriber; 24 | use std::fs; 25 | use std::path::PathBuf; 26 | use tracing_subscriber::prelude::*; 27 | use tracing_subscriber::{EnvFilter, fmt}; 28 | 29 | #[derive(Parser, Debug)] 30 | #[clap(author, version, about, long_about = None)] 31 | struct Cli { 32 | /// The certificate generate from the `cosign sign-blob` command 33 | #[clap(short, long)] 34 | certificate: PathBuf, 35 | 36 | /// The signature generated from the `cosign sign-blob` command 37 | #[clap(long, required(false))] 38 | signature: PathBuf, 39 | 40 | /// The blob to verify 41 | blob: String, 42 | 43 | /// Enable verbose mode 44 | #[clap(short, long)] 45 | verbose: bool, 46 | } 47 | 48 | #[tokio::main] 49 | pub async fn main() { 50 | let cli = Cli::parse(); 51 | 52 | // setup logging 53 | let level_filter = if cli.verbose { "debug" } else { "info" }; 54 | let filter_layer = EnvFilter::new(level_filter); 55 | tracing_subscriber::registry() 56 | .with(filter_layer) 57 | .with(fmt::layer().with_writer(std::io::stderr)) 58 | .init(); 59 | 60 | // certificate may be PEM or "double base64 encoded PEM" (cosign). 61 | let cert_input = fs::read_to_string(&cli.certificate).expect("error reading certificate"); 62 | let certificate = match BASE64_STD_ENGINE.decode(cert_input.clone()) { 63 | Ok(res) => String::from_utf8(res).expect("error stringifying PEM certificate"), 64 | Err(_) => cert_input, 65 | }; 66 | 67 | let signature = fs::read_to_string(&cli.signature).expect("error reading signature"); 68 | let blob = fs::read(cli.blob.as_str()).expect("error reading blob file"); 69 | 70 | match Client::verify_blob(&certificate, signature.trim(), &blob) { 71 | Ok(_) => println!("Verification succeeded"), 72 | Err(e) => eprintln!("Verification failed {:?}", e), 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/rekor/get_log_proof/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use clap::{Arg, Command}; 17 | use sigstore::rekor::apis::{configuration::Configuration, tlog_api}; 18 | use sigstore::rekor::models::ConsistencyProof; 19 | use std::str::FromStr; 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | /* 24 | 25 | Get information required to generate a consistency proof for the transparency log. 26 | Returns a list of hashes for specified tree sizes that can be used to confirm the consistency of the transparency log. 27 | 28 | Example command : 29 | cargo run --example get_log_proof -- --last_size 10 30 | cargo run --example get_log_proof -- --last_size 10 --first_size 1 31 | 32 | */ 33 | let matches = Command::new("cmd") 34 | .arg(Arg::new("last_size") 35 | .long("last_size") 36 | .value_name("LAST_SIZE") 37 | .help("The size of the tree that you wish to prove consistency to")) 38 | .arg(Arg::new("first_size") 39 | .long("first_size") 40 | .value_name("FIRST_SIZE") 41 | .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")) 42 | .arg(Arg::new("tree_id") 43 | .long("tree_id") 44 | .value_name("TREE_ID") 45 | .help("The tree ID of the tree that you wish to prove consistency for. To use the default value, do not input any value.")); 46 | 47 | let configuration = Configuration::default(); 48 | let flags = matches.get_matches(); 49 | 50 | // The following default value will be used if the user does not input values using cli flags 51 | const LAST_SIZE: &str = "10"; 52 | 53 | let log_proof: ConsistencyProof = tlog_api::get_log_proof( 54 | &configuration, 55 | i32::from_str( 56 | flags 57 | .get_one::("last_size") 58 | .unwrap_or(&LAST_SIZE.to_string()), 59 | ) 60 | .unwrap(), 61 | flags.get_one::("first_size").map(|s| s.as_str()), 62 | flags.get_one::("tree_id").map(|s| s.as_str()), 63 | ) 64 | .await 65 | .unwrap(); 66 | println!("{:#?}", log_proof); 67 | } 68 | -------------------------------------------------------------------------------- /src/cosign/constraint/signature.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Structs that can be used to sign a [`crate::cosign::SignatureLayer`] 17 | 18 | use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; 19 | use tracing::warn; 20 | use zeroize::Zeroizing; 21 | 22 | use crate::{ 23 | cosign::SignatureLayer, 24 | crypto::{SigStoreSigner, SigningScheme, signing_key::SigStoreKeyPair}, 25 | errors::{Result, SigstoreError}, 26 | }; 27 | 28 | use super::Constraint; 29 | 30 | /// Sign the [`SignatureLayer`] with the given [`SigStoreSigner`]. 31 | /// This constraint must be the last one to applied to a [`SignatureLayer`], 32 | /// since all the plaintext is defined. 33 | #[derive(Debug)] 34 | pub struct PrivateKeySigner { 35 | key: SigStoreSigner, 36 | } 37 | 38 | impl PrivateKeySigner { 39 | /// Create a new [PrivateKeySigner] with given raw PEM data of a 40 | /// private key. 41 | pub fn new_with_raw( 42 | key_raw: Zeroizing>, 43 | password: Zeroizing>, 44 | signing_scheme: &SigningScheme, 45 | ) -> Result { 46 | let signer = match password.is_empty() { 47 | true => SigStoreKeyPair::from_pem(&key_raw), 48 | false => SigStoreKeyPair::from_encrypted_pem(&key_raw, &password), 49 | } 50 | .map_err(|e| SigstoreError::ApplyConstraintError(e.to_string()))? 51 | .to_sigstore_signer(signing_scheme) 52 | .map_err(|e| SigstoreError::ApplyConstraintError(e.to_string()))?; 53 | 54 | Ok(Self { key: signer }) 55 | } 56 | 57 | pub fn new_with_signer(signer: SigStoreSigner) -> Self { 58 | Self { key: signer } 59 | } 60 | } 61 | 62 | impl Constraint for PrivateKeySigner { 63 | fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result { 64 | if signature_layer.signature.is_some() { 65 | warn!(signature = ?signature_layer.signature, "already has signature"); 66 | return Ok(false); 67 | } 68 | signature_layer.raw_data = serde_json::to_vec(&signature_layer.simple_signing)?; 69 | let sig = self.key.sign(&signature_layer.raw_data)?; 70 | let sig_base64 = BASE64_STD_ENGINE.encode(sig); 71 | signature_layer.signature = Some(sig_base64); 72 | Ok(true) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/cosign/constraint/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Structs that can be used to add constraints to [`crate::cosign::SignatureLayer`] 17 | //! with special business logic. 18 | //! 19 | //! This module provides some common kinds of constraints: 20 | //! * [`PrivateKeySigner`]: Attaching a signature 21 | //! * [`AnnotationMarker`]: Adding extra annotations 22 | //! 23 | //! Developers can define ad-hoc constraint logic by creating a Struct that 24 | //! implements the [`Constraint`] trait 25 | //! 26 | //! ## Warining 27 | //! Because [`PrivateKeySigner`] will sign the whole data of a given 28 | //! [`crate::cosign::SignatureLayer`], developers **must** ensure that 29 | //! a [`PrivateKeySigner`] is the last constraint to be applied on a 30 | //! [`crate::cosign::SignatureLayer`]. Before that, all constraints that 31 | //! may modify the content of the [`crate::cosign::SignatureLayer`] should 32 | //! have been applied already. 33 | 34 | use super::SignatureLayer; 35 | use crate::errors::Result; 36 | 37 | pub type SignConstraintVec = Vec>; 38 | pub type SignConstraintRefVec<'a> = Vec<&'a Box>; 39 | 40 | pub trait Constraint: std::fmt::Debug { 41 | /// Given a mutable reference of [`crate::cosign::SignatureLayer`], return 42 | /// `true` if the constraint is applied successfully. 43 | /// 44 | /// Developer can use the 45 | /// [`crate::errors::SigstoreError::ApplyConstraintError`] error 46 | /// when something goes wrong inside of the application logic. 47 | /// 48 | /// ``` 49 | /// use sigstore::{ 50 | /// cosign::constraint::Constraint, 51 | /// cosign::signature_layers::SignatureLayer, 52 | /// errors::{SigstoreError, Result}, 53 | /// }; 54 | /// 55 | /// #[derive(Debug)] 56 | /// struct MyConstraint{} 57 | /// 58 | /// impl Constraint for MyConstraint { 59 | /// fn add_constraint(&self, _sl: &mut SignatureLayer) -> Result { 60 | /// Err(SigstoreError::ApplyConstraintError( 61 | /// "something went wrong!".to_string())) 62 | /// } 63 | /// } 64 | /// 65 | /// ``` 66 | fn add_constraint(&self, signature_layer: &mut SignatureLayer) -> Result; 67 | } 68 | 69 | pub mod annotation; 70 | pub use annotation::AnnotationMarker; 71 | 72 | pub mod signature; 73 | pub use self::signature::PrivateKeySigner; 74 | -------------------------------------------------------------------------------- /src/registry/oci_reference.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use crate::errors::SigstoreError; 17 | use std::fmt::{Display, Formatter}; 18 | use std::str::FromStr; 19 | 20 | /// `OciReference` provides a general type to represent any way of referencing images within an OCI registry. 21 | #[derive(Debug, Clone, PartialEq)] 22 | pub struct OciReference { 23 | pub(crate) oci_reference: oci_client::Reference, 24 | } 25 | 26 | impl FromStr for OciReference { 27 | type Err = SigstoreError; 28 | 29 | fn from_str(s: &str) -> Result { 30 | s.parse::() 31 | .map_err(|_| SigstoreError::OciReferenceNotValidError { 32 | reference: s.to_string(), 33 | }) 34 | .map(|oci_reference| OciReference { oci_reference }) 35 | } 36 | } 37 | 38 | impl OciReference { 39 | /// Create a Reference with a registry, repository and tag. 40 | pub fn with_tag(registry: String, repository: String, tag: String) -> Self { 41 | OciReference { 42 | oci_reference: oci_client::Reference::with_tag(registry, repository, tag), 43 | } 44 | } 45 | 46 | /// Create a Reference with a registry, repository and digest. 47 | pub fn with_digest(registry: String, repository: String, digest: String) -> Self { 48 | OciReference { 49 | oci_reference: oci_client::Reference::with_digest(registry, repository, digest), 50 | } 51 | } 52 | 53 | /// Resolve the registry address of a given Reference. 54 | /// 55 | /// Some registries, such as docker.io, uses a different address for the actual 56 | /// registry. This function implements such redirection. 57 | pub fn resolve_registry(&self) -> &str { 58 | self.oci_reference.resolve_registry() 59 | } 60 | 61 | /// registry returns the name of the registry. 62 | pub fn registry(&self) -> &str { 63 | self.oci_reference.registry() 64 | } 65 | 66 | /// repository returns the name of the repository 67 | pub fn repository(&self) -> &str { 68 | self.oci_reference.repository() 69 | } 70 | 71 | /// digest returns the object's digest, if present. 72 | pub fn digest(&self) -> Option<&str> { 73 | self.oci_reference.digest() 74 | } 75 | 76 | /// tag returns the object's tag, if present. 77 | pub fn tag(&self) -> Option<&str> { 78 | self.oci_reference.tag() 79 | } 80 | 81 | /// whole returns the whole reference. 82 | pub fn whole(&self) -> String { 83 | self.oci_reference.whole() 84 | } 85 | } 86 | 87 | impl Display for OciReference { 88 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 89 | self.oci_reference.fmt(f) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/cosign/verify-bundle/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; 17 | use clap::Parser; 18 | use sigstore::cosign::CosignCapabilities; 19 | use sigstore::cosign::bundle::SignedArtifactBundle; 20 | use sigstore::cosign::client::Client; 21 | use sigstore::crypto::{CosignVerificationKey, SigningScheme}; 22 | use std::fs; 23 | use tracing_subscriber::prelude::*; 24 | use tracing_subscriber::{EnvFilter, fmt}; 25 | 26 | #[derive(Parser, Debug)] 27 | #[clap(author, version, about, long_about = None)] 28 | struct Cli { 29 | /// Path to bundle file 30 | #[clap(short, long)] 31 | bundle: String, 32 | 33 | /// Path to artifact to be verified 34 | blob: String, 35 | 36 | /// File containing Rekor's public key (e.g.: ~/.sigstore/root/targets/rekor.pub) 37 | #[clap(long, required = true)] 38 | rekor_pub_key: String, 39 | 40 | /// Rekor public key ID 41 | #[clap(long, required = true)] 42 | rekor_pub_key_id: String, 43 | 44 | /// Enable verbose mode 45 | #[clap(short, long)] 46 | verbose: bool, 47 | } 48 | 49 | #[tokio::main] 50 | pub async fn main() { 51 | let cli = Cli::parse(); 52 | 53 | // setup logging 54 | let level_filter = if cli.verbose { "debug" } else { "info" }; 55 | let filter_layer = EnvFilter::new(level_filter); 56 | tracing_subscriber::registry() 57 | .with(filter_layer) 58 | .with(fmt::layer().with_writer(std::io::stderr)) 59 | .init(); 60 | 61 | let rekor_pub_pem = 62 | fs::read_to_string(&cli.rekor_pub_key).expect("error reading rekor's public key"); 63 | let rekor_pub_key = 64 | CosignVerificationKey::from_pem(rekor_pub_pem.as_bytes(), &SigningScheme::default()) 65 | .expect("Cannot create Rekor verification key"); 66 | let bundle_json = fs::read_to_string(&cli.bundle).expect("error reading bundle json file"); 67 | let blob = fs::read(cli.blob.as_str()).expect("error reading blob file"); 68 | 69 | let rekor_pub_keys = [(cli.rekor_pub_key_id, rekor_pub_key)] 70 | .into_iter() 71 | .collect(); 72 | 73 | let bundle = SignedArtifactBundle::new_verified(&bundle_json, &rekor_pub_keys).unwrap(); 74 | 75 | // certificate in bundle is double base64 encoded, remove one layer: 76 | let cert_data = BASE64_STD_ENGINE 77 | .decode(bundle.cert) 78 | .expect("Error decoding base64 certificate"); 79 | let cert = String::from_utf8(cert_data).expect("error stringifying PEM certificate"); 80 | 81 | match Client::verify_blob(&cert, &bundle.base64_signature, &blob) { 82 | Ok(_) => println!("Verification succeeded"), 83 | Err(e) => eprintln!("Verification failed: {}", e), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/data/keys/rsa_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAzk4HyifDz3BhYUj4YDk6SjwpZgCh1dkEaL/E1/k8VUFvDH53 3 | iJ/dwbIotd920y86nqA1P8JoKbTLuDXMubyCDC7Cx/zBxwVnH2F1qeB50mun2lR0 4 | BEWP2cp8xSIl7Ns0rDFjAX6/vfJcYrzlD/xmGNtkdeterVxHb272R63kVXOfteiQ 5 | Xw4pvGUNvtjk4eeCp7n8U34zPLSzVa9C7gAMtDAeuWL4sQvTZCkb5dJ5HePbpdQ1 6 | uEHakxiCQ25x+AqvGkvcbDxad9dUm6JAwKAcfSIWsy0Ie7Pp9KeXbGxQv0Xb1kYa 7 | 6hsKtvGlKOrvu33zgwM4dfJjUXT8pkDxHdZAhT+MYfKVl1ANVSgzypezvGQY7fAc 8 | j+LXcsVtv6q0SS7e6XyddrI/mQSZdeHD5pT5cO0uLCTJwukDD1Q800xcCz98EW4w 9 | VPZGU33vB3MEKflt/FFsxmlMEjw62dqh7JOn4Mcmk684npOjffx13Dacrxn76k7H 10 | dLXu0EO56qkg00gx2s7+AVAyf0MD3YvbH3rCtD0aMctfK3CSRuQZYtqXUVjsgzm3 11 | nHPCq8iFCGKfBdk3DADp1NlTMqCMSMuUjAk2/2BL5iCfGYEJ9+TycaLDNpBQdJVJ 12 | vzrBsKngdy50X4kXTLIx2vss54Lk4CWs0+TX0RN7wiVJBjmZgHc5u5yJIJcCAwEA 13 | AQKCAgB+R08HU43MxLompVa692yRkf+5Gvv0fNDxGSjxFfLzMIk7uZGLRGelr1qx 14 | 8KW4ILmd7OyLKYE+vhbQm8XDjvp/YIQDi9hE7S6xC6PNJsUKorDsuDMHhljF8+ap 15 | d/yE3ayBFf3HJYFSUC5ylbMUNOd9oZT9hOO/87MaJ26Cc5NHJu4El+T++hlb4vMl 16 | 9XcsO9xCtFoZ9S6Bow3+jbfHHKqqBKZZzZXyMQ3kyjD0XP+b5yREff+f2FdlIGRj 17 | yA/kxw1laDf03IB3yItWdFt0TM0DX0FLzW3a4kZ7ZbYPPMG0QpuMrf69e230izcQ 18 | M7YoKrFKaUc/Eu3uJ1CapzevjryQePLUJdWldwVUipZeCQNKVmeu7G/a59rFTwsw 19 | gwghk/eUh+gssSbgUVk1J7qybx1njkiAdSOSjRxLNUGkdQFpxwUiOx0WyXysIffv 20 | qVYvYJBCD2gf9hYb0qQcdd4nud1PyECjXVS95SorvEJDCisuoBzAvMj5iHduJimm 21 | VnUqEI5qv6ZXE9mUKZW6IISlsf7EuM6QBUKuF4VXF+ynvMGTctHqXW/eoGxnugmj 22 | NrXMzvqOGRn5kXs5e3NM9UdyzOZqAh/L8wB5BQKnIoJm2sAZeLQaxOYdo+ntKIsh 23 | NcFI0+KagSKGUMF2rLxOf1BOw/dE0Ir5u0avE+YgKAk7XXG0wQKCAQEA+nT0Ql2/ 24 | PT3GqfoHyWpvcmttDPY6yTpy9TFltFABZplPpsHaWfn4wWQ3FT4aBmYF962MXgaf 25 | LlbQMHed2F3thhTyeEJUrj/L8dK7tB9OjqGQJFlstEKXQZh+eaWlxkikY2khjao8 26 | B9TFcPcMmCz0EFWVIrOIDEvgbwgp5nzopAsfqz9TRadnOOgL7x8PjVWzRA/SHuEv 27 | FcTZuTAQyXI8oRpKYLtg2q7rptKjH/tkxf7IAmCow7vk4g3Cj/ZXfXzCD6CMPuTf 28 | X4WFqJUnBYuwlhfHLcT6vOiNLhIq7sV3wle5UGkh4GQ6qWP3/r1TKVtyW7oduIJs 29 | m/8duw6b9Ct99wKCAQEA0t7rFwjFY98wJbGMB5dsKkA/WnTGBNSOkNf257DPyZeE 30 | 3ljqwq6yarnpB6fu989yG8KUhX81d0BtNJL+T71CwRL6LNxlDoYdKTuWZYHUFHNh 31 | u0BRz80d7gSiPai5ZBzgFpIFaDTwMaJjX2RLGEEgcLYFD1LtsZNSyY9FC38Vr+9k 32 | zUQ5W5rzxYGw4rRaEL0ovvjYLFz75ns2zPZapCt0c/z/Qd9kyibgGDE3bnL6gkvo 33 | aDCkQr4Xc3qy+jJzVsv0n+lcdHBfP/qlcpEWyapXM3/a9OK/KAYsaJa/RBpA+l+H 34 | g4YQVwwYODugSmzYKme1JU4h804EN6lHtSvzYkNKYQKCAQAjhpNfFo0Z0rlrQtv3 35 | 5fEI+dPuEr8j6/aCcQ9MFE0ekICL1tNyD9MJG330tWpbnf0atLNEYwwRNp8xQMZS 36 | +n/GlRIPnNkGHmZ/VrTpR8eM073uagDRUODDnS3Tc3ugNI2czDzGK294bOXUsDZJ 37 | H5c++eS9l1mk5N5g4XeQCge1vR4w3Dqjlqs9lyyaLn22PoG/Fb9oQei73cBEVF0N 38 | NfcDowcJ0YpbepRShW4+CxqwOwOD0tIdcXl11x3R7c9bLWcZcFx0T2Kf2gCrePyf 39 | /MB/ib/m7hni0dm0vz73v2rNVkQi88aqXY00mcmDiLdTFnWSLUQp99YQCo/dCKV2 40 | bPThAoIBAQCuRH3CpoQCmoN+0zEnYPOKI1h4GANCIKvFdkVdipjeQDMVUiSJSbi3 41 | TPcRVa6+65ig6ni1rsBv0jWt+kDjg0S0rUtFYcq+awWUeuM69kVftU8yYeB6vEgc 42 | 2YV/MX4tB1QGMxz21rEeQ9aeEhOhcsktfK/Hz0ASve7wFk/4RUmWAWCr5tMEKpWF 43 | Rz34zRWVuc3/rUVxvFKNUoyibIHSJPtzk8UcGlOAYQpX0+y8gZcXsUXbPT+yzMgy 44 | rldVP/Zj5+A9e6zlqax+AlVSzicn+HdiXyqDsRRLLnbq5JIi5ROIFwS2JEhCuAMY 45 | DebVOwiWWuiwcNbL7VC881AIoM7eCUBhAoIBAC4ZPQ0qHhKz4DUHNevoIje9aor1 46 | 8711SwQuUpjJ3iVmhLwLPplZZaMq6dNgjPTBosZmtnjKsrR8Kwn/ZPq8siR9Ii41 47 | TRdvT5fiChuaJCT+cUKgvb/vri+hihxT6Sd7YCGvrN/e76Vwz1Bes1DZW7bJN6hU 48 | 3Ha8N23Juv0Sb/2zTi2snVClX76l2ftKUUHcuUPCHvhpf/T8XYSIbVxJYBSWVtTv 49 | oxQ0q2S/Rle1u/WE0qFfZhfJscdm07Oq+OeXFa6ZiAJ1xjumi4YxpYOdBZPguVlh 50 | E5vzvuF5lXGTDVZWBnnI1PZTc96d3NKY86K6w9dQfPhEqTM8QfR7YNqzhw8= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Continuous integration | Docs | License | Crate version | Crate downloads 2 | ----------------------|------|---------|---------------|----------------- 3 | [![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) 4 | 5 | 6 | A crate to interact with [sigstore](https://sigstore.dev/). 7 | 8 | This crate is under active development and will not be considered 9 | stable until the 1.0 release. 10 | 11 | ## Features 12 | 13 | ### Cosign Sign and Verify 14 | 15 | The crate implements the following verification mechanisms: 16 | 17 | * Sign using a cosign key and store the signature in a registry 18 | * Verify using a given key 19 | * Verify bundle produced by transparency log (Rekor) 20 | * Verify signature produced in keyless mode, using Fulcio Web-PKI 21 | 22 | Signature annotations and certificate email can be provided at verification time. 23 | 24 | ### Fulcio Integration 25 | 26 | For use with Fulcio ephemeral key signing, an OpenID connect API is available, 27 | along with a fulcio client implementation. 28 | 29 | ### Rekor Client 30 | 31 | All rekor client APIs can be leveraged to interact with the transparency log. 32 | 33 | ### Key Interface 34 | 35 | Cryptographic key management with the following key interfaces: 36 | 37 | * Generate a key pair 38 | * Sign data 39 | * Verify signature 40 | * Export public / (encrypted) private key in PEM / DER format 41 | * Import public / (encrypted) private key in PEM / DER format 42 | 43 | #### Known limitations 44 | 45 | * The crate does not handle verification of attestations yet. 46 | 47 | ## Examples 48 | 49 | The `examples` directory contains demo programs using the library. 50 | 51 | * [`openidflow`](examples/openidflow/README.md) 52 | * [`key_interface`](examples/key_interface/README.md) 53 | * [`rekor`](examples/rekor/README.md) 54 | * [`cosign/verify`](examples/cosign/verify/README.md) 55 | * [`cosign/verify-blob`](examples/cosign/verify-blob/README.md) 56 | * [`cosign/verify-bundle`](examples/cosign/verify-bundle/README.md) 57 | * [`cosign/sign`](examples/cosign/sign/README.md) 58 | 59 | Each example can be executed with the `cargo run --example ` command. 60 | 61 | For example, `openidconnect` can be run with the following command: 62 | 63 | ```bash 64 | cargo run --example openidconnect 65 | ``` 66 | 67 | ## WebAssembly/WASM support 68 | 69 | To embedded this crate in WASM modules, build it using the `wasm` cargo feature: 70 | 71 | ```bash 72 | cargo build --no-default-features --features wasm --target wasm32-unknown-unknown 73 | ``` 74 | 75 | NOTE: The wasm32-wasi target architecture is not yet supported. 76 | 77 | ## Contributing 78 | 79 | Contributions are welcome! Please see the [contributing guidelines](CONTRIBUTORS.md) 80 | for more information. 81 | 82 | ## Security 83 | 84 | Should you discover any security issues, please refer to sigstores [security 85 | process](https://github.com/sigstore/community/security/policy) 86 | -------------------------------------------------------------------------------- /src/registry/oci_client.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use super::{ClientCapabilities, ClientCapabilitiesDeps}; 17 | use crate::errors::{Result, SigstoreError}; 18 | 19 | use async_trait::async_trait; 20 | 21 | /// Internal client for an OCI Registry. This performs actual 22 | /// calls against the remote registry.OciClient 23 | /// 24 | /// For testing purposes, use instead the client inside of the 25 | /// `mock_client` module. 26 | pub(crate) struct OciClient { 27 | pub registry_client: oci_client::Client, 28 | } 29 | 30 | impl ClientCapabilitiesDeps for OciClient {} 31 | 32 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 33 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 34 | impl ClientCapabilities for OciClient { 35 | async fn fetch_manifest_digest( 36 | &mut self, 37 | image: &oci_client::Reference, 38 | auth: &oci_client::secrets::RegistryAuth, 39 | ) -> Result { 40 | self.registry_client 41 | .fetch_manifest_digest(image, auth) 42 | .await 43 | .map_err(|e| SigstoreError::RegistryFetchManifestError { 44 | image: image.whole(), 45 | error: e.to_string(), 46 | }) 47 | } 48 | 49 | async fn pull( 50 | &mut self, 51 | image: &oci_client::Reference, 52 | auth: &oci_client::secrets::RegistryAuth, 53 | accepted_media_types: Vec<&str>, 54 | ) -> Result { 55 | self.registry_client 56 | .pull(image, auth, accepted_media_types) 57 | .await 58 | .map_err(|e| SigstoreError::RegistryPullError { 59 | image: image.whole(), 60 | error: e.to_string(), 61 | }) 62 | } 63 | 64 | async fn pull_manifest( 65 | &mut self, 66 | image: &oci_client::Reference, 67 | auth: &oci_client::secrets::RegistryAuth, 68 | ) -> Result<(oci_client::manifest::OciManifest, String)> { 69 | self.registry_client 70 | .pull_manifest(image, auth) 71 | .await 72 | .map_err(|e| SigstoreError::RegistryPullManifestError { 73 | image: image.whole(), 74 | error: e.to_string(), 75 | }) 76 | } 77 | 78 | async fn push( 79 | &mut self, 80 | image_ref: &oci_client::Reference, 81 | layers: &[oci_client::client::ImageLayer], 82 | config: oci_client::client::Config, 83 | auth: &oci_client::secrets::RegistryAuth, 84 | manifest: Option, 85 | ) -> Result { 86 | self.registry_client 87 | .push(image_ref, layers, config, auth, manifest) 88 | .await 89 | .map_err(|e| SigstoreError::RegistryPushError { 90 | image: image_ref.whole(), 91 | error: e.to_string(), 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/rekor/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! This crate aims to provide Rust API client for Rekor to Rust developers. 17 | //! 18 | //! Rekor is a cryptographically secure, immutable transparency log for signed software releases. 19 | //! 20 | //! **Warning:** this crate is still experimental. Its API can change at any time. 21 | //! 22 | //! # Security 23 | //! 24 | //! Should you discover any security issues, please refer to 25 | //! Sigstore's [security process](https://github.com/sigstore/community/blob/main/SECURITY.md). 26 | //! 27 | //! # How to use this crate 28 | //! The examples folder contains code that shows users how to make API calls. 29 | //! It also provides a clean interface with step-by-step instructions that other developers can copy and paste. 30 | //! 31 | //! ``` 32 | //! use clap::{Arg, Command}; 33 | //! use sigstore::rekor::apis::{configuration::Configuration, entries_api}; 34 | //! use sigstore::rekor::models::log_entry::LogEntry; 35 | //! use std::str::FromStr; 36 | //! #[tokio::main] 37 | //! async fn main() { 38 | //! /* 39 | //! Retrieves an entry and inclusion proof from the transparency log (if it exists) by index 40 | //! Example command : 41 | //! cargo run --example get_log_entry_by_index -- --log_index 99 42 | //! */ 43 | //! let matches = Command::new("cmd").arg( 44 | //! Arg::new("log_index") 45 | //! .long("log_index") 46 | //! .num_args(1) 47 | //! .value_parser(clap::value_parser!(i32)) 48 | //! .default_value("1") 49 | //! .help("log_index of the artifact"), 50 | //! ); 51 | //! 52 | //! let flags = matches.get_matches(); 53 | //! let index: &i32 = flags.get_one("log_index").unwrap(); 54 | //! 55 | //! let configuration = Configuration::default(); 56 | //! 57 | //! let message: LogEntry = entries_api::get_log_entry_by_index(&configuration, *index) 58 | //! .await 59 | //! .unwrap(); 60 | //! println!("{:#?}", message); 61 | //! } 62 | //! ``` 63 | //! 64 | //! The following comment in the code tells the user how to provide the required values to the API calls using cli flags. 65 | //! 66 | //! In the example below, the user can retrieve different entries by inputting a different value for the log_index flag. 67 | //! 68 | //! 69 | //!/* 70 | //!Retrieves an entry and inclusion proof from the transparency log (if it exists) by index 71 | //!Example command : 72 | //!cargo run --example get_log_entry_by_index -- --log_index 99 73 | //!*/ 74 | //! 75 | //! # The example code is provided for the following API calls: 76 | //! 77 | //!- create_log_entry 78 | //!- get_log_entry_by_index 79 | //!- get_log_entry_by_uuid 80 | //!- get_log_info 81 | //!- get_log_proof 82 | //!- get_public_key 83 | //!- get_timestamp_cert_chain 84 | //!- get_timestamp_response 85 | //!- search_index 86 | //!- search_log_query 87 | //! 88 | 89 | pub mod apis; 90 | pub mod models; 91 | type TreeSize = i64; 92 | -------------------------------------------------------------------------------- /src/registry/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | pub mod config; 17 | pub use config::*; 18 | 19 | #[cfg(feature = "cosign")] 20 | pub(crate) mod oci_client; 21 | #[cfg(feature = "cosign")] 22 | pub(crate) use oci_client::*; 23 | 24 | #[cfg(feature = "cosign")] 25 | pub mod oci_reference; 26 | #[cfg(feature = "cosign")] 27 | pub use oci_reference::OciReference; 28 | 29 | #[cfg(all(feature = "cosign", feature = "cached-client"))] 30 | pub(crate) mod oci_caching_client; 31 | #[cfg(all(feature = "cosign", feature = "cached-client"))] 32 | pub(crate) use oci_caching_client::*; 33 | 34 | use crate::errors::Result; 35 | 36 | use async_trait::async_trait; 37 | 38 | use ::oci_client as oci_client_dep; 39 | 40 | /// Workaround to ensure the `Send + Sync` supertraits are 41 | /// required by ClientCapabilities only when the target 42 | /// architecture is NOT wasm32. 43 | /// 44 | /// This intermediate trait has been created to avoid 45 | /// to define ClientCapabilities twice (one with `#[cfg(target_arch = "wasm32")]`, 46 | /// the other with `#[cfg(not(target_arch = "wasm32"))]` 47 | #[cfg(not(target_arch = "wasm32"))] 48 | pub(crate) trait ClientCapabilitiesDeps: Send + Sync {} 49 | 50 | /// Workaround to ensure the `Send + Sync` supertraits are 51 | /// required by ClientCapabilities only when the target 52 | /// architecture is NOT wasm32. 53 | /// 54 | /// This intermediate trait has been created to avoid 55 | /// to define ClientCapabilities twice (one with `#[cfg(target_arch = "wasm32")]`, 56 | /// the other with `#[cfg(not(target_arch = "wasm32"))]` 57 | #[cfg(target_arch = "wasm32")] 58 | pub(crate) trait ClientCapabilitiesDeps {} 59 | 60 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 61 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 62 | /// Capabilities that are expected to be provided by a registry client 63 | pub(crate) trait ClientCapabilities: ClientCapabilitiesDeps { 64 | async fn fetch_manifest_digest( 65 | &mut self, 66 | image: &oci_client_dep::Reference, 67 | auth: &oci_client_dep::secrets::RegistryAuth, 68 | ) -> Result; 69 | 70 | async fn pull( 71 | &mut self, 72 | image: &oci_client_dep::Reference, 73 | auth: &oci_client_dep::secrets::RegistryAuth, 74 | accepted_media_types: Vec<&str>, 75 | ) -> Result; 76 | 77 | async fn pull_manifest( 78 | &mut self, 79 | image: &oci_client_dep::Reference, 80 | auth: &oci_client_dep::secrets::RegistryAuth, 81 | ) -> Result<(oci_client_dep::manifest::OciManifest, String)>; 82 | 83 | async fn push( 84 | &mut self, 85 | image_ref: &oci_client_dep::Reference, 86 | layers: &[oci_client_dep::client::ImageLayer], 87 | config: oci_client_dep::client::Config, 88 | auth: &oci_client_dep::secrets::RegistryAuth, 89 | manifest: Option, 90 | ) -> Result; 91 | } 92 | -------------------------------------------------------------------------------- /src/rekor/models/hashedrekord.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | use crate::errors::SigstoreError; 15 | 16 | /// Hashedrekord : Hashed Rekord object 17 | 18 | #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 19 | pub struct Hashedrekord { 20 | #[serde(rename = "kind")] 21 | pub kind: String, 22 | #[serde(rename = "apiVersion")] 23 | pub api_version: String, 24 | #[serde(rename = "spec")] 25 | pub spec: Spec, 26 | } 27 | 28 | impl Hashedrekord { 29 | /// Hashed Rekord object 30 | pub fn new(kind: String, api_version: String, spec: Spec) -> Hashedrekord { 31 | Hashedrekord { 32 | kind, 33 | api_version, 34 | spec, 35 | } 36 | } 37 | } 38 | 39 | /// Stores the Signature and Data struct 40 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 41 | #[serde(rename_all = "camelCase")] 42 | pub struct Spec { 43 | pub signature: Signature, 44 | pub data: Data, 45 | } 46 | 47 | // Design a SPEC struct 48 | impl Spec { 49 | pub fn new(signature: Signature, data: Data) -> Spec { 50 | Spec { signature, data } 51 | } 52 | } 53 | 54 | /// Stores the signature format, signature of the artifact and the PublicKey struct 55 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct Signature { 58 | pub content: String, 59 | pub public_key: PublicKey, 60 | } 61 | 62 | impl Signature { 63 | pub fn new(content: String, public_key: PublicKey) -> Signature { 64 | Signature { 65 | content, 66 | public_key, 67 | } 68 | } 69 | } 70 | 71 | /// Stores the public key used to sign the artifact 72 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 73 | #[serde(rename_all = "camelCase")] 74 | pub struct PublicKey { 75 | content: String, 76 | } 77 | 78 | impl PublicKey { 79 | pub fn new(content: String) -> PublicKey { 80 | PublicKey { content } 81 | } 82 | 83 | pub fn decode(&self) -> Result { 84 | let decoded = BASE64_STD_ENGINE.decode(&self.content)?; 85 | String::from_utf8(decoded).map_err(|e| SigstoreError::from(e.utf8_error())) 86 | } 87 | } 88 | 89 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 90 | #[serde(rename_all = "camelCase")] 91 | pub struct Data { 92 | pub hash: Hash, 93 | } 94 | 95 | impl Data { 96 | pub fn new(hash: Hash) -> Data { 97 | Data { hash } 98 | } 99 | } 100 | 101 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 102 | #[allow(non_camel_case_types)] 103 | pub enum AlgorithmKind { 104 | #[default] 105 | sha256, 106 | sha1, 107 | } 108 | 109 | /// Stores the algorithm used to hash the artifact and the value of the hash 110 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 111 | #[serde(rename_all = "camelCase")] 112 | pub struct Hash { 113 | pub algorithm: AlgorithmKind, 114 | pub value: String, 115 | } 116 | 117 | impl Hash { 118 | pub fn new(algorithm: AlgorithmKind, value: String) -> Hash { 119 | Hash { algorithm, value } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/bundle/models.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::str::FromStr; 3 | 4 | use base64::{Engine as _, engine::general_purpose::STANDARD as base64}; 5 | 6 | use sigstore_protobuf_specs::dev::sigstore::{ 7 | common::v1::LogId, 8 | rekor::v1::{Checkpoint, InclusionPromise, InclusionProof, KindVersion, TransparencyLogEntry}, 9 | }; 10 | 11 | use crate::rekor::models::{ 12 | LogEntry as RekorLogEntry, log_entry::InclusionProof as RekorInclusionProof, 13 | }; 14 | 15 | // Known Sigstore bundle media types. 16 | #[derive(Clone, Copy, Debug)] 17 | pub enum Version { 18 | Bundle0_1, 19 | Bundle0_2, 20 | } 21 | 22 | impl Display for Version { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | f.write_str(match &self { 25 | Version::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1", 26 | Version::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2", 27 | }) 28 | } 29 | } 30 | 31 | impl FromStr for Version { 32 | type Err = (); 33 | 34 | fn from_str(s: &str) -> Result { 35 | match s { 36 | "application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(Version::Bundle0_1), 37 | "application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(Version::Bundle0_2), 38 | _ => Err(()), 39 | } 40 | } 41 | } 42 | 43 | #[inline] 44 | fn decode_hex>(hex: S) -> Result, ()> { 45 | hex::decode(hex.as_ref()).or(Err(())) 46 | } 47 | 48 | impl TryFrom for InclusionProof { 49 | type Error = (); 50 | 51 | fn try_from(value: RekorInclusionProof) -> Result { 52 | let hashes = value 53 | .hashes 54 | .iter() 55 | .map(decode_hex) 56 | .collect::, _>>()?; 57 | 58 | Ok(InclusionProof { 59 | checkpoint: Some(Checkpoint { 60 | envelope: value.checkpoint, 61 | }), 62 | hashes, 63 | log_index: value.log_index, 64 | root_hash: decode_hex(value.root_hash)?, 65 | tree_size: value.tree_size, 66 | }) 67 | } 68 | } 69 | 70 | /// Convert log entries returned from Rekor into Sigstore Bundle format entries. 71 | impl TryFrom for TransparencyLogEntry { 72 | type Error = (); 73 | 74 | fn try_from(value: RekorLogEntry) -> Result { 75 | let canonicalized_body = serde_json_canonicalizer::to_string(&value.body) 76 | .map_err(|_| ())? 77 | .into_bytes(); 78 | let inclusion_promise = Some(InclusionPromise { 79 | signed_entry_timestamp: base64 80 | .decode(value.verification.signed_entry_timestamp) 81 | .or(Err(()))?, 82 | }); 83 | let inclusion_proof = value 84 | .verification 85 | .inclusion_proof 86 | .map(|p| p.try_into()) 87 | .transpose()?; 88 | 89 | Ok(TransparencyLogEntry { 90 | canonicalized_body, 91 | inclusion_promise, 92 | inclusion_proof, 93 | integrated_time: value.integrated_time, 94 | kind_version: Some(KindVersion { 95 | kind: "hashedrekord".to_owned(), 96 | version: "0.0.1".to_owned(), 97 | }), 98 | log_id: Some(LogId { 99 | key_id: decode_hex(value.log_i_d)?, 100 | }), 101 | log_index: value.log_index, 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/cosign/verification_constraint/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Structs that can be used to verify [`crate::cosign::SignatureLayer`] 17 | //! with special business logic. 18 | //! 19 | //! This module provides already the most common kind of verification constraints: 20 | //! * [`PublicKeyVerifier`]: ensure a signature has been produced by a specific 21 | //! cosign key 22 | //! * [`CertSubjectEmailVerifier`]: ensure a signature has been produced in keyless mode, 23 | //! plus the email address associated with the signer matches a specific one 24 | //! * [`CertSubjectUrlVerifier`]: ensure a signature has been produced in keyless mode, 25 | //! plus the certificate SAN has a specific URI inside of it. This can be used to verify 26 | //! signatures produced by GitHub Actions. 27 | //! 28 | //! Developers can define ad-hoc validation logic by creating a Struct that implements 29 | //! the [`VerificationConstraintVec`] trait. 30 | 31 | use super::signature_layers::SignatureLayer; 32 | use crate::errors::Result; 33 | 34 | /// A list of objects implementing the [`VerificationConstraint`] trait 35 | pub type VerificationConstraintVec = Vec>; 36 | 37 | /// A list of references to objects implementing the [`VerificationConstraint`] trait 38 | pub type VerificationConstraintRefVec<'a> = Vec<&'a Box>; 39 | 40 | /// A trait that can be used to define verification constraints objects 41 | /// that use a custom verification logic. 42 | pub trait VerificationConstraint: std::fmt::Debug { 43 | /// Given the `signature_layer` object, return `true` if the verification 44 | /// check is satisfied. 45 | /// 46 | /// Developer can use the 47 | /// [`errors::SigstoreError::VerificationConstraintError`](crate::errors::SigstoreError::VerificationConstraintError) 48 | /// error when something goes wrong inside of the verification logic. 49 | /// 50 | /// ``` 51 | /// use sigstore::{ 52 | /// cosign::verification_constraint::VerificationConstraint, 53 | /// cosign::signature_layers::SignatureLayer, 54 | /// errors::{SigstoreError, Result}, 55 | /// }; 56 | /// 57 | /// #[derive(Debug)] 58 | /// struct MyVerifier{} 59 | /// 60 | /// impl VerificationConstraint for MyVerifier { 61 | /// fn verify(&self, _sl: &SignatureLayer) -> Result { 62 | /// Err(SigstoreError::VerificationConstraintError( 63 | /// "something went wrong!".to_string())) 64 | /// } 65 | /// } 66 | fn verify(&self, signature_layer: &SignatureLayer) -> Result; 67 | } 68 | 69 | pub mod certificate_verifier; 70 | pub use certificate_verifier::CertificateVerifier; 71 | 72 | pub mod public_key_verifier; 73 | pub use public_key_verifier::PublicKeyVerifier; 74 | 75 | pub mod cert_subject_email_verifier; 76 | pub use cert_subject_email_verifier::CertSubjectEmailVerifier; 77 | 78 | pub mod cert_subject_url_verifier; 79 | pub use cert_subject_url_verifier::CertSubjectUrlVerifier; 80 | 81 | pub mod annotation_verifier; 82 | pub use annotation_verifier::AnnotationVerifier; 83 | -------------------------------------------------------------------------------- /src/oauth/token.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Sigstore Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use chrono::{DateTime, Utc}; 16 | use openidconnect::core::CoreIdToken; 17 | use serde::Deserialize; 18 | 19 | use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD as base64}; 20 | 21 | use crate::errors::SigstoreError; 22 | 23 | #[derive(Deserialize)] 24 | pub struct Claims { 25 | pub aud: String, 26 | #[serde(with = "chrono::serde::ts_seconds")] 27 | pub exp: DateTime, 28 | #[serde(with = "chrono::serde::ts_seconds_option")] 29 | #[serde(default)] 30 | pub nbf: Option>, 31 | pub email: String, 32 | } 33 | 34 | pub type UnverifiedClaims = Claims; 35 | 36 | /// A Sigstore token. 37 | pub struct IdentityToken { 38 | original_token: String, 39 | claims: UnverifiedClaims, 40 | } 41 | 42 | impl IdentityToken { 43 | /// Returns the **unverified** claim set for the token. 44 | /// 45 | /// The [UnverifiedClaims] returned from this method should not be used to enforce security 46 | /// invariants. 47 | pub fn unverified_claims(&self) -> &UnverifiedClaims { 48 | &self.claims 49 | } 50 | 51 | /// Returns whether or not this token is within its self-stated validity period. 52 | pub fn in_validity_period(&self) -> bool { 53 | let now = Utc::now(); 54 | 55 | if let Some(nbf) = self.claims.nbf { 56 | nbf <= now && now < self.claims.exp 57 | } else { 58 | now < self.claims.exp 59 | } 60 | } 61 | } 62 | 63 | impl TryFrom<&str> for IdentityToken { 64 | type Error = SigstoreError; 65 | 66 | fn try_from(value: &str) -> Result { 67 | let parts: [&str; 3] = value.split('.').collect::>().try_into().or(Err( 68 | SigstoreError::IdentityTokenError("Malformed JWT".into()), 69 | ))?; 70 | 71 | let claims = base64 72 | .decode(parts[1]) 73 | .or(Err(SigstoreError::IdentityTokenError( 74 | "Malformed JWT: Unable to decode claims".into(), 75 | )))?; 76 | let claims: Claims = serde_json::from_slice(&claims).or(Err( 77 | SigstoreError::IdentityTokenError("Malformed JWT: claims JSON malformed".into()), 78 | ))?; 79 | if claims.aud != "sigstore" { 80 | return Err(SigstoreError::IdentityTokenError( 81 | "Not a Sigstore JWT".into(), 82 | )); 83 | } 84 | 85 | Ok(IdentityToken { 86 | original_token: value.to_owned(), 87 | claims, 88 | }) 89 | } 90 | } 91 | 92 | impl From for IdentityToken { 93 | fn from(value: CoreIdToken) -> Self { 94 | value 95 | .to_string() 96 | .as_str() 97 | .try_into() 98 | .expect("Token conversion failed") 99 | } 100 | } 101 | 102 | impl std::fmt::Display for IdentityToken { 103 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 104 | write!(f, "{}", self.original_token.clone()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/key_interface/key_pair_import/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::{Result, bail}; 17 | use sigstore::crypto::{ 18 | CosignVerificationKey, SigningScheme, 19 | signing_key::{SigStoreKeyPair, ecdsa::ECDSAKeys}, 20 | }; 21 | 22 | const PASSWORD: &str = "password"; 23 | 24 | const ECDSA_P256_ASN1_PUBLIC_PEM: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PUBLIC_PEM.pub"); 25 | const ECDSA_P256_ASN1_PUBLIC_DER: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PUBLIC_DER.pub"); 26 | const ECDSA_P256_ASN1_PRIVATE_PEM: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PRIVATE_PEM.key"); 27 | const ECDSA_P256_ASN1_PRIVATE_DER: &[u8] = include_bytes!("./ECDSA_P256_ASN1_PRIVATE_DER.key"); 28 | const ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM: &[u8] = 29 | include_bytes!("./ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key"); 30 | 31 | fn main() -> Result<()> { 32 | let _ = CosignVerificationKey::from_pem(ECDSA_P256_ASN1_PUBLIC_PEM, &SigningScheme::default())?; 33 | println!( 34 | "Imported PEM encoded public key as CosignVerificationKey using ECDSA_P256_ASN1_PUBLIC_PEM as verification algorithm." 35 | ); 36 | 37 | let _ = CosignVerificationKey::from_der(ECDSA_P256_ASN1_PUBLIC_DER, &SigningScheme::default())?; 38 | println!( 39 | "Imported DER encoded public key as CosignVerificationKey using ECDSA_P256_ASN1_PUBLIC_PEM as verification algorithm." 40 | ); 41 | 42 | let _ = CosignVerificationKey::try_from_pem(ECDSA_P256_ASN1_PUBLIC_PEM)?; 43 | println!("Imported PEM encoded public key as CosignVerificationKey."); 44 | 45 | let _ = CosignVerificationKey::try_from_der(ECDSA_P256_ASN1_PUBLIC_DER)?; 46 | println!("Imported DER encoded public key as CosignVerificationKey."); 47 | 48 | let _ = SigStoreKeyPair::from_pem(ECDSA_P256_ASN1_PRIVATE_PEM)?; 49 | println!("Imported PEM encoded private key as SigStoreKeyPair."); 50 | 51 | let _ = ECDSAKeys::from_pem(ECDSA_P256_ASN1_PRIVATE_PEM)?; 52 | println!("Imported PEM encoded private key as ECDSAKeys."); 53 | 54 | let _ = SigStoreKeyPair::from_der(ECDSA_P256_ASN1_PRIVATE_DER)?; 55 | println!("Imported DER encoded private key as SigStoreKeyPair."); 56 | 57 | let _ = ECDSAKeys::from_der(ECDSA_P256_ASN1_PRIVATE_DER)?; 58 | println!("Imported DER encoded private key as ECDSAKeys."); 59 | 60 | let key_pair = SigStoreKeyPair::from_encrypted_pem( 61 | ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM, 62 | PASSWORD.as_bytes(), 63 | )?; 64 | println!("Imported encrypted PEM encoded private key as SigStoreKeyPair."); 65 | 66 | let ecdsa_key_pair = 67 | ECDSAKeys::from_encrypted_pem(ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM, PASSWORD.as_bytes())?; 68 | println!("Imported encrypted PEM encoded private key as ECDSAKeys."); 69 | 70 | let _ = ecdsa_key_pair.to_sigstore_signer()?; 71 | println!("Converted ECDSAKeys to SigStoreSigner."); 72 | 73 | match key_pair { 74 | SigStoreKeyPair::ECDSA(inner) => { 75 | inner.to_sigstore_signer()?; 76 | println!("Converted SigStoreKeyPair to SigStoreSigner."); 77 | } 78 | _ => bail!("Wrong key pair type."), 79 | } 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /examples/cosign/verify-blob/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to verify a blob signature that was created by the 2 | `cosign sign-blob` command. 3 | 4 | ### Create the artifact to be signed. 5 | ```console 6 | cd examples/cosign/verify-blob 7 | echo something > artifact.txt 8 | ``` 9 | 10 | ### Sign the artifact.txt file using cosign 11 | ``` 12 | cosign sign-blob \ 13 | --output-signature signature \ 14 | --output-certificate certificate \ 15 | artifact.txt 16 | 17 | Using payload from: artifact.txt 18 | Generating ephemeral keys... 19 | Retrieving signed certificate... 20 | 21 | Note that there may be personally identifiable information associated with this signed artifact. 22 | This may include the email address associated with the account with which you authenticate. 23 | This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later. 24 | By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs. 25 | 26 | Are you sure you want to continue? (y/[N]): y 27 | Your browser will now be opened to: 28 | 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 29 | Successfully verified SCT... 30 | using ephemeral certificate: 31 | -----BEGIN CERTIFICATE----- 32 | MIICqTCCAi6gAwIBAgIUc4soYChsRq4lWUu990I7GrErO9IwCgYIKoZIzj0EAwMw 33 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 34 | cm1lZGlhdGUwHhcNMjMwMzEzMTEzOTIwWhcNMjMwMzEzMTE0OTIwWjAAMFkwEwYH 35 | KoZIzj0CAQYIKoZIzj0DAQcDQgAE7zhgP7vhI8QzXm0nMC6wvj1c/82sRx4ozvIB 36 | 6od9xfiNofmjlDJtdG+IrObrxONhAXffZWDB2N8SmjAcHVz85qOCAU0wggFJMA4G 37 | A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU3U2j 38 | b80jcAEZIXWnZgIjGEJ39EcwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y 39 | ZD8wJwYDVR0RAQH/BB0wG4EZZGFuaWVsLmJldmVuaXVzQGdtYWlsLmNvbTAsBgor 40 | BgEEAYO/MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYoGCisG 41 | AQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynu 42 | jgAAAYbaxJHJAAAEAwBHMEUCIFsrHqZF6pqZotjvHvvSPxk7jdtWkAPLn55APKmj 43 | lD72AiEA3807EnFi2HLZcoP+85fmCH5awXDX1KLPUW7kibOwKpAwCgYIKoZIzj0E 44 | AwMDaQAwZgIxAOf6C68qm6r7Rovurc7j+JQkki8hsoWd68vC+VvSazSFMpCxrvm7 45 | HlrW7oMAzjlCzwIxANQWgC60eNi7QNeqlMlo/UraZz8xFho2d0Fr5fa0ZfALBE82 46 | I9TvCXsVua7/ERp+eQ== 47 | -----END CERTIFICATE----- 48 | 49 | tlog entry created with index: 15311440 50 | MEYCIQDSsR/enheXGrFNLtgEVNLvLFTYPa1cWOTBZBqNYv/kQQIhALFxLx27ECqtVyM3jGedhharRngiHJ4EMdfvA6Bl3+pm 51 | ``` 52 | 53 | The above command will have saved two files, one containing the signature 54 | (which can also be seen as the last line of the output above), and one which 55 | contains the certificate. 56 | 57 | ### Verify using sigstore-rs: 58 | To verify the blob using this example use the following command: 59 | ```console 60 | cd examples/cosign/verify-blob 61 | cargo run --example verify-blob -- \ 62 | --certificate certificate \ 63 | --signature signature \ 64 | artifact.txt 65 | Verification succeeded 66 | ``` 67 | 68 | ### Verify using cosign 69 | To verify the blob using `cosign verify-blob` we need to specify a 70 | `--certificate-oidc-issuer` which currently can be one of: 71 | the following: 72 | * https://github.com/login/oauth 73 | * https://accounts.google.com 74 | * https://login.microsoftonline.com 75 | 76 | 77 | And we also have to specify the email address we used as the 78 | `--certificate-identity`: 79 | ```console 80 | cosign verify-blob \ 81 | --cert certificate \ 82 | --signature signature \ 83 | --certificate-identity \ 84 | --certificate-oidc-issuer https://github.com/login/oauth \ 85 | artifact.txt 86 | Verified OK 87 | ``` 88 | -------------------------------------------------------------------------------- /src/fulcio/models.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Sigstore Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Models for interfacing with Fulcio. 16 | //! 17 | //! 18 | 19 | use pem::Pem; 20 | use pkcs8::der::EncodePem; 21 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 22 | use serde_repr::Deserialize_repr; 23 | use serde_with::{ 24 | DeserializeAs, SerializeAs, 25 | base64::{Base64, Standard}, 26 | formats::Padded, 27 | serde_as, 28 | }; 29 | use x509_cert::Certificate; 30 | 31 | fn serialize_x509_csr( 32 | input: &x509_cert::request::CertReq, 33 | ser: S, 34 | ) -> std::result::Result 35 | where 36 | S: Serializer, 37 | { 38 | let encoded = input 39 | .to_pem(pkcs8::LineEnding::LF) 40 | .map_err(serde::ser::Error::custom)?; 41 | 42 | Base64::::serialize_as(&encoded, ser) 43 | } 44 | 45 | fn deserialize_inner_detached_sct<'de, D>(de: D) -> std::result::Result 46 | where 47 | D: Deserializer<'de>, 48 | { 49 | let buf: Vec = Base64::::deserialize_as(de)?; 50 | serde_json::from_slice(&buf).map_err(serde::de::Error::custom) 51 | } 52 | 53 | fn deserialize_inner_detached_sct_signature<'de, D>(de: D) -> Result, D::Error> 54 | where 55 | D: Deserializer<'de>, 56 | { 57 | let buf: Vec = Base64::::deserialize_as(de)?; 58 | 59 | // The first two bytes indicate the signature and hash algorithms so let's skip those. 60 | // The next two bytes indicate the size of the signature. 61 | let signature_size = u16::from_be_bytes(buf[2..4].try_into().expect("unexpected length")); 62 | 63 | // This should be equal to the length of the remainder of the signature buffer. 64 | let signature = buf[4..].to_vec(); 65 | if signature_size as usize != signature.len() { 66 | return Err(serde::de::Error::custom("signature size mismatch")); 67 | } 68 | Ok(signature) 69 | } 70 | 71 | #[derive(Serialize)] 72 | #[serde(rename_all = "camelCase")] 73 | pub struct CreateSigningCertificateRequest { 74 | #[serde(serialize_with = "serialize_x509_csr")] 75 | pub certificate_signing_request: x509_cert::request::CertReq, 76 | } 77 | 78 | #[derive(Deserialize)] 79 | #[serde(rename_all = "camelCase")] 80 | pub enum SigningCertificate { 81 | SignedCertificateDetachedSct(SigningCertificateDetachedSCT), 82 | SignedCertificateEmbeddedSct(SigningCertificateEmbeddedSCT), 83 | } 84 | 85 | #[derive(Deserialize, Debug, Clone)] 86 | #[serde(rename_all = "camelCase")] 87 | pub struct SigningCertificateDetachedSCT { 88 | pub chain: CertificateChain, 89 | #[serde(deserialize_with = "deserialize_inner_detached_sct")] 90 | pub signed_certificate_timestamp: InnerDetachedSCT, 91 | } 92 | 93 | #[derive(Deserialize)] 94 | #[serde(rename_all = "camelCase")] 95 | pub struct SigningCertificateEmbeddedSCT { 96 | pub chain: CertificateChain, 97 | } 98 | 99 | #[derive(Deserialize, Debug, Clone)] 100 | pub struct CertificateChain { 101 | pub certificates: Vec, 102 | } 103 | 104 | #[serde_as] 105 | #[derive(Deserialize, Debug, Clone)] 106 | pub struct InnerDetachedSCT { 107 | pub sct_version: SCTVersion, 108 | #[serde_as(as = "Base64")] 109 | pub id: [u8; 32], 110 | pub timestamp: u64, 111 | #[serde(deserialize_with = "deserialize_inner_detached_sct_signature")] 112 | pub signature: Vec, 113 | #[serde_as(as = "Base64")] 114 | pub extensions: Vec, 115 | } 116 | 117 | #[derive(Deserialize_repr, PartialEq, Debug, Clone)] 118 | #[repr(u8)] 119 | pub enum SCTVersion { 120 | V1 = 0, 121 | } 122 | 123 | pub struct CertificateResponse { 124 | pub cert: Certificate, 125 | pub chain: Vec, 126 | pub detached_sct: Option, 127 | } 128 | -------------------------------------------------------------------------------- /src/rekor/models/log_entry.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use crate::errors::SigstoreError; 17 | use crate::rekor::TreeSize; 18 | use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD_ENGINE}; 19 | 20 | use serde::{Deserialize, Serialize}; 21 | use serde_json::{Error, Value, json}; 22 | use std::collections::HashMap; 23 | use std::str::FromStr; 24 | 25 | use super::{ 26 | AlpineAllOf, HashedrekordAllOf, HelmAllOf, IntotoAllOf, JarAllOf, RekordAllOf, Rfc3161AllOf, 27 | RpmAllOf, TufAllOf, 28 | }; 29 | 30 | /// Stores the response returned by Rekor after making a new entry 31 | #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 32 | #[serde(deny_unknown_fields, rename_all = "camelCase")] 33 | pub struct LogEntry { 34 | pub uuid: String, 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub attestation: Option, 37 | pub body: Body, 38 | pub integrated_time: i64, 39 | pub log_i_d: String, 40 | pub log_index: i64, 41 | pub verification: Verification, 42 | } 43 | 44 | impl FromStr for LogEntry { 45 | type Err = Error; 46 | fn from_str(s: &str) -> Result { 47 | let mut log_entry_map = serde_json::from_str::>(s)?; 48 | log_entry_map.entry("body").and_modify(|body| { 49 | let decoded_body = serde_json::to_value( 50 | decode_body(body.as_str().expect("Failed to parse Body")) 51 | .expect("Failed to decode Body"), 52 | ) 53 | .expect("Serialization failed"); 54 | *body = json!(decoded_body); 55 | }); 56 | let log_entry_str = serde_json::to_string(&log_entry_map)?; 57 | Ok(serde_json::from_str::(&log_entry_str).expect("Serialization failed")) 58 | } 59 | } 60 | 61 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 62 | #[serde(tag = "kind")] 63 | #[allow(non_camel_case_types)] 64 | pub enum Body { 65 | alpine(AlpineAllOf), 66 | helm(HelmAllOf), 67 | jar(JarAllOf), 68 | rfc3161(Rfc3161AllOf), 69 | rpm(RpmAllOf), 70 | tuf(TufAllOf), 71 | intoto(IntotoAllOf), 72 | hashedrekord(HashedrekordAllOf), 73 | rekord(RekordAllOf), 74 | } 75 | 76 | impl Default for Body { 77 | fn default() -> Self { 78 | Self::hashedrekord(Default::default()) 79 | } 80 | } 81 | 82 | fn decode_body(s: &str) -> Result { 83 | let decoded = BASE64_STD_ENGINE.decode(s)?; 84 | serde_json::from_slice(&decoded).map_err(SigstoreError::from) 85 | } 86 | 87 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 88 | #[serde(rename_all = "camelCase")] 89 | pub struct Attestation { 90 | // This field is just a place holder 91 | // Not sure what is stored inside the Attestation struct, it is empty for now 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | dummy: Option, 94 | } 95 | 96 | /// Stores the signature over the artifact's logID, logIndex, body and integratedTime. 97 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 98 | #[serde(rename_all = "camelCase")] 99 | pub struct Verification { 100 | #[serde(skip_serializing_if = "Option::is_none")] 101 | pub inclusion_proof: Option, 102 | pub signed_entry_timestamp: String, 103 | } 104 | 105 | /// Stores the signature over the artifact's logID, logIndex, body and integratedTime. 106 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 107 | #[serde(rename_all = "camelCase")] 108 | pub struct InclusionProof { 109 | pub hashes: Vec, 110 | pub log_index: i64, 111 | pub root_hash: String, 112 | pub tree_size: TreeSize, 113 | 114 | /// A snapshot of the transparency log's state at a specific point in time, 115 | /// in [Signed Note format]. 116 | /// 117 | /// [Signed Note format]: https://github.com/transparency-dev/formats/blob/main/log/README.md 118 | pub checkpoint: String, 119 | } 120 | -------------------------------------------------------------------------------- /src/fulcio/oauth.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::Result; 2 | use crate::errors::SigstoreError; 3 | use crate::oauth::openidflow::{OpenIDAuthorize, RedirectListener}; 4 | use openidconnect::core::CoreIdToken; 5 | 6 | /// Default client id ("sigstore"). 7 | pub const DEFAULT_CLIENT_ID: &str = "sigstore"; 8 | 9 | /// Default client secret (the empty string) 10 | pub const DEFAULT_CLIENT_SECRET: &str = ""; 11 | 12 | /// Default issuer (Oauth provider at sigstore.dev) 13 | pub const DEFAULT_ISSUER: &str = "https://oauth2.sigstore.dev/auth"; 14 | 15 | /// Default local redirect port (8080) 16 | pub const DEFAULT_REDIRECT_PORT: u32 = 8080; 17 | 18 | /// Token provider that performs a human-involved OIDC flow to acquire a token id. 19 | #[derive(Default)] 20 | pub struct OauthTokenProvider { 21 | client_id: Option, 22 | client_secret: Option, 23 | issuer: Option, 24 | redirect_port: Option, 25 | } 26 | 27 | impl OauthTokenProvider { 28 | /// Set a non-default client-id. 29 | pub fn with_client_id(self, client_id: &str) -> Self { 30 | Self { 31 | client_id: Some(client_id.to_string()), 32 | client_secret: self.client_secret, 33 | issuer: self.issuer, 34 | redirect_port: self.redirect_port, 35 | } 36 | } 37 | 38 | /// Set a non-default client secret. 39 | pub fn with_client_secret(self, client_secret: &str) -> Self { 40 | Self { 41 | client_id: self.client_id, 42 | client_secret: Some(client_secret.to_string()), 43 | issuer: self.issuer, 44 | redirect_port: self.redirect_port, 45 | } 46 | } 47 | 48 | /// Set a non-default issuer. 49 | pub fn with_issuer(self, issuer: &str) -> Self { 50 | Self { 51 | client_id: self.client_id, 52 | client_secret: self.client_secret, 53 | issuer: Some(issuer.to_string()), 54 | redirect_port: self.redirect_port, 55 | } 56 | } 57 | 58 | /// Set a non-default redirect port. 59 | pub fn with_redirect_port(self, port: u32) -> Self { 60 | Self { 61 | client_id: self.client_id, 62 | client_secret: self.client_secret, 63 | issuer: self.issuer, 64 | redirect_port: Some(port), 65 | } 66 | } 67 | 68 | fn redirect_url(&self) -> String { 69 | format!( 70 | "http://localhost:{}", 71 | self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT) 72 | ) 73 | } 74 | 75 | /// Perform human-involved OIDC flow to acquire an id token, along with 76 | /// the extracted email claim value for use in signed challenge with Fulcio. 77 | pub async fn get_token(&self) -> Result<(CoreIdToken, String)> { 78 | let oidc_url = OpenIDAuthorize::new( 79 | self.client_id 80 | .as_ref() 81 | .unwrap_or(&DEFAULT_CLIENT_ID.to_string()), 82 | self.client_secret 83 | .as_ref() 84 | .unwrap_or(&DEFAULT_CLIENT_SECRET.to_string()), 85 | self.issuer.as_ref().unwrap_or(&DEFAULT_ISSUER.to_string()), 86 | &self.redirect_url(), 87 | ) 88 | .auth_url_async() 89 | .await; 90 | 91 | match oidc_url.as_ref() { 92 | Ok(url) => { 93 | webbrowser::open(url.0.as_ref())?; 94 | println!( 95 | "Open this URL in a browser if it does not automatically open for you:\n{}\n", 96 | url.0, 97 | ); 98 | } 99 | Err(e) => println!("{e}"), 100 | } 101 | 102 | let oidc_url = oidc_url?; 103 | let result = RedirectListener::new( 104 | &format!( 105 | "127.0.0.1:{}", 106 | self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT) 107 | ), 108 | oidc_url.1.clone(), // client 109 | oidc_url.2.clone(), // nonce 110 | oidc_url.3, // pkce_verifier 111 | ) 112 | .redirect_listener_async() 113 | .await; 114 | 115 | if let Ok((_, id_token)) = result { 116 | let verifier = oidc_url.1.id_token_verifier(); 117 | let nonce = &oidc_url.2; 118 | 119 | let claims = id_token.claims(&verifier, nonce); 120 | if let Ok(claims) = claims 121 | && let Some(email) = claims.email() 122 | { 123 | let email = &**email; 124 | return Ok((id_token.clone(), email.clone())); 125 | } 126 | } 127 | 128 | Err(SigstoreError::NoIDToken) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /trust_root/prod/trusted_root.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", 3 | "tlogs": [ 4 | { 5 | "baseUrl": "https://rekor.sigstore.dev", 6 | "hashAlgorithm": "SHA2_256", 7 | "publicKey": { 8 | "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", 9 | "keyDetails": "PKIX_ECDSA_P256_SHA_256", 10 | "validFor": { 11 | "start": "2021-01-12T11:53:27.000Z" 12 | } 13 | }, 14 | "logId": { 15 | "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" 16 | } 17 | } 18 | ], 19 | "certificateAuthorities": [ 20 | { 21 | "subject": { 22 | "organization": "sigstore.dev", 23 | "commonName": "sigstore" 24 | }, 25 | "uri": "https://fulcio.sigstore.dev", 26 | "certChain": { 27 | "certificates": [ 28 | { 29 | "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" 30 | } 31 | ] 32 | }, 33 | "validFor": { 34 | "start": "2021-03-07T03:20:29.000Z", 35 | "end": "2022-12-31T23:59:59.999Z" 36 | } 37 | }, 38 | { 39 | "subject": { 40 | "organization": "sigstore.dev", 41 | "commonName": "sigstore" 42 | }, 43 | "uri": "https://fulcio.sigstore.dev", 44 | "certChain": { 45 | "certificates": [ 46 | { 47 | "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" 48 | }, 49 | { 50 | "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" 51 | } 52 | ] 53 | }, 54 | "validFor": { 55 | "start": "2022-04-13T20:06:15.000Z" 56 | } 57 | } 58 | ], 59 | "ctlogs": [ 60 | { 61 | "baseUrl": "https://ctfe.sigstore.dev/test", 62 | "hashAlgorithm": "SHA2_256", 63 | "publicKey": { 64 | "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", 65 | "keyDetails": "PKIX_ECDSA_P256_SHA_256", 66 | "validFor": { 67 | "start": "2021-03-14T00:00:00.000Z", 68 | "end": "2022-10-31T23:59:59.999Z" 69 | } 70 | }, 71 | "logId": { 72 | "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" 73 | } 74 | }, 75 | { 76 | "baseUrl": "https://ctfe.sigstore.dev/2022", 77 | "hashAlgorithm": "SHA2_256", 78 | "publicKey": { 79 | "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", 80 | "keyDetails": "PKIX_ECDSA_P256_SHA_256", 81 | "validFor": { 82 | "start": "2022-10-20T00:00:00.000Z" 83 | } 84 | }, 85 | "logId": { 86 | "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" 87 | } 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /examples/rekor/search_index/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use clap::{Arg, Command}; 17 | use sigstore::rekor::apis::{configuration::Configuration, index_api}; 18 | use sigstore::rekor::models::{ 19 | SearchIndex, search_index_public_key, search_index_public_key::Format, 20 | }; 21 | 22 | #[tokio::main] 23 | async fn main() { 24 | /* 25 | 26 | Searches index by entry metadata 27 | 28 | Example command: 29 | cargo run --example search_index -- \ 30 | --hash e2535d638859bb63ea9ea5cf467562cba63b007eae1acd0d73a3f259c582561f \ 31 | --public_key c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVA3M2tuT0tKYVNyVEtEa2U2OEgvRlJoODRZWU5CU0tBN1hPVWRpWmJjeG8gdGVzdEByZWtvci5kZXYK \ 32 | --key_format ssh \ 33 | --email jpenumak@redhat.com 34 | 35 | cargo run --example search_index -- \ 36 | --public_key c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSVA3M2tuT0tKYVNyVEtEa2U2OEgvRlJoODRZWU5CU0tBN1hPVWRpWmJjeG8gdGVzdEByZWtvci5kZXYK \ 37 | --key_format ssh \ 38 | --email jpenumak@redhat.com 39 | 40 | The server might return an error sometimes, 41 | this is because the result depends on the kind of rekor object that gets returned. 42 | 43 | */ 44 | 45 | let matches = Command::new("cmd") 46 | .arg(Arg::new("hash") 47 | .long("hash") 48 | .value_name("HASH") 49 | .help("hash of the artifact")) 50 | .arg(Arg::new("url") 51 | .long("url") 52 | .value_name("URL") 53 | .help("url containing the contents of the artifact (raw github url)")) 54 | .arg(Arg::new("public_key") 55 | .long("public_key") 56 | .value_name("PUBLIC_KEY") 57 | .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.")) 58 | .arg(Arg::new("key_format") 59 | .long("key_format") 60 | .value_name("KEY_FORMAT") 61 | .help("Accepted formats are : pgp / x509 / minsign / ssh / tuf")) 62 | .arg(Arg::new("email") 63 | .long("email") 64 | .value_name("EMAIL") 65 | .help("Author's email")); 66 | 67 | let flags = matches.get_matches(); 68 | 69 | // The following default values will be used if the user does not input values using cli flags 70 | const HASH: &str = "c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413"; 71 | const PUBLIC_KEY: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="; 72 | const KEY_FORMAT: &str = "x509"; 73 | const EMAIL: &str = "jpenumak@redhat.com"; 74 | 75 | let key_format = match flags 76 | .get_one::("key_format") 77 | .unwrap_or(&KEY_FORMAT.to_string()) 78 | .as_str() 79 | { 80 | "pgp" => Format::Pgp, 81 | "x509" => Format::X509, 82 | "minisign" => Format::Minisign, 83 | "ssh" => Format::Ssh, 84 | _ => Format::Tuf, 85 | }; 86 | 87 | let public_key = search_index_public_key::SearchIndexPublicKey { 88 | format: key_format, 89 | content: Some( 90 | flags 91 | .get_one::("public_key") 92 | .unwrap_or(&PUBLIC_KEY.to_string()) 93 | .to_owned(), 94 | ), 95 | url: None, 96 | }; 97 | 98 | let query = SearchIndex { 99 | email: Some( 100 | flags 101 | .get_one::("email") 102 | .unwrap_or(&EMAIL.to_string()) 103 | .to_owned(), 104 | ), 105 | public_key: Some(public_key), 106 | hash: Some( 107 | flags 108 | .get_one("hash") 109 | .unwrap_or(&HASH.to_string()) 110 | .to_owned(), 111 | ), 112 | }; 113 | let configuration = Configuration::default(); 114 | 115 | let uuid_vec = index_api::search_index(&configuration, query).await; 116 | println!("{:#?}", uuid_vec); 117 | } 118 | -------------------------------------------------------------------------------- /examples/key_interface/README.md: -------------------------------------------------------------------------------- 1 | # Example Key Interface 2 | 3 | This is a simple example program that shows how to use the key interfaces. 4 | The key interfaces covers: 5 | * Generating Asymmetric encryption key pair 6 | * Signing with private key 7 | * Exporting the (encrypted) private/public key 8 | * Importing the (encrypted) private/public key 9 | * Verifying signature with public key 10 | 11 | The basic implementation for key-interface can be shown in the following diagram 12 | 13 | ![key_interface](key_interface.drawio.svg) 14 | 15 | The exposed interfaces (marked as `pub`) include: 16 | * `SigStoreSigner` enum: wrapper for `Signer`s of different kinds of signing algorithm. 17 | * `SigStoreKeyPair` enum: wrapper for `KeyPair`s of different kinds of asymmetric encryption algorithm. 18 | * `SigningScheme` enum: Different kinds of signing algorithm. 19 | * `CosignVerificationKey` struct: Public key types to verify signatures for different signing algorithm. 20 | 21 | To show the different usages for them, there will be three typical scenarios. 22 | 23 | ## Key Pair Generation, Signing and Verification 24 | 25 | This example shows the following operations 26 | 27 | * Generating Asymmetric encryption key pair due to given `SigningScheme`. 28 | * Signing the given test data using private key. The signature will be printed 29 | in hex. 30 | * Verifying the signature generated. 31 | 32 | The signing process is performed by `SigStoreSigner`. 33 | The verifying process is performed by `CosignVerificationKey`. 34 | 35 | ### Run the example case 36 | 37 | The following example will create a ECDSA_P256_ASN1 keypair and sign the given 38 | data. 39 | 40 | ```bash 41 | cargo run --example key_pair_gen_sign_verify 42 | ``` 43 | 44 | This example includes the following steps: 45 | 46 | * Randomly generate an `ECDSA_P256_ASN1` key pair, which is represented as `signer` of type 47 | `SigStoreSigner` and includes a private key and a public key. Here, the type of the key 48 | pair is influenced by the given `SigningScheme`. 49 | * Sign the given data `DATA_TO_BE_SIGNED` using the `signer`'s private key. 50 | * Derive [`verification_key`](../../src/crypto/verification_key.rs) from the `signer`. 51 | * Verify the signature generated before using the `verification_key`. 52 | 53 | ## Key Pair Generation and Exporting 54 | 55 | This example shows the following operations 56 | 57 | * Generating Asymmetric encryption key pair due to given `SigningScheme`. 58 | * Export the public key in both DER and PEM format. 59 | * Export the private key in both DER and PEM format. 60 | * Export the encrypted private key in PEM format. 61 | 62 | The key-related operations are performed by `SigStoreKeyPair`. 63 | 64 | ### Run the example case 65 | 66 | The following example will create a ECDSA_P256_ASN1 keypair and sign the given 67 | data. 68 | 69 | ```bash 70 | cargo run --example key_pair_gen_and_export 71 | ``` 72 | 73 | This example includes the following steps: 74 | 75 | * Randomly generate an `ECDSA_P256_ASN1` key pair, which is represented as `signer` of type 76 | `SigStoreSigner` and includes a private key and a public key. Here, the type of the key 77 | pair is influenced by the given `SigningScheme`. 78 | * Export the public key in PEM format and DER format. The result 79 | will be printed (PEM as string, DER as hex). 80 | * Export the private key in PEM format and DER format. The result 81 | will be printed (PEM as string, DER as hex). 82 | * Export the encrypted private key in PEM format. The result 83 | will be printed. 84 | 85 | ## Key Pair Importing 86 | 87 | This example shows the following operations 88 | 89 | * Import the public key in both DER and PEM format to `CosignVerificationKey`. 90 | * Import the private key in both DER and PEM format to `SigStoreKeyPair/ECDSAKeys`. 91 | * Import the encrypted private key in PEM format to `SigStoreKeyPair/ECDSAKeys`. 92 | * Convert the `SigStoreKeyPair` to `SigStoreSigner`. 93 | 94 | ### Run the example case 95 | 96 | The following example will create a ECDSA_P256_ASN1 keypair and sign the given 97 | data. 98 | 99 | ```bash 100 | cargo run --example key_pair_import 101 | ``` 102 | 103 | This example includes the following steps: 104 | 105 | * Import the public key `ECDSA_P256_ASN1_PUBLIC_PEM.pub` as `CosignVerificationKey`. 106 | * Import the public key `ECDSA_P256_ASN1_PUBLIC_DER.pub` as `CosignVerificationKey`. 107 | * Import the private key `ECDSA_P256_ASN1_PRIVATE_PEM.key` as `SigStoreKeyPair`. 108 | * Import the private key `ECDSA_P256_ASN1_PRIVATE_PEM.key` as `ECDSAKeys`. 109 | * Import the private key `ECDSA_P256_ASN1_PRIVATE_DER.key` as `SigStoreKeyPair`. 110 | * Import the private key `ECDSA_P256_ASN1_PRIVATE_DER.key` as `ECDSAKeys`. 111 | * Import the encrypted private key `ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key` as `SigStoreKeyPair`. 112 | * Import the encrypted private key `ECDSA_P256_ASN1_ENCRYPTED_PRIVATE_PEM.key` as `ECDSAKeys`. 113 | * Convert the last `SigStoreKeyPair` to `SigStoreSigner`. 114 | -------------------------------------------------------------------------------- /src/crypto/certificate_pool.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use const_oid::db::rfc5280::ID_KP_CODE_SIGNING; 17 | use pki_types::{CertificateDer, TrustAnchor, UnixTime}; 18 | use webpki::{EndEntityCert, KeyUsage, VerifiedPath}; 19 | 20 | use crate::errors::{Result as SigstoreResult, SigstoreError}; 21 | 22 | /// A collection of trusted root certificates. 23 | #[derive(Default, Debug)] 24 | pub(crate) struct CertificatePool { 25 | trusted_roots: Vec>, 26 | intermediates: Vec>, 27 | } 28 | 29 | impl CertificatePool { 30 | /// Builds a `CertificatePool` instance using the provided list of [`Certificate`]. 31 | pub(crate) fn from_certificates<'r, 'i, R, I>( 32 | trusted_roots: R, 33 | untrusted_intermediates: I, 34 | ) -> SigstoreResult 35 | where 36 | R: IntoIterator>, 37 | I: IntoIterator>, 38 | { 39 | Ok(CertificatePool { 40 | trusted_roots: trusted_roots 41 | .into_iter() 42 | .map(|x| Ok(webpki::anchor_from_trusted_cert(&x)?.to_owned())) 43 | .collect::, webpki::Error>>()?, 44 | intermediates: untrusted_intermediates 45 | .into_iter() 46 | .map(|i| i.into_owned()) 47 | .collect(), 48 | }) 49 | } 50 | 51 | /// Ensures the given certificate has been issued by one of the trusted root certificates 52 | /// An `Err` is returned when the verification fails. 53 | /// 54 | /// **Note well:** certificates issued by Fulcio are, by design, valid only 55 | /// for a really limited amount of time. 56 | /// Because of that the validity checks performed by this method are more 57 | /// relaxed. The validity checks are done inside of 58 | /// [`crate::crypto::verify_validity`] and [`crate::crypto::verify_expiration`]. 59 | pub(crate) fn verify_pem_cert( 60 | &self, 61 | cert_pem: &[u8], 62 | verification_time: Option, 63 | ) -> SigstoreResult<()> { 64 | let cert_pem = pem::parse(cert_pem)?; 65 | if cert_pem.tag() != "CERTIFICATE" { 66 | return Err(SigstoreError::CertificatePoolError( 67 | "PEM file is not a certificate".into(), 68 | )); 69 | } 70 | 71 | self.verify_der_cert(cert_pem.contents(), verification_time) 72 | } 73 | 74 | /// Ensures the given certificate has been issued by one of the trusted root certificates 75 | /// An `Err` is returned when the verification fails. 76 | /// 77 | /// **Note well:** certificates issued by Fulcio are, by design, valid only 78 | /// for a really limited amount of time. 79 | /// Because of that the validity checks performed by this method are more 80 | /// relaxed. The validity checks are done inside of 81 | /// [`crate::crypto::verify_validity`] and [`crate::crypto::verify_expiration`]. 82 | pub(crate) fn verify_der_cert( 83 | &self, 84 | der: &[u8], 85 | verification_time: Option, 86 | ) -> SigstoreResult<()> { 87 | let der = CertificateDer::from(der); 88 | let cert = EndEntityCert::try_from(&der)?; 89 | let time = std::time::Duration::from_secs(chrono::Utc::now().timestamp() as u64); 90 | 91 | self.verify_cert_with_time( 92 | &cert, 93 | verification_time.unwrap_or(UnixTime::since_unix_epoch(time)), 94 | )?; 95 | 96 | Ok(()) 97 | } 98 | 99 | pub(crate) fn verify_cert_with_time<'a, 'cert>( 100 | &'a self, 101 | cert: &'cert EndEntityCert<'cert>, 102 | verification_time: UnixTime, 103 | ) -> Result, webpki::Error> 104 | where 105 | 'a: 'cert, 106 | { 107 | let signing_algs = webpki::ALL_VERIFICATION_ALGS; 108 | let eku_code_signing = ID_KP_CODE_SIGNING.as_bytes(); 109 | 110 | cert.verify_for_usage( 111 | signing_algs, 112 | &self.trusted_roots, 113 | self.intermediates.as_slice(), 114 | verification_time, 115 | KeyUsage::required(eku_code_signing), 116 | None, 117 | None, 118 | ) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/data/keys/rsa_encrypted_private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- 2 | eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 3 | OCwicCI6MX0sInNhbHQiOiJMelBIYTM2VkllY01YZ1NkeXMyelIreHdpRWxTcGF0 4 | OXBUZFlLVXVOK3VVPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 5 | Iiwibm9uY2UiOiJkUncvUXMvOGFXUzdhZzJjS2JUM1pVMDhBdVR1QmswQiJ9LCJj 6 | aXBoZXJ0ZXh0IjoiZDBRZUlIWmlpYVlCenlodXB5R0FFRmZtVzJCTFFxemJTRm9Y 7 | VnRDek5JK2VJTm8xOU9RTW1VNHo0Z2NQY1FHbkE3ay9yc3h0NWRuWjFHOHpsZ0Fl 8 | UzlUVFVMbWdSZjU5Y1oyRGRrOHRYVSs3R3R3YkNCTWFPY1hCSTBCUDc1VGJJK0F6 9 | MEUvT1drZjNhQlhOSE5veU5Ecklyc05obEluUTZ3ODIwZ3pPK0xUYmxVbnJ0UDVm 10 | OFV6V0hDc09hazN1M0hIOXE4bytOZkJ2ZXV1cUp5MUNtckhYamJJWjRtaHR6b2tv 11 | VnROeGQwTFpQN3c3aXJFbXBQaExLMHBWRHpnTlg1OTdGVXlNUzFGcVh1YnBDbFR6 12 | QVBQNUZmNG9kaXJMZVdGL3djUkxFNlhkcDkxeVRVZHoxZ2tQRnN3UmZPVmJYbWUz 13 | d3VwNGhrMXQxeWpMZkdYbUYzWjdWWEFyRDhyenV5aDhsZUpaYlZ0ekRXSzlvVmw3 14 | STNIYmx6Qmx5eGplRjZZeVAvZ1JqUjBCOGZxN1F2a1RjdHlvcHgxdnNsM1E2S3ly 15 | YitiWE5mMGdMSkxRQ01Id3h2TE0yaHFoek5jcDYxendoT1hoNjdpK2grbUtweXBk 16 | a1FMOGZ2NEI1RTd0Y0prTVg2cTJoeHpMQVdWRXFwTUVqY1k0dmszd0dkLzhPckZQ 17 | aFd3NEpMY1c5VTdSRUx3ZTM2RFpzbk9DNW0zK3VRZDJEYUNQSUhyUEw3eVFYOUhv 18 | Qnp1UnFXQ3R3TXNXNWZmMFgycEhpY3l4MVdlY3JMc2crc2Uwck1QRUJNT3hXL3VZ 19 | VjRKTnhBa0FuMTRsbGZqRmlDZlorUGNMcVhLOXVON3k4dU9VeGVYN0hCSmRCdmVT 20 | emRiYXZoRVExbm4rU0NUK21YQjF0WXJBNFl5a2VMa2RzRWtXdGtHZFhnVjNFQmtM 21 | TDByVzlWUENWdkEwTS9mU1ZScGpmQ3g3TkZiZ21GSkl0T1dWMmczWUxxN3h0WU1i 22 | VFU5YWdpbzFBTktMeUtlYkFNdDJOUmw1T0sxSXdya29rUXo0WjZwZWlqeWJlMDBT 23 | ekZ0akQ2cTVQK3VxTHZwT0NwMEphdTdScGRsOXJqN2lmMTRRRWozNFhvaml4Sm9a 24 | ZXprY1k5SEp6QllHSWMyVnpQaEF0TjdIcnZoUDVlVHFaa2ozek5aZnBiT3Nja3BB 25 | eFk5TTRNYWhjd3g4a0VGdy8vZFZKZWREVGx2WDc2ODAvL2t3RmROYmNTcmlZYnVD 26 | UEkyR3Y2YXVnZ2RweCtWWTY2SmtZbzNYUVdmM255Z0xOUk0xMUVTTWVkVHFuL3NN 27 | WnMrNGRDaG84b0tqbGhzZU9URkdDOEpWeENaV2IraHh2RUNNZ0RLTExZbW1PS2J1 28 | cC8vWjg3cVZtUGZjUjc5RVhmN2k1bUZ6QnpzRUFOSXYvaVNpTStSM1R4NXVIUEpr 29 | eXRScTVJNkhvOWEzKzZPV3Q2V2dUbGd5aDdmZmZDWGJXamFGNVJHNE1TcDdQV2FD 30 | QlBvZ3lnZ0ZFazRIWjUyRWt1S1B4TGZsc200QnpkT0FBdmZhaTloSmt0N2t4WUpS 31 | YnVMb3cvMWZxaXNmNHJwcTU3WlZCelJBR09UYW1mT0Y3RnE1a1JNL0FEWStYZWlU 32 | UVJ3ZVRFVk4wOWh1cHdhVm9Uc2lubmNJQlIrcUZMU2pERlJWOUI4M1BBdlNSU1hM 33 | ZnpwbWgrdU4zY1dWL2k4N2xOb25wTlJUSXR3bUFvZjNRU2NPR0lsOUhiR2tWRDM1 34 | bUgvTDhQWVJMMXZlME0yUjdYbXBsWUY5NkhkU3NML3BiYlVLeGFpa0FwTmJCM2VW 35 | U0FTYXdUUEdnYmhBWEtkNEE2YXRkSU5IRHRVbXVqWGxhYTlBL0JhN2h3SU1Ic1kr 36 | R3RpL1RzQzhacmN2ZGUzQkh3cHZJdkpHdnRRUS9IZzVhSTMvRE5yazlUYjhQWUpH 37 | RlBPTGU4ajU1YUtWYVdmeFZNZnBZYW9FL3pwN09ibEVFWnpLY3A4aFZ4S3p3eFJx 38 | Vk1ueGJwYm9TbXBwVzd0Zjc1RXZiUVFyUEUwd2Mzc3EzSm9kMkRYUGlFQmFNOVhB 39 | eGZvSTM5YmZ2L2tNUU9ES3p6U2x6SnV1cTFQZUFMVEh0SmVUQld0Wk1jdytFV3p4 40 | dTNkMndSMWJBQWtpdFVHNkxhRjdZN1BDR2dQWDdSelNVL3Y5bE9Qa0VFMUZhbUxM 41 | TFJCZVU3NDU1WU9HSlFJb09TOVRsbGJyMWRVRi91cmRJbitDS2JhUHRWQ0k1TjNK 42 | SWNFYzdiYTZsd3dBMkszQVNwY2lXM1MzUmxkcVV0NFdUbXBpcU9BVytETm1qcmts 43 | eElFZ0YxOEZ0MzQ5MVBJMUlBWU05UVYrcDVEWnBzV28ycThTSWs3ZC9QL3hpVCtN 44 | QkVxMDNNRjA0cWtPeDczeEplN0NqbWJ5UCtBbzluUWpocWpwWmxxWmJoc1RkS3FR 45 | M252NXpMTmFDdUMwMFhiYUIvTmRjTGl3VEt4WnV3WHp6MFhmcE9ORnVheG9SWU9R 46 | Z01WZlhSSTZ0RXNGaWdUVWgvQm5LaWNjWm5nYWVwNHNIL0Y5MjdES1FIdCtXalc5 47 | OHpHT24rZGFRVWxOV1hOOXlRNERoWFk1WmcremJHZ3hEZi90d1hMMjIzeS9zT0Rt 48 | WXlJb2ZUWDNYRWN4YWdOOHBhSStkODNOWEJHdE4vRkNlNmpKejh2R2Fzci8wdnJy 49 | c2w4RWozV0FaMXFLWlZsd1hYMTA3V2hzZXV5bEFVaVFZZWVKZ2NmSXRPVHpSYWs4 50 | WHVSekw1eVloR2gxU1hMTURPemNBTE5FWEY5cDg5TmFLSGFNcHUwU1hKWUZidlFS 51 | UFNFUk42U2dnSkFLZXMxeDQxMk42STh2MXczdzJPUldDNEUya1RMUk5oNUhReWxp 52 | MjhNMnUzNDNtVzlUU2NrN3ZPTWkza2ZuN0VxbUQ3bWl2TElTaENhR3I3TFpJY2R3 53 | bVFoNWtYdDN4NXVzdVZYMGhnMEtIZHo1TWpMenR3bnhxK1M4M3h6eVlHQlFmbnVa 54 | Yk0vZ21OVlNiQVd3cm5pYmZuUi9aR2dZOGZFOG8vcFBiU295dWo0RE1GU0pQWmN6 55 | WHNSY004N21ZTWExd0RZdHl5ckYwSnFEQ3FZUmhQYmlSL25XSXhCckFZZW1wZXpu 56 | V2RucjBJNjBaYnZ5VDhIc3M0dVQwNU0xN1VwZVdNTkxReUFtZHVqVE1FQUF4NWVx 57 | N3llYmFGRWFvbVVFdWVFK3FmaWU0d3FjSEdJTlpVbmNFWlNqTXdBcXIwOFNGM0w4 58 | Z1M3ejk4bUtoYVIwM1p3QmVpLzJGa204SnZEbExRY1o4K2JPaEtwUGtXRTU1MjUr 59 | Y082ZjRkUXd4cXpsL0YwYzRDWDZvSmJaVWNoM3BXN1JicE1oaU1vOTFuSFlEMnp2 60 | SFhnclhWMHBIRFlsaHFZVkJlWVRaZkQ5dTE1djluMDRwWHREbXltZXdlS2JIeWtH 61 | Q0V1aG81WlI4T0JRRUl2czdaZGVtSFJrR2J1SENyMHpvVjFPam9MUk1xMmE2UzJI 62 | ZWt0ZlJ0MlkyRHlIRzUzNDQrTmZzcGJYYWkxdVBhd1pkL1Y2eXRFMUJtLzdMYklQ 63 | WVZRaHdINzY3aFFpUHY3bGZJaTQ4WGJoQ3RNakxDTmtaK1RHblBrQWw1Rmwxd0RC 64 | eUNFMk13dlBUUlN5aEJHZlMzREJiM3JaSEFTL1Z1VFRmRHRnZFJJY2lyUzJBeWI4 65 | REE0S1lNdDlVQW5qOUxSc0NRdUNUdVVvTXhPRXdIVThRZm5MM3YvY1MrZy9vUG5L 66 | U0crZ1hqVXozWWkyWFd6OHpWaHF1OHZJQWJoTVV2REtsWGtwTTJQVm9XTE5zQUtW 67 | SVpKTTEvNlMxeDhnNkp2Y3ZNK1poQjd2eGdQVE1iZFVOZ24wRFd0em1wUHZtVWc2 68 | NHREOW00WFdlczJFODZXQ1JBY0lOY2FFYk1QaVFNeUlaUUQ5U2V0dXVQVDdUV3lq 69 | SkRUQjNYWU0zc2lLN3ZiZ242N1cxRlZLamhZWGRCQm5tY2wyNnkzbWsyU0VPcmd4 70 | UVU5RDZCakRJOHhDQklTNWxnd212dXA0WWc3dCtSaUgwbG9XUE93RHEyRHBvSnJO 71 | MEN3K0loMk4rRVNTNmgyaEpOTjFxTmN5c0pPK0NqR3U4d3ZIeG90OVlRaWptVXNE 72 | YXJmQ3A0ZXRrZjF4Y0VkcVpnb1pqZ2M0K3k4M3Jhb1FEdz09In0= 73 | -----END ENCRYPTED SIGSTORE PRIVATE KEY----- 74 | -------------------------------------------------------------------------------- /src/rekor/apis/tlog_api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Rekor 3 | * 4 | * Rekor is a cryptographically secure, immutable transparency log for signed software releases. 5 | * 6 | * The version of the OpenAPI document: 0.0.1 7 | * 8 | * Generated by: https://openapi-generator.tech 9 | */ 10 | 11 | use super::{Error, configuration}; 12 | use crate::rekor::apis::ResponseContent; 13 | use serde::{Deserialize, Serialize}; 14 | 15 | /// struct for typed errors of method [`get_log_info`] 16 | #[derive(Debug, Clone, Serialize, Deserialize)] 17 | #[serde(untagged)] 18 | pub enum GetLogInfoError { 19 | DefaultResponse(crate::rekor::models::Error), 20 | UnknownValue(serde_json::Value), 21 | } 22 | 23 | /// struct for typed errors of method [`get_log_proof`] 24 | #[derive(Debug, Clone, Serialize, Deserialize)] 25 | #[serde(untagged)] 26 | pub enum GetLogProofError { 27 | Status400(crate::rekor::models::Error), 28 | DefaultResponse(crate::rekor::models::Error), 29 | UnknownValue(serde_json::Value), 30 | } 31 | 32 | /// Returns the current root hash and size of the merkle tree used to store the log entries. 33 | pub async fn get_log_info( 34 | configuration: &configuration::Configuration, 35 | ) -> Result> { 36 | let local_var_configuration = configuration; 37 | 38 | let local_var_client = &local_var_configuration.client; 39 | 40 | let local_var_uri_str = format!("{}/api/v1/log", local_var_configuration.base_path); 41 | let mut local_var_req_builder = 42 | local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); 43 | 44 | if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { 45 | local_var_req_builder = 46 | local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); 47 | } 48 | 49 | let local_var_req = local_var_req_builder.build()?; 50 | let local_var_resp = local_var_client.execute(local_var_req).await?; 51 | 52 | let local_var_status = local_var_resp.status(); 53 | let local_var_content = local_var_resp.text().await?; 54 | 55 | if !local_var_status.is_client_error() && !local_var_status.is_server_error() { 56 | serde_json::from_str(&local_var_content).map_err(Error::from) 57 | } else { 58 | let local_var_entity: Option = 59 | serde_json::from_str(&local_var_content).ok(); 60 | let local_var_error = ResponseContent { 61 | status: local_var_status, 62 | content: local_var_content, 63 | entity: local_var_entity, 64 | }; 65 | Err(Error::ResponseError(local_var_error)) 66 | } 67 | } 68 | 69 | /// Returns a list of hashes for specified tree sizes that can be used to confirm the consistency of the transparency log 70 | pub async fn get_log_proof( 71 | configuration: &configuration::Configuration, 72 | last_size: i32, 73 | first_size: Option<&str>, 74 | tree_id: Option<&str>, 75 | ) -> Result> { 76 | let local_var_configuration = configuration; 77 | 78 | let local_var_client = &local_var_configuration.client; 79 | 80 | let local_var_uri_str = format!("{}/api/v1/log/proof", local_var_configuration.base_path); 81 | let mut local_var_req_builder = 82 | local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); 83 | 84 | if let Some(ref local_var_str) = first_size { 85 | local_var_req_builder = 86 | local_var_req_builder.query(&[("firstSize", &local_var_str.to_string())]); 87 | } 88 | local_var_req_builder = local_var_req_builder.query(&[("lastSize", &last_size.to_string())]); 89 | if let Some(ref local_var_str) = tree_id { 90 | local_var_req_builder = 91 | local_var_req_builder.query(&[("treeID", &local_var_str.to_string())]); 92 | } 93 | if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { 94 | local_var_req_builder = 95 | local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); 96 | } 97 | 98 | let local_var_req = local_var_req_builder.build()?; 99 | let local_var_resp = local_var_client.execute(local_var_req).await?; 100 | 101 | let local_var_status = local_var_resp.status(); 102 | let local_var_content = local_var_resp.text().await?; 103 | 104 | if !local_var_status.is_client_error() && !local_var_status.is_server_error() { 105 | serde_json::from_str(&local_var_content).map_err(Error::from) 106 | } else { 107 | let local_var_entity: Option = 108 | serde_json::from_str(&local_var_content).ok(); 109 | let local_var_error = ResponseContent { 110 | status: local_var_status, 111 | content: local_var_content, 112 | entity: local_var_entity, 113 | }; 114 | Err(Error::ResponseError(local_var_error)) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/cosign/verification_constraint/cert_subject_url_verifier.rs: -------------------------------------------------------------------------------- 1 | use super::VerificationConstraint; 2 | use crate::cosign::signature_layers::{CertificateSubject, SignatureLayer}; 3 | use crate::errors::Result; 4 | 5 | /// Verification Constraint for signatures produced in keyless mode. 6 | /// 7 | /// Keyless signatures have a x509 certificate associated to them. This 8 | /// verifier ensures the SAN portion of the certificate has a URI 9 | /// attribute that matches the one provided by the user. 10 | /// 11 | /// The constraints needs also the `Issuer` to be provided, this is the name 12 | /// of the identity provider that was used by the user to authenticate. 13 | /// 14 | /// This verifier can be used to check keyless signatures produced in 15 | /// non-interactive mode inside of GitHub Actions. 16 | /// 17 | /// For example, `cosign` produces the following signature when the 18 | /// OIDC token is extracted from the GITHUB_TOKEN: 19 | /// 20 | /// ```hcl 21 | /// { 22 | /// "critical": { 23 | /// // not relevant 24 | /// }, 25 | /// "optional": { 26 | /// "Bundle": { 27 | /// // not relevant 28 | /// }, 29 | /// "Issuer": "https://token.actions.githubusercontent.com", 30 | /// "Subject": "https://github.com/flavio/policy-secure-pod-images/.github/workflows/release.yml@refs/heads/main" 31 | /// } 32 | /// } 33 | /// ``` 34 | /// 35 | /// The following constraint would be able to enforce this signature to be 36 | /// found: 37 | /// 38 | /// ```rust 39 | /// use sigstore::cosign::verification_constraint::CertSubjectUrlVerifier; 40 | /// 41 | /// let vc = CertSubjectUrlVerifier{ 42 | /// url: String::from("https://github.com/flavio/policy-secure-pod-images/.github/workflows/release.yml@refs/heads/main"), 43 | /// issuer: String::from("https://token.actions.githubusercontent.com"), 44 | /// }; 45 | /// ``` 46 | #[derive(Default, Debug)] 47 | pub struct CertSubjectUrlVerifier { 48 | pub url: String, 49 | pub issuer: String, 50 | } 51 | 52 | impl VerificationConstraint for CertSubjectUrlVerifier { 53 | fn verify(&self, signature_layer: &SignatureLayer) -> Result { 54 | let verified = match &signature_layer.certificate_signature { 55 | Some(signature) => { 56 | let url_matches = match &signature.subject { 57 | CertificateSubject::Uri(u) => u == &self.url, 58 | _ => false, 59 | }; 60 | let issuer_matches = Some(self.issuer.clone()) == signature.issuer; 61 | 62 | url_matches && issuer_matches 63 | } 64 | _ => false, 65 | }; 66 | Ok(verified) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use crate::cosign::{ 74 | signature_layers::tests::{ 75 | build_correct_signature_layer_with_certificate, 76 | build_correct_signature_layer_without_bundle, 77 | }, 78 | verification_constraint::{ 79 | CertSubjectEmailVerifier, cert_subject_email_verifier::StringVerifier, 80 | }, 81 | }; 82 | 83 | #[test] 84 | fn cert_subject_url_verifier() { 85 | let url = "https://sigstore.dev/test".to_string(); 86 | let issuer = "the issuer".to_string(); 87 | 88 | let mut sl = build_correct_signature_layer_with_certificate(); 89 | let mut cert_signature = sl.certificate_signature.unwrap(); 90 | let cert_subj = CertificateSubject::Uri(url.clone()); 91 | cert_signature.issuer = Some(issuer.clone()); 92 | cert_signature.subject = cert_subj; 93 | sl.certificate_signature = Some(cert_signature); 94 | 95 | let vc = CertSubjectUrlVerifier { 96 | url: url.clone(), 97 | issuer: issuer.clone(), 98 | }; 99 | assert!(vc.verify(&sl).unwrap()); 100 | 101 | let vc = CertSubjectUrlVerifier { 102 | url: "a different url".to_string(), 103 | issuer: issuer.clone(), 104 | }; 105 | assert!(!vc.verify(&sl).unwrap()); 106 | 107 | let vc = CertSubjectUrlVerifier { 108 | url, 109 | issuer: "a different issuer".to_string(), 110 | }; 111 | assert!(!vc.verify(&sl).unwrap()); 112 | 113 | // A Cert email verifier should also report a non match 114 | let vc = CertSubjectEmailVerifier { 115 | email: StringVerifier::ExactMatch("alice@example.com".to_string()), 116 | issuer: Some(StringVerifier::ExactMatch(issuer)), 117 | }; 118 | assert!(!vc.verify(&sl).unwrap()); 119 | } 120 | 121 | #[test] 122 | fn cert_subject_verifier_no_signature() { 123 | let (sl, _) = build_correct_signature_layer_without_bundle(); 124 | 125 | let vc = CertSubjectUrlVerifier { 126 | url: "https://sigstore.dev/test".to_string(), 127 | issuer: "an issuer".to_string(), 128 | }; 129 | assert!(!vc.verify(&sl).unwrap()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish 4 | to make via an [issue](https://github.com/sigstore/sigstore-rs/issues). 5 | 6 | ## Building and testing 7 | 8 | The Makefile contains useful targets for editing source code (`make lint`, `make fmt`), building executables 9 | (`make build`) and testing (`make test`). 10 | 11 | Full test suite requires Docker daemon to be running (and the user must have permissions to start a container). 12 | 13 | ## Pull Request Process 14 | 15 | 1. Create an [issue](https://github.com/sigstore/sigstore-rs/issues) 16 | outlining the fix or feature. 17 | 2. Fork the sigstore-rs repository to your own github account and clone it locally. 18 | 3. Hack on your changes. 19 | 4. Update the README.md with details of changes to any interface, this includes new environment 20 | variables, exposed ports, useful file locations, CLI parameters and 21 | new or changed configuration values. 22 | 5. Correctly format your commit message see [Commit Messages](#commit-message-guidelines) 23 | below. 24 | 6. Ensure that CI passes, if it fails, fix the failures. 25 | 7. Every pull request requires a review from the [core sigstore-rs team](https://github.com/orgs/github.com/sigstore/teams/sigstore-rs-codeowners) 26 | before merging. 27 | 8. If your pull request consists of more than one commit, please squash your 28 | commits as described in [Squash Commits](#squash-commits) 29 | 30 | ## Commit Message Guidelines 31 | 32 | 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/). 33 | 34 | Well formed commit messages not only help reviewers understand the nature of 35 | the Pull Request, but also assists the release process where commit messages 36 | are used to generate release notes. 37 | 38 | A good example of a commit message would be as follows: 39 | 40 | ``` 41 | Summarize changes in around 50 characters or less 42 | 43 | More detailed explanatory text, if necessary. Wrap it to about 72 44 | characters or so. In some contexts, the first line is treated as the 45 | subject of the commit and the rest of the text as the body. The 46 | blank line separating the summary from the body is critical (unless 47 | you omit the body entirely); various tools like `log`, `shortlog` 48 | and `rebase` can get confused if you run the two together. 49 | 50 | Explain the problem that this commit is solving. Focus on why you 51 | are making this change as opposed to how (the code explains that). 52 | Are there side effects or other unintuitive consequences of this 53 | change? Here's the place to explain them. 54 | 55 | Further paragraphs come after blank lines. 56 | 57 | - Bullet points are okay, too 58 | 59 | - Typically a hyphen or asterisk is used for the bullet, preceded 60 | by a single space, with blank lines in between, but conventions 61 | vary here 62 | 63 | If you use an issue tracker, put references to them at the bottom, 64 | like this: 65 | 66 | Resolves: #123 67 | See also: #456, #789 68 | ``` 69 | 70 | Note the `Resolves #123` tag, this references the issue raised and allows us to 71 | ensure issues are associated and closed when a pull request is merged. 72 | 73 | Please refer to [the github help page on message types](https://help.github.com/articles/closing-issues-using-keywords/) 74 | for a complete list of issue references. 75 | 76 | ## Squash Commits 77 | 78 | Should your pull request consist of more than one commit (perhaps due to 79 | a change being requested during the review cycle), please perform a git squash 80 | once a reviewer has approved your pull request. 81 | 82 | A squash can be performed as follows. Let's say you have the following commits: 83 | 84 | initial commit 85 | second commit 86 | final commit 87 | 88 | Run the command below with the number set to the total commits you wish to 89 | squash (in our case 3 commits): 90 | 91 | git rebase -i HEAD~3 92 | 93 | You default text editor will then open up and you will see the following:: 94 | 95 | pick eb36612 initial commit 96 | pick 9ac8968 second commit 97 | pick a760569 final commit 98 | 99 | # Rebase eb1429f..a760569 onto eb1429f (3 commands) 100 | 101 | We want to rebase on top of our first commit, so we change the other two commits 102 | to `squash`: 103 | 104 | pick eb36612 initial commit 105 | squash 9ac8968 second commit 106 | squash a760569 final commit 107 | 108 | After this, should you wish to update your commit message to better summarise 109 | all of your pull request, run: 110 | 111 | git commit --amend 112 | 113 | You will then need to force push (assuming your initial commit(s) were posted 114 | to github): 115 | 116 | git push origin your-branch --force 117 | 118 | Alternatively, a core member can squash your commits within Github. 119 | ## Code of Conduct 120 | 121 | sigstore-rs adheres to and enforces the [Contributor Covenant](http://contributor-covenant.org/version/1/4/) Code of Conduct. 122 | Please take a moment to read the [CODE_OF_CONDUCT.md](https://github.com/sigstore/sigstore-rs/blob/master/CODE_OF_CONDUCT.md) document. 123 | -------------------------------------------------------------------------------- /tests/conformance/conformance.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // CLI implemented to specification: 17 | // https://github.com/sigstore/sigstore-conformance/blob/main/docs/cli_protocol.md 18 | 19 | use std::{fs, process::exit}; 20 | 21 | use clap::{Parser, Subcommand}; 22 | use sigstore::{ 23 | bundle::sign::SigningContext, 24 | bundle::verify::{blocking::Verifier, policy}, 25 | oauth::IdentityToken, 26 | }; 27 | 28 | #[derive(Parser, Debug)] 29 | struct Cli { 30 | #[command(subcommand)] 31 | command: Commands, 32 | } 33 | 34 | #[derive(Subcommand, Debug)] 35 | enum Commands { 36 | Sign(Sign), 37 | SignBundle(SignBundle), 38 | Verify(Verify), 39 | VerifyBundle(VerifyBundle), 40 | } 41 | 42 | #[derive(Parser, Debug)] 43 | struct Sign { 44 | // The OIDC identity token to use 45 | #[clap(long)] 46 | identity_token: String, 47 | 48 | // The path to write the signature to 49 | #[clap(long)] 50 | signature: String, 51 | 52 | // The path to write the signing certificate to 53 | #[clap(long)] 54 | certificate: String, 55 | 56 | // The artifact to sign 57 | artifact: String, 58 | } 59 | 60 | #[derive(Parser, Debug)] 61 | struct SignBundle { 62 | // The OIDC identity token to use 63 | #[clap(long)] 64 | identity_token: String, 65 | 66 | // The path to write the bundle to 67 | #[clap(long)] 68 | bundle: String, 69 | 70 | // The artifact to sign 71 | artifact: String, 72 | } 73 | 74 | #[derive(Parser, Debug)] 75 | struct Verify { 76 | // The path to the signature to verify 77 | #[clap(long)] 78 | signature: String, 79 | 80 | // The path to the signing certificate to verify 81 | #[clap(long)] 82 | certificate: String, 83 | 84 | // The expected identity in the signing certificate's SAN extension 85 | #[clap(long)] 86 | certificate_identity: String, 87 | 88 | // The expected OIDC issuer for the signing certificate 89 | #[clap(long)] 90 | certificate_oidc_issuer: String, 91 | 92 | // The path to the artifact to verify 93 | artifact: String, 94 | } 95 | 96 | #[derive(Parser, Debug)] 97 | struct VerifyBundle { 98 | // The path to the Sigstore bundle to verify 99 | #[clap(long)] 100 | bundle: String, 101 | 102 | // The expected identity in the signing certificate's SAN extension 103 | #[clap(long)] 104 | certificate_identity: String, 105 | 106 | // The expected OIDC issuer for the signing certificate 107 | #[clap(long)] 108 | certificate_oidc_issuer: String, 109 | 110 | // The path to the artifact to verify 111 | artifact: String, 112 | } 113 | 114 | fn main() { 115 | tracing_subscriber::fmt::init(); 116 | let cli = Cli::parse(); 117 | 118 | let result = match cli.command { 119 | Commands::SignBundle(args) => sign_bundle(args), 120 | Commands::VerifyBundle(args) => verify_bundle(args), 121 | _ => unimplemented!("sig/cert commands"), 122 | }; 123 | 124 | if let Err(error) = result { 125 | eprintln!("Operation failed:\n{error:?}"); 126 | exit(-1); 127 | } 128 | 129 | eprintln!("Operation succeeded!"); 130 | } 131 | 132 | fn sign_bundle(args: SignBundle) -> anyhow::Result<()> { 133 | let SignBundle { 134 | identity_token, 135 | bundle, 136 | artifact, 137 | } = args; 138 | let identity_token = IdentityToken::try_from(identity_token.as_str())?; 139 | let bundle = fs::File::create(bundle)?; 140 | let mut artifact = fs::File::open(artifact)?; 141 | 142 | let context = SigningContext::production()?; 143 | let signer = context.blocking_signer(identity_token); 144 | 145 | let signing_artifact = signer?.sign(&mut artifact)?; 146 | let bundle_data = signing_artifact.to_bundle(); 147 | 148 | serde_json::to_writer(bundle, &bundle_data)?; 149 | 150 | Ok(()) 151 | } 152 | 153 | fn verify_bundle(args: VerifyBundle) -> anyhow::Result<()> { 154 | let VerifyBundle { 155 | bundle, 156 | certificate_identity, 157 | certificate_oidc_issuer, 158 | artifact, 159 | } = args; 160 | let bundle = fs::File::open(bundle)?; 161 | let mut artifact = fs::File::open(artifact)?; 162 | 163 | let bundle: sigstore::bundle::Bundle = serde_json::from_reader(bundle)?; 164 | let verifier = Verifier::production()?; 165 | 166 | verifier.verify( 167 | &mut artifact, 168 | bundle, 169 | &policy::Identity::new(certificate_identity, certificate_oidc_issuer), 170 | true, 171 | )?; 172 | 173 | Ok(()) 174 | } 175 | -------------------------------------------------------------------------------- /src/mock_client.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 The Sigstore Authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #[cfg(test)] 17 | pub(crate) mod test { 18 | use crate::errors::{Result, SigstoreError}; 19 | 20 | use async_trait::async_trait; 21 | use oci_client::{ 22 | Reference, 23 | client::{ImageData, PushResponse}, 24 | manifest::OciManifest, 25 | secrets::RegistryAuth, 26 | }; 27 | 28 | #[derive(Default)] 29 | pub struct MockOciClient { 30 | pub fetch_manifest_digest_response: Option>, 31 | pub pull_response: Option>, 32 | pub pull_manifest_response: Option>, 33 | pub push_response: Option>, 34 | } 35 | 36 | impl crate::registry::ClientCapabilitiesDeps for MockOciClient {} 37 | 38 | #[cfg_attr(not(target_arch = "wasm32"), async_trait)] 39 | #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] 40 | impl crate::registry::ClientCapabilities for MockOciClient { 41 | async fn fetch_manifest_digest( 42 | &mut self, 43 | image: &Reference, 44 | _auth: &RegistryAuth, 45 | ) -> Result { 46 | let mock_response = self 47 | .fetch_manifest_digest_response 48 | .as_ref() 49 | .ok_or_else(|| SigstoreError::RegistryFetchManifestError { 50 | image: image.whole(), 51 | error: String::from("No fetch_manifest_digest_response provided!"), 52 | })?; 53 | 54 | match mock_response { 55 | Ok(r) => Ok(r.clone()), 56 | Err(e) => Err(SigstoreError::RegistryFetchManifestError { 57 | image: image.whole(), 58 | error: e.to_string(), 59 | }), 60 | } 61 | } 62 | 63 | async fn pull( 64 | &mut self, 65 | image: &Reference, 66 | _auth: &RegistryAuth, 67 | _accepted_media_types: Vec<&str>, 68 | ) -> Result { 69 | let mock_response = 70 | self.pull_response 71 | .as_ref() 72 | .ok_or_else(|| SigstoreError::RegistryPullError { 73 | image: image.whole(), 74 | error: String::from("No pull_response provided!"), 75 | })?; 76 | 77 | match mock_response { 78 | Ok(r) => Ok(r.clone()), 79 | Err(e) => Err(SigstoreError::RegistryPullError { 80 | image: image.whole(), 81 | error: e.to_string(), 82 | }), 83 | } 84 | } 85 | 86 | async fn pull_manifest( 87 | &mut self, 88 | image: &Reference, 89 | _auth: &RegistryAuth, 90 | ) -> Result<(OciManifest, String)> { 91 | let mock_response = self.pull_manifest_response.as_ref().ok_or_else(|| { 92 | SigstoreError::RegistryPullError { 93 | image: image.whole(), 94 | error: String::from("No pull_manifest_response provided!"), 95 | } 96 | })?; 97 | 98 | match mock_response { 99 | Ok(r) => Ok(r.clone()), 100 | Err(e) => Err(SigstoreError::RegistryPullError { 101 | image: image.whole(), 102 | error: e.to_string(), 103 | }), 104 | } 105 | } 106 | 107 | async fn push( 108 | &mut self, 109 | image_ref: &oci_client::Reference, 110 | _layers: &[oci_client::client::ImageLayer], 111 | _config: oci_client::client::Config, 112 | _auth: &oci_client::secrets::RegistryAuth, 113 | _manifest: Option, 114 | ) -> Result { 115 | let mock_response = 116 | self.push_response 117 | .as_ref() 118 | .ok_or_else(|| SigstoreError::RegistryPushError { 119 | image: image_ref.whole(), 120 | error: String::from("No push_response provided!"), 121 | })?; 122 | 123 | match mock_response { 124 | Ok(r) => Ok(PushResponse { 125 | config_url: r.config_url.clone(), 126 | manifest_url: r.manifest_url.clone(), 127 | }), 128 | Err(e) => Err(SigstoreError::RegistryPushError { 129 | image: image_ref.whole(), 130 | error: e.to_string(), 131 | }), 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /examples/bundle/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | use tracing::debug; 5 | use tracing_subscriber::prelude::*; 6 | use tracing_subscriber::{EnvFilter, fmt}; 7 | 8 | use sigstore::bundle::sign::SigningContext; 9 | use sigstore::bundle::verify::{blocking::Verifier, policy}; 10 | use sigstore::oauth; 11 | 12 | #[derive(Parser, Debug)] 13 | #[clap(about = "Signing and verification example for sigstore::bundle module")] 14 | struct Cli { 15 | /// Enable verbose mode 16 | #[arg(short, long)] 17 | verbose: bool, 18 | 19 | #[command(subcommand)] 20 | command: Commands, 21 | } 22 | 23 | #[derive(Subcommand, Debug)] 24 | enum Commands { 25 | /// Verify a signature for an artifact 26 | Verify(VerifyArgs), 27 | /// Create a signature for an artifact 28 | Sign(SignArgs), 29 | } 30 | 31 | #[derive(Parser, Debug)] 32 | struct SignArgs { 33 | /// Path to the artifact to sign 34 | artifact: PathBuf, 35 | } 36 | 37 | #[derive(Parser, Debug)] 38 | struct VerifyArgs { 39 | /// Path to the artifact to verify 40 | artifact: PathBuf, 41 | 42 | /// expected signing identity (email) 43 | #[arg(long, value_name = "EMAIL")] 44 | identity: String, 45 | 46 | /// expected signing identity issuer (URI) 47 | #[arg(long, value_name = "URI")] 48 | issuer: String, 49 | } 50 | 51 | pub fn main() { 52 | let cli = Cli::parse(); 53 | 54 | // setup logging 55 | let level_filter = if cli.verbose { "debug" } else { "info" }; 56 | let filter_layer = EnvFilter::new(level_filter); 57 | tracing_subscriber::registry() 58 | .with(filter_layer) 59 | .with(fmt::layer().with_writer(std::io::stderr)) 60 | .init(); 61 | 62 | match cli.command { 63 | Commands::Sign(args) => sign(&args.artifact), 64 | Commands::Verify(args) => verify(&args.artifact, &args.identity, &args.issuer), 65 | } 66 | } 67 | 68 | fn sign(artifact_path: &PathBuf) { 69 | let filename = artifact_path 70 | .file_name() 71 | .and_then(|s| s.to_str()) 72 | .expect("Failed to parse artifact filename"); 73 | let mut artifact = fs::File::open(artifact_path) 74 | .unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); 75 | 76 | let mut bundle_path = artifact_path.clone(); 77 | bundle_path.set_file_name(format!("{}.sigstore.json", filename)); 78 | let bundle = fs::File::create_new(&bundle_path).unwrap_or_else(|e| { 79 | println!( 80 | "Failed to create signature bundle {}: {}", 81 | bundle_path.display(), 82 | e 83 | ); 84 | std::process::exit(1); 85 | }); 86 | 87 | let token = authorize(); 88 | let email = &token.unverified_claims().email.clone(); 89 | debug!("Signing with {}", email); 90 | 91 | let signing_artifact = SigningContext::production().and_then(|ctx| { 92 | ctx.blocking_signer(token) 93 | .and_then(|session| session.sign(&mut artifact)) 94 | }); 95 | 96 | match signing_artifact { 97 | Ok(signing_artifact) => { 98 | serde_json::to_writer(bundle, &signing_artifact.to_bundle()) 99 | .expect("Failed to write bundle to file"); 100 | } 101 | Err(e) => { 102 | panic!("Failed to sign: {}", e); 103 | } 104 | } 105 | println!( 106 | "Created signature bundle {} with identity {}", 107 | bundle_path.display(), 108 | email 109 | ); 110 | } 111 | 112 | fn verify(artifact_path: &PathBuf, identity: &str, issuer: &str) { 113 | let filename = artifact_path 114 | .file_name() 115 | .and_then(|s| s.to_str()) 116 | .expect("Failed to parse artifact filename"); 117 | let mut bundle_path = artifact_path.clone(); 118 | bundle_path.set_file_name(format!("{}.sigstore.json", filename)); 119 | 120 | let bundle = fs::File::open(&bundle_path) 121 | .unwrap_or_else(|_| panic!("Failed to open signature bundle {}", &bundle_path.display())); 122 | let mut artifact = fs::File::open(artifact_path) 123 | .unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); 124 | 125 | let bundle: sigstore::bundle::Bundle = 126 | serde_json::from_reader(bundle).expect("Failed to parse the bundle"); 127 | let verifier = Verifier::production().expect("Failed to create a verifier"); 128 | 129 | debug!("Verifying with {} (issuer {})", identity, issuer); 130 | let id_policy = policy::Identity::new(identity, issuer); 131 | 132 | if let Err(e) = verifier.verify(&mut artifact, bundle, &id_policy, true) { 133 | println!("Failed to verify: {}", e); 134 | std::process::exit(1); 135 | } 136 | println!("Verified") 137 | } 138 | 139 | fn authorize() -> oauth::IdentityToken { 140 | let oidc_url = oauth::openidflow::OpenIDAuthorize::new( 141 | "sigstore", 142 | "", 143 | "https://oauth2.sigstore.dev/auth", 144 | "http://localhost:8080", 145 | ) 146 | .auth_url() 147 | .expect("Failed to start OIDC authorization"); 148 | 149 | webbrowser::open(oidc_url.0.as_ref()).expect("Failed to open browser"); 150 | 151 | println!("Please authorize signing in web browser."); 152 | 153 | let listener = oauth::openidflow::RedirectListener::new( 154 | "127.0.0.1:8080", 155 | oidc_url.1, // client 156 | oidc_url.2, // nonce 157 | oidc_url.3, // pkce_verifier 158 | ); 159 | let (_, token) = listener 160 | .redirect_listener() 161 | .expect("Failed to receive a token"); 162 | oauth::IdentityToken::from(token) 163 | } 164 | --------------------------------------------------------------------------------