diff --git a/keyless/pepper/service/src/lib.rs b/keyless/pepper/service/src/lib.rs index 0c52182fd1945..f231a7fd4d8f7 100644 --- a/keyless/pepper/service/src/lib.rs +++ b/keyless/pepper/service/src/lib.rs @@ -320,7 +320,14 @@ async fn process_common( return Err(BadRequest("epk expired".to_string())); } - if exp_date_secs >= claims.claims.iat + config.max_exp_horizon_secs { + let (max_exp_data_secs, overflowed) = claims + .claims + .iat + .overflowing_add(config.max_exp_horizon_secs); + if overflowed { + return Err(BadRequest("max_exp_data_secs overflowed".to_string())); + } + if exp_date_secs >= max_exp_data_secs { return Err(BadRequest("epk expiry date too far".to_string())); } @@ -538,3 +545,6 @@ async fn update_account_recovery_db(input: &PepperInput) -> Result<(), Processin }, } } + +#[cfg(test)] +mod tests; diff --git a/keyless/pepper/service/src/tests.rs b/keyless/pepper/service/src/tests.rs new file mode 100644 index 0000000000000..58b09c0bb79a6 --- /dev/null +++ b/keyless/pepper/service/src/tests.rs @@ -0,0 +1,50 @@ +// Copyright (c) Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::{process_common, ProcessingFailure}; +use aptos_crypto::ed25519::Ed25519PublicKey; +use aptos_types::{ + keyless::{ + circuit_testcases::{ + sample_jwt_payload_json_overrides, SAMPLE_EXP_DATE, SAMPLE_JWT_EXTRA_FIELD, + SAMPLE_NONCE, SAMPLE_TEST_ISS_VALUE, SAMPLE_UID_VAL, + }, + test_utils::{get_sample_epk_blinder, get_sample_esk, get_sample_jwt_token_from_payload}, + }, + transaction::authenticator::EphemeralPublicKey, +}; +use uuid::Uuid; + +#[tokio::test] +async fn process_common_should_fail_if_max_exp_data_secs_overflowed() { + let session_id = Uuid::new_v4(); + let sk = get_sample_esk(); + let pk = Ed25519PublicKey::from(&sk); + + let jwt_payload = sample_jwt_payload_json_overrides( + SAMPLE_TEST_ISS_VALUE, + SAMPLE_UID_VAL, + SAMPLE_JWT_EXTRA_FIELD.as_str(), + u64::MAX - 1, // unusual iat + SAMPLE_NONCE.as_str(), + ); + + let jwt = get_sample_jwt_token_from_payload(&jwt_payload); + + let process_result = process_common( + &session_id, + jwt, + EphemeralPublicKey::ed25519(pk), + SAMPLE_EXP_DATE, + get_sample_epk_blinder(), + None, + None, + false, + None, + false, + ) + .await; + assert!( + matches!(process_result, Err(ProcessingFailure::BadRequest(e)) if e.as_str() == "max_exp_data_secs overflowed") + ); +} diff --git a/types/src/keyless/circuit_testcases.rs b/types/src/keyless/circuit_testcases.rs index f58ae07a08ed7..b542d26965232 100644 --- a/types/src/keyless/circuit_testcases.rs +++ b/types/src/keyless/circuit_testcases.rs @@ -38,7 +38,7 @@ pub(crate) static SAMPLE_JWT_HEADER_B64: Lazy = /// The JWT payload, decoded as JSON -static SAMPLE_NONCE: Lazy = Lazy::new(|| { +pub static SAMPLE_NONCE: Lazy = Lazy::new(|| { let config = Configuration::new_for_testing(); OpenIdSig::reconstruct_oauth_nonce( SAMPLE_EPK_BLINDER.as_slice(), @@ -49,9 +49,25 @@ static SAMPLE_NONCE: Lazy = Lazy::new(|| { .unwrap() }); -pub(crate) const SAMPLE_TEST_ISS_VALUE: &str = "test.oidc.provider"; +pub const SAMPLE_TEST_ISS_VALUE: &str = "test.oidc.provider"; -pub(crate) static SAMPLE_JWT_PAYLOAD_JSON: Lazy = Lazy::new(|| { +pub fn sample_jwt_payload_json() -> String { + sample_jwt_payload_json_overrides( + SAMPLE_TEST_ISS_VALUE, + SAMPLE_UID_VAL, + SAMPLE_JWT_EXTRA_FIELD.as_str(), + SAMPLE_JWT_IAT, + SAMPLE_NONCE.as_str(), + ) +} + +pub fn sample_jwt_payload_json_overrides( + iss: &str, + uid_val: &str, + extra_field: &str, + iat: u64, + nonce: &str, +) -> String { format!( r#"{{ "iss":"{}", @@ -67,27 +83,27 @@ pub(crate) static SAMPLE_JWT_PAYLOAD_JSON: Lazy = Lazy::new(|| { "given_name":"Michael", {} "locale":"en", - "iat":1700255944, + "iat":{}, "nonce":"{}", "exp":2700259544 }}"#, - SAMPLE_TEST_ISS_VALUE, - SAMPLE_UID_VAL, - SAMPLE_JWT_EXTRA_FIELD.as_str(), - SAMPLE_NONCE.as_str() + iss, uid_val, extra_field, iat, nonce ) -}); +} + +/// An example IAT. +pub const SAMPLE_JWT_IAT: u64 = 1700255944; /// Consistent with what is in `SAMPLE_JWT_PAYLOAD_JSON` pub(crate) const SAMPLE_JWT_EXTRA_FIELD_KEY: &str = "family_name"; /// Consistent with what is in `SAMPLE_JWT_PAYLOAD_JSON` -pub(crate) static SAMPLE_JWT_EXTRA_FIELD: Lazy = +pub static SAMPLE_JWT_EXTRA_FIELD: Lazy = Lazy::new(|| format!(r#""{}":"Straka","#, SAMPLE_JWT_EXTRA_FIELD_KEY)); /// The JWT parsed as a struct pub(crate) static SAMPLE_JWT_PARSED: Lazy = - Lazy::new(|| serde_json::from_str(SAMPLE_JWT_PAYLOAD_JSON.as_str()).unwrap()); + Lazy::new(|| serde_json::from_str(sample_jwt_payload_json().as_str()).unwrap()); pub(crate) static SAMPLE_JWK: Lazy = Lazy::new(insecure_test_rsa_jwk); @@ -97,10 +113,10 @@ pub(crate) static SAMPLE_JWK_SK: Lazy<&RsaKeyPair> = Lazy::new(|| &*INSECURE_TES pub(crate) const SAMPLE_UID_KEY: &str = "sub"; -pub(crate) const SAMPLE_UID_VAL: &str = "113990307082899718775"; +pub const SAMPLE_UID_VAL: &str = "113990307082899718775"; /// The nonce-committed expiration date (not the JWT `exp`), 12/21/5490 -pub(crate) const SAMPLE_EXP_DATE: u64 = 111_111_111_111; +pub const SAMPLE_EXP_DATE: u64 = 111_111_111_111; /// ~31,710 years pub(crate) const SAMPLE_EXP_HORIZON_SECS: u64 = 999_999_999_999; diff --git a/types/src/keyless/mod.rs b/types/src/keyless/mod.rs index c4209cfaada15..78e5b5365b7d4 100644 --- a/types/src/keyless/mod.rs +++ b/types/src/keyless/mod.rs @@ -23,7 +23,7 @@ use std::{ mod bn254_circom; mod circuit_constants; -mod circuit_testcases; +pub mod circuit_testcases; mod configuration; mod groth16_sig; mod groth16_vk; diff --git a/types/src/keyless/test_utils.rs b/types/src/keyless/test_utils.rs index a35e9602173a5..250175d9428b2 100644 --- a/types/src/keyless/test_utils.rs +++ b/types/src/keyless/test_utils.rs @@ -7,9 +7,9 @@ use crate::{ keyless::{ base64url_encode_str, circuit_testcases::{ - SAMPLE_EPK, SAMPLE_EPK_BLINDER, SAMPLE_ESK, SAMPLE_EXP_DATE, SAMPLE_EXP_HORIZON_SECS, - SAMPLE_JWK, SAMPLE_JWK_SK, SAMPLE_JWT_EXTRA_FIELD, SAMPLE_JWT_HEADER_B64, - SAMPLE_JWT_HEADER_JSON, SAMPLE_JWT_PARSED, SAMPLE_JWT_PAYLOAD_JSON, SAMPLE_PEPPER, + sample_jwt_payload_json, SAMPLE_EPK, SAMPLE_EPK_BLINDER, SAMPLE_ESK, SAMPLE_EXP_DATE, + SAMPLE_EXP_HORIZON_SECS, SAMPLE_JWK, SAMPLE_JWK_SK, SAMPLE_JWT_EXTRA_FIELD, + SAMPLE_JWT_HEADER_B64, SAMPLE_JWT_HEADER_JSON, SAMPLE_JWT_PARSED, SAMPLE_PEPPER, SAMPLE_PK, SAMPLE_PROOF, SAMPLE_PROOF_FOR_UPGRADED_VK, SAMPLE_PROOF_NO_EXTRA_FIELD, SAMPLE_UID_KEY, SAMPLE_UID_VAL, SAMPLE_UPGRADED_VK, }, @@ -272,8 +272,12 @@ pub fn get_sample_groth16_sig_and_pk_no_extra_field() -> (KeylessSignature, Keyl } pub fn get_sample_jwt_token() -> String { + get_sample_jwt_token_from_payload(sample_jwt_payload_json().as_str()) +} + +pub fn get_sample_jwt_token_from_payload(payload: &str) -> String { let jwt_header_b64 = SAMPLE_JWT_HEADER_B64.to_string(); - let jwt_payload_b64 = base64url_encode_str(SAMPLE_JWT_PAYLOAD_JSON.as_str()); + let jwt_payload_b64 = base64url_encode_str(payload); let msg = jwt_header_b64.clone() + "." + jwt_payload_b64.as_str(); let rng = ring::rand::SystemRandom::new(); let sk = &*SAMPLE_JWK_SK; @@ -296,7 +300,7 @@ pub fn get_sample_jwt_token() -> String { /// desired TXN. pub fn get_sample_openid_sig_and_pk() -> (KeylessSignature, KeylessPublicKey) { let jwt_header_b64 = SAMPLE_JWT_HEADER_B64.to_string(); - let jwt_payload_b64 = base64url_encode_str(SAMPLE_JWT_PAYLOAD_JSON.as_str()); + let jwt_payload_b64 = base64url_encode_str(sample_jwt_payload_json().as_str()); let msg = jwt_header_b64.clone() + "." + jwt_payload_b64.as_str(); let rng = ring::rand::SystemRandom::new(); let sk = *SAMPLE_JWK_SK; @@ -312,7 +316,7 @@ pub fn get_sample_openid_sig_and_pk() -> (KeylessSignature, KeylessPublicKey) { let openid_sig = OpenIdSig { jwt_sig, - jwt_payload_json: SAMPLE_JWT_PAYLOAD_JSON.to_string(), + jwt_payload_json: sample_jwt_payload_json().to_string(), uid_key: SAMPLE_UID_KEY.to_owned(), epk_blinder: SAMPLE_EPK_BLINDER.clone(), pepper: SAMPLE_PEPPER.clone(),