mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
refactor
This commit is contained in:
parent
d5794d3ef0
commit
ca4213ba81
7 changed files with 130 additions and 111 deletions
|
|
@ -137,7 +137,7 @@ fn info(id_file: &Path, decrypt: bool, show_private: bool) -> eyre::Result<()> {
|
||||||
PrivateKey::Ed25519 { private_key, .. } => {
|
PrivateKey::Ed25519 { private_key, .. } => {
|
||||||
println!(
|
println!(
|
||||||
" private key: {}",
|
" private key: {}",
|
||||||
base64::prelude::BASE64_STANDARD.encode(private_key)
|
base64::prelude::BASE64_STANDARD.encode(private_key.as_bytes())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PrivateKey::EcdsaSha2NistP256 { private_key, .. } => {
|
PrivateKey::EcdsaSha2NistP256 { private_key, .. } => {
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ pub(crate) fn generate_private_key(params: KeyGenerationParams) -> PrivateKey {
|
||||||
|
|
||||||
PrivateKey::Ed25519 {
|
PrivateKey::Ed25519 {
|
||||||
public_key: private_key.verifying_key(),
|
public_key: private_key.verifying_key(),
|
||||||
private_key: private_key.to_bytes(),
|
private_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyType::Ecdsa => {
|
KeyType::Ecdsa => {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl Debug for PlaintextPrivateKey {
|
||||||
pub enum PrivateKey {
|
pub enum PrivateKey {
|
||||||
Ed25519 {
|
Ed25519 {
|
||||||
public_key: ed25519_dalek::VerifyingKey,
|
public_key: ed25519_dalek::VerifyingKey,
|
||||||
private_key: [u8; 32], // TODO: store a signing key!
|
private_key: ed25519_dalek::SigningKey,
|
||||||
},
|
},
|
||||||
EcdsaSha2NistP256 {
|
EcdsaSha2NistP256 {
|
||||||
public_key: p256::ecdsa::VerifyingKey,
|
public_key: p256::ecdsa::VerifyingKey,
|
||||||
|
|
@ -326,9 +326,9 @@ impl PlaintextPrivateKey {
|
||||||
// <https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#name-eddsa-keys>
|
// <https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#name-eddsa-keys>
|
||||||
enc.string(b"ssh-ed25519");
|
enc.string(b"ssh-ed25519");
|
||||||
enc.string(public_key);
|
enc.string(public_key);
|
||||||
let combined = private_key.len() + public_key.as_bytes().len();
|
let combined = private_key.as_bytes().len() + public_key.as_bytes().len();
|
||||||
enc.u32(combined as u32);
|
enc.u32(combined as u32);
|
||||||
enc.raw(private_key);
|
enc.raw(private_key.as_bytes());
|
||||||
enc.raw(public_key.as_bytes());
|
enc.raw(public_key.as_bytes());
|
||||||
}
|
}
|
||||||
PrivateKey::EcdsaSha2NistP256 {
|
PrivateKey::EcdsaSha2NistP256 {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use cluelessh_format::Writer;
|
use cluelessh_format::Writer;
|
||||||
|
|
||||||
use crate::public::PublicKey;
|
use crate::{private::PrivateKey, public::PublicKey};
|
||||||
|
|
||||||
// TODO SessionId newtype
|
// TODO SessionId newtype
|
||||||
pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec<u8> {
|
pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec<u8> {
|
||||||
|
|
@ -17,3 +17,58 @@ pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey)
|
||||||
|
|
||||||
s.finish()
|
s.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Signature {
|
||||||
|
Ed25519 { signature: ed25519_dalek::Signature },
|
||||||
|
EcdsaSha2NistP256 { signature: p256::ecdsa::Signature },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signature {
|
||||||
|
pub fn to_wire_encoding(&self) -> Vec<u8> {
|
||||||
|
let mut data = Writer::new();
|
||||||
|
data.string(self.algorithm_name());
|
||||||
|
match self {
|
||||||
|
Self::Ed25519 { signature } => {
|
||||||
|
// <https://datatracker.ietf.org/doc/html/rfc8709#name-signature-format>
|
||||||
|
data.string(signature.to_bytes());
|
||||||
|
}
|
||||||
|
Self::EcdsaSha2NistP256 { signature } => {
|
||||||
|
// <https://datatracker.ietf.org/doc/html/rfc5656#section-3.1.2>
|
||||||
|
|
||||||
|
let (r, s) = signature.split_scalars();
|
||||||
|
|
||||||
|
let mut signature_blob = Writer::new();
|
||||||
|
signature_blob.mpint(p256::U256::from(r.as_ref()));
|
||||||
|
signature_blob.mpint(p256::U256::from(s.as_ref()));
|
||||||
|
data.string(signature_blob.finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn algorithm_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Ed25519 { .. } => "ssh-ed25519",
|
||||||
|
Self::EcdsaSha2NistP256 { .. } => "ecdsa-sha2-nistp256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateKey {
|
||||||
|
pub fn sign(&self, data: &[u8]) -> Signature {
|
||||||
|
match self {
|
||||||
|
Self::Ed25519 { private_key, .. } => {
|
||||||
|
use ed25519_dalek::Signer;
|
||||||
|
|
||||||
|
let sig = private_key.sign(data);
|
||||||
|
Signature::Ed25519 { signature: sig }
|
||||||
|
}
|
||||||
|
Self::EcdsaSha2NistP256 { private_key, .. } => {
|
||||||
|
use p256::ecdsa::signature::Signer;
|
||||||
|
|
||||||
|
let sig = private_key.sign(data);
|
||||||
|
Signature::EcdsaSha2NistP256 { signature: sig }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::{
|
crypto::{
|
||||||
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeySigningAlgorithm,
|
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm,
|
||||||
KeyExchangeSecret, SupportedAlgorithms,
|
KeyExchangeSecret, SupportedAlgorithms,
|
||||||
},
|
},
|
||||||
packet::{Packet, PacketTransport, ProtocolIdentParser},
|
packet::{Packet, PacketTransport, ProtocolIdentParser},
|
||||||
|
|
@ -36,7 +36,7 @@ enum ClientState {
|
||||||
client_ident: Vec<u8>,
|
client_ident: Vec<u8>,
|
||||||
server_ident: Vec<u8>,
|
server_ident: Vec<u8>,
|
||||||
kex_secret: Option<KeyExchangeSecret>,
|
kex_secret: Option<KeyExchangeSecret>,
|
||||||
server_hostkey_algorithm: HostKeySigningAlgorithm,
|
server_hostkey_algorithm: HostKeyVerifyAlgorithm,
|
||||||
encryption_client_to_server: EncryptionAlgorithm,
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
client_kexinit: Vec<u8>,
|
client_kexinit: Vec<u8>,
|
||||||
|
|
@ -165,7 +165,7 @@ impl ClientConnection {
|
||||||
|
|
||||||
let server_hostkey_algorithm = kexinit.name_list()?;
|
let server_hostkey_algorithm = kexinit.name_list()?;
|
||||||
let server_hostkey_algorithm =
|
let server_hostkey_algorithm =
|
||||||
sup_algs.hostkey.find(server_hostkey_algorithm.0)?;
|
sup_algs.hostkey_verify.find(server_hostkey_algorithm.0)?;
|
||||||
debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm");
|
debug!(name = %server_hostkey_algorithm.name(), "Using host key algorithm");
|
||||||
|
|
||||||
let encryption_algorithms_client_to_server = kexinit.name_list()?;
|
let encryption_algorithms_client_to_server = kexinit.name_list()?;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
pub mod encrypt;
|
pub mod encrypt;
|
||||||
|
|
||||||
use cluelessh_format::{Reader, Writer};
|
use cluelessh_format::Reader;
|
||||||
use cluelessh_keys::{
|
use cluelessh_keys::{
|
||||||
private::{PlaintextPrivateKey, PrivateKey},
|
private::{PlaintextPrivateKey, PrivateKey},
|
||||||
public::PublicKey,
|
public::PublicKey,
|
||||||
|
signature::Signature,
|
||||||
};
|
};
|
||||||
use p256::ecdsa::signature::Signer;
|
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -110,108 +110,73 @@ impl AlgorithmName for EncryptionAlgorithm {
|
||||||
pub struct EncodedSshSignature(pub Vec<u8>);
|
pub struct EncodedSshSignature(pub Vec<u8>);
|
||||||
|
|
||||||
pub struct HostKeySigningAlgorithm {
|
pub struct HostKeySigningAlgorithm {
|
||||||
|
private_key: PrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlgorithmName for HostKeySigningAlgorithm {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
self.private_key.algorithm_name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostKeySigningAlgorithm {
|
||||||
|
pub fn new(private_key: PrivateKey) -> Self {
|
||||||
|
Self { private_key }
|
||||||
|
}
|
||||||
|
pub fn sign(&self, data: &[u8]) -> Signature {
|
||||||
|
self.private_key.sign(data)
|
||||||
|
}
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
self.private_key.public_key()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HostKeyVerifyAlgorithm {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
hostkey_private: Vec<u8>,
|
|
||||||
public_key: fn(private_key: &[u8]) -> PublicKey,
|
|
||||||
sign: fn(private_key: &[u8], data: &[u8]) -> EncodedSshSignature,
|
|
||||||
pub verify:
|
pub verify:
|
||||||
fn(public_key: &[u8], message: &[u8], signature: &EncodedSshSignature) -> Result<()>,
|
fn(public_key: &[u8], message: &[u8], signature: &EncodedSshSignature) -> Result<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlgorithmName for HostKeySigningAlgorithm {
|
impl AlgorithmName for HostKeyVerifyAlgorithm {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostKeySigningAlgorithm {
|
const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm {
|
||||||
pub fn sign(&self, data: &[u8]) -> EncodedSshSignature {
|
name: "ssh-ed25519",
|
||||||
(self.sign)(&self.hostkey_private, data)
|
verify: |public_key, message, signature| {
|
||||||
}
|
// Parse out public key
|
||||||
pub fn public_key(&self) -> PublicKey {
|
let mut public_key = Reader::new(public_key);
|
||||||
(self.public_key)(&self.hostkey_private)
|
let public_key_alg = public_key.string()?;
|
||||||
}
|
if public_key_alg != b"ssh-ed25519" {
|
||||||
}
|
return Err(peer_error!("incorrect algorithm public host key"));
|
||||||
|
}
|
||||||
|
let public_key = public_key.string()?;
|
||||||
|
let Ok(public_key) = public_key.try_into() else {
|
||||||
|
return Err(peer_error!("incorrect length for public host key"));
|
||||||
|
};
|
||||||
|
let public_key = ed25519_dalek::VerifyingKey::from_bytes(public_key)
|
||||||
|
.map_err(|err| peer_error!("incorrect public host key: {err}"))?;
|
||||||
|
|
||||||
pub fn hostkey_ed25519(hostkey_private: Vec<u8>) -> HostKeySigningAlgorithm {
|
// Parse out signature
|
||||||
HostKeySigningAlgorithm {
|
let mut signature = Reader::new(&signature.0);
|
||||||
name: "ssh-ed25519",
|
let alg = signature.string()?;
|
||||||
hostkey_private,
|
if alg != b"ssh-ed25519" {
|
||||||
public_key: |key| {
|
return Err(peer_error!("incorrect algorithm for signature"));
|
||||||
let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap());
|
}
|
||||||
let public_key = key.verifying_key();
|
let signature = signature.string()?;
|
||||||
PublicKey::Ed25519 { public_key }
|
let Ok(signature) = signature.try_into() else {
|
||||||
},
|
return Err(peer_error!("incorrect length for signature"));
|
||||||
sign: |key, data| {
|
};
|
||||||
let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap());
|
let signature = ed25519_dalek::Signature::from_bytes(signature);
|
||||||
let signature = key.sign(data);
|
|
||||||
|
|
||||||
// <https://datatracker.ietf.org/doc/html/rfc8709#section-6>
|
// Verify
|
||||||
let mut data = Writer::new();
|
public_key
|
||||||
data.string(b"ssh-ed25519");
|
.verify_strict(message, &signature)
|
||||||
data.string(signature.to_bytes());
|
.map_err(|err| peer_error!("incorrect signature: {err}"))
|
||||||
EncodedSshSignature(data.finish())
|
},
|
||||||
},
|
};
|
||||||
verify: |public_key, message, signature| {
|
|
||||||
// Parse out public key
|
|
||||||
let mut public_key = Reader::new(public_key);
|
|
||||||
let public_key_alg = public_key.string()?;
|
|
||||||
if public_key_alg != b"ssh-ed25519" {
|
|
||||||
return Err(peer_error!("incorrect algorithm public host key"));
|
|
||||||
}
|
|
||||||
let public_key = public_key.string()?;
|
|
||||||
let Ok(public_key) = public_key.try_into() else {
|
|
||||||
return Err(peer_error!("incorrect length for public host key"));
|
|
||||||
};
|
|
||||||
let public_key = ed25519_dalek::VerifyingKey::from_bytes(public_key)
|
|
||||||
.map_err(|err| peer_error!("incorrect public host key: {err}"))?;
|
|
||||||
|
|
||||||
// Parse out signature
|
|
||||||
let mut signature = Reader::new(&signature.0);
|
|
||||||
let alg = signature.string()?;
|
|
||||||
if alg != b"ssh-ed25519" {
|
|
||||||
return Err(peer_error!("incorrect algorithm for signature"));
|
|
||||||
}
|
|
||||||
let signature = signature.string()?;
|
|
||||||
let Ok(signature) = signature.try_into() else {
|
|
||||||
return Err(peer_error!("incorrect length for signature"));
|
|
||||||
};
|
|
||||||
let signature = ed25519_dalek::Signature::from_bytes(signature);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
public_key
|
|
||||||
.verify_strict(message, &signature)
|
|
||||||
.map_err(|err| peer_error!("incorrect signature: {err}"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn hostkey_ecdsa_sha2_p256(hostkey_private: Vec<u8>) -> HostKeySigningAlgorithm {
|
|
||||||
HostKeySigningAlgorithm {
|
|
||||||
name: "ecdsa-sha2-nistp256",
|
|
||||||
hostkey_private,
|
|
||||||
public_key: |key| {
|
|
||||||
let key = p256::ecdsa::SigningKey::from_slice(key).unwrap();
|
|
||||||
PublicKey::EcdsaSha2NistP256 {
|
|
||||||
public_key: *key.verifying_key(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sign: |key, data| {
|
|
||||||
let key = p256::ecdsa::SigningKey::from_slice(key).unwrap();
|
|
||||||
let signature: p256::ecdsa::Signature = key.sign(data);
|
|
||||||
let (r, s) = signature.split_scalars();
|
|
||||||
|
|
||||||
// <https://datatracker.ietf.org/doc/html/rfc5656#section-3.1.2>
|
|
||||||
let mut data = Writer::new();
|
|
||||||
data.string(b"ecdsa-sha2-nistp256");
|
|
||||||
let mut signature_blob = Writer::new();
|
|
||||||
signature_blob.mpint(p256::U256::from(r.as_ref()));
|
|
||||||
signature_blob.mpint(p256::U256::from(s.as_ref()));
|
|
||||||
data.string(signature_blob.finish());
|
|
||||||
EncodedSshSignature(data.finish())
|
|
||||||
},
|
|
||||||
verify: |_public_key, _message, _signature| todo!("ecdsa p256 verification"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AlgorithmNegotiation<T> {
|
pub struct AlgorithmNegotiation<T> {
|
||||||
pub supported: Vec<T>,
|
pub supported: Vec<T>,
|
||||||
|
|
@ -244,7 +209,8 @@ impl<T: AlgorithmName> AlgorithmNegotiation<T> {
|
||||||
|
|
||||||
pub struct SupportedAlgorithms {
|
pub struct SupportedAlgorithms {
|
||||||
pub key_exchange: AlgorithmNegotiation<KexAlgorithm>,
|
pub key_exchange: AlgorithmNegotiation<KexAlgorithm>,
|
||||||
pub hostkey: AlgorithmNegotiation<HostKeySigningAlgorithm>,
|
pub hostkey_sign: AlgorithmNegotiation<HostKeySigningAlgorithm>,
|
||||||
|
pub hostkey_verify: AlgorithmNegotiation<HostKeyVerifyAlgorithm>,
|
||||||
pub encryption_to_peer: AlgorithmNegotiation<EncryptionAlgorithm>,
|
pub encryption_to_peer: AlgorithmNegotiation<EncryptionAlgorithm>,
|
||||||
pub encryption_from_peer: AlgorithmNegotiation<EncryptionAlgorithm>,
|
pub encryption_from_peer: AlgorithmNegotiation<EncryptionAlgorithm>,
|
||||||
pub mac_to_peer: AlgorithmNegotiation<&'static str>,
|
pub mac_to_peer: AlgorithmNegotiation<&'static str>,
|
||||||
|
|
@ -258,21 +224,19 @@ impl SupportedAlgorithms {
|
||||||
pub fn secure(host_keys: &[PlaintextPrivateKey]) -> Self {
|
pub fn secure(host_keys: &[PlaintextPrivateKey]) -> Self {
|
||||||
let supported_host_keys = host_keys
|
let supported_host_keys = host_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key| match &key.private_key {
|
.map(|key| HostKeySigningAlgorithm::new(key.private_key.clone()))
|
||||||
PrivateKey::Ed25519 { private_key, .. } => hostkey_ed25519(private_key.to_vec()),
|
|
||||||
PrivateKey::EcdsaSha2NistP256 { private_key, .. } => {
|
|
||||||
hostkey_ecdsa_sha2_p256(private_key.to_bytes().to_vec())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
key_exchange: AlgorithmNegotiation {
|
key_exchange: AlgorithmNegotiation {
|
||||||
supported: vec![KEX_CURVE_25519_SHA256, KEX_ECDH_SHA2_NISTP256],
|
supported: vec![KEX_CURVE_25519_SHA256, KEX_ECDH_SHA2_NISTP256],
|
||||||
},
|
},
|
||||||
hostkey: AlgorithmNegotiation {
|
hostkey_sign: AlgorithmNegotiation {
|
||||||
supported: supported_host_keys,
|
supported: supported_host_keys,
|
||||||
},
|
},
|
||||||
|
hostkey_verify: AlgorithmNegotiation {
|
||||||
|
supported: vec![HOSTKEY_VERIFY_ED25519], // TODO: p256
|
||||||
|
},
|
||||||
encryption_to_peer: AlgorithmNegotiation {
|
encryption_to_peer: AlgorithmNegotiation {
|
||||||
supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM],
|
supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ impl ServerConnection {
|
||||||
debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
|
debug!(name = %kex_algorithm.name(), "Using KEX algorithm");
|
||||||
|
|
||||||
let server_host_key_algorithm =
|
let server_host_key_algorithm =
|
||||||
sup_algs.hostkey.find(kex.server_host_key_algorithms.0)?;
|
sup_algs.hostkey_sign.find(kex.server_host_key_algorithms.0)?;
|
||||||
debug!(name = %server_host_key_algorithm.name(), "Using host key algorithm");
|
debug!(name = %server_host_key_algorithm.name(), "Using host key algorithm");
|
||||||
|
|
||||||
// TODO: Implement aes128-ctr
|
// TODO: Implement aes128-ctr
|
||||||
|
|
@ -268,7 +268,7 @@ impl ServerConnection {
|
||||||
let packet = Packet::new_msg_kex_ecdh_reply(
|
let packet = Packet::new_msg_kex_ecdh_reply(
|
||||||
&pub_hostkey.to_wire_encoding(),
|
&pub_hostkey.to_wire_encoding(),
|
||||||
&server_public_key,
|
&server_public_key,
|
||||||
&signature.0,
|
&signature.to_wire_encoding(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.packet_transport.queue_packet(packet);
|
self.packet_transport.queue_packet(packet);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue