diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -22,7 +22,7 @@ database::{DatabaseClient, Error as DBError, KeyPayload}, id::generate_uuid, nonce::generate_nonce_data, - reserved_users::validate_signed_keyserver_message, + reserved_users::validate_signed_account_ownership_message, siwe::parse_and_verify_siwe_message, token::{AccessTokenData, AuthType}, }; @@ -202,7 +202,7 @@ .await .map_err(handle_db_error)?; if username_in_reserved_usernames_table { - validate_signed_keyserver_message( + validate_signed_account_ownership_message( &message.username, &message.keyserver_message, &message.keyserver_signature, diff --git a/services/identity/src/reserved_users.rs b/services/identity/src/reserved_users.rs --- a/services/identity/src/reserved_users.rs +++ b/services/identity/src/reserved_users.rs @@ -2,55 +2,75 @@ use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; -use ed25519_dalek::Signature; +use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde::Deserialize; use tonic::Status; +use crate::config::CONFIG; + #[derive(Deserialize)] -struct AccountOwnershipMessage { +struct ReservedUsernameMessage { statement: String, username: String, issued_at: String, } -pub fn validate_signed_keyserver_message( - username: &str, +fn validate_message( keyserver_message: &str, keyserver_signature: &str, -) -> Result<(), Status> { - // Deserialize the keyserver message - let deserialized_message: AccountOwnershipMessage = + statement: &[u8], +) -> Result { + let deserialized_message: ReservedUsernameMessage = serde_json::from_str(keyserver_message) .map_err(|_| Status::invalid_argument("message format invalid"))?; - let message_statement = deserialized_message.statement; - if !constant_time_eq( - message_statement.as_bytes(), - b"This user is the owner of the following username", - ) { + if !constant_time_eq(deserialized_message.statement.as_bytes(), statement) { return Err(Status::invalid_argument("message invalid")); } - let message_username = deserialized_message.username; + let issued_at: DateTime = deserialized_message .issued_at .parse() .map_err(|_| Status::invalid_argument("message format invalid"))?; - // Check that the username in the gRPC message matches the username in the keyserver_message string - if message_username != username { - return Err(Status::invalid_argument("message invalid")); - } - - // Check that no more than 5 seconds have passed since the timestamp in the keyserver_message string let now = Utc::now(); if (now - issued_at).num_seconds() > 5 { return Err(Status::invalid_argument("message invalid")); } - let _signature = Signature::from_str(keyserver_signature) - .map_err(|_| Status::invalid_argument("message invalid"))?; + let signature = Signature::from_str(keyserver_signature) + .map_err(|_| Status::invalid_argument("signature invalid"))?; + + let public_key_string = CONFIG + .keyserver_public_key + .clone() + .ok_or(Status::failed_precondition("missing key"))?; + + let public_key: PublicKey = + PublicKey::from_bytes(public_key_string.as_bytes()) + .map_err(|_| Status::failed_precondition("malformed key"))?; + + public_key + .verify(keyserver_message.as_bytes(), &signature) + .map_err(|_| Status::permission_denied("verification failed"))?; - // TODO: verify the signature once ashoat's keyserver is registered + Ok(deserialized_message) +} + +pub fn validate_signed_account_ownership_message( + username: &str, + keyserver_message: &str, + keyserver_signature: &str, +) -> Result<(), Status> { + let deserialized_message = validate_message( + keyserver_message, + keyserver_signature, + b"This user is the owner of the following username", + )?; + + if deserialized_message.username != username { + return Err(Status::invalid_argument("message invalid")); + } Ok(()) }