mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-05-01 12:08:06 +02:00
copy plugin sources
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
// 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::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
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<Mutex<AuthenticatorService>> = 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<String> {
|
||||
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::<StatusUpdate>();
|
||||
|
||||
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 = encode_config(&key_handle, URL_SAFE_NO_PAD);
|
||||
let public_key_base64 = encode_config(&public_key, URL_SAFE_NO_PAD);
|
||||
let register_data_base64 = encode_config(®ister_data, URL_SAFE_NO_PAD);
|
||||
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<String> {
|
||||
let credential = match decode_config(&key_handle, URL_SAFE_NO_PAD) {
|
||||
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::<StatusUpdate>();
|
||||
|
||||
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 = encode_config(&sign_data, URL_SAFE_NO_PAD);
|
||||
|
||||
println!("Sign result: {}", sig);
|
||||
println!(
|
||||
"Key handle used: {}",
|
||||
encode_config(&handle_used, URL_SAFE_NO_PAD)
|
||||
);
|
||||
println!("Device info: {}", &device_info);
|
||||
println!("Done.");
|
||||
|
||||
let res = serde_json::to_string(&Signature {
|
||||
sign_data: sig,
|
||||
key_handle: encode_config(&handle_used, URL_SAFE_NO_PAD),
|
||||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_client_data(application: &str, challenge: &str) -> (Vec<u8>, Vec<u8>, String) {
|
||||
let d = format!(
|
||||
r#"{{"challenge": "{}", "version": "U2F_V2", "appId": "{}"}}"#,
|
||||
challenge, 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<u8>, Vec<u8>)> {
|
||||
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<u8> = public_key[1..public_key.len() - 1].to_vec();
|
||||
|
||||
Ok((key_handle, pk))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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] u2f::u2ferror::U2fError),
|
||||
#[error(transparent)]
|
||||
Auth(#[from] authenticator::errors::AuthenticatorError),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod auth;
|
||||
mod error;
|
||||
mod u2f;
|
||||
|
||||
use tauri::{plugin::Plugin, Invoke, Runtime};
|
||||
|
||||
pub use error::Error;
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[tauri::command]
|
||||
fn init() {
|
||||
auth::init_usb();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn register(timeout: u64, challenge: String, application: String) -> crate::Result<String> {
|
||||
auth::register(application, timeout, challenge)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn verify_registration(
|
||||
challenge: String,
|
||||
application: String,
|
||||
register_data: String,
|
||||
client_data: String,
|
||||
) -> crate::Result<String> {
|
||||
u2f::verify_registration(application, challenge, register_data, client_data)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn sign(
|
||||
timeout: u64,
|
||||
challenge: String,
|
||||
application: String,
|
||||
key_handle: String,
|
||||
) -> crate::Result<String> {
|
||||
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<u32> {
|
||||
u2f::verify_signature(
|
||||
application,
|
||||
challenge,
|
||||
sign_data,
|
||||
client_data,
|
||||
key_handle,
|
||||
pubkey,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct TauriAuthenticator<R: Runtime> {
|
||||
invoke_handler: Box<dyn Fn(Invoke<R>) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for TauriAuthenticator<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
invoke_handler: Box::new(tauri::generate_handler![
|
||||
init,
|
||||
register,
|
||||
verify_registration,
|
||||
sign,
|
||||
verify_signature
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Plugin<R> for TauriAuthenticator<R> {
|
||||
fn name(&self) -> &'static str {
|
||||
"authenticator"
|
||||
}
|
||||
|
||||
fn extend_api(&mut self, invoke: Invoke<R>) {
|
||||
(self.invoke_handler)(invoke)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use chrono::prelude::*;
|
||||
use serde::Serialize;
|
||||
use std::convert::Into;
|
||||
use u2f::messages::*;
|
||||
use u2f::protocol::*;
|
||||
use u2f::register::*;
|
||||
|
||||
static VERSION: &str = "U2F_V2";
|
||||
|
||||
pub fn make_challenge(app_id: &str, challenge_bytes: Vec<u8>) -> Challenge {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
Challenge {
|
||||
challenge: encode_config(&challenge_bytes, URL_SAFE_NO_PAD),
|
||||
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<String>,
|
||||
}
|
||||
|
||||
pub fn verify_registration(
|
||||
app_id: String,
|
||||
challenge: String,
|
||||
register_data: String,
|
||||
client_data: String,
|
||||
) -> crate::Result<String> {
|
||||
let challenge_bytes = decode_config(&challenge, URL_SAFE_NO_PAD)?;
|
||||
let challenge = make_challenge(&app_id, challenge_bytes);
|
||||
let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
|
||||
let client_data_base64 = encode_config(&client_data_bytes, URL_SAFE_NO_PAD);
|
||||
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: encode_config(&v.key_handle, URL_SAFE_NO_PAD),
|
||||
pubkey: encode_config(&v.pub_key, URL_SAFE_NO_PAD),
|
||||
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<u32> {
|
||||
let challenge_bytes = decode_config(&challenge, URL_SAFE_NO_PAD)?;
|
||||
let chal = make_challenge(&app_id, challenge_bytes);
|
||||
let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
|
||||
let client_data_base64 = encode_config(&client_data_bytes, URL_SAFE_NO_PAD);
|
||||
let key_handle_bytes = decode_config(&key_handle, URL_SAFE_NO_PAD)?;
|
||||
let pubkey_bytes = decode_config(&pub_key, URL_SAFE_NO_PAD)?;
|
||||
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()),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user