From 6c1dedc418ae1ccd392b9feec917d5f9e842982d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20G=C3=BCdelh=C3=B6fer?= Date: Fri, 30 Sep 2022 12:13:20 +0200 Subject: [PATCH 1/3] :sparkles: Add auto accept token --- Cargo.lock | 1 + Cargo.toml | 1 + src/agent/sign.rs | 27 +++++++++++++++++++++-- src/auto_accept.rs | 33 ++++++++++++++++++++++++++++ src/git.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++- src/keychain.rs | 32 +++++++++++++++++++++++++++ src/main.rs | 9 +++++--- src/me.rs | 23 ++++++++------------ src/ssh_agent.rs | 8 +++++-- 9 files changed, 166 insertions(+), 22 deletions(-) create mode 100644 src/auto_accept.rs diff --git a/Cargo.lock b/Cargo.lock index b6931da..9217b19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,7 @@ dependencies = [ "anyhow", "base64 0.13.0", "byteorder", + "chrono", "clap", "clipboard", "colored", diff --git a/Cargo.toml b/Cargo.toml index b0f656a..25a037e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ homepage = "https://creekey.io" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.19" ctrlc = "3.1.9" base64 = "0.13.0" dirs = "3.0.2" diff --git a/src/agent/sign.rs b/src/agent/sign.rs index b572a40..c8389e4 100644 --- a/src/agent/sign.rs +++ b/src/agent/sign.rs @@ -21,7 +21,8 @@ use ring_compat::signature::ecdsa::p256::NistP256; use ring_compat::signature::ecdsa::p384::NistP384; use ring_compat::signature::Verifier; -use crate::keychain::{get_phone_id, get_secret_key}; +use crate::auto_accept::get_auto_accept; +use crate::keychain::{get_phone_id, get_secret_key, store_auto_accept}; use crate::sign_on_phone::{sign_on_phone, SignError}; use thrussh_keys::key::{parse_public_key, PublicKey}; use tokio::io::AsyncWriteExt; @@ -248,10 +249,25 @@ pub async fn sign_request( let base64_data = base64::encode(data); let relay_id = base64::encode_config(randombytes(32), base64::URL_SAFE); + let request_id = match proxy { + None => None, + Some(proxy) => Some(format!("{}@{}", name, proxy.host.clone())), + }; + let auto_accept_token = match request_id.clone() { + None => None, + Some(request_id) => get_auto_accept("ssh".to_string(), request_id.clone()), + }; + let mut payload = HashMap::new(); payload.insert("type", "ssh".to_string()); payload.insert("data", base64_data); payload.insert("userName", name); + match auto_accept_token { + None => {} + Some(token) => { + payload.insert("autoAcceptToken", token); + } + } match proxy { Some(a) => { @@ -282,7 +298,6 @@ pub async fn sign_request( return Ok(()); } }; - let phone_response: PhoneSignResponse = match sign_on_phone(payload, phone_id, relay_id, key.clone()).await { Ok(res) => res, @@ -315,6 +330,14 @@ pub async fn sign_request( let signature_bytes = base64::decode(phone_response.signature.unwrap())?; println!("responding to socket with authorization"); + if let (Some(auto_accept_token), Some(expires_at), Some(request_id)) = ( + phone_response.auto_accept_token, + phone_response.auto_accept_expires_at, + request_id, + ) { + store_auto_accept("ssh".to_string(), request_id, auto_accept_token, expires_at)?; + } + let typ = 14u8; let mut msg_payload = vec![]; std::io::Write::write(&mut msg_payload, &[typ])?; diff --git a/src/auto_accept.rs b/src/auto_accept.rs new file mode 100644 index 0000000..c951e2c --- /dev/null +++ b/src/auto_accept.rs @@ -0,0 +1,33 @@ +use crate::keychain::{get_auto_accept_expires_at, get_auto_accept_token, KeyChainError}; +use crate::output::Log; +use chrono::{DateTime, Utc}; +use std::env; + +pub fn get_auto_accept(request_type: String, request_id: String) -> Option { + let auto_accept_expires_at = + match get_auto_accept_expires_at(request_type.clone(), request_id.clone()) { + Ok(it) => Some(it), + Err(error) => match error { + KeyChainError::Missing => None, + e => { + Log::NONE.handle_keychain_error("auto accept", e).ok()?; + None + } + }, + }; + + match auto_accept_expires_at { + None => None, + Some(expires_at) => { + let date = DateTime::parse_from_rfc3339(expires_at.as_str()).ok()?; + if date > Utc::now() { + match get_auto_accept_token(request_type, request_id) { + Ok(token) => Some(token), + Err(_) => None, + } + } else { + None + } + } + } +} diff --git a/src/git.rs b/src/git.rs index 81d5a23..8b19343 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,4 +1,5 @@ mod agent; +mod auto_accept; mod communication; mod constants; mod keychain; @@ -7,11 +8,16 @@ mod output; mod sign_on_phone; mod ssh_agent; +use crate::auto_accept::get_auto_accept; use crate::communication::PollError; -use crate::keychain::{get_phone_id, get_secret_key}; +use crate::keychain::{ + get_auto_accept_expires_at, get_auto_accept_token, get_phone_id, get_secret_key, + store_auto_accept, KeyChainError, +}; use crate::output::{check_color_tty, Log}; use crate::sign_on_phone::{sign_on_phone, SignError}; use anyhow::Result; +use std::borrow::BorrowMut; use pgp::armor::BlockType; @@ -23,6 +29,7 @@ use std::collections::BTreeMap; use std::env; use std::fs; +use chrono::{DateTime, Utc}; use std::io::{stdin, stdout, Read, Write}; use std::process::{Command, Stdio}; @@ -33,12 +40,21 @@ struct GgpRequest { message_type: String, #[serde(rename = "relayId")] relay_id: String, + + #[serde(rename = "autoAcceptToken")] + auto_accept_token: Option, } #[derive(Serialize, Deserialize, Debug)] struct GgpResponse { signature: Option, accepted: bool, + + #[serde(rename = "autoAcceptToken")] + auto_accept_token: Option, + + #[serde(rename = "autoAcceptExpiresAt")] + auto_accept_expires_at: Option, } struct ArmourSource { @@ -90,6 +106,24 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> { stdin().read_to_string(&mut buffer)?; + let cloned_buffer = buffer.clone(); + let lines = cloned_buffer.split("\n"); + let mut data = lines.map(|line| line.split_once(" ")); + let committer_data = data.find(|it| match it { + None => false, + Some((line_type, data)) => line_type.to_string() == "committer", + }); + + let committer = match committer_data { + Some(Some((_, committer))) => { + let mut parts: Vec<&str> = committer.split(" ").collect(); + parts.remove(parts.len() - 1); + parts.remove(parts.len() - 1); + parts.join(" ") + } + _ => return Err(anyhow!("Could not parse committer!")), + }; + let base64_data = base64::encode(&buffer); log.waiting_on("Waiting on Phone Authorization...")?; @@ -110,10 +144,12 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> { }; let relay_id = base64::encode_config(randombytes(32), base64::URL_SAFE); + let auto_accept_token = get_auto_accept("git".to_string(), committer.clone()); let request = GgpRequest { data: base64_data, message_type: "gpg".to_string(), relay_id: relay_id.clone(), + auto_accept_token, }; let response: GgpResponse = match sign_on_phone(request, phone_id, relay_id, key).await { @@ -139,6 +175,22 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> { } log.success("Accepted")?; + match response.auto_accept_token { + None => {} + Some(auto_accept_token) => match response.auto_accept_expires_at { + None => {} + Some(expires_at) => { + log.info("Storing auto accept token!"); + store_auto_accept( + "git".to_string(), + committer.to_string(), + auto_accept_token, + expires_at, + )?; + } + }, + } + if let Some(data_base64) = response.signature { let out = base64::decode(data_base64)?; diff --git a/src/keychain.rs b/src/keychain.rs index 7c2e449..cc527bd 100644 --- a/src/keychain.rs +++ b/src/keychain.rs @@ -28,6 +28,8 @@ const PHONE_ID: &str = "phone-id"; const PAIRING_DATA: &str = "pairing-data"; const GPG_KEY: &str = "gpg-key"; +const AUTO_ACCEPT: &str = "auto-accept"; + fn get(id: &str) -> Result { let keyring = Keyring::new(&SERVICE, id); @@ -90,6 +92,36 @@ pub fn store_pairing_data(key: Vec, phone_id: String) -> Result<(), KeyChain set(PAIRING_DATA, format!("{}|{}", phone_id, key_base64)) } +pub fn get_auto_accept_token( + request_type: String, + request_id: String, +) -> Result { + get(format!("{}-{}-{}-token", AUTO_ACCEPT, request_type, request_id).as_str()) +} + +pub fn get_auto_accept_expires_at( + request_type: String, + request_id: String, +) -> Result { + get(format!("{}-{}-{}-expires-at", AUTO_ACCEPT, request_type, request_id).as_str()) +} + +pub fn store_auto_accept( + request_type: String, + request_id: String, + auto_accept_token: String, + expires_at: String, +) -> Result<(), KeyChainError> { + set( + format!("{}-{}-{}-token", AUTO_ACCEPT, request_type, request_id).as_str(), + auto_accept_token, + ); + set( + format!("{}-{}-{}-expires-at", AUTO_ACCEPT, request_type, request_id).as_str(), + expires_at, + ) +} + pub fn delete_pairing_data() -> Result<(), KeyChainError> { delete(PAIRING_DATA) } diff --git a/src/main.rs b/src/main.rs index ace4028..f84e167 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use crate::unpair::unpair; #[allow(dead_code)] // because we have multiple entry points. mod agent; +mod auto_accept; mod communication; mod constants; mod keychain; @@ -93,9 +94,11 @@ async fn main() -> Result<()> { ("testssh", _) => test_sign().await, ("setupssh", Some(matches)) => setup_ssh(matches.is_present("force")), ("setupgit", Some(matches)) => setup_git(matches.is_present("force")), - ("me", Some(matches)) => { - print_ssh_key(matches.is_present("copy"), matches.is_present("raw"), matches.is_present("gpg")) - } + ("me", Some(matches)) => print_ssh_key( + matches.is_present("copy"), + matches.is_present("raw"), + matches.is_present("gpg"), + ), ("agent", _) => start_agent(matches.is_present("daemonize")).await, ("proxy", Some(matches)) => start_ssh_proxy(matches), _ => { diff --git a/src/me.rs b/src/me.rs index b1ddde7..a11fdec 100644 --- a/src/me.rs +++ b/src/me.rs @@ -1,6 +1,6 @@ +use crate::keychain::get_gpg_from_keychain; use crate::output::Log; use crate::ssh_agent::read_ssh_key; -use crate::keychain::get_gpg_from_keychain; use anyhow::{anyhow, Result}; use clipboard::{ClipboardContext, ClipboardProvider}; @@ -13,18 +13,13 @@ pub fn print_ssh_key(copy_to_clipboard: bool, raw: bool, gpg: bool) -> Result<() if copy_to_clipboard { let mut ctx: ClipboardContext = ClipboardProvider::new() .map_err(|_err| anyhow!("Could not create ClipboardProvider"))?; - let key_to_copy = if gpg { - gpg_key.clone() - } else { - key.clone() - }; - - ctx.set_contents(key_to_copy.clone()) - .map_err(|err| { - println!("{}", err); - log.error("Could not set clipboard").unwrap(); - anyhow!("error setting clipboard") - })?; + let key_to_copy = if gpg { gpg_key.clone() } else { key.clone() }; + + ctx.set_contents(key_to_copy.clone()).map_err(|err| { + println!("{}", err); + log.error("Could not set clipboard").unwrap(); + anyhow!("error setting clipboard") + })?; log.success("Copied to clipboard")?; } else { log.user_todo("You can use '--copy' to automatically copy the key to your clipboard")?; @@ -36,7 +31,7 @@ pub fn print_ssh_key(copy_to_clipboard: bool, raw: bool, gpg: bool) -> Result<() println!("{}", key); } - if (!raw) { + if !raw { println!(); log.info("gpg key:\n")?; } diff --git a/src/ssh_agent.rs b/src/ssh_agent.rs index 8362ede..375497c 100644 --- a/src/ssh_agent.rs +++ b/src/ssh_agent.rs @@ -77,14 +77,18 @@ pub struct SshProxy { pub struct PhoneSignResponse { pub signature: Option, pub accepted: bool, + #[serde(rename = "autoAcceptToken")] + pub auto_accept_token: Option, + + #[serde(rename = "autoAcceptExpiresAt")] + pub auto_accept_expires_at: Option, } pub async fn start_agent(should_daemonize: bool) -> Result<()> { check_color_tty(); if should_daemonize { - let daemonize = Daemonize::new() - .pid_file("/tmp/ck-agent.pid"); + let daemonize = Daemonize::new().pid_file("/tmp/ck-agent.pid"); Log::NONE.waiting_on("Starting deamon...")?; daemonize.start()?; } From 1a1a5422a8c0fe8040996259593a3fe080ef6141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20G=C3=BCdelh=C3=B6fer?= Date: Tue, 4 Oct 2022 21:13:56 +0200 Subject: [PATCH 2/3] Remove unnessesary log statement --- src/git.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/git.rs b/src/git.rs index 8b19343..30591df 100644 --- a/src/git.rs +++ b/src/git.rs @@ -180,7 +180,6 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> { Some(auto_accept_token) => match response.auto_accept_expires_at { None => {} Some(expires_at) => { - log.info("Storing auto accept token!"); store_auto_accept( "git".to_string(), committer.to_string(), From b7c228cd4a9e29d3da2f7ee5786e0570de1ac7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20G=C3=BCdelh=C3=B6fer?= Date: Wed, 5 Oct 2022 09:52:38 +0200 Subject: [PATCH 3/3] :art: --- src/agent/sign.rs | 7 ++----- src/git.rs | 22 +++++++++------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/agent/sign.rs b/src/agent/sign.rs index c8389e4..60dbbff 100644 --- a/src/agent/sign.rs +++ b/src/agent/sign.rs @@ -262,11 +262,8 @@ pub async fn sign_request( payload.insert("type", "ssh".to_string()); payload.insert("data", base64_data); payload.insert("userName", name); - match auto_accept_token { - None => {} - Some(token) => { - payload.insert("autoAcceptToken", token); - } + if let Some(token) = auto_accept_token { + payload.insert("autoAcceptToken", token); } match proxy { diff --git a/src/git.rs b/src/git.rs index 30591df..4d0b4bb 100644 --- a/src/git.rs +++ b/src/git.rs @@ -175,19 +175,15 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> { } log.success("Accepted")?; - match response.auto_accept_token { - None => {} - Some(auto_accept_token) => match response.auto_accept_expires_at { - None => {} - Some(expires_at) => { - store_auto_accept( - "git".to_string(), - committer.to_string(), - auto_accept_token, - expires_at, - )?; - } - }, + if let Some(auto_accept_token) = response.auto_accept_token { + if let Some(expires_at) = response.auto_accept_expires_at { + store_auto_accept( + "git".to_string(), + committer.to_string(), + auto_accept_token, + expires_at, + )?; + } } if let Some(data_base64) = response.signature {