From 8f76d9320dd84e3792ff6716f0adeb6054382ef0 Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Sat, 24 Jun 2023 09:48:28 -0700 Subject: [PATCH] support EIP1271 login --- Cargo.lock | 192 ++---------------- web3_proxy/Cargo.toml | 3 +- web3_proxy/src/errors.rs | 30 +-- web3_proxy/src/frontend/admin.rs | 55 ++--- .../src/frontend/users/authentication.rs | 62 ++---- web3_proxy/src/user_token.rs | 7 + 6 files changed, 76 insertions(+), 273 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5012ff18..978f4c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,12 +570,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -1027,7 +1021,7 @@ dependencies = [ "digest 0.10.7", "getrandom", "hmac", - "k256 0.13.1", + "k256", "lazy_static", "serde", "sha2 0.10.6", @@ -1327,18 +1321,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.2" @@ -1465,16 +1447,6 @@ dependencies = [ "pem-rfc7468", ] -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid 0.9.2", - "zeroize", -] - [[package]] name = "der" version = "0.7.6" @@ -1590,18 +1562,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - [[package]] name = "ecdsa" version = "0.16.7" @@ -1610,9 +1570,9 @@ checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" dependencies = [ "der 0.7.6", "digest 0.10.7", - "elliptic-curve 0.13.5", - "rfc6979 0.4.0", - "signature 2.1.0", + "elliptic-curve", + "rfc6979", + "signature", "spki 0.7.2", ] @@ -1622,40 +1582,21 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "rand_core", - "sec1 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ - "base16ct 0.2.0", + "base16ct", "crypto-bigint 0.5.2", "digest 0.10.7", - "ff 0.13.0", + "ff", "generic-array", - "group 0.13.0", + "group", "pkcs8 0.10.2", "rand_core", - "sec1 0.7.2", + "sec1", "subtle", "zeroize", ] @@ -1693,7 +1634,7 @@ dependencies = [ "base64 0.13.1", "bytes", "hex", - "k256 0.13.1", + "k256", "log", "rand", "rlp", @@ -1928,11 +1869,11 @@ dependencies = [ "bytes", "cargo_metadata 0.15.4", "chrono", - "elliptic-curve 0.13.5", + "elliptic-curve", "ethabi", "generic-array", "hex", - "k256 0.13.1", + "k256", "num_enum 0.6.1", "once_cell", "open-fastrlp", @@ -2036,7 +1977,7 @@ dependencies = [ "async-trait", "coins-bip32", "coins-bip39", - "elliptic-curve 0.13.5", + "elliptic-curve", "eth-keystore", "ethers-core", "hex", @@ -2145,16 +2086,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core", - "subtle", -] - [[package]] name = "ff" version = "0.13.0" @@ -2482,24 +2413,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558b88954871f5e5b2af0e62e2e176c8bde7a6c2c4ed41b13d138d96da2e2cbd" -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core", - "subtle", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff", "rand_core", "subtle", ] @@ -3049,9 +2969,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0586ad318a04c73acdbad33f67969519b5452c80770c4c72059a686da48a7e" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" dependencies = [ "memchr", "serde", @@ -3111,19 +3031,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "k256" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" -dependencies = [ - "cfg-if", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.6", - "sha3", -] - [[package]] name = "k256" version = "0.13.1" @@ -3131,11 +3038,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa 0.16.7", - "elliptic-curve 0.13.5", + "ecdsa", + "elliptic-curve", "once_cell", "sha2 0.10.6", - "signature 2.1.0", + "signature", ] [[package]] @@ -4169,16 +4076,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -4720,17 +4617,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -5261,27 +5147,13 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - [[package]] name = "sec1" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ - "base16ct 0.2.0", + "base16ct", "der 0.7.6", "generic-array", "pkcs8 0.10.2", @@ -5642,16 +5514,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core", -] - [[package]] name = "signature" version = "2.1.0" @@ -5677,13 +5539,13 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "siwe" version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6d1f422a568af1e98db37c6d0427c7218459ccac39218fd15a51a34d3933af" +source = "git+https://github.com/llamanodes/siwe-rs?rev=013be5204ff1c85778ce21619f4b677a003db8a1#013be5204ff1c85778ce21619f4b677a003db8a1" dependencies = [ + "ethers", "hex", "http", "iri-string", - "k256 0.11.6", + "k256", "rand", "serde", "sha3", @@ -5802,16 +5664,6 @@ dependencies = [ "der 0.5.1", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.2" diff --git a/web3_proxy/Cargo.toml b/web3_proxy/Cargo.toml index ce33f684..e884569a 100644 --- a/web3_proxy/Cargo.toml +++ b/web3_proxy/Cargo.toml @@ -25,7 +25,7 @@ redis-rate-limiter = { path = "../redis-rate-limiter" } #ethers = { git = "https://github.com/llamanodes/ethers-rs/", rev = "eb68f5d60850008cd302762bd3a5a4bdcfecc713", default-features = false, features = ["rustls", "ws"] } influxdb2 = { git = "https://github.com/llamanodes/influxdb2", features = ["rustls"], rev = "2d125128696a29d7e0b9abc052c928937e7c0579" } influxdb2-structmap = { git = "https://github.com/llamanodes/influxdb2/", rev = "2d125128696a29d7e0b9abc052c928937e7c0579"} -#siwe = { git = "https://github.com/llamanodes/siwe-rs", rev = "bef5449b5dd8beb4e9fc697f09cd6dd11ba8f6e6", features = ["ethers", "serde"] } +siwe = { git = "https://github.com/llamanodes/siwe-rs", rev = "013be5204ff1c85778ce21619f4b677a003db8a1", features = ["ethers", "serde"] } # TODO: regex has several "perf" features that we might want to use # TODO: make sure this uuid version matches sea-orm. PR to put this in their prelude @@ -89,7 +89,6 @@ sentry-tracing = "0.31.5" serde = { version = "1.0.164" } serde_json = { version = "1.0.99", default-features = false, features = ["raw_value"] } serde_prometheus = "0.2.3" -siwe = { version = "0.5.2", features = ["serde"] } strum = { version = "0.25.0", features = ["derive"] } time = { version = "0.3.22", features = ["serde-well-known"] } tokio = { version = "1.28.2", features = ["full", "tracing"] } diff --git a/web3_proxy/src/errors.rs b/web3_proxy/src/errors.rs index 774fccbc..bb4a910d 100644 --- a/web3_proxy/src/errors.rs +++ b/web3_proxy/src/errors.rs @@ -21,6 +21,7 @@ use reqwest::header::ToStrError; use rust_decimal::Error as DecimalError; use serde::Serialize; use serde_json::value::RawValue; +use siwe::VerificationError; use std::sync::Arc; use std::{borrow::Cow, net::IpAddr}; use tokio::{sync::AcquireError, task::JoinError, time::Instant}; @@ -54,8 +55,6 @@ pub enum Web3ProxyError { Contract(ContractError), Database(DbErr), Decimal(DecimalError), - #[display(fmt = "{:#?}, {:#?}", _0, _1)] - EipVerificationFailed(Box, Box), EthersHttpClient(ethers::prelude::HttpClientError), EthersProvider(ethers::prelude::ProviderError), EthersWsClient(ethers::prelude::WsClientError), @@ -133,6 +132,7 @@ pub enum Web3ProxyError { SemaphoreAcquireError(AcquireError), SendAppStatError(flume::SendError), SerdeJson(serde_json::Error), + SiweVerification(VerificationError), /// simple way to return an error message to the user and an anyhow to our logs #[display(fmt = "{}, {}, {:?}", _0, _1, _2)] StatusCode(StatusCode, Cow<'static, str>, Option), @@ -148,7 +148,6 @@ pub enum Web3ProxyError { UserAgentNotAllowed(headers::UserAgent), UserIdZero, PaymentRequired, - VerificationError(siwe::VerificationError), WatchRecvError(tokio::sync::watch::error::RecvError), WatchSendError, WebsocketOnly, @@ -267,20 +266,12 @@ impl Web3ProxyError { }, ) } - Self::EipVerificationFailed(err_1, err_191) => { - trace!( - "EipVerificationFailed err_1={:#?} err2={:#?}", - err_1, - err_191 - ); + Self::SiweVerification(err) => { + trace!("Siwe Verification err={:#?}", err,); ( StatusCode::UNAUTHORIZED, JsonRpcErrorData { - message: format!( - "both the primary and eip191 verification failed: {:#?}; {:#?}", - err_1, err_191 - ) - .into(), + message: format!("siwe verification error: {:#?}", err).into(), code: StatusCode::UNAUTHORIZED.as_u16().into(), data: None, }, @@ -960,17 +951,6 @@ impl Web3ProxyError { }, ) } - Self::VerificationError(err) => { - trace!("VerificationError err={:#?}", err); - ( - StatusCode::BAD_REQUEST, - JsonRpcErrorData { - message: "verification error!".into(), - code: StatusCode::BAD_REQUEST.as_u16().into(), - data: None, - }, - ) - } Self::WatchRecvError(err) => { error!("WatchRecvError err={:#?}", err); ( diff --git a/web3_proxy/src/frontend/admin.rs b/web3_proxy/src/frontend/admin.rs index 9e554b64..02c158db 100644 --- a/web3_proxy/src/frontend/admin.rs +++ b/web3_proxy/src/frontend/admin.rs @@ -252,6 +252,16 @@ pub async fn admin_login_get( .db_replica() .web3_context("login requires a replica database")?; + // delete ALL expired rows. + let now = Utc::now(); + let delete_result = pending_login::Entity::delete_many() + .filter(pending_login::Column::ExpiresAt.lte(now)) + .exec(&db_conn) + .await?; + + // TODO: emit a stat? if this is high something weird might be happening + debug!("cleared expired pending_logins: {:?}", delete_result); + // Get the user that we want to imitate from the read-only database (their id ...) // TODO: Only get the id, not the whole user object ... let user = user::Entity::find() @@ -404,39 +414,16 @@ pub async fn admin_login_post( .parse() .web3_context("parsing siwe message")?; - // default options are fine. the message includes timestamp and domain and nonce - let verify_config = VerificationOpts::default(); + // mostly default options are fine. the message includes timestamp and domain and nonce + let verify_config = VerificationOpts { + rpc_provider: Some(app.internal_provider.clone()), + ..Default::default() + }; - let db_conn = app - .db_conn() - .web3_context("deleting expired pending logins requires a db")?; - - if let Err(err_1) = our_msg + our_msg .verify(&their_sig, &verify_config) .await - .web3_context("verifying signature against our local message") - { - // verification method 1 failed. try eip191 - if let Err(err_191) = our_msg - .verify_eip191(&their_sig) - .web3_context("verifying eip191 signature against our local message") - { - // delete ALL expired rows. - let now = Utc::now(); - let delete_result = pending_login::Entity::delete_many() - .filter(pending_login::Column::ExpiresAt.lte(now)) - .exec(&db_conn) - .await?; - - // TODO: emit a stat? if this is high something weird might be happening - debug!("cleared expired pending_logins: {:?}", delete_result); - - return Err(Web3ProxyError::EipVerificationFailed( - Box::new(err_1), - Box::new(err_191), - )); - } - } + .web3_context("verifying signature against our local message")?; let imitating_user_id = user_pending_login .imitating_user @@ -456,6 +443,10 @@ pub async fn admin_login_post( .await? .web3_context("admin address was not found!")?; + let db_conn = app + .db_conn() + .web3_context("deleting expired pending logins requires a db")?; + // Add a message that the admin has logged in // Note that the admin is trying to log in as this user let trail = admin_trail::ActiveModel { @@ -500,9 +491,7 @@ pub async fn admin_login_post( // add bearer to the database // expire in 2 days, because this is more critical (and shouldn't need to be done so long!) - let expires_at = Utc::now() - .checked_add_signed(chrono::Duration::days(2)) - .unwrap(); + let expires_at = Utc::now() + chrono::Duration::days(2); // TODO: Here, the bearer token should include a message // TODO: Above, make sure that the calling address is an admin! diff --git a/web3_proxy/src/frontend/users/authentication.rs b/web3_proxy/src/frontend/users/authentication.rs index f2654d46..bd4319d8 100644 --- a/web3_proxy/src/frontend/users/authentication.rs +++ b/web3_proxy/src/frontend/users/authentication.rs @@ -29,7 +29,7 @@ use std::ops::Add; use std::str::FromStr; use std::sync::Arc; use time::{Duration, OffsetDateTime}; -use tracing::{trace, warn}; +use tracing::{error, trace, warn}; use ulid::Ulid; /// `GET /user/login/:user_address` or `GET /user/login/:user_address/:message_eip` -- Start the "Sign In with Ethereum" (siwe) login flow. @@ -38,8 +38,7 @@ use ulid::Ulid; /// - eip191_bytes /// - eip191_hash /// - eip4361 (default) -/// -/// Coming soon: eip1271 +/// - eip1271 /// /// This is the initial entrypoint for logging in. Take the response from this endpoint and give it to your user's wallet for singing. POST the response to `/user/login`. /// @@ -103,6 +102,13 @@ pub async fn user_login_get( let db_conn = app.db_conn().web3_context("login requires a database")?; + // delete ALL expired rows. + let now = Utc::now(); + let _ = pending_login::Entity::delete_many() + .filter(pending_login::Column::ExpiresAt.lte(now)) + .exec(&db_conn) + .await?; + // massage types to fit in the database. sea-orm does not make this very elegant let uuid = Uuid::from_u128(nonce.into()); // we add 1 to expire_seconds just to be sure the database has the key for the full expiration_time @@ -234,11 +240,8 @@ pub async fn user_login_post( .db_replica() .web3_context("Getting database connection")?; - // massage type for the db - let login_nonce_uuid: Uuid = login_nonce.clone().into(); - let user_pending_login = pending_login::Entity::find() - .filter(pending_login::Column::Nonce.eq(login_nonce_uuid)) + .filter(pending_login::Column::Nonce.eq(Uuid::from(login_nonce.clone()))) .one(db_replica.as_ref()) .await .web3_context("database error while finding pending_login")? @@ -249,40 +252,17 @@ pub async fn user_login_post( .parse() .web3_context("parsing siwe message")?; - // default options are fine. the message includes timestamp and domain and nonce - let verify_config = VerificationOpts::default(); + // mostly default options are fine. the message includes timestamp and domain and nonce + let verify_config = VerificationOpts { + rpc_provider: Some(app.internal_provider.clone()), + ..Default::default() + }; // Check with both verify and verify_eip191 - if let Err(err_1) = our_msg + our_msg .verify(&their_sig, &verify_config) .await - .web3_context("verifying signature against our local message") - { - // verification method 1 failed. try eip191 - if let Err(err_191) = our_msg - .verify_eip191(&their_sig) - .web3_context("verifying eip191 signature against our local message") - { - let db_conn = app - .db_conn() - .web3_context("deleting expired pending logins requires a db")?; - - // delete ALL expired rows. - let now = Utc::now(); - let delete_result = pending_login::Entity::delete_many() - .filter(pending_login::Column::ExpiresAt.lte(now)) - .exec(&db_conn) - .await?; - - // TODO: emit a stat? if this is high something weird might be happening - trace!("cleared expired pending_logins: {:?}", delete_result); - - return Err(Web3ProxyError::EipVerificationFailed( - Box::new(err_1), - Box::new(err_191), - )); - } - } + .web3_context("verifying signature against our local message")?; // TODO: limit columns or load whole user? let caller = user::Entity::find() @@ -356,11 +336,7 @@ pub async fn user_login_post( .one(&txn) .await? .ok_or(Web3ProxyError::BadRequest( - format!( - "The referral_link you provided does not exist {}", - referral_code - ) - .into(), + "The referral_link you provided does not exist".into(), ))?; // Create a new item in the database, @@ -430,7 +406,7 @@ pub async fn user_login_post( .delete(&db_conn) .await { - warn!("Failed to delete nonce:{}: {}", login_nonce.0, err); + error!("Failed to delete nonce:{}: {}", login_nonce, err); } Ok(response) diff --git a/web3_proxy/src/user_token.rs b/web3_proxy/src/user_token.rs index e5043249..c5f14066 100644 --- a/web3_proxy/src/user_token.rs +++ b/web3_proxy/src/user_token.rs @@ -1,6 +1,7 @@ use axum::headers::authorization::Bearer; use migration::sea_orm::prelude::Uuid; use serde::Serialize; +use std::fmt; use std::str::FromStr; use ulid::Ulid; @@ -47,6 +48,12 @@ impl From for Uuid { } } +impl fmt::Display for UserBearerToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + impl TryFrom for UserBearerToken { type Error = ulid::DecodeError;