First version of basic self-CA

This commit is contained in:
Evie Viau-Chow-Stuart 2022-09-14 09:03:43 -04:00
parent 23c040930f
commit 8c4921f797
Signed by: evie
GPG key ID: 928652CDFCEC8099
13 changed files with 419 additions and 12 deletions

2
.gitignore vendored
View file

@ -4,6 +4,8 @@
# Private files # Private files
.env .env
private-key.pem
private-bytes
# Misc # Misc
regexes.yaml regexes.yaml

62
Cargo.lock generated
View file

@ -17,6 +17,16 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "aead"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]] [[package]]
name = "aes" name = "aes"
version = "0.7.5" version = "0.7.5"
@ -35,7 +45,7 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [ dependencies = [
"aead", "aead 0.4.3",
"aes", "aes",
"cipher 0.3.0", "cipher 0.3.0",
"ctr", "ctr",
@ -549,6 +559,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
dependencies = [
"cfg-if",
"cipher 0.4.3",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead 0.5.1",
"chacha20",
"cipher 0.4.3",
"poly1305",
"zeroize",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.22" version = "0.4.22"
@ -582,6 +616,7 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
dependencies = [ dependencies = [
"crypto-common", "crypto-common",
"inout", "inout",
"zeroize",
] ]
[[package]] [[package]]
@ -727,6 +762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core",
"typenum", "typenum",
] ]
@ -896,6 +932,7 @@ dependencies = [
"axum", "axum",
"base64ct", "base64ct",
"blake3", "blake3",
"chacha20poly1305",
"chrono", "chrono",
"dotenv", "dotenv",
"futures", "futures",
@ -1966,6 +2003,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[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 0.5.0",
]
[[package]] [[package]]
name = "polyval" name = "polyval"
version = "0.5.3" version = "0.5.3"
@ -1975,7 +2023,7 @@ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"opaque-debug", "opaque-debug",
"universal-hash", "universal-hash 0.4.1",
] ]
[[package]] [[package]]
@ -3150,6 +3198,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"

View file

@ -22,12 +22,12 @@ ulid = "1.0.0"
axum = "0.6.0-rc.1" axum = "0.6.0-rc.1"
tower = "0.4.13" tower = "0.4.13"
chacha20poly1305 = "0.10.1"
argon2 = "0.4.1"
blake3 = "1.3.1" blake3 = "1.3.1"
base64ct = { version = "1.5.2", features = ["alloc"] } base64ct = { version = "1.5.2", features = ["alloc"] }
rand = "0.8.5" rand = "0.8.5"
argon2 = "0.4.1"
tokio = { version = "1.21.0", features = ["full"] } tokio = { version = "1.21.0", features = ["full"] }
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
@ -47,4 +47,7 @@ user-agent-parser = "0.3.3"
lapin = "2.1.1" lapin = "2.1.1"
picky = "7.0.0-rc.3" picky = "7.0.0-rc.3"
[profile.dev.package.num-bigint-dig]
opt-level = 3

View file

