From a63f178eff65f4d992bf9761e10b8e671e66d9ca Mon Sep 17 00:00:00 2001 From: Reactor Scram Date: Wed, 10 Jan 2024 17:36:17 -0600 Subject: [PATCH] feat(windows): switch to the new auth flow per #2823 (#3147) Also refactored to extract an auth state machine. The auth logic previously was scattered throughout the GUI module, which would make it hard to audit. Because of the refactoring I was able to add some simple unit tests. --- rust/Cargo.lock | 370 +++++++++--------- rust/connlib/shared/src/control.rs | 2 +- rust/windows-client/src-tauri/Cargo.toml | 3 + rust/windows-client/src-tauri/src/client.rs | 1 + .../src-tauri/src/client/auth.rs | 354 +++++++++++++++++ .../src-tauri/src/client/deep_link.rs | 44 +-- .../src-tauri/src/client/gui.rs | 136 ++----- 7 files changed, 590 insertions(+), 320 deletions(-) mode change 100644 => 100755 rust/Cargo.lock create mode 100755 rust/windows-client/src-tauri/src/client/auth.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock old mode 100644 new mode 100755 index 6b13401d0..eaada709a --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -169,9 +169,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arboard" @@ -261,7 +261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener 4.0.1", + "event-listener 4.0.3", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -286,11 +286,11 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock 3.2.0", + "async-lock 3.3.0", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "slab", ] @@ -332,11 +332,11 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" dependencies = [ - "async-lock 3.2.0", + "async-lock 3.3.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "parking", "polling 3.3.1", "rustix 0.38.28", @@ -356,11 +356,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ - "event-listener 4.0.1", + "event-listener 4.0.3", "event-listener-strategy", "pin-project-lite", ] @@ -390,7 +390,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -413,19 +413,19 @@ dependencies = [ [[package]] name = "async-task" -version = "4.6.0" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -517,7 +517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.11", + "getrandom 0.2.12", "instant", "pin-project-lite", "rand 0.8.5", @@ -553,9 +553,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "base64ct" @@ -683,11 +683,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel", - "async-lock 3.2.0", + "async-lock 3.3.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.2.0", "piper", "tracing", ] @@ -738,9 +738,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -901,9 +901,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a" dependencies = [ "smallvec 1.11.2", "target-lexicon", @@ -974,20 +974,20 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", - "libloading 0.7.4", + "libloading 0.8.1", ] [[package]] name = "clap" -version = "4.4.13" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", @@ -995,9 +995,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", @@ -1014,7 +1014,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1169,7 +1169,7 @@ dependencies = [ name = "connlib-shared" version = "1.0.0" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "boringtun", "chrono", "domain", @@ -1258,9 +1258,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1291,45 +1291,37 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.16" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-bigint" @@ -1378,7 +1370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1388,7 +1380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1402,12 +1394,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" dependencies = [ "nix 0.27.1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1434,7 +1426,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1458,7 +1450,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1469,7 +1461,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1505,9 +1497,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -1590,15 +1582,16 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] name = "domain" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af83e443e4bfe8602af356e5ca10b9676634e53d178875017f2ff729898a388" +checksum = "e853e3f6d4c6e52a4d73a94c1810c66ad71958fbe24934a7119b447f425aed76" dependencies = [ + "bytes", "octseq", "rand 0.8.5", "serde", @@ -1669,11 +1662,12 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2" +checksum = "3bde55e389bea6a966bd467ad1ad7da0ae14546a5bc794d16d1e55e7fca44881" dependencies = [ "cc", + "memchr", "rustc_version", "toml 0.8.8", "vswhom", @@ -1704,7 +1698,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1725,7 +1719,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1786,9 +1780,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.1" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", @@ -1801,7 +1795,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 4.0.1", + "event-listener 4.0.3", "pin-project-lite", ] @@ -1822,9 +1816,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" dependencies = [ "simd-adler32", ] @@ -1966,7 +1960,7 @@ version = "1.0.0" dependencies = [ "anyhow", "axum", - "base64 0.21.5", + "base64 0.21.6", "bytecodec", "bytes", "clap", @@ -2055,13 +2049,16 @@ dependencies = [ "connlib-shared", "firezone-cli-utils", "git-version", + "hex", "hostname", "ipconfig", "keyring", + "rand 0.8.5", "ring 0.17.7", "secrecy", "serde", "serde_json", + "subtle", "tauri", "tauri-build", "tauri-utils", @@ -2132,9 +2129,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -2157,9 +2154,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -2167,15 +2164,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -2184,9 +2181,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -2205,9 +2202,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ "fastrand 2.0.1", "futures-core", @@ -2218,26 +2215,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -2247,9 +2244,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -2405,9 +2402,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -2477,7 +2474,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -2880,16 +2877,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.51.1", + "windows-core 0.52.0", ] [[package]] @@ -2939,9 +2936,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ "crossbeam-deque", "globset", @@ -3090,13 +3087,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix 0.38.28", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3223,16 +3220,16 @@ dependencies = [ [[package]] name = "keyring" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6488afbd1d8202dbd6e2dd38c0753d8c0adba9ac9985fc6f732a0d551f75e1" +checksum = "85b479dcf9eae65481044dfda57af7fe2da6c1401180360f6898801fe9ed4db9" dependencies = [ "byteorder", "lazy_static", "linux-keyutils", "secret-service", "security-framework", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3286,9 +3283,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -3484,9 +3481,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -3855,19 +3852,20 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "octseq" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd09a3d18c298e5d9b66cf65db82e2e1694b94fbc032f83e304a5cbc87bcc3bb" +checksum = "d92b38a4aabbacf619b8083841713216e7668178422decfe06bbc70643024c5d" dependencies = [ + "bytes", "serde", ] @@ -4134,7 +4132,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "serde", ] @@ -4257,7 +4255,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4291,7 +4289,7 @@ dependencies = [ name = "phoenix-channel" version = "1.0.0" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "futures", "rand_core 0.6.4", "secrecy", @@ -4321,7 +4319,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4365,9 +4363,9 @@ checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "platforms" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "plist" @@ -4375,7 +4373,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "indexmap 2.1.0", "line-wrap", "quick-xml", @@ -4401,7 +4399,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4560,9 +4558,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -4627,9 +4625,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -4694,7 +4692,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", ] [[package]] @@ -4802,7 +4800,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "libredox", "thiserror", ] @@ -4857,7 +4855,7 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "bytes", "encoding_rs", "futures-core", @@ -4959,7 +4957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom 0.2.11", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -5093,7 +5091,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", ] [[package]] @@ -5147,11 +5145,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -5291,18 +5289,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -5320,20 +5318,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa 1.0.10", "ryu", @@ -5342,13 +5340,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5378,7 +5376,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "chrono", "hex", "indexmap 1.9.3", @@ -5398,7 +5396,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5716,7 +5714,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5727,7 +5725,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5735,7 +5733,7 @@ name = "stun" version = "0.5.0" source = "git+https://github.com/firezone/webrtc?branch=expose-new-endpoint#4918ae812ab7e45b8ab000efbc316b5850290ddb" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "crc", "lazy_static", "md-5", @@ -5836,9 +5834,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -5903,7 +5901,7 @@ version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" dependencies = [ - "cfg-expr 0.15.5", + "cfg-expr 0.15.6", "heck 0.4.1", "pkg-config", "toml 0.8.8", @@ -5983,9 +5981,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.12" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "tauri" @@ -6060,7 +6058,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", "brotli", "ico", "json-patch", @@ -6177,15 +6175,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall", "rustix 0.38.28", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6201,9 +6199,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -6217,7 +6215,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -6228,22 +6226,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -6337,7 +6335,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -6467,7 +6465,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.5", + "base64 0.21.6", "bytes", "futures-core", "futures-util", @@ -6562,7 +6560,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -6771,7 +6769,7 @@ version = "0.7.0" source = "git+https://github.com/firezone/webrtc?branch=expose-new-endpoint#4918ae812ab7e45b8ab000efbc316b5850290ddb" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.21.6", "futures", "log", "md-5", @@ -6897,7 +6895,7 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.12", "serde", ] @@ -7021,7 +7019,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -7055,7 +7053,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7537,7 +7535,7 @@ checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -7548,7 +7546,7 @@ checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -7847,9 +7845,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.30" +version = "0.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" +checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa" dependencies = [ "memchr", ] @@ -8000,9 +7998,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" dependencies = [ "libc", "linux-raw-sys 0.4.12", @@ -8111,7 +8109,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] diff --git a/rust/connlib/shared/src/control.rs b/rust/connlib/shared/src/control.rs index e4f4d640e..8b3baa0c1 100644 --- a/rust/connlib/shared/src/control.rs +++ b/rust/connlib/shared/src/control.rs @@ -34,7 +34,7 @@ pub type Reference = String; // TODO: Refactor this PhoenixChannel to use the top-level phoenix-channel crate instead. // See https://github.com/firezone/firezone/issues/2158 pub struct SecureUrl { - inner: Url, + pub inner: Url, } impl SecureUrl { pub fn from_url(url: Url) -> Self { diff --git a/rust/windows-client/src-tauri/Cargo.toml b/rust/windows-client/src-tauri/Cargo.toml index 04648f5c4..af6a17f45 100755 --- a/rust/windows-client/src-tauri/Cargo.toml +++ b/rust/windows-client/src-tauri/Cargo.toml @@ -18,6 +18,7 @@ clap = { version = "4.4", features = ["derive", "env"] } connlib-client-shared = { workspace = true } connlib-shared = { workspace = true } firezone-cli-utils = { workspace = true } +hex = "0.4.3" git-version = "0.3.9" # Same crate Hickory uses hostname = "0.3.1" @@ -28,6 +29,7 @@ ring = "0.17" secrecy = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +subtle = "2.5.0" thiserror = { version = "1.0", default-features = false } tokio = { version = "1.33.0", features = ["time"] } tracing = { workspace = true } @@ -37,6 +39,7 @@ url = { version = "2.5.0", features = ["serde"] } uuid = { version = "1.5.0", features = ["v4"] } tracing-panic = "0.1.1" zip = { version = "0.6.6", features = ["deflate", "time"], default-features = false } +rand = "0.8.5" windows-implement = "0.52.0" # These dependencies are locked behind `cfg(windows)` because they either can't compile at all on Linux, or they need native dependencies like glib that are difficult to get. Try not to add more here. diff --git a/rust/windows-client/src-tauri/src/client.rs b/rust/windows-client/src-tauri/src/client.rs index e823bb20f..0b6c38499 100644 --- a/rust/windows-client/src-tauri/src/client.rs +++ b/rust/windows-client/src-tauri/src/client.rs @@ -3,6 +3,7 @@ use clap::Parser; use cli::CliCommands as Cmd; use std::{os::windows::process::CommandExt, process::Command}; +mod auth; mod cli; mod debug_commands; mod deep_link; diff --git a/rust/windows-client/src-tauri/src/client/auth.rs b/rust/windows-client/src-tauri/src/client/auth.rs new file mode 100755 index 000000000..096d2017a --- /dev/null +++ b/rust/windows-client/src-tauri/src/client/auth.rs @@ -0,0 +1,354 @@ +//! Fulfills + +use connlib_shared::control::SecureUrl; +use rand::{thread_rng, RngCore}; +use secrecy::{ExposeSecret, Secret, SecretString}; +use subtle::ConstantTimeEq; +use url::Url; + +const NONCE_LENGTH: usize = 32; + +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error(transparent)] + Keyring(#[from] keyring::Error), + #[error("No in-flight request")] + NoInflightRequest, + #[error("State in server response doesn't match state in client request")] + StatesDontMatch, +} + +type Result = std::result::Result; + +pub(crate) struct Auth { + /// Key for secure keyrings, e.g. "dev.firezone.client/token" for releases + /// and something else for automated tests of the auth module. + keyring_key: &'static str, + state: State, +} + +pub(crate) enum State { + SignedOut, + // TODO: Need a way to time out this state if the server never signs us in + NeedResponse(Request), + SignedIn(Session), +} + +pub(crate) struct Request { + nonce: SecretString, + state: SecretString, +} + +impl Request { + pub fn to_url(&self, auth_base_url: &Url) -> Secret { + let mut url = auth_base_url.clone(); + url.query_pairs_mut() + .append_pair("as", "client") + .append_pair("nonce", self.nonce.expose_secret()) + .append_pair("state", self.state.expose_secret()); + Secret::from(SecureUrl::from_url(url)) + } +} + +pub(crate) struct Response { + pub actor_name: String, + pub fragment: SecretString, + pub state: SecretString, +} + +pub(crate) struct Session { + pub actor_name: String, +} + +impl Auth { + /// Creates a new Auth struct using the "dev.firezone.client/token" keyring key. If the token is stored on disk, the struct is automatically signed in. + /// + /// Performs I/O. + pub fn new() -> Result { + Self::new_with_key("dev.firezone.client/token") + } + + /// Creates a new Auth struct with a custom keyring key for testing. + fn new_with_key(keyring_key: &'static str) -> Result { + let mut this = Self { + keyring_key, + state: State::SignedOut, + }; + + if this.get_token_from_disk()?.is_some() { + this.state = State::SignedIn(Session { + // TODO: Save and reload actor name to/from disk + actor_name: "TODO".to_string(), + }); + tracing::debug!("Reloaded token"); + } + + Ok(this) + } + + pub fn session(&self) -> Option<&Session> { + match &self.state { + State::SignedIn(x) => Some(x), + _ => None, + } + } + + /// Mark the session as signed out, or cancel an ongoing sign-in flow + /// + /// Performs I/O. + pub fn sign_out(&mut self) -> Result<()> { + // TODO: After we store the actor name on disk, clear the actor name here too. + match self.keyring_entry()?.delete_password() { + Ok(_) | Err(keyring::Error::NoEntry) => {} + Err(e) => Err(e)?, + } + self.state = State::SignedOut; + Ok(()) + } + + /// Start a new sign-in flow, replacing any ongoing flow + /// + /// Returns parameters used to make a URL for the web browser to open + /// May return Ok(None) if we're already signed in + pub fn start_sign_in(&mut self) -> Result> { + self.sign_out()?; + self.state = State::NeedResponse(Request { + nonce: generate_nonce(), + state: generate_nonce(), + }); + Ok(Some(self.ongoing_request()?)) + } + + /// Complete an ongoing sign-in flow using parameters from a deep link + /// + /// Returns a valid token. + /// Performs I/O. + /// + /// Errors if we don't have any ongoing flow, or if the response is invalid + pub fn handle_response(&mut self, resp: Response) -> Result { + let req = self.ongoing_request()?; + + if !secure_equality(&resp.state, &req.state) { + self.sign_out()?; + return Err(Error::StatesDontMatch); + } + + let token = format!( + "{}{}", + req.nonce.expose_secret(), + resp.fragment.expose_secret() + ); + self.keyring_entry()?.set_password(&token)?; + self.state = State::SignedIn(Session { + actor_name: resp.actor_name, + }); + Ok(SecretString::from(token)) + } + + /// Returns the token if we are signed in + /// + /// This may always make syscalls, but it should be fast enough for normal use. + pub fn token(&self) -> Result> { + match self.state { + State::SignedIn(_) => {} + _ => return Ok(None), + } + + self.get_token_from_disk() + } + + /// Retrieves the token from disk regardless of in-memory state + /// + /// Performs I/O + fn get_token_from_disk(&self) -> Result> { + Ok(match self.keyring_entry()?.get_password() { + Err(keyring::Error::NoEntry) => None, + Ok(token) => Some(SecretString::from(token)), + Err(e) => Err(e)?, + }) + } + + /// Returns an Entry into the OS' credential manager + /// + /// Anything you do in there is technically blocking I/O. + fn keyring_entry(&self) -> Result { + Ok(keyring::Entry::new_with_target(self.keyring_key, "", "")?) + } + + fn ongoing_request(&self) -> Result<&Request> { + match &self.state { + State::NeedResponse(x) => Ok(x), + _ => Err(Error::NoInflightRequest), + } + } +} + +/// Generates a random nonce using a CSPRNG, then returns it as hexadecimal +fn generate_nonce() -> SecretString { + let mut buf = [0u8; NONCE_LENGTH]; + // rand's thread-local RNG is said to be cryptographically secure here: https://docs.rs/rand/latest/rand/rngs/struct.ThreadRng.html + thread_rng().fill_bytes(&mut buf); + + // Make sure it's not somehow all still zeroes. + assert_ne!(buf, [0u8; NONCE_LENGTH]); + hex::encode(buf).into() +} + +/// Checks if two byte strings are equal in constant-time. +/// May not be constant-time if the lengths differ: +/// +fn secure_equality(a: &SecretString, b: &SecretString) -> bool { + let a = a.expose_secret().as_bytes(); + let b = b.expose_secret().as_bytes(); + a.ct_eq(b).into() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn bogus_secret(x: &str) -> SecretString { + SecretString::new(x.into()) + } + + #[test] + fn utils() { + // This doesn't test for constant-time properties, it just makes sure the function + // gives the right result + let f = |a: &str, b: &str| secure_equality(&bogus_secret(a), &bogus_secret(b)); + + assert!(f("1234", "1234")); + assert!(!f("1234", "123")); + assert!(!f("1234", "1235")); + + let hex_string = generate_nonce(); + let hex_string = hex_string.expose_secret(); + assert_eq!(hex_string.len(), NONCE_LENGTH * 2); + dbg!(hex_string); + + let auth_base_url = Url::parse("https://app.firez.one").unwrap(); + let req = Request { + nonce: bogus_secret("some_nonce"), + state: bogus_secret("some_state"), + }; + assert_eq!( + req.to_url(&auth_base_url).expose_secret().inner, + Url::parse("https://app.firez.one?as=client&nonce=some_nonce&state=some_state") + .unwrap() + ); + } + + #[test] + fn happy_path() { + // Start the program + let mut state = + Auth::new_with_key("dev.firezone.client/test_DMRCZ67A_happy_path/token").unwrap(); + + // Delete any token on disk from previous test runs + state.sign_out().unwrap(); + assert!(state.token().unwrap().is_none()); + + // User clicks "Sign In", build a fake server response + let req = state.start_sign_in().unwrap().unwrap(); + + let resp = Response { + actor_name: "Jane Doe".into(), + fragment: bogus_secret("fragment"), + state: req.state.clone(), + }; + assert!(state.token().unwrap().is_none()); + + // Handle deep link from the server, now we are signed in and have a token + state.handle_response(resp).unwrap(); + assert!(state.token().unwrap().is_some()); + + // Accidentally sign in again, this can happen if the user holds the systray menu open while a sign in is succeeding. + // For now, we treat that like signing out and back in immediately, so it wipes the old token. + // TODO: That sounds wrong. + assert!(state.start_sign_in().unwrap().is_some()); + assert!(state.token().unwrap().is_none()); + + // Sign out again, now the token is gone + state.sign_out().unwrap(); + assert!(state.token().unwrap().is_none()); + } + + #[test] + fn no_inflight_request() { + // Start the program + let mut state = + Auth::new_with_key("dev.firezone.client/test_DMRCZ67A_invalid_response/token").unwrap(); + + // Delete any token on disk from previous test runs + state.sign_out().unwrap(); + assert!(state.token().unwrap().is_none()); + + // If we get a deep link with no in-flight request, it's invalid + let r = state.handle_response(Response { + actor_name: "Jane Doe".into(), + fragment: bogus_secret("fragment"), + state: bogus_secret("state"), + }); + + match r { + Err(Error::NoInflightRequest) => {} + _ => panic!("Expected NoInflightRequest error"), + } + + // Clean up the test token + state.sign_out().unwrap(); + } + + #[test] + fn states_dont_match() { + // Start the program + let mut state = + Auth::new_with_key("dev.firezone.client/test_DMRCZ67A_states_dont_match/token") + .unwrap(); + + // Delete any token on disk from previous test runs + state.sign_out().unwrap(); + assert!(state.token().unwrap().is_none()); + + // User clicks "Sign In", build a fake server response + state.start_sign_in().unwrap(); + let resp = Response { + actor_name: "Jane Doe".into(), + fragment: bogus_secret("fragment"), + state: SecretString::from( + "bogus state from a replay attack or browser mis-click".to_string(), + ), + }; + assert!(state.token().unwrap().is_none()); + + // Handle deep link from the server, we should get an error + let r = state.handle_response(resp); + match r { + Err(Error::StatesDontMatch) => {} + _ => panic!("Expected StatesDontMatch error"), + } + assert!(state.token().unwrap().is_none()); + } + + #[test] + fn test_keyring() -> anyhow::Result<()> { + // I used this test to find that `service` is not used - We have to namespace on our own. + + let name_1 = "dev.firezone.client/test_1/token"; + let name_2 = "dev.firezone.client/test_2/token"; + + keyring::Entry::new_with_target(name_1, "", "")?.set_password("test_password_1")?; + + keyring::Entry::new_with_target(name_2, "", "")?.set_password("test_password_2")?; + + let actual = keyring::Entry::new_with_target(name_1, "", "")?.get_password()?; + let expected = "test_password_1"; + + assert_eq!(actual, expected); + + keyring::Entry::new_with_target(name_1, "", "")?.delete_password()?; + keyring::Entry::new_with_target(name_2, "", "")?.delete_password()?; + + Ok(()) + } +} diff --git a/rust/windows-client/src-tauri/src/client/deep_link.rs b/rust/windows-client/src-tauri/src/client/deep_link.rs index 767387b7a..7552ea19e 100755 --- a/rust/windows-client/src-tauri/src/client/deep_link.rs +++ b/rust/windows-client/src-tauri/src/client/deep_link.rs @@ -1,6 +1,7 @@ //! A module for registering, catching, and parsing deep links that are sent over to the app's already-running instance //! Based on reading some of the Windows code from , which is licensed "MIT OR Apache-2.0" +use crate::client::auth::Response as AuthResponse; use secrecy::SecretString; use std::{ffi::c_void, io, path::Path}; use tokio::{io::AsyncReadExt, io::AsyncWriteExt, net::windows::named_pipe}; @@ -34,15 +35,9 @@ pub enum Error { WindowsRegistry(io::Error), } -pub(crate) struct AuthCallback { - pub actor_name: String, - pub token: SecretString, - pub _identifier: SecretString, -} - -pub(crate) fn parse_auth_callback(url: &url::Url) -> Option { +pub(crate) fn parse_auth_callback(url: &url::Url) -> Option { match url.host() { - Some(url::Host::Domain("handle_client_auth_callback")) => {} + Some(url::Host::Domain("handle_client_sign_in_callback")) => {} _ => return None, } if url.path() != "/" { @@ -50,8 +45,8 @@ pub(crate) fn parse_auth_callback(url: &url::Url) -> Option { } let mut actor_name = None; - let mut token = None; - let mut identifier = None; + let mut fragment = None; + let mut state = None; for (key, value) in url.query_pairs() { match key.as_ref() { @@ -62,28 +57,28 @@ pub(crate) fn parse_auth_callback(url: &url::Url) -> Option { } actor_name = Some(value.to_string()); } - "client_auth_token" => { - if token.is_some() { - // client_auth_token must appear exactly once + "fragment" => { + if fragment.is_some() { + // must appear exactly once return None; } - token = Some(SecretString::new(value.to_string())); + fragment = Some(SecretString::new(value.to_string())); } - "identity_provider_identifier" => { - if identifier.is_some() { - // identity_provider_identifier must appear exactly once + "state" => { + if state.is_some() { + // must appear exactly once return None; } - identifier = Some(SecretString::new(value.to_string())); + state = Some(SecretString::new(value.to_string())); } _ => {} } } - Some(AuthCallback { + Some(AuthResponse { actor_name: actor_name?, - token: token?, - _identifier: identifier?, + fragment: fragment?, + state: state?, }) } @@ -235,15 +230,16 @@ mod tests { #[test] fn parse_auth_callback() -> Result<()> { - let input = "firezone://handle_client_auth_callback/?actor_name=Reactor+Scram&client_auth_token=a_very_secret_string&identity_provider_identifier=12345"; + let input = "firezone://handle_client_sign_in_callback/?actor_name=Reactor+Scram&fragment=a_very_secret_string&state=a_less_secret_string&identity_provider_identifier=12345"; let input = url::Url::parse(input)?; dbg!(&input); let actual = super::parse_auth_callback(&input).unwrap(); assert_eq!(actual.actor_name, "Reactor Scram"); - assert_eq!(actual.token.expose_secret(), "a_very_secret_string"); + assert_eq!(actual.fragment.expose_secret(), "a_very_secret_string"); + assert_eq!(actual.state.expose_secret(), "a_less_secret_string"); - let input = "firezone://not_handle_client_auth_callback/?actor_name=Reactor+Scram&client_auth_token=a_very_secret_string&identity_provider_identifier=12345"; + let input = "firezone://not_handle_client_sign_in_callback/?actor_name=Reactor+Scram&fragment=a_very_secret_string&state=a_less_secret_string&identity_provider_identifier=12345"; let actual = super::parse_auth_callback(&url::Url::parse(input)?); assert!(actual.is_none()); diff --git a/rust/windows-client/src-tauri/src/client/gui.rs b/rust/windows-client/src-tauri/src/client/gui.rs index 83b74775e..65d4dbe01 100755 --- a/rust/windows-client/src-tauri/src/client/gui.rs +++ b/rust/windows-client/src-tauri/src/client/gui.rs @@ -16,10 +16,7 @@ use secrecy::{ExposeSecret, SecretString}; use std::{net::IpAddr, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; use system_tray_menu::Event as TrayMenuEvent; use tauri::{Manager, SystemTray, SystemTrayEvent}; -use tokio::{ - sync::{mpsc, oneshot, Notify}, - task::spawn_blocking, -}; +use tokio::sync::{mpsc, oneshot, Notify}; use ControllerRequest as Req; mod system_tray_menu; @@ -224,16 +221,6 @@ pub(crate) enum ControllerRequest { SignOut, } -// TODO: Should these be keyed to the Google ID or email or something? -// The callback returns a human-readable name but those aren't good keys. -fn keyring_entry() -> Result { - Ok(keyring::Entry::new_with_target( - "dev.firezone.client/token", - "", - "", - )?) -} - #[derive(Clone)] struct CallbackHandler { logger: file_logger::Handle, @@ -298,8 +285,10 @@ struct Controller { /// Debugging-only settings like API URL, auth URL, log filter advanced_settings: AdvancedSettings, app: tauri::AppHandle, + // Sign-in state + auth: client::auth::Auth, ctlr_tx: CtlrTx, - /// Session for the currently signed-in user, if there is one + /// connlib session for the currently signed-in user, if there is one session: Option, /// The UUIDv4 device ID persisted to disk /// Sent verbatim to Session::connect @@ -312,19 +301,10 @@ struct Controller { /// Everything related to a signed-in user session struct Session { - auth_info: AuthInfo, callback_handler: CallbackHandler, connlib: connlib_client_shared::Session, } -/// Auth info that's persisted to disk if a session outlives an app instance -struct AuthInfo { - /// User name, e.g. "John Doe", from the sign-in deep link - actor_name: String, - /// Secret token to authenticate with the portal - token: SecretString, -} - impl Controller { async fn new( app: tauri::AppHandle, @@ -338,6 +318,7 @@ impl Controller { let mut this = Self { advanced_settings, app, + auth: client::auth::Auth::new()?, ctlr_tx, session: None, device_id, @@ -346,32 +327,9 @@ impl Controller { notify_controller, }; - tracing::trace!("re-loading token"); - // spawn_blocking because accessing the keyring is I/O - if let Some(auth_info) = spawn_blocking(|| { - let entry = keyring_entry()?; - match entry.get_password() { - Ok(token) => { - let token = SecretString::new(token); - tracing::debug!("re-loaded token from Windows credential manager"); - let auth_info = AuthInfo { - // TODO: Reload actor name from disk here - actor_name: "TODO".to_string(), - token, - }; - Ok(Some(auth_info)) - } - Err(keyring::Error::NoEntry) => { - tracing::debug!("no token in Windows credential manager"); - Ok(None) - } - Err(e) => Err(anyhow::Error::from(e)), - } - }) - .await?? - { + if let Some(token) = this.auth.token()? { // Connect immediately if we reloaded the token - if let Err(e) = this.start_session(auth_info) { + if let Err(e) = this.start_session(token) { tracing::error!("couldn't restart session on app start: {e:#?}"); } } @@ -380,7 +338,8 @@ impl Controller { } // TODO: Figure out how re-starting sessions automatically will work - fn start_session(&mut self, auth_info: AuthInfo) -> Result<()> { + /// Pre-req: the auth module must be signed in + fn start_session(&mut self, token: SecretString) -> Result<()> { if self.session.is_some() { bail!("can't start session, we're already in a session"); } @@ -394,7 +353,7 @@ impl Controller { let connlib = connlib_client_shared::Session::connect( self.advanced_settings.api_url.clone(), - auth_info.token.clone(), + token, self.device_id.clone(), None, // TODO: Send device name here (windows computer name) None, @@ -403,7 +362,6 @@ impl Controller { )?; self.session = Some(Session { - auth_info, callback_handler, connlib, }); @@ -430,28 +388,13 @@ impl Controller { } async fn handle_deep_link(&mut self, url: &url::Url) -> Result<()> { - let Some(auth) = client::deep_link::parse_auth_callback(url) else { + let Some(auth_response) = client::deep_link::parse_auth_callback(url) else { // TODO: `bail` is redundant here, just do `.context("")?;` since it's `anyhow` bail!("couldn't parse scheme request"); }; - let token = auth.token.clone(); - // spawn_blocking because keyring access is I/O - if let Err(e) = spawn_blocking(move || { - let entry = keyring_entry()?; - entry.set_password(token.expose_secret())?; - Ok::<_, anyhow::Error>(()) - }) - .await? - { - tracing::error!("couldn't save token to keyring: {e:#?}"); - } - - let auth_info = AuthInfo { - actor_name: auth.actor_name, - token: auth.token, - }; - if let Err(e) = self.start_session(auth_info) { + let token = self.auth.handle_response(auth_response)?; + if let Err(e) = self.start_session(token) { // TODO: Replace `bail` with `context` here too bail!("couldn't start session: {e:#?}"); } @@ -464,12 +407,11 @@ impl Controller { return Ok(()); }; let resources = session.callback_handler.resources.load(); - // TODO: Save the user name between runs of the app - let actor_name = self - .session - .as_ref() - .map(|x| x.auth_info.actor_name.as_str()) - .unwrap_or("TODO"); + let actor_name = &self + .auth + .session() + .ok_or_else(|| anyhow!("connlib is signed in but auth module isn't"))? + .actor_name; self.app .tray_handle() .set_menu(system_tray_menu::signed_in(actor_name, &resources))?; @@ -524,16 +466,17 @@ async fn run_controller( tracing::error!("couldn't handle deep link: {e:#?}"); } Req::SignIn => { - // TODO: Put the platform and local server callback in here - tauri::api::shell::open( - &app.shell_scope(), - &controller.advanced_settings.auth_base_url, - None, - )?; + if let Some(req) = controller.auth.start_sign_in()? { + let url = req.to_url(&controller.advanced_settings.auth_base_url); + tauri::api::shell::open( + &app.shell_scope(), + &url.expose_secret().inner, + None, + )?; + } } Req::SignOut => { - // TODO: After we store the actor name on disk, clear the actor name here too. - keyring_entry()?.delete_password()?; + controller.auth.sign_out()?; if let Some(mut session) = controller.session.take() { tracing::debug!("disconnecting connlib"); session.connlib.disconnect(None); @@ -563,28 +506,3 @@ async fn run_controller( tracing::debug!("GUI controller task exiting cleanly"); Ok(()) } - -#[cfg(test)] -mod tests { - #[test] - fn test_keyring() -> anyhow::Result<()> { - // I used this test to find that `service` is not used - We have to namespace on our own. - - let name_1 = "dev.firezone.client/test_1/token"; - let name_2 = "dev.firezone.client/test_2/token"; - - keyring::Entry::new_with_target(name_1, "", "")?.set_password("test_password_1")?; - - keyring::Entry::new_with_target(name_2, "", "")?.set_password("test_password_2")?; - - let actual = keyring::Entry::new_with_target(name_1, "", "")?.get_password()?; - let expected = "test_password_1"; - - assert_eq!(actual, expected); - - keyring::Entry::new_with_target(name_1, "", "")?.delete_password()?; - keyring::Entry::new_with_target(name_2, "", "")?.delete_password()?; - - Ok(()) - } -}