mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
small fixes
This commit is contained in:
parent
185d77e94f
commit
026965bda5
19 changed files with 124 additions and 106 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -449,11 +449,13 @@ dependencies = [
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
"ctr",
|
"ctr",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"hex",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"p256",
|
"p256",
|
||||||
"poly1305",
|
"poly1305",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"subtle",
|
"subtle",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
let transport_config = cluelessh_protocol::transport::server::ServerConfig {
|
let transport_config = cluelessh_protocol::transport::server::ServerConfig {
|
||||||
host_keys: pub_host_keys,
|
host_keys: pub_host_keys,
|
||||||
|
// This is definitely who we are.
|
||||||
|
server_identification: b"SSH-2.0-OpenSSH_9.7\r\n".to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut listener =
|
let mut listener =
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
result.wrap_err("failed to prompt password")
|
result.wrap_err("failed to prompt password")
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
sign_pubkey: Arc::new(move |session_identifier| {
|
sign_pubkey: Arc::new(move |session_id| {
|
||||||
let mut attempted_public_keys = HashSet::new();
|
let mut attempted_public_keys = HashSet::new();
|
||||||
let username = username.clone();
|
let username = username.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
|
@ -93,11 +93,8 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?;
|
let pubkey = PublicKey::from_wire_encoding(&identity.key_blob)?;
|
||||||
|
|
||||||
let sign_data = cluelessh_keys::signature::signature_data(
|
let sign_data =
|
||||||
session_identifier,
|
cluelessh_keys::signature::signature_data(session_id.0, &username, &pubkey);
|
||||||
&username,
|
|
||||||
&pubkey,
|
|
||||||
);
|
|
||||||
let signature = agent
|
let signature = agent
|
||||||
.sign(&identity.key_blob, &sign_data, 0)
|
.sign(&identity.key_blob, &sign_data, 0)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ pub async fn verify_signature(auth: VerifySignature) -> eyre::Result<Option<User
|
||||||
// Verify signature...
|
// Verify signature...
|
||||||
|
|
||||||
let sign_data = cluelessh_keys::signature::signature_data(
|
let sign_data = cluelessh_keys::signature::signature_data(
|
||||||
auth.session_identifier,
|
auth.session_id.0,
|
||||||
&auth.user,
|
&auth.user,
|
||||||
&auth.public_key,
|
&auth.public_key,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,6 @@ fn default_false() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn addr_default() -> IpAddr {
|
fn addr_default() -> IpAddr {
|
||||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,10 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
|
||||||
let stream = TcpStream::from_std(stream)?;
|
let stream = TcpStream::from_std(stream)?;
|
||||||
|
|
||||||
let host_keys = state.pub_host_keys;
|
let host_keys = state.pub_host_keys;
|
||||||
let transport_config = cluelessh_transport::server::ServerConfig { host_keys };
|
let transport_config = cluelessh_transport::server::ServerConfig {
|
||||||
|
host_keys,
|
||||||
|
server_identification: b"SSH-2.0-ClueleSSH_0.1\r\n".to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
let rpc_client = unsafe { OwnedFd::from_raw_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD) };
|
let rpc_client = unsafe { OwnedFd::from_raw_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD) };
|
||||||
let rpc_client1 = Arc::new(rpc::Client::from_fd(rpc_client)?);
|
let rpc_client1 = Arc::new(rpc::Client::from_fd(rpc_client)?);
|
||||||
|
|
@ -66,25 +69,13 @@ async fn connection_inner(state: SerializedConnectionState) -> Result<()> {
|
||||||
let rpc_client = rpc_client1.clone();
|
let rpc_client = rpc_client1.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
rpc_client
|
rpc_client
|
||||||
.verify_signature(
|
.verify_signature(msg.user, msg.session_id, msg.public_key, msg.signature)
|
||||||
msg.user,
|
|
||||||
msg.session_identifier,
|
|
||||||
msg.public_key,
|
|
||||||
msg.signature,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
check_pubkey: Some(Arc::new(move |msg| {
|
check_pubkey: Some(Arc::new(move |msg| {
|
||||||
let rpc_client = rpc_client2.clone();
|
let rpc_client = rpc_client2.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move { rpc_client.check_public_key(msg.user, msg.public_key).await })
|
||||||
rpc_client
|
|
||||||
.check_public_key(
|
|
||||||
msg.user,
|
|
||||||
msg.public_key,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
})),
|
})),
|
||||||
auth_banner: config.auth.banner,
|
auth_banner: config.auth.banner,
|
||||||
do_key_exchange: Arc::new(move |msg| {
|
do_key_exchange: Arc::new(move |msg| {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use cluelessh_keys::public::PublicKey;
|
||||||
use cluelessh_keys::signature::Signature;
|
use cluelessh_keys::signature::Signature;
|
||||||
use cluelessh_protocol::auth::VerifySignature;
|
use cluelessh_protocol::auth::VerifySignature;
|
||||||
use cluelessh_transport::crypto::AlgorithmName;
|
use cluelessh_transport::crypto::AlgorithmName;
|
||||||
|
use cluelessh_transport::SessionId;
|
||||||
use eyre::bail;
|
use eyre::bail;
|
||||||
use eyre::ensure;
|
use eyre::ensure;
|
||||||
use eyre::eyre;
|
use eyre::eyre;
|
||||||
|
|
@ -56,7 +57,7 @@ enum Request {
|
||||||
/// If it is okay, store the user so we can later spawn a process as them.
|
/// If it is okay, store the user so we can later spawn a process as them.
|
||||||
VerifySignature {
|
VerifySignature {
|
||||||
user: String,
|
user: String,
|
||||||
session_identifier: [u8; 32],
|
session_id: SessionId,
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
},
|
},
|
||||||
|
|
@ -115,7 +116,7 @@ impl secrecy::DebugSecret for SerializableSharedSecret {}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct KeyExchangeResponse {
|
pub struct KeyExchangeResponse {
|
||||||
pub hash: [u8; 32],
|
pub hash: SessionId,
|
||||||
pub server_ephemeral_public_key: Vec<u8>,
|
pub server_ephemeral_public_key: Vec<u8>,
|
||||||
pub shared_secret: secrecy::Secret<SerializableSharedSecret>,
|
pub shared_secret: secrecy::Secret<SerializableSharedSecret>,
|
||||||
pub signature: Signature,
|
pub signature: Signature,
|
||||||
|
|
@ -259,7 +260,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
Request::VerifySignature {
|
Request::VerifySignature {
|
||||||
user,
|
user,
|
||||||
session_identifier,
|
session_id,
|
||||||
public_key,
|
public_key,
|
||||||
signature,
|
signature,
|
||||||
} => {
|
} => {
|
||||||
|
|
@ -269,7 +270,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
let is_ok = crate::auth::verify_signature(VerifySignature {
|
let is_ok = crate::auth::verify_signature(VerifySignature {
|
||||||
user,
|
user,
|
||||||
session_identifier,
|
session_id,
|
||||||
public_key,
|
public_key,
|
||||||
signature,
|
signature,
|
||||||
})
|
})
|
||||||
|
|
@ -487,13 +488,13 @@ impl Client {
|
||||||
pub async fn verify_signature(
|
pub async fn verify_signature(
|
||||||
&self,
|
&self,
|
||||||
user: String,
|
user: String,
|
||||||
session_identifier: [u8; 32],
|
session_id: SessionId,
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
self.request_response::<VerifySignatureResponse>(&Request::VerifySignature {
|
self.request_response::<VerifySignatureResponse>(&Request::VerifySignature {
|
||||||
user,
|
user,
|
||||||
session_identifier,
|
session_id,
|
||||||
public_key,
|
public_key,
|
||||||
signature,
|
signature,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ use rustix::{
|
||||||
use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule, TargetArch};
|
use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule, TargetArch};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use crate::{SerializedConnectionState, PRIVSEP_CONNECTION_RPC_CLIENT_FD, PRIVSEP_CONNECTION_STREAM_FD};
|
use crate::{
|
||||||
|
SerializedConnectionState, PRIVSEP_CONNECTION_RPC_CLIENT_FD, PRIVSEP_CONNECTION_STREAM_FD,
|
||||||
|
};
|
||||||
|
|
||||||
#[tracing::instrument(skip(state), ret)]
|
#[tracing::instrument(skip(state), ret)]
|
||||||
pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> {
|
pub fn drop_privileges(state: &SerializedConnectionState) -> Result<()> {
|
||||||
|
|
@ -228,12 +230,24 @@ fn seccomp() -> Result<()> {
|
||||||
(libc::SYS_eventfd2, vec![]),
|
(libc::SYS_eventfd2, vec![]),
|
||||||
(libc::SYS_epoll_wait, vec![]),
|
(libc::SYS_epoll_wait, vec![]),
|
||||||
(libc::SYS_epoll_ctl, vec![]),
|
(libc::SYS_epoll_ctl, vec![]),
|
||||||
(libc::SYS_fcntl, vec![]), // todo: restrict (72)
|
(libc::SYS_fcntl, vec![]), // todo: restrict this
|
||||||
(libc::SYS_socketpair, vec![]),
|
(libc::SYS_socketpair, vec![]),
|
||||||
(libc::SYS_sendmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],),
|
(
|
||||||
(libc::SYS_recvmsg, vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)]),
|
libc::SYS_sendmsg,
|
||||||
(libc::SYS_sendto, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]),
|
vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],
|
||||||
(libc::SYS_recvfrom, vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)]),
|
),
|
||||||
|
(
|
||||||
|
libc::SYS_recvmsg,
|
||||||
|
vec![limit_fd(PRIVSEP_CONNECTION_RPC_CLIENT_FD)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
libc::SYS_sendto,
|
||||||
|
vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
libc::SYS_recvfrom,
|
||||||
|
vec![limit_fd(PRIVSEP_CONNECTION_STREAM_FD)],
|
||||||
|
),
|
||||||
(libc::SYS_getrandom, vec![]),
|
(libc::SYS_getrandom, vec![]),
|
||||||
(libc::SYS_rt_sigaction, vec![]),
|
(libc::SYS_rt_sigaction, vec![]),
|
||||||
(libc::SYS_rt_sigprocmask, vec![]),
|
(libc::SYS_rt_sigprocmask, vec![]),
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,8 @@ impl ChannelsState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
|
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
|
||||||
// TODO: window
|
// TODO: what if we mostly ignored window and just always increased it again?
|
||||||
|
// there's an excention to ignore it entirely that we could also support...
|
||||||
let mut p = packet.payload_parser();
|
let mut p = packet.payload_parser();
|
||||||
let packet_type = p.u8()?;
|
let packet_type = p.u8()?;
|
||||||
match packet_type {
|
match packet_type {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use cluelessh_format::{ParseError, Reader, Writer};
|
||||||
|
|
||||||
use crate::{private::PrivateKey, 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> {
|
pub fn signature_data(session_id: [u8; 32], username: &str, pubkey: &PublicKey) -> Vec<u8> {
|
||||||
let mut s = Writer::new();
|
let mut s = Writer::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,11 @@ impl ServerConnection {
|
||||||
self.transport.recv_bytes(bytes)?;
|
self.transport.recv_bytes(bytes)?;
|
||||||
|
|
||||||
if let ServerConnectionState::Setup(options, auth_banner) = &mut self.state {
|
if let ServerConnectionState::Setup(options, auth_banner) = &mut self.state {
|
||||||
if let Some(session_ident) = self.transport.is_open() {
|
if let Some(session_id) = self.transport.is_open() {
|
||||||
self.state = ServerConnectionState::Auth(auth::ServerAuth::new(
|
self.state = ServerConnectionState::Auth(auth::ServerAuth::new(
|
||||||
mem::take(options),
|
mem::take(options),
|
||||||
auth_banner.take(),
|
auth_banner.take(),
|
||||||
session_ident,
|
session_id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,9 +177,9 @@ impl ClientConnection {
|
||||||
self.transport.recv_bytes(bytes)?;
|
self.transport.recv_bytes(bytes)?;
|
||||||
|
|
||||||
if let ClientConnectionState::Setup(auth) = &mut self.state {
|
if let ClientConnectionState::Setup(auth) = &mut self.state {
|
||||||
if let Some(session_ident) = self.transport.is_open() {
|
if let Some(session_id) = self.transport.is_open() {
|
||||||
let mut auth = mem::take(auth).unwrap();
|
let mut auth = mem::take(auth).unwrap();
|
||||||
auth.set_session_identifier(session_ident);
|
auth.set_session_id(session_id);
|
||||||
|
|
||||||
debug!("Connection has been opened");
|
debug!("Connection has been opened");
|
||||||
self.state = ClientConnectionState::Auth(auth);
|
self.state = ClientConnectionState::Auth(auth);
|
||||||
|
|
@ -278,7 +278,7 @@ pub mod auth {
|
||||||
|
|
||||||
use cluelessh_format::{numbers, NameList};
|
use cluelessh_format::{numbers, NameList};
|
||||||
use cluelessh_keys::{public::PublicKey, signature::Signature};
|
use cluelessh_keys::{public::PublicKey, signature::Signature};
|
||||||
use cluelessh_transport::{packet::Packet, peer_error, Result};
|
use cluelessh_transport::{packet::Packet, peer_error, Result, SessionId};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
pub struct ServerAuth {
|
pub struct ServerAuth {
|
||||||
|
|
@ -288,7 +288,7 @@ pub mod auth {
|
||||||
options: HashSet<AuthOption>,
|
options: HashSet<AuthOption>,
|
||||||
banner: Option<String>,
|
banner: Option<String>,
|
||||||
server_requests: VecDeque<ServerRequest>,
|
server_requests: VecDeque<ServerRequest>,
|
||||||
session_ident: [u8; 32],
|
session_id: SessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ServerRequest {
|
pub enum ServerRequest {
|
||||||
|
|
@ -314,7 +314,7 @@ pub mod auth {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct VerifySignature {
|
pub struct VerifySignature {
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub session_identifier: [u8; 32],
|
pub session_id: SessionId,
|
||||||
pub public_key: PublicKey,
|
pub public_key: PublicKey,
|
||||||
/// The signature. Guaranteed to match the algorithm of `public_key`.
|
/// The signature. Guaranteed to match the algorithm of `public_key`.
|
||||||
pub signature: Signature,
|
pub signature: Signature,
|
||||||
|
|
@ -330,14 +330,14 @@ pub mod auth {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: HashSet<AuthOption>,
|
options: HashSet<AuthOption>,
|
||||||
banner: Option<String>,
|
banner: Option<String>,
|
||||||
session_ident: [u8; 32],
|
session_id: SessionId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
has_failed: false,
|
has_failed: false,
|
||||||
packets_to_send: VecDeque::new(),
|
packets_to_send: VecDeque::new(),
|
||||||
options,
|
options,
|
||||||
is_authenticated: None,
|
is_authenticated: None,
|
||||||
session_ident,
|
session_id,
|
||||||
banner,
|
banner,
|
||||||
server_requests: VecDeque::new(),
|
server_requests: VecDeque::new(),
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +426,7 @@ pub mod auth {
|
||||||
self.server_requests
|
self.server_requests
|
||||||
.push_back(ServerRequest::VerifySignature(VerifySignature {
|
.push_back(ServerRequest::VerifySignature(VerifySignature {
|
||||||
user: username.to_owned(),
|
user: username.to_owned(),
|
||||||
session_identifier: self.session_ident,
|
session_id: self.session_id,
|
||||||
public_key,
|
public_key,
|
||||||
signature,
|
signature,
|
||||||
}));
|
}));
|
||||||
|
|
@ -512,12 +512,12 @@ pub mod auth {
|
||||||
packets_to_send: VecDeque<Packet>,
|
packets_to_send: VecDeque<Packet>,
|
||||||
user_requests: VecDeque<ClientUserRequest>,
|
user_requests: VecDeque<ClientUserRequest>,
|
||||||
is_authenticated: bool,
|
is_authenticated: bool,
|
||||||
session_identifier: Option<[u8; 32]>,
|
session_id: Option<SessionId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ClientUserRequest {
|
pub enum ClientUserRequest {
|
||||||
Password,
|
Password,
|
||||||
PrivateKeySign { session_identifier: [u8; 32] },
|
PrivateKeySign { session_id: SessionId },
|
||||||
Banner(Vec<u8>),
|
Banner(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,13 +533,13 @@ pub mod auth {
|
||||||
username,
|
username,
|
||||||
user_requests: VecDeque::new(),
|
user_requests: VecDeque::new(),
|
||||||
is_authenticated: false,
|
is_authenticated: false,
|
||||||
session_identifier: None,
|
session_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_session_identifier(&mut self, ident: [u8; 32]) {
|
pub fn set_session_id(&mut self, session_id: SessionId) {
|
||||||
assert!(self.session_identifier.is_none());
|
assert!(self.session_id.is_none());
|
||||||
self.session_identifier = Some(ident);
|
self.session_id = Some(session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_authenticated(&self) -> bool {
|
pub fn is_authenticated(&self) -> bool {
|
||||||
|
|
@ -605,9 +605,9 @@ pub mod auth {
|
||||||
// TODO: Ask the server whether there are any keys we can use instead of just yoloing the signature.
|
// TODO: Ask the server whether there are any keys we can use instead of just yoloing the signature.
|
||||||
self.user_requests
|
self.user_requests
|
||||||
.push_back(ClientUserRequest::PrivateKeySign {
|
.push_back(ClientUserRequest::PrivateKeySign {
|
||||||
session_identifier: self
|
session_id: self
|
||||||
.session_identifier
|
.session_id
|
||||||
.expect("set_session_identifier has not been called"),
|
.expect("set_session_id has not been called"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Err(peer_error!(
|
return Err(peer_error!(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
||||||
|
use cluelessh_transport::SessionId;
|
||||||
use std::{collections::HashMap, pin::Pin, sync::Arc};
|
use std::{collections::HashMap, pin::Pin, sync::Arc};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ pub struct ClientAuth {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>,
|
pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>,
|
||||||
pub sign_pubkey:
|
pub sign_pubkey:
|
||||||
Arc<dyn Fn([u8; 32]) -> BoxFuture<'static, Result<SignatureResult>> + Send + Sync>,
|
Arc<dyn Fn(SessionId) -> BoxFuture<'static, Result<SignatureResult>> + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operation {
|
enum Operation {
|
||||||
|
|
@ -59,9 +60,7 @@ impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
|
||||||
channel_ops_recv,
|
channel_ops_recv,
|
||||||
channels: HashMap::new(),
|
channels: HashMap::new(),
|
||||||
proto: cluelessh_protocol::ClientConnection::new(
|
proto: cluelessh_protocol::ClientConnection::new(
|
||||||
cluelessh_transport::client::ClientConnection::new(
|
cluelessh_transport::client::ClientConnection::new(cluelessh_protocol::OsRng),
|
||||||
cluelessh_protocol::OsRng,
|
|
||||||
),
|
|
||||||
cluelessh_protocol::auth::ClientAuth::new(auth.username.as_bytes().to_vec()),
|
cluelessh_protocol::auth::ClientAuth::new(auth.username.as_bytes().to_vec()),
|
||||||
),
|
),
|
||||||
auth,
|
auth,
|
||||||
|
|
@ -88,13 +87,11 @@ impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
|
||||||
let _ = send.send(Operation::PasswordEntered(password)).await;
|
let _ = send.send(Operation::PasswordEntered(password)).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cluelessh_protocol::auth::ClientUserRequest::PrivateKeySign {
|
cluelessh_protocol::auth::ClientUserRequest::PrivateKeySign { session_id } => {
|
||||||
session_identifier,
|
|
||||||
} => {
|
|
||||||
let send = self.operations_send.clone();
|
let send = self.operations_send.clone();
|
||||||
let sign_pubkey = self.auth.sign_pubkey.clone();
|
let sign_pubkey = self.auth.sign_pubkey.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let signature_result = sign_pubkey(session_identifier).await;
|
let signature_result = sign_pubkey(session_id).await;
|
||||||
let _ = send.send(Operation::Signature(signature_result)).await;
|
let _ = send.send(Operation::Signature(signature_result)).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,10 +213,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = check(check_pubkey.clone()).await;
|
let result = check(check_pubkey.clone()).await;
|
||||||
let _ = send
|
let _ = send
|
||||||
.send(Operation::CheckPubkey(
|
.send(Operation::CheckPubkey(result, check_pubkey.public_key))
|
||||||
result,
|
|
||||||
check_pubkey.public_key,
|
|
||||||
))
|
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ x25519-dalek = "2.0.1"
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
secrecy = "0.8.0"
|
secrecy = "0.8.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::{
|
crypto::{
|
||||||
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm, KeyExchangeSecret, SharedSecret, SupportedAlgorithms
|
self, AlgorithmName, EncodedSshSignature, EncryptionAlgorithm, HostKeyVerifyAlgorithm,
|
||||||
|
KeyExchangeSecret, SharedSecret, SupportedAlgorithms,
|
||||||
},
|
},
|
||||||
packet::{Packet, PacketTransport, ProtocolIdentParser},
|
packet::{Packet, PacketTransport, ProtocolIdentParser},
|
||||||
peer_error, Msg, Result, SshRng, SshStatus,
|
peer_error, Msg, Result, SessionId, SshRng, SshStatus,
|
||||||
};
|
};
|
||||||
use cluelessh_format::{numbers, NameList, Reader, Writer};
|
use cluelessh_format::{numbers, NameList, Reader, Writer};
|
||||||
|
|
||||||
|
|
@ -50,10 +51,10 @@ enum ClientState {
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
},
|
},
|
||||||
ServiceRequest {
|
ServiceRequest {
|
||||||
session_identifier: [u8; 32],
|
session_id: SessionId,
|
||||||
},
|
},
|
||||||
Open {
|
Open {
|
||||||
session_identifier: [u8; 32],
|
session_id: SessionId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,10 +311,10 @@ impl ClientConnection {
|
||||||
.queue_packet(Packet::new_msg_service_request(b"ssh-userauth"));
|
.queue_packet(Packet::new_msg_service_request(b"ssh-userauth"));
|
||||||
|
|
||||||
self.state = ClientState::ServiceRequest {
|
self.state = ClientState::ServiceRequest {
|
||||||
session_identifier: *h,
|
session_id: SessionId(*h),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ClientState::ServiceRequest { session_identifier } => {
|
ClientState::ServiceRequest { session_id } => {
|
||||||
let mut accept = packet.payload_parser();
|
let mut accept = packet.payload_parser();
|
||||||
let packet_type = accept.u8()?;
|
let packet_type = accept.u8()?;
|
||||||
if packet_type != numbers::SSH_MSG_SERVICE_ACCEPT {
|
if packet_type != numbers::SSH_MSG_SERVICE_ACCEPT {
|
||||||
|
|
@ -326,7 +327,7 @@ impl ClientConnection {
|
||||||
|
|
||||||
debug!("Connection has been opened successfully");
|
debug!("Connection has been opened successfully");
|
||||||
self.state = ClientState::Open {
|
self.state = ClientState::Open {
|
||||||
session_identifier: *session_identifier,
|
session_id: *session_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ClientState::Open { .. } => {
|
ClientState::Open { .. } => {
|
||||||
|
|
@ -349,9 +350,9 @@ impl ClientConnection {
|
||||||
self.packet_transport.queue_packet(packet);
|
self.packet_transport.queue_packet(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_open(&self) -> Option<[u8; 32]> {
|
pub fn is_open(&self) -> Option<SessionId> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ClientState::Open { session_identifier } => Some(session_identifier),
|
ClientState::Open { session_id } => Some(session_id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use sha2::Digest;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
packet::{EncryptedPacket, MsgKind, Packet, RawPacket},
|
packet::{EncryptedPacket, MsgKind, Packet, RawPacket},
|
||||||
peer_error, Msg, Result, SshRng,
|
peer_error, Msg, Result, SessionId, SshRng,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type SharedSecret = secrecy::Secret<SharedSecretInner>;
|
pub type SharedSecret = secrecy::Secret<SharedSecretInner>;
|
||||||
|
|
@ -308,7 +308,7 @@ impl SupportedAlgorithms {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Session {
|
pub(crate) struct Session {
|
||||||
session_id: [u8; 32],
|
session_id: SessionId,
|
||||||
from_peer: Tunnel,
|
from_peer: Tunnel,
|
||||||
to_peer: Tunnel,
|
to_peer: Tunnel,
|
||||||
}
|
}
|
||||||
|
|
@ -326,6 +326,7 @@ pub(crate) trait Keys: Send + Sync + 'static {
|
||||||
fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg;
|
fn encrypt_packet_to_msg(&mut self, packet: Packet, packet_number: u64) -> Msg;
|
||||||
|
|
||||||
fn additional_mac_len(&self) -> usize;
|
fn additional_mac_len(&self) -> usize;
|
||||||
|
// TODO: actually rekey...
|
||||||
fn rekey(
|
fn rekey(
|
||||||
&mut self,
|
&mut self,
|
||||||
h: [u8; 32],
|
h: [u8; 32],
|
||||||
|
|
@ -362,7 +363,7 @@ impl Keys for Plaintext {
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
h: [u8; 32],
|
h: SessionId,
|
||||||
k: &SharedSecret,
|
k: &SharedSecret,
|
||||||
encryption_client_to_server: EncryptionAlgorithm,
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
|
|
@ -370,7 +371,7 @@ impl Session {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::from_keys(
|
Self::from_keys(
|
||||||
h,
|
h,
|
||||||
h,
|
h.0,
|
||||||
k,
|
k,
|
||||||
encryption_client_to_server,
|
encryption_client_to_server,
|
||||||
encryption_server_to_client,
|
encryption_server_to_client,
|
||||||
|
|
@ -380,7 +381,7 @@ impl Session {
|
||||||
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2>
|
/// <https://datatracker.ietf.org/doc/html/rfc4253#section-7.2>
|
||||||
fn from_keys(
|
fn from_keys(
|
||||||
session_id: [u8; 32],
|
session_id: SessionId,
|
||||||
h: [u8; 32],
|
h: [u8; 32],
|
||||||
k: &SharedSecret,
|
k: &SharedSecret,
|
||||||
alg_c2s: EncryptionAlgorithm,
|
alg_c2s: EncryptionAlgorithm,
|
||||||
|
|
@ -462,7 +463,7 @@ fn derive_key(
|
||||||
k: &SharedSecret,
|
k: &SharedSecret,
|
||||||
h: [u8; 32],
|
h: [u8; 32],
|
||||||
letter: &str,
|
letter: &str,
|
||||||
session_id: [u8; 32],
|
session_id: SessionId,
|
||||||
key_size: usize,
|
key_size: usize,
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
let sha2len = sha2::Sha256::output_size();
|
let sha2len = sha2::Sha256::output_size();
|
||||||
|
|
@ -476,7 +477,7 @@ fn derive_key(
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
hash.update(letter.as_bytes());
|
hash.update(letter.as_bytes());
|
||||||
hash.update(session_id);
|
hash.update(session_id.0);
|
||||||
} else {
|
} else {
|
||||||
hash.update(&output[..(i * sha2len)]);
|
hash.update(&output[..(i * sha2len)]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ pub mod crypto;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use cluelessh_format::ParseError;
|
use cluelessh_format::ParseError;
|
||||||
pub use packet::Msg;
|
pub use packet::Msg;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// TODO: extensions
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SshStatus {
|
pub enum SshStatus {
|
||||||
|
|
@ -17,6 +22,17 @@ pub enum SshStatus {
|
||||||
PeerError(String),
|
PeerError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct SessionId(pub [u8; 32]);
|
||||||
|
|
||||||
|
impl Debug for SessionId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("SessionId")
|
||||||
|
.field(&hex::encode(self.0))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T, E = SshStatus> = std::result::Result<T, E>;
|
pub type Result<T, E = SshStatus> = std::result::Result<T, E>;
|
||||||
|
|
||||||
impl From<ParseError> for SshStatus {
|
impl From<ParseError> for SshStatus {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ use std::mem;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::crypto::{self, EncryptionAlgorithm, Keys, Plaintext, Session, SharedSecret};
|
use crate::crypto::{self, EncryptionAlgorithm, Keys, Plaintext, Session, SharedSecret};
|
||||||
use crate::peer_error;
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use crate::{peer_error, SessionId};
|
||||||
use cluelessh_format::numbers;
|
use cluelessh_format::numbers;
|
||||||
use cluelessh_format::{NameList, Reader, Writer};
|
use cluelessh_format::{NameList, Reader, Writer};
|
||||||
|
|
||||||
|
|
@ -67,7 +67,8 @@ impl PacketTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
|
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
|
||||||
// TODO: This might not work if we buffer two packets where one changes keys in between?
|
// This would not work if we buffer two packets where one changes keys in between,
|
||||||
|
// but SSH_MSG_NEWKEYS messages guarantee that this cannot happen.
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
self.recv_next_packet
|
self.recv_next_packet
|
||||||
|
|
@ -125,7 +126,7 @@ impl PacketTransport {
|
||||||
is_server,
|
is_server,
|
||||||
) {
|
) {
|
||||||
self.keys = Box::new(Session::new(
|
self.keys = Box::new(Session::new(
|
||||||
h,
|
SessionId(h),
|
||||||
k,
|
k,
|
||||||
encryption_client_to_server,
|
encryption_client_to_server,
|
||||||
encryption_server_to_client,
|
encryption_server_to_client,
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,14 @@ use crate::crypto::{
|
||||||
use crate::packet::{
|
use crate::packet::{
|
||||||
KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser,
|
KeyExchangeEcDhInitPacket, KeyExchangeInitPacket, Packet, PacketTransport, ProtocolIdentParser,
|
||||||
};
|
};
|
||||||
use crate::Result;
|
|
||||||
use crate::{peer_error, Msg, SshRng, SshStatus};
|
use crate::{peer_error, Msg, SshRng, SshStatus};
|
||||||
|
use crate::{Result, SessionId};
|
||||||
use cluelessh_format::numbers;
|
use cluelessh_format::numbers;
|
||||||
use cluelessh_format::{NameList, Reader, Writer};
|
use cluelessh_format::{NameList, Reader, Writer};
|
||||||
use cluelessh_keys::private::PlaintextPrivateKey;
|
use cluelessh_keys::private::PlaintextPrivateKey;
|
||||||
use cluelessh_keys::signature::Signature;
|
use cluelessh_keys::signature::Signature;
|
||||||
use tracing::{debug, info, trace};
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
// This is definitely who we are.
|
|
||||||
// TODO: dont make cluelesshd do this
|
|
||||||
pub const SERVER_IDENTIFICATION: &[u8] = b"SSH-2.0-OpenSSH_9.7\r\n";
|
|
||||||
|
|
||||||
pub struct ServerConnection {
|
pub struct ServerConnection {
|
||||||
state: ServerState,
|
state: ServerState,
|
||||||
packet_transport: PacketTransport,
|
packet_transport: PacketTransport,
|
||||||
|
|
@ -31,6 +27,7 @@ pub struct ServerConnection {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
|
pub server_identification: Vec<u8>,
|
||||||
pub host_keys: Vec<cluelessh_keys::public::PublicKey>,
|
pub host_keys: Vec<cluelessh_keys::public::PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,10 +66,10 @@ enum ServerState {
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
},
|
},
|
||||||
ServiceRequest {
|
ServiceRequest {
|
||||||
session_ident: [u8; 32],
|
session_id: SessionId,
|
||||||
},
|
},
|
||||||
Open {
|
Open {
|
||||||
session_ident: [u8; 32],
|
session_id: SessionId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +84,7 @@ pub struct KeyExchangeParameters {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyExchangeResponse {
|
pub struct KeyExchangeResponse {
|
||||||
pub hash: [u8; 32],
|
pub hash: SessionId,
|
||||||
pub server_ephemeral_public_key: Vec<u8>,
|
pub server_ephemeral_public_key: Vec<u8>,
|
||||||
pub shared_secret: SharedSecret,
|
pub shared_secret: SharedSecret,
|
||||||
pub signature: Signature,
|
pub signature: Signature,
|
||||||
|
|
@ -111,7 +108,7 @@ impl ServerConnection {
|
||||||
ident_parser.recv_bytes(bytes);
|
ident_parser.recv_bytes(bytes);
|
||||||
if let Some(client_identification) = ident_parser.get_peer_ident() {
|
if let Some(client_identification) = ident_parser.get_peer_ident() {
|
||||||
self.packet_transport
|
self.packet_transport
|
||||||
.queue_send_protocol_info(SERVER_IDENTIFICATION.to_vec());
|
.queue_send_protocol_info(self.config.server_identification.clone());
|
||||||
self.state = ServerState::KeyExchangeInit {
|
self.state = ServerState::KeyExchangeInit {
|
||||||
client_identification,
|
client_identification,
|
||||||
};
|
};
|
||||||
|
|
@ -311,10 +308,11 @@ impl ServerConnection {
|
||||||
*encryption_server_to_client,
|
*encryption_server_to_client,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
self.state = ServerState::ServiceRequest { session_ident: *h };
|
self.state = ServerState::ServiceRequest {
|
||||||
|
session_id: SessionId(*h),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ServerState::ServiceRequest { session_ident } => {
|
ServerState::ServiceRequest { session_id } => {
|
||||||
// TODO: this should probably move out of here? unsure.
|
|
||||||
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
|
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
|
||||||
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||||
}
|
}
|
||||||
|
|
@ -335,7 +333,7 @@ impl ServerConnection {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
self.state = ServerState::Open {
|
self.state = ServerState::Open {
|
||||||
session_ident: *session_ident,
|
session_id: *session_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ServerState::Open { .. } => {
|
ServerState::Open { .. } => {
|
||||||
|
|
@ -346,9 +344,9 @@ impl ServerConnection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_open(&self) -> Option<[u8; 32]> {
|
pub fn is_open(&self) -> Option<SessionId> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ServerState::Open { session_ident } => Some(session_ident),
|
ServerState::Open { session_id } => Some(session_id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +363,7 @@ impl ServerConnection {
|
||||||
..
|
..
|
||||||
} => Some(KeyExchangeParameters {
|
} => Some(KeyExchangeParameters {
|
||||||
client_ident: client_identification.clone(),
|
client_ident: client_identification.clone(),
|
||||||
server_ident: SERVER_IDENTIFICATION.to_vec(),
|
server_ident: self.config.server_identification.to_vec(),
|
||||||
client_kexinit: client_kexinit.clone(),
|
client_kexinit: client_kexinit.clone(),
|
||||||
server_kexinit: server_kexinit.clone(),
|
server_kexinit: server_kexinit.clone(),
|
||||||
eph_client_public_key: client_ephemeral_public_key.clone(),
|
eph_client_public_key: client_ephemeral_public_key.clone(),
|
||||||
|
|
@ -392,7 +390,7 @@ impl ServerConnection {
|
||||||
|
|
||||||
self.packet_transport.queue_packet(packet);
|
self.packet_transport.queue_packet(packet);
|
||||||
self.state = ServerState::NewKeys {
|
self.state = ServerState::NewKeys {
|
||||||
hash: response.hash,
|
hash: response.hash.0,
|
||||||
shared_secret: response.shared_secret.clone(),
|
shared_secret: response.shared_secret.clone(),
|
||||||
encryption_client_to_server: *encryption_client_to_server,
|
encryption_client_to_server: *encryption_client_to_server,
|
||||||
encryption_server_to_client: *encryption_server_to_client,
|
encryption_server_to_client: *encryption_server_to_client,
|
||||||
|
|
@ -437,7 +435,7 @@ pub fn do_key_exchange(
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(KeyExchangeResponse {
|
Ok(KeyExchangeResponse {
|
||||||
hash,
|
hash: SessionId(hash),
|
||||||
server_ephemeral_public_key,
|
server_ephemeral_public_key,
|
||||||
shared_secret,
|
shared_secret,
|
||||||
signature: private.private_key.sign(&hash),
|
signature: private.private_key.sign(&hash),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue