Feat: encrypted mqtt sending

This commit is contained in:
Dorian Zedler 2023-02-09 13:23:28 +01:00
parent c2c62d8a64
commit 33b24a70a7
Signed by: dorian
GPG Key ID: 989DE36109AFA354
15 changed files with 1396 additions and 118 deletions

5
crypto_helper/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
**/*.rs.bk
bin/
pkg/
wasm-pack.log

534
crypto_helper/Cargo.lock generated Normal file
View File

@ -0,0 +1,534 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241"
dependencies = [
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "cpufeatures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "crypto_helper"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"console_error_panic_hook",
"generic-array",
"hex",
"pbkdf2",
"sha256",
"wasm-bindgen",
"wasm-bindgen-test",
"wee_alloc",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.3",
"crypto-common",
"subtle",
]
[[package]]
name = "generic-array"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "ghash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.6",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "memory_units"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest 0.10.6",
"hmac",
"password-hash",
"sha2 0.10.6",
]
[[package]]
name = "polyval"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.10.6",
]
[[package]]
name = "sha256"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e334db67871c14c18fc066ad14af13f9fdf5f9a91c61af432d1e3a39c8c6a141"
dependencies = [
"hex",
"sha2 0.9.9",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wee_alloc"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"memory_units",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

38
crypto_helper/Cargo.toml Normal file
View File

@ -0,0 +1,38 @@
[package]
name = "crypto_helper"
version = "0.1.0"
authors = ["Dorian Zedler <dorian@itsblue.de>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }
base64 = "0.21.0"
pbkdf2 = "0.11.0"
hex = "0.4.3"
aes-gcm = { version="0.10.1", features = ["aes", "alloc"], default-features = false }
generic-array = "0.14.6"
sha256 = "1.1.1"
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

86
crypto_helper/README.md Normal file
View File

@ -0,0 +1,86 @@
<div align="center">
<h1><code>wasm-pack-template</code></h1>
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## About
[**📚 Read this template tutorial! 📚**][template-docs]
This template is designed for compiling Rust libraries into WebAssembly and
publishing the resulting package to NPM.
Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other
templates and usages of `wasm-pack`.
[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html
[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html
## 🚴 Usage
### 🐑 Use `cargo generate` to Clone this Template
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```
wasm-pack test --headless --firefox
```
### 🎁 Publish to NPM with `wasm-pack publish`
```
wasm-pack publish
```
## 🔋 Batteries Included
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
between WebAssembly and JavaScript.
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
for logging panic messages to the developer console.
* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
for small code size.
* `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.

View File

@ -0,0 +1,60 @@
use pbkdf2::{self, password_hash::PasswordHasher};
use base64::{Engine as _};
use generic_array::typenum::{UInt, B1, B0, UTerm, U32};
use wasm_bindgen::prelude::*;
use aes_gcm::{
aead::{Aead, KeyInit, generic_array::GenericArray},
Aes256Gcm
};
#[wasm_bindgen]
pub struct Crypto {
iv: IV,
c: Aes256Gcm
}
type Key = GenericArray<u8, U32>;
type IV = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
#[wasm_bindgen]
impl Crypto {
#[wasm_bindgen(constructor)]
pub fn new(password: &str, salt: &str) -> Self {
let key = Self::_pbkdf2(password, salt, 1000, 32);
let iv = Self::_pbkdf2(password, salt, 5000, 12);
println!("Key {}\nIV {}", hex::encode(&key), hex::encode(&iv));
let iv: IV = GenericArray::clone_from_slice(&iv);
let key: &Key = GenericArray::from_slice(&key);
let c = Aes256Gcm::new(key);
Crypto { iv: iv, c:c }
}
pub fn sha256(input: &str) -> String {
sha256::digest(input)
}
fn _pbkdf2(password: &str, salt: &str, rounds: u32, output_length: usize) -> Vec<u8> {
let params = pbkdf2::Params { rounds: rounds, output_length: output_length };
let salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
let salt = pbkdf2::password_hash::Salt::new(&salt).unwrap();
let res = pbkdf2::Pbkdf2.hash_password_customized(password.as_bytes(), None, None, params, salt).unwrap();
res.hash.unwrap().as_bytes().to_owned()
}
pub fn encrypt(&self, plaintext: &str) -> String {
base64::engine::general_purpose::STANDARD_NO_PAD.encode(self.c.encrypt(&self.iv, plaintext.as_bytes()).unwrap())
}
pub fn decrypt(&self, ciphertext: &str) -> String {
let ciphertext = base64::engine::general_purpose::STANDARD_NO_PAD.decode(ciphertext).unwrap();
String::from_utf8(self.c.decrypt(&self.iv, &*ciphertext).unwrap()).unwrap()
}
}

20
crypto_helper/src/lib.rs Normal file
View File

@ -0,0 +1,20 @@
mod utils;
pub mod crypto;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, crypto!");
}

View File

@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@ -0,0 +1,13 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}

View File

@ -4,6 +4,13 @@
}
}
.loading {
font-size: 2em;
font-weight: bold;
text-align: center;
line-height: 1.2;
}
.timer {
font-size: 8em;
font-weight: bold;
@ -16,13 +23,31 @@
animation: blinker 2s ease infinite;
}
.timer.sending {
color: #ff0;
animation: blinker 2s ease infinite;
}
main {
height: 100vh;
padding-top: 0 !important;
}
main > div {
height: 100%;
.timer-container-div {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.timer-div {
cursor: pointer;
}
.tap-area {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
cursor: pointer;
}

View File

@ -6,13 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script src="https://unpkg.com/crypto-js@4.1.1/crypto-js.js"></script>
<script src="lib/crypto_helper.js"></script>
<script src="js/index.js"></script>
<script src="js/localState.js"></script>
<script src="js/mqtt.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css" />
<link rel="stylesheet" href="index.css" />
<audio id="sound-0" src="sound/0.mp3" preload="auto"></audio>
<audio id="sound-1" src="sound/1.mp3" preload="auto"></audio>
<audio id="sound-2" src="sound/2.mp3" preload="auto"></audio>
@ -28,21 +30,50 @@
</head>
<body>
<main class="container">
<div x-data>
<div @click="$store.localState.next()">
<hgroup>
<h1>OK! .. READY! ... GO!</h1>
<h2 x-text="$store.localState.stateHint"></h2>
</hgroup>
<script>
const event = new Event('wasm-loaded');
wasm_bindgen('lib/crypto_helper_bg.wasm').then(() => {
document.dispatchEvent(event);
});
</script>
<main class="container" x-data>
<div class="timer-container-div" x-show="$store.localState._state !== 5">
<div @click="$store.localState.next()" class="tap-area" x-show="$store.localState._state === 2"></div>
<div @click="$store.localState.next()" class="timer-div">
<hgroup>
<h1>OK! .. READY! ... GO!</h1>
<h2 x-text="$store.localState.stateHint"></h2>
</hgroup>
<div x-data="Timer">
<p :class="'timer' + (over ? ' over':'')"
x-text="time + 's'"></p>
<p :class="'timer' + (over ? ($store.localState._state === 3 ? ' sending':' over'):'')" x-text="time + 's'"></p>
</div>
</div>
</div>
<div x-show="$store.localState._state === 0">
<details>
<summary>Remote connection</summary>
<div x-show="!$store.mqtt.connected">
<p>To send the times to a computer, please enter a password here:</p>
<form x-data="PasswordForm()" @submit.prevent="submitForm">
<label for="passwordForm_password">Password:</label>
<input id="passwordForm_password" type="text" x-model="formData.password" placeholder="Password" />
<small>Make sure, this is exactly the same on your computer!</small>
<button type="submit">Connect</button>
</form>
</div>
<div x-show="$store.mqtt.connected">
<p>Connected</p>
<button @click="$store.localState.password = ''">Disconnect</button>
</div>
</details>
</div>
<div class="timer-container-div" x-show="$store.localState._state === 5">
<h1 class="loading" aria-busy="true">Connecting...</h1>
</div>
</main>
</body>

View File

@ -1,108 +1,62 @@
async function play() {
const number = document.getElementById("number").value;
sayNumber(number);
}
async function sayNumber(number) {
console.log(number)
number = number.toString();
number = number.replace(/[^0-9]/, "");
for(let i = 0; i < number.length; i++) {
await playAudio(document.getElementById(`sound-${number[i]}`));
}
console.log(number);
number = number.toString();
number = number.replace(/[^0-9]/, "");
for (let i = 0; i < number.length; i++) {
await playAudio(document.getElementById(`sound-${number[i]}`));
}
}
function playAudio(audio){
return new Promise(res=>{
audio.play()
audio.onended = res
})
function playAudio(audio) {
return new Promise((res) => {
audio.play();
audio.onended = res;
});
}
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
);
}
function PasswordForm() {
return {
formData: {
password: "",
},
submitForm() {
Alpine.store("localState").password = this.formData.password;
},
};
}
function Timer() {
return {
time: 0,
over: false,
init() {
setInterval(() => {
const startedAt = Alpine.store("localState").startedAt;
const resultTime = Alpine.store("localState").time;
return {
time: 0,
over: false,
init() {
setInterval(() => {
const startedAt = Alpine.store("localState").startedAt;
const resultTime = Alpine.store("localState").time;
let time;
if (!startedAt && !resultTime) {
time = 0;
this.over = false;
} else if (resultTime) {
time = (resultTime / 1000);
this.over = true;
} else {
this.over = false;
time =
((new Date().getTime() - startedAt) / 1000)
;
}
let time;
if (!startedAt && !resultTime) {
time = 0;
this.over = false;
} else if (resultTime) {
time = resultTime / 1000;
this.over = true;
} else {
this.over = false;
time = (new Date().getTime() - startedAt) / 1000;
}
this.time = time.toFixed(2)
}, 10);
},
};
}
document.addEventListener("alpine:init", () => {
Alpine.store("localState", {
_state: 0,
// 0: idle
// 1: starting
// 2: running
// 3: stopped
startedAt: null,
time: null,
stateHint: "",
init() {
Alpine.effect(() => {
switch(this._state) {
case 0: {
this.startedAt = null;
this.time = null;
this.stateHint = "Tap to start";
break;
}
case 1: {
this.stateHint = "Get ready..."
playAudio(document.getElementById("sound-ok-ready-go")).then(() => {
Alpine.store("localState")._state = 2;
});
break;
}
case 2: {
this.stateHint = "Tap to stop"
this.startedAt = new Date().getTime() - 200;
break;
}
case 3: {
this.stateHint = "Tap to reset"
this.time = new Date().getTime() - this.startedAt;
sayNumber((this.time / 1000).toFixed(2));
this.startedAt = null;
break;
}
}
})
},
next() {
console.log("next");
playAudio(document.getElementById("sound-silence"))
if(this._state == 1) return;
this._state = (this._state + 1) % 4
},
})
})
this.time = time.toFixed(2);
}, 10);
},
};
}

105
js/localState.js Normal file
View File

@ -0,0 +1,105 @@
document.addEventListener("alpine:init", () => {
Alpine.store("localState", {
_state: 0,
// 0: idle
// 1: starting
// 2: running
// 3: sending time to mqtt
// 4: stopped
// 5: connecting to mqtt
startedAt: null,
time: null,
stateHint: "",
password: null,
init() {
Alpine.effect(() => {
if (this.password == null) {
this.password = localStorage.getItem("password");
} else {
localStorage.setItem("password", this.password);
}
const mqtt = Alpine.store("mqtt");
if (!mqtt) return;
if (this.password == null || this.password == "") {
mqtt.disconnect();
} else {
mqtt.connect();
}
});
Alpine.effect(() => {
switch (this._state) {
case 0:
this.stateHint = "Tap here to start";
break;
case 1:
this.stateHint = "Get ready...";
break;
case 2:
this.stateHint = "Tap anywhere to stop";
break;
case 3:
this.stateHint = "Sending time to MQTT...";
break;
case 4:
this.stateHint = "Tap here to reset";
break;
}
});
},
next() {
playAudio(document.getElementById("sound-silence"));
if (this._state == 1 || this._state == 3) return;
this._setState((this._state + 1) % 5);
},
_setState(state) {
switch (state) {
case 0: {
this.startedAt = null;
this.time = null;
break;
}
case 1: {
playAudio(document.getElementById("sound-ok-ready-go")).then(() => {
Alpine.store("localState")._setState(2);
});
break;
}
case 2: {
this.startedAt = new Date().getTime() - 200;
break;
}
case 3: {
this.time =
((new Date().getTime() - this.startedAt) / 1000).toFixed(2) * 1000;
this.startedAt = null;
sayNumber(this.time / 10);
if (Alpine.store("mqtt").connected) {
Alpine.store("mqtt")
.sendTime(this.time)
.then(() => {
Alpine.store("localState")._setState(4);
});
break;
}
state = 4;
break;
}
default:
break;
}
this._state = state;
},
});
});

117
js/mqtt.js Normal file
View File

@ -0,0 +1,117 @@
let wasm_inited_resolve;
const wasm_inited = new Promise((resolve) => {wasm_inited_resolve = resolve;});
document.addEventListener("wasm-loaded", () => {
wasm_inited_resolve(wasm_bindgen)
});
document.addEventListener("alpine:init", () => {
Alpine.store("mqtt", {
connected: false,
_client: null,
_topic: null,
_c: null,
_pendingPromises: {},
sendTime(time) {
if (!this.connected) return null;
const id = uuidv4();
const promise = new Promise((resolve, reject) => {this._pendingPromises[id] = [resolve, reject]});
this._publish({
id: id, // used to prevent replay attacks and to identify confirm messages
kind: "Time", // can be "time" or "confirm"
time: time, // only used for "time"
});
return promise;
},
async connect() {
if (this.connected) return;
const password = Alpine.store("localState").password;
const that = this;
const brokerDomain = "broker.emqx.io";
const url = `wss://${brokerDomain}:8084/mqtt`;
if (!password) return false;
const {Crypto} = await wasm_inited;
Alpine.store("localState")._state = 5;
// derive key from password
this._c = new Crypto(password, brokerDomain);
console.log("Test", this._encrypt("test"));
this._topic = Crypto.sha256(
this._encrypt(`org.speedclimbing.ok-ready-go.${password}`)
).toString();
console.log("Connecting to MQTT broker...");
console.log("topic:", this._topic);
const options = {
// Clean session
clean: true,
connectTimeout: 4000,
};
this._client = mqtt.connect(url, options);
this._client.on("connect", () => {
Alpine.store("localState")._state = 0;
that._client.subscribe(that._topic);
this.connected = true;
});
this._client.on("message", (topic, message) => {
// message is Buffer
message = that._decrypt(message.toString());
const data = JSON.parse(message);
if (topic !== that._topic || data.kind !== "Confirm" || Object.keys(this._pendingPromises).indexOf(data.id) === -1) return;
console.log("<<< ", data);
this._pendingPromises[data.id][0]();
});
},
disconnect() {
if(!this.connected) return;
this._client.end(true);
this._client = null;
this.connected = false;
this._topic = null;
for (const promiseId in this._pendingPromises) {
this._pendingPromises[promiseId][1]();
}
this._pendingPromises = {};
},
_publish(data) {
const encryptedData = this._encrypt(JSON.stringify(data));
console.log(">>> ", data);
this._client.publish(this._topic, encryptedData, {
qos: 1,
retain: false,
});
},
_encrypt(data) {
return this._c.encrypt(data);
},
_decrypt(data) {
return this._c.decrypt(data);
},
});
});

280
lib/crypto_helper.js Normal file
View File

@ -0,0 +1,280 @@
let wasm_bindgen;
(function() {
const __exports = {};
let script_src;
if (typeof document === 'undefined') {
script_src = location.href;
} else {
script_src = new URL(document.currentScript.src, location.href).toString();
}
let wasm;
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachedUint8Memory0 = null;
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachedInt32Memory0 = null;
function getInt32Memory0() {
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
/**
*/
__exports.greet = function() {
wasm.greet();
};
/**
*/
class Crypto {
static __wrap(ptr) {
const obj = Object.create(Crypto.prototype);
obj.ptr = ptr;
return obj;
}
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_crypto_free(ptr);
}
/**
* @param {string} password
* @param {string} salt
*/
constructor(password, salt) {
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(salt, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.crypto_new(ptr0, len0, ptr1, len1);
return Crypto.__wrap(ret);
}
/**
* @param {string} input
* @returns {string}
*/
static sha256(input) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.crypto_sha256(retptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
}
}
/**
* @param {string} plaintext
* @returns {string}
*/
encrypt(plaintext) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(plaintext, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.crypto_encrypt(retptr, this.ptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
}
}
/**
* @param {string} ciphertext
* @returns {string}
*/
decrypt(ciphertext) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(ciphertext, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.crypto_decrypt(retptr, this.ptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(r0, r1);
}
}
}
__exports.Crypto = Crypto;
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function getImports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_alert_0cc0cb8b17d72dde = function(arg0, arg1) {
alert(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
return imports;
}
function initMemory(imports, maybe_memory) {
}
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = null;
cachedUint8Memory0 = null;
return wasm;
}
function initSync(module) {
const imports = getImports();
initMemory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return finalizeInit(instance, module);
}
async function init(input) {
if (typeof input === 'undefined') {
input = script_src.replace(/\.js$/, '_bg.wasm');
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
initMemory(imports);
const { instance, module } = await load(await input, imports);
return finalizeInit(instance, module);
}
wasm_bindgen = Object.assign(init, { initSync }, __exports);
})();

BIN
lib/crypto_helper_bg.wasm Normal file

Binary file not shown.