Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3249481
D5796.id20951.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D5796.id20951.diff
View Options
diff --git a/.buildkite/eslint_flow_jest.yml b/.buildkite/eslint_flow_jest.yml
--- a/.buildkite/eslint_flow_jest.yml
+++ b/.buildkite/eslint_flow_jest.yml
@@ -4,6 +4,7 @@
- '(pkill flow || true)'
- 'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y'
- '. /root/.cargo/env'
+ - 'apt update && apt install -y cmake'
- 'yarn cleaninstall --frozen-lockfile --skip-optional --network-timeout 180000'
- 'yarn eslint --max-warnings=0 && yarn workspace lib flow && yarn workspace web flow && yarn workspace landing flow && yarn workspace native flow && yarn workspace keyserver flow && yarn workspace desktop flow'
- 'yarn workspace lib test && yarn workspace keyserver test'
diff --git a/.buildkite/jsi_codegen.yml b/.buildkite/jsi_codegen.yml
--- a/.buildkite/jsi_codegen.yml
+++ b/.buildkite/jsi_codegen.yml
@@ -2,6 +2,9 @@
- label: 'JSI Codegen'
command:
- '(pkill flow || true)'
+ - 'apt update && apt install -y cmake'
+ - 'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y'
+ - '. /root/.cargo/env'
- 'yarn cleaninstall --frozen-lockfile --skip-optional --network-timeout 180000'
- 'cd native && yarn codegen-jsi && git diff --exit-code'
plugins:
diff --git a/keyserver/Dockerfile b/keyserver/Dockerfile
--- a/keyserver/Dockerfile
+++ b/keyserver/Dockerfile
@@ -49,9 +49,11 @@
# We need rsync in the prod-build yarn script
# We need mariadb-client so we can use mysqldump for backups
+# We need protobuf-compiler to build rust-node-addon
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
rsync \
mariadb-client \
+ protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
#-------------------------------------------------------------------------------
@@ -100,7 +102,7 @@
# We run yarn cleaninstall before copying most of the files in for build caching
#-------------------------------------------------------------------------------
-# Copy in package.json and yarn.lock files
+# Copy in package.json files, yarn.lock files, and required scripts
COPY --chown=comm package.json yarn.lock ./
COPY --chown=comm keyserver/package.json keyserver/.flowconfig keyserver/
COPY --chown=comm lib/package.json lib/.flowconfig lib/
@@ -109,6 +111,7 @@
COPY --chown=comm landing/package.json landing/.flowconfig landing/
COPY --chown=comm desktop/package.json desktop/
COPY --chown=comm keyserver/addons/rust-node-addon/package.json \
+ keyserver/addons/rust-node-addon/install_external_deps.sh \
keyserver/addons/rust-node-addon/
COPY --chown=comm native/expo-modules/android-lifecycle/package.json \
native/expo-modules/android-lifecycle/
@@ -118,6 +121,9 @@
COPY --chown=comm keyserver/addons/rust-node-addon/Cargo.toml \
keyserver/addons/rust-node-addon/
+# Copy in comm-opaque library, a dependency of rust-node-addon
+COPY --chown=comm shared/comm-opaque shared/comm-opaque/
+
# Copy in files needed for patch-package
COPY --chown=comm patches patches/
diff --git a/keyserver/addons/rust-node-addon/Cargo.toml b/keyserver/addons/rust-node-addon/Cargo.toml
--- a/keyserver/addons/rust-node-addon/Cargo.toml
+++ b/keyserver/addons/rust-node-addon/Cargo.toml
@@ -9,11 +9,24 @@
[dependencies]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
-napi = { version = "2.10.1", default-features = false, features = ["napi4"] }
+napi = { version = "2.10.1", default-features = false, features = [
+ "napi4",
+ "tokio_rt",
+] }
napi-derive = { version = "2.9.1", default-features = false }
+opaque-ke = "1.2"
+rand = "0.8"
+tonic = "0.8"
+tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
+tokio-stream = "0.1"
+tracing = "0.1"
+prost = "0.11"
+comm-opaque = {path = "../../../shared/comm-opaque"}
+lazy_static = "1.4"
[build-dependencies]
napi-build = "2.0.1"
+tonic-build = "0.8"
[profile.release]
lto = true
diff --git a/keyserver/addons/rust-node-addon/build.rs b/keyserver/addons/rust-node-addon/build.rs
--- a/keyserver/addons/rust-node-addon/build.rs
+++ b/keyserver/addons/rust-node-addon/build.rs
@@ -2,4 +2,6 @@
fn main() {
napi_build::setup();
+ tonic_build::compile_protos("../../../shared/protos/identity.proto")
+ .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
}
diff --git a/keyserver/addons/rust-node-addon/index.js b/keyserver/addons/rust-node-addon/index.js
--- a/keyserver/addons/rust-node-addon/index.js
+++ b/keyserver/addons/rust-node-addon/index.js
@@ -3,7 +3,13 @@
const { platform, arch } = process;
type RustAPI = {
- +sum: (a: number, b: number) => number,
+ +registerUser: (
+ userId: string,
+ deviceId: string,
+ username: string,
+ password: string,
+ userPublicKey: string,
+ ) => Promise<string>,
};
async function getRustAPI(): Promise<RustAPI> {
@@ -28,8 +34,8 @@
throw new Error('Failed to load native binding');
}
- const { sum } = nativeBinding.default;
- return { sum };
+ const { registerUser } = nativeBinding.default;
+ return { registerUser };
}
export { getRustAPI };
diff --git a/keyserver/addons/rust-node-addon/install_external_deps.sh b/keyserver/addons/rust-node-addon/install_external_deps.sh
new file mode 100755
--- /dev/null
+++ b/keyserver/addons/rust-node-addon/install_external_deps.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -e
+
+echo "Buildkite var set: $BUILDKITE"
+
+# We can skip this script if it's not part of a CI workflow
+if ! { [ -n "$BUILDKITE" ] || [ -n "$CI" ]; }
+then
+ echo "Not in a CI workflow, exiting"
+ exit
+fi
+
+# Install protobuf if it's not already installed
+if [[ ! "$(command -v protoc)" ]]
+then
+ echo "Installing protobuf"
+ . ../../../scripts/install_protobuf.sh
+fi
diff --git a/keyserver/addons/rust-node-addon/package.json b/keyserver/addons/rust-node-addon/package.json
--- a/keyserver/addons/rust-node-addon/package.json
+++ b/keyserver/addons/rust-node-addon/package.json
@@ -30,10 +30,11 @@
},
"scripts": {
"artifacts": "napi artifacts",
- "build": "napi build --platform napi --release",
+ "build": "yarn install-external-deps && napi build --platform napi --release",
"build:debug": "napi build --platform napi",
"version": "napi version",
"postinstall": "yarn build",
- "clean": "rm -rf target/ && rm -rf napi/ && rm -rf node_modules/"
+ "clean": "rm -rf target/ && rm -rf napi/ && rm -rf node_modules/",
+ "install-external-deps": "./install_external_deps.sh"
}
}
diff --git a/keyserver/addons/rust-node-addon/src/identity_client.rs b/keyserver/addons/rust-node-addon/src/identity_client.rs
new file mode 100644
--- /dev/null
+++ b/keyserver/addons/rust-node-addon/src/identity_client.rs
@@ -0,0 +1,312 @@
+use lazy_static::lazy_static;
+use napi::bindgen_prelude::*;
+use opaque_ke::{
+ ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters,
+ ClientLoginStartResult, ClientRegistration,
+ ClientRegistrationFinishParameters, CredentialFinalization,
+ CredentialResponse, RegistrationResponse, RegistrationUpload,
+};
+use rand::{rngs::OsRng, CryptoRng, Rng};
+use tokio::sync::mpsc;
+use tokio_stream::wrappers::ReceiverStream;
+use tonic::Request;
+use tracing::{error, instrument};
+mod identity {
+ tonic::include_proto!("identity");
+}
+use comm_opaque::Cipher;
+use identity::identity_service_client::IdentityServiceClient;
+use identity::{
+ pake_login_response::Data::AccessToken,
+ pake_login_response::Data::PakeCredentialResponse,
+ registration_request::Data::PakeCredentialFinalization as RegistrationPakeCredentialFinalization,
+ registration_request::Data::PakeRegistrationRequestAndUserId,
+ registration_request::Data::PakeRegistrationUploadAndCredentialRequest,
+ registration_response::Data::PakeLoginResponse as RegistrationPakeLoginResponse,
+ registration_response::Data::PakeRegistrationResponse,
+ PakeLoginResponse as PakeLoginResponseStruct,
+ PakeRegistrationRequestAndUserId as PakeRegistrationRequestAndUserIdStruct,
+ PakeRegistrationUploadAndCredentialRequest as PakeRegistrationUploadAndCredentialRequestStruct,
+ RegistrationRequest, RegistrationResponse as RegistrationResponseMessage,
+};
+use std::env::var;
+
+lazy_static! {
+ static ref IDENTITY_SERVICE_SOCKET_ADDR: String =
+ var("COMM_IDENTITY_SERVICE_SOCKET_ADDR")
+ .unwrap_or("https://[::1]:50051".to_string());
+}
+
+#[napi]
+#[instrument(skip_all)]
+pub async fn register_user(
+ user_id: String,
+ device_id: String,
+ username: String,
+ password: String,
+ user_public_key: String,
+) -> Result<String> {
+ let mut identity_client =
+ IdentityServiceClient::connect(IDENTITY_SERVICE_SOCKET_ADDR.as_str())
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?;
+
+ // Create a RegistrationRequest channel and use ReceiverStream to turn the
+ // MPSC receiver into a Stream for outbound messages
+ let (tx, rx) = mpsc::channel(1);
+ let stream = ReceiverStream::new(rx);
+ let request = Request::new(stream);
+
+ // `response` is the Stream for inbound messages
+ let mut response = identity_client
+ .register_user(request)
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?
+ .into_inner();
+
+ // Start PAKE registration on client and send initial registration request
+ // to Identity service
+ let mut client_rng = OsRng;
+ let (registration_request, client_registration) = pake_registration_start(
+ &mut client_rng,
+ user_id,
+ &password,
+ device_id,
+ username,
+ user_public_key,
+ )?;
+ if let Err(e) = tx.send(registration_request).await {
+ error!("Response was dropped: {}", e);
+ return Err(Error::from_status(Status::GenericFailure));
+ }
+
+ // Handle responses from Identity service sequentially, making sure we get
+ // messages in the correct order
+
+ // Finish PAKE registration and begin PAKE login; send the final
+ // registration request and initial login request together to reduce the
+ // number of trips
+ let message = response
+ .message()
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?;
+ let client_login = handle_registration_response(
+ message,
+ &mut client_rng,
+ client_registration,
+ &password,
+ tx.clone(),
+ )
+ .await?;
+
+ // Finish PAKE login; send final login request to Identity service
+ let message = response
+ .message()
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?;
+ handle_registration_credential_response(message, client_login, tx)
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?;
+
+ // Return access token
+ let message = response
+ .message()
+ .await
+ .map_err(|_| Error::from_status(Status::GenericFailure))?;
+ handle_registration_token_response(message)
+}
+
+fn handle_unexpected_response<T: std::fmt::Debug>(message: Option<T>) -> Error {
+ error!("Received an unexpected message: {:?}", message);
+ Error::from_status(Status::GenericFailure)
+}
+
+async fn send_to_mpsc<T>(tx: mpsc::Sender<T>, request: T) -> Result<()> {
+ if let Err(e) = tx.send(request).await {
+ error!("Response was dropped: {}", e);
+ return Err(Error::from_status(Status::GenericFailure));
+ }
+ Ok(())
+}
+
+fn pake_login_start(
+ rng: &mut (impl Rng + CryptoRng),
+ password: &str,
+) -> Result<ClientLoginStartResult<Cipher>> {
+ ClientLogin::<Cipher>::start(
+ rng,
+ password.as_bytes(),
+ ClientLoginStartParameters::default(),
+ )
+ .map_err(|e| {
+ error!("Failed to start PAKE login: {}", e);
+ Error::from_status(Status::GenericFailure)
+ })
+}
+
+fn pake_login_finish(
+ credential_response_bytes: &[u8],
+ client_login: ClientLogin<Cipher>,
+) -> Result<CredentialFinalization<Cipher>> {
+ client_login
+ .finish(
+ CredentialResponse::deserialize(credential_response_bytes).map_err(
+ |e| {
+ error!("Could not deserialize credential response bytes: {}", e);
+ Error::from_status(Status::GenericFailure)
+ },
+ )?,
+ ClientLoginFinishParameters::default(),
+ )
+ .map_err(|e| {
+ error!("Failed to finish PAKE login: {}", e);
+ Error::from_status(Status::GenericFailure)
+ })
+ .map(|res| res.message)
+}
+
+fn pake_registration_start(
+ rng: &mut (impl Rng + CryptoRng),
+ user_id: String,
+ password: &str,
+ device_id: String,
+ username: String,
+ user_public_key: String,
+) -> Result<(RegistrationRequest, ClientRegistration<Cipher>)> {
+ let client_registration_start_result =
+ ClientRegistration::<Cipher>::start(rng, password.as_bytes()).map_err(
+ |e| {
+ error!("Failed to start PAKE registration: {}", e);
+ Error::from_status(Status::GenericFailure)
+ },
+ )?;
+ let pake_registration_request =
+ client_registration_start_result.message.serialize();
+ Ok((
+ RegistrationRequest {
+ data: Some(PakeRegistrationRequestAndUserId(
+ PakeRegistrationRequestAndUserIdStruct {
+ user_id,
+ device_id,
+ pake_registration_request,
+ username,
+ user_public_key,
+ },
+ )),
+ },
+ client_registration_start_result.state,
+ ))
+}
+
+async fn handle_registration_response(
+ message: Option<RegistrationResponseMessage>,
+ client_rng: &mut (impl Rng + CryptoRng),
+ client_registration: ClientRegistration<Cipher>,
+ password: &str,
+ tx: mpsc::Sender<RegistrationRequest>,
+) -> Result<ClientLogin<Cipher>> {
+ if let Some(RegistrationResponseMessage {
+ data: Some(PakeRegistrationResponse(registration_response_bytes)),
+ ..
+ }) = message
+ {
+ let pake_registration_upload = pake_registration_finish(
+ client_rng,
+ ®istration_response_bytes,
+ client_registration,
+ )?
+ .serialize();
+ let client_login_start_result = pake_login_start(client_rng, password)?;
+
+ // `registration_request` is a gRPC message containing serialized bytes to
+ // complete PAKE registration and begin PAKE login
+ let registration_request = RegistrationRequest {
+ data: Some(PakeRegistrationUploadAndCredentialRequest(
+ PakeRegistrationUploadAndCredentialRequestStruct {
+ pake_registration_upload,
+ pake_credential_request: client_login_start_result
+ .message
+ .serialize()
+ .map_err(|e| {
+ error!("Could not serialize credential request: {}", e);
+ Error::from_status(Status::GenericFailure)
+ })?,
+ },
+ )),
+ };
+ if let Err(e) = tx.send(registration_request).await {
+ error!("Response was dropped: {}", e);
+ return Err(Error::from_status(Status::GenericFailure));
+ }
+ Ok(client_login_start_result.state)
+ } else {
+ Err(handle_unexpected_response(message))
+ }
+}
+
+async fn handle_registration_credential_response(
+ message: Option<RegistrationResponseMessage>,
+ client_login: ClientLogin<Cipher>,
+ tx: mpsc::Sender<RegistrationRequest>,
+) -> Result<()> {
+ if let Some(RegistrationResponseMessage {
+ data:
+ Some(RegistrationPakeLoginResponse(PakeLoginResponseStruct {
+ data: Some(PakeCredentialResponse(credential_response_bytes)),
+ })),
+ }) = message
+ {
+ let registration_request = RegistrationRequest {
+ data: Some(RegistrationPakeCredentialFinalization(
+ pake_login_finish(&credential_response_bytes, client_login)?
+ .serialize()
+ .map_err(|e| {
+ error!("Could not serialize credential request: {}", e);
+ Error::from_status(Status::GenericFailure)
+ })?,
+ )),
+ };
+ send_to_mpsc(tx, registration_request).await
+ } else {
+ Err(handle_unexpected_response(message))
+ }
+}
+
+fn handle_registration_token_response(
+ message: Option<RegistrationResponseMessage>,
+) -> Result<String> {
+ if let Some(RegistrationResponseMessage {
+ data:
+ Some(RegistrationPakeLoginResponse(PakeLoginResponseStruct {
+ data: Some(AccessToken(access_token)),
+ })),
+ }) = message
+ {
+ Ok(access_token)
+ } else {
+ Err(handle_unexpected_response(message))
+ }
+}
+
+fn pake_registration_finish(
+ rng: &mut (impl Rng + CryptoRng),
+ registration_response_bytes: &[u8],
+ client_registration: ClientRegistration<Cipher>,
+) -> Result<RegistrationUpload<Cipher>> {
+ client_registration
+ .finish(
+ rng,
+ RegistrationResponse::deserialize(registration_response_bytes).map_err(
+ |e| {
+ error!("Could not deserialize registration response bytes: {}", e);
+ Error::from_status(Status::GenericFailure)
+ },
+ )?,
+ ClientRegistrationFinishParameters::default(),
+ )
+ .map_err(|e| {
+ error!("Failed to finish PAKE registration: {}", e);
+ Error::from_status(Status::GenericFailure)
+ })
+ .map(|res| res.message)
+}
diff --git a/keyserver/addons/rust-node-addon/src/lib.rs b/keyserver/addons/rust-node-addon/src/lib.rs
--- a/keyserver/addons/rust-node-addon/src/lib.rs
+++ b/keyserver/addons/rust-node-addon/src/lib.rs
@@ -1,9 +1,4 @@
-#![deny(clippy::all)]
+pub mod identity_client;
#[macro_use]
extern crate napi_derive;
-
-#[napi]
-pub fn sum(a: i32, b: i32) -> i32 {
- a + b
-}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 16, 3:20 PM (20 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2498114
Default Alt Text
D5796.id20951.diff (16 KB)
Attached To
Mode
D5796: [keyserver] implement registerUser rpc with napi-rs/tonic/opaque-ke
Attached
Detach File
Event Timeline
Log In to Comment