aboutsummaryrefslogtreecommitdiffhomepage
path: root/oxish-proto
diff options
context:
space:
mode:
Diffstat (limited to 'oxish-proto')
-rw-r--r--oxish-proto/src/host_keys.rs106
-rw-r--r--oxish-proto/src/key_exchange.rs103
-rw-r--r--oxish-proto/src/lib.rs2
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)