@ -23,7 +23,8 @@ For more information, visit https://driptorch.net/
| DATABASE_URL | PostgreSQL database connection URL | Y | | DATABASE_URL | PostgreSQL database connection URL | Y |
| AMQP_ADDR | Message queue (RabbitMQ) connection URL | Y | | AMQP_ADDR | Message queue (RabbitMQ) connection URL | Y |
| UAP_REGEXES | Path to the [BrowserScope UA regex YAML](https://github.com/ua-parser/uap-core/blob/master/regexes.yaml) | N | | UAP_REGEXES | Path to the [BrowserScope UA regex YAML](https://github.com/ua-parser/uap-core/blob/master/regexes.yaml) | N |
| RSA_KEY | Path to the RSA private key used to create certificates !!! KEEP THIS SAFE !!! SERIOUSLY !!! | Y | | RSA_KEY | Path to the RSA private key used to create certificates !!! KEEP THIS SAFE | Y |
| XCC20_KEY | Path to the XChaCha20-Poly1305 key used to encrypt private keys !!! KEEP THIS SAFE | Y |
--- ---

View file

@ -29,6 +29,11 @@ impl MigrationTrait for Migration {
.binary() .binary()
.not_null() .not_null()
) )
.col(ColumnDef::new(Certificate::Nonce)
.binary()
.not_null()
.unique_key()
)
.col(ColumnDef::new(Certificate::CertType) .col(ColumnDef::new(Certificate::CertType)
.string() .string()
.not_null() .not_null()
@ -52,5 +57,6 @@ pub enum Certificate {
Id, Id,
Data, Data,
Key, Key,
Nonce,
CertType CertType
} }

76
src/cert/generate.rs Normal file
View file

@ -0,0 +1,76 @@
use std::fmt;
use std::fmt::Formatter;
use picky::x509::{Cert, KeyIdGenMethod};
use picky::x509::certificate::{CertError, CertificateBuilder};
use picky::x509::date::UtcDate;
use chrono::prelude::*;
use picky::hash::HashAlgorithm;
use picky::key::PrivateKey;
use picky::signature::SignatureAlgorithm;
use picky::x509::name::DirectoryName;
pub enum InterTarget {
CLIENT,
PROXY
}
impl fmt::Display for InterTarget {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
InterTarget::CLIENT => write!(f, "CLIENT"),
InterTarget::PROXY => write!(f, "PROXY")
}
}
}
pub async fn generate_root_cert(key: &PrivateKey) -> Result<Cert, CertError> {
let current_date: DateTime<Utc> = Utc::now();
CertificateBuilder::new()
.validity(
UtcDate::ymd(
current_date.year() as u16,
current_date.month() as u8,
current_date.day() as u8
).unwrap(),
UtcDate::ymd(
(current_date.year() + 20) as u16,
current_date.month() as u8,
current_date.day() as u8
).unwrap()
)
.self_signed(DirectoryName::new_common_name("Driptorch"), key)
.ca(true)
.signature_hash_type(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512))
.key_id_gen_method(KeyIdGenMethod::SPKFullDER(HashAlgorithm::SHA2_512))
.build()
}
pub async fn generate_inter_cert(key: &PrivateKey, target: InterTarget, root: (&Cert, &PrivateKey)) -> Result<Cert, CertError> {
let current_date: DateTime<Utc> = Utc::now();
CertificateBuilder::new()
.validity(
UtcDate::ymd(
current_date.year() as u16,
current_date.month() as u8,
current_date.day() as u8
).unwrap(),
UtcDate::ymd(
(current_date.year() + 5) as u16,
current_date.month() as u8,
current_date.day() as u8
).unwrap()
)
.issuer_cert(&root.0, &root.1)
.ca(true)
.signature_hash_type(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512))
.key_id_gen_method(KeyIdGenMethod::SPKFullDER(HashAlgorithm::SHA2_512))
.pathlen(0)
.subject(
DirectoryName::new_common_name(format!("Driptorch {}", target.to_string())),
key.to_public_key()
)
.build()
}

39
src/cert/mod.rs Normal file
View file

@ -0,0 +1,39 @@
use std::{env, fmt, fs};
use std::fmt::Formatter;
use std::path::Path;
use chacha20poly1305::{AeadCore, KeyInit, XChaCha20Poly1305};
use chacha20poly1305::aead::{Aead, OsRng};
pub mod generate;
pub enum Types {
ROOT,
CLIENTINTER,
PROXYINTER,
CLIENTLEAF,
PROXYLEAF
}
impl fmt::Display for Types {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Types::ROOT => write!(f, "ROOT"),
Types::CLIENTINTER => write!(f, "CLIENTINTER"),
Types::PROXYINTER => write!(f, "PROXYINTER"),
Types::CLIENTLEAF => write!(f, "CLIENTLEAF"),
Types::PROXYLEAF => write!(f, "PROXYLEAF")
}
}
}
pub async fn encrypt_priv_key(key: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
let cipher = XChaCha20Poly1305::new_from_slice(
fs::read(
Path::new(&env::var("XCC20_KEY").expect("XCC20_KEY must be set!"))
).expect("Failed to load the XCC20 key!").as_slice()
).expect("Error creating chacha20 cipher!");
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
(nonce.to_vec(), cipher.encrypt(&nonce, key.as_slice()).expect("Failed to encrypt private key!"))
}

View file

@ -0,0 +1,37 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.2
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "certificate")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub data: Vec<u8>,
pub key: Vec<u8>,
#[sea_orm(unique)]
pub nonce: Vec<u8>,
pub cert_type: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::proxy::Entity")]
Proxy,
#[sea_orm(has_many = "super::client::Entity")]
Client,
}
impl Related<super::proxy::Entity> for Entity {
fn to() -> RelationDef {
Relation::Proxy.def()
}
}
impl Related<super::client::Entity> for Entity {
fn to() -> RelationDef {
Relation::Client.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -15,14 +15,24 @@ pub struct Model {
pub dns: bool, pub dns: bool,
pub proxy: bool, pub proxy: bool,
pub health: String, pub health: String,
pub certificate: String,
} }
#[derive(Copy, Clone, Debug, EnumIter)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {
#[sea_orm(
belongs_to = "super::certificate::Entity",
from = "Column::Certificate",
to = "super::certificate::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
Certificate,
}
impl RelationTrait for Relation { impl Related<super::certificate::Entity> for Entity {
fn def(&self) -> RelationDef { fn to() -> RelationDef {
panic!("No RelationDef") Relation::Certificate.def()
} }
} }

