Skip to content

Commit a80a2a7

Browse files
committed
feat: add set-edge-ca-certificate command
1 parent c3cf7b4 commit a80a2a7

File tree

4 files changed

+256
-42
lines changed

4 files changed

+256
-42
lines changed

src/cli.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,30 @@ pub enum IdentityConfig {
179179
#[arg(short = 'p', long = "pack-image", value_enum)]
180180
compress_image: Option<Compression>,
181181
},
182+
/// set edge-ca certificates in order to support X.509 based DPS provisioning and certificate renewal via EST for web services provided by the device
183+
SetEdgeCaCertificate {
184+
/// path to intermediate full-chain-certificate pem file
185+
#[arg(short = 'c', long = "intermediate-full-chain-cert")]
186+
intermediate_full_chain_cert: PathBuf,
187+
/// path to intermediate key pem file
188+
#[arg(short = 'k', long = "intermediate-key")]
189+
intermediate_key: PathBuf,
190+
/// path to wic image file (optionally compressed with xz, bzip2 or gzip)
191+
#[arg(short = 'i', long = "image")]
192+
image: PathBuf,
193+
/// subject name for the edge ca
194+
#[arg(short = 's', long = "subject")]
195+
subject: String,
196+
/// period of validity in days
197+
#[arg(short = 'D', long = "days")]
198+
days: u32,
199+
/// optional: generate bmap file (currently not working in docker image)
200+
#[arg(short = 'b', long = "generate-bmap-file")]
201+
generate_bmap: bool,
202+
/// optional: pack image [xz, bzip2, gzip] (for xz default level '9' is used, which can be overwritten by setting 'XZ_COMPRESSION_LEVEL=')
203+
#[arg(short = 'p', long = "pack-image", value_enum)]
204+
compress_image: Option<Compression>,
205+
},
182206
}
183207

184208
#[derive(Parser, Debug)]

src/file/mod.rs

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,35 +115,91 @@ pub fn set_identity_config(
115115
copy_to_image(&file_copies, image_file)
116116
}
117117

