diff --git a/trifid-api/src/codegen/mod.rs b/trifid-api/src/codegen/mod.rs index bc0ff87..354c10a 100644 --- a/trifid-api/src/codegen/mod.rs +++ b/trifid-api/src/codegen/mod.rs @@ -12,17 +12,13 @@ use crate::config::{ NebulaConfigRelay, NebulaConfigTun, CONFIG, }; use crate::crypto::{decrypt_with_nonce, get_cipher_from_config}; -use crate::keystore::keystore_init; use crate::AppState; use ed25519_dalek::SigningKey; use ipnet::Ipv4Net; use log::{error}; use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter}; use serde_yaml::{Mapping, Value}; -use trifid_api_entities::entity::{ - firewall_rule, host, host_config_override, host_static_address, network, organization, - signing_ca, -}; +use trifid_api_entities::entity::{firewall_rule, host, host_config_override, host_static_address, keystore_entry, network, organization, signing_ca}; use trifid_pki::cert::{ deserialize_ed25519_private, deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails, @@ -44,7 +40,7 @@ pub struct CodegenRequiredInfo { } pub async fn generate_config( - _data: &Data, + db: &Data, info: &CodegenRequiredInfo, ) -> Result<(NebulaConfig, NebulaCertificate), Box> { // decode the CA data @@ -90,14 +86,22 @@ pub async fn generate_config( } cas += &String::from_utf8(hex::decode(&info.ca.cert)?)?; - let ks = keystore_init()?; - // blocked hosts let mut blocked_hosts_fingerprints = vec![]; + for host in &info.blocked_hosts { - if let Some(host) = ks.hosts.iter().find(|u| &u.id == host) { - for cert in &host.certs { - blocked_hosts_fingerprints.push(cert.cert.sha256sum()?); + + // check if the host exists + if host::Entity::find().filter(host::Column::Id.eq(host)).one(&db.conn).await?.is_some() { + // pull all of their certs ever and block them + let host_entries = keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(host)).all(&db.conn).await?; + + for entry in &host_entries { + + // decode the cert + let cert = deserialize_nebula_certificate_from_pem(&entry.certificate)?; + + blocked_hosts_fingerprints.push(cert.sha256sum()?); } } } diff --git a/trifid-api/src/keystore.rs b/trifid-api/src/legacy_keystore.rs similarity index 100% rename from trifid-api/src/keystore.rs rename to trifid-api/src/legacy_keystore.rs diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 5996afd..c3923a1 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -26,7 +26,6 @@ use std::time::Duration; use actix_cors::Cors; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; -use crate::keystore::keystore_init; use crate::tokens::random_id_no_id; use trifid_api_migration::{Migrator, MigratorTrait}; @@ -36,11 +35,12 @@ pub mod config; pub mod crypto; pub mod cursor; pub mod error; -pub mod keystore; +//pub mod legacy_keystore; // TODO- Remove pub mod magic_link; pub mod routes; pub mod timers; pub mod tokens; +pub mod response; pub struct AppState { pub conn: DatabaseConnection, @@ -50,10 +50,6 @@ pub struct AppState { async fn main() -> Result<(), Box> { simple_logger::init_with_level(Level::Debug).unwrap(); - info!("Creating keystore..."); - - let _keystore = keystore_init()?; - info!("Connecting to database at {}...", CONFIG.database.url); let mut opt = ConnectOptions::new(CONFIG.database.url.clone()); diff --git a/trifid-api/src/response.rs b/trifid-api/src/response.rs new file mode 100644 index 0000000..3b70d0f --- /dev/null +++ b/trifid-api/src/response.rs @@ -0,0 +1,51 @@ +use std::fmt::{Display, Formatter}; +use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError}; +use actix_web::body::EitherBody; +use actix_web::web::Json; +use log::error; +use sea_orm::DbErr; +use serde::Serialize; +use crate::error::{APIError, APIErrorsResponse}; + +pub struct OkResponse(T); + +#[derive(Debug)] +pub struct ErrResponse(APIErrorsResponse); + +impl Responder for OkResponse { + type Body = T::Body; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + self.0.respond_to(req) + } +} +impl Responder for ErrResponse { + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + Json(self.0).respond_to(req) + } +} + +impl From for ErrResponse { + fn from(value: DbErr) -> Self { + error!("database error: {}", value); + Self { + 0: APIErrorsResponse { errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database query. Please try again later.".to_string(), + path: None, + } + ] }, + } + } +} + +impl Display for ErrResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl ResponseError for ErrResponse {} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/dnclient.rs b/trifid-api/src/routes/v1/dnclient.rs index 75ce2ba..c78ad04 100644 --- a/trifid-api/src/routes/v1/dnclient.rs +++ b/trifid-api/src/routes/v1/dnclient.rs @@ -1,6 +1,4 @@ use crate::codegen::{collect_info, generate_config}; -use crate::keystore::{keystore_flush, keystore_init, KSCert, KSClientKey, KSConfig, KSSigningKey}; -use crate::AppState; use actix_web::web::{Data, Json}; use actix_web::{post, HttpRequest, HttpResponse}; use base64::Engine; @@ -10,10 +8,11 @@ use dnapi_rs::message::{ DoUpdateResponse, EnrollResponse, RequestV1, RequestWrapper, SignedResponse, SignedResponseWrapper, }; -use ed25519_dalek::{Signature, Signer, Verifier, VerifyingKey}; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use log::{error, warn}; use std::clone::Clone; use std::time::{SystemTime, UNIX_EPOCH}; +use ed25519_dalek::ed25519::signature::Keypair; use sea_orm::{ActiveModelTrait, EntityTrait}; use trifid_pki::cert::{deserialize_ed25519_public, deserialize_x25519_public}; use trifid_pki::x25519_dalek::PublicKey; @@ -21,7 +20,8 @@ use trifid_api_entities::entity::{host, keystore_entry, keystore_host}; use crate::error::APIErrorsResponse; use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel}; use sea_orm::ActiveValue::Set; -use trifid_api_entities::entity::prelude::KeystoreHost; +use crate::AppState; +use crate::tokens::random_id; #[post("/v1/dnclient")] pub async fn dnclient( @@ -41,20 +41,6 @@ pub async fn dnclient( let host = &req.host_id; - let mut keystore = match keystore_init() { - Ok(ks) => ks, - Err(e) => { - error!("keystore load error: {}", e); - return HttpResponse::InternalServerError().json(EnrollResponse::Error { - errors: vec![APIError { - code: "ERR_KS_LOAD_ERROR".to_string(), - message: e.to_string(), - path: None, - }], - }); - } - }; - let host_in_ks = match keystore_host::Entity::find().filter(keystore_host::Column::Id.eq(host)).one(&db.conn).await { Ok(maybe_host) => maybe_host, Err(e) => { @@ -119,7 +105,7 @@ pub async fn dnclient( } }; - let key = VerifyingKey::from(keystore_data.client_signing_key.try_into().unwrap()); + let key = VerifyingKey::from_bytes(&keystore_data.client_signing_key.try_into().unwrap()).unwrap(); if !key.verify(&req.message.as_bytes(), &signature).is_ok() { // Be intentionally vague as the message is invalid. @@ -321,26 +307,6 @@ pub async fn dnclient( } }; - let ks = keystore_header; - - ks.certs.push(KSCert { - id: ks.current_cert + 1, - cert, - }); - ks.current_cert += 1; - - ks.config.push(KSConfig { - id: ks.current_config + 1, - config: cfg.clone(), - }); - ks.current_config += 1; - - ks.signing_keys.push(KSSigningKey { - id: ks.current_signing_key + 1, - key: ks.signing_keys[0].key.clone(), - }); - ks.current_signing_key += 1; - let dh_pubkey = match deserialize_x25519_public(&do_update_req.dh_pubkey_pem) { Ok(r) => r, Err(e) => { @@ -365,53 +331,38 @@ pub async fn dnclient( } }; - let dh_pubkey_typed: [u8; 32] = dh_pubkey.try_into().unwrap(); - - ks.client_keys.push(KSClientKey { - id: ks.current_client_key + 1, - dh_pub: PublicKey::from(dh_pubkey_typed), - ed_pub: VerifyingKey::from_bytes(&ed_pubkey.try_into().unwrap()).unwrap(), - }); - ks.current_client_key += 1; - - let host_in_ks = ks.clone(); - - match keystore_flush(&keystore) { - Ok(_) => (), + let cfg_str = match serde_yaml::to_string(&cfg) { + Ok(c_str) => c_str, Err(e) => { - error!("keystore save error: {}", e); + error!("config serialization error: {}", e); return HttpResponse::InternalServerError().json(vec![APIError { - code: "ERR_SAVE_ERR".to_string(), - message: "There was an error saving the keystore.".to_string(), + code: "ERR_CFG_SERIALIZATION".to_string(), + message: "There was an error serializing the new configuration." + .to_string(), path: None, }]); } - } + }; + + let ks_entry_model = keystore_entry::Model { + id: random_id("ksentry"), + host: host.clone(), + counter: counter + 1, + certificate: cert.serialize_to_pem().unwrap(), + client_dh_key: dh_pubkey, + client_signing_key: ed_pubkey, + config: cfg_str.clone(), + signing_key: keystore_data.signing_key.clone() + }; + + let signing_key = SigningKey::from_bytes(&keystore_data.signing_key.try_into().unwrap()); // get the signing key that the client last trusted based on its current config version - // this is their current counter - let signing_key = host_in_ks - .signing_keys - .iter() - .find(|u| u.id == (req.counter as u64)) - .unwrap(); - let msg = DoUpdateResponse { - config: match serde_yaml::to_string(&cfg) { - Ok(c_str) => c_str.as_bytes().to_vec(), - Err(e) => { - error!("config serialization error: {}", e); - return HttpResponse::InternalServerError().json(vec![APIError { - code: "ERR_CFG_SERIALIZATION".to_string(), - message: "There was an error serializing the new configuration." - .to_string(), - path: None, - }]); - } - }, - counter: host_in_ks.current_config as u32, + config: cfg_str.as_bytes().to_vec(), + counter: (counter + 1) as u32, nonce: do_update_req.nonce, - trusted_keys: ed25519_public_keys_to_pem(&[signing_key.key.verifying_key()]), + trusted_keys: ed25519_public_keys_to_pem(&[signing_key.verifying_key()]), }; let msg_bytes = match serde_json::to_vec(&msg) { @@ -430,7 +381,7 @@ pub async fn dnclient( let resp = SignedResponse { version: 1, message: msg_bytes.clone(), - signature: signing_key.key.sign(&msg_bytes).to_vec(), + signature: signing_key.sign(&msg_bytes).to_vec(), }; let resp_w = SignedResponseWrapper { data: resp }; diff --git a/trifid-api/src/routes/v2/enroll.rs b/trifid-api/src/routes/v2/enroll.rs index b9b845e..cc2ca3b 100644 --- a/trifid-api/src/routes/v2/enroll.rs +++ b/trifid-api/src/routes/v2/enroll.rs @@ -1,40 +1,39 @@ use actix_web::web::{Data, Json}; -use actix_web::{post, HttpRequest, HttpResponse}; +use actix_web::{post, HttpRequest, HttpResponse, Responder, ResponseError}; +use base64::Engine; use dnapi_rs::message::{ APIError, EnrollRequest, EnrollResponse, EnrollResponseData, EnrollResponseDataOrg, }; use ed25519_dalek::{SigningKey, VerifyingKey}; use log::{debug, error}; use rand::rngs::OsRng; -use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; use crate::codegen::{collect_info, generate_config}; -use crate::keystore::{ - keystore_flush, keystore_init, KSCert, KSClientKey, KSConfig, KSSigningKey, - KeystoreHostInformation, -}; use crate::AppState; -use trifid_api_entities::entity::host_enrollment_code; +use trifid_api_entities::entity::{host_enrollment_code, keystore_entry, keystore_host}; use trifid_pki::cert::{ deserialize_ed25519_public, deserialize_x25519_public, serialize_ed25519_public, }; -use trifid_pki::x25519_dalek::PublicKey; +use crate::response::ErrResponse; use crate::timers::expired; +use crate::tokens::random_id; #[post("/v2/enroll")] pub async fn enroll( req: Json, _req_info: HttpRequest, db: Data, -) -> HttpResponse { +) -> Result { debug!("{:x?} {:x?}", req.dh_pubkey, req.ed_pubkey); // pull enroll information from the db - let code_info = match host_enrollment_code::Entity::find() + let code_info = host_enrollment_code::Entity::find() .filter(host_enrollment_code::Column::Id.eq(&req.code)) .one(&db.conn) - .await + .await?; + /* { Ok(ci) => ci, Err(e) => { @@ -51,27 +50,29 @@ pub async fn enroll( } }; + */ + let enroll_info = match code_info { Some(ei) => ei, None => { - return HttpResponse::Unauthorized().json(EnrollResponse::Error { + return Ok(HttpResponse::Unauthorized().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_UNAUTHORIZED".to_string(), message: "That code is invalid or has expired.".to_string(), path: None, }], - }); + })); } }; if expired(enroll_info.expires_on as u64) { - return HttpResponse::Unauthorized().json(EnrollResponse::Error { + return Ok(HttpResponse::Unauthorized().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_UNAUTHORIZED".to_string(), message: "That code is invalid or has expired.".to_string(), path: None, }], - }); + })); } // deserialize @@ -79,26 +80,26 @@ pub async fn enroll( Ok(k) => k, Err(e) => { error!("public key deserialization error: {}", e); - return HttpResponse::BadRequest().json(EnrollResponse::Error { + return Ok(HttpResponse::BadRequest().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_BAD_DH_PUB".to_string(), message: "Unable to deserialize the DH public key.".to_string(), path: None, }], - }); + })); } }; let ed_pubkey = match deserialize_ed25519_public(&req.ed_pubkey) { Ok(k) => k, Err(e) => { error!("public key deserialization error: {}", e); - return HttpResponse::BadRequest().json(EnrollResponse::Error { + return Ok(HttpResponse::BadRequest().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_BAD_ED_PUB".to_string(), message: "Unable to deserialize the ED25519 public key.".to_string(), path: None, }], - }); + })); } }; @@ -108,7 +109,7 @@ pub async fn enroll( Ok(_) => (), Err(e) => { error!("database error: {}", e); - return HttpResponse::InternalServerError().json(EnrollResponse::Error { + return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_DB_ERROR".to_string(), message: @@ -116,20 +117,20 @@ pub async fn enroll( .to_string(), path: None, }], - }); + })); } } let info = match collect_info(&db, &enroll_info.host, &dh_pubkey).await { Ok(i) => i, Err(e) => { - return HttpResponse::InternalServerError().json(EnrollResponse::Error { + return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_CFG_GENERATION_ERROR".to_string(), message: e.to_string(), path: None, }], - }); + })); } }; @@ -138,107 +139,74 @@ pub async fn enroll( Ok(cfg) => cfg, Err(e) => { error!("error generating configuration: {}", e); - return HttpResponse::InternalServerError().json(EnrollResponse::Error { + return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { errors: vec![APIError { code: "ERR_CFG_GENERATION_ERROR".to_string(), message: "There was an error generating the host configuration.".to_string(), path: None, }], - }); + })); } }; - let mut ks_clone = match keystore_init() { - Ok(ks) => ks, - Err(e) => { - error!("error loading keystore: {}", e); - return HttpResponse::InternalServerError().json(EnrollResponse::Error { - errors: vec![APIError { - code: "ERR_KS_LOAD_ERROR".to_string(), - message: "There was an error loading the keystore.".to_string(), - path: None, - }], - }); - } - }; + // delete all entries in the keystore for this host - loop { - let host_in_ks = ks_clone.hosts.iter().position(|u| u.id == enroll_info.host); - if let Some(host) = host_in_ks { - ks_clone.hosts.remove(host); - } else { - break; - } + let entries = keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(&enroll_info.host)).all(&db.conn).await?; + + for entry in entries { + entry.delete(&db.conn).await?; } - let dh_pubkey_typed: [u8; 32] = dh_pubkey.clone().try_into().unwrap(); + let host_info = keystore_host::Entity::find().filter(keystore_host::Column::Id.eq(&enroll_info.host)).one(&db.conn).await?; - let host = KeystoreHostInformation { - id: enroll_info.host.clone(), - current_signing_key: 0, - current_client_key: 1, - current_config: 1, - current_cert: 1, - certs: vec![KSCert { id: 1, cert }], - config: vec![KSConfig { - id: 1, - config: cfg.clone(), - }], - signing_keys: vec![KSSigningKey { - id: 0, - key: SigningKey::generate(&mut OsRng), - }], - client_keys: vec![KSClientKey { - id: 1, - dh_pub: PublicKey::from(dh_pubkey_typed), - ed_pub: VerifyingKey::from_bytes(&ed_pubkey.try_into().unwrap()).unwrap(), - }], - }; + if let Some(old_host) = host_info { + old_host.delete(&db.conn).await?; + } - ks_clone.hosts.push(host.clone()); - - match keystore_flush(&ks_clone) { - Ok(_) => (), + let cfg = match serde_yaml::to_string(&cfg) { + Ok(cfg) => cfg, Err(e) => { - error!("keystore save error: {}", e); - return HttpResponse::InternalServerError().json(vec![APIError { - code: "ERR_SAVE_ERR".to_string(), - message: "There was an error saving the keystore.".to_string(), + error!("serialization error: {}", e); + return Ok(HttpResponse::BadRequest().json(vec![APIError { + code: "ERR_ED_INVALID".to_string(), + message: "There was an error deserializing the ED pubkey.".to_string(), path: None, - }]); + }])); } - } + }; - HttpResponse::Ok().json(EnrollResponse::Success { + let cert_bytes = cert.serialize_to_pem().unwrap(); + + let key = SigningKey::generate(&mut OsRng); + + let host_header = keystore_host::Model { + id: enroll_info.host.clone(), + counter: 1 + }; + let entry = keystore_entry::Model { + id: random_id("ksentry"), + host: enroll_info.host.clone(), + counter: 1, + certificate: cert_bytes, + client_dh_key: dh_pubkey, + client_signing_key: ed_pubkey, + config: cfg.clone(), + signing_key: key.to_bytes().to_vec() + }; + + host_header.into_active_model().insert(&db.conn).await?; + entry.into_active_model().insert(&db.conn).await?; + + Ok(HttpResponse::Ok().json(EnrollResponse::Success { data: EnrollResponseData { - config: match serde_yaml::to_string(&cfg) { - Ok(cfg) => cfg.as_bytes().to_vec(), - Err(e) => { - error!("serialization error: {}", e); - return HttpResponse::BadRequest().json(vec![APIError { - code: "ERR_ED_INVALID".to_string(), - message: "There was an error deserializing the ED pubkey.".to_string(), - path: None, - }]); - } - }, + config: cfg.as_bytes().to_vec(), host_id: enroll_info.host.clone(), - counter: host.current_config as u32, - trusted_keys: serialize_ed25519_public( - host.signing_keys - .iter() - .find(|u| u.id == host.current_signing_key) - .unwrap() - .key - .verifying_key() - .as_bytes() - .as_slice(), - ) - .to_vec(), + counter: 1, + trusted_keys: serialize_ed25519_public(&key.verifying_key().to_bytes().to_vec()), organization: EnrollResponseDataOrg { id: info.organization.id.clone(), name: info.organization.name.clone(), }, }, - }) + })) }