Skip to content

Commit 3cd987d

Browse files
authored
Merge pull request #110 from dfinity/igor/certs
feat(BOUN-1401): Add `--cert-provider-file` CLI option
2 parents f180274 + 4671860 commit 3cd987d

File tree

9 files changed

+104
-46
lines changed

9 files changed

+104
-46
lines changed

Cargo.lock

Lines changed: 15 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ ic-agent = { version = "0.40.0", features = [
5353
"ring",
5454
"_internal_dynamic-routing",
5555
] }
56-
ic-bn-lib = { git = "https://github.com/dfinity/ic-bn-lib", rev = "ec4a6b4abab2d94e09c4d5fee2d57d7bb1260835", features = [
56+
ic-bn-lib = { git = "https://github.com/dfinity/ic-bn-lib", rev = "23b3b0b76795c9b75eed96742c2185da0ce9ee2a", features = [
5757
"vector",
5858
] }
5959
ic-http-certification = { version = "3.0.3", optional = true }

src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ pub struct Ic {
198198

199199
#[derive(Args)]
200200
pub struct Cert {
201+
/// Read certificates from given files.
202+
/// Each file should be PEM-encoded concatenated certificate chain with a private key.
203+
#[clap(env, long, value_delimiter = ',')]
204+
pub cert_provider_file: Vec<PathBuf>,
205+
201206
/// Read certificates from given directories
202207
/// Each certificate should be a pair .pem + .key files with the same base name.
203208
#[clap(env, long, value_delimiter = ',')]

src/tls/cert/mod.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ pub mod storage;
33

44
use std::sync::{Arc, Mutex};
55

6-
use anyhow::{Error, anyhow};
6+
use anyhow::{Context, Error, anyhow};
77
use async_trait::async_trait;
88
use ic_bn_lib::{
99
tasks::Run,
10-
tls::{extract_sans_der, pem_convert_to_rustls},
10+
tls::{extract_sans_der, pem_convert_to_rustls_single},
1111
};
1212
use rustls::sign::CertifiedKey;
1313
use tokio_util::sync::CancellationToken;
@@ -26,10 +26,11 @@ pub struct Cert<T: Clone + Send + Sync> {
2626
// Commonly used concrete type of the above for Rustls
2727
pub type CertKey = Cert<Arc<CertifiedKey>>;
2828

29-
pub fn pem_convert_to_certkey(key: &[u8], certs: &[u8]) -> Result<CertKey, Error> {
30-
let cert_key = pem_convert_to_rustls(key, certs)?;
29+
pub fn pem_convert_to_certkey(pem: &[u8]) -> Result<CertKey, Error> {
30+
let cert_key = pem_convert_to_rustls_single(pem)
31+
.context("unable to convert certificate chain and/or private key from PEM")?;
3132

32-
let san = extract_sans_der(cert_key.cert[0].as_ref())?;
33+
let san = extract_sans_der(cert_key.cert[0].as_ref()).context("unable to extract SANs")?;
3334
if san.is_empty() {
3435
return Err(anyhow!(
3536
"no supported names found in SubjectAlternativeName extension"
@@ -44,7 +45,7 @@ pub fn pem_convert_to_certkey(key: &[u8], certs: &[u8]) -> Result<CertKey, Error
4445

4546
fn parse_pem(pem: &[Pem]) -> Result<Vec<CertKey>, Error> {
4647
pem.iter()
47-
.map(|x| pem_convert_to_certkey(&x.key, &x.cert))
48+
.map(|x| pem_convert_to_certkey(&x.0))
4849
.collect::<Result<Vec<_>, _>>()
4950
}
5051

@@ -316,29 +317,17 @@ pub mod test {
316317

317318
#[test]
318319
fn test_pem_convert_to_certkey() -> Result<(), Error> {
319-
let cert = pem_convert_to_certkey(KEY_1, CERT_1)?;
320+
let cert = pem_convert_to_certkey(&[KEY_1, CERT_1].concat())?;
320321
assert_eq!(cert.san, vec!["novg"]);
321-
let cert = pem_convert_to_certkey(KEY_2, CERT_2)?;
322+
let cert = pem_convert_to_certkey(&[KEY_2, CERT_2].concat())?;
322323
assert_eq!(cert.san, vec!["3658153f27e0"]);
323324
Ok(())
324325
}
325326

326327
#[tokio::test]
327328
async fn test_aggregator() -> Result<(), Error> {
328-
let prov1 = TestProvider(
329-
Pem {
330-
key: KEY_1.to_vec(),
331-
cert: CERT_1.to_vec(),
332-
},
333-
AtomicUsize::new(0),
334-
);
335-
let prov2 = TestProvider(
336-
Pem {
337-
key: KEY_2.to_vec(),
338-
cert: CERT_2.to_vec(),
339-
},
340-
AtomicUsize::new(0),
341-
);
329+
let prov1 = TestProvider(Pem([KEY_1, CERT_1].concat().to_vec()), AtomicUsize::new(0));
330+
let prov2 = TestProvider(Pem([KEY_2, CERT_2].concat().to_vec()), AtomicUsize::new(0));
342331

343332
let storage = Arc::new(storage::StorageKey::new(
344333
None,

src/tls/cert/providers/dir.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl ProvidesCertificates for Provider {
5757
v.path().to_string_lossy()
5858
))?;
5959

60-
certs.push(Pem { cert, key });
60+
certs.push(Pem([cert, key].concat()));
6161
}
6262

6363
debug!(
@@ -73,27 +73,31 @@ impl ProvidesCertificates for Provider {
7373
#[cfg(test)]
7474
mod test {
7575
use super::*;
76-
use crate::tls::cert::test::{CERT_1, KEY_1};
76+
use crate::tls::cert::test::{CERT_1, CERT_2, KEY_1, KEY_2};
7777

7878
#[tokio::test]
7979
async fn test() -> Result<(), Error> {
8080
let dir = tempfile::tempdir()?;
8181

82-
let keyfile = dir.path().join("foobar.key");
83-
std::fs::write(keyfile, KEY_1)?;
82+
let keyfile1 = dir.path().join("foobar1.key");
83+
std::fs::write(keyfile1, KEY_1)?;
84+
let certfile1 = dir.path().join("foobar1.pem");
85+
std::fs::write(certfile1, CERT_1)?;
8486

85-
let certfile = dir.path().join("foobar.pem");
86-
std::fs::write(certfile, CERT_1)?;
87+
let keyfile2 = dir.path().join("foobar2.key");
88+
std::fs::write(keyfile2, KEY_2)?;
89+
let certfile2 = dir.path().join("foobar2.pem");
90+
std::fs::write(certfile2, CERT_2)?;
8791

8892
// Some junk to be ignored
8993
std::fs::write(dir.path().join("foobar.baz"), b"foobar")?;
9094

9195
let prov = Provider::new(dir.path().to_path_buf());
9296
let certs = prov.get_certificates().await?;
9397

94-
assert_eq!(certs.len(), 1);
95-
assert_eq!(certs[0].key, KEY_1);
96-
assert_eq!(certs[0].cert, CERT_1);
98+
assert_eq!(certs.len(), 2);
99+
assert_eq!(certs[0].0, [CERT_1, KEY_1].concat());
100+
assert_eq!(certs[1].0, [CERT_2, KEY_2].concat());
97101

98102
Ok(())
99103
}

src/tls/cert/providers/file.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::path::PathBuf;
2+
3+
use anyhow::{Context, Error};
4+
use async_trait::async_trait;
5+
6+
use super::Pem;
7+
use crate::tls::cert::providers::ProvidesCertificates;
8+
9+
/// Loads the certificate chain & private key from provided PEM-encoded file
10+
#[derive(derive_new::new)]
11+
pub struct Provider {
12+
path: PathBuf,
13+
}
14+
15+
impl std::fmt::Debug for Provider {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
write!(f, "FileProvider({})", self.path.to_str().unwrap_or(""))
18+
}
19+
}
20+
21+
#[async_trait]
22+
impl ProvidesCertificates for Provider {
23+
async fn get_certificates(&self) -> Result<Vec<Pem>, Error> {
24+
let pem = std::fs::read(&self.path).context("unable to read the PEM file")?;
25+
Ok(vec![Pem(pem)])
26+
}
27+
}
28+
29+
#[cfg(test)]
30+
mod test {
31+
use super::*;
32+
use crate::tls::cert::test::{CERT_1, KEY_1};
33+
34+
#[tokio::test]
35+
async fn test() -> Result<(), Error> {
36+
let dir = tempfile::tempdir()?;
37+
38+
let pemfile = dir.path().join("foobar.pem");
39+
let pem = [KEY_1, CERT_1].concat();
40+
std::fs::write(&pemfile, &pem)?;
41+
42+
let prov = Provider::new(pemfile);
43+
let certs = prov.get_certificates().await?;
44+
45+
assert_eq!(certs.len(), 1);
46+
assert_eq!(certs[0].0, pem);
47+
48+
Ok(())
49+
}
50+
}

src/tls/cert/providers/issuer/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,7 @@ impl ProvidesCertificates for CertificatesImporter {
210210
.as_ref()
211211
.clone()
212212
.into_iter()
213-
.map(|x| Pem {
214-
cert: x.pair.1,
215-
key: x.pair.0,
216-
})
213+
.map(|x| Pem([x.pair.0, x.pair.1].concat()))
217214
.collect::<Vec<_>>();
218215

219216
Ok(certs)

src/tls/cert/providers/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pub mod dir;
2+
pub mod file;
23
pub mod issuer;
34

45
pub use dir::Provider as Dir;
6+
pub use file::Provider as File;
57
pub use issuer::CertificatesImporter as Issuer;
68

79
use async_trait::async_trait;
@@ -12,10 +14,7 @@ use std::sync::Arc;
1214
use crate::{cli::Cli, routing::domain::ProvidesCustomDomains};
1315

1416
#[derive(Clone, Debug, Eq, PartialEq)]
15-
pub struct Pem {
16-
pub cert: Vec<u8>,
17-
pub key: Vec<u8>,
18-
}
17+
pub struct Pem(pub Vec<u8>);
1918

2019
// Trait that the certificate providers should implement
2120
// It should return a vector of PEM-encoded cert-keys pairs

src/tls/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ pub async fn setup(
126126

127127
let mut cert_providers: Vec<Arc<dyn ProvidesCertificates>> = vec![];
128128

129+
// Create File providers
130+
for v in &cli.cert.cert_provider_file {
131+
cert_providers.push(Arc::new(providers::File::new(v.clone())));
132+
}
133+
129134
// Create Dir providers
130135
for v in &cli.cert.cert_provider_dir {
131136
cert_providers.push(Arc::new(providers::Dir::new(v.clone())));

0 commit comments

Comments
 (0)