Compare commits

..

No commits in common. "797c8fd7f20ac2332400861eb5259d8582cb89bf" and "cdd0eacdd8c6b89ed3f2bf8b36aab7ddb502722e" have entirely different histories.

26 changed files with 103 additions and 307 deletions

33
.dev-config.yml Normal file
View File

@ -0,0 +1,33 @@
origin:
store_dir_path: /tmp/domani_dev_env/origin
domain:
store_dir_path: /tmp/domani_dev_env/domain
builtins:
bar:
kind: git
url: a
branch_name: b
public: true
service:
http:
form_method: GET
proxied_domains:
foo:
url: http://127.0.0.1:9000
request_headers:
- name: x-foo
value: BAR
- name: host
value: hi
- name: user-agent
value: ""
gemini:
proxied_domains:
mediocregopher.com:
url: gemini://127.0.0.1:1965
passphrase: foobar
dns_records:
- kind: A
addr: 127.0.0.1
- kind: AAAA
addr: ::1

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/target /target
.cargo .cargo
/result /result
config.yml

View File

@ -153,7 +153,6 @@ In order to open a shell with all necessary tooling (expected rust toolchain
versions, etc...) simply do: versions, etc...) simply do:
``` ```
cp config.yml.tpl config.yml
nix develop nix develop
``` ```

View File

@ -1,14 +0,0 @@
# This is an example configuration file intended for use in development.
origin:
store_dir_path: /tmp/domani_dev_env/origin
domain:
store_dir_path: /tmp/domani_dev_env/domain
service:
passphrase: foobar
primary_domain: localhost
dns_records:
- kind: A
addr: 127.0.0.1
- kind: AAAA
addr: ::1

View File

@ -48,11 +48,8 @@
pkgs.nmap # ncat pkgs.nmap # ncat
]; ];
shellHook = '' shellHook = ''
source $(pwd)/.env.dev
export CARGO_HOME=$(pwd)/.cargo export CARGO_HOME=$(pwd)/.cargo
if [ -f "config.yml" ]; then
export DOMANI_CONFIG_PATH=config.yml
fi
''; '';
} // opensslEnv); } // opensslEnv);
}); });

View File

@ -1,12 +1,10 @@
pub mod acme; pub mod acme;
pub mod checker; pub mod checker;
mod config; mod config;
pub mod gemini;
pub mod manager; pub mod manager;
mod name; mod name;
mod settings; mod settings;
pub mod store; pub mod store;
mod tls;
pub use config::*; pub use config::*;
pub use name::*; pub use name::*;

View File

@ -1,2 +1,8 @@
pub mod manager; pub mod manager;
pub mod store; pub mod store;
mod private_key;
pub use self::private_key::PrivateKey;
mod certificate;
pub use self::certificate::Certificate;

View File

@ -0,0 +1,45 @@
use std::convert::{From, TryFrom};
use std::fmt;
use std::str::FromStr;
use serde_with::{DeserializeFromStr, SerializeDisplay};
#[derive(Debug, Clone, PartialEq, DeserializeFromStr, SerializeDisplay)]
/// DER-encoded X.509, like rustls::Certificate.
pub struct Certificate(Vec<u8>);
impl FromStr for Certificate {
type Err = pem::PemError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Certificate(pem::parse(s)?.into_contents()))
}
}
impl fmt::Display for Certificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
pem::Pem::new("CERTIFICATE", self.0.clone()).fmt(f)
}
}
impl TryFrom<&openssl::x509::X509Ref> for Certificate {
type Error = openssl::error::ErrorStack;
fn try_from(c: &openssl::x509::X509Ref) -> Result<Self, Self::Error> {
Ok(Certificate(c.to_der()?))
}
}
impl TryFrom<&Certificate> for openssl::x509::X509 {
type Error = openssl::error::ErrorStack;
fn try_from(c: &Certificate) -> Result<Self, Self::Error> {
openssl::x509::X509::from_der(&c.0)
}
}
impl From<Certificate> for rustls::Certificate {
fn from(c: Certificate) -> Self {
rustls::Certificate(c.0)
}
}

View File

@ -1,7 +1,6 @@
use std::{sync, time}; use std::{sync, time};
use crate::domain::acme; use crate::domain::acme::{self, Certificate, PrivateKey};
use crate::domain::tls::{Certificate, PrivateKey};
use crate::error::unexpected::{self, Intoable, Mappable}; use crate::error::unexpected::{self, Intoable, Mappable};
use crate::{domain, token, util}; use crate::{domain, token, util};
@ -81,7 +80,7 @@ impl ManagerImpl {
.await .await
.or_unexpected_while("building account")?; .or_unexpected_while("building account")?;
let account_key: PrivateKey = account let account_key: acme::PrivateKey = account
.private_key() .private_key()
.as_ref() .as_ref()
.try_into() .try_into()
@ -106,8 +105,8 @@ impl Manager for ManagerImpl {
) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>> { ) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>> {
Box::pin(async move { Box::pin(async move {
// if there's an existing cert, and its expiry (determined by the soonest value of // if there's an existing cert, and its expiry (determined by the soonest value of
// not_after amongst its parts) is later than 30 days from now, then we consider it to // not_after amongst its parts) is later than 30 days from now, then we consider it to be
// be synced. // synced.
if let Ok((_, cert)) = self.store.get_certificate(domain.as_str()) { if let Ok((_, cert)) = self.store.get_certificate(domain.as_str()) {
let thirty_days = openssl::asn1::Asn1Time::days_from_now(30) let thirty_days = openssl::asn1::Asn1Time::days_from_now(30)
.expect("parsed thirty days from now as Asn1Time"); .expect("parsed thirty days from now as Asn1Time");
@ -243,7 +242,7 @@ impl Manager for ManagerImpl {
} }
// Generate an RSA private key for the certificate. // Generate an RSA private key for the certificate.
let pkey = PrivateKey::new(); let pkey = acme::PrivateKey::new();
let acme2_pkey = (&pkey) let acme2_pkey = (&pkey)
.try_into() .try_into()
@ -288,8 +287,8 @@ impl Manager for ManagerImpl {
"expected the order to return a certificate", "expected the order to return a certificate",
))? ))?
.into_iter() .into_iter()
.map(|cert| Certificate::try_from(cert.as_ref())) .map(|cert| acme::Certificate::try_from(cert.as_ref()))
.try_collect::<Vec<Certificate>>() .try_collect::<Vec<acme::Certificate>>()
.or_unexpected_while("parsing certificate")?; .or_unexpected_while("parsing certificate")?;
if cert.len() <= 1 { if cert.len() <= 1 {

View File

@ -11,9 +11,9 @@ pub struct PrivateKey(Vec<u8>);
impl PrivateKey { impl PrivateKey {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new() -> PrivateKey { pub fn new() -> PrivateKey {
let rsa = openssl::rsa::Rsa::generate(4096).expect("generating RSA"); acme2::gen_rsa_private_key(4096)
let key = openssl::pkey::PKey::from_rsa(rsa).expect("creating private key from RSA"); .expect("RSA private key generated")
key.as_ref() .as_ref()
.try_into() .try_into()
.expect("RSA private key converted to internal representation") .expect("RSA private key converted to internal representation")
} }

View File

@ -2,7 +2,7 @@ use std::io::{Read, Write};
use std::str::FromStr; use std::str::FromStr;
use std::{fs, path}; use std::{fs, path};
use crate::domain::tls::{Certificate, PrivateKey}; use crate::domain::acme::{Certificate, PrivateKey};
use crate::error::unexpected::{self, Mappable}; use crate::error::unexpected::{self, Mappable};
use crate::util; use crate::util;

View File

@ -1,101 +0,0 @@
use crate::domain::tls::{Certificate, PrivateKey};
use crate::error::unexpected::{self, Mappable};
use crate::{domain, util};
use serde::{Deserialize, Serialize};
use std::{fs, path, sync};
#[derive(Debug, Serialize, Deserialize)]
struct StoredPKeyCert {
private_key: PrivateKey,
cert: Certificate,
}
pub struct FSStore {
cert_dir_path: path::PathBuf,
}
impl FSStore {
pub fn new(dir_path: &path::Path) -> unexpected::Result<Self> {
let cert_dir_path = dir_path.join("certificates");
fs::create_dir_all(&cert_dir_path)
.map_unexpected_while(|| format!("creating dir {}", cert_dir_path.display()))?;
Ok(Self { cert_dir_path })
}
fn pkey_cert_path(&self, domain: &domain::Name) -> path::PathBuf {
let mut domain = domain.as_str().to_string();
domain.push_str(".json");
self.cert_dir_path.join(domain)
}
fn get_certificate(
&self,
domain: &domain::Name,
) -> unexpected::Result<(PrivateKey, Certificate)> {
let path = self.pkey_cert_path(domain);
let file = match util::open_file(path.as_path())
.map_unexpected_while(|| format!("opening file {}", path.display()))?
{
Some(file) => file,
None => {
let pkey = PrivateKey::new();
let cert = Certificate::new_self_signed(&pkey, domain)
.or_unexpected_while("creating self-signed cert")?;
let file = fs::File::create(path.as_path())
.map_unexpected_while(|| format!("creating file {}", path.display()))?;
let stored = StoredPKeyCert {
private_key: pkey,
cert,
};
serde_json::to_writer(file, &stored).or_unexpected_while("writing cert to file")?;
return Ok((stored.private_key, stored.cert));
}
};
let stored: StoredPKeyCert =
serde_json::from_reader(file).or_unexpected_while("parsing json")?;
Ok((stored.private_key, stored.cert))
}
}
impl rustls::server::ResolvesServerCert for FSStore {
fn resolve(
&self,
client_hello: rustls::server::ClientHello<'_>,
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
let domain = client_hello.server_name()?;
let res: unexpected::Result<Option<sync::Arc<rustls::sign::CertifiedKey>>> = (|| {
let domain: domain::Name = domain
.parse()
.map_unexpected_while(|| format!("parsing domain {domain}"))?;
let (pkey, cert) = self
.get_certificate(&domain)
.or_unexpected_while("fetching pkey/cert")?;
let pkey = rustls::sign::any_supported_type(&pkey.into()).or_unexpected()?;
Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey {
cert: vec![cert.into()],
key: pkey,
ocsp: None,
sct_list: None,
})))
})();
res.unwrap_or_else(|err| {
log::error!("Unexpected error getting cert for domain {domain}: {err}");
None
})
}
}

View File

@ -149,7 +149,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
path: &str, path: &str,
) -> Result<util::BoxByteStream, GetFileError>; ) -> Result<util::BoxByteStream, GetFileError>;
fn sync_https_cert<'mgr>( fn sync_cert<'mgr>(
&'mgr self, &'mgr self,
domain: domain::Name, domain: domain::Name,
) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>>; ) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>>;
@ -256,7 +256,7 @@ impl Manager for ManagerImpl {
Ok(f) Ok(f)
} }
fn sync_https_cert<'mgr>( fn sync_cert<'mgr>(
&'mgr self, &'mgr self,
domain: domain::Name, domain: domain::Name,
) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>> { ) -> util::BoxFuture<'mgr, Result<(), unexpected::Error>> {
@ -285,7 +285,7 @@ impl Manager for ManagerImpl {
self.domain_store.set(&domain, &settings)?; self.domain_store.set(&domain, &settings)?;
self.sync_https_cert(domain).await?; self.sync_cert(domain).await?;
Ok(()) Ok(())
}) })

