diff options
Diffstat (limited to 'oxish-proto/src')
| -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 |
3 files changed, 110 insertions, 101 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) |