View file

@ -2,6 +2,7 @@
pub mod prelude; pub mod prelude;
pub mod certificate;
pub mod client; pub mod client;
pub mod proxy; pub mod proxy;
pub mod record; pub mod record;

View file

@ -1,5 +1,6 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.2 //! SeaORM Entity. Generated by sea-orm-codegen 0.9.2
pub use super::certificate::Entity as Certificate;
pub use super::client::Entity as Client; pub use super::client::Entity as Client;
pub use super::proxy::Entity as Proxy; pub use super::proxy::Entity as Proxy;
pub use super::record::Entity as Record; pub use super::record::Entity as Record;

View file

@ -10,10 +10,19 @@ pub struct Model {
pub record: String, pub record: String,
pub port: i32, pub port: i32,
pub active: bool, pub active: bool,
pub certificate: String,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation { pub enum Relation {
#[sea_orm(
belongs_to = "super::certificate::Entity",
from = "Column::Certificate",
to = "super::certificate::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
Certificate,
#[sea_orm( #[sea_orm(
belongs_to = "super::record::Entity", belongs_to = "super::record::Entity",
from = "Column::Record", from = "Column::Record",
@ -24,6 +33,12 @@ pub enum Relation {
Record, Record,
} }
impl Related<super::certificate::Entity> for Entity {
fn to() -> RelationDef {
Relation::Certificate.def()
}
}
impl Related<super::record::Entity> for Entity { impl Related<super::record::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::Record.def() Relation::Record.def()

View file

@ -10,15 +10,24 @@ use axum::routing::{delete, get, post};
use dotenv::dotenv; use dotenv::dotenv;
use lapin::ConnectionProperties; use lapin::ConnectionProperties;
use picky::key::PrivateKey; use picky::key::PrivateKey;
use sea_orm::{ConnectOptions, Database}; use picky::x509::Cert;
use sea_orm::{ActiveValue, ColumnTrait, ConnectOptions, Database, EntityTrait, QueryFilter};
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use ulid::Ulid;
use crate::cert::encrypt_priv_key;
use crate::cert::generate::{generate_inter_cert, generate_root_cert};
use crate::cert::generate::InterTarget::{CLIENT, PROXY};
use crate::cert::Types::{CLIENTINTER, PROXYINTER, ROOT};
use crate::certificate::Model;
use crate::entities::certificate;
mod entities; mod entities;
mod dns; mod dns;
mod util; mod util;
mod routes; mod routes;
mod rpc; mod rpc;
mod cert;
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -58,6 +67,9 @@ async fn main() {
if !Path::new(&env::var("RSA_KEY").expect("RSA_KEY must be set! Halting start-up.")).exists() { if !Path::new(&env::var("RSA_KEY").expect("RSA_KEY must be set! Halting start-up.")).exists() {
error!("Please generate an RSA private key for creating certificates!") error!("Please generate an RSA private key for creating certificates!")
} }
if !Path::new(&env::var("XCC20_KEY").expect("XCC20_KEY must be set! Halting start-up.")).exists() {
error!("Please generate a 32 bits of randomness to encrypt private keys!")
}
let root_rsa_key = PrivateKey::from_pem_str( let root_rsa_key = PrivateKey::from_pem_str(
&*fs::read_to_string( &*fs::read_to_string(
@ -78,6 +90,152 @@ async fn main() {
.await .await
.expect("Failed to run migrations! Halting start-up."); .expect("Failed to run migrations! Halting start-up.");
info!("Checking for root cert...");
let root_cert_model: Option<Model> = certificate::Entity::find()
.filter(certificate::Column::CertType.eq(cert::Types::ROOT.to_string()))
.one(&connection)
.await
.expect("Failed to retrieve the root cert from the database! Halting start-up.");
let root_cert: Cert;
match root_cert_model {
None => {
info!("Generating new root cert...");
let new_root_cert = generate_root_cert(&root_rsa_key)
.await
.expect("Failed to generate the root cert from private key! Halting start-up.");
root_cert = new_root_cert.clone();
let root_insert = certificate::Entity::insert(certificate::ActiveModel {
id: ActiveValue::Set(Ulid::new().to_string()),
data: ActiveValue::Set(
new_root_cert.to_der().expect("Failed to convert cert into der!")
),
key: ActiveValue::set(vec![145, 66, 62, 61, 56, 156, 145, 164]),
nonce: ActiveValue::set(vec![145, 71, 62, 66, 56, 156, 145, 164]),
cert_type: ActiveValue::Set(ROOT.to_string())
})
.exec(&connection)
.await
.expect("Failed to insert new root cert into database! Halting start-up.");
info!("Generated new root cert: {}", root_insert.last_insert_id);
}
Some(root_cert_model) => {
info!("Found root cert: {}", root_cert_model.id);
root_cert = Cert::from_der(&root_cert_model.data)
.expect("Failed to decode root cert!");
}
}
info!("Checking for proxy intermediate cert...");
let proxy_inter_model: Option<Model> = certificate::Entity::find()
.filter(certificate::Column::CertType.eq(PROXYINTER.to_string()))
.one(&connection)
.await
.expect("Failed to retrieve the proxy intermediate cert from the database! Halting start-up.");
let proxy_inter_cert: Cert;
match proxy_inter_model {
None => {
info!("Generating proxy intermediate cert...");
// Generate 4096 bit RSA private key
let priv_key = PrivateKey::generate_rsa(4096)
.expect("Failed to generate a key");
let encrypted_priv_key = encrypt_priv_key(priv_key
.clone()
.to_pkcs8()
.expect("Failed to convert generated private key to pkcs8!")
).await;
let new_proxy_inter_cert = generate_inter_cert(&priv_key, PROXY, (&root_cert, &root_rsa_key))
.await
.expect("Failed to generate new proxy intermediate cert");
proxy_inter_cert = new_proxy_inter_cert.clone();
let proxy_inter_insert = certificate::Entity::insert(certificate::ActiveModel {
id: ActiveValue::Set(Ulid::new().to_string()),
data: ActiveValue::Set(
new_proxy_inter_cert.to_der().expect("Failed to convert cert into der!")
),
key: ActiveValue::set(encrypted_priv_key.1),
nonce: ActiveValue::set(encrypted_priv_key.0),
cert_type: ActiveValue::Set(PROXYINTER.to_string())
})
.exec(&connection)
.await
.expect("Failed to insert new proxy intermediate cert into database! Halting start-up.");
info!("Generated new proxy intermediate cert: {}", proxy_inter_insert.last_insert_id)
}
Some(proxy_inter_model) => {
info!("Found proxy intermediate cert: {}", proxy_inter_model.id);
proxy_inter_cert = Cert::from_der(&proxy_inter_model.data)
.expect("Failed to decode proxy intermediate cert!");
}
}
info!("Checking for client intermediate cert...");
let client_inter_model: Option<Model> = certificate::Entity::find()
.filter(certificate::Column::CertType.eq(CLIENTINTER.to_string()))
.one(&connection)
.await
.expect("Failed to retrieve the client intermediate cert from the database! Halting start-up.");
let client_inter_cert: Cert;
match client_inter_model {
None => {
info!("Generating client intermediate cert...");
// Generate 4096 bit RSA private key
let priv_key = PrivateKey::generate_rsa(4096)
.expect("Failed to generate a key");
let encrypted_priv_key = encrypt_priv_key(priv_key
.clone()
.to_pkcs8()
.expect("Failed to convert generated private key to pkcs8!")
).await;
let new_client_inter_cert = generate_inter_cert(&priv_key, CLIENT, (&root_cert, &root_rsa_key))
.await
.expect("Failed to generate new client intermediate cert");
client_inter_cert = new_client_inter_cert.clone();
let client_inter_insert = certificate::Entity::insert(certificate::ActiveModel {
id: ActiveValue::Set(Ulid::new().to_string()),
data: ActiveValue::Set(
new_client_inter_cert.to_der().expect("Failed to convert cert into der!")
),
key: ActiveValue::set(encrypted_priv_key.1),
nonce: ActiveValue::set(encrypted_priv_key.0),
cert_type: ActiveValue::Set(CLIENTINTER.to_string())
})
.exec(&connection)
.await
.expect("Failed to insert new client intermediate cert into database! Halting start-up.");
info!("Generated new client intermediate cert: {}", client_inter_insert.last_insert_id)
}
Some(client_inter_model) => {
info!("Found client intermediate cert: {}", client_inter_model.id);
client_inter_cert = Cert::from_der(&client_inter_model.data)
.expect("Failed to decode client intermediate cert!");
}
}
info!("Connecting to message broker..."); info!("Connecting to message broker...");
let amqp_addr = env::var("AMQP_ADDR") let amqp_addr = env::var("AMQP_ADDR")
.expect("AMQP_ADDR mut be set! Halting start-up."); .expect("AMQP_ADDR mut be set! Halting start-up.");