Verify native Dosh Ed25519 user auth
This commit is contained in:
+429
-10
@@ -3,7 +3,7 @@ use crate::config::{ServerConfig, expand_tilde};
|
|||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -95,6 +95,24 @@ pub struct NativeAuthOk {
|
|||||||
pub policy_flags: Vec<String>,
|
pub policy_flags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AuthorizedKey {
|
||||||
|
pub algorithm: String,
|
||||||
|
pub key: Vec<u8>,
|
||||||
|
pub options: AuthorizedKeyOptions,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct AuthorizedKeyOptions {
|
||||||
|
pub from: Option<String>,
|
||||||
|
pub force_command: Option<String>,
|
||||||
|
pub restricted: bool,
|
||||||
|
pub no_port_forwarding: bool,
|
||||||
|
pub permitopen: Vec<String>,
|
||||||
|
pub unsupported: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_or_create_host_key(config: &ServerConfig) -> Result<SigningKey> {
|
pub fn load_or_create_host_key(config: &ServerConfig) -> Result<SigningKey> {
|
||||||
let path = expand_tilde(&config.host_key);
|
let path = expand_tilde(&config.host_key);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
@@ -233,6 +251,296 @@ pub fn verify_server_hello(client: &NativeClientHello, server: &NativeServerHell
|
|||||||
verify_host_signature(&server.host_key, &transcript, &server.host_signature)
|
verify_host_signature(&server.host_key, &transcript, &server.host_signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn user_auth_transcript(
|
||||||
|
client: &NativeClientHello,
|
||||||
|
server: &NativeServerHello,
|
||||||
|
auth: &NativeUserAuth,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let mut unsigned = auth.clone();
|
||||||
|
unsigned.signature.clear();
|
||||||
|
let mut out = b"dosh/native/user-auth/v1".to_vec();
|
||||||
|
out.extend(bincode::serialize(client)?);
|
||||||
|
out.extend(bincode::serialize(server)?);
|
||||||
|
out.extend(bincode::serialize(&unsigned)?);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign_user_auth(
|
||||||
|
signing_key: &SigningKey,
|
||||||
|
client: &NativeClientHello,
|
||||||
|
server: &NativeServerHello,
|
||||||
|
requested_forwardings: Vec<ForwardingRequest>,
|
||||||
|
) -> Result<NativeUserAuth> {
|
||||||
|
let verifying = VerifyingKey::from(signing_key);
|
||||||
|
let mut auth = NativeUserAuth {
|
||||||
|
public_key_algorithm: "ssh-ed25519".to_string(),
|
||||||
|
public_key: verifying.to_bytes().to_vec(),
|
||||||
|
signature: Vec::new(),
|
||||||
|
requested_forwardings,
|
||||||
|
};
|
||||||
|
let transcript = user_auth_transcript(client, server, &auth)?;
|
||||||
|
auth.signature = signing_key.sign(&transcript).to_bytes().to_vec();
|
||||||
|
Ok(auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_native_user_auth(
|
||||||
|
client: &NativeClientHello,
|
||||||
|
server: &NativeServerHello,
|
||||||
|
auth: &NativeUserAuth,
|
||||||
|
authorized_keys: &[AuthorizedKey],
|
||||||
|
) -> Result<AuthorizedKey> {
|
||||||
|
anyhow::ensure!(
|
||||||
|
auth.public_key_algorithm == "ssh-ed25519",
|
||||||
|
"unsupported native user key algorithm {}",
|
||||||
|
auth.public_key_algorithm
|
||||||
|
);
|
||||||
|
anyhow::ensure!(
|
||||||
|
auth.public_key.len() == 32,
|
||||||
|
"ssh-ed25519 public key must be 32 bytes"
|
||||||
|
);
|
||||||
|
anyhow::ensure!(
|
||||||
|
auth.signature.len() == 64,
|
||||||
|
"ssh-ed25519 signature must be 64 bytes"
|
||||||
|
);
|
||||||
|
let authorized = authorized_keys
|
||||||
|
.iter()
|
||||||
|
.find(|key| key.algorithm == "ssh-ed25519" && key.key == auth.public_key)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("native user key is not authorized"))?;
|
||||||
|
authorized.ensure_native_allowed(auth)?;
|
||||||
|
|
||||||
|
let mut public_key = [0u8; 32];
|
||||||
|
public_key.copy_from_slice(&auth.public_key);
|
||||||
|
let verifying_key = VerifyingKey::from_bytes(&public_key).context("parse user public key")?;
|
||||||
|
let mut signature = [0u8; 64];
|
||||||
|
signature.copy_from_slice(&auth.signature);
|
||||||
|
let signature = Signature::from_bytes(&signature);
|
||||||
|
let transcript = user_auth_transcript(client, server, auth)?;
|
||||||
|
verifying_key
|
||||||
|
.verify(&transcript, &signature)
|
||||||
|
.context("verify native user signature")?;
|
||||||
|
Ok(authorized.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_native_user_auth_from_config(
|
||||||
|
config: &ServerConfig,
|
||||||
|
client: &NativeClientHello,
|
||||||
|
server: &NativeServerHello,
|
||||||
|
auth: &NativeUserAuth,
|
||||||
|
) -> Result<AuthorizedKey> {
|
||||||
|
let authorized_keys = load_authorized_keys(&config.authorized_keys)?;
|
||||||
|
verify_native_user_auth(client, server, auth, &authorized_keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthorizedKey {
|
||||||
|
fn ensure_native_allowed(&self, auth: &NativeUserAuth) -> Result<()> {
|
||||||
|
if !self.options.unsupported.is_empty() {
|
||||||
|
bail!(
|
||||||
|
"authorized key has unsupported options: {}",
|
||||||
|
self.options.unsupported.join(",")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self.options.force_command.is_some() {
|
||||||
|
bail!("authorized key command= is not supported for native Dosh terminal login");
|
||||||
|
}
|
||||||
|
if self.options.no_port_forwarding
|
||||||
|
&& auth
|
||||||
|
.requested_forwardings
|
||||||
|
.iter()
|
||||||
|
.any(|forwarding| !matches!(forwarding.kind, ForwardingKind::Dynamic))
|
||||||
|
{
|
||||||
|
bail!("authorized key forbids port forwarding");
|
||||||
|
}
|
||||||
|
if !self.options.permitopen.is_empty() {
|
||||||
|
for forwarding in &auth.requested_forwardings {
|
||||||
|
if matches!(forwarding.kind, ForwardingKind::Local)
|
||||||
|
&& let (Some(host), Some(port)) =
|
||||||
|
(forwarding.target_host.as_ref(), forwarding.target_port)
|
||||||
|
{
|
||||||
|
let target = format!("{host}:{port}");
|
||||||
|
if !self
|
||||||
|
.options
|
||||||
|
.permitopen
|
||||||
|
.iter()
|
||||||
|
.any(|permit| permit == &target)
|
||||||
|
{
|
||||||
|
bail!("authorized key does not permit opening {target}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_authorized_keys(paths: &[String]) -> Result<Vec<AuthorizedKey>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for path in paths {
|
||||||
|
let path = expand_tilde(path);
|
||||||
|
if !path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let raw = fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?;
|
||||||
|
out.extend(
|
||||||
|
parse_authorized_keys(&raw).with_context(|| format!("parse {}", path.display()))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_authorized_keys(raw: &str) -> Result<Vec<AuthorizedKey>> {
|
||||||
|
raw.lines()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, line)| {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(parse_authorized_key_line(index + 1, line))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_authorized_key_line(line_number: usize, line: &str) -> Result<AuthorizedKey> {
|
||||||
|
let fields = split_authorized_key_fields(line);
|
||||||
|
anyhow::ensure!(
|
||||||
|
fields.len() >= 2,
|
||||||
|
"authorized_keys:{line_number}: expected key fields"
|
||||||
|
);
|
||||||
|
let (options, key_type_index) = if fields[0].starts_with("ssh-") {
|
||||||
|
(AuthorizedKeyOptions::default(), 0)
|
||||||
|
} else {
|
||||||
|
(parse_authorized_key_options(&fields[0])?, 1)
|
||||||
|
};
|
||||||
|
let algorithm = fields
|
||||||
|
.get(key_type_index)
|
||||||
|
.with_context(|| format!("authorized_keys:{line_number}: missing key type"))?;
|
||||||
|
let key_blob = fields
|
||||||
|
.get(key_type_index + 1)
|
||||||
|
.with_context(|| format!("authorized_keys:{line_number}: missing key blob"))?;
|
||||||
|
anyhow::ensure!(
|
||||||
|
algorithm == "ssh-ed25519",
|
||||||
|
"authorized_keys:{line_number}: unsupported key type {algorithm}"
|
||||||
|
);
|
||||||
|
let decoded = STANDARD
|
||||||
|
.decode(key_blob)
|
||||||
|
.with_context(|| format!("authorized_keys:{line_number}: decode key blob"))?;
|
||||||
|
let key = parse_ssh_ed25519_public_blob(&decoded)
|
||||||
|
.with_context(|| format!("authorized_keys:{line_number}: parse ssh-ed25519 key"))?;
|
||||||
|
let comment = if fields.len() > key_type_index + 2 {
|
||||||
|
Some(fields[key_type_index + 2..].join(" "))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(AuthorizedKey {
|
||||||
|
algorithm: algorithm.to_string(),
|
||||||
|
key: key.to_vec(),
|
||||||
|
options,
|
||||||
|
comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_authorized_key_fields(line: &str) -> Vec<String> {
|
||||||
|
line.split_whitespace().map(ToString::to_string).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_authorized_key_options(raw: &str) -> Result<AuthorizedKeyOptions> {
|
||||||
|
let mut options = AuthorizedKeyOptions::default();
|
||||||
|
for option in split_options(raw)? {
|
||||||
|
if option == "restrict" {
|
||||||
|
options.restricted = true;
|
||||||
|
} else if option == "no-port-forwarding" {
|
||||||
|
options.no_port_forwarding = true;
|
||||||
|
} else if let Some(value) = option.strip_prefix("from=") {
|
||||||
|
options.from = Some(strip_quotes(value).to_string());
|
||||||
|
} else if let Some(value) = option.strip_prefix("command=") {
|
||||||
|
options.force_command = Some(strip_quotes(value).to_string());
|
||||||
|
} else if let Some(value) = option.strip_prefix("permitopen=") {
|
||||||
|
options.permitopen.push(strip_quotes(value).to_string());
|
||||||
|
} else {
|
||||||
|
options.unsupported.push(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_options(raw: &str) -> Result<Vec<String>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut quoted = false;
|
||||||
|
let mut escaped = false;
|
||||||
|
for ch in raw.chars() {
|
||||||
|
if escaped {
|
||||||
|
current.push(ch);
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == '\\' && quoted {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == '"' {
|
||||||
|
quoted = !quoted;
|
||||||
|
current.push(ch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ch == ',' && !quoted {
|
||||||
|
out.push(current);
|
||||||
|
current = String::new();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.push(ch);
|
||||||
|
}
|
||||||
|
anyhow::ensure!(!quoted, "unterminated authorized_keys option quote");
|
||||||
|
if !current.is_empty() {
|
||||||
|
out.push(current);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_quotes(raw: &str) -> &str {
|
||||||
|
raw.strip_prefix('"')
|
||||||
|
.and_then(|value| value.strip_suffix('"'))
|
||||||
|
.unwrap_or(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ssh_ed25519_public_blob(blob: &[u8]) -> Result<[u8; 32]> {
|
||||||
|
let mut cursor = blob;
|
||||||
|
let key_type = read_ssh_string(&mut cursor)?;
|
||||||
|
anyhow::ensure!(key_type == b"ssh-ed25519", "key blob type mismatch");
|
||||||
|
let key = read_ssh_string(&mut cursor)?;
|
||||||
|
anyhow::ensure!(key.len() == 32, "ssh-ed25519 key must be 32 bytes");
|
||||||
|
anyhow::ensure!(cursor.is_empty(), "trailing data in ssh-ed25519 key blob");
|
||||||
|
let mut out = [0u8; 32];
|
||||||
|
out.copy_from_slice(key);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ssh_string<'a>(cursor: &mut &'a [u8]) -> Result<&'a [u8]> {
|
||||||
|
anyhow::ensure!(cursor.len() >= 4, "truncated SSH string length");
|
||||||
|
let len = u32::from_be_bytes(cursor[..4].try_into().unwrap()) as usize;
|
||||||
|
*cursor = &cursor[4..];
|
||||||
|
anyhow::ensure!(cursor.len() >= len, "truncated SSH string body");
|
||||||
|
let (value, rest) = cursor.split_at(len);
|
||||||
|
*cursor = rest;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn ssh_ed25519_authorized_key_line(signing_key: &SigningKey) -> String {
|
||||||
|
let verifying = VerifyingKey::from(signing_key);
|
||||||
|
let mut blob = Vec::new();
|
||||||
|
write_ssh_string(&mut blob, b"ssh-ed25519");
|
||||||
|
write_ssh_string(&mut blob, &verifying.to_bytes());
|
||||||
|
format!("ssh-ed25519 {} test-key", STANDARD.encode(blob))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn write_ssh_string(out: &mut Vec<u8>, value: &[u8]) {
|
||||||
|
out.extend_from_slice(&(value.len() as u32).to_be_bytes());
|
||||||
|
out.extend_from_slice(value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn known_host_line(host: &str, key: &HostPublicKey, source: &str, first_seen: u64) -> String {
|
pub fn known_host_line(host: &str, key: &HostPublicKey, source: &str, first_seen: u64) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{} {} {} first-seen={} source={}",
|
"{} {} {} first-seen={} source={}",
|
||||||
@@ -482,7 +790,119 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn server_hello_signature_round_trips() {
|
fn server_hello_signature_round_trips() {
|
||||||
let signing = SigningKey::from_bytes(&[3u8; 32]);
|
let signing = SigningKey::from_bytes(&[3u8; 32]);
|
||||||
let client = NativeClientHello {
|
let client = test_client_hello();
|
||||||
|
let mut server = test_server_hello(&signing);
|
||||||
|
sign_server_hello(&signing, &client, &mut server).unwrap();
|
||||||
|
verify_server_hello(&client, &server).unwrap();
|
||||||
|
server.auth_challenge = [7u8; 32];
|
||||||
|
assert!(verify_server_hello(&client, &server).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authorized_keys_parses_ed25519() {
|
||||||
|
let signing = SigningKey::from_bytes(&[8u8; 32]);
|
||||||
|
let raw = ssh_ed25519_authorized_key_line(&signing);
|
||||||
|
let parsed = parse_authorized_keys(&raw).unwrap();
|
||||||
|
assert_eq!(parsed.len(), 1);
|
||||||
|
assert_eq!(parsed[0].algorithm, "ssh-ed25519");
|
||||||
|
assert_eq!(
|
||||||
|
parsed[0].key,
|
||||||
|
VerifyingKey::from(&signing).to_bytes().to_vec()
|
||||||
|
);
|
||||||
|
assert_eq!(parsed[0].comment.as_deref(), Some("test-key"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_user_auth_accepts_authorized_key_and_rejects_removed_key() {
|
||||||
|
let host_signing = SigningKey::from_bytes(&[3u8; 32]);
|
||||||
|
let user_signing = SigningKey::from_bytes(&[9u8; 32]);
|
||||||
|
let other_signing = SigningKey::from_bytes(&[10u8; 32]);
|
||||||
|
let client = test_client_hello();
|
||||||
|
let mut server = test_server_hello(&host_signing);
|
||||||
|
sign_server_hello(&host_signing, &client, &mut server).unwrap();
|
||||||
|
let auth = sign_user_auth(&user_signing, &client, &server, Vec::new()).unwrap();
|
||||||
|
let authorized =
|
||||||
|
parse_authorized_keys(&ssh_ed25519_authorized_key_line(&user_signing)).unwrap();
|
||||||
|
|
||||||
|
verify_native_user_auth(&client, &server, &auth, &authorized).unwrap();
|
||||||
|
|
||||||
|
let removed =
|
||||||
|
parse_authorized_keys(&ssh_ed25519_authorized_key_line(&other_signing)).unwrap();
|
||||||
|
assert!(verify_native_user_auth(&client, &server, &auth, &removed).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_user_auth_rejects_tampered_transcript() {
|
||||||
|
let host_signing = SigningKey::from_bytes(&[3u8; 32]);
|
||||||
|
let user_signing = SigningKey::from_bytes(&[9u8; 32]);
|
||||||
|
let client = test_client_hello();
|
||||||
|
let mut server = test_server_hello(&host_signing);
|
||||||
|
sign_server_hello(&host_signing, &client, &mut server).unwrap();
|
||||||
|
let auth = sign_user_auth(&user_signing, &client, &server, Vec::new()).unwrap();
|
||||||
|
let authorized =
|
||||||
|
parse_authorized_keys(&ssh_ed25519_authorized_key_line(&user_signing)).unwrap();
|
||||||
|
let mut tampered = client.clone();
|
||||||
|
tampered.requested_session = "other".to_string();
|
||||||
|
|
||||||
|
assert!(verify_native_user_auth(&tampered, &server, &auth, &authorized).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_user_auth_fails_closed_on_unsupported_authorized_key_options() {
|
||||||
|
let host_signing = SigningKey::from_bytes(&[3u8; 32]);
|
||||||
|
let user_signing = SigningKey::from_bytes(&[9u8; 32]);
|
||||||
|
let client = test_client_hello();
|
||||||
|
let mut server = test_server_hello(&host_signing);
|
||||||
|
sign_server_hello(&host_signing, &client, &mut server).unwrap();
|
||||||
|
let auth = sign_user_auth(&user_signing, &client, &server, Vec::new()).unwrap();
|
||||||
|
let raw = format!(
|
||||||
|
"no-agent-forwarding {}",
|
||||||
|
ssh_ed25519_authorized_key_line(&user_signing)
|
||||||
|
);
|
||||||
|
let authorized = parse_authorized_keys(&raw).unwrap();
|
||||||
|
|
||||||
|
assert!(verify_native_user_auth(&client, &server, &auth, &authorized).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_user_auth_enforces_no_port_forwarding_and_permitopen() {
|
||||||
|
let host_signing = SigningKey::from_bytes(&[3u8; 32]);
|
||||||
|
let user_signing = SigningKey::from_bytes(&[9u8; 32]);
|
||||||
|
let client = test_client_hello();
|
||||||
|
let mut server = test_server_hello(&host_signing);
|
||||||
|
sign_server_hello(&host_signing, &client, &mut server).unwrap();
|
||||||
|
let forward = ForwardingRequest {
|
||||||
|
kind: ForwardingKind::Local,
|
||||||
|
bind_host: None,
|
||||||
|
listen_port: 8080,
|
||||||
|
target_host: Some("127.0.0.1".to_string()),
|
||||||
|
target_port: Some(80),
|
||||||
|
};
|
||||||
|
let auth = sign_user_auth(&user_signing, &client, &server, vec![forward]).unwrap();
|
||||||
|
let no_forwarding = parse_authorized_keys(&format!(
|
||||||
|
"no-port-forwarding {}",
|
||||||
|
ssh_ed25519_authorized_key_line(&user_signing)
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
assert!(verify_native_user_auth(&client, &server, &auth, &no_forwarding).is_err());
|
||||||
|
|
||||||
|
let permitted = parse_authorized_keys(&format!(
|
||||||
|
"permitopen=\"127.0.0.1:80\" {}",
|
||||||
|
ssh_ed25519_authorized_key_line(&user_signing)
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
verify_native_user_auth(&client, &server, &auth, &permitted).unwrap();
|
||||||
|
|
||||||
|
let denied = parse_authorized_keys(&format!(
|
||||||
|
"permitopen=\"127.0.0.1:81\" {}",
|
||||||
|
ssh_ed25519_authorized_key_line(&user_signing)
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
assert!(verify_native_user_auth(&client, &server, &auth, &denied).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_client_hello() -> NativeClientHello {
|
||||||
|
NativeClientHello {
|
||||||
protocol_version: NATIVE_PROTOCOL_VERSION,
|
protocol_version: NATIVE_PROTOCOL_VERSION,
|
||||||
client_random: [1u8; 32],
|
client_random: [1u8; 32],
|
||||||
client_ephemeral_public: [2u8; 32],
|
client_ephemeral_public: [2u8; 32],
|
||||||
@@ -495,21 +915,20 @@ mod tests {
|
|||||||
supported_user_key_algorithms: vec!["ssh-ed25519".to_string()],
|
supported_user_key_algorithms: vec!["ssh-ed25519".to_string()],
|
||||||
cached_host_key_fingerprint: None,
|
cached_host_key_fingerprint: None,
|
||||||
attach_ticket_envelope: None,
|
attach_ticket_envelope: None,
|
||||||
};
|
}
|
||||||
let mut server = NativeServerHello {
|
}
|
||||||
|
|
||||||
|
fn test_server_hello(signing: &SigningKey) -> NativeServerHello {
|
||||||
|
NativeServerHello {
|
||||||
protocol_version: NATIVE_PROTOCOL_VERSION,
|
protocol_version: NATIVE_PROTOCOL_VERSION,
|
||||||
server_random: [4u8; 32],
|
server_random: [4u8; 32],
|
||||||
server_ephemeral_public: [5u8; 32],
|
server_ephemeral_public: [5u8; 32],
|
||||||
host_key: host_public_key(&signing),
|
host_key: host_public_key(signing),
|
||||||
chosen_aead: "chacha20poly1305".to_string(),
|
chosen_aead: "chacha20poly1305".to_string(),
|
||||||
server_key_epoch: 1,
|
server_key_epoch: 1,
|
||||||
auth_challenge: [6u8; 32],
|
auth_challenge: [6u8; 32],
|
||||||
rate_limit_remaining: Some(30),
|
rate_limit_remaining: Some(30),
|
||||||
host_signature: Vec::new(),
|
host_signature: Vec::new(),
|
||||||
};
|
}
|
||||||
sign_server_hello(&signing, &client, &mut server).unwrap();
|
|
||||||
verify_server_hello(&client, &server).unwrap();
|
|
||||||
server.auth_challenge = [7u8; 32];
|
|
||||||
assert!(verify_server_hello(&client, &server).is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user