118-
pub fn set_device_cert(
119-
intermediate_full_chain_cert_path: Option<&Path>,
120-
device_cert_path: &Path,
121-
device_key_path: &Path,
118+
struct IntermediateFullChainCertDescr<'a> {
119+
src: &'a Path,
120+
name: &'a str,
121+
}
122+
123+
struct CopyDescr<'a> {
124+
src: &'a Path,
125+
dest: &'a Path,
126+
}
127+
128+
fn set_cert(
129+
intermediate_full_chain_cert: Option<IntermediateFullChainCertDescr>,
130+
cert: CopyDescr,
131+
key: CopyDescr,
122132
image_file: &Path,
123133
) -> Result<()> {
124134
let mut copy_params = vec![
125-
FileCopyToParams::new(
126-
device_cert_path,
127-
Partition::cert,
128-
Path::new("/priv/device_id_cert.pem"),
129-
),
130-
FileCopyToParams::new(
131-
device_key_path,
132-
Partition::cert,
133-
Path::new("/priv/device_id_cert_key.pem"),
134-
),
135+
FileCopyToParams::new(cert.src, Partition::cert, cert.dest),
136+
FileCopyToParams::new(key.src, Partition::cert, key.dest),
135137
];
136138

137-
if let Some(p) = intermediate_full_chain_cert_path {
139+
if let Some(p) = intermediate_full_chain_cert {
138140
copy_params.append(&mut vec![
139-
FileCopyToParams::new(p, Partition::cert, Path::new("/priv/ca.crt.pem")),
140-
FileCopyToParams::new(p, Partition::cert, Path::new("/ca/ca.crt")),
141+
FileCopyToParams::new(
142+
p.src,
143+
Partition::cert,
144+
Path::new(&format!("/priv/{}.crt.pem", p.name)),
145+
),
146+
FileCopyToParams::new(
147+
p.src,
148+
Partition::cert,
149+
Path::new(&format!("/ca/{}.crt", p.name)),
150+
),
141151
])
142152
}
143153

144154
copy_to_image(&copy_params, image_file)
145155
}
146156

157+
pub fn set_device_cert(
158+
intermediate_full_chain_cert_path: Option<&Path>,
159+
device_cert_path: &Path,
160+
device_key_path: &Path,
161+
image_file: &Path,
162+
) -> Result<()> {
163+
let full_chain_descr = intermediate_full_chain_cert_path
164+
.map(|p| IntermediateFullChainCertDescr { src: p, name: "ca" });
165+
166+
set_cert(
167+
full_chain_descr,
168+
CopyDescr {
169+
src: device_cert_path,
170+
dest: Path::new("/priv/device_id_cert.pem"),
171+
},
172+
CopyDescr {
173+
src: device_key_path,
174+
dest: Path::new("/priv/device_id_cert_key.pem"),
175+
},
176+
image_file,
177+
)
178+
}
179+
180+
pub fn set_edge_ca_cert(
181+
intermediate_full_chain_cert_path: Option<&Path>,
182+
device_cert_path: &Path,
183+
device_key_path: &Path,
184+
image_file: &Path,
185+
) -> Result<()> {
186+
let full_chain_descr = intermediate_full_chain_cert_path
187+
.map(|p| IntermediateFullChainCertDescr { src: p, name: "ca" });
188+
189+
set_cert(
190+
full_chain_descr,
191+
CopyDescr {
192+
src: device_cert_path,
193+
dest: Path::new("/priv/edge_ca_cert.pem"),
194+
},
195+
CopyDescr {
196+
src: device_key_path,
197+
dest: Path::new("/priv/edge_ca_cert_key.pem"),
198+
},
199+
image_file,
200+
)
201+
}
202+
147203
pub fn set_iot_hub_device_update_config(du_config_file: &Path, image_file: &Path) -> Result<()> {
148204
device_update::validate_config(du_config_file)?;
149205

src/lib.rs

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ use cli::{
1414
Docker::Inject,
1515
File::{CopyFromImage, CopyToImage},
1616
IdentityConfig::{
17-
SetConfig, SetDeviceCertificate, SetDeviceCertificateNoEst, SetIotLeafSasConfig,
18-
SetIotedgeGatewayConfig,
17+
SetConfig, SetDeviceCertificate, SetDeviceCertificateNoEst, SetEdgeCaCertificate,
18+
SetIotLeafSasConfig, SetIotedgeGatewayConfig,
1919
},
2020
IotHubDeviceUpdate::{self, SetDeviceConfig as IotHubDeviceUpdateSet},
2121
SshConfig::{SetCertificate, SetConnection},
2222
};
2323
use file::{compression::Compression, functions::FileCopyToParams};
2424
use log::error;
25-
use std::{fs, path::PathBuf};
25+
use std::{fs, path::Path, path::PathBuf};
2626
use tokio::fs::remove_dir_all;
2727
use uuid::Uuid;
2828

@@ -150,6 +150,46 @@ where
150150
Ok(())
151151
}
152152

153+
struct CertInfo {
154+
cert_path: PathBuf,
155+
key_path: PathBuf,
156+
}
157+
158+
struct CertificateOptions<'a> {
159+
intermediate_full_chain_cert: &'a Path,
160+
intermediate_key: &'a Path,
161+
target_cert: &'a str,
162+
target_key: &'a str,
163+
subject: &'a str,
164+
validity_days: u32,
165+
}
166+
167+
fn create_image_cert(image: &Path, cert_opts: CertificateOptions) -> Result<CertInfo> {
168+
let intermediate_full_chain_cert_str =
169+
std::fs::read_to_string(cert_opts.intermediate_full_chain_cert)
170+
.context("create_and_set_image_cert: couldn't read intermediate fullchain cert")?;
171+
let intermediate_key_str = std::fs::read_to_string(cert_opts.intermediate_key)
172+
.context("create_and_set_image_cert: couldn't read intermediate key")?;
173+
let crypto = omnect_crypto::Crypto::new(
174+
intermediate_key_str.as_bytes(),
175+
intermediate_full_chain_cert_str.as_bytes(),
176+
)?;
177+
let (cert_pem, key_pem) = crypto
178+
.create_cert_and_key(cert_opts.subject, &None, cert_opts.validity_days)
179+
.context("create_and_set_image_cert: couldn't create device cert and key")?;
180+
181+
let cert_path = file::get_file_path(image, cert_opts.target_cert)?;
182+
let key_path = file::get_file_path(image, cert_opts.target_key)?;
183+
184+
fs::write(&cert_path, cert_pem).context("create_and_set_image_cert: write device_cert_path")?;
185+
fs::write(&key_path, key_pem).context("create_and_set_image_cert: write device_key_path")?;
186+
187+
Ok(CertInfo {
188+
cert_path: cert_path.to_path_buf(),
189+
key_path: key_path.to_path_buf(),
190+
})
191+
}
192+
153193
pub fn run() -> Result<()> {
154194
match cli::from_args() {
155195
Command::Docker(Inject {
@@ -211,32 +251,55 @@ pub fn run() -> Result<()> {
211251
generate_bmap,
212252
compress_image,
213253
}) => {
214-
let intermediate_full_chain_cert_str =
215-
std::fs::read_to_string(&intermediate_full_chain_cert)
216-
.context("couldn't read intermediate fullchain cert")?;
217-
let intermediate_key_str = std::fs::read_to_string(intermediate_key)
218-
.context("couldn't read intermediate key")?;
219-
let crypto = omnect_crypto::Crypto::new(
220-
intermediate_key_str.as_bytes(),
221-
intermediate_full_chain_cert_str.as_bytes(),
222-
)?;
223-
let (device_cert_pem, device_key_pem) = crypto
224-
.create_cert_and_key(&device_id, &None, days)
225-
.context("couldn't create device cert and key")?;
226-
227-
let device_cert_path = file::get_file_path(&image, "device_cert_path.pem")?;
228-
let device_key_path = file::get_file_path(&image, "device_key_path.key.pem")?;
229-
230-
fs::write(&device_cert_path, device_cert_pem)
231-
.context("set_device_cert: write device_cert_path")?;
232-
fs::write(&device_key_path, device_key_pem)
233-
.context("set_device_cert: write device_key_path")?;
254+
let cert_info = create_image_cert(
255+
&image,
256+
CertificateOptions {
257+
intermediate_full_chain_cert: &intermediate_full_chain_cert,
258+
intermediate_key: &intermediate_key,
259+
target_cert: "device_cert_path.pem",
260+
target_key: "device_key_path.key.pem",
261+
subject: &device_id,
262+
validity_days: days,
263+
},
264+
)
265+
.context("set_edge_ca_certificate: could not create certificate")?;
234266

235267
run_image_command(image, generate_bmap, compress_image, |img| {
236268
file::set_device_cert(
237269
Some(&intermediate_full_chain_cert),
238-
&device_cert_path,
239-
&device_key_path,
270+
&cert_info.cert_path,
271+
&cert_info.key_path,
272+
img,
273+
)
274+
})?
275+
}
276+
Command::Identity(SetEdgeCaCertificate {
277+
intermediate_full_chain_cert,
278+
intermediate_key,
279+
image,
280+
subject,
281+
days,
282+
generate_bmap,
283+
compress_image,
284+
}) => {
285+
let cert_info = create_image_cert(
286+
&image,
287+
CertificateOptions {
288+
intermediate_full_chain_cert: &intermediate_full_chain_cert,
289+
intermediate_key: &intermediate_key,
290+
target_cert: "edge_ca_cert_path.pem",
291+
target_key: "edge_ca_path.key.pem",
292+
subject: &subject,
293+
validity_days: days,
294+
},
295+
)
296+
.context("set_edge_ca_certificate: could not create certificate")?;
297+
298+
run_image_command(image, generate_bmap, compress_image, |img| {
299+
file::set_edge_ca_cert(
300+
Some(&intermediate_full_chain_cert),
301+
&cert_info.cert_path,
302+
&cert_info.key_path,
240303
img,
241304
)
242305
})?

tests/integration_tests.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,77 @@ fn check_set_device_cert_no_est() {
525525
));
526526
}
527527

528+
#[test]
529+
fn check_set_edge_ca_cert() {
530+
let tr = Testrunner::new(function_name!().split("::").last().unwrap());
531+
let image_path = tr.to_pathbuf("testfiles/image.wic");
532+
let intermediate_full_chain_crt_path = tr.to_pathbuf("testfiles/test-int-ca_fullchain.pem");
533+
let intermediate_full_chain_crt_key_path = tr.to_pathbuf("testfiles/test-int-ca.key");
534+
535+
let mut set_device_certificate = Command::cargo_bin("omnect-cli").unwrap();
536+
let assert = set_device_certificate
537+
.arg("identity")
538+
.arg("set-edge-ca-certificate")
539+
.arg("-c")
540+
.arg(&intermediate_full_chain_crt_path)
541+
.arg("-k")
542+
.arg(&intermediate_full_chain_crt_key_path)
543+
.arg("-i")
544+
.arg(&image_path)
545+
.arg("-s")
546+
.arg("edge-ca")
547+
.arg("-D")
548+
.arg("1")
549+
.assert();
550+
assert.success();
551+
552+
let mut edge_ca_cert_out_path = tr.pathbuf();
553+
edge_ca_cert_out_path.push("dir1");
554+
create_dir_all(edge_ca_cert_out_path.clone()).unwrap();
555+
556+
let mut edge_ca_cert_key_out_path = edge_ca_cert_out_path.clone();
557+
let mut ca_crt_pem_out_path = edge_ca_cert_out_path.clone();
558+
let mut ca_pem_out_path = edge_ca_cert_out_path.clone();
559+
560+
edge_ca_cert_out_path.push("edge_ca_cert_out_path");
561+
let edge_ca_cert_out_path = edge_ca_cert_out_path.to_str().unwrap();
562+
563+
edge_ca_cert_key_out_path.push("edge_ca_cert_key_out_path");
564+
let edge_ca_cert_key_out_path = edge_ca_cert_key_out_path.to_str().unwrap();
565+
566+
ca_crt_pem_out_path.push("ca_crt_pem_out_path");
567+
let ca_crt_pem_out_path = ca_crt_pem_out_path.to_str().unwrap();
568+
569+
ca_pem_out_path.push("ca_pem_out_path");
570+
let ca_pem_out_path = ca_pem_out_path.to_str().unwrap();
571+
572+
let mut copy_from_img = Command::cargo_bin("omnect-cli").unwrap();
573+
let assert = copy_from_img
574+
.arg("file")
575+
.arg("copy-from-image")
576+
.arg("-f")
577+
.arg(format!(
578+
"cert:/priv/edge_ca_cert.pem,{edge_ca_cert_out_path}"
579+
))
580+
.arg("-f")
581+
.arg(format!(
582+
"cert:/priv/edge_ca_cert_key.pem,{edge_ca_cert_key_out_path}"
583+
))
584+
.arg("-f")
585+
.arg(format!("cert:/priv/ca.crt.pem,{ca_crt_pem_out_path}"))
586+
.arg("-f")
587+
.arg(format!("cert:/ca/ca.crt,{ca_pem_out_path}"))
588+
.arg("-i")
589+
.arg(&image_path)
590+
.assert();
591+
assert.success();
592+
593+
assert!(file_diff::diff(
594+
intermediate_full_chain_crt_path.to_str().unwrap(),
595+
ca_crt_pem_out_path
596+
));
597+
}
598+
528599
#[test]
529600
fn check_set_iot_hub_device_update_template() {
530601
let tr = Testrunner::new(function_name!().split("::").last().unwrap());

0 commit comments

Comments
 (0)