Skip to content

Commit ad33ba1

Browse files
committed
make async tedge cert create/renew
Signed-off-by: Didier Wenzek <[email protected]>
1 parent 758f025 commit ad33ba1

File tree

4 files changed

+138
-99
lines changed

4 files changed

+138
-99
lines changed

crates/common/tedge_utils/src/paths.rs

+20-24
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,19 @@ pub fn set_permission(_file: &File, _mode: u32) -> Result<(), std::io::Error> {
143143
Ok(())
144144
}
145145

146-
pub fn validate_parent_dir_exists(path: impl AsRef<Path>) -> Result<(), PathsError> {
146+
pub async fn validate_parent_dir_exists(path: impl AsRef<Path>) -> Result<(), PathsError> {
147147
let path = path.as_ref();
148148
if path.is_relative() {
149-
Err(PathsError::RelativePathNotPermitted { path: path.into() })
150-
} else {
151-
match path.parent() {
152-
None => Err(PathsError::ParentDirNotFound { path: path.into() }),
153-
Some(parent) => {
154-
if !parent.exists() {
155-
Err(PathsError::DirNotFound {
156-
path: parent.into(),
157-
})
158-
} else {
159-
Ok(())
160-
}
161-
}
162-
}
149+
return Err(PathsError::RelativePathNotPermitted { path: path.into() });
150+
};
151+
match path.parent() {
152+
None => Err(PathsError::ParentDirNotFound { path: path.into() }),
153+
Some(parent) => match tokio::fs::try_exists(parent).await {
154+
Ok(true) => Ok(()),
155+
_ => Err(PathsError::DirNotFound {
156+
path: parent.into(),
157+
}),
158+
},
163159
}
164160
}
165161

@@ -168,23 +164,23 @@ mod tests {
168164
use super::*;
169165
use assert_matches::assert_matches;
170166

171-
#[test]
167+
#[tokio::test]
172168
#[cfg(unix)] // On windows the error is unexpectedly RelativePathNotPermitted
173-
fn validate_path_non_existent() {
174-
let result = validate_parent_dir_exists(Path::new("/non/existent/path"));
169+
async fn validate_path_non_existent() {
170+
let result = validate_parent_dir_exists(Path::new("/non/existent/path")).await;
175171
assert_matches!(result.unwrap_err(), PathsError::DirNotFound { .. });
176172
}
177173

178-
#[test]
174+
#[tokio::test]
179175
#[cfg(unix)] // On windows the error is unexpectedly RelativePathNotPermitted
180-
fn validate_parent_dir_non_existent() {
181-
let result = validate_parent_dir_exists(Path::new("/"));
176+
async fn validate_parent_dir_non_existent() {
177+
let result = validate_parent_dir_exists(Path::new("/")).await;
182178
assert_matches!(result.unwrap_err(), PathsError::ParentDirNotFound { .. });
183179
}
184180

185-
#[test]
186-
fn validate_parent_dir_relative_path() {
187-
let result = validate_parent_dir_exists(Path::new("test.txt"));
181+
#[tokio::test]
182+
async fn validate_parent_dir_relative_path() {
183+
let result = validate_parent_dir_exists(Path::new("test.txt")).await;
188184
assert_matches!(
189185
result.unwrap_err(),
190186
PathsError::RelativePathNotPermitted { .. }

crates/core/tedge/src/cli/certificate/create.rs

+81-51
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
use super::error::CertError;
22
use crate::cli::certificate::show::ShowCertCmd;
3-
use crate::command::Command;
3+
use crate::command::CommandAsync;
44
use crate::log::MaybeFancy;
55
use camino::Utf8PathBuf;
66
use certificate::KeyCertPair;
77
use certificate::KeyKind;
88
use certificate::NewCertificateConfig;
99
use certificate::PemCertificate;
10-
use std::fs::File;
11-
use std::fs::OpenOptions;
12-
use std::io::prelude::*;
10+
use std::fs::Permissions;
11+
use std::os::unix::fs::PermissionsExt;
1312
use std::path::Path;
14-
use tedge_utils::paths::set_permission;
1513
use tedge_utils::paths::validate_parent_dir_exists;
14+
use tokio::fs::File;
15+
use tokio::fs::OpenOptions;
16+
use tokio::io::AsyncWriteExt;
1617

1718
/// Create self-signed device certificate
1819
pub struct CreateCertCmd {
@@ -30,25 +31,29 @@ pub struct CreateCertCmd {
3031
pub group: String,
3132
}
3233

33-
impl Command for CreateCertCmd {
34+
#[async_trait::async_trait]
35+
impl CommandAsync for CreateCertCmd {
3436
fn description(&self) -> String {
3537
format!("create a test certificate for the device {}.", self.id)
3638
}
3739

38-
fn execute(&self) -> Result<(), MaybeFancy<anyhow::Error>> {
40+
async fn execute(&self) -> Result<(), MaybeFancy<anyhow::Error>> {
3941
let config = NewCertificateConfig::default();
40-
self.create_test_certificate(&config)?;
42+
self.create_test_certificate(&config).await?;
4143
eprintln!("Certificate was successfully created\n");
4244
let show_cert_cmd = ShowCertCmd {
4345
cert_path: self.cert_path.clone(),
4446
};
45-
show_cert_cmd.execute()?;
47+
show_cert_cmd.show_certificate().await?;
4648
Ok(())
4749
}
4850
}
4951

5052
impl CreateCertCmd {
51-
pub fn create_test_certificate(&self, config: &NewCertificateConfig) -> Result<(), CertError> {
53+
pub async fn create_test_certificate(
54+
&self,
55+
config: &NewCertificateConfig,
56+
) -> Result<(), CertError> {
5257
let cert = KeyCertPair::new_selfsigned_certificate(config, &self.id, &KeyKind::New)?;
5358

5459
let cert_path = &self.cert_path;
@@ -58,6 +63,7 @@ impl CreateCertCmd {
5863
&self.user,
5964
&self.group,
6065
)
66+
.await
6167
.map_err(|err| err.cert_context(cert_path.clone()))?;
6268

6369
let key_path = &self.key_path;
@@ -67,99 +73,119 @@ impl CreateCertCmd {
6773
&self.user,
6874
&self.group,
6975
)
76+
.await
7077
.map_err(|err| err.key_context(key_path.clone()))?;
7178
Ok(())
7279
}
7380
}
7481

75-
pub fn persist_new_public_key(
82+
pub async fn persist_new_public_key(
7683
cert_path: &Utf8PathBuf,
7784
pem_string: String,
7885
user: &str,
7986
group: &str,
8087
) -> Result<(), CertError> {
81-
validate_parent_dir_exists(cert_path).map_err(CertError::CertPathError)?;
82-
persist_public_key(create_new_file(cert_path, user, group)?, pem_string)?;
88+
validate_parent_dir_exists(cert_path)
89+
.await
90+
.map_err(CertError::CertPathError)?;
91+
let key_file = create_new_file(cert_path, user, group).await?;
92+
persist_public_key(key_file, pem_string).await?;
8393
Ok(())
8494
}
8595

86-
pub fn persist_new_private_key(
96+
pub async fn persist_new_private_key(
8797
key_path: &Utf8PathBuf,
8898
key: certificate::Zeroizing<String>,
8999
user: &str,
90100
group: &str,
91101
) -> Result<(), CertError> {
92-
validate_parent_dir_exists(key_path).map_err(CertError::KeyPathError)?;
93-
persist_private_key(create_new_file(key_path, user, group)?, key)?;
102+
validate_parent_dir_exists(key_path)
103+
.await
104+
.map_err(CertError::KeyPathError)?;
105+
let key_file = create_new_file(key_path, user, group).await?;
106+
persist_private_key(key_file, key).await?;
94107
Ok(())
95108
}
96109

97-
pub fn override_public_key(cert_path: &Utf8PathBuf, pem_string: String) -> Result<(), CertError> {
98-
validate_parent_dir_exists(cert_path).map_err(CertError::CertPathError)?;
99-
persist_public_key(override_file(cert_path)?, pem_string)?;
110+
pub async fn override_public_key(
111+
cert_path: &Utf8PathBuf,
112+
pem_string: String,
113+
) -> Result<(), CertError> {
114+
validate_parent_dir_exists(cert_path)
115+
.await
116+
.map_err(CertError::CertPathError)?;
117+
let key_file = override_file(cert_path).await?;
118+
persist_public_key(key_file, pem_string).await?;
100119
Ok(())
101120
}
102121

103-
fn create_new_file(
104-
path: impl AsRef<Path>,
122+
async fn create_new_file(
123+
path: &Utf8PathBuf,
105124
user: &str,
106125
group: &str,
107126
) -> Result<File, std::io::Error> {
108127
let file = OpenOptions::new()
109128
.write(true)
110129
.create_new(true)
111-
.open(path.as_ref())?;
130+
.open(path)
131+
.await?;
112132

113133
// Ignore errors - This was the behavior with the now deprecated user manager.
114134
// - When `tedge cert create` is not run as root, a certificate is created but owned by the user running the command.
115135
// - A better approach could be to remove this `chown` and run the command as mosquitto.
116-
let _ = tedge_utils::file::change_user_and_group(path.as_ref(), user, group);
136+
let _ = tedge_utils::file_async::change_user_and_group(
137+
path.into(),
138+
user.to_string(),
139+
group.to_string(),
140+
)
141+
.await;
117142

118143
Ok(file)
119144
}
120145

121-
fn override_file(path: impl AsRef<Path>) -> Result<File, std::io::Error> {
146+
async fn override_file(path: impl AsRef<Path>) -> Result<File, std::io::Error> {
122147
OpenOptions::new()
123148
.write(true)
124149
.create(true)
125150
.truncate(true)
126151
.open(path.as_ref())
152+
.await
127153
}
128154

129-
pub fn reuse_private_key(key_path: &Utf8PathBuf) -> Result<KeyKind, std::io::Error> {
130-
std::fs::read_to_string(key_path).map(|keypair_pem| KeyKind::Reuse { keypair_pem })
155+
pub async fn reuse_private_key(key_path: &Utf8PathBuf) -> Result<KeyKind, std::io::Error> {
156+
tokio::fs::read_to_string(key_path)
157+
.await
158+
.map(|keypair_pem| KeyKind::Reuse { keypair_pem })
131159
}
132160

133-
fn persist_private_key(
161+
async fn persist_private_key(
134162
mut key_file: File,
135163
cert_key: certificate::Zeroizing<String>, // Zero the private key on drop
136164
) -> Result<(), std::io::Error> {
137165
// Make sure the key is secret, before write
138-
set_permission(&key_file, 0o600)?;
139-
key_file.write_all(cert_key.as_bytes())?;
140-
key_file.sync_all()?;
166+
File::set_permissions(&key_file, Permissions::from_mode(0o600)).await?;
167+
key_file.write_all(cert_key.as_bytes()).await?;
168+
key_file.sync_all().await?;
141169

142170
// Prevent the key to be overwritten
143-
set_permission(&key_file, 0o400)?;
171+
File::set_permissions(&key_file, Permissions::from_mode(0o400)).await?;
144172
Ok(())
145173
}
146174

147-
fn persist_public_key(mut key_file: File, cert_pem: String) -> Result<(), std::io::Error> {
148-
key_file.write_all(cert_pem.as_bytes())?;
149-
key_file.sync_all()?;
175+
async fn persist_public_key(mut key_file: File, cert_pem: String) -> Result<(), std::io::Error> {
176+
key_file.write_all(cert_pem.as_bytes()).await?;
177+
key_file.sync_all().await?;
150178

151179
// Make the file public
152-
set_permission(&key_file, 0o444)?;
180+
File::set_permissions(&key_file, Permissions::from_mode(0o444)).await?;
153181
Ok(())
154182
}
155183

156-
pub fn cn_of_self_signed_certificate(cert_path: &Utf8PathBuf) -> Result<String, CertError> {
157-
let pem = PemCertificate::from_pem_file(cert_path).map_err(|err| match err {
158-
certificate::CertificateError::IoError { error, .. } => {
159-
CertError::IoError(error).cert_context(cert_path.clone())
160-
}
161-
from => CertError::CertificateError(from),
162-
})?;
184+
pub async fn cn_of_self_signed_certificate(cert_path: &Utf8PathBuf) -> Result<String, CertError> {
185+
let cert = tokio::fs::read_to_string(cert_path)
186+
.await
187+
.map_err(|err| CertError::IoError(err).cert_context(cert_path.clone()))?;
188+
let pem = PemCertificate::from_pem_string(&cert)?;
163189

164190
if pem.issuer()? == pem.subject()? {
165191
Ok(pem.subject_common_name()?)
@@ -177,8 +203,8 @@ mod tests {
177203
use std::fs;
178204
use tempfile::*;
179205

180-
#[test]
181-
fn basic_usage() {
206+
#[tokio::test]
207+
async fn basic_usage() {
182208
let dir = tempdir().unwrap();
183209
let cert_path = temp_file_path(&dir, "my-device-cert.pem");
184210
let key_path = temp_file_path(&dir, "my-device-key.pem");
@@ -193,15 +219,16 @@ mod tests {
193219
};
194220

195221
assert_matches!(
196-
cmd.create_test_certificate(&NewCertificateConfig::default()),
222+
cmd.create_test_certificate(&NewCertificateConfig::default())
223+
.await,
197224
Ok(())
198225
);
199226
assert_eq!(parse_pem_file(&cert_path).unwrap().tag, "CERTIFICATE");
200227
assert_eq!(parse_pem_file(&key_path).unwrap().tag, "PRIVATE KEY");
201228
}
202229

203-
#[test]
204-
fn check_certificate_is_not_overwritten() {
230+
#[tokio::test]
231+
async fn check_certificate_is_not_overwritten() {
205232
let dir = tempdir().unwrap();
206233

207234
let cert_path = temp_file_path(&dir, "my-device-cert.pem");
@@ -223,15 +250,16 @@ mod tests {
223250

224251
assert!(cmd
225252
.create_test_certificate(&NewCertificateConfig::default())
253+
.await
226254
.ok()
227255
.is_none());
228256

229257
assert_eq!(fs::read(&cert_path).unwrap(), cert_content.as_bytes());
230258
assert_eq!(fs::read(&key_path).unwrap(), key_content.as_bytes());
231259
}
232260

233-
#[test]
234-
fn create_certificate_in_non_existent_directory() {
261+
#[tokio::test]
262+
async fn create_certificate_in_non_existent_directory() {
235263
let dir = tempdir().unwrap();
236264
let key_path = temp_file_path(&dir, "my-device-key.pem");
237265
let cert_path = Utf8PathBuf::from("/non/existent/cert/path");
@@ -246,12 +274,13 @@ mod tests {
246274

247275
let cert_error = cmd
248276
.create_test_certificate(&NewCertificateConfig::default())
277+
.await
249278
.unwrap_err();
250279
assert_matches!(cert_error, CertError::CertPathError { .. });
251280
}
252281

253-
#[test]
254-
fn create_key_in_non_existent_directory() {
282+
#[tokio::test]
283+
async fn create_key_in_non_existent_directory() {
255284
let dir = tempdir().unwrap();
256285
let cert_path = temp_file_path(&dir, "my-device-cert.pem");
257286
let key_path = Utf8PathBuf::from("/non/existent/key/path");
@@ -266,6 +295,7 @@ mod tests {
266295

267296
let cert_error = cmd
268297
.create_test_certificate(&NewCertificateConfig::default())
298+
.await
269299
.unwrap_err();
270300
assert_matches!(cert_error, CertError::KeyPathError { .. });
271301
}

0 commit comments

Comments
 (0)