use log::{error, info};
use openssl::asn1::Asn1Time;
use openssl::bn::{BigNum, MsbOption};
use openssl::error::ErrorStack;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
use openssl::x509::{X509Builder, X509NameBuilder, X509};
use std::collections::HashMap;
use std::fs;
use std::fs::{create_dir_all, OpenOptions};
use std::io::{Error, ErrorKind, Write};
use std::ops::{Add, Sub};
use std::path::Path;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub const CHIA_CA_CRT: &str = r"-----BEGIN CERTIFICATE-----
MIIDKTCCAhGgAwIBAgIUXIpxI5MoZQ65/vhc7DK/d5ymoMUwDQYJKoZIhvcNAQEL
BQAwRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8GA1UECwwY
T3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMB4XDTIxMDEyMzA4NTEwNloXDTMxMDEy
MTA4NTEwNlowRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8G
A1UECwwYT3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAzz/L219Zjb5CIKnUkpd2julGC+j3E97KUiuOalCH9wdq
gpJi9nBqLccwPCSFXFew6CNBIBM+CW2jT3UVwgzjdXJ7pgtu8gWj0NQ6NqSLiXV2
WbpZovfrVh3x7Z4bjPgI3ouWjyehUfmK1GPIld4BfUSQtPlUJ53+XT32GRizUy+b
0CcJ84jp1XvyZAMajYnclFRNNJSw9WXtTlMUu+Z1M4K7c4ZPwEqgEnCgRc0TCaXj
180vo7mCHJQoDiNSCRATwfH+kWxOOK/nePkq2t4mPSFaX8xAS4yILISIOWYn7sNg
dy9D6gGNFo2SZ0FR3x9hjUjYEV3cPqg3BmNE3DDynQIDAQABoxMwETAPBgNVHRMB
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAEugnFQjzHhS0eeCqUwOHmP3ww
/rXPkKF+bJ6uiQgXZl+B5W3m3zaKimJeyatmuN+5ST1gUET+boMhbA/7grXAsRsk
SFTHG0T9CWfPiuimVmGCzoxLGpWDMJcHZncpQZ72dcy3h7mjWS+U59uyRVHeiprE
hvSyoNSYmfvh7vplRKS1wYeA119LL5fRXvOQNW6pSsts17auu38HWQGagSIAd1UP
5zEvDS1HgvaU1E09hlHzlpdSdNkAx7si0DMzxKHUg9oXeRZedt6kcfyEmryd52Mj
1r1R9mf4iMIUv1zc2sHVc1omxnCw9+7U4GMWLtL5OgyJyfNyoxk3tC+D3KNU
-----END CERTIFICATE-----";
pub const CHIA_CA_KEY: &str = r"-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzz/L219Zjb5CIKnUkpd2julGC+j3E97KUiuOalCH9wdqgpJi
9nBqLccwPCSFXFew6CNBIBM+CW2jT3UVwgzjdXJ7pgtu8gWj0NQ6NqSLiXV2WbpZ
ovfrVh3x7Z4bjPgI3ouWjyehUfmK1GPIld4BfUSQtPlUJ53+XT32GRizUy+b0CcJ
84jp1XvyZAMajYnclFRNNJSw9WXtTlMUu+Z1M4K7c4ZPwEqgEnCgRc0TCaXj180v
o7mCHJQoDiNSCRATwfH+kWxOOK/nePkq2t4mPSFaX8xAS4yILISIOWYn7sNgdy9D
6gGNFo2SZ0FR3x9hjUjYEV3cPqg3BmNE3DDynQIDAQABAoIBAGupS4BJdx8gEAAh
2VDRqAAzhHTZb8j9uoKXJ+NotEkKrDTqUMiOu0nOqOsFWdYPo9HjxoggFuEU+Hpl
a4kj4uF3OG6Yj+jgLypjpV4PeoFM6M9R9BCp07In2i7DLLK9gvYA85SoVLBd/tW4
hFH+Qy3M+ZNZ1nLCK4pKjtaYs0dpi5zLoVvpEcEem2O+aRpUPCZqkNwU0umATCfg
ZGfFzgXI/XPJr8Uy+LVZOFp3PXXHfnZZD9T5AjO/ViBeqbMFuWQ8BpVOqapNPKj8
xDY3ovw3uiAYPC7eLib3u/WoFelMc2OMX0QljLp5Y+FScFHAMxoco3AQdWSYvSQw
b5xZmg0CgYEA6zKASfrw3EtPthkLR5NBmesI4RbbY6iFVhS5loLbzTtStvsus8EI
6RQgLgAFF14H21YSHxb6dB1Mbo45BN83gmDpUvKPREslqD3YPMKFo5GXMmv+JhNo
5Y9fhiOEnxzLJGtBB1HeGmg5NXp9mr2Ch9u8w/slfuCHckbA9AYvdxMCgYEA4ZR5
zg73+UA1a6Pm93bLYZGj+hf7OaB/6Hiw9YxCBgDfWM9dJ48iz382nojT5ui0rClV
5YAo8UCLh01Np9AbBZHuBdYm9IziuKNzTeK31UW+Tvbz+dEx7+PlYQffNOhcIgd+
9SXjoZorQksImKdMGZld1lEReHuBawq92JQvtY8CgYEAtNwUws7xQLW5CjKf9d5K
5+1Q2qYU9sG0JsmxHQhrtZoUtRjahOe/zlvnkvf48ksgh43cSYQF/Bw7lhhPyGtN
6DhVs69KdB3FS2ajTbXXxjxCpEdfHDB4zW4+6ouNhD1ECTFgxBw0SuIye+lBhSiN
o6NZuOr7nmFSRpIZ9ox7G3kCgYA4pvxMNtAqJekEpn4cChab42LGLX2nhFp7PMxc
bqQqM8/j0vg3Nihs6isCd6SYKjstvZfX8m7V3/rquQxWp9oRdQvNJXJVGojaDBqq
JdU7V6+qzzSIufQLpjV2P+7br7trxGwrDx/y9vAETynShLmE+FJrv6Jems3u3xy8
psKwmwKBgG5uLzCyMvMB2KwI+f3np2LYVGG0Pl1jq6yNXSaBosAiF0y+IgUjtWY5
EejO8oPWcb9AbqgPtrWaiJi17KiKv4Oyba5+y36IEtyjolWt0AB6F3oDK0X+Etw8
j/xlvBNuzDL6gRJHQg1+d4dO8Lz54NDUbKW8jGl+N/7afGVpGmX9
-----END RSA PRIVATE KEY-----";
pub fn generate_ca_signed_cert(
cert_path: &Path,
cert_data: &[u8],
key_path: &Path,
key_data: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), Error> {
let (cert_data, key_data) = generate_ca_signed_cert_data(cert_data, key_data)
.map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?;
write_ssl_cert_and_key(cert_path, &cert_data, key_path, &key_data, true)?;
Ok((cert_data, key_data))
}
fn write_ssl_cert_and_key(
cert_path: &Path,
cert_data: &[u8],
key_path: &Path,
key_data: &[u8],
overwrite: bool,
) -> Result<(), Error> {
if cert_path.exists() && overwrite {
fs::remove_file(cert_path)?;
}
let mut crt = OpenOptions::new()
.write(true)
.create_new(true)
.open(cert_path)?;
crt.write_all(cert_data)?;
crt.flush()?;
if key_path.exists() && overwrite {
fs::remove_file(key_path)?;
}
let mut key = OpenOptions::new()
.write(true)
.create_new(true)
.open(key_path)?;
key.write_all(key_data)?;
key.flush()
}
pub fn generate_ca_signed_cert_data(
cert_data: &[u8],
key_data: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), ErrorStack> {
let root_cert = X509::from_pem(cert_data)?;
let root_key = PKey::from_rsa(Rsa::private_key_from_pem(key_data)?)?;
let cert_key = Rsa::generate(2048)?;
let pub_key = PKey::from_rsa(cert_key)?;
let mut cert = X509Builder::new()?;
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_nid(Nid::COMMONNAME, "Chia")?;
x509_name.append_entry_by_nid(Nid::ORGANIZATIONNAME, "Chia")?;
x509_name.append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, "Organic Farming Division")?;
let name = x509_name.build();
cert.set_subject_name(name.as_ref())?;
cert.set_issuer_name(root_cert.issuer_name())?;
cert.set_pubkey(pub_key.as_ref())?;
let mut bn = BigNum::new()?;
bn.rand(32, MsbOption::MAYBE_ZERO, true)?;
cert.set_serial_number(bn.to_asn1_integer()?.as_ref())?;
cert.set_not_before(
Asn1Time::from_unix(
SystemTime::now()
.sub(Duration::from_secs(60 * 60 * 24))
.duration_since(UNIX_EPOCH)
.expect("Should be later than Epoch")
.as_secs() as i64,
)?
.as_ref(),
)?;
cert.set_not_after(Asn1Time::from_str("21000802000000Z")?.as_ref())?;
let ctx = cert.x509v3_context(None, None);
let san = SubjectAlternativeName::new().dns("chia.net").build(&ctx)?;
cert.append_extension(san)?;
cert.set_version(2)?;
cert.sign(root_key.as_ref(), MessageDigest::sha256())?;
let x509 = cert.build();
Ok((x509.to_pem()?, pub_key.rsa()?.private_key_to_pem()?))
}
pub fn make_ca_cert(cert_path: &Path, key_path: &Path) -> Result<(Vec<u8>, Vec<u8>), Error> {
let (cert_data, key_data) = make_ca_cert_data()
.map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?;
write_ssl_cert_and_key(cert_path, &cert_data, key_path, &key_data, true)?;
Ok((cert_data, key_data))
}
fn make_ca_cert_data() -> Result<(Vec<u8>, Vec<u8>), ErrorStack> {
let root_key = PKey::from_rsa(Rsa::generate(2048)?)?;
let mut x509_name = X509NameBuilder::new()?;
x509_name.append_entry_by_text("O", "Chia").unwrap();
x509_name
.append_entry_by_text("OU", "Organic Farming Division")
.unwrap();
x509_name.append_entry_by_text("CN", "Chia CA").unwrap();
let mut cert = X509Builder::new()?;
let name = x509_name.build();
cert.set_subject_name(name.as_ref())?;
cert.set_issuer_name(name.as_ref())?;
cert.set_pubkey(root_key.as_ref())?;
let mut bn = BigNum::new()?;
bn.rand(32, MsbOption::MAYBE_ZERO, true)?;
cert.set_serial_number(bn.to_asn1_integer()?.as_ref())?;
cert.set_not_before(
Asn1Time::from_unix(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Should be later than Epoch")
.as_secs() as i64,
)?
.as_ref(),
)?;
cert.set_not_after(
Asn1Time::from_unix(
SystemTime::now()
.add(Duration::from_secs(60 * 60 * 24 * 3650))
.duration_since(UNIX_EPOCH)
.expect("Should be later than Epoch")
.as_secs() as i64,
)?
.as_ref(),
)?;
let base = BasicConstraints::new().critical().ca().build()?;
cert.append_extension(base)?;
cert.set_version(2)?;
cert.sign(root_key.as_ref(), MessageDigest::sha256())?;
let x509 = cert.build();
Ok((x509.to_pem()?, root_key.rsa()?.private_key_to_pem()?))
}
const ALL_PRIVATE_NODE_NAMES: [&str; 8] = [
"full_node",
"wallet",
"farmer",
"harvester",
"timelord",
"crawler",
"data_layer",
"daemon",
];
const ALL_PUBLIC_NODE_NAMES: [&str; 6] = [
"full_node",
"wallet",
"farmer",
"introducer",
"timelord",
"data_layer",
];
pub struct MemorySSL {
pub public: HashMap<String, MemoryNodeSSL>,
pub private: HashMap<String, MemoryNodeSSL>,
}
pub struct MemoryNodeSSL {
pub cert: Vec<u8>,
pub key: Vec<u8>,
}
pub fn create_all_ssl_memory() -> Result<MemorySSL, Error> {
info!("Generating CA Certs");
let mut public_map = HashMap::new();
let mut private_map = HashMap::new();
let (ca_cert_data, ca_key_data) = make_ca_cert_data()
.map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?;
info!("Generating Private Certs");
let private_certs =
generate_ssl_for_nodes_in_memory(&ca_cert_data, &ca_key_data, &ALL_PRIVATE_NODE_NAMES)?;
private_map.insert(
"ca".to_string(),
MemoryNodeSSL {
cert: ca_cert_data,
key: ca_key_data,
},
);
private_map.extend(private_certs);
info!("Generating Public Certs");
let public_certs = generate_ssl_for_nodes_in_memory(
CHIA_CA_CRT.as_bytes(),
CHIA_CA_KEY.as_bytes(),
&ALL_PUBLIC_NODE_NAMES,
)?;
public_map.insert(
"ca".to_string(),
MemoryNodeSSL {
cert: CHIA_CA_CRT.as_bytes().to_vec(),
key: CHIA_CA_KEY.as_bytes().to_vec(),
},
);
public_map.extend(public_certs);
Ok(MemorySSL {
public: public_map,
private: private_map,
})
}
pub fn create_all_ssl(ssl_dir: &Path, overwrite: bool) -> Result<(), Error> {
let ca_dir = ssl_dir.join(Path::new("ca"));
create_dir_all(&ca_dir)?;
let private_ca_key_path = ca_dir.join("private_ca.key");
let private_ca_crt_path = ca_dir.join("private_ca.crt");
let chia_ca_crt_path = ca_dir.join("chia_ca.crt");
let chia_ca_key_path = ca_dir.join("chia_ca.key");
write_ssl_cert_and_key(
&chia_ca_crt_path,
CHIA_CA_CRT.as_bytes(),
&chia_ca_key_path,
CHIA_CA_KEY.as_bytes(),
true,
)?;
let (crt, key) = if !private_ca_crt_path.exists() || !private_ca_key_path.exists() {
info!("Generating SSL CA Cert");
make_ca_cert(&private_ca_crt_path, &private_ca_key_path)?
} else {
info!("Loading SSL CA Cert");
(
fs::read(private_ca_crt_path)?,
fs::read(private_ca_key_path)?,
)
};
info!("Generating Private Certs");
generate_ssl_for_nodes(
ssl_dir,
&crt,
&key,
"private",
&ALL_PRIVATE_NODE_NAMES,
overwrite,
)?;
info!("Generating Public Certs");
generate_ssl_for_nodes(
ssl_dir,
CHIA_CA_CRT.as_bytes(),
CHIA_CA_KEY.as_bytes(),
"public",
&ALL_PUBLIC_NODE_NAMES,
overwrite,
)
}
fn generate_ssl_for_nodes(
ssl_dir: &Path,
crt: &[u8],
key: &[u8],
prefix: &str,
nodes: &[&str],
overwrite: bool,
) -> Result<(), Error> {
for node_name in nodes {
let node_dir = ssl_dir.join(Path::new(*node_name));
create_dir_all(&node_dir)?;
let crt_path = node_dir.join(Path::new(&format!("{prefix}_{node_name}.crt")));
let key_path = node_dir.join(Path::new(&format!("{prefix}_{node_name}.key")));
if key_path.exists() && crt_path.exists() && !overwrite {
continue;
}
if let Err(e) = generate_ca_signed_cert(&crt_path, crt, &key_path, key) {
error!("Failed to write Cert Files: {:?}", e);
}
}
Ok(())
}
pub fn generate_ssl_for_nodes_in_memory(
crt: &[u8],
key: &[u8],
nodes: &[&str],
) -> Result<HashMap<String, MemoryNodeSSL>, Error> {
let mut map = HashMap::new();
for node_name in nodes {
let (cert, key) = generate_ca_signed_cert_data(crt, key)
.map_err(|e| Error::new(ErrorKind::Other, format!("OpenSSL Errors: {:?}", e)))?;
map.insert(node_name.to_string(), MemoryNodeSSL { cert, key });
}
Ok(map)
}
#[test]
pub fn test_ssl() {
use simple_logger::SimpleLogger;
SimpleLogger::new().init().unwrap();
create_all_ssl("./ssl".as_ref(), true).unwrap();
}