From ae23ec282fc6e4d60a8cdd107499e4e8c6c75387 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 21 Oct 2024 15:28:57 +0300 Subject: [PATCH 1/4] oauth: Add simple test for an interactive token Signed-off-by: Jussi Kukkonen --- src/oauth/token.rs | 16 ++++++++++++++++ tests/data/tokens/interactive-token.txt | 1 + 2 files changed, 17 insertions(+) create mode 100644 tests/data/tokens/interactive-token.txt diff --git a/src/oauth/token.rs b/src/oauth/token.rs index e916a30003..17dc40545d 100644 --- a/src/oauth/token.rs +++ b/src/oauth/token.rs @@ -104,3 +104,19 @@ impl std::fmt::Display for IdentityToken { write!(f, "{}", self.original_token.clone()) } } + +#[cfg(test)] +mod tests { + use std::fs; + use super::*; + + #[test] + fn interactive_token() { + let content = fs::read_to_string("tests/data/tokens/interactive-token.txt").unwrap(); + let identity_token = IdentityToken::try_from(content.as_str()).unwrap(); + assert_eq!(identity_token.claims.email, "jku@goto.fi"); + assert_eq!(identity_token.claims.aud, "sigstore"); + assert_eq!(identity_token.claims.exp, DateTime::parse_from_rfc3339("2024-10-21T12:15:30Z").unwrap()); + } + +} \ No newline at end of file diff --git a/tests/data/tokens/interactive-token.txt b/tests/data/tokens/interactive-token.txt new file mode 100644 index 0000000000..5c8a9340f3 --- /dev/null +++ b/tests/data/tokens/interactive-token.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6IjMxNjA2OGMzM2ZhMjg2OTZhZmI5YzM5YWI2OTMxMjY1ZDk0Y2I3NTUifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNnVXpNVGc0T1JJbWFIUjBjSE02SlRKR0pUSkdaMmwwYUhWaUxtTnZiU1V5Um14dloybHVKVEpHYjJGMWRHZyIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNzI5NTEyOTMwLCJpYXQiOjE3Mjk1MTI4NzAsIm5vbmNlIjoiNTI3NjM3Y2UtN2Q2MS00MDA5LThkM2EtNGNjZGM3OGJiZDg1IiwiYXRfaGFzaCI6IktmMUNPTXB5TVJDTkdzWWp1QXczclEiLCJlbWFpbCI6ImprdUBnb3RvLmZpIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZlZGVyYXRlZF9jbGFpbXMiOnsiY29ubmVjdG9yX2lkIjoiaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoIiwidXNlcl9pZCI6IjMxODg5In19.s27uZ3vpIzRS4eWdC3pM0FSsYkHNvScQoii_TcSRVZhtrcPAbA4D95Pw_R_UB-qRquMK1BHepKmeN1b1-CQ00jiFZgUOf9sDLC3Hy3oQejGJsYKb-7oeHs7amLz3SBzPwDwVd09e-7Yu1x9YV5k6aezqruLLt42C_kyOTsHeCIWWMEVmGp32105Jkj8YT5uEYXS-aOEvQFvAYsDfKgGuiJtGybUycVcJEfqyWI3cami7fkjU5PcCx8oFyP2E7YNRw4UeNWCTn7WFtL2onrgDm0oa2AqF3gtH4Q-9ByksVq3y6xQdoLj1ydzWcoCzsF43oZ6O6DkLmWk5fu3FxNyewg From 912186fdfa056d9641128f525683056740f3d9e1 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 21 Oct 2024 16:47:51 +0300 Subject: [PATCH 2/4] oauth: Fix the token identity handling * Make email optional when parsing JWT (e.g. GitHub actions does not use it) * Add IdentityToken.identity field: this is the identity claim that we believe Fulcio uses for this issuer * Fix the bundle signing so it uses the new identity field * Add test with a GitHub Actions token Note that signing with a Sub claim is still not supported but we're now a bit closer. Signed-off-by: Jussi Kukkonen --- examples/bundle/main.rs | 6 +- src/bundle/sign.rs | 13 +++- src/oauth/mod.rs | 1 + src/oauth/token.rs | 107 ++++++++++++++++++++++++++++---- tests/data/tokens/gha-token.txt | 1 + 5 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 tests/data/tokens/gha-token.txt diff --git a/examples/bundle/main.rs b/examples/bundle/main.rs index 6506d51637..44e6751c46 100644 --- a/examples/bundle/main.rs +++ b/examples/bundle/main.rs @@ -85,8 +85,8 @@ fn sign(artifact_path: &PathBuf) { }); let token = authorize(); - let email = &token.unverified_claims().email.clone(); - debug!("Signing with {}", email); + let identity = token.identity.to_string(); + debug!("Signing with {}", identity); let signing_artifact = SigningContext::production().and_then(|ctx| { ctx.blocking_signer(token) @@ -105,7 +105,7 @@ fn sign(artifact_path: &PathBuf) { println!( "Created signature bundle {} with identity {}", bundle_path.display(), - email + identity ); } diff --git a/src/bundle/sign.rs b/src/bundle/sign.rs index 0c6e4c82af..d80d5e3467 100644 --- a/src/bundle/sign.rs +++ b/src/bundle/sign.rs @@ -44,7 +44,7 @@ use crate::crypto::transparency::{verify_sct, CertificateEmbeddedSCT}; use crate::errors::{Result as SigstoreResult, SigstoreError}; use crate::fulcio::oauth::OauthTokenProvider; use crate::fulcio::{self, FulcioClient, FULCIO_ROOT}; -use crate::oauth::IdentityToken; +use crate::oauth::{Identity, IdentityToken}; use crate::rekor::apis::configuration::Configuration as RekorConfiguration; use crate::rekor::apis::entries_api::create_log_entry; use crate::rekor::models::{hashedrekord, proposed_entry::ProposedEntry as ProposedLogEntry}; @@ -85,6 +85,15 @@ impl<'ctx> SigningSession<'ctx> { fulcio: &FulcioClient, token: &IdentityToken, ) -> SigstoreResult<(ecdsa::SigningKey, fulcio::CertificateResponse)> { + let identity = match &token.identity { + Identity::Sub(_) => { + return Err(SigstoreError::IdentityTokenError( + "Non-email identities are not yet supported".to_string(), + )) + } + Identity::Email(identity) => identity.as_str(), + }; + let subject = // SEQUENCE OF RelativeDistinguishedName vec![ @@ -95,7 +104,7 @@ impl<'ctx> SigningSession<'ctx> { oid: const_oid::db::rfc3280::EMAIL_ADDRESS, value: AttributeValue::new( pkcs8::der::Tag::Utf8String, - token.unverified_claims().email.as_ref(), + identity.as_ref(), )?, } ].try_into()? diff --git a/src/oauth/mod.rs b/src/oauth/mod.rs index b78bd78d6c..3959059918 100644 --- a/src/oauth/mod.rs +++ b/src/oauth/mod.rs @@ -16,4 +16,5 @@ pub mod openidflow; pub mod token; +pub use token::Identity; pub use token::IdentityToken; diff --git a/src/oauth/token.rs b/src/oauth/token.rs index 17dc40545d..dc4d1e777d 100644 --- a/src/oauth/token.rs +++ b/src/oauth/token.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt; + use chrono::{DateTime, Utc}; use openidconnect::core::CoreIdToken; use serde::Deserialize; @@ -28,15 +30,35 @@ pub struct Claims { #[serde(with = "chrono::serde::ts_seconds_option")] #[serde(default)] pub nbf: Option>, - pub email: String, + pub email: Option, + pub iss: String, + pub sub: Option, } pub type UnverifiedClaims = Claims; +// identity is the claim that we believe Fulcio uses: Depending on the issuer it is +// either a "sub" or "email" claim. +#[derive(Debug, PartialEq)] +pub enum Identity { + Sub(String), + Email(String), +} + +impl fmt::Display for Identity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Identity::Sub(sub) => sub.fmt(f), + Identity::Email(email) => email.fmt(f), + } + } +} + /// A Sigstore token. pub struct IdentityToken { original_token: String, claims: UnverifiedClaims, + pub identity: Identity, } impl IdentityToken { @@ -82,9 +104,35 @@ impl TryFrom<&str> for IdentityToken { )); } + // Find the identity claim that we believe Fulcio used for this token. + // This means a few special cases and fall back on "sub" claim + let identity = match claims.iss.as_str() { + "https://accounts.google.com" + | "https://oauth2.sigstore.dev/auth" + | "https://oauth2.sigstage.dev/auth" => { + if let Some(email) = claims.email.as_ref() { + Identity::Email(email.clone()) + } else { + return Err(SigstoreError::IdentityTokenError( + "Email claim not found in JWT".into(), + )); + } + } + _ => { + if let Some(sub) = claims.sub.as_ref() { + Identity::Sub(sub.clone()) + } else { + return Err(SigstoreError::IdentityTokenError( + "Sub claim not found in JWT".into(), + )); + } + } + }; + Ok(IdentityToken { original_token: value.to_owned(), claims, + identity, }) } } @@ -107,16 +155,53 @@ impl std::fmt::Display for IdentityToken { #[cfg(test)] mod tests { - use std::fs; use super::*; + use std::fs; - #[test] - fn interactive_token() { - let content = fs::read_to_string("tests/data/tokens/interactive-token.txt").unwrap(); - let identity_token = IdentityToken::try_from(content.as_str()).unwrap(); - assert_eq!(identity_token.claims.email, "jku@goto.fi"); - assert_eq!(identity_token.claims.aud, "sigstore"); - assert_eq!(identity_token.claims.exp, DateTime::parse_from_rfc3339("2024-10-21T12:15:30Z").unwrap()); - } + #[test] + fn interactive_token() { + let content = fs::read_to_string("tests/data/tokens/interactive-token.txt").unwrap(); + let identity_token = IdentityToken::try_from(content.as_str()).unwrap(); + assert_eq!( + identity_token.claims.email, + Some(String::from("jku@goto.fi")) + ); + assert_eq!( + identity_token.identity, + Identity::Email(String::from("jku@goto.fi")) + ); + assert_eq!(identity_token.claims.aud, "sigstore"); + assert_eq!( + identity_token.claims.iss, + "https://oauth2.sigstore.dev/auth" + ); + assert_eq!( + identity_token.claims.exp, + DateTime::parse_from_rfc3339("2024-10-21T12:15:30Z").unwrap() + ); + } -} \ No newline at end of file + #[test] + fn github_actions_token() { + let content = fs::read_to_string("tests/data/tokens/gha-token.txt").unwrap(); + let identity_token = IdentityToken::try_from(content.as_str()).unwrap(); + assert_eq!(identity_token.claims.email, None); + assert_eq!( + identity_token.claims.sub, + Some(String::from("repo:sigstore-conformance/extremely-dangerous-public-oidc-beacon:ref:refs/heads/main")) + ); + assert_eq!( + identity_token.identity, + Identity::Sub(String::from("repo:sigstore-conformance/extremely-dangerous-public-oidc-beacon:ref:refs/heads/main")) + ); + assert_eq!(identity_token.claims.aud, "sigstore"); + assert_eq!( + identity_token.claims.iss, + "https://token.actions.githubusercontent.com" + ); + assert_eq!( + identity_token.claims.exp, + DateTime::parse_from_rfc3339("2024-10-21T07:29:49Z").unwrap() + ); + } +} diff --git a/tests/data/tokens/gha-token.txt b/tests/data/tokens/gha-token.txt new file mode 100644 index 0000000000..cc7dd10fea --- /dev/null +++ b/tests/data/tokens/gha-token.txt @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ikh5cTROQVRBanNucUM3bWRydEFoaHJDUjJfUSIsImtpZCI6IjFGMkFCODM0MDRDMDhFQzlFQTBCQjk5REFFRDAyMTg2QjA5MURCRjQifQ.eyJqdGkiOiI4NTI3ZGQyMy0zYmNjLTQzYjgtYTMyNy1kYjFkM2Q4ZTY4NGUiLCJzdWIiOiJyZXBvOnNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uOnJlZjpyZWZzL2hlYWRzL21haW4iLCJhdWQiOiJzaWdzdG9yZSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJyZXBvc2l0b3J5Ijoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24iLCJyZXBvc2l0b3J5X293bmVyIjoic2lnc3RvcmUtY29uZm9ybWFuY2UiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTMxODA0NTYzIiwicnVuX2lkIjoiMTE0MzUyMjEzNDQiLCJydW5fbnVtYmVyIjoiMTY5NDA3IiwicnVuX2F0dGVtcHQiOiIxIiwicmVwb3NpdG9yeV92aXNpYmlsaXR5IjoicHVibGljIiwicmVwb3NpdG9yeV9pZCI6IjYzMjU5Njg5NyIsImFjdG9yX2lkIjoiNDE4OTgyODIiLCJhY3RvciI6ImdpdGh1Yi1hY3Rpb25zW2JvdF0iLCJ3b3JrZmxvdyI6IkV4dHJlbWVseSBkYW5nZXJvdXMgT0lEQyBiZWFjb24iLCJoZWFkX3JlZiI6IiIsImJhc2VfcmVmIjoiIiwiZXZlbnRfbmFtZSI6IndvcmtmbG93X2Rpc3BhdGNoIiwicmVmX3Byb3RlY3RlZCI6InRydWUiLCJyZWZfdHlwZSI6ImJyYW5jaCIsIndvcmtmbG93X3JlZiI6InNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uLy5naXRodWIvd29ya2Zsb3dzL2V4dHJlbWVseS1kYW5nZXJvdXMtb2lkYy1iZWFjb24ueW1sQHJlZnMvaGVhZHMvbWFpbiIsIndvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJqb2Jfd29ya2Zsb3dfcmVmIjoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24vLmdpdGh1Yi93b3JrZmxvd3MvZXh0cmVtZWx5LWRhbmdlcm91cy1vaWRjLWJlYWNvbi55bWxAcmVmcy9oZWFkcy9tYWluIiwiam9iX3dvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJydW5uZXJfZW52aXJvbm1lbnQiOiJnaXRodWItaG9zdGVkIiwiaXNzIjoiaHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbSIsIm5iZiI6MTcyOTQ5NDg4OSwiZXhwIjoxNzI5NDk1Nzg5LCJpYXQiOjE3Mjk0OTU0ODl9.qMQkx6qIODt4fFvEpOFQNT7Gw-GVeoUtPNLwl-RGpJaO2EdCys4o3iPrX1-h8yvEtKpv4DFrtgNfzwTJCb9ueEWqW1Ll5oijYyd2VR7ghAYEeGV-sdwdkNQ1HO09UcRZcht00dgYayeVhbY4967dV5fNWuGib0c9BwJ2K1stzber3HgvGjjhPXoeKYdXvEE0L0MMq30b_eu1XW6ojvfeBTzujgHNxK8_drKAK1R9ENpBFBgreBeJvA1zu3hrvkby2g_sktRoHH2daOsdp4UhZjnr8IWJVMTAVHyueNJSu-UFKd5--TLKUdt_CpVW_PI_uv_xrKgJ9gU7n63w1qdbIQ From 07cb957a1c2dc107fa5aec054f02772a3bd4ccab Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Wed, 23 Oct 2024 14:29:51 +0300 Subject: [PATCH 3/4] bundle: Just use email OID regardless of actual value Apparently Fulcio does not care about the CSR subject: just claim everything is an email. https://github.com/sigstore/fulcio/blob/main/fulcio.proto#L106 Signed-off-by: Jussi Kukkonen --- src/bundle/sign.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/bundle/sign.rs b/src/bundle/sign.rs index d80d5e3467..7be4962b9e 100644 --- a/src/bundle/sign.rs +++ b/src/bundle/sign.rs @@ -85,13 +85,10 @@ impl<'ctx> SigningSession<'ctx> { fulcio: &FulcioClient, token: &IdentityToken, ) -> SigstoreResult<(ecdsa::SigningKey, fulcio::CertificateResponse)> { + // NOTE: Currently both email and machine identities get wrapped in a "email" OID. + // Fulcio does not care about the content. let identity = match &token.identity { - Identity::Sub(_) => { - return Err(SigstoreError::IdentityTokenError( - "Non-email identities are not yet supported".to_string(), - )) - } - Identity::Email(identity) => identity.as_str(), + Identity::Sub(identity) | Identity::Email(identity) => identity.as_str(), }; let subject = From 817851bee3487af4dfde8f321e2b45e455a694c9 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Thu, 24 Oct 2024 14:02:02 +0300 Subject: [PATCH 4/4] oauth: Make it clear that identity claim is untrusted We don't judge the claims that OIDC provider makes (apart from some compatibility checks): make this clear in the API and docs. Signed-off-by: Jussi Kukkonen --- examples/bundle/main.rs | 2 +- src/bundle/sign.rs | 6 +++--- src/oauth/mod.rs | 2 +- src/oauth/token.rs | 34 ++++++++++++++++++++-------------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/examples/bundle/main.rs b/examples/bundle/main.rs index 44e6751c46..612db59ad9 100644 --- a/examples/bundle/main.rs +++ b/examples/bundle/main.rs @@ -85,7 +85,7 @@ fn sign(artifact_path: &PathBuf) { }); let token = authorize(); - let identity = token.identity.to_string(); + let identity = token.identity_claim.to_string(); debug!("Signing with {}", identity); let signing_artifact = SigningContext::production().and_then(|ctx| { diff --git a/src/bundle/sign.rs b/src/bundle/sign.rs index 7be4962b9e..51d4ef2331 100644 --- a/src/bundle/sign.rs +++ b/src/bundle/sign.rs @@ -44,7 +44,7 @@ use crate::crypto::transparency::{verify_sct, CertificateEmbeddedSCT}; use crate::errors::{Result as SigstoreResult, SigstoreError}; use crate::fulcio::oauth::OauthTokenProvider; use crate::fulcio::{self, FulcioClient, FULCIO_ROOT}; -use crate::oauth::{Identity, IdentityToken}; +use crate::oauth::{IdentityClaim, IdentityToken}; use crate::rekor::apis::configuration::Configuration as RekorConfiguration; use crate::rekor::apis::entries_api::create_log_entry; use crate::rekor::models::{hashedrekord, proposed_entry::ProposedEntry as ProposedLogEntry}; @@ -87,8 +87,8 @@ impl<'ctx> SigningSession<'ctx> { ) -> SigstoreResult<(ecdsa::SigningKey, fulcio::CertificateResponse)> { // NOTE: Currently both email and machine identities get wrapped in a "email" OID. // Fulcio does not care about the content. - let identity = match &token.identity { - Identity::Sub(identity) | Identity::Email(identity) => identity.as_str(), + let identity = match &token.identity_claim { + IdentityClaim::Sub(identity) | IdentityClaim::Email(identity) => identity.as_str(), }; let subject = diff --git a/src/oauth/mod.rs b/src/oauth/mod.rs index 3959059918..a5998155bf 100644 --- a/src/oauth/mod.rs +++ b/src/oauth/mod.rs @@ -16,5 +16,5 @@ pub mod openidflow; pub mod token; -pub use token::Identity; +pub use token::IdentityClaim; pub use token::IdentityToken; diff --git a/src/oauth/token.rs b/src/oauth/token.rs index dc4d1e777d..cc837ddadd 100644 --- a/src/oauth/token.rs +++ b/src/oauth/token.rs @@ -37,28 +37,34 @@ pub struct Claims { pub type UnverifiedClaims = Claims; -// identity is the claim that we believe Fulcio uses: Depending on the issuer it is +// The identity that should be compatible with Fulcio: Depending on the issuer it is // either a "sub" or "email" claim. #[derive(Debug, PartialEq)] -pub enum Identity { +pub enum IdentityClaim { Sub(String), Email(String), } -impl fmt::Display for Identity { +impl fmt::Display for IdentityClaim { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Identity::Sub(sub) => sub.fmt(f), - Identity::Email(email) => email.fmt(f), + IdentityClaim::Sub(sub) => sub.fmt(f), + IdentityClaim::Email(email) => email.fmt(f), } } } -/// A Sigstore token. +/// A Sigstore (Fulcio) authentication token +/// +/// An IdentityToken is built from a OIDC JWT received from an OIDC provider and is used to +/// authenticate a signing identity to Fulcio to get a signing certificate for that identity. +/// +/// The content of the token, including identity and issuer claims, come unverified from +/// the JWT: IdentityToken only makes some validity checks. pub struct IdentityToken { original_token: String, claims: UnverifiedClaims, - pub identity: Identity, + pub identity_claim: IdentityClaim, } impl IdentityToken { @@ -111,7 +117,7 @@ impl TryFrom<&str> for IdentityToken { | "https://oauth2.sigstore.dev/auth" | "https://oauth2.sigstage.dev/auth" => { if let Some(email) = claims.email.as_ref() { - Identity::Email(email.clone()) + IdentityClaim::Email(email.clone()) } else { return Err(SigstoreError::IdentityTokenError( "Email claim not found in JWT".into(), @@ -120,7 +126,7 @@ impl TryFrom<&str> for IdentityToken { } _ => { if let Some(sub) = claims.sub.as_ref() { - Identity::Sub(sub.clone()) + IdentityClaim::Sub(sub.clone()) } else { return Err(SigstoreError::IdentityTokenError( "Sub claim not found in JWT".into(), @@ -132,7 +138,7 @@ impl TryFrom<&str> for IdentityToken { Ok(IdentityToken { original_token: value.to_owned(), claims, - identity, + identity_claim: identity, }) } } @@ -167,8 +173,8 @@ mod tests { Some(String::from("jku@goto.fi")) ); assert_eq!( - identity_token.identity, - Identity::Email(String::from("jku@goto.fi")) + identity_token.identity_claim, + IdentityClaim::Email(String::from("jku@goto.fi")) ); assert_eq!(identity_token.claims.aud, "sigstore"); assert_eq!( @@ -191,8 +197,8 @@ mod tests { Some(String::from("repo:sigstore-conformance/extremely-dangerous-public-oidc-beacon:ref:refs/heads/main")) ); assert_eq!( - identity_token.identity, - Identity::Sub(String::from("repo:sigstore-conformance/extremely-dangerous-public-oidc-beacon:ref:refs/heads/main")) + identity_token.identity_claim, + IdentityClaim::Sub(String::from("repo:sigstore-conformance/extremely-dangerous-public-oidc-beacon:ref:refs/heads/main")) ); assert_eq!(identity_token.claims.aud, "sigstore"); assert_eq!(