Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token fixes #412

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/bundle/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -105,7 +105,7 @@ fn sign(artifact_path: &PathBuf) {
println!(
"Created signature bundle {} with identity {}",
bundle_path.display(),
email
identity
);
}

Expand Down
10 changes: 8 additions & 2 deletions src/bundle/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -85,6 +85,12 @@ impl<'ctx> SigningSession<'ctx> {
fulcio: &FulcioClient,
token: &IdentityToken,
) -> SigstoreResult<(ecdsa::SigningKey<NistP256>, 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 subject =
// SEQUENCE OF RelativeDistinguishedName
vec![
Expand All @@ -95,7 +101,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()?
Expand Down
1 change: 1 addition & 0 deletions src/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
pub mod openidflow;

pub mod token;
pub use token::Identity;
pub use token::IdentityToken;
103 changes: 102 additions & 1 deletion src/oauth/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,15 +30,35 @@ pub struct Claims {
#[serde(with = "chrono::serde::ts_seconds_option")]
#[serde(default)]
pub nbf: Option<DateTime<Utc>>,
pub email: String,
pub email: Option<String>,
pub iss: String,
pub sub: Option<String>,
}

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,
jku marked this conversation as resolved.
Show resolved Hide resolved
}

impl IdentityToken {
Expand Down Expand Up @@ -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(),
));
}
}
};

This comment was marked as resolved.

Ok(IdentityToken {
original_token: value.to_owned(),
claims,
identity,
})
}
}
Expand All @@ -104,3 +152,56 @@ impl std::fmt::Display for IdentityToken {
write!(f, "{}", self.original_token.clone())
}
}

#[cfg(test)]
mod tests {
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,
Some(String::from("[email protected]"))
);
assert_eq!(
identity_token.identity,
Identity::Email(String::from("[email protected]"))
);
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()
);
}

#[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"))
Copy link
Member Author

@jku jku Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to double check if this is correct: It is the token "sub" field... but it does not seem to be what "bundle verify" uses for --identity

