瀏覽代碼

Add basic implemntation of key-exchange

develop
Silvestr Predko 2 年之前
父節點
當前提交
36ae7a5ac5
共有 3 個文件被更改,包括 515 次插入60 次删除
  1. 14
    4
      web-client/Cargo.toml
  2. 105
    0
      web-client/src/errors.rs
  3. 396
    56
      web-client/src/lib.rs

+ 14
- 4
web-client/Cargo.toml 查看文件

@@ -10,17 +10,27 @@ description = """
10 10
 crate-type = ["cdylib", "rlib"]
11 11
 
12 12
 [dependencies]
13
-vodozemac = { version = "0.2", features = ["js"] }
13
+aes-gcm = "0.10"
14 14
 serde = { version = "1", default-features = false, features = ["derive"] }
15 15
 serde_json = { version = "1", default-features = false }
16
-wasm-bindgen = "0.2"
16
+wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
17 17
 js-sys = "0.3"
18 18
 wasm-bindgen-test = "0.3"
19 19
 wasm-bindgen-futures = "0.4.32"
20
-web-sys = { version = "0.3", features = ["console", "WebSocket", "WebSocketDict", "WebSocketElement"] }
21
-borsh = "0.9"
20
+web-sys = { version = "0.3", features = ["console", "WebSocket", "WebSocketDict", "WebSocketElement", "Window"] }
22 21
 near-account-id = "0.14.0"
23 22
 near-primitives = { version = "0.14.0", package = "near-primitives-core" }
24 23
 near-units = "0.2"
25 24
 near-client = { path = "../near-client" }
25
+common-api = { path = "../common-api" }
26 26
 url = "2.2"
27
+futures = "0.3"
28
+reqwest = { version = "0.11", features = ["json"] }
29
+console_error_panic_hook = "0.1.7"
30
+uuid = { version = "1.1.2", features = ["v4", "serde", "js"] }
31
+blake2 = "0.10"
32
+base64 = "0.13"
33
+itertools = "0.10"
34
+rand = { version = "0.8.5" }
35
+rand_chacha = "0.3"
36
+serde-wasm-bindgen = "0.4.3"

+ 105
- 0
web-client/src/errors.rs 查看文件

@@ -0,0 +1,105 @@
1
+use near_account_id::ParseAccountError;
2
+use near_client::Error;
3
+use wasm_bindgen::prelude::*;
4
+
5
+#[wasm_bindgen]
6
+#[derive(Clone, Debug)]
7
+pub struct ApiError {
8
+    r#type: ErrorType,
9
+    msg: String,
10
+}
11
+
12
+#[wasm_bindgen]
13
+impl ApiError {
14
+    pub fn err_msg(&self) -> String {
15
+        self.msg.clone()
16
+    }
17
+
18
+    pub fn err_type(&self) -> ErrorType {
19
+        self.r#type
20
+    }
21
+}
22
+
23
+impl ApiError {
24
+    pub fn new(r#type: ErrorType, msg: String) -> Self {
25
+        Self { r#type, msg }
26
+    }
27
+}
28
+
29
+#[wasm_bindgen]
30
+#[derive(Clone, Copy, Debug)]
31
+pub enum ErrorType {
32
+    Type,
33
+    Rpc,
34
+    EmptyBlock,
35
+    ArgsSerialize,
36
+    DeserializeTxResp,
37
+    TransactionSerialize,
38
+    TransactionExec,
39
+    TxNotStarted,
40
+    NoSigner,
41
+    ParseRpcUrl,
42
+    BadContractId,
43
+    Other,
44
+}
45
+
46
+impl From<Error> for ApiError {
47
+    fn from(error: Error) -> Self {
48
+        match error {
49
+            Error::Type(type_err) => Self {
50
+                r#type: ErrorType::Type,
51
+                msg: type_err.to_string(),
52
+            },
53
+            Error::Rpc { error, method } => Self {
54
+                r#type: ErrorType::Rpc,
55
+                msg: format!("Method: [\"{method}\"], Cause: \"{error}\""),
56
+            },
57
+            Error::EmptyBlock => Self {
58
+                r#type: ErrorType::EmptyBlock,
59
+                msg: error.to_string(),
60
+            },
61
+            Error::ArgsSerialize(err) => Self {
62
+                r#type: ErrorType::ArgsSerialize,
63
+                msg: err.to_string(),
64
+            },
65
+            Error::DeserializeTxResp(err) => Self {
66
+                r#type: ErrorType::DeserializeTxResp,
67
+                msg: err.to_string(),
68
+            },
69
+            Error::TransactionSerialize(err) => Self {
70
+                r#type: ErrorType::TransactionSerialize,
71
+                msg: err.to_string(),
72
+            },
73
+            Error::TransactionExec(err) => Self {
74
+                r#type: ErrorType::TransactionExec,
75
+                msg: format!("{err:?}"),
76
+            },
77
+            Error::TxNotStarted => Self {
78
+                r#type: ErrorType::TxNotStarted,
79
+                msg: error.to_string(),
80
+            },
81
+            Error::NoSigner => Self {
82
+                r#type: ErrorType::NoSigner,
83
+                msg: error.to_string(),
84
+            },
85
+        }
86
+    }
87
+}
88
+
89
+impl From<near_client::types::Error> for ApiError {
90
+    fn from(err: near_client::types::Error) -> Self {
91
+        Self {
92
+            r#type: ErrorType::Type,
93
+            msg: err.to_string(),
94
+        }
95
+    }
96
+}
97
+
98
+impl From<ParseAccountError> for ApiError {
99
+    fn from(err: ParseAccountError) -> Self {
100
+        Self::new(
101
+            ErrorType::BadContractId,
102
+            format!("AccountId has wrong format. Cause: {err}"),
103
+        )
104
+    }
105
+}

+ 396
- 56
web-client/src/lib.rs 查看文件

@@ -1,74 +1,414 @@
1
-use std::str::FromStr;
2
-
1
+use aes_gcm::{
2
+    aead::{Aead, KeyInit},
3
+    Aes256Gcm, Nonce as AesNonce,
4
+};
5
+use futures::lock::Mutex;
6
+use itertools::Itertools;
7
+use near_account_id::AccountId;
8
+use rand::{RngCore, SeedableRng};
3 9
 use serde_json::json;
4 10
 use wasm_bindgen::prelude::*;
5 11
 
6
-use near_client::{
7
-    client::{NearClient, Signer},
8
-    types::crypto::Keypair,
12
+use near_client::client::{NearClient, Signer};
13
+
14
+use js_sys::Promise;
15
+use near_primitives::types::Nonce;
16
+
17
+use std::{ops::DerefMut, str::FromStr, sync::Arc};
18
+
19
+use blake2::{
20
+    digest::{Update, VariableOutput},
21
+    Blake2bVar,
22
+};
23
+
24
+mod errors;
25
+use common_api::crypto::{
26
+    key_exchange::{Public, Secret},
27
+    Key as _,
9 28
 };
10 29
 
30
+use errors::{ApiError, ErrorType};
31
+
32
+#[allow(unused_macros)]
11 33
 macro_rules! console_log {
12 34
     ($($t:tt)*) => (web_sys::console::log_1(&format!($($t)*).into()))
13 35
 }
14 36
 
15
-#[wasm_bindgen]
16
-pub async fn greet() -> Result<(), JsValue> {
17
-    // let socket = web_sys::WebSocket::new("ws://127.0.0.1:3000/ws")?;
18
-    // let cl_socket = socket.clone();
19
-    // let on_open = Closure::<dyn Fn()>::new(move || {
20
-    //     console_log!("Open A socket");
21
-    //     cl_socket.send_with_str("Init Message").unwrap();
22
-    // });
23
-    // socket.set_onopen(Some(on_open.as_ref().unchecked_ref()));
24
-    // on_open.forget();
37
+type Result<T> = std::result::Result<T, ApiError>;
25 38
 
39
+#[wasm_bindgen(start)]
40
+pub fn start() -> std::result::Result<(), JsValue> {
41
+    console_error_panic_hook::set_once();
26 42
     Ok(())
27 43
 }
28 44
 
45
+struct Handler {
46
+    contract_id: AccountId,
47
+    owner_sk: Secret,
48
+    url: url::Url,
49
+}
50
+
29 51
 #[wasm_bindgen]
30
-pub async fn send_transaction(
31
-    keypair: String,
32
-    nounce: near_primitives::types::Nonce,
33
-    account_id: String,
34
-) -> Result<(), JsValue> {
35
-    let signer = near_account_id::AccountId::from_str(&account_id)
36
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
37
-    let contract_id = near_account_id::AccountId::from_str("contract1.demoacc.testnet")
38
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
39
-
40
-    console_log!("SignAcc");
41
-    let mut signer_acc = Signer::new(&keypair, signer, nounce)
42
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
43
-
44
-    console_log!("Keypair");
45
-    let keypair = Keypair::from_encoded(&keypair)
46
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
47
-
48
-    console_log!("Near Client");
49
-    let near_client = NearClient::new(url::Url::from_str("https://rpc.testnet.near.org").unwrap())
50
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
51
-
52
-    console_log!("Call");
53
-    let build = near_client
54
-        .function_call(&mut signer_acc, &contract_id, "add_key")
55
-        .gas(near_units::parse_gas!("300 T") as u64)
56
-        .args(json!({
57
-            "id": "Some",
58
-            "value": String::from(keypair.public_key)
59
-        }))
60
-        .build()
61
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
62
-
63
-    console_log!("Build");
64
-    let res = build
65
-        .commit_async()
66
-        .await
67
-        .map_err(|err| JsValue::from_str(&format!("Error {err}")))?;
68
-
69
-    console_log!("Response: {}", res);
52
+pub struct ProvisionerConfig {
53
+    contract_id: AccountId,
54
+    rpc_url: url::Url,
55
+    key_exchange_url: url::Url,
56
+}
70 57
 
71
-    Ok(())
58
+#[wasm_bindgen]
59
+impl ProvisionerConfig {
60
+    #[wasm_bindgen(constructor)]
61
+    pub fn new(
62
+        contract_id: String,
63
+        rpc_url: &str,
64
+        exchange_url: &str,
65
+    ) -> Result<ProvisionerConfig> {
66
+        let rpc_url = url::Url::from_str(rpc_url)
67
+            .map_err(|err| ApiError::new(ErrorType::ParseRpcUrl, err.to_string()))?;
68
+        let key_exchange_url = url::Url::from_str(exchange_url)
69
+            .map_err(|err| ApiError::new(ErrorType::ParseRpcUrl, err.to_string()))?;
70
+        let contract_id = AccountId::from_str(&contract_id).map_err(Into::<ApiError>::into)?;
71
+
72
+        Ok(Self {
73
+            contract_id,
74
+            rpc_url,
75
+            key_exchange_url,
76
+        })
77
+    }
78
+}
79
+
80
+#[wasm_bindgen]
81
+pub struct KeyProvisioner {
82
+    client: Arc<NearClient>,
83
+    participants: Arc<Mutex<Option<Vec<String>>>>,
84
+    signer: Arc<Mutex<Signer>>,
85
+    handler: Arc<Handler>,
86
+}
87
+
88
+impl KeyProvisioner {
89
+    fn handler(&self) -> Arc<Handler> {
90
+        Arc::clone(&self.handler)
91
+    }
92
+
93
+    fn client(&self) -> Arc<NearClient> {
94
+        Arc::clone(&self.client)
95
+    }
96
+
97
+    fn signer(&self) -> Arc<Mutex<Signer>> {
98
+        Arc::clone(&self.signer)
99
+    }
100
+}
101
+
102
+#[wasm_bindgen]
103
+impl KeyProvisioner {
104
+    #[wasm_bindgen(constructor)]
105
+    pub fn new(
106
+        keypair_str: String,
107
+        nonce: Nonce,
108
+        account_id: String,
109
+        config: ProvisionerConfig,
110
+    ) -> Result<KeyProvisioner> {
111
+        let client = Arc::new(NearClient::new(config.rpc_url).map_err(ApiError::from)?);
112
+        let signer = Arc::new(Mutex::new(
113
+            Signer::new(
114
+                &keypair_str,
115
+                AccountId::from_str(&account_id).map_err(Into::<ApiError>::into)?,
116
+                nonce,
117
+            )
118
+            .map_err(ApiError::from)?,
119
+        ));
120
+        let handler = Arc::new(Handler {
121
+            contract_id: config.contract_id,
122
+            owner_sk: new_secret()?,
123
+            url: config.key_exchange_url,
124
+        });
125
+
126
+        Ok(Self {
127
+            client,
128
+            signer,
129
+            handler,
130
+            participants: Arc::new(Mutex::new(None)),
131
+        })
132
+    }
133
+
134
+    pub fn init(&self, participants_set: js_sys::Set) -> Promise {
135
+        let handler = self.handler();
136
+        let client = self.client();
137
+        let signer = self.signer();
138
+        let participants_lock = self.participants.clone();
139
+
140
+        wasm_bindgen_futures::future_to_promise(async move {
141
+            let exchange_uuid = uuid::Uuid::new_v4();
142
+            let (contract_id, owner_pk) =
143
+                { (handler.contract_id.clone(), Public::from(&handler.owner_sk)) };
144
+
145
+            let res: Vec<Result<String>> = participants_set
146
+                .keys()
147
+                .into_iter()
148
+                .map(|item| {
149
+                    item.map(|it| {
150
+                        it.as_string()
151
+                            .ok_or_else(|| {
152
+                                ApiError::new(
153
+                                    ErrorType::Other,
154
+                                    "participants_set is empty".to_owned(),
155
+                                )
156
+                            })
157
+                            .map_err(ApiError::from)
158
+                    })
159
+                })
160
+                .try_collect()?;
161
+
162
+            let participants: Vec<String> = res.into_iter().try_collect()?;
163
+
164
+            client
165
+                .function_call(
166
+                    signer.lock().await.deref_mut(),
167
+                    &contract_id,
168
+                    "init_meeting",
169
+                )
170
+                .gas(near_units::parse_gas!("20 T") as u64)
171
+                .args(json!({
172
+                    "owner_public_key": base64::encode(owner_pk.to_bytes()),
173
+                    "id": exchange_uuid.to_string(),
174
+                    "participants": participants,
175
+                }))
176
+                .build()
177
+                .map_err(ApiError::from)?
178
+                .commit::<()>()
179
+                .await
180
+                .map_err(ApiError::from)?;
181
+
182
+            *participants_lock.lock_owned().await = Some(participants);
183
+
184
+            Ok(JsValue::from_str(&exchange_uuid.to_string()))
185
+        })
186
+    }
187
+
188
+    pub fn send_keys(&self, meeting_id: String) -> Promise {
189
+        let handler = self.handler();
190
+        let client = self.client();
191
+        let participants_lock = self.participants.clone();
192
+        wasm_bindgen_futures::future_to_promise(async move {
193
+            let guard = participants_lock.lock().await;
194
+            let participants = guard
195
+                .as_ref()
196
+                .ok_or_else(|| ApiError::new(ErrorType::Other, "Empty participants".to_owned()))?;
197
+
198
+            let (contract_id, url) = { (handler.contract_id.clone(), handler.url.to_string()) };
199
+
200
+            let participants_keys =
201
+                futures::future::try_join_all(participants.iter().map(|participant| {
202
+                    let client_cp = client.clone();
203
+                    let contract_cp = contract_id.clone();
204
+                    let meeting_id_cp = meeting_id.clone();
205
+                    async move {
206
+                        loop {
207
+                            let resp = client_cp
208
+                                .view::<Option<String>>(
209
+                                    &contract_cp,
210
+                                    "public_key",
211
+                                    Some(json!({
212
+                                        "id": meeting_id_cp,
213
+                                        "participant_id": participant.to_string()
214
+                                    })),
215
+                                )
216
+                                .await
217
+                                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
218
+
219
+                            if let Some(key) = resp {
220
+                                return Ok::<(String, String), ApiError>((
221
+                                    key,
222
+                                    participant.to_string(),
223
+                                ));
224
+                            }
225
+                        }
226
+                    }
227
+                }))
228
+                .await?;
229
+
230
+            let keys: Vec<(Public, String)> = participants_keys
231
+                .into_iter()
232
+                .map(|(pk, participant_id)| {
233
+                    base64::decode(&pk)
234
+                        .map(|it| (it, participant_id))
235
+                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
236
+                        .and_then(|(pk_bytes, participant_id)| {
237
+                            Public::try_from_bytes(&pk_bytes)
238
+                                .map(|it| (it, participant_id))
239
+                                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
240
+                        })
241
+                })
242
+                .try_collect()?;
243
+
244
+            let aes_secret = base64::encode(new_secret()?.to_bytes());
245
+
246
+            let encrypted_keys: Vec<(Vec<u8>, String)> = keys
247
+                .into_iter()
248
+                .map(|(pk, participant_id)| {
249
+                    let res_key = handler.owner_sk.exchange(&pk);
250
+                    encrypt(&res_key, aes_secret.as_bytes(), participant_id)
251
+                })
252
+                .try_collect()?;
253
+
254
+            let _: Vec<reqwest::Response> = futures::future::try_join_all(
255
+                encrypted_keys.into_iter().map(|(bytes, participant_id)| {
256
+                    let client = reqwest::ClientBuilder::new().build().unwrap();
257
+                    client
258
+                        .post(format!("{url}/meeting"))
259
+                        .query(&[
260
+                            ("id", serde_json::to_value(meeting_id.clone()).unwrap()),
261
+                            ("participant", serde_json::to_value(participant_id).unwrap()),
262
+                        ])
263
+                        .json(&serde_json::Value::String(base64::encode(bytes)))
264
+                        .send()
265
+                }),
266
+            )
267
+            .await
268
+            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
269
+            .into_iter()
270
+            .map(|it| {
271
+                it.error_for_status()
272
+                    .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
273
+            })
274
+            .try_collect()?;
275
+
276
+            Ok(JsValue::from_str(&aes_secret))
277
+        })
278
+    }
279
+
280
+    pub fn get_key(&self, meeting_id: String) -> Promise {
281
+        let handler = self.handler();
282
+        let client = self.client();
283
+        let signer = self.signer();
284
+
285
+        wasm_bindgen_futures::future_to_promise(async move {
286
+            let signer = signer.lock().await;
287
+            let (contract_id, sk, url) = {
288
+                (
289
+                    handler.contract_id.clone(),
290
+                    &handler.owner_sk,
291
+                    handler.url.to_string(),
292
+                )
293
+            };
294
+            loop {
295
+                let http_client = reqwest::ClientBuilder::new().build().unwrap();
296
+                let msg = http_client
297
+                    .get(format!("{}/meeting", &url))
298
+                    .query(&[
299
+                        ("id", serde_json::to_value(meeting_id.clone()).unwrap()),
300
+                        (
301
+                            "participant",
302
+                            serde_json::to_value(signer.account()).unwrap(),
303
+                        ),
304
+                    ])
305
+                    .send()
306
+                    .await
307
+                    .and_then(|it| it.error_for_status())
308
+                    .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
309
+                    .text()
310
+                    .await
311
+                    .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
312
+
313
+                if msg.is_empty() {
314
+                    continue;
315
+                }
316
+
317
+                let owner_pub_key = client
318
+                    .view::<Option<String>>(
319
+                        &contract_id,
320
+                        "owner_key",
321
+                        Some(serde_json::json!({
322
+                            "id": meeting_id.to_string()
323
+                        })),
324
+                    )
325
+                    .await
326
+                    .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
327
+
328
+                if let Some(owner_pk) = owner_pub_key {
329
+                    let bytes = base64::decode(owner_pk)
330
+                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
331
+                    let owner_pk = Public::try_from_bytes(&bytes)
332
+                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
333
+                    let key = sk.exchange(&owner_pk);
334
+
335
+                    let msg_bytes = base64::decode(msg)
336
+                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
337
+                    let decrypted_msg = decrypt(&key, &msg_bytes)?;
338
+
339
+                    return Ok(JsValue::from_str(
340
+                        std::str::from_utf8(&decrypted_msg)
341
+                            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?,
342
+                    ));
343
+                }
344
+            }
345
+        })
346
+    }
347
+
348
+    pub fn push_to_near(&self, meeting_id: String) -> Promise {
349
+        let handler = self.handler();
350
+        let client = self.client();
351
+        let signer = self.signer();
352
+        wasm_bindgen_futures::future_to_promise(async move {
353
+            let (contract_id, pk) =
354
+                { (handler.contract_id.clone(), Public::from(&handler.owner_sk)) };
355
+            client
356
+                .function_call(signer.lock().await.deref_mut(), &contract_id, "set_key")
357
+                .gas(near_units::parse_gas!("300 T") as u64)
358
+                .args(json!({
359
+                    "id": meeting_id.to_string(),
360
+                    "pk": base64::encode(pk.to_bytes()),
361
+                }))
362
+                .build()
363
+                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
364
+                .commit::<()>()
365
+                .await
366
+                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
367
+
368
+            Ok(JsValue::undefined())
369
+        })
370
+    }
371
+}
372
+
373
+fn new_secret() -> Result<Secret> {
374
+    use rand_chacha::ChaChaRng;
375
+    let mut chacha = ChaChaRng::from_entropy();
376
+    let mut bytes = [0_u8; 32];
377
+    chacha.fill_bytes(&mut bytes);
378
+    Secret::try_from_bytes(&bytes).map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
379
+}
380
+
381
+fn encrypt(
382
+    exchange_key: &[u8],
383
+    secret: &[u8],
384
+    participant_id: String,
385
+) -> Result<(Vec<u8>, String)> {
386
+    aes(exchange_key)
387
+        .and_then(|it: Aes256Gcm| {
388
+            it.encrypt(AesNonce::from_slice(b"unique nonce"), secret)
389
+                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
390
+        })
391
+        .map(|encrypted_msg| (encrypted_msg, participant_id))
392
+}
393
+
394
+fn decrypt(exchange_key: &[u8], secret: &[u8]) -> Result<Vec<u8>> {
395
+    aes(exchange_key).and_then(|it: Aes256Gcm| {
396
+        it.decrypt(AesNonce::from_slice(b"unique nonce"), secret)
397
+            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
398
+    })
399
+}
400
+
401
+fn aes(exchange_key: &[u8]) -> Result<Aes256Gcm> {
402
+    Blake2bVar::new(32)
403
+        .map(|mut kdf| {
404
+            kdf.update(exchange_key);
405
+            kdf.finalize_boxed()
406
+        })
407
+        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
408
+        .and_then(|it| {
409
+            Aes256Gcm::new_from_slice(&it)
410
+                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
411
+        })
72 412
 }
73 413
 
74 414
 #[cfg(test)]

Loading…
取消
儲存