diff options
| -rw-r--r-- | oxish-proto/src/host_keys.rs | 106 | ||||
| -rw-r--r-- | oxish-proto/src/key_exchange.rs | 103 | ||||
| -rw-r--r-- | oxish-proto/src/lib.rs | 2 | ||||
| -rw-r--r-- | oxish/src/bin/oxish-server.rs | 2 | ||||
| -rw-r--r-- | oxish/src/lib.rs | 8 | ||||
| -rw-r--r-- | oxish/src/server.rs | 6 | ||||
| -rw-r--r-- | oxish/src/session/mod.rs | 4 | ||||
| -rw-r--r-- | oxish/src/tests.rs | 4 |
8 files changed, 120 insertions, 115 deletions
diff --git a/oxish-proto/src/host_keys.rs b/oxish-proto/src/host_keys.rs new file mode 100644 index 0000000..b8ead0c --- /dev/null +++ b/oxish-proto/src/host_keys.rs @@ -0,0 +1,106 @@ +use zeroize::Zeroizing; + +use crate::{ + Decode, Decoded, Encode, ProtoError, PublicKeyAlgorithm, + crypto::{CryptoError, CryptoProvider, SigningKey}, + key_exchange::Negotiated, +}; + +/// The server's host keys, used to authenticate the key exchange +#[expect(clippy::type_complexity)] +pub struct HostKeys(Vec<(Zeroizing<Vec<u8>>, Box<dyn SigningKey>)>); + +impl HostKeys { + /// Create a new set of host keys from the given PKCS#8 private keys + /// + /// `pkcs8` must have more than 0 and less than 16 elements. + pub fn new( + pkcs8: impl Iterator<Item = Zeroizing<Vec<u8>>>, + provider: &dyn CryptoProvider, + ) -> Result<Self, ProtoError> { + let mut keys = Vec::new(); + for pkcs8 in pkcs8 { + if keys.len() >= Self::MAX_KEYS { + return Err(ProtoError::TooManyHostKeys); + } + + let signing_key = provider.signing_key_from_pkcs8(&pkcs8)?; + keys.push((pkcs8, signing_key)); + } + + if keys.is_empty() { + return Err(ProtoError::NoHostKeys); + } + + Ok(Self(keys)) + } + + /// Select the host key matching the negotiated algorithm + pub fn key<'a>(&'a self, negotiated: &Negotiated) -> Result<ServerHostKey<'a>, CryptoError> { + let mut iter = self.0.iter(); + match iter.find(|(_, key)| key.algorithm() == negotiated.server_host_key) { + Some((pkcs8, key)) => Ok(ServerHostKey { + pkcs8, + key: key.as_ref(), + }), + None => Err(CryptoError::UnknownAlgorithm), + } + } + + /// The public key algorithms of the held host keys + pub fn algorithms(&self) -> impl Iterator<Item = PublicKeyAlgorithm<'static>> + '_ { + self.0.iter().map(|(_, key)| key.algorithm()) + } + + const MAX_KEYS: usize = 16; +} + +/// A borrowed single host key, used to sign the key exchange output +pub struct ServerHostKey<'a> { + pkcs8: &'a Zeroizing<Vec<u8>>, + pub(crate) key: &'a dyn SigningKey, +} + +impl Encode for ServerHostKey<'_> { + fn encode(&self, buf: &mut Vec<u8>) { + let Self { pkcs8, key: _ } = self; + pkcs8.encode(buf); + } +} + +#[doc(hidden)] // for testing +impl<'a> From<(&'a Zeroizing<Vec<u8>>, &'a dyn SigningKey)> for ServerHostKey<'a> { + fn from((pkcs8, key): (&'a Zeroizing<Vec<u8>>, &'a dyn SigningKey)) -> Self { + Self { pkcs8, key } + } +} + +/// A single host key, used to sign rekeying exchanges +pub struct SessionHostKey(pub(crate) Box<dyn SigningKey>); + +impl SessionHostKey { + /// Create a new session host key from a borrowed server host key + pub fn from_server( + host_key: ServerHostKey<'_>, + provider: &dyn CryptoProvider, + ) -> Result<Self, ProtoError> { + Ok(Self(provider.signing_key_from_pkcs8(host_key.pkcs8)?)) + } + + /// Decode a host key from encoded PKCS#8 bytes + pub fn decode<'a>( + buf: &'a [u8], + provider: &dyn CryptoProvider, + ) -> Result<Decoded<'a, Self>, ProtoError> { + let Decoded { value: pkcs8, next } = <&[u8]>::decode(buf)?; + Ok(Decoded { + value: Self(provider.signing_key_from_pkcs8(pkcs8)?), + next, + }) + } + + /// The public key algorithm of this host key + pub fn algorithm(&self) -> PublicKeyAlgorithm<'static> { + self.0.algorithm() + } +} diff --git a/oxish-proto/src/key_exchange.rs b/oxish-proto/src/key_exchange.rs index eaa75e4..17b056e 100644 --- a/oxish-proto/src/key_exchange.rs +++ b/oxish-proto/src/key_exchange.rs @@ -2,7 +2,6 @@ use core::fmt; use std::borrow::Cow; use tracing::debug; -use zeroize::Zeroizing; use crate::{ Decode, Decoded, Encode, IncomingPacket, MessageType, Pretty, ProtoError, PublicKeyAlgorithm, @@ -10,6 +9,7 @@ use crate::{ CryptoError, CryptoProvider, Digest, HandshakeBuffer, HandshakeHash, KeyDerivation, KeySourceSide, SharedSecret, SigningKey, }, + host_keys::{HostKeys, ServerHostKey, SessionHostKey}, named::{ CompressionAlgorithm, EncryptionAlgorithm, ExtensionId, ExtensionName, IncomingNameList, KeyExchangeAlgorithm, KeyExchangeAlgorithmOrExtensionId, Language, MacAlgorithm, @@ -485,105 +485,6 @@ impl Encode for EcdhKeyExchangeReply { } } -/// The server's host keys, used to authenticate the key exchange -#[expect(clippy::type_complexity)] -pub struct HostKeys(Vec<(Zeroizing<Vec<u8>>, Box<dyn SigningKey>)>); - -impl HostKeys { - /// Create a new set of host keys from the given PKCS#8 private keys - /// - /// `pkcs8` must have more than 0 and less than 16 elements. - pub fn new( - pkcs8: impl Iterator<Item = Zeroizing<Vec<u8>>>, - provider: &dyn CryptoProvider, - ) -> Result<Self, ProtoError> { - let mut keys = Vec::new(); - for pkcs8 in pkcs8 { - if keys.len() >= Self::MAX_KEYS { - return Err(ProtoError::TooManyHostKeys); - } - - let signing_key = provider.signing_key_from_pkcs8(&pkcs8)?; - keys.push((pkcs8, signing_key)); - } - - if keys.is_empty() { - return Err(ProtoError::NoHostKeys); - } - - Ok(Self(keys)) - } - - /// Select the host key matching the negotiated algorithm - pub fn key<'a>(&'a self, negotiated: &Negotiated) -> Result<ServerHostKey<'a>, CryptoError> { - let mut iter = self.0.iter(); - match iter.find(|(_, key)| key.algorithm() == negotiated.server_host_key) { - Some((pkcs8, key)) => Ok(ServerHostKey { - pkcs8, - key: key.as_ref(), - }), - None => Err(CryptoError::UnknownAlgorithm), - } - } - - /// The public key algorithms of the held host keys - pub fn algorithms(&self) -> impl Iterator<Item = PublicKeyAlgorithm<'static>> + '_ { - self.0.iter().map(|(_, key)| key.algorithm()) - } - - const MAX_KEYS: usize = 16; -} - -/// A borrowed single host key, used to sign the key exchange output -pub struct ServerHostKey<'a> { - pkcs8: &'a Zeroizing<Vec<u8>>, - key: &'a dyn SigningKey, -} - -impl Encode for ServerHostKey<'_> { - fn encode(&self, buf: &mut Vec<u8>) { - let Self { pkcs8, key: _ } = self; - pkcs8.encode(buf); - } -} - -#[doc(hidden)] // for testing -impl<'a> From<(&'a Zeroizing<Vec<u8>>, &'a dyn SigningKey)> for ServerHostKey<'a> { - fn from((pkcs8, key): (&'a Zeroizing<Vec<u8>>, &'a dyn SigningKey)) -> Self { - Self { pkcs8, key } - } -} - -/// A single host key, used to sign rekeying exchanges -pub struct SessionHostKey(Box<dyn SigningKey>); - -impl SessionHostKey { - /// Create a new session host key from a borrowed server host key - pub fn from_server( - host_key: ServerHostKey<'_>, - provider: &dyn CryptoProvider, - ) -> Result<Self, ProtoError> { - Ok(Self(provider.signing_key_from_pkcs8(host_key.pkcs8)?)) - } - - /// Decode a host key from encoded PKCS#8 bytes - pub fn decode<'a>( - buf: &'a [u8], - provider: &dyn CryptoProvider, - ) -> Result<Decoded<'a, Self>, ProtoError> { - let Decoded { value: pkcs8, next } = <&[u8]>::decode(buf)?; - Ok(Decoded { - value: Self(provider.signing_key_from_pkcs8(pkcs8)?), - next, - }) - } - - /// The public key algorithm of this host key - pub fn algorithm(&self) -> PublicKeyAlgorithm<'static> { - self.0.algorithm() - } -} - struct KeyExchangeStarted { shared_secret: SharedSecret, exchange_hash: Digest, @@ -753,7 +654,7 @@ impl fmt::Debug for TaggedSignature<'_> { pub struct Negotiated { /// Negotiated key exchange algorithm pub key_exchange: KeyExchangeAlgorithm<'static>, - server_host_key: PublicKeyAlgorithm<'static>, + pub(crate) server_host_key: PublicKeyAlgorithm<'static>, encryption_client_to_server: EncryptionAlgorithm<'static>, encryption_server_to_client: EncryptionAlgorithm<'static>, /// Whether the client requested `SSH_MSG_EXT_INFO` via `ext-info-c` (RFC 8308) diff --git a/oxish-proto/src/lib.rs b/oxish-proto/src/lib.rs index 272a1d4..f2b3b5b 100644 --- a/oxish-proto/src/lib.rs +++ b/oxish-proto/src/lib.rs @@ -16,6 +16,8 @@ pub mod channels; /// Traits abstracting over cryptographic primitives and key derivation pub mod crypto; use crypto::CryptoError; +mod host_keys; +pub use host_keys::{HostKeys, ServerHostKey, SessionHostKey}; mod io; pub use io::{Encoder, ReadState, WriteState}; /// Key exchange messages and negotiation (RFC 4253 section 7, RFC 5656) diff --git a/oxish/src/bin/oxish-server.rs b/oxish/src/bin/oxish-server.rs index 321d064..a88995e 100644 --- a/oxish/src/bin/oxish-server.rs +++ b/oxish/src/bin/oxish-server.rs @@ -13,7 +13,7 @@ use clap::Parser; use listenfd::ListenFd; use oxish::{Config, DEFAULT_PROVIDER, DefaultStore, Server}; use proto::{ - key_exchange::HostKeys, + HostKeys, named::{Named, PublicKeyAlgorithm}, }; use tokio::net::TcpListener; diff --git a/oxish/src/lib.rs b/oxish/src/lib.rs index 8e389a6..cf453fe 100644 --- a/oxish/src/lib.rs +++ b/oxish/src/lib.rs @@ -13,15 +13,15 @@ use std::{io, str, task::ready}; use anyhow::Context as _; use proto::{ - Completion, Decode, Decoded, Encode, Identification, IdentificationError, Ignore, - IncomingPacket, PROTOCOL, ProtoError, ReadState, WriteState, + Completion, Decode, Decoded, Encode, HostKeys, Identification, IdentificationError, Ignore, + IncomingPacket, PROTOCOL, ProtoError, ReadState, ServerHostKey, SessionHostKey, WriteState, crypto::{ CryptoError, CryptoProvider, Digest, HandshakeBuffer, HandshakeHash, KeyLengths, KeySourceSide, }, key_exchange::{ - EcdhKeyExchangeInit, HostKeys, Identities, KeyExchange, KeyExchangeOutput, KeySourceSet, - NewKeys, Rekey, ServerHostKey, SessionHostKey, StrictKeyExchange, + EcdhKeyExchangeInit, Identities, KeyExchange, KeyExchangeOutput, KeySourceSet, NewKeys, + Rekey, StrictKeyExchange, }, named::{EncryptionAlgorithm, ExtensionId}, }; diff --git a/oxish/src/server.rs b/oxish/src/server.rs index 30f62f2..7a08e04 100644 --- a/oxish/src/server.rs +++ b/oxish/src/server.rs @@ -18,11 +18,7 @@ use std::{ }; use anyhow::Context as _; -use proto::{ - Encode, ReadState, WriteState, - crypto::CryptoProvider, - key_exchange::{HostKeys, ServerHostKey}, -}; +use proto::{Encode, HostKeys, ReadState, ServerHostKey, WriteState, crypto::CryptoProvider}; use rustix::net::{ RecvAncillaryBuffer, RecvFlags, SendAncillaryBuffer, SendAncillaryMessage, SendFlags, }; diff --git a/oxish/src/session/mod.rs b/oxish/src/session/mod.rs index 26a1899..1fdd95c 100644 --- a/oxish/src/session/mod.rs +++ b/oxish/src/session/mod.rs @@ -10,10 +10,10 @@ use std::{ }; use proto::{ - Decoded, Disconnect, Encoder, MessageType, Pretty, ReadState, WriteState, + Decoded, Disconnect, Encoder, MessageType, Pretty, ReadState, SessionHostKey, WriteState, channels::{ChannelRequest, ChannelRequestType}, crypto::CryptoProvider, - key_exchange::{Rekey, SessionHostKey}, + key_exchange::Rekey, }; use rustix::net::{RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, SendFlags}; use tokio::{ diff --git a/oxish/src/tests.rs b/oxish/src/tests.rs index 4926b92..3470b97 100644 --- a/oxish/src/tests.rs +++ b/oxish/src/tests.rs @@ -3,10 +3,10 @@ use std::{env, fs, panic::resume_unwind, path::Path, path::PathBuf, process::Std use anyhow::Context; use proto::{ - Decoded, Encode, + Decoded, Encode, HostKeys, ServerHostKey, auth::AuthorizedKey, crypto::{CryptoProvider, Digest, KeySourceSide}, - key_exchange::{HostKeys, Identities, ServerHostKey}, + key_exchange::Identities, named::{EncryptionAlgorithm, PublicKeyAlgorithm}, }; use tempfile::TempDir; |
