Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add auto accept token #14

Merged
merged 3 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
24 changes: 22 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,22 @@ 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);
if let Some(token) = auto_accept_token {
payload.insert("autoAcceptToken", token);
}

match proxy {
Some(a) => {
Expand Down Expand Up @@ -282,7 +295,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 +327,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
}
}
}
}
49 changes: 48 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,17 @@ pub async fn sign_git_commit(armour_output: bool) -> Result<()> {
}
log.success("Accepted")?;

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 {
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