1use anyhow::{Context, Result};
2use log::debug;
3use std::path::Path;
4use tonic::transport::{Certificate, ClientTlsConfig, Identity};
5use x509_certificate::X509Certificate;
6
7const CA_RAW: &[u8] = include_str!("../.resources/tls/ca.pem").as_bytes();
8const NOBODY_CRT: &[u8] = include_str!(env!("GL_NOBODY_CRT")).as_bytes();
9const NOBODY_KEY: &[u8] = include_str!(env!("GL_NOBODY_KEY")).as_bytes();
10
11#[derive(Clone, Debug)]
19pub struct TlsConfig {
20 pub(crate) inner: ClientTlsConfig,
21
22 pub(crate) private_key: Option<Vec<u8>>,
25
26 pub ca: Vec<u8>,
27
28 pub x509_cert: Option<X509Certificate>,
32}
33
34fn load_file_or_default(varname: &str, default: &[u8]) -> Vec<u8> {
38 match std::env::var(varname) {
39 Ok(fname) => {
40 debug!("Loading file {} for envvar {}", fname, varname);
41 let f = std::fs::read(fname.clone());
42 if f.is_err() {
43 debug!(
44 "Could not find file {} for var {}, loading from default",
45 fname, varname
46 );
47 default.to_vec()
48 } else {
49 f.unwrap()
50 }
51 }
52 Err(_) => default.to_vec(),
53 }
54}
55
56impl TlsConfig {
57 pub fn new() -> Self {
58 debug!("Configuring TlsConfig with nobody identity");
59 let nobody_crt = load_file_or_default("GL_NOBODY_CRT", NOBODY_CRT);
60 let nobody_key = load_file_or_default("GL_NOBODY_KEY", NOBODY_KEY);
61 let ca_crt = load_file_or_default("GL_CA_CRT", CA_RAW);
62 Self::with(nobody_crt, nobody_key, ca_crt)
65 }
66
67 pub fn with<V: AsRef<[u8]>>(crt: V, key: V, ca_crt: V) -> Self {
68 let x509_cert = x509_certificate_from_pem_or_none(&crt);
69
70 let config = ClientTlsConfig::new()
71 .ca_certificate(Certificate::from_pem(ca_crt.as_ref()))
72 .identity(Identity::from_pem(crt, key.as_ref()));
73
74 TlsConfig {
75 inner: config,
76 private_key: Some(key.as_ref().to_vec()),
77 ca: ca_crt.as_ref().to_vec(),
78 x509_cert,
79 }
80 }
81}
82
83impl TlsConfig {
84 pub fn identity(self, cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Self {
91 let x509_cert = x509_certificate_from_pem_or_none(&cert_pem);
92
93 TlsConfig {
94 inner: self.inner.identity(Identity::from_pem(&cert_pem, &key_pem)),
95 private_key: Some(key_pem),
96 x509_cert,
97 ..self
98 }
99 }
100
101 pub fn identity_from_path<P: AsRef<Path>>(self, path: P) -> Result<Self> {
108 let cert_path = path.as_ref().join("client.crt");
109 let key_path = path.as_ref().join("client-key.pem");
110
111 let cert_pem = std::fs::read(cert_path.clone())
112 .with_context(|| format!("Failed to read '{}'", cert_path.display()))?;
113 let key_pem = std::fs::read(key_path.clone())
114 .with_context(|| format!("Failed to read '{}", key_path.display()))?;
115
116 Ok(self.identity(cert_pem, key_pem))
117 }
118
119 pub fn ca_certificate(self, ca: Vec<u8>) -> Self {
125 TlsConfig {
126 inner: self.inner.ca_certificate(Certificate::from_pem(&ca)),
127 ca,
128 ..self
129 }
130 }
131
132 pub fn client_tls_config(&self) -> ClientTlsConfig {
133 self.inner.clone()
134 }
135}
136
137impl Default for TlsConfig {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143fn x509_certificate_from_pem_or_none(pem: impl AsRef<[u8]>) -> Option<X509Certificate> {
147 X509Certificate::from_pem(pem)
148 .map_err(|e| debug!("Failed to parse x509 certificate: {}", e))
149 .ok()
150}
151
152pub fn generate_self_signed_device_cert(
159 node_id: &str,
160 device: &str,
161 subject_alt_names: Vec<String>,
162 key_pair: Option<rcgen::KeyPair>,
163) -> rcgen::Certificate {
164 let mut params = cert_params_from_template(subject_alt_names);
166
167 params.is_ca = rcgen::IsCa::ExplicitNoCa;
170 params.distinguished_name.push(
171 rcgen::DnType::CommonName,
172 format!("/users/{}/{}", node_id, device),
173 );
174
175 params.key_pair = key_pair;
177 params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
178
179 rcgen::Certificate::from_params(params).unwrap()
180}
181
182fn cert_params_from_template(subject_alt_names: Vec<String>) -> rcgen::CertificateParams {
183 let mut params = rcgen::CertificateParams::new(subject_alt_names);
184
185 params
187 .distinguished_name
188 .push(rcgen::DnType::CountryName, "US");
189 params
190 .distinguished_name
191 .push(rcgen::DnType::LocalityName, "SAN FRANCISCO");
192 params
193 .distinguished_name
194 .push(rcgen::DnType::OrganizationName, "Blockstream");
195 params
196 .distinguished_name
197 .push(rcgen::DnType::StateOrProvinceName, "CALIFORNIA");
198 params.distinguished_name.push(
199 rcgen::DnType::OrganizationalUnitName,
200 "CertificateAuthority",
201 );
202
203 params
204}
205
206pub fn generate_ecdsa_key_pair() -> rcgen::KeyPair {
207 rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap()
208}
209
210#[cfg(test)]
211pub mod tests {
212 use rcgen::KeyPair;
213
214 use super::*;
215
216 #[test]
217 fn test_generate_self_signed_device_cert() {
218 let device_cert =
219 generate_self_signed_device_cert("mynodeid", "device", vec!["localhost".into()], None);
220 assert!(device_cert
221 .serialize_pem()
222 .unwrap()
223 .starts_with("-----BEGIN CERTIFICATE-----"));
224 assert!(device_cert
225 .serialize_private_key_pem()
226 .starts_with("-----BEGIN PRIVATE KEY-----"));
227 }
228
229 #[test]
230 fn test_generate_self_signed_device_cert_from_pem() {
231 let kp = generate_ecdsa_key_pair();
232 let keys = KeyPair::from_der(kp.serialized_der()).unwrap();
233 let cert = generate_self_signed_device_cert(
234 "mynodeid",
235 "device",
236 vec!["localhost".into()],
237 Some(keys),
238 );
239 assert!(kp.serialize_pem() == cert.get_key_pair().serialize_pem());
240 }
241}