Skip to content

Commit

Permalink
✨ Add auto accept token
Browse files Browse the repository at this point in the history
  • Loading branch information
reckter committed Sep 30, 2022
1 parent b6fe628 commit 6c1dedc
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
27 changes: 25 additions & 2 deletions src/agent/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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])?;
Expand Down
33 changes: 33 additions & 0 deletions src/auto_accept.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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
}
}
}
}
54 changes: 53 additions & 1 deletion src/git.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod agent;
mod auto_accept;
mod communication;
mod constants;
mod keychain;
Expand All @@ -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;

Expand All @@ -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};

Expand All @@ -33,12 +40,21 @@ struct GgpRequest {
message_type: String,
#[serde(rename = "relayId")]
relay_id: String,

#[serde(rename = "autoAcceptToken")]
auto_accept_token: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
struct GgpResponse {
signature: Option<String>,
accepted: bool,

#[serde(rename = "autoAcceptToken")]
auto_accept_token: Option<String>,

#[serde(rename = "autoAcceptExpiresAt")]
auto_accept_expires_at: Option<String>,
}

struct ArmourSource {
Expand Down Expand Up @@ -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...")?;
Expand All @@ -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 {
Expand All @@ -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)?;

Expand Down
32 changes: 32 additions & 0 deletions src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, KeyChainError> {
let keyring = Keyring::new(&SERVICE, id);

Expand Down Expand Up @@ -90,6 +92,36 @@ pub fn store_pairing_data(key: Vec<u8>, 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<String, KeyChainError> {
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<String, KeyChainError> {
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)
}
Expand Down
9 changes: 6 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
_ => {
Expand Down
23 changes: 9 additions & 14 deletions src/me.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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")?;
Expand All @@ -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")?;
}
Expand Down
8 changes: 6 additions & 2 deletions src/ssh_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,18 @@ pub struct SshProxy {
pub struct PhoneSignResponse {
pub signature: Option<String>,
pub accepted: bool,
#[serde(rename = "autoAcceptToken")]
pub auto_accept_token: Option<String>,

#[serde(rename = "autoAcceptExpiresAt")]
pub auto_accept_expires_at: Option<String>,
}

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()?;
}
Expand Down

0 comments on commit 6c1dedc

Please sign in to comment.