fix RSA key signatures

This commit is contained in:
Joseph Montanaro 2024-07-04 21:57:27 -04:00
parent 10231df860
commit a32e36be7e
8 changed files with 124 additions and 32 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "creddy", "name": "creddy",
"version": "0.5.1", "version": "0.5.2",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

67
src-tauri/Cargo.lock generated
View File

@ -1071,7 +1071,7 @@ dependencies = [
"cocoa-foundation", "cocoa-foundation",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"foreign-types", "foreign-types 0.5.0",
"libc", "libc",
"objc", "objc",
] ]
@ -1146,7 +1146,7 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"core-foundation", "core-foundation",
"core-graphics-types", "core-graphics-types",
"foreign-types", "foreign-types 0.5.0",
"libc", "libc",
] ]
@ -1196,7 +1196,7 @@ dependencies = [
[[package]] [[package]]
name = "creddy" name = "creddy"
version = "0.5.0" version = "0.5.2"
dependencies = [ dependencies = [
"argon2", "argon2",
"auto-launch", "auto-launch",
@ -1211,13 +1211,17 @@ dependencies = [
"futures", "futures",
"is-terminal", "is-terminal",
"once_cell", "once_cell",
"openssl",
"rfd 0.13.0", "rfd 0.13.0",
"rsa",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"signature 2.2.0", "signature 2.2.0",
"sodiumoxide", "sodiumoxide",
"sqlx", "sqlx",
"ssh-agent-lib", "ssh-agent-lib",
"ssh-encoding",
"ssh-key", "ssh-key",
"strum", "strum",
"strum_macros", "strum_macros",
@ -1841,6 +1845,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.5.0" version = "0.5.0"
@ -1848,7 +1861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [ dependencies = [
"foreign-types-macros", "foreign-types-macros",
"foreign-types-shared", "foreign-types-shared 0.3.1",
] ]
[[package]] [[package]]
@ -1862,6 +1875,12 @@ dependencies = [
"syn 2.0.68", "syn 2.0.68",
] ]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "foreign-types-shared" name = "foreign-types-shared"
version = "0.3.1" version = "0.3.1"
@ -3426,12 +3445,50 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
]
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.5" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -4788,7 +4845,7 @@ dependencies = [
"bytemuck", "bytemuck",
"cfg_aliases", "cfg_aliases",
"core-graphics", "core-graphics",
"foreign-types", "foreign-types 0.5.0",
"js-sys", "js-sys",
"log", "log",
"objc2", "objc2",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "creddy" name = "creddy"
version = "0.5.1" version = "0.5.2"
description = "A friendly AWS credentials manager" description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"] authors = ["Joseph Montanaro"]
license = "" license = ""
@ -58,6 +58,10 @@ tokio-stream = "0.1.15"
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] } sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] }
tokio-util = { version = "0.7.11", features = ["codec"] } tokio-util = { version = "0.7.11", features = ["codec"] }
futures = "0.3.30" futures = "0.3.30"
openssl = "0.10.64"
rsa = "0.9.6"
sha2 = "0.10.8"
ssh-encoding = "0.2.0"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@ -12,6 +12,8 @@ use serde::ser::{
SerializeStruct, SerializeStruct,
}; };
use serde::de::{self, Visitor}; use serde::de::{self, Visitor};
use sha2::{Sha256, Sha512};
use signature::{Signer, SignatureEncoding};
use sqlx::{ use sqlx::{
FromRow, FromRow,
Sqlite, Sqlite,
@ -19,11 +21,15 @@ use sqlx::{
Transaction, Transaction,
types::Uuid, types::Uuid,
}; };
use ssh_agent_lib::proto::message::Identity; use ssh_agent_lib::proto::message::{
Identity,
SignRequest,
};
use ssh_encoding::Encode;
use ssh_key::{ use ssh_key::{
Algorithm, Algorithm,
LineEnding, LineEnding,
private::PrivateKey, private::{PrivateKey, KeypairData},
public::PublicKey, public::PublicKey,
}; };
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
@ -119,6 +125,33 @@ impl SshKey {
Ok(identities) Ok(identities)
} }
pub fn sign_request(&self, req: &SignRequest) -> Result<Vec<u8>, HandlerError> {
let mut sig = Vec::new();
match self.private_key.key_data() {
KeypairData::Rsa(keypair) => {
// 2 is the flag value for `SSH_AGENT_RSA_SHA2_256`
if req.flags & 2 > 0 {
let signer = rsa::pkcs1v15::SigningKey::<Sha256>::try_from(keypair)?;
let sig_data = signer.try_sign(&req.data)?.to_vec();
"rsa-sha-256".encode(&mut sig)?;
sig_data.encode(&mut sig)?;
}
else {
let signer = rsa::pkcs1v15::SigningKey::<Sha512>::try_from(keypair)?;
let sig_data = signer.try_sign(&req.data)?.to_vec();
"rsa-sha2-512".encode(&mut sig)?;
sig_data.encode(&mut sig)?;
}
},
_ => {
let sig_data = self.private_key.try_sign(&req.data)?;
self.algorithm.as_str().encode(&mut sig)?;
sig_data.as_bytes().encode(&mut sig)?;
},
}
Ok(sig)
}
} }

