diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -155,7 +155,7 @@ +updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise; +uploadKeysForRegisteredDeviceAndLogIn: ( userID: string, - nonceChallengeResponse: SignedMessage, + signedNonce: SignedNonce, ) => Promise; +getFarcasterUsers: ( farcasterIDs: $ReadOnlyArray, @@ -222,13 +222,9 @@ export const signedDeviceListHistoryValidator: TList> = t.list(signedDeviceListValidator); -export type NonceChallenge = { +export type SignedNonce = { +nonce: string, -}; - -export type SignedMessage = { - +message: string, - +signature: string, + +nonceSignature: string, }; export const ONE_TIME_KEYS_NUMBER = 10; diff --git a/native/cpp/CommonCpp/NativeModules/CommRustModule.h b/native/cpp/CommonCpp/NativeModules/CommRustModule.h --- a/native/cpp/CommonCpp/NativeModules/CommRustModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommRustModule.h @@ -116,7 +116,8 @@ virtual jsi::Value uploadSecondaryDeviceKeysAndLogIn( jsi::Runtime &rt, jsi::String userID, - jsi::String challengeResponse, + jsi::String nonce, + jsi::String nonceSignature, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, @@ -129,7 +130,8 @@ jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, - jsi::String challengeResponse) override; + jsi::String nonce, + jsi::String nonceSignature) override; virtual jsi::Value findUserIDForWalletAddress( jsi::Runtime &rt, jsi::String walletAddress) override; diff --git a/native/cpp/CommonCpp/NativeModules/CommRustModule.cpp b/native/cpp/CommonCpp/NativeModules/CommRustModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommRustModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommRustModule.cpp @@ -549,7 +549,8 @@ jsi::Value CommRustModule::uploadSecondaryDeviceKeysAndLogIn( jsi::Runtime &rt, jsi::String userID, - jsi::String challengeResponse, + jsi::String nonce, + jsi::String nonceSignature, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, @@ -559,7 +560,8 @@ jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys) { auto userIDRust = jsiStringToRustString(userID, rt); - auto challengeResponseRust = jsiStringToRustString(challengeResponse, rt); + auto nonceRust = jsiStringToRustString(nonce, rt); + auto nonceSignatureRust = jsiStringToRustString(nonceSignature, rt); auto keyPayloadRust = jsiStringToRustString(keyPayload, rt); auto keyPayloadSignatureRust = jsiStringToRustString(keyPayloadSignature, rt); auto contentPrekeyRust = jsiStringToRustString(contentPrekey, rt); @@ -578,7 +580,8 @@ {promise, this->jsInvoker_, innerRt}); identityUploadSecondaryDeviceKeysAndLogIn( userIDRust, - challengeResponseRust, + nonceRust, + nonceSignatureRust, keyPayloadRust, keyPayloadSignatureRust, contentPrekeyRust, @@ -602,10 +605,12 @@ jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, - jsi::String challengeResponse) { + jsi::String nonce, + jsi::String nonceSignature) { auto userIDRust = jsiStringToRustString(userID, rt); auto deviceIDRust = jsiStringToRustString(deviceID, rt); - auto challengeResponseRust = jsiStringToRustString(challengeResponse, rt); + auto nonceRust = jsiStringToRustString(nonce, rt); + auto nonceSignatureRust = jsiStringToRustString(nonceSignature, rt); return createPromiseAsJSIValue( rt, [=, this](jsi::Runtime &innerRt, std::shared_ptr promise) { @@ -614,7 +619,11 @@ auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); identityLogInExistingDevice( - userIDRust, deviceIDRust, challengeResponseRust, currentID); + userIDRust, + deviceIDRust, + nonceRust, + nonceSignatureRust, + currentID); } catch (const std::exception &e) { error = e.what(); }; diff --git a/native/cpp/CommonCpp/_generated/rustJSI-generated.cpp b/native/cpp/CommonCpp/_generated/rustJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/rustJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/rustJSI-generated.cpp @@ -58,10 +58,10 @@ return static_cast(&turboModule)->updateDeviceList(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt)); } static jsi::Value __hostFunction_CommRustModuleSchemaCxxSpecJSI_uploadSecondaryDeviceKeysAndLogIn(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { - return static_cast(&turboModule)->uploadSecondaryDeviceKeysAndLogIn(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt), args[4].asString(rt), args[5].asString(rt), args[6].asString(rt), args[7].asString(rt), args[8].asObject(rt).asArray(rt), args[9].asObject(rt).asArray(rt)); + return static_cast(&turboModule)->uploadSecondaryDeviceKeysAndLogIn(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt), args[4].asString(rt), args[5].asString(rt), args[6].asString(rt), args[7].asString(rt), args[8].asString(rt), args[9].asObject(rt).asArray(rt), args[10].asObject(rt).asArray(rt)); } static jsi::Value __hostFunction_CommRustModuleSchemaCxxSpecJSI_logInExistingDevice(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { - return static_cast(&turboModule)->logInExistingDevice(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt)); + return static_cast(&turboModule)->logInExistingDevice(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt)); } static jsi::Value __hostFunction_CommRustModuleSchemaCxxSpecJSI_findUserIDForWalletAddress(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->findUserIDForWalletAddress(rt, args[0].asString(rt)); @@ -96,8 +96,8 @@ methodMap_["getKeyserverKeys"] = MethodMetadata {4, __hostFunction_CommRustModuleSchemaCxxSpecJSI_getKeyserverKeys}; methodMap_["getDeviceListForUser"] = MethodMetadata {5, __hostFunction_CommRustModuleSchemaCxxSpecJSI_getDeviceListForUser}; methodMap_["updateDeviceList"] = MethodMetadata {4, __hostFunction_CommRustModuleSchemaCxxSpecJSI_updateDeviceList}; - methodMap_["uploadSecondaryDeviceKeysAndLogIn"] = MethodMetadata {10, __hostFunction_CommRustModuleSchemaCxxSpecJSI_uploadSecondaryDeviceKeysAndLogIn}; - methodMap_["logInExistingDevice"] = MethodMetadata {3, __hostFunction_CommRustModuleSchemaCxxSpecJSI_logInExistingDevice}; + methodMap_["uploadSecondaryDeviceKeysAndLogIn"] = MethodMetadata {11, __hostFunction_CommRustModuleSchemaCxxSpecJSI_uploadSecondaryDeviceKeysAndLogIn}; + methodMap_["logInExistingDevice"] = MethodMetadata {4, __hostFunction_CommRustModuleSchemaCxxSpecJSI_logInExistingDevice}; methodMap_["findUserIDForWalletAddress"] = MethodMetadata {1, __hostFunction_CommRustModuleSchemaCxxSpecJSI_findUserIDForWalletAddress}; methodMap_["findUserIDForUsername"] = MethodMetadata {1, __hostFunction_CommRustModuleSchemaCxxSpecJSI_findUserIDForUsername}; methodMap_["getFarcasterUsers"] = MethodMetadata {1, __hostFunction_CommRustModuleSchemaCxxSpecJSI_getFarcasterUsers}; diff --git a/native/cpp/CommonCpp/_generated/rustJSI.h b/native/cpp/CommonCpp/_generated/rustJSI.h --- a/native/cpp/CommonCpp/_generated/rustJSI.h +++ b/native/cpp/CommonCpp/_generated/rustJSI.h @@ -35,8 +35,8 @@ virtual jsi::Value getKeyserverKeys(jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken, jsi::String keyserverID) = 0; virtual jsi::Value getDeviceListForUser(jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken, jsi::String userID, std::optional sinceTimestamp) = 0; virtual jsi::Value updateDeviceList(jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken, jsi::String updatePayload) = 0; - virtual jsi::Value uploadSecondaryDeviceKeysAndLogIn(jsi::Runtime &rt, jsi::String userID, jsi::String challengeResponse, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, jsi::String contentPrekeySignature, jsi::String notifPrekey, jsi::String notifPrekeySignature, jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys) = 0; - virtual jsi::Value logInExistingDevice(jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String challengeResponse) = 0; + virtual jsi::Value uploadSecondaryDeviceKeysAndLogIn(jsi::Runtime &rt, jsi::String userID, jsi::String nonce, jsi::String nonceSignature, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, jsi::String contentPrekeySignature, jsi::String notifPrekey, jsi::String notifPrekeySignature, jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys) = 0; + virtual jsi::Value logInExistingDevice(jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String nonce, jsi::String nonceSignature) = 0; virtual jsi::Value findUserIDForWalletAddress(jsi::Runtime &rt, jsi::String walletAddress) = 0; virtual jsi::Value findUserIDForUsername(jsi::Runtime &rt, jsi::String username) = 0; virtual jsi::Value getFarcasterUsers(jsi::Runtime &rt, jsi::Array farcasterIDs) = 0; @@ -183,21 +183,21 @@ return bridging::callFromJs( rt, &T::updateDeviceList, jsInvoker_, instance_, std::move(authUserID), std::move(authDeviceID), std::move(authAccessToken), std::move(updatePayload)); } - jsi::Value uploadSecondaryDeviceKeysAndLogIn(jsi::Runtime &rt, jsi::String userID, jsi::String challengeResponse, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, jsi::String contentPrekeySignature, jsi::String notifPrekey, jsi::String notifPrekeySignature, jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys) override { + jsi::Value uploadSecondaryDeviceKeysAndLogIn(jsi::Runtime &rt, jsi::String userID, jsi::String nonce, jsi::String nonceSignature, jsi::String keyPayload, jsi::String keyPayloadSignature, jsi::String contentPrekey, jsi::String contentPrekeySignature, jsi::String notifPrekey, jsi::String notifPrekeySignature, jsi::Array contentOneTimeKeys, jsi::Array notifOneTimeKeys) override { static_assert( - bridging::getParameterCount(&T::uploadSecondaryDeviceKeysAndLogIn) == 11, - "Expected uploadSecondaryDeviceKeysAndLogIn(...) to have 11 parameters"); + bridging::getParameterCount(&T::uploadSecondaryDeviceKeysAndLogIn) == 12, + "Expected uploadSecondaryDeviceKeysAndLogIn(...) to have 12 parameters"); return bridging::callFromJs( - rt, &T::uploadSecondaryDeviceKeysAndLogIn, jsInvoker_, instance_, std::move(userID), std::move(challengeResponse), std::move(keyPayload), std::move(keyPayloadSignature), std::move(contentPrekey), std::move(contentPrekeySignature), std::move(notifPrekey), std::move(notifPrekeySignature), std::move(contentOneTimeKeys), std::move(notifOneTimeKeys)); + rt, &T::uploadSecondaryDeviceKeysAndLogIn, jsInvoker_, instance_, std::move(userID), std::move(nonce), std::move(nonceSignature), std::move(keyPayload), std::move(keyPayloadSignature), std::move(contentPrekey), std::move(contentPrekeySignature), std::move(notifPrekey), std::move(notifPrekeySignature), std::move(contentOneTimeKeys), std::move(notifOneTimeKeys)); } - jsi::Value logInExistingDevice(jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String challengeResponse) override { + jsi::Value logInExistingDevice(jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String nonce, jsi::String nonceSignature) override { static_assert( - bridging::getParameterCount(&T::logInExistingDevice) == 4, - "Expected logInExistingDevice(...) to have 4 parameters"); + bridging::getParameterCount(&T::logInExistingDevice) == 5, + "Expected logInExistingDevice(...) to have 5 parameters"); return bridging::callFromJs( - rt, &T::logInExistingDevice, jsInvoker_, instance_, std::move(userID), std::move(deviceID), std::move(challengeResponse)); + rt, &T::logInExistingDevice, jsInvoker_, instance_, std::move(userID), std::move(deviceID), std::move(nonce), std::move(nonceSignature)); } jsi::Value findUserIDForWalletAddress(jsi::Runtime &rt, jsi::String walletAddress) override { static_assert( diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -12,7 +12,7 @@ import { type SignedDeviceList, signedDeviceListHistoryValidator, - type SignedMessage, + type SignedNonce, type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, type IdentityServiceClient, @@ -457,7 +457,7 @@ }, uploadKeysForRegisteredDeviceAndLogIn: async ( userID: string, - nonceChallengeResponse: SignedMessage, + nonceChallengeResponse: SignedNonce, ) => { await commCoreModule.initializeCryptoAccount(); const [ @@ -469,11 +469,12 @@ commCoreModule.getOneTimeKeys(ONE_TIME_KEYS_NUMBER), commCoreModule.validateAndGetPrekeys(), ]); - const challengeResponse = JSON.stringify(nonceChallengeResponse); + const { nonce, nonceSignature } = nonceChallengeResponse; const registrationResult = await commRustModule.uploadSecondaryDeviceKeysAndLogIn( userID, - challengeResponse, + nonce, + nonceSignature, blobPayload, signature, prekeys.contentPrekey, diff --git a/native/native_rust_library/src/identity/login.rs b/native/native_rust_library/src/identity/login.rs --- a/native/native_rust_library/src/identity/login.rs +++ b/native/native_rust_library/src/identity/login.rs @@ -81,7 +81,8 @@ // QR code device log in pub fn upload_secondary_device_keys_and_log_in( user_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, @@ -113,7 +114,8 @@ let result = upload_secondary_device_keys_and_log_in_helper( user_id, - challenge_response, + nonce, + nonce_signature, device_key_upload, ) .await; @@ -124,13 +126,18 @@ pub fn log_in_existing_device( user_id: String, device_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, promise_id: u32, ) { RUNTIME.spawn(async move { - let result = - log_in_existing_device_helper(user_id, device_id, challenge_response) - .await; + let result = log_in_existing_device_helper( + user_id, + device_id, + nonce, + nonce_signature, + ) + .await; handle_string_result_as_callback(result, promise_id); }); } @@ -242,7 +249,8 @@ async fn upload_secondary_device_keys_and_log_in_helper( user_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, device_key_upload: DeviceKeyUpload, ) -> Result { let mut identity_client = get_unauthenticated_client( @@ -254,7 +262,8 @@ let request = SecondaryDeviceKeysUploadRequest { user_id, - challenge_response, + nonce, + nonce_signature, device_key_upload: Some(device_key_upload), }; @@ -270,7 +279,8 @@ async fn log_in_existing_device_helper( user_id: String, device_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, ) -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, @@ -282,7 +292,8 @@ let request = ExistingDeviceLoginRequest { user_id, device_id, - challenge_response, + nonce, + nonce_signature, }; let response = identity_client diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -206,7 +206,8 @@ #[cxx_name = "identityUploadSecondaryDeviceKeysAndLogIn"] fn upload_secondary_device_keys_and_log_in( user_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, @@ -222,7 +223,8 @@ fn log_in_existing_device( user_id: String, device_id: String, - challenge_response: String, + nonce: String, + nonce_signature: String, promise_id: u32, ); diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js --- a/native/qr-code/qr-code-screen.react.js +++ b/native/qr-code/qr-code-screen.react.js @@ -11,10 +11,7 @@ import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; import type { BackupKeys } from 'lib/types/backup-types.js'; -import type { - NonceChallenge, - SignedMessage, -} from 'lib/types/identity-service-types.js'; +import type { SignedNonce } from 'lib/types/identity-service-types.js'; import { getContentSigningKey } from 'lib/utils/crypto-utils.js'; import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js'; @@ -58,12 +55,10 @@ invariant(identityClient, 'identity context not set'); try { const nonce = await identityClient.generateNonce(); - const nonceChallenge: NonceChallenge = { nonce }; - const nonceMessage = JSON.stringify(nonceChallenge); - const signature = await olmAPI.signMessage(nonceMessage); - const challengeResponse: SignedMessage = { - message: nonceMessage, - signature, + const nonceSignature = await olmAPI.signMessage(nonce); + const challengeResponse: SignedNonce = { + nonce, + nonceSignature, }; await identityClient.uploadKeysForRegisteredDeviceAndLogIn( diff --git a/native/schema/CommRustModuleSchema.js b/native/schema/CommRustModuleSchema.js --- a/native/schema/CommRustModuleSchema.js +++ b/native/schema/CommRustModuleSchema.js @@ -110,7 +110,8 @@ ) => Promise; +uploadSecondaryDeviceKeysAndLogIn: ( userID: string, - challengeResponse: string, + nonce: string, + nonceSignature: string, keyPayload: string, keyPayloadSignature: string, contentPrekey: string, @@ -123,7 +124,8 @@ +logInExistingDevice: ( userID: string, deviceID: string, - challengeResponse: string, + nonce: string, + nonceSignature: string, ) => Promise; +findUserIDForWalletAddress: (walletAddress: string) => Promise; +findUserIDForUsername: (username: string) => Promise; diff --git a/services/commtest/src/identity/mod.rs b/services/commtest/src/identity/mod.rs --- a/services/commtest/src/identity/mod.rs +++ b/services/commtest/src/identity/mod.rs @@ -1,7 +1,6 @@ use base64::Engine; use ed25519_dalek::{ed25519::signature::SignerMut, Keypair, Signature}; use rand::rngs::OsRng; -use serde_json::json; use self::olm_account_infos::{ ClientPublicKeys, IdentityPublicKeys, DEFAULT_CLIENT_KEYS, @@ -43,22 +42,6 @@ base64::engine::general_purpose::STANDARD_NO_PAD .encode(signature.to_bytes()) } - - /// returns value for challenge_response gRPC field - pub fn sign_nonce(&mut self, nonce: String) -> String { - let message = json!({ - "nonce": nonce - }); - let message_str = - serde_json::to_string(&message).expect("message stringify failed"); - let message_signature = self.sign_message(&message_str); - - let response = json!({ - "message": message_str, - "signature": message_signature, - }); - serde_json::to_string(&response).expect("response stringify failed") - } } impl Default for SigningCapableAccount { diff --git a/services/commtest/tests/identity_access_tokens_tests.rs b/services/commtest/tests/identity_access_tokens_tests.rs --- a/services/commtest/tests/identity_access_tokens_tests.rs +++ b/services/commtest/tests/identity_access_tokens_tests.rs @@ -54,12 +54,13 @@ .expect("failed to generate nonce") .into_inner() .nonce; - let challenge_response = account.sign_nonce(nonce); + let nonce_signature = account.sign_message(&nonce); let new_credentials = client .log_in_existing_device(ExistingDeviceLoginRequest { user_id: user.user_id.clone(), device_id: user.device_id.clone(), - challenge_response, + nonce, + nonce_signature, }) .await .expect("LogInExistingDevice call failed") 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 @@ -31,7 +31,7 @@ }; use crate::grpc_services::shared::get_value; use crate::grpc_utils::{ - ChallengeResponse, DeviceKeyUploadActions, NonceChallenge, + SignedNonce, DeviceKeyUploadActions, }; use crate::nonce::generate_nonce_data; use crate::reserved_users::{ @@ -680,16 +680,14 @@ let code_version = get_code_version(&request); let message = request.into_inner(); - let challenge_response = ChallengeResponse::try_from(&message)?; + let challenge_response = SignedNonce::try_from(&message)?; let flattened_device_key_upload = construct_flattened_device_key_upload(&message)?; let user_id = message.user_id; let device_id = flattened_device_key_upload.device_id_key.clone(); - let NonceChallenge { nonce } = - challenge_response.verify_and_get_message(&device_id)?; - + let nonce = challenge_response.verify_and_get_nonce(&device_id)?; self.verify_and_remove_nonce(&nonce).await?; let user_identifier = self @@ -753,13 +751,12 @@ request: tonic::Request, ) -> std::result::Result, tonic::Status> { let message = request.into_inner(); - let challenge_response = ChallengeResponse::try_from(&message)?; + let challenge_response = SignedNonce::try_from(&message)?; let ExistingDeviceLoginRequest { user_id, device_id, .. } = message; - let NonceChallenge { nonce } = - challenge_response.verify_and_get_message(&device_id)?; + let nonce = challenge_response.verify_and_get_nonce(&device_id)?; self.verify_and_remove_nonce(&nonce).await?; let (identifier_response, device_list_response) = tokio::join!( diff --git a/services/identity/src/grpc_utils.rs b/services/identity/src/grpc_utils.rs --- a/services/identity/src/grpc_utils.rs +++ b/services/identity/src/grpc_utils.rs @@ -2,7 +2,6 @@ use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde::Deserialize; use tonic::Status; -use tracing::error; use crate::{ database::DeviceRow, @@ -16,43 +15,41 @@ WalletAuthRequest, }, }, - siwe::SocialProof, }; #[derive(Deserialize)] -pub struct ChallengeResponse { - message: String, +pub struct SignedNonce { + nonce: String, signature: String, } -#[derive(Deserialize)] -pub struct NonceChallenge { - pub nonce: String, -} - -impl TryFrom<&SecondaryDeviceKeysUploadRequest> for ChallengeResponse { +impl TryFrom<&SecondaryDeviceKeysUploadRequest> for SignedNonce { type Error = Status; fn try_from( value: &SecondaryDeviceKeysUploadRequest, ) -> Result { - serde_json::from_str(&value.challenge_response) - .map_err(|_| Status::invalid_argument("message format invalid")) + Ok(Self { + nonce: value.nonce.to_string(), + signature: value.nonce_signature.to_string(), + }) } } -impl TryFrom<&ExistingDeviceLoginRequest> for ChallengeResponse { +impl TryFrom<&ExistingDeviceLoginRequest> for SignedNonce { type Error = Status; fn try_from(value: &ExistingDeviceLoginRequest) -> Result { - serde_json::from_str(&value.challenge_response) - .map_err(|_| Status::invalid_argument("message format invalid")) + Ok(Self { + nonce: value.nonce.to_string(), + signature: value.nonce_signature.to_string(), + }) } } -impl ChallengeResponse { - pub fn verify_and_get_message( - &self, +impl SignedNonce { + pub fn verify_and_get_nonce( + self, signing_public_key: &str, - ) -> Result { + ) -> Result { let signature_bytes = general_purpose::STANDARD_NO_PAD .decode(&self.signature) .map_err(|_| Status::invalid_argument("signature invalid"))?; @@ -68,11 +65,10 @@ .map_err(|_| Status::failed_precondition("malformed key"))?; public_key - .verify(self.message.as_bytes(), &signature) + .verify(self.nonce.as_bytes(), &signature) .map_err(|_| Status::permission_denied("verification failed"))?; - serde_json::from_str(&self.message) - .map_err(|_| Status::invalid_argument("message format invalid")) + Ok(self.nonce) } } @@ -263,31 +259,27 @@ #[cfg(test)] mod tests { - use serde_json::json; - use super::*; #[test] fn test_challenge_response_verification() { - let signing_key = "TF6XVmtso2xpCfUWcU1dOTPDnoo+Euls3H4wJhO6T6A"; - let challenge_response_json = json!({ - "message": r#"{"nonce":"hello"}"#, - "signature": "pXQZc9if5/p926HoomKEtLfe10SNOHdkf3wIXLjax0yg3mOE0z+0JTf+IgsjB7p9RGSisVRfskQQXa30uPupAQ" - }); + let expected_nonce = "hello"; + let signing_key = "jnBariweGMSdfmJYvuObTu4IGT1fpaJTo/ovbkU0SAY"; + let request = SecondaryDeviceKeysUploadRequest { - challenge_response: serde_json::to_string(&challenge_response_json) - .unwrap(), + nonce: expected_nonce.to_string(), + nonce_signature: "LWlgCDND3bmgIS8liW/0eKJvuNs4Vcb4iMf43zD038/MnC0cSAYl2l3bO9dFc0fa2w6/2ABsUlPDMVr+isE0Aw".to_string(), user_id: "foo".to_string(), device_key_upload: None, }; - let challenge_response = ChallengeResponse::try_from(&request) + let challenge_response = SignedNonce::try_from(&request) .expect("failed to parse challenge response"); - let msg: NonceChallenge = challenge_response - .verify_and_get_message(signing_key) + let retrieved_nonce = challenge_response + .verify_and_get_nonce(signing_key) .expect("verification failed"); - assert_eq!(msg.nonce, "hello".to_string()); + assert_eq!(retrieved_nonce, expected_nonce); } } diff --git a/shared/protos/identity_unauth.proto b/shared/protos/identity_unauth.proto --- a/shared/protos/identity_unauth.proto +++ b/shared/protos/identity_unauth.proto @@ -215,11 +215,11 @@ message SecondaryDeviceKeysUploadRequest { string user_id = 1; - // Signed nonce message, JSON-stringified - string challenge_response = 2; + string nonce = 2; + string nonce_signature = 3; // Information specific to a user's device needed to open a new channel of // communication with this user - DeviceKeyUpload device_key_upload = 3; + DeviceKeyUpload device_key_upload = 4; } // LogInExistingDevice @@ -227,8 +227,8 @@ message ExistingDeviceLoginRequest { string user_id = 1; string device_id = 2; - // Signed nonce message, JSON-stringified - string challenge_response = 3; + string nonce = 3; + string nonce_signature = 4; } // GenerateNonce diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js --- a/web/account/qr-code-login.react.js +++ b/web/account/qr-code-login.react.js @@ -12,10 +12,7 @@ import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js'; -import type { - NonceChallenge, - SignedMessage, -} from 'lib/types/identity-service-types.js'; +import type { SignedNonce } from 'lib/types/identity-service-types.js'; import { peerToPeerMessageTypes, type QRCodeAuthMessage, @@ -86,12 +83,10 @@ invariant(identityClient, 'identity context not set'); try { const nonce = await identityClient.generateNonce(); - const nonceChallenge: NonceChallenge = { nonce }; - const nonceMessage = JSON.stringify(nonceChallenge); - const signature = await olmAPI.signMessage(nonceMessage); - const challengeResponse: SignedMessage = { - message: nonceMessage, - signature, + const nonceSignature = await olmAPI.signMessage(nonce); + const challengeResponse: SignedNonce = { + nonce, + nonceSignature, }; await dispatchActionPromise( diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -11,7 +11,7 @@ import { type SignedDeviceList, signedDeviceListHistoryValidator, - type SignedMessage, + type SignedNonce, type IdentityServiceAuthLayer, type IdentityServiceClient, type DeviceOlmOutboundKeys, @@ -443,18 +443,19 @@ uploadKeysForRegisteredDeviceAndLogIn: ( ownerUserID: string, - nonceChallengeResponse: SignedMessage, + nonceChallengeResponse: SignedNonce, ) => Promise = async ( ownerUserID, nonceChallengeResponse, ) => { const identityDeviceKeyUpload = await this.getNewDeviceKeyUpload(); const deviceKeyUpload = authNewDeviceKeyUpload(identityDeviceKeyUpload); - const challengeResponse = JSON.stringify(nonceChallengeResponse); + const { nonce, nonceSignature } = nonceChallengeResponse; const request = new SecondaryDeviceKeysUploadRequest(); request.setUserId(ownerUserID); - request.setChallengeResponse(challengeResponse); + request.setNonce(nonce); + request.setNonceSignature(nonceSignature); request.setDeviceKeyUpload(deviceKeyUpload); let response; diff --git a/web/protobufs/identity-unauth-structs.cjs b/web/protobufs/identity-unauth-structs.cjs --- a/web/protobufs/identity-unauth-structs.cjs +++ b/web/protobufs/identity-unauth-structs.cjs @@ -3725,7 +3725,8 @@ proto.identity.unauth.SecondaryDeviceKeysUploadRequest.toObject = function(includeInstance, msg) { var f, obj = { userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - challengeResponse: jspb.Message.getFieldWithDefault(msg, 2, ""), + nonce: jspb.Message.getFieldWithDefault(msg, 2, ""), + nonceSignature: jspb.Message.getFieldWithDefault(msg, 3, ""), deviceKeyUpload: (f = msg.getDeviceKeyUpload()) && proto.identity.unauth.DeviceKeyUpload.toObject(includeInstance, f) }; @@ -3769,9 +3770,13 @@ break; case 2: var value = /** @type {string} */ (reader.readString()); - msg.setChallengeResponse(value); + msg.setNonce(value); break; case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setNonceSignature(value); + break; + case 4: var value = new proto.identity.unauth.DeviceKeyUpload; reader.readMessage(value,proto.identity.unauth.DeviceKeyUpload.deserializeBinaryFromReader); msg.setDeviceKeyUpload(value); @@ -3812,17 +3817,24 @@ f ); } - f = message.getChallengeResponse(); + f = message.getNonce(); if (f.length > 0) { writer.writeString( 2, f ); } + f = message.getNonceSignature(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } f = message.getDeviceKeyUpload(); if (f != null) { writer.writeMessage( - 3, + 4, f, proto.identity.unauth.DeviceKeyUpload.serializeBinaryToWriter ); @@ -3849,10 +3861,10 @@ /** - * optional string challenge_response = 2; + * optional string nonce = 2; * @return {string} */ -proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.getChallengeResponse = function() { +proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.getNonce = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); }; @@ -3861,18 +3873,36 @@ * @param {string} value * @return {!proto.identity.unauth.SecondaryDeviceKeysUploadRequest} returns this */ -proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.setChallengeResponse = function(value) { +proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.setNonce = function(value) { return jspb.Message.setProto3StringField(this, 2, value); }; /** - * optional DeviceKeyUpload device_key_upload = 3; + * optional string nonce_signature = 3; + * @return {string} + */ +proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.getNonceSignature = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.identity.unauth.SecondaryDeviceKeysUploadRequest} returns this + */ +proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.setNonceSignature = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional DeviceKeyUpload device_key_upload = 4; * @return {?proto.identity.unauth.DeviceKeyUpload} */ proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.getDeviceKeyUpload = function() { return /** @type{?proto.identity.unauth.DeviceKeyUpload} */ ( - jspb.Message.getWrapperField(this, proto.identity.unauth.DeviceKeyUpload, 3)); + jspb.Message.getWrapperField(this, proto.identity.unauth.DeviceKeyUpload, 4)); }; @@ -3881,7 +3911,7 @@ * @return {!proto.identity.unauth.SecondaryDeviceKeysUploadRequest} returns this */ proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.setDeviceKeyUpload = function(value) { - return jspb.Message.setWrapperField(this, 3, value); + return jspb.Message.setWrapperField(this, 4, value); }; @@ -3899,7 +3929,7 @@ * @return {boolean} */ proto.identity.unauth.SecondaryDeviceKeysUploadRequest.prototype.hasDeviceKeyUpload = function() { - return jspb.Message.getField(this, 3) != null; + return jspb.Message.getField(this, 4) != null; }; @@ -3937,7 +3967,8 @@ var f, obj = { userId: jspb.Message.getFieldWithDefault(msg, 1, ""), deviceId: jspb.Message.getFieldWithDefault(msg, 2, ""), - challengeResponse: jspb.Message.getFieldWithDefault(msg, 3, "") + nonce: jspb.Message.getFieldWithDefault(msg, 3, ""), + nonceSignature: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -3984,7 +4015,11 @@ break; case 3: var value = /** @type {string} */ (reader.readString()); - msg.setChallengeResponse(value); + msg.setNonce(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setNonceSignature(value); break; default: reader.skipField(); @@ -4029,13 +4064,20 @@ f ); } - f = message.getChallengeResponse(); + f = message.getNonce(); if (f.length > 0) { writer.writeString( 3, f ); } + f = message.getNonceSignature(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -4076,10 +4118,10 @@ /** - * optional string challenge_response = 3; + * optional string nonce = 3; * @return {string} */ -proto.identity.unauth.ExistingDeviceLoginRequest.prototype.getChallengeResponse = function() { +proto.identity.unauth.ExistingDeviceLoginRequest.prototype.getNonce = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); }; @@ -4088,11 +4130,29 @@ * @param {string} value * @return {!proto.identity.unauth.ExistingDeviceLoginRequest} returns this */ -proto.identity.unauth.ExistingDeviceLoginRequest.prototype.setChallengeResponse = function(value) { +proto.identity.unauth.ExistingDeviceLoginRequest.prototype.setNonce = function(value) { return jspb.Message.setProto3StringField(this, 3, value); }; +/** + * optional string nonce_signature = 4; + * @return {string} + */ +proto.identity.unauth.ExistingDeviceLoginRequest.prototype.getNonceSignature = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.identity.unauth.ExistingDeviceLoginRequest} returns this + */ +proto.identity.unauth.ExistingDeviceLoginRequest.prototype.setNonceSignature = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + diff --git a/web/protobufs/identity-unauth-structs.cjs.flow b/web/protobufs/identity-unauth-structs.cjs.flow --- a/web/protobufs/identity-unauth-structs.cjs.flow +++ b/web/protobufs/identity-unauth-structs.cjs.flow @@ -384,8 +384,11 @@ getUserId(): string; setUserId(value: string): SecondaryDeviceKeysUploadRequest; - getChallengeResponse(): string; - setChallengeResponse(value: string): SecondaryDeviceKeysUploadRequest; + getNonce(): string; + setNonce(value: string): SecondaryDeviceKeysUploadRequest; + + getNonceSignature(): string; + setNonceSignature(value: string): SecondaryDeviceKeysUploadRequest; getDeviceKeyUpload(): DeviceKeyUpload | void; setDeviceKeyUpload(value?: DeviceKeyUpload): SecondaryDeviceKeysUploadRequest; @@ -402,6 +405,8 @@ export type SecondaryDeviceKeysUploadRequestObject = { userId: string, + nonce: string, + nonceSignature: string, deviceKeyUpload?: DeviceKeyUploadObject, } @@ -412,8 +417,11 @@ getDeviceId(): string; setDeviceId(value: string): ExistingDeviceLoginRequest; - getChallengeResponse(): string; - setChallengeResponse(value: string): ExistingDeviceLoginRequest; + getNonce(): string; + setNonce(value: string): ExistingDeviceLoginRequest; + + getNonceSignature(): string; + setNonceSignature(value: string): ExistingDeviceLoginRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ExistingDeviceLoginRequestObject; @@ -426,7 +434,8 @@ export type ExistingDeviceLoginRequestObject = { userId: string, deviceId: string, - challengeResponse: string, + nonce: string, + nonceSignature: string, } declare export class GenerateNonceResponse extends Message {