From 06f0c71f9e964fe2a5b39b29c2152e2da2b93304 Mon Sep 17 00:00:00 2001 From: Francisco Silva Date: Mon, 9 Dec 2024 16:01:49 +0100 Subject: [PATCH] Adding support for oauth2 trusted call authentication (#3189) --- common/primitives/core/src/identity.rs | 29 +++- local-setup/.env.dev | 4 +- tee-worker/Cargo.lock | 3 + .../identity/enclave-runtime/Cargo.lock | 3 + .../enclave-runtime/src/rpc/common_api.rs | 44 +++++- .../core/data-providers/src/google.rs | 76 +++++++++++ .../litentry/core/data-providers/src/lib.rs | 38 ++++-- .../core/identity-verification/src/lib.rs | 4 +- .../src/web2/email/mod.rs | 2 + .../email}/verification_code_store.rs | 0 .../src/web2/google/mod.rs | 108 +++++++++++++++ .../src/web2/google/oauth2_state_store.rs | 71 ++++++++++ .../identity-verification/src/web2/mod.rs | 7 +- .../core/native-task/receiver/Cargo.toml | 5 +- .../receiver/src/authentication_utils.rs | 128 ++++++++++++++++++ .../core/native-task/receiver/src/lib.rs | 60 ++++++-- .../src/trusted_call_authenticated.rs | 52 +------ .../core/native-task/receiver/src/types.rs | 7 +- 18 files changed, 549 insertions(+), 92 deletions(-) create mode 100644 tee-worker/identity/litentry/core/data-providers/src/google.rs rename tee-worker/identity/litentry/core/identity-verification/src/{ => web2/email}/verification_code_store.rs (100%) create mode 100644 tee-worker/identity/litentry/core/identity-verification/src/web2/google/mod.rs create mode 100644 tee-worker/identity/litentry/core/identity-verification/src/web2/google/oauth2_state_store.rs create mode 100644 tee-worker/identity/litentry/core/native-task/receiver/src/authentication_utils.rs diff --git a/common/primitives/core/src/identity.rs b/common/primitives/core/src/identity.rs index 1b648889a3..37830dedca 100644 --- a/common/primitives/core/src/identity.rs +++ b/common/primitives/core/src/identity.rs @@ -524,11 +524,36 @@ impl Identity { self.using_encoded(blake2_256).into() } - pub fn from_email(email: &str) -> Self { - Identity::Email(IdentityString::new(email.as_bytes().to_vec())) + pub fn from_web2_account(handle: &str, identity_type: Web2IdentityType) -> Self { + match identity_type { + Web2IdentityType::Twitter => { + Identity::Twitter(IdentityString::new(handle.as_bytes().to_vec())) + } + Web2IdentityType::Discord => { + Identity::Discord(IdentityString::new(handle.as_bytes().to_vec())) + } + Web2IdentityType::Github => { + Identity::Github(IdentityString::new(handle.as_bytes().to_vec())) + } + Web2IdentityType::Email => { + Identity::Email(IdentityString::new(handle.as_bytes().to_vec())) + } + Web2IdentityType::Google => { + Identity::Google(IdentityString::new(handle.as_bytes().to_vec())) + } + } } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Web2IdentityType { + Twitter, + Discord, + Github, + Email, + Google, +} + impl From for Identity { fn from(value: ed25519::Public) -> Self { Identity::Substrate(value.into()) diff --git a/local-setup/.env.dev b/local-setup/.env.dev index 641630178a..7d67abacd6 100644 --- a/local-setup/.env.dev +++ b/local-setup/.env.dev @@ -21,7 +21,7 @@ NODE_ENV=local ## ----------------------------------- # The following key/token are MANDATORY to give when running worker. -# Otherwise request-vc might suffering from data provider error. +# Otherwise request-vc might suffering from data provider error and OAuth2 verification will not work. TWITTER_AUTH_TOKEN_V2= TWITTER_CLIENT_ID= TWITTER_CLIENT_SECRET= @@ -34,6 +34,8 @@ NODEREAL_API_KEY= GENIIDATA_API_KEY= MORALIS_API_KEY= MAGIC_CRAFT_API_KEY= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= # The followings are default value. # Can be skipped; or overwrite within non-production mode. diff --git a/tee-worker/Cargo.lock b/tee-worker/Cargo.lock index 283982ca59..355ef7ab86 100644 --- a/tee-worker/Cargo.lock +++ b/tee-worker/Cargo.lock @@ -5195,6 +5195,7 @@ dependencies = [ name = "lc-native-task-receiver" version = "0.1.0" dependencies = [ + "base64 0.22.1", "frame-support", "futures 0.3.28", "futures 0.3.8", @@ -5224,6 +5225,8 @@ dependencies = [ "litentry-primitives", "log 0.4.20", "parity-scale-codec", + "serde 1.0.210", + "serde_json 1.0.120", "sgx_tstd", "sp-core", ] diff --git a/tee-worker/identity/enclave-runtime/Cargo.lock b/tee-worker/identity/enclave-runtime/Cargo.lock index a74173f4ef..02721733cc 100644 --- a/tee-worker/identity/enclave-runtime/Cargo.lock +++ b/tee-worker/identity/enclave-runtime/Cargo.lock @@ -3121,6 +3121,7 @@ dependencies = [ name = "lc-native-task-receiver" version = "0.1.0" dependencies = [ + "base64 0.22.1", "frame-support", "futures 0.3.8", "id-ita-sgx-runtime", @@ -3149,6 +3150,8 @@ dependencies = [ "litentry-primitives", "log", "parity-scale-codec", + "serde 1.0.204", + "serde_json 1.0.120", "sgx_tstd", "sp-core", ] diff --git a/tee-worker/identity/enclave-runtime/src/rpc/common_api.rs b/tee-worker/identity/enclave-runtime/src/rpc/common_api.rs index 5d72c2eefb..6995ac26ae 100644 --- a/tee-worker/identity/enclave-runtime/src/rpc/common_api.rs +++ b/tee-worker/identity/enclave-runtime/src/rpc/common_api.rs @@ -33,11 +33,12 @@ use jsonrpc_core::{serde_json::json, IoHandler, Params, Value}; use lc_data_providers::DataProviderConfig; use lc_identity_verification::{ generate_verification_code, - web2::{email, twitter}, - VerificationCodeStore, + web2::{email, google, twitter}, }; use litentry_macros::{if_development, if_development_or}; -use litentry_primitives::{aes_decrypt, AesRequest, DecryptableRequest, Identity}; +use litentry_primitives::{ + aes_decrypt, AesRequest, DecryptableRequest, Identity, Web2IdentityType, +}; use log::debug; use sgx_crypto_helper::rsa3072::Rsa3072PubKey; use sp_core::Pair; @@ -466,6 +467,39 @@ pub fn add_common_api() { + Ok((encoded_omni_account, google_account, redirect_uri)) => { + let omni_account = match AccountId::from_hex(encoded_omni_account.as_str()) { + Ok(account_id) => account_id, + Err(_) => + return Ok(json!(compute_hex_encoded_return_error( + "Could not parse omni account" + ))), + }; + let google_identity = + Identity::from_web2_account(&google_account, Web2IdentityType::Google); + let state = generate_verification_code(); + let authorize_data = google::get_authorize_data(&google_client_id, &redirect_uri); + + match google::OAuthStateStore::insert(omni_account, google_identity.hash(), state) { + Ok(_) => { + let json_value = RpcReturnValue::new( + authorize_data.authorize_url.encode(), + false, + DirectRequestStatus::Ok, + ); + Ok(json!(json_value.to_hex())) + }, + Err(_) => Ok(json!(compute_hex_encoded_return_error("Could not save state"))), + } + }, + Err(_) => Ok(json!(compute_hex_encoded_return_error("Could not parse params"))), + } + }); + io_handler.add_sync_method("omni_requestEmailVerificationCode", move |params: Params| { match params.parse::<(String, String)>() { Ok((encoded_omni_account, email)) => { @@ -481,9 +515,9 @@ pub fn add_common_api. + +use crate::{build_client_with_cert, format, Error, Headers, HttpError, String, ToString}; +use itc_rest_client::{ + http_client::{HttpClient, SendWithCertificateVerification}, + rest_client::RestClient, + RestPath, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +const OAUTH2_BASE_URL: &str = "https://oauth2.googleapis.com"; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct GoogleOAuth2TokenResponse { + access_token: String, + expires_in: u64, + id_token: String, + scope: String, + token_type: String, +} + +impl RestPath for GoogleOAuth2TokenResponse { + fn get_path(path: String) -> Result { + Ok(path) + } +} + +pub struct GoogleOAuth2Client { + client: RestClient>, + client_id: String, + client_secret: String, +} + +impl GoogleOAuth2Client { + pub fn new(client_id: String, client_secret: String) -> Self { + let client = build_client_with_cert(OAUTH2_BASE_URL, Headers::new()); + Self { client, client_id, client_secret } + } + + pub fn exchange_code_for_token( + &mut self, + code: String, + redirect_uri: String, + ) -> Result { + let path = String::from("/token"); + + let mut body = HashMap::new(); + body.insert("code".to_string(), code); + body.insert("client_id".to_string(), self.client_id.clone()); + body.insert("client_secret".to_string(), self.client_secret.clone()); + body.insert("redirect_uri".to_string(), redirect_uri); + body.insert("grant_type".to_string(), "authorization_code".to_string()); + + let response = self + .client + .post_form_urlencoded_capture::(path, body) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + + Ok(response.id_token) + } +} diff --git a/tee-worker/identity/litentry/core/data-providers/src/lib.rs b/tee-worker/identity/litentry/core/data-providers/src/lib.rs index cba11218fe..b83bdeaf2e 100644 --- a/tee-worker/identity/litentry/core/data-providers/src/lib.rs +++ b/tee-worker/identity/litentry/core/data-providers/src/lib.rs @@ -18,6 +18,7 @@ #![allow(clippy::large_enum_variant)] #![allow(clippy::result_large_err)] +extern crate alloc; extern crate core; #[cfg(all(not(feature = "std"), feature = "sgx"))] extern crate sgx_tstd as std; @@ -34,6 +35,12 @@ pub mod sgx_reexport_prelude { #[cfg(all(not(feature = "std"), feature = "sgx"))] use crate::sgx_reexport_prelude::*; +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; use codec::{Decode, Encode}; use core::time::Duration; use http_req::response::Headers; @@ -43,19 +50,13 @@ use itc_rest_client::{ rest_client::RestClient, Query, RestGet, RestPath, RestPost, }; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::{thread, vec}; - use litentry_primitives::{ AchainableParams, Assertion, ErrorDetail, ErrorString, IntoErrorDetail, ParameterString, VCMPError, }; -use std::{ - env, format, - string::{String, ToString}, - vec::Vec, -}; +use log::debug; +use serde::{Deserialize, Serialize}; +use std::{env, thread}; use url::Url; #[cfg(all(feature = "std", feature = "sgx"))] @@ -68,6 +69,7 @@ pub mod daren_market; pub mod discord_litentry; pub mod discord_official; pub mod geniidata; +pub mod google; pub mod karat_dao; pub mod magic_craft; pub mod moralis; @@ -174,6 +176,8 @@ pub struct DataProviderConfig { pub discord_official_url: String, pub discord_client_id: String, pub discord_client_secret: String, + pub google_client_id: String, + pub google_client_secret: String, pub litentry_discord_microservice_url: String, pub discord_auth_token: String, pub achainable_url: String, @@ -230,6 +234,8 @@ impl DataProviderConfig { discord_official_url: "https://discordapp.com".to_string(), discord_client_id: "".to_string(), discord_client_secret: "".to_string(), + google_client_id: "".to_string(), + google_client_secret: "".to_string(), litentry_discord_microservice_url: "https://tee-microservice.litentry.io:9528" .to_string(), discord_auth_token: "".to_string(), @@ -399,6 +405,12 @@ impl DataProviderConfig { if let Ok(v) = env::var("DISCORD_CLIENT_SECRET") { config.set_discord_client_secret(v); } + if let Ok(v) = env::var("GOOGLE_CLIENT_ID") { + config.set_google_client_id(v); + } + if let Ok(v) = env::var("GOOGLE_CLIENT_SECRET") { + config.set_google_client_secret(v); + } if let Ok(v) = env::var("ACHAINABLE_AUTH_KEY") { config.set_achainable_auth_key(v); } @@ -458,6 +470,14 @@ impl DataProviderConfig { debug!("set_discord_client_secret: {:?}", v); self.discord_client_secret = v; } + pub fn set_google_client_id(&mut self, v: String) { + debug!("set_google_client_id: {:?}", v); + self.google_client_id = v; + } + pub fn set_google_client_secret(&mut self, v: String) { + debug!("set_google_client_secret: {:?}", v); + self.google_client_secret = v; + } pub fn set_litentry_discord_microservice_url(&mut self, v: String) -> Result<(), Error> { check_url(&v)?; debug!("set_litentry_discord_microservice_url: {:?}", v); diff --git a/tee-worker/identity/litentry/core/identity-verification/src/lib.rs b/tee-worker/identity/litentry/core/identity-verification/src/lib.rs index 052c68b067..01eb5597b4 100644 --- a/tee-worker/identity/litentry/core/identity-verification/src/lib.rs +++ b/tee-worker/identity/litentry/core/identity-verification/src/lib.rs @@ -16,7 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub extern crate alloc; +extern crate alloc; #[cfg(all(not(feature = "std"), feature = "sgx"))] extern crate sgx_tstd as std; @@ -35,9 +35,7 @@ compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the sam mod error; mod helpers; -mod verification_code_store; pub mod web2; -pub use verification_code_store::VerificationCodeStore; use alloc::string::String; use error::{Error, Result}; diff --git a/tee-worker/identity/litentry/core/identity-verification/src/web2/email/mod.rs b/tee-worker/identity/litentry/core/identity-verification/src/web2/email/mod.rs index cb63aa851b..d186d234f7 100644 --- a/tee-worker/identity/litentry/core/identity-verification/src/web2/email/mod.rs +++ b/tee-worker/identity/litentry/core/identity-verification/src/web2/email/mod.rs @@ -1,3 +1,5 @@ +mod verification_code_store; +pub use verification_code_store::*; mod mailer; pub use mailer::*; diff --git a/tee-worker/identity/litentry/core/identity-verification/src/verification_code_store.rs b/tee-worker/identity/litentry/core/identity-verification/src/web2/email/verification_code_store.rs similarity index 100% rename from tee-worker/identity/litentry/core/identity-verification/src/verification_code_store.rs rename to tee-worker/identity/litentry/core/identity-verification/src/web2/email/verification_code_store.rs diff --git a/tee-worker/identity/litentry/core/identity-verification/src/web2/google/mod.rs b/tee-worker/identity/litentry/core/identity-verification/src/web2/google/mod.rs new file mode 100644 index 0000000000..f1c0921217 --- /dev/null +++ b/tee-worker/identity/litentry/core/identity-verification/src/web2/google/mod.rs @@ -0,0 +1,108 @@ +mod oauth2_state_store; +pub use oauth2_state_store::*; + +use crate::helpers; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; +use serde::Deserialize; +use url::Url; + +const BASE_URL: &str = "https://accounts.google.com/o/oauth2/v2/auth"; +const SCOPES: &str = "openid email"; + +pub struct AuthorizeData { + pub authorize_url: String, + pub state: String, +} + +pub fn get_authorize_data(client_id: &str, redirect_uri: &str) -> AuthorizeData { + let state = helpers::get_random_string(32); + + let mut authorize_url = Url::parse(BASE_URL).unwrap(); + authorize_url.query_pairs_mut().extend_pairs(&[ + ("response_type", "code"), + ("client_id", client_id), + ("redirect_uri", redirect_uri), + ("scope", SCOPES), + ("state", &state), + ]); + + AuthorizeData { authorize_url: authorize_url.into(), state } +} + +#[derive(Deserialize)] +pub struct GoogleClaims { + pub iss: String, + pub azp: String, + pub email_verified: bool, + pub at_hash: String, + pub aud: String, + pub exp: u64, + pub iat: u64, + pub sub: String, + pub hd: String, + pub email: String, +} + +pub fn decode_jwt(jwt: &str) -> Result { + let parts: Vec<&str> = jwt.split('.').collect(); + if parts.len() != 3 { + return Err("Invalid JWT") + } + let payload = base64_decode(parts[1])?; + let claims: GoogleClaims = + serde_json::from_str(&payload).map_err(|_| "Failed to parse claims")?; + Ok(claims) +} + +fn base64_decode(input: &str) -> Result { + let decoded = &BASE64_URL_SAFE_NO_PAD.decode(input).map_err(|_| "Failed to decode base64")?; + + Ok(String::from_utf8_lossy(decoded).to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::format; + use url::Url; + + #[test] + fn test_get_authorize_data() { + let client_id = "client_id"; + let redirect_uri = "http://localhost:8080"; + let authorize_data = get_authorize_data(client_id, redirect_uri); + + let authorize_url = Url::parse(&authorize_data.authorize_url).unwrap(); + std::println!("{:?}", authorize_url.as_str()); + let expected_url = format!("https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080&scope=openid+email&state={}", authorize_data.state); + + assert_eq!(authorize_url.as_str(), expected_url); + } + + #[test] + fn decode_google_jwt_works() { + let jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjM2MjgyNTg2MDExMTNlNjU3NmE0NTMzNzM2NWZlOGI4OTczZDE2NzEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI2ODYyOTM4MTAwNjktbTBhNzVwYm9mMWVwbzJzZzkyYTU3cHRtazg1c2FnbGYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI2ODYyOTM4MTAwNjktbTBhNzVwYm9mMWVwbzJzZzkyYTU3cHRtazg1c2FnbGYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDE2NTk5MjE1MTM4NzY4MzIwNDgiLCJoZCI6Imthd2FnYXJiby10ZWNoLmlvIiwiZW1haWwiOiJmcmFuY2lzY29Aa2F3YWdhcmJvLXRlY2guaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IlBuYndCRVIzTnVBa055dFplR2wzcGciLCJpYXQiOjE3MzMyMzU4NDcsImV4cCI6MTczMzIzOTQ0N30.n4gYeYhp2U1ud4bZNW02xMJadki_93CzlcsJnr8F6eIBXwu4-CbsqToNNn40Kq780Wwz44MqnrEIU8dkBLqBc6MBWkMqzQV-RteEXMiZSOAhkNl8dIzds4vDZUnXunom4y-RYcW7yFMu_Vzpdi9A1NmgMvKVf9wqgfTJrqmPwaUh1GfgV8e7SrqHJiI3XVTE_zIxQVdjybR-7dXGh2B9LaXtA1m8v47tNkvtifa7KUw-miSIVt0of0Dq3keETLyptf8HJ1HouwpACMnxSH-Foq3r5EVp3lfGmkmf5dWMxweagsi7-hMhSKsGY2q2g3gy8xxsCaS1Q3uiB1Htw1Dn7Q"; + let claims = decode_jwt(jwt).unwrap(); + + assert_eq!(claims.iss, "https://accounts.google.com"); + assert_eq!( + claims.azp, + "686293810069-m0a75pbof1epo2sg92a57ptmk85saglf.apps.googleusercontent.com" + ); + assert_eq!(claims.email_verified, true); + assert_eq!(claims.at_hash, "PnbwBER3NuAkNytZeGl3pg"); + assert_eq!( + claims.aud, + "686293810069-m0a75pbof1epo2sg92a57ptmk85saglf.apps.googleusercontent.com" + ); + assert_eq!(claims.exp, 1733239447); + assert_eq!(claims.iat, 1733235847); + assert_eq!(claims.sub, "101659921513876832048"); + assert_eq!(claims.hd, "kawagarbo-tech.io"); + assert_eq!(claims.email, "francisco@kawagarbo-tech.io"); + } +} diff --git a/tee-worker/identity/litentry/core/identity-verification/src/web2/google/oauth2_state_store.rs b/tee-worker/identity/litentry/core/identity-verification/src/web2/google/oauth2_state_store.rs new file mode 100644 index 0000000000..662eb5f8a8 --- /dev/null +++ b/tee-worker/identity/litentry/core/identity-verification/src/web2/google/oauth2_state_store.rs @@ -0,0 +1,71 @@ +use crate::alloc::{fmt, format, string::String}; +use codec::Encode; +use core::result::Result; +use lazy_static::lazy_static; +use litentry_primitives::{ + ErrorDetail, ErrorString, IntoErrorDetail, ParentchainAccountId as AccountId, +}; +use lru::LruCache; +use sp_core::H256; +use std::num::NonZeroUsize; +#[cfg(feature = "std")] +use std::sync::RwLock; +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[derive(Debug)] +pub enum OAuthStateStoreError { + LockPoisoning, + Other(String), +} + +impl fmt::Display for OAuthStateStoreError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OAuthStateStoreError::LockPoisoning => write!(f, "Lock poisoning"), + OAuthStateStoreError::Other(msg) => write!(f, "{}", msg), + } + } +} + +impl std::error::Error for OAuthStateStoreError {} + +impl IntoErrorDetail for OAuthStateStoreError { + fn into_error_detail(self) -> ErrorDetail { + ErrorDetail::StfError(ErrorString::truncate_from(format!("{}", self).into())) + } +} + +lazy_static! { + static ref STORE: RwLock> = + RwLock::new(LruCache::new(NonZeroUsize::new(500).unwrap())); +} + +pub struct OAuthStateStore; + +type StateVerifier = String; + +impl OAuthStateStore { + pub fn insert( + account_id: AccountId, + identity_hash: H256, + state_verifier: String, + ) -> Result<(), OAuthStateStoreError> { + STORE + .write() + .map_err(|_| OAuthStateStoreError::LockPoisoning)? + .put(hex::encode((account_id, identity_hash).encode()), state_verifier); + Ok(()) + } + + pub fn get( + account_id: &AccountId, + identity_hash: H256, + ) -> Result, OAuthStateStoreError> { + let code = STORE + .write() + .map_err(|_| OAuthStateStoreError::LockPoisoning)? + .pop(hex::encode((account_id, identity_hash).encode()).as_str()); + Ok(code) + } +} diff --git a/tee-worker/identity/litentry/core/identity-verification/src/web2/mod.rs b/tee-worker/identity/litentry/core/identity-verification/src/web2/mod.rs index 020981d7f8..19b7927cce 100644 --- a/tee-worker/identity/litentry/core/identity-verification/src/web2/mod.rs +++ b/tee-worker/identity/litentry/core/identity-verification/src/web2/mod.rs @@ -22,9 +22,11 @@ compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the sam mod discord; pub mod email; +pub mod google; pub mod twitter; -use crate::{ensure, Error, Result, VerificationCodeStore}; +use crate::{ensure, Error, Result}; +use email::VerificationCodeStore; use itp_sgx_crypto::ShieldingCryptoDecrypt; use itp_utils::stringify::account_id_to_string; use lc_data_providers::{ @@ -212,7 +214,8 @@ pub fn verify( _ => return Err(Error::LinkIdentityFailed(ErrorDetail::InvalidIdentity)), }; - let email_identity = Identity::from_email(&email); + let email_identity = + Identity::from_web2_account(&email, litentry_primitives::Web2IdentityType::Email); let stored_verification_code = match VerificationCodeStore::get(&account_id, email_identity.hash()) { Ok(data) => data.ok_or_else(|| { diff --git a/tee-worker/identity/litentry/core/native-task/receiver/Cargo.toml b/tee-worker/identity/litentry/core/native-task/receiver/Cargo.toml index 6367c09cb6..4ec3218592 100644 --- a/tee-worker/identity/litentry/core/native-task/receiver/Cargo.toml +++ b/tee-worker/identity/litentry/core/native-task/receiver/Cargo.toml @@ -4,11 +4,13 @@ version = "0.1.0" edition = "2021" [dependencies] +base64 = { version = "0.22", default-features = false, features = ["alloc"] } # a newer base64 futures = { workspace = true, optional = true } futures_sgx = { workspace = true, features = ["thread-pool"], optional = true } +serde = { workspace = true } +serde_json = { workspace = true } sgx_tstd = { workspace = true, features = ["net", "thread"], optional = true } - codec = { package = "parity-scale-codec", workspace = true } log = { workspace = true } sp-core = { workspace = true, features = ["full_crypto"] } @@ -68,7 +70,6 @@ std = [ "itp-extrinsics-factory/std", "sp-core/std", "litentry-primitives/std", - "ita-sgx-runtime/std", "frame-support/std", "itp-node-api/std", "lc-native-task-sender/std", diff --git a/tee-worker/identity/litentry/core/native-task/receiver/src/authentication_utils.rs b/tee-worker/identity/litentry/core/native-task/receiver/src/authentication_utils.rs new file mode 100644 index 0000000000..5003214b9f --- /dev/null +++ b/tee-worker/identity/litentry/core/native-task/receiver/src/authentication_utils.rs @@ -0,0 +1,128 @@ +use crate::VerificationCode; +use alloc::{format, string::String, sync::Arc}; +use codec::{Decode, Encode}; +use ita_stf::{LitentryMultiSignature, TrustedCall}; +use itp_types::parentchain::Index as ParentchainIndex; +use lc_data_providers::{google::GoogleOAuth2Client, DataProviderConfig}; +use lc_identity_verification::web2::{email::VerificationCodeStore, google}; +use lc_omni_account::InMemoryStore as OmniAccountStore; +use litentry_hex_utils::hex_encode; +use litentry_primitives::{Identity, ShardIdentifier, Web2IdentityType}; +use sp_core::{blake2_256, crypto::AccountId32 as AccountId, H256}; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub enum OAuth2Provider { + Google, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct OAuth2Data { + pub provider: OAuth2Provider, + pub code: String, + pub state: String, + pub redirect_uri: String, +} + +#[derive(Debug)] +pub enum AuthenticationError { + Web3InvalidSignature, + EmailVerificationCodeNotFound, + EmailInvalidVerificationCode, + OAuth2Error(String), +} + +pub fn verify_tca_web3_authentication( + signature: &LitentryMultiSignature, + call: &TrustedCall, + nonce: ParentchainIndex, + mrenclave: &[u8; 32], + shard: &ShardIdentifier, +) -> Result<(), AuthenticationError> { + let mut payload = call.encode(); + payload.append(&mut nonce.encode()); + payload.append(&mut mrenclave.encode()); + payload.append(&mut shard.encode()); + + // The signature should be valid in either case: + // 1. blake2_256(payload) + // 2. Signature Prefix + blake2_256(payload) + + let hashed = blake2_256(&payload); + + let prettified_msg_hash = call.signature_message_prefix() + &hex_encode(&hashed); + let prettified_msg_hash = prettified_msg_hash.as_bytes(); + + // Most common signatures variants by clients are verified first (4 and 2). + match signature.verify(prettified_msg_hash, call.sender_identity()) + || signature.verify(&hashed, call.sender_identity()) + { + true => Ok(()), + false => Err(AuthenticationError::Web3InvalidSignature), + } +} + +pub fn verify_tca_email_authentication( + sender_identity_hash: H256, + omni_account: &AccountId, + verification_code: VerificationCode, +) -> Result<(), AuthenticationError> { + let Ok(Some(code)) = VerificationCodeStore::get(omni_account, sender_identity_hash) else { + return Err(AuthenticationError::EmailVerificationCodeNotFound); + }; + if code == verification_code { + Ok(()) + } else { + Err(AuthenticationError::EmailInvalidVerificationCode) + } +} + +pub fn verify_tca_oauth2_authentication( + data_providers_config: Arc, + sender_identity_hash: H256, + omni_account: &AccountId, + payload: OAuth2Data, +) -> Result<(), AuthenticationError> { + match payload.provider { + OAuth2Provider::Google => + verify_google_oauth2(data_providers_config, sender_identity_hash, omni_account, payload), + } +} + +fn verify_google_oauth2( + data_providers_config: Arc, + sender_identity_hash: H256, + omni_account: &AccountId, + payload: OAuth2Data, +) -> Result<(), AuthenticationError> { + let state_verifier_result = google::OAuthStateStore::get(omni_account, sender_identity_hash); + let Ok(Some(state_verifier)) = state_verifier_result else { + return Err(AuthenticationError::OAuth2Error( + String::from("State verifier not found"), + )); + }; + if state_verifier != payload.state { + return Err(AuthenticationError::OAuth2Error(String::from("Invalid state"))) + } + + let mut google_client = GoogleOAuth2Client::new( + data_providers_config.google_client_id.clone(), + data_providers_config.google_client_secret.clone(), + ); + let code = payload.code.clone(); + let redirect_uri = payload.redirect_uri; + let token = google_client.exchange_code_for_token(code, redirect_uri).map_err(|e| { + AuthenticationError::OAuth2Error(format!("Failed to exchange code for token: {:?}", e)) + })?; + let claims = google::decode_jwt(&token) + .map_err(|e| AuthenticationError::OAuth2Error(format!("Failed to decode JWT: {:?}", e)))?; + let google_identity = Identity::from_web2_account(&claims.email, Web2IdentityType::Google); + let identity_omni_account = match OmniAccountStore::get_omni_account(google_identity.hash()) { + Ok(Some(account_id)) => account_id, + _ => google_identity.to_omni_account(), + }; + + match *omni_account == identity_omni_account { + true => Ok(()), + false => Err(AuthenticationError::OAuth2Error(String::from("Invalid identity member"))), + } +} diff --git a/tee-worker/identity/litentry/core/native-task/receiver/src/lib.rs b/tee-worker/identity/litentry/core/native-task/receiver/src/lib.rs index ec7e9937af..3bb7dd0d26 100644 --- a/tee-worker/identity/litentry/core/native-task/receiver/src/lib.rs +++ b/tee-worker/identity/litentry/core/native-task/receiver/src/lib.rs @@ -38,6 +38,12 @@ use std::sync::Mutex; #[cfg(feature = "sgx")] use std::sync::SgxMutex as Mutex; +mod authentication_utils; +use authentication_utils::{ + verify_tca_email_authentication, verify_tca_oauth2_authentication, + verify_tca_web3_authentication, +}; + mod trusted_call_handlers; mod trusted_call_authenticated; @@ -53,7 +59,6 @@ use types::*; use alloc::{borrow::ToOwned, boxed::Box, format, string::ToString, sync::Arc, vec::Vec}; use codec::{Compact, Decode, Encode}; use futures::executor::{ThreadPool, ThreadPoolBuilder}; -use ita_sgx_runtime::Hash; use ita_stf::{ helpers::{get_expected_raw_message, verify_web3_identity}, trusted_call_result::RequestVcErrorDetail, @@ -101,7 +106,7 @@ pub fn run_native_task_receiver::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt, - AA: AuthorApiTrait + Send + Sync + 'static, + AA: AuthorApiTrait + Send + Sync + 'static, SES: StfEnclaveSigningTrait + Send + Sync + 'static, OA: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi + EnclaveMetricsOCallApi + 'static, EF: CreateExtrinsics + Send + Sync + 'static, @@ -151,7 +156,7 @@ fn handle_request( where ShieldingKeyRepository: AccessKey + Send + Sync + 'static, ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt, - AA: AuthorApiTrait + Send + Sync + 'static, + AA: AuthorApiTrait + Send + Sync + 'static, SES: StfEnclaveSigningTrait + Send + Sync + 'static, OA: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi + EnclaveMetricsOCallApi + 'static, EF: CreateExtrinsics + Send + Sync + 'static, @@ -195,7 +200,7 @@ where }, }; - let authentication_valid = match tca.authentication { + let authentication_result = match tca.authentication { TCAuthentication::Web3(signature) => verify_tca_web3_authentication( &signature, &tca.call, @@ -203,17 +208,42 @@ where &mrenclave, &request.shard, ), - TCAuthentication::Email(verification_code) => - verify_tca_email_authentication(&tca.call, verification_code), + TCAuthentication::Email(verification_code) => { + let sender_identity = tca.call.sender_identity(); + let omni_account = match get_omni_account(context.ocall_api.clone(), sender_identity) { + Ok(account) => account, + _ => sender_identity.to_omni_account(), + }; + verify_tca_email_authentication( + sender_identity.hash(), + &omni_account, + verification_code, + ) + }, + TCAuthentication::OAuth2(oauth2_data) => { + let sender_identity = tca.call.sender_identity(); + let omni_account = match get_omni_account(context.ocall_api.clone(), sender_identity) { + Ok(account) => account, + _ => sender_identity.to_omni_account(), + }; + verify_tca_oauth2_authentication( + context.data_provider_config.clone(), + sender_identity.hash(), + &omni_account, + oauth2_data, + ) + }, }; - if !authentication_valid { - let res: TrustedCallResult = Err(TrustedCallError::AuthenticationVerificationFailed); - context.author_api.send_rpc_response(connection_hash, res.encode(), false); - return Err("Authentication verification failed") + match authentication_result { + Ok(_) => Ok(tca.call), + Err(e) => { + log::error!("Failed to verify authentication: {:?}", e); + let res: TrustedCallResult = Err(TrustedCallError::AuthenticationVerificationFailed); + context.author_api.send_rpc_response(connection_hash, res.encode(), false); + Err("Authentication verification failed") + }, } - - Ok(tca.call) } type TrustedCallResult = Result, TrustedCallError>; @@ -227,7 +257,7 @@ fn handle_trusted_call::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt, - AA: AuthorApiTrait + Send + Sync + 'static, + AA: AuthorApiTrait + Send + Sync + 'static, SES: StfEnclaveSigningTrait + Send + Sync + 'static, OA: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + 'static, EF: CreateExtrinsics + Send + Sync + 'static, @@ -409,10 +439,10 @@ fn handle_trusted_call = HashSet::new(); + let mut seen: HashSet = HashSet::new(); let mut unique_assertions = Vec::new(); for assertion in assertions.into_iter() { - let hash = Hash::from(blake2_256(&assertion.encode())); + let hash = H256::from(blake2_256(&assertion.encode())); if seen.insert(hash) { unique_assertions.push(Some(assertion)); } else { diff --git a/tee-worker/identity/litentry/core/native-task/receiver/src/trusted_call_authenticated.rs b/tee-worker/identity/litentry/core/native-task/receiver/src/trusted_call_authenticated.rs index c97b848bd3..73a9fa0ac3 100644 --- a/tee-worker/identity/litentry/core/native-task/receiver/src/trusted_call_authenticated.rs +++ b/tee-worker/identity/litentry/core/native-task/receiver/src/trusted_call_authenticated.rs @@ -14,27 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . +use crate::authentication_utils::OAuth2Data; use alloc::string::String; use codec::{Decode, Encode}; use ita_stf::{LitentryMultiSignature, TrustedCall}; use itp_stf_primitives::traits::TrustedCallVerification; use itp_types::parentchain::Index as ParentchainIndex; -use lc_identity_verification::VerificationCodeStore; -use lc_omni_account::InMemoryStore as OmniAccountStore; -use litentry_hex_utils::hex_encode; use litentry_primitives::{Identity, ShardIdentifier}; use sp_core::{ - blake2_256, crypto::{AccountId32 as AccountId, UncheckedFrom}, ed25519, }; -type VerificationCode = String; +pub type VerificationCode = String; #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] pub enum TCAuthentication { Web3(LitentryMultiSignature), Email(VerificationCode), + OAuth2(OAuth2Data), } #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] @@ -85,47 +83,3 @@ impl TrustedCallVerification for TrustedCallAuthenticated { self.call.metric_name() } } - -pub fn verify_tca_web3_authentication( - signature: &LitentryMultiSignature, - call: &TrustedCall, - nonce: ParentchainIndex, - mrenclave: &[u8; 32], - shard: &ShardIdentifier, -) -> bool { - let mut payload = call.encode(); - payload.append(&mut nonce.encode()); - payload.append(&mut mrenclave.encode()); - payload.append(&mut shard.encode()); - - // The signature should be valid in either case: - // 1. blake2_256(payload) - // 2. Signature Prefix + blake2_256(payload) - - let hashed = blake2_256(&payload); - - let prettified_msg_hash = call.signature_message_prefix() + &hex_encode(&hashed); - let prettified_msg_hash = prettified_msg_hash.as_bytes(); - - // Most common signatures variants by clients are verified first (4 and 2). - signature.verify(prettified_msg_hash, call.sender_identity()) - || signature.verify(&hashed, call.sender_identity()) -} - -pub fn verify_tca_email_authentication(call: &TrustedCall, verification_code: String) -> bool { - let identity_hash = call.sender_identity().hash(); - let omni_account = extract_omni_account_from_call(call); - match VerificationCodeStore::get(&omni_account, identity_hash) { - Ok(Some(code)) => code == verification_code, - _ => false, - } -} - -fn extract_omni_account_from_call(call: &TrustedCall) -> AccountId { - let member_identity = call.sender_identity(); - if let Ok(Some(account_id)) = OmniAccountStore::get_omni_account(member_identity.hash()) { - account_id - } else { - member_identity.to_omni_account() - } -} diff --git a/tee-worker/identity/litentry/core/native-task/receiver/src/types.rs b/tee-worker/identity/litentry/core/native-task/receiver/src/types.rs index c42132cae3..75b3812f91 100644 --- a/tee-worker/identity/litentry/core/native-task/receiver/src/types.rs +++ b/tee-worker/identity/litentry/core/native-task/receiver/src/types.rs @@ -16,7 +16,6 @@ use alloc::{string::String, sync::Arc, vec::Vec}; use codec::{Decode, Encode}; -use ita_sgx_runtime::Hash; use ita_stf::{ trusted_call_result::{RequestVcErrorDetail, RequestVcResultOrError}, Getter, TrustedCallSigned, @@ -36,13 +35,13 @@ use itp_top_pool_author::traits::AuthorApi as AuthorApiTrait; use lc_data_providers::DataProviderConfig; use lc_dynamic_assertion::AssertionLogicRepository; use lc_evm_dynamic_assertions::AssertionRepositoryItem; -use sp_core::{ed25519::Pair as Ed25519Pair, H160}; +use sp_core::{ed25519::Pair as Ed25519Pair, H160, H256}; pub struct NativeTaskContext where ShieldingKeyRepository: AccessKey + Send + Sync + 'static, ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt, - AA: AuthorApiTrait + Send + Sync + 'static, + AA: AuthorApiTrait + Send + Sync + 'static, SES: StfEnclaveSigningTrait + Send + Sync + 'static, OA: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + 'static, EF: CreateExtrinsics + Send + Sync + 'static, @@ -69,7 +68,7 @@ impl where ShieldingKeyRepository: AccessKey + Send + Sync + 'static, ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt, - AA: AuthorApiTrait + Send + Sync + 'static, + AA: AuthorApiTrait + Send + Sync + 'static, SES: StfEnclaveSigningTrait + Send + Sync + 'static, OA: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + 'static, EF: CreateExtrinsics + Send + Sync + 'static,