work
/ build (push) Successful in 1m45s Details
/ build_x64 (push) Successful in 5m42s Details
/ build_arm64 (push) Successful in 2m33s Details
/ build_win64 (push) Successful in 6m6s Details

This commit is contained in:
core 2023-12-15 15:16:59 -05:00
parent 2045d7b636
commit d44a6c1166
Signed by: core
GPG Key ID: FDBF740DADDCEECF
14 changed files with 290 additions and 19 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="trifidapi@localhost" uuid="39c81b89-3fc4-493f-b203-7a00527cffe6">
<data-source source="LOCAL" name="trifid@localhost" uuid="39c81b89-3fc4-493f-b203-7a00527cffe6">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>

View File

@ -12,6 +12,7 @@
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/trifid_api_entities/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-old/trifid_api_migration/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api-derive/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

77
Cargo.lock generated
View File

@ -211,6 +211,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.3"
@ -545,6 +555,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
"zeroize",
]
[[package]]
name = "checked_int_cast"
version = "1.0.0"
@ -574,6 +608,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
@ -779,6 +814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"typenum",
]
@ -1867,6 +1903,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "option-ext"
version = "0.2.0"
@ -1998,6 +2040,17 @@ version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8"
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "postgres-protocol"
version = "0.6.6"
@ -2721,7 +2774,7 @@ dependencies = [
[[package]]
name = "tfcli"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"clap",
"comfy-table",
@ -3027,17 +3080,29 @@ dependencies = [
"actix-cors",
"actix-web",
"bb8",
"chacha20poly1305",
"diesel",
"diesel-async",
"diesel_migrations",
"env_logger",
"hex",
"log",
"mail-send",
"rand",
"serde",
"serde_json",
"thiserror",
"toml 0.8.5",
"totp-rs",
"trifid-pki",
]
[[package]]
name = "trifid-api-derive"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.38",
]
[[package]]
@ -3095,6 +3160,16 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"

View File

@ -5,6 +5,9 @@ members = [
"dnapi-rs",
"tfcli",
"nebula-ffi",
"trifid-api"
"trifid-api",
"trifid-api-derive"
]
resolver = "2"

View File

@ -0,0 +1,13 @@
[package]
name = "trifid-api-derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full", "fold"] }
quote = "1"

View File

View File

@ -26,3 +26,7 @@ bb8 = "0.8"
rand = "0.8"
mail-send = "0.4"
totp-rs = { version = "5.4", features = ["gen_secret", "otpauth"] }
trifid-pki = { version = "0.1", path = "../trifid-pki", features = ["serde_derive"] }
chacha20poly1305 = "0.10"
hex = "0.4"
thiserror = "1"

View File

@ -25,7 +25,7 @@ server = "mail.e3t.cc"
port = 465
# (Required) The username to authenticate with.
username = "core"
# (Required) The password to authenticate with. If set to %PASSWORD%, will be filled from the environment variable TRIFID_EMAIL_PASSWORD.
# (Required) The password to authenticate with. If set to $PASSWORD$, will be filled from the environment variable TRIFID_EMAIL_PASSWORD.
password = "$PASSWORD$"
# (Required) The "From Name" to send the email from
from_name = "Trifid"
@ -46,3 +46,7 @@ session_token_expiry_seconds = 31536000 # ~1 year
# (Required) How long should auth tokens be valid for, in seconds? This controls how long users can remain logged in
# before they must re-authenticate via 2FA.
auth_token_expiry_seconds = 86400 # 24 hours
# (Required) (VERY IMPORTANT!) The per-instance encryption key used to encrypt sensitive data in the database.
# It is INCREDIBLY IMPORTANT that you change this value! It should be a 32-byte/256-bit hex-encoded randomly generated
# key.
data_encryption_key = "dd5aa62f0fd9b7fb4ff65567493f889557212f3a8e9587a79268161f9ae070a6"

100
trifid-api/src/ca.rs Normal file
View File

