-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
Showing
5 changed files
with
112 additions
and
16 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
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
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 |
---|---|---|
|
@@ -16,4 +16,5 @@ | |
pub mod openidflow; | ||
|
||
pub mod token; | ||
pub use token::Identity; | ||
pub use token::IdentityToken; |
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 |
---|---|---|
|
@@ -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<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 { | ||
|
@@ -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, "[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() | ||
); | ||
} | ||
} |
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 @@ | ||
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ikh5cTROQVRBanNucUM3bWRydEFoaHJDUjJfUSIsImtpZCI6IjFGMkFCODM0MDRDMDhFQzlFQTBCQjk5REFFRDAyMTg2QjA5MURCRjQifQ.eyJqdGkiOiI4NTI3ZGQyMy0zYmNjLTQzYjgtYTMyNy1kYjFkM2Q4ZTY4NGUiLCJzdWIiOiJyZXBvOnNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uOnJlZjpyZWZzL2hlYWRzL21haW4iLCJhdWQiOiJzaWdzdG9yZSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJyZXBvc2l0b3J5Ijoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24iLCJyZXBvc2l0b3J5X293bmVyIjoic2lnc3RvcmUtY29uZm9ybWFuY2UiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMTMxODA0NTYzIiwicnVuX2lkIjoiMTE0MzUyMjEzNDQiLCJydW5fbnVtYmVyIjoiMTY5NDA3IiwicnVuX2F0dGVtcHQiOiIxIiwicmVwb3NpdG9yeV92aXNpYmlsaXR5IjoicHVibGljIiwicmVwb3NpdG9yeV9pZCI6IjYzMjU5Njg5NyIsImFjdG9yX2lkIjoiNDE4OTgyODIiLCJhY3RvciI6ImdpdGh1Yi1hY3Rpb25zW2JvdF0iLCJ3b3JrZmxvdyI6IkV4dHJlbWVseSBkYW5nZXJvdXMgT0lEQyBiZWFjb24iLCJoZWFkX3JlZiI6IiIsImJhc2VfcmVmIjoiIiwiZXZlbnRfbmFtZSI6IndvcmtmbG93X2Rpc3BhdGNoIiwicmVmX3Byb3RlY3RlZCI6InRydWUiLCJyZWZfdHlwZSI6ImJyYW5jaCIsIndvcmtmbG93X3JlZiI6InNpZ3N0b3JlLWNvbmZvcm1hbmNlL2V4dHJlbWVseS1kYW5nZXJvdXMtcHVibGljLW9pZGMtYmVhY29uLy5naXRodWIvd29ya2Zsb3dzL2V4dHJlbWVseS1kYW5nZXJvdXMtb2lkYy1iZWFjb24ueW1sQHJlZnMvaGVhZHMvbWFpbiIsIndvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJqb2Jfd29ya2Zsb3dfcmVmIjoic2lnc3RvcmUtY29uZm9ybWFuY2UvZXh0cmVtZWx5LWRhbmdlcm91cy1wdWJsaWMtb2lkYy1iZWFjb24vLmdpdGh1Yi93b3JrZmxvd3MvZXh0cmVtZWx5LWRhbmdlcm91cy1vaWRjLWJlYWNvbi55bWxAcmVmcy9oZWFkcy9tYWluIiwiam9iX3dvcmtmbG93X3NoYSI6IjZjZjNkMWQ3MTNjZDM0MTA4ODA3NTRmNWQxZDAyNTE4MTM2OTMzNTciLCJydW5uZXJfZW52aXJvbm1lbnQiOiJnaXRodWItaG9zdGVkIiwiaXNzIjoiaHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbSIsIm5iZiI6MTcyOTQ5NDg4OSwiZXhwIjoxNzI5NDk1Nzg5LCJpYXQiOjE3Mjk0OTU0ODl9.qMQkx6qIODt4fFvEpOFQNT7Gw-GVeoUtPNLwl-RGpJaO2EdCys4o3iPrX1-h8yvEtKpv4DFrtgNfzwTJCb9ueEWqW1Ll5oijYyd2VR7ghAYEeGV-sdwdkNQ1HO09UcRZcht00dgYayeVhbY4967dV5fNWuGib0c9BwJ2K1stzber3HgvGjjhPXoeKYdXvEE0L0MMq30b_eu1XW6ojvfeBTzujgHNxK8_drKAK1R9ENpBFBgreBeJvA1zu3hrvkby2g_sktRoHH2daOsdp4UhZjnr8IWJVMTAVHyueNJSu-UFKd5--TLKUdt_CpVW_PI_uv_xrKgJ9gU7n63w1qdbIQ |