-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Examples: Add a minimal example of bundle sign/verify
This uses * sigstore::bundle to sign and verify * sigstore::oauth to get a signing token Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
- Loading branch information
Showing
2 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
This interactive example shows how to sign and verify Sigstore signature bundles. The bundle | ||
format used in the example is supported by most Sigstore clients but notably cosign requires | ||
`--new-bundle-format` to do so. | ||
|
||
This example uses `sigstore::bundle` for signing and verification; and `sigstore::oauth` for | ||
OIDC authorization. In addition to the bundle format a notable difference compared to the "cosign" | ||
examples is that `sigstore::bundle` also handles the Sigstore trust root update before signing | ||
or verifying. | ||
|
||
### Sign README.md | ||
|
||
```console | ||
cargo run --example bundle \ | ||
sign README.md | ||
``` | ||
|
||
A browser window will be opened to authorize signing with an OIDC identity. | ||
After the authorization the signature bundle is created in `README.md.sigstore.json`. | ||
|
||
### Verify README.md using the signature bundle | ||
|
||
```console | ||
cargo run --example bundle \ | ||
verify README.md <IDENTITY> <ISSUER> | ||
```console | ||
|
||
`IDENTITY` is the email address of the OIDC account and <ISSUER> is the OIDC issuer URI that were used | ||
during signing. As an example `cargo run --example bundle verify README.md name@example.com https://github.com/login/oauth` verifies | ||
that the bundle `README.md.sigstore.json` was signed by "name@example.com" as authenticated by GitHub. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
use clap::{Parser, Subcommand}; | ||
use std::fs; | ||
use std::path::PathBuf; | ||
use tracing::debug; | ||
use tracing_subscriber::prelude::*; | ||
use tracing_subscriber::{fmt, EnvFilter}; | ||
|
||
use sigstore::bundle::sign::SigningContext; | ||
use sigstore::bundle::verify::{blocking::Verifier, policy}; | ||
use sigstore::oauth; | ||
|
||
#[derive(Parser, Debug)] | ||
#[clap(author, version, about, long_about = "Signing and verification example for sigstore::bundle module")] | ||
struct Cli { | ||
/// Enable verbose mode | ||
#[arg(short, long)] | ||
verbose: bool, | ||
|
||
#[command(subcommand)] | ||
command: Commands, | ||
} | ||
|
||
#[derive(Subcommand, Debug)] | ||
enum Commands { | ||
/// Verify a signature for an artifact | ||
Verify(VerifyArgs), | ||
/// Create a signature for an artifact | ||
Sign(SignArgs), | ||
} | ||
|
||
#[derive(Parser, Debug)] | ||
struct SignArgs { | ||
/// Path to the artifact to sign | ||
artifact: PathBuf, | ||
} | ||
|
||
#[derive(Parser, Debug)] | ||
struct VerifyArgs { | ||
/// Path to the artifact to verify | ||
artifact: PathBuf, | ||
|
||
/// expected signing identity (email) | ||
#[arg(long, value_name="EMAIL")] | ||
identity: String, | ||
|
||
/// expected signing identity issuer (URI) | ||
#[arg(long, value_name="URI")] | ||
issuer: String, | ||
} | ||
|
||
pub fn main() { | ||
let cli = Cli::parse(); | ||
|
||
// setup logging | ||
let level_filter = if cli.verbose { "debug" } else { "info" }; | ||
let filter_layer = EnvFilter::new(level_filter); | ||
tracing_subscriber::registry() | ||
.with(filter_layer) | ||
.with(fmt::layer().with_writer(std::io::stderr)) | ||
.init(); | ||
|
||
match cli.command { | ||
Commands::Sign(args) => sign(&args.artifact), | ||
Commands::Verify(args) => verify(&args.artifact, &args.identity, &args.issuer), | ||
} | ||
} | ||
|
||
fn sign(artifact_path: &PathBuf) { | ||
let mut artifact = fs::File::open(artifact_path) | ||
.unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); | ||
|
||
let bundle_path = format!("{}.sigstore.json", artifact_path.display()); | ||
let bundle = fs::File::create_new(&bundle_path).unwrap_or_else(|e| { | ||
println!("Failed to create signature bundle {}: {}", bundle_path, e); | ||
std::process::exit(1); | ||
}); | ||
let token = authorize(); | ||
let email = &token.unverified_claims().email.clone(); | ||
debug!("Signing with {}", email); | ||
|
||
let signing_artifact = SigningContext::production().and_then(|ctx| { | ||
ctx.blocking_signer(token) | ||
.and_then(|session| session.sign(&mut artifact)) | ||
}); | ||
|
||
match signing_artifact { | ||
Ok(signing_artifact) => { | ||
serde_json::to_writer(bundle, &signing_artifact.to_bundle()) | ||
.expect("Failed to write bundle to file"); | ||
} | ||
Err(e) => { | ||
panic!("Failed to sign: {}", e); | ||
} | ||
} | ||
println!( | ||
"Created signature bundle {} with identity {}", | ||
&bundle_path, email | ||
); | ||
} | ||
|
||
fn verify(artifact_path: &PathBuf, identity: &str, issuer: &str) { | ||
let bundle_path = format!("{}.sigstore.json", artifact_path.display()); | ||
let bundle = fs::File::open(&bundle_path) | ||
.unwrap_or_else(|_| panic!("Failed to open signature bundle {}", &bundle_path)); | ||
let mut artifact = fs::File::open(artifact_path) | ||
.unwrap_or_else(|_| panic!("Failed to read artifact {}", artifact_path.display())); | ||
|
||
let bundle: sigstore::bundle::Bundle = | ||
serde_json::from_reader(bundle).expect("Failed to parse the bundle"); | ||
let verifier = Verifier::production().expect("Failed to create a verifier"); | ||
|
||
debug!("Verifying with {} (issuer {})", identity, issuer); | ||
let id_policy = policy::Identity::new(identity, issuer); | ||
|
||
if let Err(e) = verifier.verify(&mut artifact, bundle, &id_policy, true) { | ||
println!("Failed to verify: {}", e); | ||
std::process::exit(1); | ||
} | ||
println!("Verified") | ||
} | ||
|
||
fn authorize() -> oauth::IdentityToken { | ||
let oidc_url = oauth::openidflow::OpenIDAuthorize::new( | ||
"sigstore", | ||
"", | ||
"https://oauth2.sigstore.dev/auth", | ||
"http://localhost:8080", | ||
) | ||
.auth_url() | ||
.expect("Failed to start OIDC authorization"); | ||
|
||
webbrowser::open(oidc_url.0.as_ref()).expect("Failed to open browser"); | ||
|
||
let listener = oauth::openidflow::RedirectListener::new( | ||
"127.0.0.1:8080", | ||
oidc_url.1, // client | ||
oidc_url.2, // nonce | ||
oidc_url.3, // pkce_verifier | ||
); | ||
let (_, token) = listener | ||
.redirect_listener() | ||
.expect("Failed to receive a token"); | ||
oauth::IdentityToken::from(token) | ||
} |