This commit is contained in:
c0repwn3r 2023-02-25 23:16:30 -05:00
parent d00df353d8
commit bba39afc6b
Signed by: core
GPG Key ID: FDBF740DADDCEECF
13 changed files with 212 additions and 4 deletions

View File

@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

7
Cargo.lock generated
View File

@ -2205,11 +2205,14 @@ dependencies = [
name = "trifid-api" name = "trifid-api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aes-gcm",
"base64 0.21.0", "base64 0.21.0",
"chrono", "chrono",
"dotenvy", "dotenvy",
"hex",
"log", "log",
"paste", "paste",
"rand",
"rocket", "rocket",
"serde", "serde",
"sqlx", "sqlx",
@ -2221,6 +2224,10 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "trifid-pki"
version = "0.1.0"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.4" version = "0.2.4"

View File

@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"trifid-api", "trifid-api",
"tfclient" "tfclient",
"trifid-pki"
] ]

View File

@ -0,0 +1,2 @@
[build]
rustc-wrapper = "sccache"

View File

@ -19,4 +19,7 @@ totp-rs = { version = "4.2.0", features = ["qr", "otpauth", "gen_secret"]}
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]} uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]}
url = { version = "2.3.1", features = ["serde"] } url = { version = "2.3.1", features = ["serde"] }
urlencoding = "2.1.2" urlencoding = "2.1.2"
chrono = "0.4.23" chrono = "0.4.23"
aes-gcm = "0.10.1"
hex = "0.4.3"
rand = "0.8.5"

View File

@ -0,0 +1,7 @@
CREATE TABLE organization_authorized_users (
id SERIAL NOT NULL PRIMARY KEY,
user_id SERIAL NOT NULL REFERENCES users(id),
org_id SERIAL NOT NULL REFERENCES organizations(id)
);
CREATE INDEX idx_organization_authorized_users_user ON organization_authorized_users(user_id);
CREATE INDEX idx_organization_authorized_users_org ON organization_authorized_users(org_id);

21
trifid-api/src/crypto.rs Normal file
View File

@ -0,0 +1,21 @@
use std::error::Error;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use aes_gcm::aead::{Aead, Payload};
use crate::config::TFConfig;
pub fn get_cipher_from_config(config: &TFConfig) -> Result<Aes256Gcm, Box<dyn Error>> {
let key_slice = hex::decode(&config.data_key)?;
Ok(Aes256Gcm::new_from_slice(&key_slice)?)
}
pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
let nonce = Nonce::from_slice(&nonce);
let ciphertext = cipher.encrypt(nonce, plaintext)?;
Ok(ciphertext)
}
pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
let nonce = Nonce::from_slice(&nonce);
let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?;
Ok(plaintext)
}

View File