@ -0,0 +1,100 @@
use std::error::Error;
use std::time::SystemTime;
use actix_web::cookie::time::Duration;
use chacha20poly1305::{AeadCore, KeyInit, Nonce, XChaCha20Poly1305, XNonce};
use chacha20poly1305::aead::{Aead, Payload};
use log::error;
use rand::Rng;
use rand::rngs::OsRng;
use thiserror::Error;
use crate::models::SigningCA;
use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails};
use trifid_pki::ed25519_dalek::{SignatureError, SigningKey};
use crate::config::Config;
#[derive(Error, Debug)]
pub enum CryptographyError {
#[error("certificate signing error: {0}")]
CertificateSigningError(Box<dyn Error>),
#[error("PEM serialization error: {0}")]
PemSerializeError(Box<dyn Error>),
#[error("JSON serialization error: {0}")]
JsonSerializeError(serde_json::Error),
#[error("Invalid data_encryption_key content: {0}")]
InvalidKey(hex::FromHexError),
#[error("Invalid data_encryption_key length (must be 32 bytes)")]
InvalidKeyLength,
#[error("Error locking lockbox")]
LockingError,
#[error("Invalid salt length")]
InvalidSaltLength,
#[error("Key material decryption failed")]
DecryptFailed,
#[error("Invalid signing key length after lockbox unlock")]
InvalidSigningKeyLength,
#[error("Signature error {0}")]
SignatureError(SignatureError)
}
pub fn create_signing_ca(expires: SystemTime, org_id: String, user_email: String, config: &Config) -> Result<SigningCA, CryptographyError> {
let key = SigningKey::generate(&mut OsRng);
let mut cert = NebulaCertificate {
details: NebulaCertificateDetails {
name: format!("Certificate Authority for {user_email}'s Organization"),
ips: vec![],
subnets: vec![],
groups: vec![],
not_before: SystemTime::now() - Duration::hours(24),
not_after: expires,
public_key: key.verifying_key().as_bytes().clone(),
is_ca: true,
issuer: "".to_string(),
},
signature: vec![],
};
cert.sign(&key).map_err(|e| CryptographyError::CertificateSigningError(e))?;
let pem = cert.serialize_to_pem().map_err(|e| CryptographyError::PemSerializeError(e))?;
let cert_value = serde_json::to_value(cert).map_err(|e| CryptographyError::JsonSerializeError(e))?;
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(SigningCA {
id: randid!(id "ca"),
pem: String::from_utf8(pem).unwrap(),
cert: cert_value,
expires_at: cert.details.not_after.clone(),
organization_id: org_id,
salt: salt.as_slice().to_vec(),
info: aad.to_vec(),
private_key: lockbox,
})
}
pub fn sign_cert_with_ca(ca: &SigningCA, cert: &mut NebulaCertificate, config: &Config) -> Result<(), 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] = ca.salt.try_into().map_err(|_| CryptographyError::InvalidSaltLength)?;
let salt = XNonce::from(salt_u24);
let plaintext = lockbox_key.decrypt(&salt, Payload {
msg: &ca.private_key,
aad: &ca.info,
}).map_err(|_| CryptographyError::DecryptFailed)?;
let key = SigningKey::from_keypair_bytes(&plaintext.try_into().map_err(|_| CryptographyError::InvalidSigningKeyLength)?).map_err(|e| CryptographyError::SignatureError(e))?;
cert.sign(&key).map_err(|e| CryptographyError::CertificateSigningError(e))
}

View File

@ -43,4 +43,5 @@ pub struct ConfigTokens {
pub magic_link_expiry_seconds: u64,
pub session_token_expiry_seconds: u64,
pub auth_token_expiry_seconds: u64,
pub data_encryption_key: String
}

0
trifid-api/src/macros.rs Normal file
View File

View File

