Skip to content

Commit

Permalink
oauth: Fix the token identity handling
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
jku committed Oct 22, 2024
1 parent 6c779c8 commit 73597db
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 16 deletions.
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
13 changes: 11 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,15 @@ impl<'ctx> SigningSession<'ctx> {
fulcio: &FulcioClient,
token: &IdentityToken,
) -> SigstoreResult<(ecdsa::SigningKey<NistP256>, 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![
Expand All @@ -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()?
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;
107 changes: 96 additions & 11 deletions 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,
}

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(),
));
}
}
};

Ok(IdentityToken {
original_token: value.to_owned(),
claims,
identity,
})
}
}
Expand All @@ -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, "[email protected]");
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("[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"))
);
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

0 comments on commit 73597db

Please sign in to comment.