forked from zerotier/coyote
-
Notifications
You must be signed in to change notification settings - Fork 1
/
acmed-tls.rs
146 lines (122 loc) · 4.37 KB
/
acmed-tls.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::{
io::Write,
ops::Add,
time::{Duration, SystemTime},
};
use openssl::{
error::ErrorStack,
pkey::{PKey, Private},
rsa::Rsa,
x509::{X509Extension, X509Name, X509Req},
};
use coyote::{
acme::{
ca::{CACollector, CA},
challenge::Challenger,
handlers::{configure_routes, ServiceState},
PostgresNonceValidator,
},
models::Postgres,
};
use ratpack::prelude::*;
const CHALLENGE_EXPIRATION: i64 = 600;
#[tokio::main]
async fn main() -> Result<(), ServerError> {
// set HOSTNAME in your environment to something your webserver or certbot can hit; otherwise
// it will be 'localhost'. a cert will be generated with this name to serve the service with.
// This is really important.
let dnsname = &std::env::var("HOSTNAME").unwrap_or("localhost".to_string());
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
//
// to start a database to work with me:
//
// make postgres
//
let pg = Postgres::new("host=localhost dbname=coyote user=postgres", 10)
.await
.unwrap();
pg.migrate().await.unwrap();
let c = Challenger::new(Some(chrono::Duration::seconds(CHALLENGE_EXPIRATION)));
let ca = CACollector::new(Duration::MAX);
let pg2 = pg.clone();
let c2 = c.clone();
// FIXME probably need something magical with signals here to manage shutdown that I don't want to think about yet
tokio::spawn(async move {
loop {
// FIXME whitelist all challenge requests. This is not how ACME is supposed to work. You have to write this.
c2.tick(|_c| Some(())).await;
// NOTE this will explode violently if it unwraps to error, e.g. if the db goes down.
c2.reconcile(pg2.clone()).await.unwrap();
tokio::time::sleep(Duration::new(1, 0)).await;
}
});
let mut ca2 = ca.clone();
let (csr, key) = generate_csr(dnsname)?;
let test_ca = CA::new_test_ca().unwrap();
let cert = test_ca.generate_and_sign_cert(
csr,
SystemTime::now(),
SystemTime::now().add(Duration::from_secs(365 * 24 * 60 * 60)),
)?;
let test_ca2 = test_ca.clone();
tokio::spawn(async move {
// after CA generation, write out the key and certificate
let mut buf = std::fs::File::create("ca.key").unwrap();
let private = test_ca
.clone()
.private_key()
.private_key_to_pem_pkcs8()
.unwrap();
buf.write(&private).unwrap();
let mut buf = std::fs::File::create("ca.pem").unwrap();
let cert = test_ca.clone().certificate().to_pem().unwrap();
buf.write(&cert).unwrap();
ca2.spawn_collector(|| -> Result<CA, ErrorStack> { Ok(test_ca.clone()) })
.await
});
let validator = PostgresNonceValidator::new(pg.clone());
let ss = ServiceState::new(
format!("https://{}:8000", dnsname),
pg.clone(),
c,
ca,
validator,
)?;
let mut app = App::with_state(ss);
configure_routes(&mut app, None);
let key = key.private_key_to_der()?;
let config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(
vec![
rustls::Certificate(cert.to_der()?),
rustls::Certificate(test_ca2.certificate().to_der()?),
],
rustls::PrivateKey(key),
)?;
Ok(app.serve_tls("0.0.0.0:8000", config).await?)
}
fn generate_csr(dnsname: &str) -> Result<(X509Req, Rsa<Private>), ErrorStack> {
log::info!("hostname: {}", dnsname);
let mut namebuilder = X509Name::builder().unwrap();
namebuilder.append_entry_by_text("CN", dnsname).unwrap();
let mut req = X509Req::builder().unwrap();
req.set_subject_name(&namebuilder.build()).unwrap();
let mut extensions = openssl::stack::Stack::new()?;
extensions.push(X509Extension::new(
None,
Some(&req.x509v3_context(None)),
"subjectAltName",
&format!("DNS:{}", dnsname),
)?)?;
req.add_extensions(&extensions)?;
req.set_version(2)?;
let key = Rsa::generate(4096).unwrap();
// FIXME there has to be a much better way of doing this!
let pubkey = PKey::public_key_from_pem(&key.public_key_to_pem().unwrap()).unwrap();
req.set_pubkey(&pubkey).unwrap();
Ok((req.build(), key))
}