View File

@ -1,5 +0,0 @@
mod private_key;
pub use self::private_key::PrivateKey;
mod certificate;
pub use self::certificate::Certificate;

View File

@ -1,109 +0,0 @@
use crate::domain;
use crate::domain::tls::PrivateKey;
use crate::error::unexpected::{self, Mappable};
use std::convert::{From, TryFrom};
use std::fmt;
use std::str::FromStr;
use serde_with::{DeserializeFromStr, SerializeDisplay};
#[derive(Debug, Clone, PartialEq, DeserializeFromStr, SerializeDisplay)]
/// DER-encoded X.509, like rustls::Certificate.
pub struct Certificate(Vec<u8>);
impl Certificate {
pub fn new_self_signed(
pkey: &PrivateKey,
domain: &domain::Name,
) -> unexpected::Result<Certificate> {
use openssl::asn1::*;
use openssl::pkey::*;
use openssl::x509::extension::SubjectAlternativeName;
use openssl::x509::*;
let name = {
let mut builder = X509Name::builder().expect("initializing x509 name builder");
builder
.append_entry_by_text("CN", domain.as_str())
.or_unexpected_while("adding CN")?;
builder.build()
};
let mut builder = X509::builder().expect("initializing x509 builder");
builder
.set_subject_name(&name)
.or_unexpected_while("setting subject name")?;
// 9999/07/23
let not_after = Asn1Time::from_unix(253388296800).expect("initializing not_after");
builder
.set_not_after(not_after.as_ref())
.or_unexpected_while("setting not_after")?;
// Add domain as SAN
let san_extension = {
let mut san = SubjectAlternativeName::new();
san.dns(domain.as_str());
san.build(&builder.x509v3_context(None, None))
.or_unexpected_while("building SAN")?
};
builder
.append_extension(san_extension)
.or_unexpected_while("appending SAN")?;
let pkey: PKey<Private> = pkey.try_into().or_unexpected_while("converting PKey")?;
builder
.set_pubkey(pkey.as_ref())
.or_unexpected_while("setting pubkey")?;
builder
.sign(pkey.as_ref(), openssl::hash::MessageDigest::sha256())
.or_unexpected_while("signing with private key")?;
let cert = builder.build();
Ok(cert
.as_ref()
.try_into()
.or_unexpected_while("converting to cert")?)
}
}
impl FromStr for Certificate {
type Err = pem::PemError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Certificate(pem::parse(s)?.into_contents()))
}
}
impl fmt::Display for Certificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
pem::Pem::new("CERTIFICATE", self.0.clone()).fmt(f)
}
}
impl TryFrom<&openssl::x509::X509Ref> for Certificate {
type Error = openssl::error::ErrorStack;
fn try_from(c: &openssl::x509::X509Ref) -> Result<Self, Self::Error> {
Ok(Certificate(c.to_der()?))
}
}
impl TryFrom<&Certificate> for openssl::x509::X509 {
type Error = openssl::error::ErrorStack;
fn try_from(c: &Certificate) -> Result<Self, Self::Error> {
openssl::x509::X509::from_der(&c.0)
}
}
impl From<Certificate> for rustls::Certificate {
fn from(c: Certificate) -> Self {
rustls::Certificate(c.0)
}
}