View File

@ -195,6 +195,10 @@ pub enum HandlerError {
SshAgent(#[from] ssh_agent_lib::error::AgentError), SshAgent(#[from] ssh_agent_lib::error::AgentError),
#[error(transparent)] #[error(transparent)]
SshKey(#[from] ssh_key::Error), SshKey(#[from] ssh_key::Error),
#[error(transparent)]
Signature(#[from] signature::Error),
#[error(transparent)]
Encoding(#[from] ssh_encoding::Error),
} }

View File

@ -1,5 +1,4 @@
use futures::SinkExt; use futures::SinkExt;
use signature::Signer;
use ssh_agent_lib::agent::MessageCodec; use ssh_agent_lib::agent::MessageCodec;
use ssh_agent_lib::proto::message::{ use ssh_agent_lib::proto::message::{
Message, Message,
@ -36,12 +35,21 @@ async fn handle(
adapter.send(resp).await?; adapter.send(resp).await?;
}, },
Message::SignRequest(req) => { Message::SignRequest(req) => {
// CloseWaiter could corrupt the framing, but this doesn't matter // Note: If the client writes more data to the stream *while* at the
// since we don't plan to pull any more frames out of the stream // same time waiting for a resopnse to a previous request, this will
// corrupt the framing. Clients don't seem to behave that way though?
let waiter = CloseWaiter { stream: adapter.get_mut() }; let waiter = CloseWaiter { stream: adapter.get_mut() };
let resp = sign_request(req, app_handle.clone(), client_pid, waiter).await?; let resp = sign_request(req, app_handle.clone(), client_pid, waiter).await?;
// have to do this before we send since we can't inspect the message after
let is_failure = matches!(resp, Message::Failure);
adapter.send(resp).await?; adapter.send(resp).await?;
break;
if is_failure {
// this way we don't get spammed with requests for other keys
// after denying the first
break
}
}, },
_ => adapter.send(Message::Failure).await?, _ => adapter.send(Message::Failure).await?,
}; };
@ -93,15 +101,8 @@ async fn sign_request(
} }
let key = state.sshkey_by_name(&key_name).await?; let key = state.sshkey_by_name(&key_name).await?;
let sig = Signer::sign(&key.private_key, &req.data); let sig = key.sign_request(&req)?;
let key_type = key.algorithm.as_str().as_bytes(); Ok(Message::SignResponse(sig))
let payload_len = key_type.len() + sig.as_bytes().len() + 8;
let mut payload = Vec::with_capacity(payload_len);
encode_string(&mut payload, key.algorithm.as_str().as_bytes());
encode_string(&mut payload, sig.as_bytes());
Ok(Message::SignResponse(payload))
}; };
let res = proceed.await; let res = proceed.await;
@ -112,10 +113,3 @@ async fn sign_request(
lease.release(); lease.release();
res res
} }
fn encode_string(buf: &mut Vec<u8>, s: &[u8]) {
let len = s.len() as u32;
buf.extend(len.to_be_bytes());
buf.extend(s);
}

View File

@ -50,7 +50,7 @@
} }
}, },
"productName": "creddy", "productName": "creddy",
"version": "0.5.1", "version": "0.5.2",
"identifier": "creddy", "identifier": "creddy",
"plugins": {}, "plugins": {},
"app": { "app": {

View File

@ -60,7 +60,7 @@
<button <button
type="button" type="button"
role="tab" role="tab"
class="join-item flex-1 btn border border-primary" class="join-item flex-1 btn border border-primary hover:border-primary"
class:btn-primary={mode === 'file'} class:btn-primary={mode === 'file'}
on:click={() => mode = 'file'} on:click={() => mode = 'file'}
> >
@ -69,7 +69,7 @@
<button <button
type="button" type="button"
role="tab" role="tab"
class="join-item flex-1 btn border border-primary" class="join-item flex-1 btn border border-primary hover:border-primary"
class:btn-primary={mode === 'direct'} class:btn-primary={mode === 'direct'}
on:click={() => mode = 'direct'} on:click={() => mode = 'direct'}
> >