some work on config generation
This commit is contained in:
parent
f7de7ff592
commit
13dec2963c
|
@ -1831,6 +1831,14 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nebula-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ipnet",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nebula-ffi"
|
name = "nebula-ffi"
|
||||||
version = "1.7.3"
|
version = "1.7.3"
|
||||||
|
@ -3088,8 +3096,10 @@ dependencies = [
|
||||||
"dnapi-rs",
|
"dnapi-rs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
|
"ipnet",
|
||||||
"log",
|
"log",
|
||||||
"mail-send",
|
"mail-send",
|
||||||
|
"nebula-config",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -5,7 +5,7 @@ members = [
|
||||||
"dnapi-rs",
|
"dnapi-rs",
|
||||||
"tfcli",
|
"tfcli",
|
||||||
"nebula-ffi",
|
"nebula-ffi",
|
||||||
|
"nebula-config",
|
||||||
"trifid-api",
|
"trifid-api",
|
||||||
"trifid-api-derive"
|
"trifid-api-derive"
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "nebula-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
ipnet = { version = "2.9", features = ["serde"] }
|
|
@ -0,0 +1,532 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
use ipnet::{IpNet, Ipv4Net};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfig {
|
||||||
|
pub pki: NebulaConfigPki,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub static_host_map: HashMap<Ipv4Addr, Vec<SocketAddrV4>>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub lighthouse: Option<NebulaConfigLighthouse>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub listen: Option<NebulaConfigListen>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub punchy: Option<NebulaConfigPunchy>,
|
||||||
|
#[serde(default = "cipher_aes")]
|
||||||
|
#[serde(skip_serializing_if = "is_cipher_aes")]
|
||||||
|
pub cipher: NebulaConfigCipher,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub preferred_ranges: Vec<IpNet>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub relay: Option<NebulaConfigRelay>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub tun: Option<NebulaConfigTun>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub logging: Option<NebulaConfigLogging>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub sshd: Option<NebulaConfigSshd>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub firewall: Option<NebulaConfigFirewall>,
|
||||||
|
|
||||||
|
#[serde(default = "u64_1")]
|
||||||
|
#[serde(skip_serializing_if = "is_u64_1")]
|
||||||
|
pub routines: u64,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub stats: Option<NebulaConfigStats>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub local_range: Option<Ipv4Net>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigPki {
|
||||||
|
pub ca: String,
|
||||||
|
pub cert: String,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub key: Option<String>,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub blocklist: Vec<String>,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub disconnect_invalid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigLighthouse {
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub am_lighthouse: bool,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub serve_dns: bool,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub dns: Option<NebulaConfigLighthouseDns>,
|
||||||
|
#[serde(default = "u32_10")]
|
||||||
|
#[serde(skip_serializing_if = "is_u32_10")]
|
||||||
|
pub interval: u32,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub hosts: Vec<Ipv4Addr>,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub remote_allow_list: HashMap<Ipv4Net, bool>,
|
||||||
|
#[serde(default = "empty_hashmap")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_hashmap")]
|
||||||
|
pub local_allow_list: HashMap<Ipv4Net, bool>, // `interfaces` is not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigLighthouseDns {
|
||||||
|
#[serde(default = "string_empty")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_empty")]
|
||||||
|
pub host: String,
|
||||||
|
#[serde(default = "u16_53")]
|
||||||
|
#[serde(skip_serializing_if = "is_u16_53")]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigListen {
|
||||||
|
#[serde(default = "string_empty")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_empty")]
|
||||||
|
pub host: String,
|
||||||
|
#[serde(default = "u16_0")]
|
||||||
|
#[serde(skip_serializing_if = "is_u16_0")]
|
||||||
|
pub port: u16,
|
||||||
|
#[serde(default = "u32_64")]
|
||||||
|
#[serde(skip_serializing_if = "is_u32_64")]
|
||||||
|
pub batch: u32,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub read_buffer: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub write_buffer: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigPunchy {
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub punch: bool,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub respond: bool,
|
||||||
|
#[serde(default = "string_1s")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_1s")]
|
||||||
|
pub delay: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigCipher {
|
||||||
|
#[serde(rename = "aes")]
|
||||||
|
Aes,
|
||||||
|
#[serde(rename = "chachapoly")]
|
||||||
|
ChaChaPoly,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigRelay {
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub relays: Vec<Ipv4Addr>,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub am_relay: bool,
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_true")]
|
||||||
|
pub use_relays: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigTun {
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub disabled: bool,
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub dev: Option<String>,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub drop_local_broadcast: bool,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub drop_multicast: bool,
|
||||||
|
#[serde(default = "u64_500")]
|
||||||
|
#[serde(skip_serializing_if = "is_u64_500")]
|
||||||
|
pub tx_queue: u64,
|
||||||
|
#[serde(default = "u64_1300")]
|
||||||
|
#[serde(skip_serializing_if = "is_u64_1300")]
|
||||||
|
pub mtu: u64,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub routes: Vec<NebulaConfigTunRouteOverride>,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub unsafe_routes: Vec<NebulaConfigTunUnsafeRoute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigTunRouteOverride {
|
||||||
|
pub mtu: u64,
|
||||||
|
pub route: Ipv4Net,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigTunUnsafeRoute {
|
||||||
|
pub route: Ipv4Net,
|
||||||
|
pub via: Ipv4Addr,
|
||||||
|
#[serde(default = "u64_1300")]
|
||||||
|
#[serde(skip_serializing_if = "is_u64_1300")]
|
||||||
|
pub mtu: u64,
|
||||||
|
#[serde(default = "i64_100")]
|
||||||
|
#[serde(skip_serializing_if = "is_i64_100")]
|
||||||
|
pub metric: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigLogging {
|
||||||
|
#[serde(default = "loglevel_info")]
|
||||||
|
#[serde(skip_serializing_if = "is_loglevel_info")]
|
||||||
|
pub level: NebulaConfigLoggingLevel,
|
||||||
|
#[serde(default = "format_text")]
|
||||||
|
#[serde(skip_serializing_if = "is_format_text")]
|
||||||
|
pub format: NebulaConfigLoggingFormat,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub disable_timestamp: bool,
|
||||||
|
#[serde(default = "timestamp")]
|
||||||
|
#[serde(skip_serializing_if = "is_timestamp")]
|
||||||
|
pub timestamp_format: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigLoggingLevel {
|
||||||
|
#[serde(rename = "panic")]
|
||||||
|
Panic,
|
||||||
|
#[serde(rename = "fatal")]
|
||||||
|
Fatal,
|
||||||
|
#[serde(rename = "error")]
|
||||||
|
Error,
|
||||||
|
#[serde(rename = "warning")]
|
||||||
|
Warning,
|
||||||
|
#[serde(rename = "info")]
|
||||||
|
Info,
|
||||||
|
#[serde(rename = "debug")]
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigLoggingFormat {
|
||||||
|
#[serde(rename = "json")]
|
||||||
|
Json,
|
||||||
|
#[serde(rename = "text")]
|
||||||
|
Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigSshd {
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub enabled: bool,
|
||||||
|
pub listen: SocketAddrV4,
|
||||||
|
pub host_key: String,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub authorized_users: Vec<NebulaConfigSshdAuthorizedUser>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigSshdAuthorizedUser {
|
||||||
|
pub user: String,
|
||||||
|
#[serde(default = "empty_vec")]
|
||||||
|
#[serde(skip_serializing_if = "is_empty_vec")]
|
||||||
|
pub keys: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum NebulaConfigStats {
|
||||||
|
#[serde(rename = "graphite")]
|
||||||
|
Graphite(NebulaConfigStatsGraphite),
|
||||||
|
#[serde(rename = "prometheus")]
|
||||||
|
Prometheus(NebulaConfigStatsPrometheus),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigStatsGraphite {
|
||||||
|
#[serde(default = "string_nebula")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_nebula")]
|
||||||
|
pub prefix: String,
|
||||||
|
#[serde(default = "protocol_tcp")]
|
||||||
|
#[serde(skip_serializing_if = "is_protocol_tcp")]
|
||||||
|
pub protocol: NebulaConfigStatsGraphiteProtocol,
|
||||||
|
pub host: SocketAddrV4,
|
||||||
|
pub interval: String,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub message_metrics: bool,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub lighthouse_metrics: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum NebulaConfigStatsGraphiteProtocol {
|
||||||
|
#[serde(rename = "tcp")]
|
||||||
|
Tcp,
|
||||||
|
#[serde(rename = "udp")]
|
||||||
|
Udp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigStatsPrometheus {
|
||||||
|
pub listen: String,
|
||||||
|
pub path: String,
|
||||||
|
#[serde(default = "string_nebula")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_nebula")]
|
||||||
|
pub namespace: String,
|
||||||
|
#[serde(default = "string_nebula")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_nebula")]
|
||||||
|
pub subsystem: String,
|
||||||
|
pub interval: String,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub message_metrics: bool,
|
||||||
|
#[serde(default = "bool_false")]
|
||||||
|
#[serde(skip_serializing_if = "is_bool_false")]
|
||||||
|
pub lighthouse_metrics: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigFirewall {
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub conntrack: Option<NebulaConfigFirewallConntrack>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub inbound: Option<Vec<NebulaConfigFirewallRule>>,
|
||||||
|
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub outbound: Option<Vec<NebulaConfigFirewallRule>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigFirewallConntrack {
|
||||||
|
#[serde(default = "string_12m")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_12m")]
|
||||||
|
pub tcp_timeout: String,
|
||||||
|
#[serde(default = "string_3m")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_3m")]
|
||||||
|
pub udp_timeout: String,
|
||||||
|
#[serde(default = "string_10m")]
|
||||||
|
#[serde(skip_serializing_if = "is_string_10m")]
|
||||||
|
pub default_timeout: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct NebulaConfigFirewallRule {
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub port: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub proto: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub ca_name: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub ca_sha: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub host: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub group: Option<String>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub groups: Option<Vec<String>>,
|
||||||
|
#[serde(default = "none")]
|
||||||
|
#[serde(skip_serializing_if = "is_none")]
|
||||||
|
pub cidr: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values for serde
|
||||||
|
|
||||||
|
fn string_12m() -> String {
|
||||||
|
"12m".to_string()
|
||||||
|
}
|
||||||
|
fn is_string_12m(s: &str) -> bool {
|
||||||
|
s == "12m"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_3m() -> String {
|
||||||
|
"3m".to_string()
|
||||||
|
}
|
||||||
|
fn is_string_3m(s: &str) -> bool {
|
||||||
|
s == "3m"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_10m() -> String {
|
||||||
|
"10m".to_string()
|
||||||
|
}
|
||||||
|
fn is_string_10m(s: &str) -> bool {
|
||||||
|
s == "10m"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_vec<T>() -> Vec<T> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
fn is_empty_vec<T>(v: &Vec<T>) -> bool {
|
||||||
|
v.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_hashmap<A, B>() -> HashMap<A, B> {
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
fn is_empty_hashmap<A, B>(h: &HashMap<A, B>) -> bool {
|
||||||
|
h.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_false() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_bool_false(b: &bool) -> bool {
|
||||||
|
!*b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn is_bool_true(b: &bool) -> bool {
|
||||||
|
*b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u16_53() -> u16 {
|
||||||
|
53
|
||||||
|
}
|
||||||
|
fn is_u16_53(u: &u16) -> bool {
|
||||||
|
*u == 53
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u32_10() -> u32 {
|
||||||
|
10
|
||||||
|
}
|
||||||
|
fn is_u32_10(u: &u32) -> bool {
|
||||||
|
*u == 10
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u16_0() -> u16 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn is_u16_0(u: &u16) -> bool {
|
||||||
|
*u == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u32_64() -> u32 {
|
||||||
|
64
|
||||||
|
}
|
||||||
|
fn is_u32_64(u: &u32) -> bool {
|
||||||
|
*u == 64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_1s() -> String {
|
||||||
|
"1s".to_string()
|
||||||
|
}
|
||||||
|
fn is_string_1s(s: &str) -> bool {
|
||||||
|
s == "1s"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cipher_aes() -> NebulaConfigCipher {
|
||||||
|
NebulaConfigCipher::Aes
|
||||||
|
}
|
||||||
|
fn is_cipher_aes(c: &NebulaConfigCipher) -> bool {
|
||||||
|
matches!(c, NebulaConfigCipher::Aes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u64_500() -> u64 {
|
||||||
|
500
|
||||||
|
}
|
||||||
|
fn is_u64_500(u: &u64) -> bool {
|
||||||
|
*u == 500
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u64_1300() -> u64 {
|
||||||
|
1300
|
||||||
|
}
|
||||||
|
fn is_u64_1300(u: &u64) -> bool {
|
||||||
|
*u == 1300
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i64_100() -> i64 {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
fn is_i64_100(i: &i64) -> bool {
|
||||||
|
*i == 100
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loglevel_info() -> NebulaConfigLoggingLevel {
|
||||||
|
NebulaConfigLoggingLevel::Info
|
||||||
|
}
|
||||||
|
fn is_loglevel_info(l: &NebulaConfigLoggingLevel) -> bool {
|
||||||
|
matches!(l, NebulaConfigLoggingLevel::Info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_text() -> NebulaConfigLoggingFormat {
|
||||||
|
NebulaConfigLoggingFormat::Text
|
||||||
|
}
|
||||||
|
fn is_format_text(f: &NebulaConfigLoggingFormat) -> bool {
|
||||||
|
matches!(f, NebulaConfigLoggingFormat::Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp() -> String {
|
||||||
|
"2006-01-02T15:04:05Z07:00".to_string()
|
||||||
|
}
|
||||||
|
fn is_timestamp(s: &str) -> bool {
|
||||||
|
s == "2006-01-02T15:04:05Z07:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u64_1() -> u64 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn is_u64_1(u: &u64) -> bool {
|
||||||
|
*u == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_nebula() -> String {
|
||||||
|
"nebula".to_string()
|
||||||
|
}
|
||||||
|
fn is_string_nebula(s: &str) -> bool {
|
||||||
|
s == "nebula"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_empty() -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
fn is_string_empty(s: &str) -> bool {
|
||||||
|
s.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn protocol_tcp() -> NebulaConfigStatsGraphiteProtocol {
|
||||||
|
NebulaConfigStatsGraphiteProtocol::Tcp
|
||||||
|
}
|
||||||
|
fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool {
|
||||||
|
matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none<T>() -> Option<T> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn is_none<T>(o: &Option<T>) -> bool {
|
||||||
|
o.is_none()
|
||||||
|
}
|
|
@ -31,4 +31,6 @@ chacha20poly1305 = "0.10"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
dnapi-rs = { version = "0.2", path = "../dnapi-rs" }
|
dnapi-rs = { version = "0.2", path = "../dnapi-rs" }
|
||||||
|
nebula-config = { version = "0.1", path = "../nebula-config" }
|
||||||
|
ipnet = "2.9"
|
|
@ -50,3 +50,6 @@ auth_token_expiry_seconds = 86400 # 24 hours
|
||||||
# It is INCREDIBLY IMPORTANT that you change this value! It should be a 32-byte/256-bit hex-encoded randomly generated
|
# It is INCREDIBLY IMPORTANT that you change this value! It should be a 32-byte/256-bit hex-encoded randomly generated
|
||||||
# key.
|
# key.
|
||||||
data_encryption_key = "dd5aa62f0fd9b7fb4ff65567493f889557212f3a8e9587a79268161f9ae070a6"
|
data_encryption_key = "dd5aa62f0fd9b7fb4ff65567493f889557212f3a8e9587a79268161f9ae070a6"
|
||||||
|
# (Required) How long should client certs be valid for?
|
||||||
|
# Clients will require a config update after this period.
|
||||||
|
cert_expiry_time_seconds = 31536000 # ~1 year
|
|
@ -44,4 +44,5 @@ pub struct ConfigTokens {
|
||||||
pub session_token_expiry_seconds: u64,
|
pub session_token_expiry_seconds: u64,
|
||||||
pub auth_token_expiry_seconds: u64,
|
pub auth_token_expiry_seconds: u64,
|
||||||
pub data_encryption_key: String,
|
pub data_encryption_key: String,
|
||||||
|
pub cert_expiry_time_seconds: u64
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
// The main event! This file handles generation of actual config files.
|
||||||
|
// This file is part of the sensitive stack, along with `crypt.rs`
|
||||||
|
// and the entirety of trifid-pki, as it deals with CA private keys.
|
||||||
|
// Review carefully what you write here!
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
|
||||||
|
use diesel_async::pooled_connection::bb8::RunError;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use ipnet::Ipv4Net;
|
||||||
|
use thiserror::Error;
|
||||||
|
use nebula_config::{NebulaConfig, NebulaConfigPki};
|
||||||
|
use trifid_pki::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails};
|
||||||
|
use trifid_pki::x25519_dalek::PublicKey;
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::crypt::sign_cert_with_ca;
|
||||||
|
use crate::models::{Host, Network, SigningCA, HostKey};
|
||||||
|
use crate::schema::{host_keys, hosts, networks, signing_cas};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ConfigGenError {
|
||||||
|
#[error("error acquiring connection from pool: {0}")]
|
||||||
|
AcquireError(RunError),
|
||||||
|
#[error("error in database: {0}")]
|
||||||
|
DbError(diesel::result::Error),
|
||||||
|
#[error("error parsing a signing CA: {0}")]
|
||||||
|
InvalidCACert(serde_json::Error),
|
||||||
|
#[error("an error occured: {0}")]
|
||||||
|
GenericError(Box<dyn Error>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppState>) -> Result<NebulaConfig, ConfigGenError> {
|
||||||
|
let mut conn = state.pool.get().await.map_err(ConfigGenError::AcquireError)?;
|
||||||
|
|
||||||
|
let cas = signing_cas::dsl::signing_cas.filter(signing_cas::organization_id.eq(&host.organization_id)).select(SigningCA::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?;
|
||||||
|
|
||||||
|
let mut good_cas = vec![];
|
||||||
|
let mut ca_string = String::new();
|
||||||
|
|
||||||
|
for ca in cas {
|
||||||
|
if ca.expires_at < SystemTime::now() {
|
||||||
|
let ca_cert: NebulaCertificate = serde_json::from_value(ca.cert.clone()).map_err(ConfigGenError::InvalidCACert)?;
|
||||||
|
good_cas.push((ca, ca_cert));
|
||||||
|
ca_string += &String::from_utf8_lossy(&ca_cert.serialize_to_pem().map_err(ConfigGenError::GenericError)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (signing_ca, ca_cert) = &good_cas[0];
|
||||||
|
|
||||||
|
let network = networks::dsl::networks.find(&host.network_id).first::<Network>(&mut conn).await.map_err(ConfigGenError::DbError)?;
|
||||||
|
|
||||||
|
let mut cert = NebulaCertificate {
|
||||||
|
details: NebulaCertificateDetails {
|
||||||
|
name: host.name.clone(),
|
||||||
|
ips: vec![
|
||||||
|
Ipv4Net::new(
|
||||||
|
Ipv4Addr::from_str(&host.ip_address).unwrap(),
|
||||||
|
Ipv4Net::from_str(&network.cidr).unwrap().prefix_len()
|
||||||
|
).unwrap()
|
||||||
|
],
|
||||||
|
subnets: vec![],
|
||||||
|
groups: if let Some(role_id) = &host.role_id {
|
||||||
|
vec![format!("role:{}", role_id)]
|
||||||
|
} else { vec![] },
|
||||||
|
not_before: SystemTime::now() - Duration::from_secs(3600),
|
||||||
|
not_after: SystemTime::now() + Duration::from_secs(state.config.tokens.cert_expiry_time_seconds),
|
||||||
|
public_key: *dh_pubkey.as_bytes(),
|
||||||
|
is_ca: false,
|
||||||
|
issuer: ca_cert.sha256sum().unwrap(),
|
||||||
|
},
|
||||||
|
signature: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
sign_cert_with_ca(signing_ca, &mut cert, &state.config).unwrap();
|
||||||
|
|
||||||
|
let all_blocked_hosts = hosts::dsl::hosts.filter(hosts::network_id.eq(&host.network_id)).filter(hosts::is_blocked.eq(true)).select(Host::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?;
|
||||||
|
|
||||||
|
let mut all_blocked_fingerprints = vec![];
|
||||||
|
|
||||||
|
for blocked_host in all_blocked_hosts {
|
||||||
|
let hosts_blocked_key_entries = host_keys::dsl::host_keys.filter(host_keys::host_id.eq(&blocked_host.id)).select(HostKey::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?;
|
||||||
|
for blocked_key in hosts_blocked_key_entries {
|
||||||
|
let cert = deserialize_nebula_certificate_from_pem(&blocked_key.client_cert).unwrap();
|
||||||
|
let fingerprint = cert.sha256sum().unwrap();
|
||||||
|
all_blocked_fingerprints.push(fingerprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pki = NebulaConfigPki {
|
||||||
|
ca: ca_string,
|
||||||
|
cert: String::from_utf8(cert.serialize_to_pem().unwrap()).unwrap(),
|
||||||
|
key: None,
|
||||||
|
blocklist: all_blocked_fingerprints,
|
||||||
|
disconnect_invalid: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ use crate::config::Config;
|
||||||
use crate::models::SigningCA;
|
use crate::models::SigningCA;
|
||||||
use actix_web::cookie::time::Duration;
|
use actix_web::cookie::time::Duration;
|
||||||
use chacha20poly1305::aead::{Aead, Payload};
|
use chacha20poly1305::aead::{Aead, Payload};
|
||||||
use chacha20poly1305::{AeadCore, KeyInit, Nonce, XChaCha20Poly1305, XNonce};
|
use chacha20poly1305::{AeadCore, KeyInit, XChaCha20Poly1305, XNonce};
|
||||||
use log::error;
|
use log::error;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -10,7 +10,7 @@ use std::error::Error;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails};
|
use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails};
|
||||||
use trifid_pki::ed25519_dalek::{SignatureError, SigningKey};
|
use trifid_pki::ed25519_dalek::{Signature, SignatureError, Signer, SigningKey, VerifyingKey};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum CryptographyError {
|
pub enum CryptographyError {
|
||||||
|
@ -139,3 +139,80 @@ pub fn sign_cert_with_ca(
|
||||||
cert.sign(&key)
|
cert.sign(&key)
|
||||||
.map_err(|e| CryptographyError::CertificateSigningError(e))
|
.map_err(|e| CryptographyError::CertificateSigningError(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DnclientKeyLockbox {
|
||||||
|
pub info: Vec<u8>,
|
||||||
|
pub nonce: Vec<u8>,
|
||||||
|
pub key: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dnclient_ed_key(
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<(DnclientKeyLockbox, VerifyingKey), CryptographyError> {
|
||||||
|
let key = SigningKey::generate(&mut OsRng);
|
||||||
|
|
||||||
|
let lockbox_key = XChaCha20Poly1305::new_from_slice(
|
||||||
|
&hex::decode(&config.tokens.data_encryption_key)
|
||||||
|
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
||||||
|
)
|
||||||
|
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
||||||
|
|
||||||
|
let salt = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
|
|
||||||
|
let aad: [u8; 16] = OsRng.gen();
|
||||||
|
|
||||||
|
let lockbox = lockbox_key
|
||||||
|
.encrypt(
|
||||||
|
&salt,
|
||||||
|
Payload {
|
||||||
|
msg: &key.to_keypair_bytes(),
|
||||||
|
aad: &aad,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| CryptographyError::LockingError)?;
|
||||||
|
|
||||||
|
Ok((DnclientKeyLockbox {
|
||||||
|
info: aad.to_vec(),
|
||||||
|
nonce: salt.as_slice().to_vec(),
|
||||||
|
key: lockbox,
|
||||||
|
}, key.verifying_key()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign_dnclient_with_lockbox(
|
||||||
|
lockbox: &DnclientKeyLockbox,
|
||||||
|
bytes: &[u8],
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Signature, CryptographyError> {
|
||||||
|
let lockbox_key = XChaCha20Poly1305::new_from_slice(
|
||||||
|
&hex::decode(&config.tokens.data_encryption_key)
|
||||||
|
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
||||||
|
)
|
||||||
|
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
||||||
|
|
||||||
|
let salt_u24: [u8; 24] = lockbox
|
||||||
|
.nonce
|
||||||
|
.clone()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| CryptographyError::InvalidSaltLength)?;
|
||||||
|
|
||||||
|
let salt = XNonce::from(salt_u24);
|
||||||
|
|
||||||
|
let plaintext = lockbox_key
|
||||||
|
.decrypt(
|
||||||
|
&salt,
|
||||||
|
Payload {
|
||||||
|
msg: &lockbox.key,
|
||||||
|
aad: &lockbox.info,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|_| CryptographyError::DecryptFailed)?;
|
||||||
|
|
||||||
|
let key = SigningKey::from_keypair_bytes(
|
||||||
|
&plaintext
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| CryptographyError::InvalidSigningKeyLength)?,
|
||||||
|
)
|
||||||
|
.map_err(|e| CryptographyError::SignatureError(e))?;
|
||||||
|
|
||||||
|
Ok(key.sign(bytes))
|
||||||
|
}
|
|
@ -26,7 +26,8 @@ pub mod auth;
|
||||||
pub mod email;
|
pub mod email;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod ca;
|
pub mod crypt;
|
||||||
|
mod config_generator;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::ca::create_signing_ca;
|
use crate::crypt::create_signing_ca;
|
||||||
use crate::models::{Network, NetworkNormalized, Organization, SigningCA, User};
|
use crate::models::{Network, NetworkNormalized, Organization, SigningCA, User};
|
||||||
use crate::response::JsonAPIResponse;
|
use crate::response::JsonAPIResponse;
|
||||||
use crate::schema::networks::dsl::networks;
|
use crate::schema::networks::dsl::networks;
|
||||||
|
|
|
@ -1,7 +1,17 @@
|
||||||
|
use std::time::SystemTime;
|
||||||
use crate::response::JsonAPIResponse;
|
use crate::response::JsonAPIResponse;
|
||||||
use crate::AppState;
|
use crate::{AppState, randid};
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
use dnapi_rs::message::{EnrollRequest, EnrollResponse};
|
use dnapi_rs::message::{EnrollRequest, EnrollResponse};
|
||||||
|
use crate::models::{EnrollmentCode, Host, HostKey};
|
||||||
|
use crate::schema::{enrollment_codes, hosts};
|
||||||
|
use diesel::{QueryDsl, OptionalExtension, ExpressionMethods};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use trifid_pki::x25519_dalek::PublicKey;
|
||||||
|
use crate::config_generator::generate_config;
|
||||||
|
use crate::crypt::create_dnclient_ed_key;
|
||||||
|
use crate::schema::host_keys;
|
||||||
|
|
||||||
pub async fn enroll_req(
|
pub async fn enroll_req(
|
||||||
req: Json<EnrollRequest>,
|
req: Json<EnrollRequest>,
|
||||||
|
@ -9,5 +19,80 @@ pub async fn enroll_req(
|
||||||
) -> JsonAPIResponse<EnrollResponse> {
|
) -> JsonAPIResponse<EnrollResponse> {
|
||||||
let mut conn = handle_error!(state.pool.get().await);
|
let mut conn = handle_error!(state.pool.get().await);
|
||||||
|
|
||||||
|
let token: EnrollmentCode = match handle_error!(enrollment_codes::table
|
||||||
|
.find(&req.code)
|
||||||
|
.first::<EnrollmentCode>(&mut conn)
|
||||||
|
.await
|
||||||
|
.optional())
|
||||||
|
{
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
err!(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
make_err!(
|
||||||
|
"ERR_INVALID_VALUE",
|
||||||
|
"does not exist (maybe it expired?)",
|
||||||
|
"magicLinkToken"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if token.expires < SystemTime::now() {
|
||||||
|
err!(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
make_err!(
|
||||||
|
"ERR_INVALID_VALUE",
|
||||||
|
"does not exist (maybe it expired?)",
|
||||||
|
"magicLinkToken"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid token
|
||||||
|
|
||||||
|
let host: Host = match handle_error!(hosts::table
|
||||||
|
.find(&token.host_id)
|
||||||
|
.first::<Host>(&mut conn)
|
||||||
|
.await
|
||||||
|
.optional())
|
||||||
|
{
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
err!(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
make_err!(
|
||||||
|
"ERR_INVALID_VALUE",
|
||||||
|
"does not exist (maybe it expired?)",
|
||||||
|
"magicLinkToken.host_id"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handle_error!(diesel::delete(&token).execute(&mut conn).await);
|
||||||
|
|
||||||
|
// reset the host's key entries
|
||||||
|
|
||||||
|
handle_error!(diesel::delete(host_keys::dsl::host_keys.filter(host_keys::host_id.eq(&host.id))).execute(&mut conn).await);
|
||||||
|
|
||||||
|
let (key_lockbox, trusted_key) = handle_error!(create_dnclient_ed_key(&state.config));
|
||||||
|
|
||||||
|
let user_dh_key = PublicKey::from(req.dh_pubkey.try_into().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let config = handle_error!(generate_config(&host, state.clone()).await);
|
||||||
|
|
||||||
|
let new_counter_1 = HostKey {
|
||||||
|
id: randid!(id "hostkey"),
|
||||||
|
host_id: host.id.clone(),
|
||||||
|
counter: 1,
|
||||||
|
client_ed_pub: vec![],
|
||||||
|
client_dh_pub: vec![],
|
||||||
|
client_cert: vec![],
|
||||||
|
salt: vec![],
|
||||||
|
info: vec![],
|
||||||
|
server_ed_priv: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue