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

@ -157,7 +157,7 @@ pub(crate) fn generate_private_key(params: KeyGenerationParams) -> PrivateKey {
PrivateKey::Ed25519 {
public_key: private_key.verifying_key(),
private_key: private_key.to_bytes(),
private_key,
}
}
KeyType::Ecdsa => {

View file

@ -36,7 +36,7 @@ impl Debug for PlaintextPrivateKey {
pub enum PrivateKey {
Ed25519 {
public_key: ed25519_dalek::VerifyingKey,
private_key: [u8; 32], // TODO: store a signing key!
private_key: ed25519_dalek::SigningKey,
},
EcdsaSha2NistP256 {
public_key: p256::ecdsa::VerifyingKey,
@ -326,9 +326,9 @@ impl PlaintextPrivateKey {
// <https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#name-eddsa-keys>
enc.string(b"ssh-ed25519");
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.raw(private_key);
enc.raw(private_key.as_bytes());
enc.raw(public_key.as_bytes());
}
PrivateKey::EcdsaSha2NistP256 {

View file

@ -1,6 +1,6 @@
use cluelessh_format::Writer;
use crate::public::PublicKey;
use crate::{private::PrivateKey, public::PublicKey};
// TODO SessionId newtype
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()
}
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::{
crypto::{
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeySigningAlgorithm,
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm,
KeyExchangeSecret, SupportedAlgorithms,
},
packet::{Packet, PacketTransport, ProtocolIdentParser},
@ -36,7 +36,7 @@ enum ClientState {
client_ident: Vec<u8>,
server_ident: Vec<u8>,
kex_secret: Option<KeyExchangeSecret>,
server_hostkey_algorithm: HostKeySigningAlgorithm,
server_hostkey_algorithm: HostKeyVerifyAlgorithm,
encryption_client_to_server: EncryptionAlgorithm,
encryption_server_to_client: EncryptionAlgorithm,
client_kexinit: Vec<u8>,
@ -165,7 +165,7 @@ impl ClientConnection {
let server_hostkey_algorithm = kexinit.name_list()?;
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");
let encryption_algorithms_client_to_server = kexinit.name_list()?;

View file

@ -1,11 +1,11 @@
pub mod encrypt;
use cluelessh_format::{Reader, Writer};
use cluelessh_format::Reader;
use cluelessh_keys::{
private::{PlaintextPrivateKey, PrivateKey},
public::PublicKey,
signature::Signature,
};
use p256::ecdsa::signature::Signer;
use sha2::Digest;
use crate::{
@ -110,108 +110,73 @@ impl AlgorithmName for EncryptionAlgorithm {
pub struct EncodedSshSignature(pub Vec<u8>);
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,
hostkey_private: Vec<u8>,
public_key: fn(private_key: &[u8]) -> PublicKey,
sign: fn(private_key: &[u8], data: &[u8]) -> EncodedSshSignature,
pub verify:
fn(public_key: &[u8], message: &[u8], signature: &EncodedSshSignature) -> Result<()>,
}
impl AlgorithmName for HostKeySigningAlgorithm {
impl AlgorithmName for HostKeyVerifyAlgorithm {
fn name(&self) -> &'static str {
self.name
}
}
impl HostKeySigningAlgorithm {
pub fn sign(&self, data: &[u8]) -> EncodedSshSignature {
(self.sign)(&self.hostkey_private, data)
}
pub fn public_key(&self) -> PublicKey {
(self.public_key)(&self.hostkey_private)
}
}
const HOSTKEY_VERIFY_ED25519: HostKeyVerifyAlgorithm = HostKeyVerifyAlgorithm {
name: "ssh-ed25519",
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}"))?;
pub fn hostkey_ed25519(hostkey_private: Vec<u8>) -> HostKeySigningAlgorithm {
HostKeySigningAlgorithm {
name: "ssh-ed25519",
hostkey_private,
public_key: |key| {
let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap());
let public_key = key.verifying_key();
PublicKey::Ed25519 { public_key }
},
sign: |key, data| {
let key = ed25519_dalek::SigningKey::from_bytes(key.try_into().unwrap());
let signature = key.sign(data);
// 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);
// <https://datatracker.ietf.org/doc/html/rfc8709#section-6>
let mut data = Writer::new();
data.string(b"ssh-ed25519");
data.string(signature.to_bytes());
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"),
}
}
// Verify
public_key
.verify_strict(message, &signature)
.map_err(|err| peer_error!("incorrect signature: {err}"))
},
};
pub struct AlgorithmNegotiation<T> {
pub supported: Vec<T>,
@ -244,7 +209,8 @@ impl<T: AlgorithmName> AlgorithmNegotiation<T> {
pub struct SupportedAlgorithms {
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_from_peer: AlgorithmNegotiation<EncryptionAlgorithm>,
pub mac_to_peer: AlgorithmNegotiation<&'static str>,
@ -258,21 +224,19 @@ impl SupportedAlgorithms {
pub fn secure(host_keys: &[PlaintextPrivateKey]) -> Self {
let supported_host_keys = host_keys
.iter()
.map(|key| match &key.private_key {
PrivateKey::Ed25519 { private_key, .. } => hostkey_ed25519(private_key.to_vec()),
PrivateKey::EcdsaSha2NistP256 { private_key, .. } => {
hostkey_ecdsa_sha2_p256(private_key.to_bytes().to_vec())
}
})
.map(|key| HostKeySigningAlgorithm::new(key.private_key.clone()))
.collect();
Self {
key_exchange: AlgorithmNegotiation {
supported: vec![KEX_CURVE_25519_SHA256, KEX_ECDH_SHA2_NISTP256],
},
hostkey: AlgorithmNegotiation {
hostkey_sign: AlgorithmNegotiation {
supported: supported_host_keys,
},
hostkey_verify: AlgorithmNegotiation {
supported: vec![HOSTKEY_VERIFY_ED25519], // TODO: p256
},
encryption_to_peer: AlgorithmNegotiation {
supported: vec![encrypt::CHACHA20POLY1305, encrypt::AES256_GCM],
},

View file

@ -146,7 +146,7 @@ impl ServerConnection {
debug!(name = %kex_algorithm.name(), "Using KEX 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");
// TODO: Implement aes128-ctr
@ -268,7 +268,7 @@ impl ServerConnection {
let packet = Packet::new_msg_kex_ecdh_reply(
&pub_hostkey.to_wire_encoding(),
&server_public_key,
&signature.0,
&signature.to_wire_encoding(),
);
self.packet_transport.queue_packet(packet);