View File

@ -4,7 +4,7 @@ use clap::Parser;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use signal_hook_tokio::Signals; use signal_hook_tokio::Signals;
use std::{path, sync}; use std::path;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version)] #[command(version)]
@ -128,10 +128,6 @@ async fn main() {
None None
}; };
let domain_gemini_store =
domani::domain::gemini::FSStore::new(&config.domain.store_dir_path.join("gemini"))
.unwrap_or_else(|e| panic!("domain gemini store initialization failed: {e}"));
let mut task_stack = domani::task_stack::TaskStack::new(); let mut task_stack = domani::task_stack::TaskStack::new();
let domain_manager = domani::domain::manager::ManagerImpl::new( let domain_manager = domani::domain::manager::ManagerImpl::new(
@ -151,7 +147,7 @@ async fn main() {
let _ = domani::service::gemini::Service::new( let _ = domani::service::gemini::Service::new(
&mut task_stack, &mut task_stack,
sync::Arc::new(domain_gemini_store), domain_manager.clone(),
config.service, config.service,
); );

View File

@ -123,7 +123,7 @@ pub async fn cert_refresher(
} }
_ = domain_manager _ = domain_manager
.sync_https_cert(primary_domain.clone()) .sync_cert(primary_domain.clone())
.await .await
.inspect_err(|err| { .inspect_err(|err| {
log::error!( log::error!(
@ -141,7 +141,7 @@ pub async fn cert_refresher(
for domain in domains_iter.unwrap().into_iter() { for domain in domains_iter.unwrap().into_iter() {
let _ = domain_manager let _ = domain_manager
.sync_https_cert(domain.clone()) .sync_cert(domain.clone())
.await .await
.inspect_err(|err| { .inspect_err(|err| {
log::error!("Error while getting cert for {}: {err}", domain.as_str(),) log::error!("Error while getting cert for {}: {err}", domain.as_str(),)

View File

@ -1,3 +0,0 @@
# FOO
This is foo

View File

@ -1,7 +0,0 @@
<html>
<body>
<h1>FOO</h1>
<p>This is the foo page</p>
</body>
</html>

View File

@ -1,9 +0,0 @@
# INDEX
This is the gemini index.
=> foo /foo.gmi
=> bar /subdir/bar.gmi
=> penguin /penguin.jpg

View File

@ -1,9 +0,0 @@
<html>
<body>
<h1>INDEX</h1>
<p>This is the index page.</p>
<p>Check out <a href="/foo.html">foo</a>.</p>
<p>Check out <a href="/subdir/foo.html">bar</a>.</p>
<img src="/penguin.jpg" />
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

View File

@ -1,3 +0,0 @@
# BAR
This is bar

View File

@ -1,7 +0,0 @@
<html>
<body>
<h1>BAR</h1>
<p>This is the bar page</p>
</body>
</html>

View File

@ -1,3 +0,0 @@
# INDEX
This is the gemini index of the subdir.

View File

@ -1,6 +0,0 @@
<html>
<body>
<h1>INDEX</h1>
<p>This is the index page of the subdir</p>
</body>
</html>