This commit is contained in:
nora 2024-08-26 19:46:44 +02:00
parent d5794d3ef0
commit ca4213ba81
7 changed files with 130 additions and 111 deletions

View file

@ -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, .. } => {

View file

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

View file

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

View file

@ -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 }
}
}
}
}

View file

@ -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()?;

View file

@ -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],
}, },

View file

@ -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);