@ -24,6 +24,10 @@ pub mod schema;
pub mod id;
pub mod auth;
pub mod email;
#[macro_use]
pub mod macros;
pub mod ca;
pub mod crypt;
#[derive(Clone)]
pub struct AppState {

View File

@ -1,8 +1,9 @@
use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use serde_json::Value;
#[derive(Queryable, Selectable, Insertable, Identifiable, Debug, PartialEq, Clone)]
#[derive(Queryable, Selectable, Insertable, Identifiable, Debug, PartialEq, Clone, Serialize, Deserialize)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
@ -11,7 +12,7 @@ pub struct User {
}
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::magic_links)]
@ -23,7 +24,7 @@ pub struct MagicLink {
}
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::session_tokens)]
@ -35,7 +36,7 @@ pub struct SessionToken {
}
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::totp_authenticators)]
@ -51,7 +52,7 @@ pub struct TotpAuthenticator {
}
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(User))]
#[diesel(table_name = crate::schema::auth_tokens)]
@ -63,7 +64,7 @@ pub struct AuthToken {
}
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(User, foreign_key = owner_id))]
#[diesel(table_name = crate::schema::organizations)]
@ -86,7 +87,7 @@ id -> Varchar,
*/
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, Serialize, Deserialize
)]
#[diesel(belongs_to(Organization))]
#[diesel(table_name = crate::schema::signing_cas)]
@ -101,6 +102,17 @@ pub struct SigningCA {
pub info: Vec<u8>,
pub private_key: Vec<u8>
}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct SigningCANormalized {
pub id: String,
pub pem: String,
pub cert: Value,
pub expires_at: String,
pub organization_id: String,
pub salt: Vec<u8>,
pub info: Vec<u8>,
pub private_key: Vec<u8>
}
/*
id VARCHAR NOT NULL PRIMARY KEY,
@ -113,7 +125,7 @@ id VARCHAR NOT NULL PRIMARY KEY,
*/
#[derive(
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone,
Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone
)]
#[diesel(belongs_to(Organization))]
#[diesel(belongs_to(SigningCA, foreign_key = signing_ca_id))]
@ -128,3 +140,13 @@ pub struct Network {
pub name: String,
pub lighthouses_as_relays: bool
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct NetworkNormalized {
pub id: String,
pub cidr: String,
pub organization_id: String,
pub signing_ca_id: String,
pub created_at: String,
pub name: String,
pub lighthouses_as_relays: bool
}

View File

@ -1,7 +1,14 @@
use std::time::{Duration, SystemTime};
use actix_web::HttpRequest;
use actix_web::web::Json;
use serde::Deserialize;
use crate::AppState;
use actix_web::web::{Data, Json};
use serde::{Deserialize, Serialize};
use crate::{AppState, auth, enforce, randid};
use crate::models::{Network, NetworkNormalized, Organization, SigningCA, User};
use crate::response::JsonAPIResponse;
use diesel::{SelectableHelper, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use crate::ca::create_signing_ca;
use crate::schema::users;
#[derive(Deserialize, Debug)]
pub struct CreateNetworkReq {
@ -9,8 +16,45 @@ pub struct CreateNetworkReq {
pub name: String
}
#[derive(Serialize, Debug)]
pub struct CreateNetworkResp {
pub data: NetworkNormalized
}
pub async fn create_network_req(req: Json<CreateNetworkReq>, state: Data<AppState>, req_info: HttpRequest)
pub async fn create_network_req(req: Json<CreateNetworkReq>, state: Data<AppState>, req_info: HttpRequest) -> JsonAPIResponse<CreateNetworkResp> {
let mut conn = handle_error!(state.pool.get().await);
let auth_info = auth!(req_info, conn);
let (session_token, auth_token) = enforce!(sess auth auth_info);
let user = handle_error!(
users::table
.find(&session_token.user_id)
.first::<User>(&mut conn)
.await
);
let new_org = Organization {
id: randid!(id "org"),
owner_id: user.id.clone(),
name: format!("{}'s Organization", user.email),
};
// create 3 signing CAs, to mimic upstream DN functionality
let ca_oneyear = handle_error!(create_signing_ca(SystemTime::now() + Duration::from_secs(86400 * 365), new_org.id.clone(), user.email.clone(), &state.config));
let ca_twoyears = handle_error!(create_signing_ca(SystemTime::now() + Duration::from_secs(86400 * 365 * 2), new_org.id.clone(), user.email.clone(), &state.config));
let ca_threeyears = handle_error!(create_signing_ca(SystemTime::now() + Duration::from_secs(86400 * 365 * 3), new_org.id.clone(), user.email.clone(), &state.config));
let new_network = Network {
id: randid!(id "net"),
cidr: req.0.cidr.clone(),
organization_id: new_org.id.clone(),
signing_ca_id: "".to_string(),
created_at: SystemTime::now(),
name: "".to_string(),
lighthouses_as_relays: false,
};
todo!()
}