some work

This commit is contained in:
c0repwn3r 2023-03-29 11:18:33 -04:00
parent b6940ba0f3
commit 553a95d6bc
Signed by: core
GPG Key ID: FDBF740DADDCEECF
14 changed files with 334 additions and 156 deletions

27
Cargo.lock generated
View File

@ -619,6 +619,20 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "dnapi-rs"
version = "0.1.0"
dependencies = [
"base64 0.21.0",
"base64-serde",
"log",
"reqwest",
"serde",
"serde_json",
"trifid-pki",
"url",
]
[[package]]
name = "dotenvy"
version = "0.15.6"
@ -2061,18 +2075,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.158"
version = "1.0.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.158"
version = "1.0.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
dependencies = [
"proc-macro2",
"quote",
@ -2081,9 +2095,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.94"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
dependencies = [
"itoa",
"ryu",
@ -2436,6 +2450,7 @@ dependencies = [
"clap",
"ctrlc",
"dirs 5.0.0",
"dnapi-rs",
"flate2",
"hex",
"ipnet",

View File

@ -2,5 +2,6 @@
members = [
"trifid-api",
"tfclient",
"trifid-pki"
"trifid-pki",
"dnapi-rs"
]

22
dnapi-rs/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "dnapi-rs"
version = "0.1.0"
edition = "2021"
description = "A rust client for the Defined Networking API"
license = "AGPL-3.0-or-later"
documentation = "https://docs.rs/dnapi-rs"
homepage = "https://git.e3t.cc/~core/trifid"
repository = "https://git.e3t.cc/~core/trifid"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.159", features = ["derive"] }
base64-serde = "0.7.0"
log = "0.4.17"
reqwest = { version = "0.11.16", features = ["blocking", "json"] }
url = "2.3.1"
base64 = "0.21.0"
serde_json = "1.0.95"
trifid-pki = { version = "0.1.6", path = "../trifid-pki" }

1
dnapi-rs/src/client.rs Normal file
View File

@ -0,0 +1 @@
//! Client structs to handle communication with the Defined Networking API.

View File

@ -0,0 +1,38 @@
//! Contains the `Credentials` struct, which contains all keys, IDs, organizations and other identity-related and security-related data that is persistent in a `Client`
use std::error::Error;
use trifid_pki::cert::{deserialize_ed25519_public, serialize_ed25519_public};
use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey};
/// Contains information necessary to make requests against the `DNClient` API.
pub struct Credentials {
/// The assigned Host ID that this client represents
pub host_id: String,
/// The ed25519 private key used to sign requests against the API
pub ed_privkey: SigningKey,
/// The counter used in the other API requests. It is unknown what the purpose of this is, but the original client persists it and it is needed for API calls.
pub counter: u32,
/// The set of trusted ed25519 keys that may be used by the API to sign API responses.
pub trusted_keys: Vec<VerifyingKey>
}
/// Converts an array of `VerifyingKey`s to a singular bundle of PEM-encoded keys
pub fn ed25519_public_keys_to_pem(keys: &[VerifyingKey]) -> Vec<u8> {
let mut res = vec![];
for key in keys {
res.append(&mut serialize_ed25519_public(&key.to_bytes()));
}
res
}
pub fn ed25519_public_keys_from_pem(pem: Vec<u8>) -> Result<Vec<VerifyingKey>, Box<dyn Error>> {
let mut keys = vec![];
for key in keys.chunks(32) {
}
Ok(keys)
}

20
dnapi-rs/src/lib.rs Normal file
View File

@ -0,0 +1,20 @@
//! # dnapi-rs
//! **dnapi-rs** is a Rust-native crate for interacting with the Defined Networking client API. It is a direct port of `dnapi`, an officially maintained API client by Defined Networking.
//!
//! This crate is maintained as a part of the trifid project. Check out the other crates in [the git repository](https://git.e3t.cc/~core/trifid).
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(missing_docs)]
#![deny(clippy::missing_errors_doc)]
#![deny(clippy::missing_panics_doc)]
#![deny(clippy::missing_safety_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::module_name_repetitions)]
pub mod message;
pub mod client;
pub mod credentials;

197
dnapi-rs/src/message.rs Normal file
View File

@ -0,0 +1,197 @@
//! Models for interacting with the Defined Networking API.
use base64_serde::base64_serde_type;
use serde::{Serialize, Deserialize};
/// The version 1 `DNClient` API endpoint
const ENDPOINT_V1: &str = "/v1/dnclient";
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
#[derive(Serialize, Deserialize)]
/// `RequestV1` is the version 1 `DNClient` request message.
pub struct RequestV1 {
/// Version is always 1
pub version: i32,
#[serde(rename = "hostID")]
/// The Host ID of this dnclient instance
pub host_id: String,
/// The counter last returned by the server
pub counter: u32,
#[serde(with = "Base64Standard")]
/// A base64-encoded message
pub message: Vec<u8>,
#[serde(with = "Base64Standard")]
/// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key
pub signature: Vec<u8>
}
#[derive(Serialize, Deserialize)]
/// `RequestWrapper` wraps a `DNClient` request message. It consists of a
/// type and value, with the type string indicating how to interpret the value blob.
pub struct RequestWrapper {
#[serde(rename = "type")]
/// The type of the message. Used to determine how `value` is encoded
pub message_type: String,
#[serde(with = "Base64Standard")]
/// A base64-encoded arbitrary message, the type of which is stated in `message_type`
pub value: Vec<u8>,
/// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
/// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
/// For example:
/// `2023-03-29T09:56:42.380006369-04:00`
/// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
pub timestamp: String
}
#[derive(Serialize, Deserialize)]
/// `SignedResponseWrapper` contains a response message and a signature to validate inside `data`.
pub struct SignedResponseWrapper {
/// The response data contained in this message
pub data: SignedResponse
}
#[derive(Serialize, Deserialize)]
/// `SignedResponse` contains a response message and a signature to validate.
pub struct SignedResponse {
/// The API version - always 1
pub version: i32,
#[serde(with = "Base64Standard")]
/// The Base64-encoded message signed inside this message
pub message: Vec<u8>,
#[serde(with = "Base64Standard")]
/// The ed25519 signature over the `message`
pub signature: Vec<u8>
}
#[derive(Serialize, Deserialize)]
/// `CheckForUpdateResponseWrapper` contains a response to `CheckForUpdate` inside "data."
pub struct CheckForUpdateResponseWrapper {
/// The response data contained in this message
pub data: CheckForUpdateResponse
}
#[derive(Serialize, Deserialize)]
/// `CheckForUpdateResponse` is the response generated for a `CheckForUpdate` request.
pub struct CheckForUpdateResponse {
#[serde(rename = "updateAvailable")]
/// Set to true if a config update is available
pub update_available: bool
}
#[derive(Serialize, Deserialize)]
/// `DoUpdateRequest` is the request sent for a `DoUpdate` request.
pub struct DoUpdateRequest {
#[serde(rename = "edPubkeyPEM")]
#[serde(with = "Base64Standard")]
/// The new ed25519 public key that should be used for future API requests
pub ed_pubkey_pem: Vec<u8>,
#[serde(rename = "dhPubkeyPEM")]
#[serde(with = "Base64Standard")]
/// The new ECDH public key that the Nebula certificate should be signed for
pub dh_pubkey_pem: Vec<u8>,
#[serde(with = "Base64Standard")]
/// A randomized value used to uniquely identify this request.
/// The original client uses a randomized, 16-byte value here, which dnapi-rs replicates
pub nonce: Vec<u8>
}
#[derive(Serialize, Deserialize)]
/// A server response to a `DoUpdateRequest`, with the updated config and key information
pub struct DoUpdateResponse {
#[serde(with = "Base64Standard")]
/// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
pub config: Vec<u8>,
/// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api
pub counter: u32,
#[serde(with = "Base64Standard")]
/// The same base64-encoded nonce that was sent in the `DoUpdateRequest`.
pub nonce: Vec<u8>,
#[serde(rename = "trustedKeys")]
#[serde(with = "Base64Standard")]
/// A new set of trusted ed25519 keys that can be used by the server to sign messages.
pub trusted_keys: Vec<u8>
}
/// The REST enrollment endpoint
const ENROLL_ENDPOINT: &str = "/v2/enroll";
#[derive(Serialize, Deserialize)]
/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization
pub struct EnrollRequest {
/// The enrollment code given by the API server.
pub code: String,
#[serde(rename = "dhPubkey")]
#[serde(with = "Base64Standard")]
/// The ECDH public-key that should be used to sign the Nebula certificate given to this node.
pub dh_pubkey: Vec<u8>,
#[serde(rename = "edPubkey")]
#[serde(with = "Base64Standard")]
/// The Ed25519 public-key that this node will use to sign messages sent to the API.
pub ed_pubkey: Vec<u8>,
/// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
/// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
/// For example:
/// `2023-03-29T09:56:42.380006369-04:00`
/// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
pub timestamp: String
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
/// The response to an `EnrollRequest`
pub enum EnrollResponse {
/// A successful enrollment, with a `data` field pointing to an `EnrollResponseData`
Success {
/// The response data from this response
data: EnrollResponseData
},
/// An unsuccessful enrollment, with an `errors` field pointing to an array of `APIError`s.
Error {
/// A list of `APIError`s that happened while trying to enroll. `APIErrors` is a type alias to `Vec<APIError>`
errors: APIErrors
}
}
#[derive(Serialize, Deserialize)]
/// The data included in an successful enrollment.
pub struct EnrollResponseData {
#[serde(with = "Base64Standard")]
/// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
pub config: Vec<u8>,
#[serde(rename = "hostID")]
/// The server-side Host ID that this node now has.
pub host_id: String,
/// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api
pub counter: u32,
#[serde(rename = "trustedKeys")]
#[serde(with = "Base64Standard")]
/// A new set of trusted ed25519 keys that can be used by the server to sign messages.
pub trusted_keys: Vec<u8>,
/// The organization data that this node is now a part of
pub organization: EnrollResponseDataOrg
}
#[derive(Serialize, Deserialize)]
/// The organization data that this node is now a part of
pub struct EnrollResponseDataOrg {
/// The organization ID that this node is now a part of
pub id: String,
/// The name of the organization that this node is now a part of
pub name: String
}
#[derive(Serialize, Deserialize)]
/// `APIError` represents a single error returned in an API error response.
pub struct APIError {
/// The error code
pub code: String,
/// The human-readable error message
pub message: String,
/// An optional path to where the error occured
pub path: Option<String>
}
/// A type alias to a array of `APIErrors`. Just for parity with dnapi.
pub type APIErrors = Vec<APIError>;

View File

@ -24,6 +24,7 @@ base64 = "0.21.0"
chrono = "0.4.24"
ipnet = "2.7.1"
base64-serde = "0.7.0"
dnapi-rs = { version = "0.1.0", path = "../dnapi-rs" }
[build-dependencies]
serde = { version = "1.0.157", features = ["derive"] }

View File

@ -1,145 +0,0 @@
use std::error::Error;
use base64_serde::base64_serde_type;
use log::trace;
use reqwest::blocking::Client;
use serde::{Serialize, Deserialize};
use url::Url;
const ENDPOINT_V1: &str = "/v1/dnclient";
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<dyn Error>> {
let endpoint = server.join("/v2/enroll")?;
let client = Client::new();
let text = serde_json::to_string(request)?;
trace!("sending enroll: {}", text);
let resp = client.post(endpoint).body(text).send()?;
Ok(resp.json()?)
}
#[derive(Serialize, Deserialize)]
pub struct RequestV1 {
pub version: i32,
#[serde(rename = "hostID")]
pub host_id: String,
pub counter: u32,
pub message: String,
#[serde(with = "Base64Standard")]
pub signature: Vec<u8>
}
#[derive(Serialize, Deserialize)]
pub struct RequestWrapper {
#[serde(rename = "type")]
pub message_type: String,
#[serde(with = "Base64Standard")]
pub value: Vec<u8>,
pub timestamp: String
}
#[derive(Serialize, Deserialize)]
pub struct SignedResponseWrapper {
pub data: SignedResponse
}
#[derive(Serialize, Deserialize)]
pub struct SignedResponse {
pub version: i32,
#[serde(with = "Base64Standard")]
pub message: Vec<u8>,
#[serde(with = "Base64Standard")]
pub signature: Vec<u8>
}
#[derive(Serialize, Deserialize)]
pub struct CheckForUpdateResponseWrapper {
pub data: CheckForUpdateResponse
}
#[derive(Serialize, Deserialize)]
pub struct CheckForUpdateResponse {
#[serde(rename = "updateAvailable")]
pub update_available: bool
}
#[derive(Serialize, Deserialize)]
pub struct DoUpdateRequest {
#[serde(rename = "edPubkeyPEM")]
#[serde(with = "Base64Standard")]
pub ed_pubkey_pem: Vec<u8>,
#[serde(rename = "dhPubkeyPEM")]
#[serde(with = "Base64Standard")]
pub dh_pubkey_pem: Vec<u8>,
#[serde(with = "Base64Standard")]
pub nonce: Vec<u8>
}
#[derive(Serialize, Deserialize)]
pub struct DoUpdateResponse {
#[serde(with = "Base64Standard")]
pub config: Vec<u8>,
pub counter: u32,
#[serde(with = "Base64Standard")]
pub nonce: Vec<u8>,
#[serde(rename = "trustedKeys")]
#[serde(with = "Base64Standard")]
pub trusted_keys: Vec<u8>
}
const ENROLL_ENDPOINT: &str = "/v2/enroll";
#[derive(Serialize, Deserialize)]
pub struct EnrollRequest {
pub code: String,
#[serde(rename = "dhPubkey")]
#[serde(with = "Base64Standard")]
pub dh_pubkey: Vec<u8>,
#[serde(rename = "edPubkey")]
#[serde(with = "Base64Standard")]
pub ed_pubkey: Vec<u8>,
pub timestamp: String
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum EnrollResponse {
Success {
data: EnrollResponseData
},
Error {
errors: APIErrors
}
}
#[derive(Serialize, Deserialize)]
pub struct EnrollResponseData {
#[serde(with = "Base64Standard")]
pub config: Vec<u8>,
#[serde(rename = "hostID")]
pub host_id: String,
pub counter: u32,
#[serde(rename = "trustedKeys")]
#[serde(with = "Base64Standard")]
pub trusted_keys: Vec<u8>,
pub organization: EnrollResponseDataOrg
}
#[derive(Serialize, Deserialize)]
pub struct EnrollResponseDataOrg {
pub id: String,
pub name: String
}
#[derive(Serialize, Deserialize)]
pub struct APIError {
pub code: String,
pub message: String,
pub path: Option<String>
}
pub type APIErrors = Vec<APIError>;

View File

@ -8,7 +8,7 @@ use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public};
use trifid_pki::ed25519_dalek::{SecretKey, SigningKey};
use trifid_pki::rand_core::OsRng;
use trifid_pki::x25519_dalek::StaticSecret;
use crate::api::{APIResponse, enroll, EnrollRequest};
use crate::message::{APIResponse, enroll, EnrollRequest};
use crate::config::{load_cdata, save_cdata, TFClientConfig};
use crate::daemon::ThreadMessageSender;

View File

@ -1 +0,0 @@
/// Essentially a direct port of https://github.com/DefinedNet/dnapi

View File

@ -23,7 +23,6 @@ pub mod config;
pub mod service;
pub mod apiworker;
pub mod socketworker;
pub mod api;
pub mod socketclient;
pub mod timerworker;

View File

@ -313,6 +313,26 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
Ok(pem.contents)
}
/// Attempt to deserialize multiple PEM encoded Ed25519 public keys
/// # Errors
/// This function will return an error if the PEM data is invalid or has the wrong tag
pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
let mut keys = vec![];
let pems = pem::parse_many(bytes)?;
for pem in pems {
if pem.tag != ED25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into())
}
if pem.contents.len() != 64 {
return Err(KeyError::Not64Bytes.into())
}
keys.push(pem.contents);
}
Ok(keys)
}
impl NebulaCertificate {
/// Sign a nebula certificate with the provided private key
/// # Errors

View File

@ -6,7 +6,7 @@ use std::net::Ipv4Addr;
use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
use ipnet::Ipv4Net;
use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public};
use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_ed25519_public_many, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public};
use std::str::FromStr;
use ed25519_dalek::{SigningKey, VerifyingKey};
use quick_protobuf::{MessageWrite, Writer};
@ -300,6 +300,16 @@ fn ed25519_serialization() {
assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes);
assert!(deserialize_ed25519_public(&[0u8; 32]).is_err());
let mut bytes = vec![];
bytes.append(&mut serialize_ed25519_public(&[0u8; 64]));
bytes.append(&mut serialize_ed25519_public(&[1u8; 64]));
let deser = deserialize_ed25519_public_many(&bytes).unwrap();
assert_eq!(deser[0], [0u8; 64]);
assert_eq!(deser[1], [1u8; 64]);
bytes.append(&mut serialize_ed25519_public(&[1u8; 63]));
deserialize_ed25519_public_many(&bytes).unwrap_err();
}
#[test]