diff --git a/services/identity/Cargo.lock b/services/identity/Cargo.lock --- a/services/identity/Cargo.lock +++ b/services/identity/Cargo.lock @@ -119,6 +119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ + "block-padding", "generic-array", ] @@ -131,6 +132,12 @@ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.9.1" @@ -266,6 +273,18 @@ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -299,6 +318,12 @@ "zeroize", ] +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" + [[package]] name = "derive_more" version = "0.99.17" @@ -364,12 +389,39 @@ "syn", ] +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der", + "elliptic-curve", + "hmac", + "signature", +] + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" +dependencies = [ + "crypto-bigint", + "ff", + "generic-array", + "group", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "fastrand" version = "1.7.0" @@ -379,6 +431,16 @@ "instant", ] +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -529,6 +591,17 @@ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "h2" version = "0.3.13" @@ -706,7 +779,9 @@ "rusoto_core", "rusoto_dynamodb", "sha2", + "siwe", "tokio", + "tokio-stream", "tonic", "tonic-build", "tracing", @@ -732,6 +807,15 @@ "cfg-if", ] +[[package]] +name = "iri-string" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0f7638c1e223529f1bfdc48c8b133b9e0b434094d1d28473161ee48b235f78" +dependencies = [ + "nom", +] + [[package]] name = "itertools" version = "0.10.3" @@ -756,6 +840,24 @@ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha3", +] + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + [[package]] name = "lazy_static" version = "1.4.0" @@ -794,6 +896,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.8.2" @@ -841,6 +949,16 @@ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.3.7" @@ -1393,6 +1511,18 @@ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1417,6 +1547,32 @@ "libc", ] +[[package]] +name = "signature" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] + +[[package]] +name = "siwe" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f2d8ae2d4ae58df46e173aa496562ea857ac6a4f0d435ed30fcd19da0aaa79" +dependencies = [ + "chrono", + "hex", + "http", + "iri-string", + "k256", + "rand", + "sha3", + "thiserror", +] + [[package]] name = "slab" version = "0.4.6" @@ -1594,9 +1750,9 @@ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", "pin-project-lite", diff --git a/services/identity/Cargo.toml b/services/identity/Cargo.toml --- a/services/identity/Cargo.toml +++ b/services/identity/Cargo.toml @@ -8,6 +8,7 @@ prost = "0.9" futures-core = "0.3" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +tokio-stream = "0.1.9" opaque-ke = { version = "1.2.0", features = ["std"] } argon2 = "0.3" curve25519-dalek = "3" @@ -23,6 +24,7 @@ rand = "0.8" bytes = "1.1" constant_time_eq = "0.2.2" +siwe = "0.3" [build-dependencies] tonic-build = "0.6" diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -13,6 +13,7 @@ use crate::opaque::Cipher; use crate::token::{AccessToken, AuthType}; +#[derive(Clone)] pub struct DatabaseClient { client: DynamoDbClient, } diff --git a/services/identity/src/service.rs b/services/identity/src/service.rs --- a/services/identity/src/service.rs +++ b/services/identity/src/service.rs @@ -1,12 +1,18 @@ +use chrono::Utc; use constant_time_eq::constant_time_eq; use futures_core::Stream; +use rand::rngs::OsRng; use rusoto_core::RusotoError; -use rusoto_dynamodb::GetItemError; +use rusoto_dynamodb::{GetItemError, PutItemError}; +use siwe::Message; use std::pin::Pin; +use tokio::sync::mpsc; +use tokio_stream::{wrappers::ReceiverStream, StreamExt}; use tonic::{Request, Response, Status}; use tracing::{error, info, instrument}; use crate::database::DatabaseClient; +use crate::token::{AccessToken, AuthType}; use crate::{config::Config, database::Error}; pub use proto::identity_service_server::IdentityServiceServer; @@ -16,6 +22,13 @@ VerifyUserTokenResponse, }; +use self::proto::login_response::Data; +use self::proto::WalletLoginResponse; +use self::proto::{ + login_request::Data::PakeLoginRequest, + login_request::Data::WalletLoginRequest, +}; + mod proto { tonic::include_proto!("identity"); } @@ -45,12 +58,123 @@ type LoginUserStream = Pin> + Send + 'static>>; + #[instrument(skip(self))] async fn login_user( &self, request: Request>, ) -> Result, Status> { - println!("Got a login request: {:?}", request); - unimplemented!() + let mut in_stream = request.into_inner(); + let (tx, rx) = mpsc::channel(1); + let client = self.client.clone(); + tokio::spawn(async move { + while let Some(message) = in_stream.next().await { + if let Ok(login_request) = message { + if let Some(data) = login_request.data { + match data { + WalletLoginRequest(req) => { + let siwe_message: Message = match req.message.parse() { + Ok(m) => m, + Err(e) => { + error!("Failed to parse SIWE message: {}", e); + if let Err(e) = tx + .send(Err(Status::invalid_argument("invalid message"))) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + }; + if let Err(e) = siwe_message.verify( + match req.signature.try_into() { + Ok(s) => s, + Err(e) => { + error!("Conversion to SIWE signature failed: {:?}", e); + break; + } + }, + None, + None, + Some(&Utc::now()), + ) { + error!("Signature verification failed for user {} on device {}: {}", req.user_id, req.device_id, e); + if let Err(e) = tx + .send(Err(Status::unauthenticated( + "message not authenticated", + ))) + .await + { + error!("Response was dropped: {}", e); + } + break; + } else { + let token = AccessToken::new( + req.user_id, + req.device_id, + AuthType::Wallet, + &mut OsRng, + ); + match client.put_token(token.clone()).await { + Ok(_) => { + if let Err(e) = tx + .send(Ok(LoginResponse { + data: Some(Data::WalletLoginResponse( + WalletLoginResponse { + token: token.token.into_bytes(), + }, + )), + })) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + Err(Error::RusotoPut(RusotoError::Service( + PutItemError::ResourceNotFound(_), + ))) + | Err(Error::RusotoPut(RusotoError::Credentials(_))) => { + if let Err(e) = tx + .send(Err(Status::failed_precondition( + "internal error", + ))) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + Err(Error::RusotoPut(_)) => { + if let Err(e) = + tx.send(Err(Status::unavailable("please retry"))).await + { + error!("Response was dropped: {}", e); + } + break; + } + Err(e) => { + error!("Encountered an unexpected error: {}", e); + if let Err(e) = tx + .send(Err(Status::failed_precondition( + "unexpected error", + ))) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + } + } + } + PakeLoginRequest(_) => unimplemented!(), + } + } + } + } + }); + let out_stream = ReceiverStream::new(rx); + Ok(Response::new(Box::pin(out_stream) as Self::LoginUserStream)) } #[instrument(skip(self))] diff --git a/services/identity/src/token.rs b/services/identity/src/token.rs --- a/services/identity/src/token.rs +++ b/services/identity/src/token.rs @@ -4,11 +4,13 @@ CryptoRng, Rng, }; +#[derive(Clone)] pub enum AuthType { Password, Wallet, } +#[derive(Clone)] pub struct AccessToken { pub user_id: String, pub device_id: String,