This comment was marked as outdated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I found the identity handling bits in fulcio and can confirm: before we get the certificate we ultimately do not know who we are signing as... exposing the token identity here is still useful since it happens to match the signing identity in the interactive case but it's unfortunately not as useful as I hoped.

);
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()
);
}
}
1 change: 1 addition & 0 deletions tests/data/tokens/gha-token.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ikh5cTROQVRBanNucUM3bWRydEFoaHJDUjJfUSIsImtpZCI6IjFGMkFCODM0MDRDMDhFQzlFQTBCQjk5REFFRDAyMTg2QjA5MURCRjQifQ.eyJqdGkiOiI4NTI3ZGQyMy0zYmNjLTQzYjgtYTMyNy1kYjFkM2Q4ZTY4NGUiLCJzdWIiOiJyZXBvOnNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uOnJlZjpyZWZzL2hlYWRzL21haW4iLCJhdWQiOiJzaWdzdG9yZSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJyZXBvc2l0b3J5Ijoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24iLCJyZXBvc2l0b3J5X293bmVyIjoic2lnc3RvcmUtY29uZm9ybWFuY2UiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTMxODA0NTYzIiwicnVuX2lkIjoiMTE0MzUyMjEzNDQiLCJydW5fbnVtYmVyIjoiMTY5NDA3IiwicnVuX2F0dGVtcHQiOiIxIiwicmVwb3NpdG9yeV92aXNpYmlsaXR5IjoicHVibGljIiwicmVwb3NpdG9yeV9pZCI6IjYzMjU5Njg5NyIsImFjdG9yX2lkIjoiNDE4OTgyODIiLCJhY3RvciI6ImdpdGh1Yi1hY3Rpb25zW2JvdF0iLCJ3b3JrZmxvdyI6IkV4dHJlbWVseSBkYW5nZXJvdXMgT0lEQyBiZWFjb24iLCJoZWFkX3JlZiI6IiIsImJhc2VfcmVmIjoiIiwiZXZlbnRfbmFtZSI6IndvcmtmbG93X2Rpc3BhdGNoIiwicmVmX3Byb3RlY3RlZCI6InRydWUiLCJyZWZfdHlwZSI6ImJyYW5jaCIsIndvcmtmbG93X3JlZiI6InNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uLy5naXRodWIvd29ya2Zsb3dzL2V4dHJlbWVseS1kYW5nZXJvdXMtb2lkYy1iZWFjb24ueW1sQHJlZnMvaGVhZHMvbWFpbiIsIndvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJqb2Jfd29ya2Zsb3dfcmVmIjoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24vLmdpdGh1Yi93b3JrZmxvd3MvZXh0cmVtZWx5LWRhbmdlcm91cy1vaWRjLWJlYWNvbi55bWxAcmVmcy9oZWFkcy9tYWluIiwiam9iX3dvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJydW5uZXJfZW52aXJvbm1lbnQiOiJnaXRodWItaG9zdGVkIiwiaXNzIjoiaHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbSIsIm5iZiI6MTcyOTQ5NDg4OSwiZXhwIjoxNzI5NDk1Nzg5LCJpYXQiOjE3Mjk0OTU0ODl9.qMQkx6qIODt4fFvEpOFQNT7Gw-GVeoUtPNLwl-RGpJaO2EdCys4o3iPrX1-h8yvEtKpv4DFrtgNfzwTJCb9ueEWqW1Ll5oijYyd2VR7ghAYEeGV-sdwdkNQ1HO09UcRZcht00dgYayeVhbY4967dV5fNWuGib0c9BwJ2K1stzber3HgvGjjhPXoeKYdXvEE0L0MMq30b_eu1XW6ojvfeBTzujgHNxK8_drKAK1R9ENpBFBgreBeJvA1zu3hrvkby2g_sktRoHH2daOsdp4UhZjnr8IWJVMTAVHyueNJSu-UFKd5--TLKUdt_CpVW_PI_uv_xrKgJ9gU7n63w1qdbIQ
1 change: 1 addition & 0 deletions tests/data/tokens/interactive-token.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsImtpZCI6IjMxNjA2OGMzM2ZhMjg2OTZhZmI5YzM5YWI2OTMxMjY1ZDk0Y2I3NTUifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNnVXpNVGc0T1JJbWFIUjBjSE02SlRKR0pUSkdaMmwwYUhWaUxtTnZiU1V5Um14dloybHVKVEpHYjJGMWRHZyIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNzI5NTEyOTMwLCJpYXQiOjE3Mjk1MTI4NzAsIm5vbmNlIjoiNTI3NjM3Y2UtN2Q2MS00MDA5LThkM2EtNGNjZGM3OGJiZDg1IiwiYXRfaGFzaCI6IktmMUNPTXB5TVJDTkdzWWp1QXczclEiLCJlbWFpbCI6ImprdUBnb3RvLmZpIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZlZGVyYXRlZF9jbGFpbXMiOnsiY29ubmVjdG9yX2lkIjoiaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoIiwidXNlcl9pZCI6IjMxODg5In19.s27uZ3vpIzRS4eWdC3pM0FSsYkHNvScQoii_TcSRVZhtrcPAbA4D95Pw_R_UB-qRquMK1BHepKmeN1b1-CQ00jiFZgUOf9sDLC3Hy3oQejGJsYKb-7oeHs7amLz3SBzPwDwVd09e-7Yu1x9YV5k6aezqruLLt42C_kyOTsHeCIWWMEVmGp32105Jkj8YT5uEYXS-aOEvQFvAYsDfKgGuiJtGybUycVcJEfqyWI3cami7fkjU5PcCx8oFyP2E7YNRw4UeNWCTn7WFtL2onrgDm0oa2AqF3gtH4Q-9ByksVq3y6xQdoLj1ydzWcoCzsF43oZ6O6DkLmWk5fu3FxNyewg
Loading