From b4c503c11628f1cb24d967450fe41ac62ae42bcb Mon Sep 17 00:00:00 2001 From: FabianLars Date: Tue, 3 Sep 2024 10:30:12 +0200 Subject: [PATCH] Remove authenticator plugin --- .changes/config.json | 9 - .../workflows/covector-version-or-publish.yml | 4 +- .github/workflows/lint-rust.yml | 4 +- .github/workflows/msrv-check.yml | 4 +- Cargo.lock | 76 ------- Cargo.toml | 1 - README.md | 1 - plugins/authenticator/.gitignore | 1 - plugins/authenticator/Cargo.toml | 29 --- plugins/authenticator/LICENSE.spdx | 20 -- plugins/authenticator/LICENSE_APACHE-2.0 | 177 --------------- plugins/authenticator/LICENSE_MIT | 21 -- plugins/authenticator/README.md | 126 ----------- plugins/authenticator/banner.png | Bin 44877 -> 0 bytes plugins/authenticator/guest-js/index.ts | 60 ----- plugins/authenticator/package.json | 33 --- plugins/authenticator/rollup.config.mjs | 11 - plugins/authenticator/src/auth.rs | 212 ------------------ plugins/authenticator/src/error.rs | 22 -- plugins/authenticator/src/lib.rs | 77 ------- plugins/authenticator/src/u2f.rs | 105 --------- plugins/authenticator/src/u2f_crate/LICENSE | 8 - .../src/u2f_crate/authorization.rs | 65 ------ plugins/authenticator/src/u2f_crate/crypto.rs | 156 ------------- .../authenticator/src/u2f_crate/messages.rs | 55 ----- plugins/authenticator/src/u2f_crate/mod.rs | 12 - .../authenticator/src/u2f_crate/protocol.rs | 191 ---------------- .../authenticator/src/u2f_crate/register.rs | 101 --------- .../authenticator/src/u2f_crate/u2ferror.rs | 39 ---- plugins/authenticator/src/u2f_crate/util.rs | 66 ------ plugins/authenticator/tsconfig.json | 4 - plugins/mirrors.txt | 1 - pnpm-lock.yaml | 10 - 33 files changed, 6 insertions(+), 1695 deletions(-) delete mode 100644 plugins/authenticator/.gitignore delete mode 100644 plugins/authenticator/Cargo.toml delete mode 100644 plugins/authenticator/LICENSE.spdx delete mode 100644 plugins/authenticator/LICENSE_APACHE-2.0 delete mode 100644 plugins/authenticator/LICENSE_MIT delete mode 100644 plugins/authenticator/README.md delete mode 100644 plugins/authenticator/banner.png delete mode 100644 plugins/authenticator/guest-js/index.ts delete mode 100644 plugins/authenticator/package.json delete mode 100644 plugins/authenticator/rollup.config.mjs delete mode 100644 plugins/authenticator/src/auth.rs delete mode 100644 plugins/authenticator/src/error.rs delete mode 100644 plugins/authenticator/src/lib.rs delete mode 100644 plugins/authenticator/src/u2f.rs delete mode 100644 plugins/authenticator/src/u2f_crate/LICENSE delete mode 100644 plugins/authenticator/src/u2f_crate/authorization.rs delete mode 100644 plugins/authenticator/src/u2f_crate/crypto.rs delete mode 100644 plugins/authenticator/src/u2f_crate/messages.rs delete mode 100644 plugins/authenticator/src/u2f_crate/mod.rs delete mode 100644 plugins/authenticator/src/u2f_crate/protocol.rs delete mode 100644 plugins/authenticator/src/u2f_crate/register.rs delete mode 100644 plugins/authenticator/src/u2f_crate/u2ferror.rs delete mode 100644 plugins/authenticator/src/u2f_crate/util.rs delete mode 100644 plugins/authenticator/tsconfig.json diff --git a/.changes/config.json b/.changes/config.json index 70ddfdd5..d9cf7f67 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -33,15 +33,6 @@ } }, "packages": { - "authenticator": { - "path": "./plugins/authenticator", - "manager": "rust-disabled" - }, - "authenticator-js": { - "path": "./plugins/authenticator", - "manager": "javascript-disabled" - }, - "autostart": { "path": "./plugins/autostart", "manager": "rust-disabled" diff --git a/.github/workflows/covector-version-or-publish.yml b/.github/workflows/covector-version-or-publish.yml index 61498f3d..d5b04f81 100644 --- a/.github/workflows/covector-version-or-publish.yml +++ b/.github/workflows/covector-version-or-publish.yml @@ -29,10 +29,10 @@ jobs: version: 9.x.x run_install: true - - name: install webkit2gtk and libudev for [authenticator] + - name: install webkit2gtk run: | sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev - name: cargo login run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }} diff --git a/.github/workflows/lint-rust.yml b/.github/workflows/lint-rust.yml index f724e838..014cfd07 100644 --- a/.github/workflows/lint-rust.yml +++ b/.github/workflows/lint-rust.yml @@ -31,10 +31,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: install webkit2gtk and libudev for [authenticator] + - name: install webkit2gtk run: | sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev - name: Install clippy with stable toolchain uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/msrv-check.yml b/.github/workflows/msrv-check.yml index 3e8acb3d..9370fe12 100644 --- a/.github/workflows/msrv-check.yml +++ b/.github/workflows/msrv-check.yml @@ -33,10 +33,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: install webkit2gtk and libudev for [authenticator] + - name: install webkit2gtk run: | sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev - uses: dtolnay/rust-toolchain@1.67.0 diff --git a/Cargo.lock b/Cargo.lock index b251aeac..ca84701a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,23 +330,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" -[[package]] -name = "authenticator" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08cee7a0952628fde958e149507c2bb321ab4fccfafd225da0b20adc956ef88a" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "devd-rs", - "libc", - "libudev", - "log", - "rand 0.7.3", - "runloop", - "winapi", -] - [[package]] name = "auto-launch" version = "0.5.0" @@ -670,10 +653,8 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", "serde", - "wasm-bindgen", "windows-targets 0.48.1", ] @@ -1040,16 +1021,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "devd-rs" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9313f104b590510b46fc01c0a324fc76505c13871454d3c48490468d04c8d395" -dependencies = [ - "libc", - "nom", -] - [[package]] name = "digest" version = "0.9.0" @@ -2475,26 +2446,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libudev" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" -dependencies = [ - "libc", - "libudev-sys", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "line-wrap" version = "0.1.1" @@ -3721,12 +3672,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "runloop" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd" - [[package]] name = "rust-argon2" version = "1.0.0" @@ -4861,27 +4806,6 @@ dependencies = [ "tauri-utils", ] -[[package]] -name = "tauri-plugin-authenticator" -version = "0.0.0" -dependencies = [ - "authenticator", - "base64 0.22.0", - "byteorder", - "bytes", - "chrono", - "log", - "once_cell", - "openssl", - "rand 0.8.5", - "rusty-fork", - "serde", - "serde_json", - "sha2 0.10.7", - "tauri", - "thiserror", -] - [[package]] name = "tauri-plugin-autostart" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 7a2d143d..ad325cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] # Listed without globs to prevent issues with renovate's baseBranches config. members = [ - "plugins/authenticator", "plugins/autostart", "plugins/fs-extra", "plugins/fs-watch", diff --git a/README.md b/README.md index 6c765f76..932e7b1d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ | | | Win | Mac | Lin | iOS | And | | ------------------------------------------ | --------------------------------------------------------- | --- | --- | --- | --- | --- | -| [authenticator](plugins/authenticator) | Interface with hardware security keys. | ✅ | ✅ | ✅ | ? | ? | | [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | ? | ? | | [fs-extra](plugins/fs-extra) | File system methods that aren't included in the core API. | ✅ | ✅ | ✅ | ? | ? | | [fs-watch](plugins/fs-watch) | Watch the filesystem for changes. | ✅ | ✅ | ✅ | ? | ? | diff --git a/plugins/authenticator/.gitignore b/plugins/authenticator/.gitignore deleted file mode 100644 index b512c09d..00000000 --- a/plugins/authenticator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/plugins/authenticator/Cargo.toml b/plugins/authenticator/Cargo.toml deleted file mode 100644 index e7cdc77b..00000000 --- a/plugins/authenticator/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tauri-plugin-authenticator" -version = "0.0.0" -description = "Use hardware security-keys in your Tauri App." -authors = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -rust-version = { workspace = true } - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -tauri = { workspace = true } -log = { workspace = true } -thiserror = { workspace = true } -authenticator = "0.3.1" -once_cell = "1" -sha2 = "0.10" -base64 = "0.22" -chrono = "0.4" -bytes = "1" -byteorder = "1" -openssl = "0.10" - -[dev-dependencies] -rand = "0.8" -rusty-fork = "0.3" diff --git a/plugins/authenticator/LICENSE.spdx b/plugins/authenticator/LICENSE.spdx deleted file mode 100644 index cdd0df5a..00000000 --- a/plugins/authenticator/LICENSE.spdx +++ /dev/null @@ -1,20 +0,0 @@ -SPDXVersion: SPDX-2.1 -DataLicense: CC0-1.0 -PackageName: tauri -DataFormat: SPDXRef-1 -PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy -PackageHomePage: https://tauri.app -PackageLicenseDeclared: Apache-2.0 -PackageLicenseDeclared: MIT -PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy -PackageSummary: Tauri is a rust project that enables developers to make secure -and small desktop applications using a web frontend. - -PackageComment: The package includes the following libraries; see -Relationship information. - -Created: 2019-05-20T09:00:00Z -PackageDownloadLocation: git://github.com/tauri-apps/tauri -PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git -PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git -Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/plugins/authenticator/LICENSE_APACHE-2.0 b/plugins/authenticator/LICENSE_APACHE-2.0 deleted file mode 100644 index 4947287f..00000000 --- a/plugins/authenticator/LICENSE_APACHE-2.0 +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/plugins/authenticator/LICENSE_MIT b/plugins/authenticator/LICENSE_MIT deleted file mode 100644 index 4d754725..00000000 --- a/plugins/authenticator/LICENSE_MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 - Present Tauri Apps Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/plugins/authenticator/README.md b/plugins/authenticator/README.md deleted file mode 100644 index 6095a5a4..00000000 --- a/plugins/authenticator/README.md +++ /dev/null @@ -1,126 +0,0 @@ -![plugin-authenticator](https://github.com/tauri-apps/plugins-workspace/raw/v1/plugins/authenticator/banner.png) - -Use hardware security-keys in your Tauri App. - -## Install - -_This plugin requires a Rust version of at least **1.67**_ - -There are three general methods of installation that we can recommend. - -1. Use crates.io and npm (easiest and requires you to trust that our publishing pipeline worked) -2. Pull sources directly from Github using git tags / revision hashes (most secure) -3. Git submodule install this repo in your tauri project and then use the file protocol to ingest the source (most secure, but inconvenient to use) - -Install the authenticator plugin by adding the following lines to your `Cargo.toml` file: - -`src-tauri/Cargo.toml` - -```toml -[dependencies] -tauri-plugin-authenticator = "0.1" -# or through git -tauri-plugin-authenticator = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } -``` - -You can install the JavaScript Guest bindings using your preferred JavaScript package manager: - -> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use. - -```sh -pnpm add https://github.com/tauri-apps/tauri-plugin-authenticator#v1 -# or -npm add https://github.com/tauri-apps/tauri-plugin-authenticator#v1 -# or -yarn add https://github.com/tauri-apps/tauri-plugin-authenticator#v1 -``` - -## Usage - -First, you need to register the authenticator plugin with Tauri: - -`src-tauri/src/main.rs` - -```rust -fn main() { - tauri::Builder::default() - .plugin(tauri_plugin_authenticator::init()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} -``` - -Afterwards, all the plugin's APIs are available through the JavaScript guest bindings: - -```javascript -import { Authenticator } from "tauri-plugin-authenticator-api"; - -const auth = new Authenticator(); -auth.init(); // initialize transports - -// generate a 32-bytes long random challenge -const arr = new Uint32Array(32); -window.crypto.getRandomValues(arr); -const b64 = btoa(String.fromCharCode.apply(null, arr)); -// web-safe base64 -const challenge = b64.replace(/\+/g, "-").replace(/\//g, "_"); - -const domain = "https://tauri.app"; - -// attempt to register with the security key -const json = await auth.register(challenge, domain); -const registerResult = JSON.parse(json); - -// verify the registration was successful -const r2 = await auth.verifyRegistration( - challenge, - app, - registerResult.registerData, - registerResult.clientData, -); -const j2 = JSON.parse(r2); - -// sign some data -const json = await auth.sign(challenge, app, keyHandle); -const signData = JSON.parse(json); - -// verify the signature again -const counter = await auth.verifySignature( - challenge, - app, - signData.signData, - clientData, - keyHandle, - pubkey, -); - -if (counter && counter > 0) { - console.log("SUCCESS!"); -} -``` - -## Contributing - -PRs accepted. Please make sure to read the Contributing Guide before making a pull request. - -## Partners - - - - - - - -
- - CrabNebula - -
- -For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). - -## License - -Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. - -MIT or MIT/Apache 2.0 where applicable. diff --git a/plugins/authenticator/banner.png b/plugins/authenticator/banner.png deleted file mode 100644 index 405dc6018dc2e50d81fa368d738b5abc9abdc28c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44877 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 diff --git a/plugins/authenticator/guest-js/index.ts b/plugins/authenticator/guest-js/index.ts deleted file mode 100644 index ab3be8b9..00000000 --- a/plugins/authenticator/guest-js/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { invoke } from "@tauri-apps/api/tauri"; - -export class Authenticator { - async init(): Promise { - return await invoke("plugin:authenticator|init_auth"); - } - - async register(challenge: string, application: string): Promise { - return await invoke("plugin:authenticator|register", { - timeout: 10000, - challenge, - application, - }); - } - - async verifyRegistration( - challenge: string, - application: string, - registerData: string, - clientData: string, - ): Promise { - return await invoke("plugin:authenticator|verify_registration", { - challenge, - application, - registerData, - clientData, - }); - } - - async sign( - challenge: string, - application: string, - keyHandle: string, - ): Promise { - return await invoke("plugin:authenticator|sign", { - timeout: 10000, - challenge, - application, - keyHandle, - }); - } - - async verifySignature( - challenge: string, - application: string, - signData: string, - clientData: string, - keyHandle: string, - pubkey: string, - ): Promise { - return await invoke("plugin:authenticator|verify_signature", { - challenge, - application, - signData, - clientData, - keyHandle, - pubkey, - }); - } -} diff --git a/plugins/authenticator/package.json b/plugins/authenticator/package.json deleted file mode 100644 index 1f75b6dd..00000000 --- a/plugins/authenticator/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "tauri-plugin-authenticator-api", - "version": "0.0.0", - "description": "Use hardware security-keys in your Tauri App.", - "license": "MIT or APACHE-2.0", - "authors": [ - "Tauri Programme within The Commons Conservancy" - ], - "type": "module", - "browser": "dist-js/index.min.js", - "module": "dist-js/index.mjs", - "types": "dist-js/index.d.ts", - "exports": { - "import": "./dist-js/index.mjs", - "types": "./dist-js/index.d.ts", - "browser": "./dist-js/index.min.js" - }, - "scripts": { - "build": "rollup -c" - }, - "files": [ - "dist-js", - "!dist-js/**/*.map", - "README.md", - "LICENSE" - ], - "devDependencies": { - "tslib": "2.7.0" - }, - "dependencies": { - "@tauri-apps/api": "1.6.0" - } -} diff --git a/plugins/authenticator/rollup.config.mjs b/plugins/authenticator/rollup.config.mjs deleted file mode 100644 index 99a3dd31..00000000 --- a/plugins/authenticator/rollup.config.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import { readFileSync } from "fs"; - -import { createConfig } from "../../shared/rollup.config.mjs"; - -export default createConfig({ - input: "guest-js/index.ts", - pkg: JSON.parse( - readFileSync(new URL("./package.json", import.meta.url), "utf8"), - ), - external: [/^@tauri-apps\/api/], -}); diff --git a/plugins/authenticator/src/auth.rs b/plugins/authenticator/src/auth.rs deleted file mode 100644 index c334173d..00000000 --- a/plugins/authenticator/src/auth.rs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use authenticator::{ - authenticatorservice::AuthenticatorService, statecallback::StateCallback, - AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate, -}; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use once_cell::sync::Lazy; -use serde::Serialize; -use sha2::{Digest, Sha256}; -use std::io; -use std::sync::mpsc::channel; -use std::{convert::Into, sync::Mutex}; - -static MANAGER: Lazy> = Lazy::new(|| { - let manager = AuthenticatorService::new().expect("The auth service should initialize safely"); - Mutex::new(manager) -}); - -pub fn init_usb() { - let mut manager = MANAGER.lock().unwrap(); - // theres also "add_detected_transports()" in the docs? - manager.add_u2f_usb_hid_platform_transports(); -} - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Registration { - pub key_handle: String, - pub pubkey: String, - pub register_data: String, - pub client_data: String, -} - -pub fn register(application: String, timeout: u64, challenge: String) -> crate::Result { - let (chall_bytes, app_bytes, client_data_string) = - format_client_data(application.as_str(), challenge.as_str()); - - // log the status rx? - let (status_tx, _status_rx) = channel::(); - - let mut manager = MANAGER.lock().unwrap(); - - let (register_tx, register_rx) = channel(); - let callback = StateCallback::new(Box::new(move |rv| { - register_tx.send(rv).unwrap(); - })); - - let res = manager.register( - RegisterFlags::empty(), - timeout, - chall_bytes, - app_bytes, - vec![], - status_tx, - callback, - ); - - match res { - Ok(_r) => { - let register_result = register_rx - .recv() - .expect("Problem receiving, unable to continue"); - - if let Err(e) = register_result { - return Err(e.into()); - } - - let (register_data, device_info) = register_result.unwrap(); // error already has been checked - - // println!("Register result: {}", base64::encode(®ister_data)); - println!("Device info: {}", &device_info); - - let (key_handle, public_key) = - _u2f_get_key_handle_and_public_key_from_register_response(®ister_data).unwrap(); - let key_handle_base64 = URL_SAFE_NO_PAD.encode(key_handle); - let public_key_base64 = URL_SAFE_NO_PAD.encode(public_key); - let register_data_base64 = URL_SAFE_NO_PAD.encode(®ister_data); - println!("Key Handle: {}", &key_handle_base64); - println!("Public Key: {}", &public_key_base64); - - // Ok(base64::encode(®ister_data)) - // Ok(key_handle_base64) - let res = serde_json::to_string(&Registration { - key_handle: key_handle_base64, - pubkey: public_key_base64, - register_data: register_data_base64, - client_data: client_data_string, - })?; - Ok(res) - } - Err(e) => Err(e.into()), - } -} - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Signature { - pub key_handle: String, - pub sign_data: String, -} - -pub fn sign( - application: String, - timeout: u64, - challenge: String, - key_handle: String, -) -> crate::Result { - let credential = match URL_SAFE_NO_PAD.decode(key_handle) { - Ok(v) => v, - Err(e) => { - return Err(e.into()); - } - }; - let key_handle = KeyHandle { - credential, - transports: AuthenticatorTransports::empty(), - }; - - let (chall_bytes, app_bytes, _) = format_client_data(application.as_str(), challenge.as_str()); - - let (sign_tx, sign_rx) = channel(); - let callback = StateCallback::new(Box::new(move |rv| { - sign_tx.send(rv).unwrap(); - })); - - // log the status rx? - let (status_tx, _status_rx) = channel::(); - - let mut manager = MANAGER.lock().unwrap(); - - let res = manager.sign( - SignFlags::empty(), - timeout, - chall_bytes, - vec![app_bytes], - vec![key_handle], - status_tx, - callback, - ); - match res { - Ok(_v) => { - let sign_result = sign_rx - .recv() - .expect("Problem receiving, unable to continue"); - - if let Err(e) = sign_result { - return Err(e.into()); - } - - let (_, handle_used, sign_data, device_info) = sign_result.unwrap(); - - let sig = URL_SAFE_NO_PAD.encode(sign_data); - - println!("Sign result: {sig}"); - println!("Key handle used: {}", URL_SAFE_NO_PAD.encode(&handle_used)); - println!("Device info: {}", &device_info); - println!("Done."); - - let res = serde_json::to_string(&Signature { - sign_data: sig, - key_handle: URL_SAFE_NO_PAD.encode(&handle_used), - })?; - Ok(res) - } - Err(e) => Err(e.into()), - } -} - -fn format_client_data(application: &str, challenge: &str) -> (Vec, Vec, String) { - let d = - format!(r#"{{"challenge": "{challenge}", "version": "U2F_V2", "appId": "{application}"}}"#); - let mut challenge = Sha256::new(); - challenge.update(d.as_bytes()); - let chall_bytes = challenge.finalize().to_vec(); - - let mut app = Sha256::new(); - app.update(application.as_bytes()); - let app_bytes = app.finalize().to_vec(); - - (chall_bytes, app_bytes, d) -} - -fn _u2f_get_key_handle_and_public_key_from_register_response( - register_response: &[u8], -) -> io::Result<(Vec, Vec)> { - if register_response[0] != 0x05 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Reserved byte not set correctly", - )); - } - - // 1: reserved - // 65: public key - // 1: key handle length - // key handle - // x.509 cert - // sig - - let key_handle_len = register_response[66] as usize; - let mut public_key = register_response.to_owned(); - let mut key_handle = public_key.split_off(67); - let _attestation = key_handle.split_off(key_handle_len); - - // remove fist (reserved) and last (handle len) bytes - let pk: Vec = public_key[1..public_key.len() - 1].to_vec(); - - Ok((key_handle, pk)) -} diff --git a/plugins/authenticator/src/error.rs b/plugins/authenticator/src/error.rs deleted file mode 100644 index db731e8f..00000000 --- a/plugins/authenticator/src/error.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::{Serialize, Serializer}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error(transparent)] - Base64Decode(#[from] base64::DecodeError), - #[error(transparent)] - JSON(#[from] serde_json::Error), - #[error(transparent)] - U2F(#[from] crate::u2f_crate::u2ferror::U2fError), - #[error(transparent)] - Auth(#[from] authenticator::errors::AuthenticatorError), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/plugins/authenticator/src/lib.rs b/plugins/authenticator/src/lib.rs deleted file mode 100644 index 36ed0228..00000000 --- a/plugins/authenticator/src/lib.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -mod auth; -mod error; -mod u2f; -mod u2f_crate; - -use tauri::{ - plugin::{Builder as PluginBuilder, TauriPlugin}, - Runtime, -}; - -pub use error::Error; -type Result = std::result::Result; - -#[tauri::command] -fn init_auth() { - auth::init_usb(); -} - -#[tauri::command] -fn register(timeout: u64, challenge: String, application: String) -> crate::Result { - auth::register(application, timeout, challenge) -} - -#[tauri::command] -fn verify_registration( - challenge: String, - application: String, - register_data: String, - client_data: String, -) -> crate::Result { - u2f::verify_registration(application, challenge, register_data, client_data) -} - -#[tauri::command] -fn sign( - timeout: u64, - challenge: String, - application: String, - key_handle: String, -) -> crate::Result { - auth::sign(application, timeout, challenge, key_handle) -} - -#[tauri::command] -fn verify_signature( - challenge: String, - application: String, - sign_data: String, - client_data: String, - key_handle: String, - pubkey: String, -) -> crate::Result { - u2f::verify_signature( - application, - challenge, - sign_data, - client_data, - key_handle, - pubkey, - ) -} - -pub fn init() -> TauriPlugin { - PluginBuilder::new("authenticator") - .invoke_handler(tauri::generate_handler![ - init_auth, - register, - verify_registration, - sign, - verify_signature - ]) - .build() -} diff --git a/plugins/authenticator/src/u2f.rs b/plugins/authenticator/src/u2f.rs deleted file mode 100644 index 8a443b16..00000000 --- a/plugins/authenticator/src/u2f.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::u2f_crate::messages::*; -use crate::u2f_crate::protocol::*; -use crate::u2f_crate::register::*; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use chrono::prelude::*; -use serde::Serialize; -use std::convert::Into; - -static VERSION: &str = "U2F_V2"; - -pub fn make_challenge(app_id: &str, challenge_bytes: Vec) -> Challenge { - let utc: DateTime = Utc::now(); - Challenge { - challenge: URL_SAFE_NO_PAD.encode(challenge_bytes), - timestamp: format!("{utc:?}"), - app_id: app_id.to_string(), - } -} - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct RegistrationVerification { - pub key_handle: String, - pub pubkey: String, - pub device_name: Option, -} - -pub fn verify_registration( - app_id: String, - challenge: String, - register_data: String, - client_data: String, -) -> crate::Result { - let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?; - let challenge = make_challenge(&app_id, challenge_bytes); - let client_data_bytes: Vec = client_data.as_bytes().into(); - let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes); - let client = U2f::new(app_id); - match client.register_response( - challenge, - RegisterResponse { - registration_data: register_data, - client_data: client_data_base64, - version: VERSION.to_string(), - }, - ) { - Ok(v) => { - let rv = RegistrationVerification { - key_handle: URL_SAFE_NO_PAD.encode(&v.key_handle), - pubkey: URL_SAFE_NO_PAD.encode(&v.pub_key), - device_name: v.device_name, - }; - Ok(serde_json::to_string(&rv)?) - } - Err(e) => Err(e.into()), - } -} - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct SignatureVerification { - pub counter: u8, -} - -pub fn verify_signature( - app_id: String, - challenge: String, - sign_data: String, - client_data: String, - key_handle: String, - pub_key: String, -) -> crate::Result { - let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?; - let chal = make_challenge(&app_id, challenge_bytes); - let client_data_bytes: Vec = client_data.as_bytes().into(); - let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes); - let key_handle_bytes = URL_SAFE_NO_PAD.decode(&key_handle)?; - let pubkey_bytes = URL_SAFE_NO_PAD.decode(pub_key)?; - let client = U2f::new(app_id); - let mut _counter: u32 = 0; - match client.sign_response( - chal, - Registration { - // here only needs pubkey and keyhandle - key_handle: key_handle_bytes, - pub_key: pubkey_bytes, - attestation_cert: None, - device_name: None, - }, - SignResponse { - // here needs client data and sig data and key_handle - signature_data: sign_data, - client_data: client_data_base64, - key_handle, - }, - _counter, - ) { - Ok(v) => Ok(v), - Err(e) => Err(e.into()), - } -} diff --git a/plugins/authenticator/src/u2f_crate/LICENSE b/plugins/authenticator/src/u2f_crate/LICENSE deleted file mode 100644 index d26d5f6c..00000000 --- a/plugins/authenticator/src/u2f_crate/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -Copyright (c) 2017 - -Licensed under either of - - * Apache License, Version 2.0, (http://www.apache.org/licenses/LICENSE-2.0) - * MIT license (http://opensource.org/licenses/MIT) - -at your option. \ No newline at end of file diff --git a/plugins/authenticator/src/u2f_crate/authorization.rs b/plugins/authenticator/src/u2f_crate/authorization.rs deleted file mode 100644 index 35a1a3e1..00000000 --- a/plugins/authenticator/src/u2f_crate/authorization.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use bytes::{Buf, BufMut}; -use openssl::sha::sha256; -use serde::Serialize; -use std::io::Cursor; - -use crate::u2f_crate::u2ferror::U2fError; - -/// The `Result` type used in this crate. -type Result = ::std::result::Result; - -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Authorization { - pub counter: u32, - pub user_presence: bool, -} - -pub fn parse_sign_response( - app_id: String, - client_data: Vec, - public_key: Vec, - sign_data: Vec, -) -> Result { - if sign_data.len() <= 5 { - return Err(U2fError::InvalidSignatureData); - } - - let user_presence_flag = &sign_data[0]; - let counter = &sign_data[1..=4]; - let signature = &sign_data[5..]; - - // Let's build the msg to verify the signature - let app_id_hash = sha256(&app_id.into_bytes()); - let client_data_hash = sha256(&client_data[..]); - - let mut msg = vec![]; - msg.put(app_id_hash.as_ref()); - msg.put_u8(*user_presence_flag); - msg.put(counter); - msg.put(client_data_hash.as_ref()); - - let public_key = super::crypto::NISTP256Key::from_bytes(&public_key)?; - - // The signature is to be verified by the relying party using the public key obtained during registration. - let verified = public_key.verify_signature(signature, msg.as_ref())?; - if !verified { - return Err(U2fError::BadSignature); - } - - let authorization = Authorization { - counter: get_counter(counter), - user_presence: true, - }; - - Ok(authorization) -} - -fn get_counter(counter: &[u8]) -> u32 { - let mut buf = Cursor::new(counter); - buf.get_u32() -} diff --git a/plugins/authenticator/src/u2f_crate/crypto.rs b/plugins/authenticator/src/u2f_crate/crypto.rs deleted file mode 100644 index 32379805..00000000 --- a/plugins/authenticator/src/u2f_crate/crypto.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! Cryptographic operation wrapper for Webauthn. This module exists to -//! allow ease of auditing, safe operation wrappers for the webauthn library, -//! and cryptographic provider abstraction. This module currently uses OpenSSL -//! as the cryptographic primitive provider. - -// Source can be found here: https://github.com/Firstyear/webauthn-rs/blob/master/src/crypto.rs - -#![allow(non_camel_case_types)] - -use openssl::{bn, ec, hash, nid, sign, x509}; -use std::convert::TryFrom; - -// use super::constants::*; -use crate::u2f_crate::u2ferror::U2fError; -use openssl::pkey::Public; - -// use super::proto::*; - -// Why OpenSSL over another rust crate? -// - Well, the openssl crate allows us to reconstruct a public key from the -// x/y group coords, where most others want a pkcs formatted structure. As -// a result, it's easiest to use openssl as it gives us exactly what we need -// for these operations, and despite it's many challenges as a library, it -// has resources and investment into it's maintenance, so we can a least -// assert a higher level of confidence in it that . - -// Object({Integer(-3): Bytes([48, 185, 178, 204, 113, 186, 105, 138, 190, 33, 160, 46, 131, 253, 100, 177, 91, 243, 126, 128, 245, 119, 209, 59, 186, 41, 215, 196, 24, 222, 46, 102]), Integer(-2): Bytes([158, 212, 171, 234, 165, 197, 86, 55, 141, 122, 253, 6, 92, 242, 242, 114, 158, 221, 238, 163, 127, 214, 120, 157, 145, 226, 232, 250, 144, 150, 218, 138]), Integer(-1): U64(1), Integer(1): U64(2), Integer(3): I64(-7)}) -// - -/// An X509PublicKey. This is what is otherwise known as a public certificate -/// which comprises a public key and other signed metadata related to the issuer -/// of the key. -pub struct X509PublicKey { - pubk: x509::X509, -} - -impl std::fmt::Debug for X509PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "X509PublicKey") - } -} - -impl TryFrom<&[u8]> for X509PublicKey { - type Error = U2fError; - - // Must be DER bytes. If you have PEM, base64decode first! - fn try_from(d: &[u8]) -> Result { - let pubk = x509::X509::from_der(d)?; - Ok(X509PublicKey { pubk }) - } -} - -impl X509PublicKey { - pub(crate) fn common_name(&self) -> Option { - let cert = &self.pubk; - - let subject = cert.subject_name(); - let common = subject - .entries_by_nid(openssl::nid::Nid::COMMONNAME) - .next() - .map(|b| b.data().as_slice()); - - if let Some(common) = common { - std::str::from_utf8(common).ok().map(|s| s.to_string()) - } else { - None - } - } - - pub(crate) fn is_secp256r1(&self) -> Result { - // Can we get the public key? - let pk = self.pubk.public_key()?; - - let ec_key = pk.ec_key()?; - - ec_key.check_key()?; - - let ec_grpref = ec_key.group(); - - let ec_curve = ec_grpref.curve_name().ok_or(U2fError::OpenSSLNoCurveName)?; - - Ok(ec_curve == nid::Nid::X9_62_PRIME256V1) - } - - pub(crate) fn verify_signature( - &self, - signature: &[u8], - verification_data: &[u8], - ) -> Result { - let pkey = self.pubk.public_key()?; - - // TODO: Should this determine the hash type from the x509 cert? Or other? - let mut verifier = sign::Verifier::new(hash::MessageDigest::sha256(), &pkey)?; - verifier.update(verification_data)?; - Ok(verifier.verify(signature)?) - } -} - -pub struct NISTP256Key { - /// The key's public X coordinate. - pub x: [u8; 32], - /// The key's public Y coordinate. - pub y: [u8; 32], -} - -impl NISTP256Key { - pub fn from_bytes(public_key_bytes: &[u8]) -> Result { - if public_key_bytes.len() != 65 { - return Err(U2fError::InvalidPublicKey); - } - - if public_key_bytes[0] != 0x04 { - return Err(U2fError::InvalidPublicKey); - } - - let mut x: [u8; 32] = Default::default(); - x.copy_from_slice(&public_key_bytes[1..=32]); - - let mut y: [u8; 32] = Default::default(); - y.copy_from_slice(&public_key_bytes[33..=64]); - - Ok(NISTP256Key { x, y }) - } - - fn get_key(&self) -> Result, U2fError> { - let ec_group = ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1)?; - - let xbn = bn::BigNum::from_slice(&self.x)?; - let ybn = bn::BigNum::from_slice(&self.y)?; - - let ec_key = openssl::ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)?; - - // Validate the key is sound. IIRC this actually checks the values - // are correctly on the curve as specified - ec_key.check_key()?; - - Ok(ec_key) - } - - pub fn verify_signature( - &self, - signature: &[u8], - verification_data: &[u8], - ) -> Result { - let pkey = self.get_key()?; - - let signature = openssl::ecdsa::EcdsaSig::from_der(signature)?; - let hash = openssl::sha::sha256(verification_data); - - Ok(signature.verify(hash.as_ref(), &pkey)?) - } -} diff --git a/plugins/authenticator/src/u2f_crate/messages.rs b/plugins/authenticator/src/u2f_crate/messages.rs deleted file mode 100644 index 8e0cea71..00000000 --- a/plugins/authenticator/src/u2f_crate/messages.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -// As defined by FIDO U2F Javascript API. -// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-javascript-api.html#registration - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct U2fRegisterRequest { - pub app_id: String, - pub register_requests: Vec, - pub registered_keys: Vec, -} - -#[derive(Serialize)] -pub struct RegisterRequest { - pub version: String, - pub challenge: String, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RegisteredKey { - pub version: String, - pub key_handle: Option, - pub app_id: String, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RegisterResponse { - pub registration_data: String, - #[allow(dead_code)] - pub version: String, - pub client_data: String, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct U2fSignRequest { - pub app_id: String, - pub challenge: String, - pub registered_keys: Vec, -} - -#[derive(Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SignResponse { - pub key_handle: String, - pub signature_data: String, - pub client_data: String, -} diff --git a/plugins/authenticator/src/u2f_crate/mod.rs b/plugins/authenticator/src/u2f_crate/mod.rs deleted file mode 100644 index 0aaebf6f..00000000 --- a/plugins/authenticator/src/u2f_crate/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -mod util; - -pub mod authorization; -mod crypto; -pub mod messages; -pub mod protocol; -pub mod register; -pub mod u2ferror; diff --git a/plugins/authenticator/src/u2f_crate/protocol.rs b/plugins/authenticator/src/u2f_crate/protocol.rs deleted file mode 100644 index fc7343ea..00000000 --- a/plugins/authenticator/src/u2f_crate/protocol.rs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::u2f_crate::authorization::*; -use crate::u2f_crate::messages::*; -use crate::u2f_crate::register::*; -use crate::u2f_crate::u2ferror::U2fError; -use crate::u2f_crate::util::*; - -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use chrono::prelude::*; -use chrono::Duration; -use serde::{Deserialize, Serialize}; - -type Result = ::std::result::Result; - -#[derive(Clone)] -pub struct U2f { - app_id: String, -} - -#[derive(Deserialize, Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Challenge { - pub app_id: String, - pub challenge: String, - pub timestamp: String, -} - -impl Challenge { - // Not used in this plugin. - #[allow(dead_code)] - pub fn new() -> Self { - Challenge { - app_id: String::new(), - challenge: String::new(), - timestamp: String::new(), - } - } -} - -impl U2f { - // The app ID is a string used to uniquely identify an U2F app - pub fn new(app_id: String) -> Self { - U2f { app_id } - } - - // Not used in this plugin. - #[allow(dead_code)] - pub fn generate_challenge(&self) -> Result { - let utc: DateTime = Utc::now(); - - let challenge_bytes = generate_challenge(32)?; - let challenge = Challenge { - challenge: URL_SAFE_NO_PAD.encode(challenge_bytes), - timestamp: format!("{:?}", utc), - app_id: self.app_id.clone(), - }; - - Ok(challenge.clone()) - } - - // Not used in this plugin. - #[allow(dead_code)] - pub fn request( - &self, - challenge: Challenge, - registrations: Vec, - ) -> Result { - let u2f_request = U2fRegisterRequest { - app_id: self.app_id.clone(), - register_requests: self.register_request(challenge), - registered_keys: self.registered_keys(registrations), - }; - - Ok(u2f_request) - } - - fn register_request(&self, challenge: Challenge) -> Vec { - let mut requests: Vec = vec![]; - - let request = RegisterRequest { - version: U2F_V2.into(), - challenge: challenge.challenge, - }; - requests.push(request); - - requests - } - - pub fn register_response( - &self, - challenge: Challenge, - response: RegisterResponse, - ) -> Result { - if expiration(challenge.timestamp) > Duration::seconds(300) { - return Err(U2fError::ChallengeExpired); - } - - let registration_data: Vec = URL_SAFE_NO_PAD - .decode(&response.registration_data[..]) - .unwrap(); - let client_data: Vec = URL_SAFE_NO_PAD.decode(&response.client_data[..]).unwrap(); - - parse_registration(challenge.app_id, client_data, registration_data) - } - - fn registered_keys(&self, registrations: Vec) -> Vec { - let mut keys: Vec = vec![]; - - for registration in registrations { - keys.push(get_registered_key( - self.app_id.clone(), - registration.key_handle, - )); - } - - keys - } - - // Not used in this plugin. - #[allow(dead_code)] - pub fn sign_request( - &self, - challenge: Challenge, - registrations: Vec, - ) -> U2fSignRequest { - let mut keys: Vec = vec![]; - - for registration in registrations { - keys.push(get_registered_key( - self.app_id.clone(), - registration.key_handle, - )); - } - - let signed_request = U2fSignRequest { - app_id: self.app_id.clone(), - challenge: URL_SAFE_NO_PAD.encode(challenge.challenge.as_bytes()), - registered_keys: keys, - }; - - signed_request - } - - pub fn sign_response( - &self, - challenge: Challenge, - reg: Registration, - sign_resp: SignResponse, - counter: u32, - ) -> Result { - if expiration(challenge.timestamp) > Duration::seconds(300) { - return Err(U2fError::ChallengeExpired); - } - - if sign_resp.key_handle != get_encoded(®.key_handle[..]) { - return Err(U2fError::WrongKeyHandler); - } - - let client_data: Vec = URL_SAFE_NO_PAD - .decode(&sign_resp.client_data[..]) - .map_err(|_e| U2fError::InvalidClientData)?; - let sign_data: Vec = URL_SAFE_NO_PAD - .decode(&sign_resp.signature_data[..]) - .map_err(|_e| U2fError::InvalidSignatureData)?; - - let public_key = reg.pub_key; - - let auth = parse_sign_response( - self.app_id.clone(), - client_data.clone(), - public_key, - sign_data.clone(), - ); - - match auth { - Ok(ref res) => { - // CounterTooLow is raised when the counter value received from the device is - // lower than last stored counter value. - if res.counter < counter { - Err(U2fError::CounterTooLow) - } else { - Ok(res.counter) - } - } - Err(e) => Err(e), - } - } -} diff --git a/plugins/authenticator/src/u2f_crate/register.rs b/plugins/authenticator/src/u2f_crate/register.rs deleted file mode 100644 index 3717b003..00000000 --- a/plugins/authenticator/src/u2f_crate/register.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use byteorder::{BigEndian, ByteOrder}; -use bytes::{BufMut, Bytes}; -use openssl::sha::sha256; -use serde::Serialize; - -use crate::u2f_crate::messages::RegisteredKey; -use crate::u2f_crate::u2ferror::U2fError; -use crate::u2f_crate::util::*; -use std::convert::TryFrom; - -/// The `Result` type used in this crate. -type Result = ::std::result::Result; - -// Single enrolment or pairing between an application and a token. -#[derive(Serialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Registration { - pub key_handle: Vec, - pub pub_key: Vec, - - // AttestationCert can be null for Authenticate requests. - pub attestation_cert: Option>, - pub device_name: Option, -} - -pub fn parse_registration( - app_id: String, - client_data: Vec, - registration_data: Vec, -) -> Result { - let reserved_byte = registration_data[0]; - if reserved_byte != 0x05 { - return Err(U2fError::InvalidReservedByte); - } - - let mut mem = Bytes::from(registration_data); - - //Start parsing ... advance the reserved byte. - let _ = mem.split_to(1); - - // P-256 NIST elliptic curve - let public_key = mem.split_to(65); - - // Key Handle - let key_handle_size = mem.split_to(1); - let key_len = BigEndian::read_uint(&key_handle_size[..], 1); - let key_handle = mem.split_to(key_len as usize); - - // The certificate length needs to be inferred by parsing. - let cert_len = asn_length(mem.clone()).unwrap(); - let attestation_certificate = mem.split_to(cert_len); - - // Remaining data corresponds to the signature - let signature = mem; - - // Let's build the msg to verify the signature - let app_id_hash = sha256(&app_id.into_bytes()); - let client_data_hash = sha256(&client_data[..]); - - let mut msg = vec![0x00]; // A byte reserved for future use [1 byte] with the value 0x00 - msg.put(app_id_hash.as_ref()); - msg.put(client_data_hash.as_ref()); - msg.put(key_handle.clone()); - msg.put(public_key.clone()); - - // The signature is to be verified by the relying party using the public key certified - // in the attestation certificate. - let cerificate_public_key = - super::crypto::X509PublicKey::try_from(&attestation_certificate[..])?; - - if !(cerificate_public_key.is_secp256r1()?) { - return Err(U2fError::BadCertificate); - } - - let verified = cerificate_public_key.verify_signature(&signature[..], &msg[..])?; - - if !verified { - return Err(U2fError::BadCertificate); - } - - let registration = Registration { - key_handle: key_handle[..].to_vec(), - pub_key: public_key[..].to_vec(), - attestation_cert: Some(attestation_certificate[..].to_vec()), - device_name: cerificate_public_key.common_name(), - }; - - Ok(registration) -} - -pub fn get_registered_key(app_id: String, key_handle: Vec) -> RegisteredKey { - RegisteredKey { - app_id, - version: U2F_V2.into(), - key_handle: Some(get_encoded(key_handle.as_slice())), - } -} diff --git a/plugins/authenticator/src/u2f_crate/u2ferror.rs b/plugins/authenticator/src/u2f_crate/u2ferror.rs deleted file mode 100644 index 9ae8fa92..00000000 --- a/plugins/authenticator/src/u2f_crate/u2ferror.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum U2fError { - #[error("ASM1 Decoder error")] - Asm1DecoderError, - #[error("Not able to verify signature")] - BadSignature, - #[error("Not able to generate random bytes")] - RandomSecureBytesError, - #[error("Invalid Reserved Byte")] - InvalidReservedByte, - #[error("Challenge Expired")] - ChallengeExpired, - #[error("Wrong Key Handler")] - WrongKeyHandler, - #[error("Invalid Client Data")] - InvalidClientData, - #[error("Invalid Signature Data")] - InvalidSignatureData, - #[error("Invalid User Presence Byte")] - InvalidUserPresenceByte, - #[error("Failed to parse certificate")] - BadCertificate, - #[error("Not Trusted Anchor")] - NotTrustedAnchor, - #[error("Counter too low")] - CounterTooLow, - #[error("Invalid public key")] - OpenSSLNoCurveName, - #[error("OpenSSL no curve name")] - InvalidPublicKey, - #[error(transparent)] - OpenSSLError(#[from] openssl::error::ErrorStack), -} diff --git a/plugins/authenticator/src/u2f_crate/util.rs b/plugins/authenticator/src/u2f_crate/util.rs deleted file mode 100644 index cba58d4d..00000000 --- a/plugins/authenticator/src/u2f_crate/util.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021 Flavio Oliveira -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use crate::u2f_crate::u2ferror::U2fError; -use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use bytes::Bytes; -use chrono::prelude::*; -use chrono::Duration; -use openssl::rand; - -/// The `Result` type used in this crate. -type Result = ::std::result::Result; - -pub const U2F_V2: &str = "U2F_V2"; - -// Generates a challenge from a secure, random source. -pub fn generate_challenge(size: usize) -> Result> { - let mut bytes: Vec = vec![0; size]; - rand::rand_bytes(&mut bytes).map_err(|_e| U2fError::RandomSecureBytesError)?; - Ok(bytes) -} - -pub fn expiration(timestamp: String) -> Duration { - let now: DateTime = Utc::now(); - - let ts = timestamp.parse::>(); - - now.signed_duration_since(ts.unwrap()) -} - -// Decode initial bytes of buffer as ASN and return the length of the encoded structure. -// http://en.wikipedia.org/wiki/X.690 -pub fn asn_length(mem: Bytes) -> Result { - let buffer: &[u8] = &mem[..]; - - if mem.len() < 2 || buffer[0] != 0x30 { - // Type - return Err(U2fError::Asm1DecoderError); - } - - let len = buffer[1]; // Len - if len & 0x80 == 0 { - return Ok((len & 0x7f) as usize); - } - - let numbem_of_bytes = len & 0x7f; - if numbem_of_bytes == 0 { - return Err(U2fError::Asm1DecoderError); - } - - let mut length: usize = 0; - for num in 0..numbem_of_bytes { - length = length * 0x100 + (buffer[(2 + num) as usize] as usize); - } - - length += numbem_of_bytes as usize; - - Ok(length + 2) // Add the 2 initial bytes: type and length. -} - -pub fn get_encoded(data: &[u8]) -> String { - let encoded: String = URL_SAFE_NO_PAD.encode(data); - - encoded.trim_end_matches('=').to_string() -} diff --git a/plugins/authenticator/tsconfig.json b/plugins/authenticator/tsconfig.json deleted file mode 100644 index 5098169a..00000000 --- a/plugins/authenticator/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "include": ["guest-js/*.ts"] -} diff --git a/plugins/mirrors.txt b/plugins/mirrors.txt index e981fdcb..6c7f8283 100644 --- a/plugins/mirrors.txt +++ b/plugins/mirrors.txt @@ -1,4 +1,3 @@ -authenticator autostart fs-extra fs-watch diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37e3e84d..115199b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,16 +46,6 @@ importers: specifier: rc-v8 version: 8.0.0-alpha.62(eslint@9.9.1)(typescript@5.5.4) - plugins/authenticator: - dependencies: - '@tauri-apps/api': - specifier: 1.6.0 - version: 1.6.0 - devDependencies: - tslib: - specifier: 2.7.0 - version: 2.7.0 - plugins/autostart: dependencies: '@tauri-apps/api':