Page MenuHomePhabricator

D5796.id20951.diff
No OneTemporary

D5796.id20951.diff

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,
+ &registration_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

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)

Event Timeline