Skip to content

Commit

Permalink
Examples: Add a minimal example of bundle sign/verify
Browse files Browse the repository at this point in the history
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
jku committed Oct 17, 2024
1 parent 0c61dd5 commit 56f2a67
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
29 changes: 29 additions & 0 deletions examples/bundle/README.md
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.
144 changes: 144 additions & 0 deletions examples/bundle/main.rs
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)
}

0 comments on commit 56f2a67

Please sign in to comment.