Compare commits
No commits in common. "9665fda81593fc001cf969fb9d21f27f5b29c034" and "de16535651bfe9cedb395decc8318fb37db2c112" have entirely different histories.
9665fda815
...
de16535651
|
@ -12,13 +12,17 @@ use crate::config::{
|
||||||
NebulaConfigRelay, NebulaConfigTun, CONFIG,
|
NebulaConfigRelay, NebulaConfigTun, CONFIG,
|
||||||
};
|
};
|
||||||
use crate::crypto::{decrypt_with_nonce, get_cipher_from_config};
|
use crate::crypto::{decrypt_with_nonce, get_cipher_from_config};
|
||||||
|
use crate::keystore::keystore_init;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
use ipnet::Ipv4Net;
|
use ipnet::Ipv4Net;
|
||||||
use log::{error};
|
use log::{error};
|
||||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use trifid_api_entities::entity::{firewall_rule, host, host_config_override, host_static_address, keystore_entry, network, organization, signing_ca};
|
use trifid_api_entities::entity::{
|
||||||
|
firewall_rule, host, host_config_override, host_static_address, network, organization,
|
||||||
|
signing_ca,
|
||||||
|
};
|
||||||
use trifid_pki::cert::{
|
use trifid_pki::cert::{
|
||||||
deserialize_ed25519_private, deserialize_nebula_certificate_from_pem, NebulaCertificate,
|
deserialize_ed25519_private, deserialize_nebula_certificate_from_pem, NebulaCertificate,
|
||||||
NebulaCertificateDetails,
|
NebulaCertificateDetails,
|
||||||
|
@ -40,7 +44,7 @@ pub struct CodegenRequiredInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_config(
|
pub async fn generate_config(
|
||||||
db: &Data<AppState>,
|
_data: &Data<AppState>,
|
||||||
info: &CodegenRequiredInfo,
|
info: &CodegenRequiredInfo,
|
||||||
) -> Result<(NebulaConfig, NebulaCertificate), Box<dyn Error>> {
|
) -> Result<(NebulaConfig, NebulaCertificate), Box<dyn Error>> {
|
||||||
// decode the CA data
|
// decode the CA data
|
||||||
|
@ -86,22 +90,14 @@ pub async fn generate_config(
|
||||||
}
|
}
|
||||||
cas += &String::from_utf8(hex::decode(&info.ca.cert)?)?;
|
cas += &String::from_utf8(hex::decode(&info.ca.cert)?)?;
|
||||||
|
|
||||||
|
let ks = keystore_init()?;
|
||||||
|
|
||||||
// blocked hosts
|
// blocked hosts
|
||||||
let mut blocked_hosts_fingerprints = vec![];
|
let mut blocked_hosts_fingerprints = vec![];
|
||||||
|
|
||||||
for host in &info.blocked_hosts {
|
for host in &info.blocked_hosts {
|
||||||
|
if let Some(host) = ks.hosts.iter().find(|u| &u.id == host) {
|
||||||
// check if the host exists
|
for cert in &host.certs {
|
||||||
if host::Entity::find().filter(host::Column::Id.eq(host)).one(&db.conn).await?.is_some() {
|
blocked_hosts_fingerprints.push(cert.cert.sha256sum()?);
|
||||||
// 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()?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ use std::time::Duration;
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
use crate::error::{APIError, APIErrorsResponse};
|
use crate::error::{APIError, APIErrorsResponse};
|
||||||
|
use crate::keystore::keystore_init;
|
||||||
use crate::tokens::random_id_no_id;
|
use crate::tokens::random_id_no_id;
|
||||||
use trifid_api_migration::{Migrator, MigratorTrait};
|
use trifid_api_migration::{Migrator, MigratorTrait};
|
||||||
|
|
||||||
|
@ -35,12 +36,11 @@ pub mod config;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
//pub mod legacy_keystore; // TODO- Remove
|
pub mod keystore;
|
||||||
pub mod magic_link;
|
pub mod magic_link;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod timers;
|
pub mod timers;
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod response;
|
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub conn: DatabaseConnection,
|
pub conn: DatabaseConnection,
|
||||||
|
@ -50,6 +50,10 @@ pub struct AppState {
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
simple_logger::init_with_level(Level::Debug).unwrap();
|
simple_logger::init_with_level(Level::Debug).unwrap();
|
||||||
|
|
||||||
|
info!("Creating keystore...");
|
||||||
|
|
||||||
|
let _keystore = keystore_init()?;
|
||||||
|
|
||||||
info!("Connecting to database at {}...", CONFIG.database.url);
|
info!("Connecting to database at {}...", CONFIG.database.url);
|
||||||
|
|
||||||
let mut opt = ConnectOptions::new(CONFIG.database.url.clone());
|
let mut opt = ConnectOptions::new(CONFIG.database.url.clone());
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
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 crate::error::{APIError, APIErrorsResponse};
|
|
||||||
|
|
||||||
pub struct OkResponse<T: Responder>(T);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ErrResponse(APIErrorsResponse);
|
|
||||||
|
|
||||||
impl<T: Responder> Responder for OkResponse<T> {
|
|
||||||
type Body = T::Body;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
|
||||||
self.0.respond_to(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Responder for ErrResponse {
|
|
||||||
type Body = EitherBody<String>;
|
|
||||||
|
|
||||||
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
|
|
||||||
Json(self.0).respond_to(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DbErr> for ErrResponse {
|
|
||||||
fn from(value: DbErr) -> Self {
|
|
||||||
error!("database error: {}", value);
|
|
||||||
Self(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 {}
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::codegen::{collect_info, generate_config};
|
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::web::{Data, Json};
|
||||||
use actix_web::{post, HttpRequest, HttpResponse};
|
use actix_web::{post, HttpRequest, HttpResponse};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
@ -8,18 +10,18 @@ use dnapi_rs::message::{
|
||||||
DoUpdateResponse, EnrollResponse, RequestV1, RequestWrapper, SignedResponse,
|
DoUpdateResponse, EnrollResponse, RequestV1, RequestWrapper, SignedResponse,
|
||||||
SignedResponseWrapper,
|
SignedResponseWrapper,
|
||||||
};
|
};
|
||||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
use ed25519_dalek::{Signature, Signer, Verifier, VerifyingKey};
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use sea_orm::{ActiveModelTrait, EntityTrait};
|
use sea_orm::{ActiveModelTrait, EntityTrait};
|
||||||
use trifid_pki::cert::{deserialize_ed25519_public, deserialize_x25519_public};
|
use trifid_pki::cert::{deserialize_ed25519_public, deserialize_x25519_public};
|
||||||
|
use trifid_pki::x25519_dalek::PublicKey;
|
||||||
use trifid_api_entities::entity::{host, keystore_entry, keystore_host};
|
use trifid_api_entities::entity::{host, keystore_entry, keystore_host};
|
||||||
use crate::error::APIErrorsResponse;
|
use crate::error::APIErrorsResponse;
|
||||||
use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel};
|
use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use crate::AppState;
|
use trifid_api_entities::entity::prelude::KeystoreHost;
|
||||||
use crate::tokens::random_id;
|
use crate::tokens::random_id;
|
||||||
|
|
||||||
#[post("/v1/dnclient")]
|
#[post("/v1/dnclient")]
|
||||||
|
@ -40,9 +42,23 @@ pub async fn dnclient(
|
||||||
|
|
||||||
let host = &req.host_id;
|
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 {
|
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,
|
Ok(maybe_host) => maybe_host,
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
return HttpResponse::InternalServerError().json(vec![APIError {
|
return HttpResponse::InternalServerError().json(vec![APIError {
|
||||||
code: "ERR_DB_ERROR".to_string(),
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
message: "There was an error finding the keys for your host, please contact your administrator".to_string(),
|
message: "There was an error finding the keys for your host, please contact your administrator".to_string(),
|
||||||
|
@ -69,7 +85,7 @@ pub async fn dnclient(
|
||||||
|
|
||||||
let key_info = match keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(&keystore_header.id)).one(&db.conn).await {
|
let key_info = match keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(&keystore_header.id)).one(&db.conn).await {
|
||||||
Ok(maybe_keys) => maybe_keys,
|
Ok(maybe_keys) => maybe_keys,
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
return HttpResponse::InternalServerError().json(vec![APIError {
|
return HttpResponse::InternalServerError().json(vec![APIError {
|
||||||
code: "ERR_DB_ERROR".to_string(),
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
message: "There was an error finding the keys for your host, please contact your administrator".to_string(),
|
message: "There was an error finding the keys for your host, please contact your administrator".to_string(),
|
||||||
|
@ -104,9 +120,9 @@ pub async fn dnclient(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = VerifyingKey::from_bytes(&keystore_data.client_signing_key.try_into().unwrap()).unwrap();
|
let key = VerifyingKey::from(keystore_data.client_signing_key.try_into().unwrap());
|
||||||
|
|
||||||
if key.verify(req.message.as_bytes(), &signature).is_err() {
|
if !key.verify(&req.message.as_bytes(), &signature).is_ok() {
|
||||||
// Be intentionally vague as the message is invalid.
|
// Be intentionally vague as the message is invalid.
|
||||||
warn!("! invalid signature from {}", host);
|
warn!("! invalid signature from {}", host);
|
||||||
return HttpResponse::Unauthorized().json(vec![APIError {
|
return HttpResponse::Unauthorized().json(vec![APIError {
|
||||||
|
@ -306,6 +322,12 @@ pub async fn dnclient(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//// START KEYSTORE ENTRY - THIS IS THE BUGGY ZONE ////
|
||||||
|
|
||||||
|
let ks = keystore_header;
|
||||||
|
|
||||||
|
// make a new keystore entity
|
||||||
|
|
||||||
let dh_pubkey = match deserialize_x25519_public(&do_update_req.dh_pubkey_pem) {
|
let dh_pubkey = match deserialize_x25519_public(&do_update_req.dh_pubkey_pem) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -330,7 +352,7 @@ pub async fn dnclient(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cfg_str = match serde_yaml::to_string(&cfg) {
|
let config_str = match serde_yaml::to_string(&cfg) {
|
||||||
Ok(c_str) => c_str,
|
Ok(c_str) => c_str,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("config serialization error: {}", e);
|
error!("config serialization error: {}", e);
|
||||||
|
@ -343,25 +365,31 @@ pub async fn dnclient(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ks_entry_model = keystore_entry::Model {
|
let new_ks_entry = keystore_entry::Model {
|
||||||
id: random_id("ksentry"),
|
id: random_id("ksentry"),
|
||||||
host: host.clone(),
|
host: keystore_header.id.clone(),
|
||||||
counter: counter + 1,
|
counter: counter + 1,
|
||||||
certificate: cert.serialize_to_pem().unwrap(),
|
signing_key: keystore_data.signing_key.clone(), // TODO: Rotate keys
|
||||||
client_dh_key: dh_pubkey,
|
|
||||||
client_signing_key: ed_pubkey,
|
client_signing_key: ed_pubkey,
|
||||||
config: cfg_str.clone(),
|
client_dh_key: dh_pubkey,
|
||||||
signing_key: keystore_data.signing_key.clone()
|
config: config_str.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let signing_key = SigningKey::from_bytes(&keystore_data.signing_key.try_into().unwrap());
|
//// THIS IS THE END OF THE KEYSTORE ADDING BUGGY AREA ////
|
||||||
|
|
||||||
// get the signing key that the client last trusted based on its current config version
|
// 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 {
|
let msg = DoUpdateResponse {
|
||||||
config: cfg_str.as_bytes().to_vec(),
|
config: config_str.as_bytes().to_vec(),
|
||||||
counter: (counter + 1) as u32,
|
counter: counter as u32,
|
||||||
nonce: do_update_req.nonce,
|
nonce: do_update_req.nonce,
|
||||||
trusted_keys: ed25519_public_keys_to_pem(&[signing_key.verifying_key()]),
|
trusted_keys: ed25519_public_keys_to_pem(&[signing_key.key.verifying_key()]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg_bytes = match serde_json::to_vec(&msg) {
|
let msg_bytes = match serde_json::to_vec(&msg) {
|
||||||
|
@ -380,7 +408,7 @@ pub async fn dnclient(
|
||||||
let resp = SignedResponse {
|
let resp = SignedResponse {
|
||||||
version: 1,
|
version: 1,
|
||||||
message: msg_bytes.clone(),
|
message: msg_bytes.clone(),
|
||||||
signature: signing_key.sign(&msg_bytes).to_vec(),
|
signature: signing_key.key.sign(&msg_bytes).to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let resp_w = SignedResponseWrapper { data: resp };
|
let resp_w = SignedResponseWrapper { data: resp };
|
||||||
|
|
|
@ -1,39 +1,40 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use actix_web::{post, HttpRequest, HttpResponse, Responder};
|
use actix_web::{post, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
use dnapi_rs::message::{
|
use dnapi_rs::message::{
|
||||||
APIError, EnrollRequest, EnrollResponse, EnrollResponseData, EnrollResponseDataOrg,
|
APIError, EnrollRequest, EnrollResponse, EnrollResponseData, EnrollResponseDataOrg,
|
||||||
};
|
};
|
||||||
use ed25519_dalek::{SigningKey};
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter};
|
use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter};
|
||||||
|
|
||||||
use crate::codegen::{collect_info, generate_config};
|
use crate::codegen::{collect_info, generate_config};
|
||||||
|
use crate::keystore::{
|
||||||
|
keystore_flush, keystore_init, KSCert, KSClientKey, KSConfig, KSSigningKey,
|
||||||
|
KeystoreHostInformation,
|
||||||
|
};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use trifid_api_entities::entity::{host_enrollment_code, keystore_entry, keystore_host};
|
use trifid_api_entities::entity::host_enrollment_code;
|
||||||
use trifid_pki::cert::{
|
use trifid_pki::cert::{
|
||||||
deserialize_ed25519_public, deserialize_x25519_public, serialize_ed25519_public,
|
deserialize_ed25519_public, deserialize_x25519_public, serialize_ed25519_public,
|
||||||
};
|
};
|
||||||
use crate::response::ErrResponse;
|
use trifid_pki::x25519_dalek::PublicKey;
|
||||||
|
|
||||||
use crate::timers::expired;
|
use crate::timers::expired;
|
||||||
use crate::tokens::random_id;
|
|
||||||
|
|
||||||
#[post("/v2/enroll")]
|
#[post("/v2/enroll")]
|
||||||
pub async fn enroll(
|
pub async fn enroll(
|
||||||
req: Json<EnrollRequest>,
|
req: Json<EnrollRequest>,
|
||||||
_req_info: HttpRequest,
|
_req_info: HttpRequest,
|
||||||
db: Data<AppState>,
|
db: Data<AppState>,
|
||||||
) -> Result<impl Responder, ErrResponse> {
|
) -> HttpResponse {
|
||||||
debug!("{:x?} {:x?}", req.dh_pubkey, req.ed_pubkey);
|
debug!("{:x?} {:x?}", req.dh_pubkey, req.ed_pubkey);
|
||||||
|
|
||||||
// pull enroll information from the db
|
// pull enroll information from the db
|
||||||
let code_info = host_enrollment_code::Entity::find()
|
let code_info = match host_enrollment_code::Entity::find()
|
||||||
.filter(host_enrollment_code::Column::Id.eq(&req.code))
|
.filter(host_enrollment_code::Column::Id.eq(&req.code))
|
||||||
.one(&db.conn)
|
.one(&db.conn)
|
||||||
.await?;
|
.await
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
Ok(ci) => ci,
|
Ok(ci) => ci,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -50,29 +51,27 @@ pub async fn enroll(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
let enroll_info = match code_info {
|
let enroll_info = match code_info {
|
||||||
Some(ei) => ei,
|
Some(ei) => ei,
|
||||||
None => {
|
None => {
|
||||||
return Ok(HttpResponse::Unauthorized().json(EnrollResponse::Error {
|
return HttpResponse::Unauthorized().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_UNAUTHORIZED".to_string(),
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
message: "That code is invalid or has expired.".to_string(),
|
message: "That code is invalid or has expired.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if expired(enroll_info.expires_on as u64) {
|
if expired(enroll_info.expires_on as u64) {
|
||||||
return Ok(HttpResponse::Unauthorized().json(EnrollResponse::Error {
|
return HttpResponse::Unauthorized().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_UNAUTHORIZED".to_string(),
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
message: "That code is invalid or has expired.".to_string(),
|
message: "That code is invalid or has expired.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// deserialize
|
// deserialize
|
||||||
|
@ -80,26 +79,26 @@ pub async fn enroll(
|
||||||
Ok(k) => k,
|
Ok(k) => k,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("public key deserialization error: {}", e);
|
error!("public key deserialization error: {}", e);
|
||||||
return Ok(HttpResponse::BadRequest().json(EnrollResponse::Error {
|
return HttpResponse::BadRequest().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_BAD_DH_PUB".to_string(),
|
code: "ERR_BAD_DH_PUB".to_string(),
|
||||||
message: "Unable to deserialize the DH public key.".to_string(),
|
message: "Unable to deserialize the DH public key.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ed_pubkey = match deserialize_ed25519_public(&req.ed_pubkey) {
|
let ed_pubkey = match deserialize_ed25519_public(&req.ed_pubkey) {
|
||||||
Ok(k) => k,
|
Ok(k) => k,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("public key deserialization error: {}", e);
|
error!("public key deserialization error: {}", e);
|
||||||
return Ok(HttpResponse::BadRequest().json(EnrollResponse::Error {
|
return HttpResponse::BadRequest().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_BAD_ED_PUB".to_string(),
|
code: "ERR_BAD_ED_PUB".to_string(),
|
||||||
message: "Unable to deserialize the ED25519 public key.".to_string(),
|
message: "Unable to deserialize the ED25519 public key.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,7 +108,7 @@ pub async fn enroll(
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database error: {}", e);
|
error!("database error: {}", e);
|
||||||
return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
return HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_DB_ERROR".to_string(),
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
message:
|
message:
|
||||||
|
@ -117,20 +116,20 @@ pub async fn enroll(
|
||||||
.to_string(),
|
.to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = match collect_info(&db, &enroll_info.host, &dh_pubkey).await {
|
let info = match collect_info(&db, &enroll_info.host, &dh_pubkey).await {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
return HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_CFG_GENERATION_ERROR".to_string(),
|
code: "ERR_CFG_GENERATION_ERROR".to_string(),
|
||||||
message: e.to_string(),
|
message: e.to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -139,74 +138,107 @@ pub async fn enroll(
|
||||||
Ok(cfg) => cfg,
|
Ok(cfg) => cfg,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("error generating configuration: {}", e);
|
error!("error generating configuration: {}", e);
|
||||||
return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
return HttpResponse::InternalServerError().json(EnrollResponse::Error {
|
||||||
errors: vec![APIError {
|
errors: vec![APIError {
|
||||||
code: "ERR_CFG_GENERATION_ERROR".to_string(),
|
code: "ERR_CFG_GENERATION_ERROR".to_string(),
|
||||||
message: "There was an error generating the host configuration.".to_string(),
|
message: "There was an error generating the host configuration.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}],
|
}],
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// delete all entries in the keystore for this host
|
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,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let entries = keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(&enroll_info.host)).all(&db.conn).await?;
|
loop {
|
||||||
|
let host_in_ks = ks_clone.hosts.iter().position(|u| u.id == enroll_info.host);
|
||||||
for entry in entries {
|
if let Some(host) = host_in_ks {
|
||||||
entry.delete(&db.conn).await?;
|
ks_clone.hosts.remove(host);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_info = keystore_host::Entity::find().filter(keystore_host::Column::Id.eq(&enroll_info.host)).one(&db.conn).await?;
|
let dh_pubkey_typed: [u8; 32] = dh_pubkey.clone().try_into().unwrap();
|
||||||
|
|
||||||
if let Some(old_host) = host_info {
|
let host = KeystoreHostInformation {
|
||||||
old_host.delete(&db.conn).await?;
|
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(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
ks_clone.hosts.push(host.clone());
|
||||||
|
|
||||||
|
match keystore_flush(&ks_clone) {
|
||||||
|
Ok(_) => (),
|
||||||
|
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(),
|
||||||
|
path: None,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cfg = match serde_yaml::to_string(&cfg) {
|
HttpResponse::Ok().json(EnrollResponse::Success {
|
||||||
Ok(cfg) => cfg,
|
data: EnrollResponseData {
|
||||||
|
config: match serde_yaml::to_string(&cfg) {
|
||||||
|
Ok(cfg) => cfg.as_bytes().to_vec(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("serialization error: {}", e);
|
error!("serialization error: {}", e);
|
||||||
return Ok(HttpResponse::BadRequest().json(vec![APIError {
|
return HttpResponse::BadRequest().json(vec![APIError {
|
||||||
code: "ERR_ED_INVALID".to_string(),
|
code: "ERR_ED_INVALID".to_string(),
|
||||||
message: "There was an error deserializing the ED pubkey.".to_string(),
|
message: "There was an error deserializing the ED pubkey.".to_string(),
|
||||||
path: None,
|
path: None,
|
||||||
}]));
|
}]);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
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: cfg.as_bytes().to_vec(),
|
|
||||||
host_id: enroll_info.host.clone(),
|
host_id: enroll_info.host.clone(),
|
||||||
counter: 1,
|
counter: host.current_config as u32,
|
||||||
trusted_keys: serialize_ed25519_public(key.verifying_key().to_bytes().as_ref()),
|
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(),
|
||||||
organization: EnrollResponseDataOrg {
|
organization: EnrollResponseDataOrg {
|
||||||
id: info.organization.id.clone(),
|
id: info.organization.id.clone(),
|
||||||
name: info.organization.name.clone(),
|
name: info.organization.name.clone(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue