Skip to content

Commit

Permalink
Adding support for auth token authentication for trusted calls (#3192)
Browse files Browse the repository at this point in the history
  • Loading branch information
silva-fj authored Dec 20, 2024
1 parent fb55f92 commit 5c1a358
Show file tree
Hide file tree
Showing 15 changed files with 386 additions and 17 deletions.
4 changes: 4 additions & 0 deletions local-setup/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ MAGIC_CRAFT_API_KEY=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# The following key/token are MANDATORY to give when running worker.
# use a secure 256-bit key for JWT token generation.
JWT_SECRET=

# The followings are default value.
# Can be skipped; or overwrite within non-production mode.
TWITTER_OFFICIAL_URL=http://localhost:19527
Expand Down
16 changes: 16 additions & 0 deletions tee-worker/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tee-worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ members = [
"identity/litentry/core/data-providers",
"identity/litentry/core/vc-task/sender",
"identity/litentry/core/vc-task/receiver",
"identity/litentry/core/authentication",
"identity/litentry/core/native-task/sender",
"identity/litentry/core/native-task/receiver",
"identity/litentry/core/identity-verification",
Expand Down Expand Up @@ -284,6 +285,7 @@ lc-vc-task-receiver = { path = "identity/litentry/core/vc-task/receiver", defaul
lc-omni-account = { path = "identity/litentry/core/omni-account", default-features = false }
lc-native-task-sender = { path = "identity/litentry/core/native-task/sender", default-features = false }
lc-native-task-receiver = { path = "identity/litentry/core/native-task/receiver", default-features = false }
lc-authentication = { path = "identity/litentry/core/authentication", default-features = false }

itc-peer-top-broadcaster = { path = "identity/core/peer-top-broadcaster", default-features = false }
itc-rpc-server = { path = "identity/core/rpc-server", default-features = false }
Expand Down
3 changes: 3 additions & 0 deletions tee-worker/identity/app-libs/stf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sp-std = { workspace = true }

# litentry
itp-node-api-metadata-provider = { workspace = true }
lc-authentication = { path = "../../litentry/core/authentication", default-features = false }
lc-stf-task-sender = { path = "../../litentry/core/stf-task/sender", default-features = false }
litentry-hex-utils = { workspace = true }
litentry-macros = { workspace = true }
Expand All @@ -56,6 +57,7 @@ sgx = [
"itp-node-api/sgx",
"litentry-primitives/sgx",
"lc-stf-task-sender/sgx",
"lc-authentication/sgx",
"itp-node-api-metadata-provider/sgx",
]
std = [
Expand All @@ -79,6 +81,7 @@ std = [
"sp-io/std",
"litentry-primitives/std",
"lc-stf-task-sender/std",
"lc-authentication/std",
"itp-node-api-metadata-provider/std",
]
test = []
Expand Down
6 changes: 6 additions & 0 deletions tee-worker/identity/app-libs/stf/src/trusted_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use itp_types::{
Moment, OpaqueCall, H256,
};
use itp_utils::stringify::account_id_to_string;
use lc_authentication::AuthOptions;
use litentry_hex_utils::hex_encode;
pub use litentry_primitives::{
aes_encrypt_default, all_evm_web3networks, all_substrate_web3networks, AesOutput, Assertion,
Expand Down Expand Up @@ -149,6 +150,8 @@ pub enum TrustedCall {
remove_accounts(Identity, Vec<Identity>),
#[codec(index = 30)]
publicize_account(Identity, Identity),
#[codec(index = 31)]
request_auth_token(Identity, AuthOptions),

// original integritee trusted calls, starting from index 50
#[codec(index = 50)]
Expand Down Expand Up @@ -244,6 +247,7 @@ impl TrustedCall {
Self::add_account(sender_identity, ..) => sender_identity,
Self::remove_accounts(sender_identity, ..) => sender_identity,
Self::publicize_account(sender_identity, ..) => sender_identity,
Self::request_auth_token(sender_identity, ..) => sender_identity,
}
}

Expand All @@ -262,6 +266,7 @@ impl TrustedCall {
Self::add_account(..) => "add_account",
Self::remove_accounts(..) => "remove_account",
Self::publicize_account(..) => "publicize_account",
Self::request_auth_token(..) => "request_auth_token",
_ => "unsupported_trusted_call",
}
}
Expand Down Expand Up @@ -920,6 +925,7 @@ where
| TrustedCall::create_account_store(..)
| TrustedCall::add_account(..)
| TrustedCall::remove_accounts(..)
| TrustedCall::request_auth_token(..)
| TrustedCall::publicize_account(..) => {
error!("please use author_submitNativeRequest instead");
Ok(TrustedCallResult::Empty)
Expand Down
16 changes: 16 additions & 0 deletions tee-worker/identity/enclave-runtime/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions tee-worker/identity/litentry/core/authentication/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
authors = ["Trust Computing GmbH <[email protected]>"]
edition = "2021"
name = "lc-authentication"
version = "0.1.0"

[dependencies]
base64 = { version = "0.22", default-features = false, features = ["alloc"] } # a newer base64
codec = { package = "parity-scale-codec", workspace = true }
parentchain-primitives = { workspace = true }
ring = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

sgx_tstd = { workspace = true, features = ["net", "thread"], optional = true }

[features]
default = ["std"]
std = [
"codec/std",
"serde/std",
"serde_json/std",
"ring/std",
"parentchain-primitives/std",
]
sgx = [
"sgx_tstd",
]
173 changes: 173 additions & 0 deletions tee-worker/identity/litentry/core/authentication/src/jwt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2020-2024 Trust Computing GmbH.
// This file is part of Litentry.
//
// Litentry is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Litentry is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Litentry. If not, see <https://www.gnu.org/licenses/>.

use crate::{AuthOptions, BlockNumber};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use ring::hmac;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
pub enum Error {
InvalidToken,
InvalidSignature,
ExpiredToken,
InvalidSubject,
Base64DecodeError,
JsonError,
}

impl From<base64::DecodeError> for Error {
fn from(_: base64::DecodeError) -> Self {
Error::Base64DecodeError
}
}

#[derive(Serialize, Deserialize)]
pub struct Header {
alg: String,
typ: String,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Payload {
sub: String,
exp: BlockNumber,
}

impl Payload {
pub fn new(sub: String, options: AuthOptions) -> Self {
Self { sub, exp: options.expires_at }
}
}

pub struct Validation {
pub sub: String,
pub current_block: BlockNumber,
}

impl Validation {
pub fn new(sub: String, current_block: BlockNumber) -> Self {
Self { sub, current_block }
}

pub fn validate(&self, payload: &Payload) -> Result<(), Error> {
if self.sub != payload.sub {
return Err(Error::InvalidSubject)
}

if self.current_block > payload.exp {
return Err(Error::ExpiredToken)
}

Ok(())
}
}

fn base64_encode<T: AsRef<[u8]>>(input: T) -> String {
BASE64_URL_SAFE_NO_PAD.encode(input)
}

fn base64_decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, base64::DecodeError> {
BASE64_URL_SAFE_NO_PAD.decode(input)
}

pub fn create(payload: &Payload, secret: &[u8]) -> Result<String, Error> {
let header = Header { alg: "HS256".to_string(), typ: "JWT".to_string() };
let encoded_header =
base64_encode(&serde_json::to_string(&header).map_err(|_| Error::JsonError)?);
let encoded_payload =
base64_encode(&serde_json::to_string(&payload).map_err(|_| Error::JsonError)?);
let data = [encoded_header, encoded_payload].join(".");
let key = hmac::Key::new(hmac::HMAC_SHA256, secret);
let signature = hmac::sign(&key, data.as_bytes());
let encoded_signature = base64_encode(signature);

Ok([data, encoded_signature].join("."))
}

pub fn verify(jwt: &str, secret: &[u8], validation: Validation) -> Result<(), Error> {
let parts: Vec<&str> = jwt.split('.').collect();
if parts.len() != 3 {
return Err(Error::InvalidToken)
}

let data = [parts[0], parts[1]].join(".");
let key = hmac::Key::new(hmac::HMAC_SHA256, secret);
let decoded_signature = base64_decode(parts[2])?;

hmac::verify(&key, data.as_bytes(), &decoded_signature).map_err(|_| Error::InvalidSignature)?;
let payload = base64_decode(parts[1])?;
let payload: Payload = serde_json::from_slice(&payload).map_err(|_| Error::JsonError)?;

validation.validate(&payload)
}

pub fn decode(jwt: &str) -> Result<Payload, Error> {
let parts: Vec<&str> = jwt.split('.').collect();
if parts.len() != 3 {
return Err(Error::InvalidToken)
}
let payload = base64_decode(parts[1])?;
let payload: Payload = serde_json::from_slice(&payload).map_err(|_| Error::JsonError)?;

Ok(payload)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_jwt() {
let secret = "secret".as_bytes();
let payload = Payload::new("subject".to_string(), AuthOptions { expires_at: 10 });
let jwt = create(&payload, secret).unwrap();

assert!(verify(&jwt, secret, Validation::new("subject".to_string(), 5)).is_ok());

let decoded_payload = decode(&jwt).unwrap();
assert_eq!(decoded_payload, payload);
}

#[test]
fn test_jwt_exp_validation() {
let secret = "secret".as_bytes();
let payload = Payload::new("subject".to_string(), AuthOptions { expires_at: 10 });
let jwt = create(&payload, secret).unwrap();

let current_block = 12;
let result = verify(&jwt, secret, Validation::new("subject".to_string(), current_block));

assert_eq!(result, Err(Error::ExpiredToken));
}

#[test]
fn test_jwt_sub_validation() {
let secret = "secret".as_bytes();
let payload = Payload::new("subject".to_string(), AuthOptions { expires_at: 10 });
let jwt = create(&payload, secret).unwrap();

let current_block = 5;
let result =
verify(&jwt, secret, Validation::new("other-subject".to_string(), current_block));

assert_eq!(result, Err(Error::InvalidSubject));
}
}
Loading

0 comments on commit 5c1a358

Please sign in to comment.