@ -19,6 +19,8 @@ pub mod config;
pub mod tokens; pub mod tokens;
pub mod routes; pub mod routes;
pub mod auth; pub mod auth;
pub mod crypto;
pub mod org;
static MIGRATOR: Migrator = sqlx::migrate!(); static MIGRATOR: Migrator = sqlx::migrate!();
@ -118,7 +120,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
crate::routes::v1::auth::check_session::options, crate::routes::v1::auth::check_session::options,
crate::routes::v1::auth::check_session::options_auth, crate::routes::v1::auth::check_session::options_auth,
crate::routes::v2::whoami::whoami_request, crate::routes::v2::whoami::whoami_request,
crate::routes::v2::whoami::options crate::routes::v2::whoami::options,
crate::routes::v1::organization::options,
crate::routes::v1::organization::orgidoptions,
crate::routes::v1::organization::orginfo_req,
crate::routes::v1::organization::orglist_req,
crate::routes::v1::organization::create_org
]) ])
.register("/", catchers![ .register("/", catchers![
crate::routes::handler_400, crate::routes::handler_400,

50
trifid-api/src/org.rs Normal file
View File

@ -0,0 +1,50 @@
use std::error::Error;
use rocket::form::validate::Contains;
use sqlx::PgPool;
pub async fn get_org_by_owner_id(user: i32, db: &PgPool) -> Result<Option<i32>, Box<dyn Error>> {
Ok(match sqlx::query!("SELECT id FROM organizations WHERE owner = $1", user).fetch_optional(db).await? {
Some(r) => Some(r.id),
None => None
})
}
pub async fn get_orgs_by_assoc_id(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
let res: Vec<_> = sqlx::query!("SELECT org_id FROM organization_authorized_users WHERE user_id = $1", user).fetch_all(db).await?;
let mut ret = vec![];
for i in res {
ret.push(i.org_id);
}
Ok(ret)
}
pub async fn get_users_by_assoc_org(org: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
let res: Vec<_> = sqlx::query!("SELECT user_id FROM organization_authorized_users WHERE org_id = $1", org).fetch_all(db).await?;
let mut ret = vec![];
for i in res {
ret.push(i.org_id);
}
Ok(ret)
}
pub async fn get_associated_orgs(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
let mut assoc_orgs = vec![];
if let Some(owned_org) = get_org_by_owner_id(user, db).await? {
assoc_orgs.push(owned_org);
}
assoc_orgs.append(&mut get_orgs_by_assoc_id(user, db).await?);
Ok(assoc_orgs)
}
pub async fn user_has_org_assoc(user: i32, org: i32, db: &PgPool) -> Result<bool, Box<dyn Error>> {
Ok(get_associated_orgs(user, db).await?.contains(org))
}

View File

@ -2,4 +2,5 @@ pub mod auth;
pub mod signup; pub mod signup;
pub mod totp_authenticators; pub mod totp_authenticators;
pub mod verify_totp_authenticator; pub mod verify_totp_authenticator;
pub mod organization;

View File

@ -0,0 +1,84 @@
use std::slice::from_raw_parts_mut;
use rocket::{get, post, options, State};
use rocket::http::{ContentType, Status};
use rocket::serde::json::Json;
use serde::{Serialize, Deserialize};
use sqlx::PgPool;
use crate::auth::TOTPAuthenticatedUserInfo;
use crate::config::TFConfig;
use crate::org::{get_associated_orgs, get_org_by_owner_id, get_users_by_assoc_org, user_has_org_assoc};
#[options("/v1/orgs")]
pub fn options() -> &'static str {
""
}
#[options("/v1/org/<_id>")]
pub fn orgidoptions(_id: i32) -> &'static str {
""
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct OrglistStruct {
pub org_ids: Vec<i32>
}
#[get("/v1/orgs")]
pub async fn orglist_req(user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrglistStruct>), (Status, String)> {
// this endpoint lists the associated organizations this user has access to
let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await {
Ok(r) => r,
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query")))
};
Ok((ContentType::JSON, Json(OrglistStruct {
org_ids: associated_orgs
})))
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct OrginfoStruct {
pub org_id: i32,
pub owner_id: i32,
pub ca_crt: String,
pub authorized_users: Vec<i32>
}
#[get("/v1/org/<orgid>")]
pub async fn orginfo_req(orgid: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
if !user_has_org_assoc(orgid, user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
}
let org = sqlx::query!("SELECT id, owner, ca_crt FROM organizations WHERE id = $1", orgid).fetch_one(orgid).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
let authorized_users = get_users_by_assoc_org(orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
Ok((ContentType::JSON, Json(
OrginfoStruct {
org_id: orgid,
owner_id: org.owner,
ca_crt: org.ca_crt,
authorized_users,
}
)))
}
#[post("/v1/org")]
pub async fn create_org(user: TOTPAuthenticatedUserInfo, db: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
if get_org_by_owner_id(user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?.is_some() {
return Err((Status::Conflict, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_USER_OWNS_ORG", "a user can only own one organization at a time")))
}
// we need to generate a keypair and CA certificate for this new org
Ok((ContentType::JSON, Json(
OrginfoStruct {
org_id: orgid,
owner_id: org.owner,
ca_crt: org.ca_crt,
authorized_users,
}
)))
}

9
trifid-pki/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "trifid-pki"
version = "0.1.0"
edition = "2021"
description = "A rust implementation of the Nebula PKI system"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

14
trifid-pki/src/lib.rs Normal file
View File

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}