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::{AccessTokenData, 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,9 +1,14 @@ +use chrono::Utc; use constant_time_eq::constant_time_eq; use futures_core::Stream; +use rand::rngs::OsRng; use rand::{CryptoRng, Rng}; use rusoto_core::RusotoError; 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}; @@ -14,11 +19,14 @@ pub use proto::identity_service_server::IdentityServiceServer; use proto::{ identity_service_server::IdentityService, + login_request::Data::PakeLoginRequest, + login_request::Data::WalletLoginRequest, login_response::Data::PakeLoginResponse, login_response::Data::WalletLoginResponse, pake_login_response::Data::AccessToken, LoginRequest, LoginResponse, PakeLoginResponse as PakeLoginResponseStruct, RegistrationRequest, RegistrationResponse, VerifyUserTokenRequest, VerifyUserTokenResponse, + WalletLoginRequest as WalletLoginRequestStruct, WalletLoginResponse as WalletLoginResponseStruct, }; @@ -51,12 +59,65 @@ 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 { + let mut num_messages_received = 0; + while let Some(message) = in_stream.next().await { + match message { + Ok(login_request) => { + if let Some(data) = login_request.data { + match data { + WalletLoginRequest(req) => { + if let Err(e) = tx + .send( + wallet_login_helper( + client, + req, + &mut OsRng, + num_messages_received, + ) + .await, + ) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + PakeLoginRequest(_) => unimplemented!(), + } + } else { + error!("Received empty login request"); + if let Err(e) = tx + .send(Err(Status::invalid_argument("invalid message"))) + .await + { + error!("Response was dropped: {}", e); + } + break; + } + } + Err(e) => { + error!("Received an unexpected error: {}", e); + if let Err(e) = tx.send(Err(Status::unknown("unknown error"))).await + { + error!("Response was dropped: {}", e); + } + break; + } + } + num_messages_received += 1; + } + }); + let out_stream = ReceiverStream::new(rx); + Ok(Response::new(Box::pin(out_stream) as Self::LoginUserStream)) } #[instrument(skip(self))] @@ -134,3 +195,71 @@ } } } + +fn parse_and_verify_siwe_message( + user_id: &str, + device_id: &str, + siwe_message: &str, + siwe_signature: Vec, +) -> Result<(), Status> { + let siwe_message: Message = match siwe_message.parse() { + Ok(m) => m, + Err(e) => { + error!("Failed to parse SIWE message: {}", e); + return Err(Status::invalid_argument("invalid message")); + } + }; + match siwe_message.verify( + match siwe_signature.try_into() { + Ok(s) => s, + Err(e) => { + error!("Conversion to SIWE signature failed: {:?}", e); + return Err(Status::invalid_argument("invalid message")); + } + }, + None, + None, + Some(&Utc::now()), + ) { + Err(e) => { + error!( + "Signature verification failed for user {} on device {}: {}", + user_id, device_id, e + ); + Err(Status::unauthenticated("message not authenticated")) + } + Ok(_) => Ok(()), + } +} + +async fn wallet_login_helper( + client: DatabaseClient, + wallet_login_request: WalletLoginRequestStruct, + rng: &mut (impl Rng + CryptoRng), + num_messages_received: u8, +) -> Result { + if num_messages_received != 0 { + error!("Too many messages received in stream, aborting"); + return Err(Status::aborted("please retry")); + } + match parse_and_verify_siwe_message( + &wallet_login_request.user_id, + &wallet_login_request.device_id, + &wallet_login_request.siwe_message, + wallet_login_request.siwe_signature, + ) { + Ok(()) => Ok(LoginResponse { + data: Some(WalletLoginResponse(WalletLoginResponseStruct { + access_token: put_token_helper( + client, + AuthType::Wallet, + &wallet_login_request.user_id, + &wallet_login_request.device_id, + rng, + ) + .await?, + })), + }), + Err(e) => Err(e), + } +}