diff --git a/crypto_helper/.gitignore b/crypto_helper/.gitignore new file mode 100644 index 0000000..1711838 --- /dev/null +++ b/crypto_helper/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +bin/ +pkg/ +wasm-pack.log diff --git a/crypto_helper/Cargo.lock b/crypto_helper/Cargo.lock new file mode 100644 index 0000000..a21755a --- /dev/null +++ b/crypto_helper/Cargo.lock @@ -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" diff --git a/crypto_helper/Cargo.toml b/crypto_helper/Cargo.toml new file mode 100644 index 0000000..b811977 --- /dev/null +++ b/crypto_helper/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "crypto_helper" +version = "0.1.0" +authors = ["Dorian Zedler "] +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" diff --git a/crypto_helper/README.md b/crypto_helper/README.md new file mode 100644 index 0000000..be29ebd --- /dev/null +++ b/crypto_helper/README.md @@ -0,0 +1,86 @@ +
+ +

wasm-pack-template

+ + A template for kick starting a Rust and WebAssembly project using wasm-pack. + +

+ Build Status +

+ +

+ Tutorial + | + Chat +

+ + Built with 🦀🕸 by The Rust and WebAssembly Working Group +
+ +## 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. \ No newline at end of file diff --git a/crypto_helper/src/crypto.rs b/crypto_helper/src/crypto.rs new file mode 100644 index 0000000..7b24c41 --- /dev/null +++ b/crypto_helper/src/crypto.rs @@ -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; +type IV = GenericArray, 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 { + 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() + } +} \ No newline at end of file diff --git a/crypto_helper/src/lib.rs b/crypto_helper/src/lib.rs new file mode 100644 index 0000000..d5df469 --- /dev/null +++ b/crypto_helper/src/lib.rs @@ -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!"); +} diff --git a/crypto_helper/src/utils.rs b/crypto_helper/src/utils.rs new file mode 100644 index 0000000..b1d7929 --- /dev/null +++ b/crypto_helper/src/utils.rs @@ -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(); +} diff --git a/crypto_helper/tests/web.rs b/crypto_helper/tests/web.rs new file mode 100644 index 0000000..de5c1da --- /dev/null +++ b/crypto_helper/tests/web.rs @@ -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); +} diff --git a/index.css b/index.css index 55b1bfb..bdc80aa 100644 --- a/index.css +++ b/index.css @@ -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; } \ No newline at end of file diff --git a/index.html b/index.html index 1fde5e4..95a841c 100644 --- a/index.html +++ b/index.html @@ -6,13 +6,15 @@ - - + + - + + + - + @@ -28,21 +30,50 @@ -
-
-
- - -
-

OK! .. READY! ... GO!

-

-
+ +
+
+
+
+
+

OK! .. READY! ... GO!

+

+
-

+

+ +
+
+ Remote connection + +
+

To send the times to a computer, please enter a password here:

+
+ + + Make sure, this is exactly the same on your computer! + +
+
+ +
+

Connected

+ +
+
+
+ +
+

Connecting...

+
\ No newline at end of file diff --git a/js/index.js b/js/index.js index 78bd920..06e5afa 100644 --- a/js/index.js +++ b/js/index.js @@ -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 - }, - }) - - - -}) \ No newline at end of file + this.time = time.toFixed(2); + }, 10); + }, + }; +} \ No newline at end of file diff --git a/js/localState.js b/js/localState.js new file mode 100644 index 0000000..18513c2 --- /dev/null +++ b/js/localState.js @@ -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; + }, + }); +}); diff --git a/js/mqtt.js b/js/mqtt.js new file mode 100644 index 0000000..0c0b6ea --- /dev/null +++ b/js/mqtt.js @@ -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); + }, + }); +}); diff --git a/lib/crypto_helper.js b/lib/crypto_helper.js new file mode 100644 index 0000000..789d119 --- /dev/null +++ b/lib/crypto_helper.js @@ -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); + +})(); diff --git a/lib/crypto_helper_bg.wasm b/lib/crypto_helper_bg.wasm new file mode 100644 index 0000000..baf2cec Binary files /dev/null and b/lib/crypto_helper_bg.wasm differ