Skip to content

Commit c3efbe0

Browse files
committed
working tedge cert download with CSR signed by PCKS 11 token
Signed-off-by: Marcel Guzik <marcel.guzik@cumulocity.com>
1 parent 822d012 commit c3efbe0

File tree

13 files changed

+236
-80
lines changed

13 files changed

+236
-80
lines changed

Cargo.lock

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

crates/common/certificate/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ anyhow = { workspace = true }
1717
asn1-rs = { workspace = true }
1818
base64 = { workspace = true }
1919
camino = { workspace = true }
20+
pem.workspace = true
2021
rcgen = { workspace = true }
2122
reqwest = { workspace = true, optional = true, features = [
2223
"rustls-tls-native-roots",

crates/common/certificate/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::Context;
2+
use asn1_rs::nom::HexDisplay;
23
use camino::Utf8Path;
34
use device_id::DeviceIdError;
45
use rcgen::Certificate;
@@ -9,8 +10,12 @@ use sha1::Sha1;
910
use std::path::Path;
1011
use std::path::PathBuf;
1112
use tedge_p11_server::CryptokiConfig;
13+
use tedge_p11_server::CryptokiConfigDirect;
1214
use time::Duration;
1315
use time::OffsetDateTime;
16+
use tracing::debug;
17+
use tracing::instrument;
18+
use tracing::trace;
1419
use x509_parser::oid_registry;
1520
use x509_parser::public_key::PublicKey;
1621
pub use zeroize::Zeroizing;
@@ -198,6 +203,49 @@ impl KeyKind {
198203
algorithm,
199204
}))
200205
}
206+
207+
#[instrument]
208+
pub fn from_cryptoki_and_public_key_pem(
209+
cryptoki_config: CryptokiConfig,
210+
private_key_label: String,
211+
public_key_pem: String,
212+
) -> Result<Self, CertificateError> {
213+
let public_key = pem::parse(public_key_pem).unwrap();
214+
let public_key_raw = public_key.into_contents();
215+
trace!("pubkey raw: {public_key_raw:x?}");
216+
217+
// TODO: implement other algs
218+
let algorithm = &rcgen::PKCS_RSA_SHA256;
219+
220+
// construct a URI that uses private key we just created to sign
221+
let mut cryptoki_config = cryptoki_config;
222+
let uri = match cryptoki_config {
223+
CryptokiConfig::Direct(CryptokiConfigDirect { ref mut uri, .. }) => uri,
224+
CryptokiConfig::SocketService { ref mut uri, .. } => uri,
225+
};
226+
let private_key_uri = match uri {
227+
Some(uri) if uri.contains("object=") => {
228+
let mut uri: String = uri
229+
.strip_prefix("pkcs11:")
230+
.unwrap_or("")
231+
.split(';')
232+
.filter(|a| !a.contains("object="))
233+
.collect();
234+
235+
format!("pkcs11:{uri};object={private_key_label}")
236+
}
237+
Some(uri) => format!("{uri};object={private_key_label}"),
238+
None => format!("pkcs11:object={private_key_label}"),
239+
};
240+
*uri = Some(private_key_uri.into());
241+
debug!(?uri);
242+
243+
Ok(Self::ReuseRemote(RemoteKeyPair {
244+
cryptoki_config,
245+
public_key_raw,
246+
algorithm,
247+
}))
248+
}
201249
}
202250

203251
/// A key pair using a remote private key.
@@ -252,6 +300,7 @@ impl rcgen::RemoteKeyPair for RemoteKeyPair {
252300
// the error here is not PEM-related, but we need to return a foreign error type, and there
253301
// are no other better variants that could let us return context, so we'll have to use this
254302
// until `rcgen::Error::RemoteKeyError` can take a parameter
303+
trace!(?self.cryptoki_config, msg = %String::from_utf8_lossy(msg), "sign");
255304
let signer = tedge_p11_server::signing_key(self.cryptoki_config.clone())
256305
.map_err(|e| rcgen::Error::PemError(e.to_string()))?;
257306
signer

crates/core/tedge/src/cli/certificate/c8y/download.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::cli::certificate::c8y::create_device_csr;
21
use crate::cli::certificate::c8y::read_csr_from_file;
32
use crate::cli::certificate::c8y::store_device_cert;
43
use crate::cli::certificate::create_csr::Key;
4+
use crate::cli::certificate::create_device_csr;
55
use crate::cli::certificate::show::ShowCertCmd;
66
use crate::command::Command;
77
use crate::error;
@@ -113,6 +113,7 @@ impl DownloadCertCmd {
113113
error!("Fail to extract a certificate from the response returned by {c8y_url}");
114114
}
115115
Ok(response) => {
116+
tracing::error!(?response);
116117
let error = Self::c8y_error_message(response).await;
117118
error!("The device {common_name} is not registered yet on {c8y_url}: {error}");
118119
}
@@ -182,7 +183,7 @@ impl DownloadCertCmd {
182183

183184
async fn c8y_error_message(response: Response) -> String {
184185
let status = response.status().to_string();
185-
if let Ok(C8yAPIError { message, .. }) = response.json().await {
186+
if let Ok(C8yAPIError { message, .. }) = dbg!(response.json().await) {
186187
format!("{status}: {}", message)
187188
} else {
188189
status

crates/core/tedge/src/cli/certificate/c8y/mod.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,6 @@ pub use download::DownloadCertCmd;
1212
pub use renew::RenewCertCmd;
1313
pub use upload::UploadCertCmd;
1414

15-
/// Create a device private key and CSR
16-
///
17-
/// Return the CSR in the format expected by c8y CA
18-
async fn create_device_csr(
19-
common_name: String,
20-
key: super::create_csr::Key,
21-
current_cert: Option<Utf8PathBuf>,
22-
csr_path: Utf8PathBuf,
23-
csr_template: CsrTemplate,
24-
) -> Result<(), CertError> {
25-
let create_cmd = CreateCsrCmd {
26-
id: common_name,
27-
csr_path: csr_path.clone(),
28-
key,
29-
current_cert,
30-
user: "tedge".to_string(),
31-
group: "tedge".to_string(),
32-
csr_template,
33-
};
34-
create_cmd.create_certificate_signing_request().await?;
35-
Ok(())
36-
}
37-
3815
/// Return the CSR in the format expected by c8y CA
3916
async fn read_csr_from_file(csr_path: &Utf8PathBuf) -> Result<String, CertError> {
4017
let csr = read_cert_to_string(csr_path).await?;

crates/core/tedge/src/cli/certificate/c8y/renew.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::certificate_cn;
2-
use crate::cli::certificate::c8y::create_device_csr;
32
use crate::cli::certificate::c8y::read_csr_from_file;
43
use crate::cli::certificate::c8y::store_device_cert;
54
use crate::cli::certificate::create_csr::Key;
5+
use crate::cli::certificate::create_device_csr;
66
use crate::cli::certificate::show::ShowCertCmd;
77
use crate::command::Command;
88
use crate::get_webpki_error_from_reqwest;

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ pub enum TEdgeCertCli {
6666

6767
#[arg(long, default_value = "256")]
6868
curve: u16,
69+
70+
/// The device identifier to be used as the common name for the certificate
71+
#[clap(long = "device-id", global = true)]
72+
id: Option<String>,
73+
74+
#[clap(subcommand)]
75+
cloud: Option<CloudArg>,
6976
},
7077

7178
/// Renew the device certificate
@@ -209,7 +216,11 @@ impl BuildCommand for TEdgeCertCli {
209216
.transpose()?;
210217
let cryptoki = config.device.cryptoki_config(cloud_config)?;
211218
let key = cryptoki
212-
.map(super::create_csr::Key::Cryptoki)
219+
.map(|config| super::create_csr::Key::Cryptoki {
220+
config,
221+
privkey_label: None,
222+
pubkey_pem: None,
223+
})
213224
.unwrap_or(Key::Local(
214225
config.device_key_path(cloud.as_ref())?.to_owned(),
215226
));
@@ -242,14 +253,24 @@ impl BuildCommand for TEdgeCertCli {
242253
label,
243254
r#type,
244255
curve,
245-
} => CreateKeyCmd {
246-
bits,
247-
label,
248-
r#type,
249-
curve,
250-
}
251-
.into_boxed(),
252256

257+
id,
258+
cloud,
259+
} => {
260+
let cloud: Option<Cloud> = cloud.map(<_>::try_into).transpose()?;
261+
262+
CreateKeyCmd {
263+
bits,
264+
label,
265+
r#type,
266+
curve,
267+
268+
device_id: get_device_id(id, config, &cloud)?,
269+
csr_template,
270+
csr_path: config.device_csr_path(cloud.as_ref())?.to_owned(),
271+
}
272+
.into_boxed()
273+
}
253274
TEdgeCertCli::Show {
254275
cloud,
255276
cert_path,
@@ -331,7 +352,11 @@ impl BuildCommand for TEdgeCertCli {
331352

332353
let cryptoki = config.device.cryptoki_config(Some(c8y_config))?;
333354
let key = cryptoki
334-
.map(super::create_csr::Key::Cryptoki)
355+
.map(|config| super::create_csr::Key::Cryptoki {
356+
config,
357+
privkey_label: None,
358+
pubkey_pem: None,
359+
})
335360
.unwrap_or(Key::Local(
336361
config
337362
.device_key_path(Some(tedge_config::tedge_toml::Cloud::C8y(
@@ -419,7 +444,11 @@ impl BuildCommand for TEdgeCertCli {
419444
.transpose()?;
420445
let cryptoki = config.device.cryptoki_config(cloud_config)?;
421446
let key = cryptoki
422-
.map(super::create_csr::Key::Cryptoki)
447+
.map(|config| super::create_csr::Key::Cryptoki {
448+
config,
449+
privkey_label: None,
450+
pubkey_pem: None,
451+
})
423452
.unwrap_or(Key::Local(
424453
config.device_key_path(cloud.as_ref())?.to_owned(),
425454
));

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ pub struct CreateCsrCmd {
3939
#[derive(Debug, Clone)]
4040
pub enum Key {
4141
Local(Utf8PathBuf),
42-
Cryptoki(CryptokiConfig),
42+
Cryptoki {
43+
config: CryptokiConfig,
44+
// TODO: move it where it makes sense
45+
privkey_label: Option<String>,
46+
pubkey_pem: Option<String>,
47+
},
4348
}
4449

4550
#[async_trait::async_trait]
@@ -67,12 +72,22 @@ impl CreateCsrCmd {
6772
.await
6873
.map_err(|e| CertError::IoError(e).key_context(key_path.clone()))?,
6974

70-
Key::Cryptoki(cryptoki) => {
71-
let current_cert = self
72-
.current_cert
73-
.clone()
74-
.context("Need an existing cert when using an HSM")?;
75-
KeyKind::from_cryptoki_and_existing_cert(cryptoki.clone(), &current_cert)?
75+
Key::Cryptoki {
76+
config,
77+
privkey_label,
78+
pubkey_pem,
79+
} => {
80+
let current_cert = self.current_cert.clone();
81+
match current_cert {
82+
Some(current_cert) => {
83+
KeyKind::from_cryptoki_and_existing_cert(config.clone(), &current_cert)?
84+
}
85+
None => KeyKind::from_cryptoki_and_public_key_pem(
86+
config.clone(),
87+
privkey_label.clone().unwrap(),
88+
pubkey_pem.as_ref().unwrap().clone(),
89+
)?,
90+
}
7691
}
7792
};
7893
debug!(?previous_key);

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
use camino::Utf8PathBuf;
2+
use certificate::CsrTemplate;
13
use clap::ValueEnum;
24
use tedge_config::TEdgeConfig;
35
use tedge_p11_server::pkcs11::{CreateKeyParams, KeyTypeParams};
6+
use tedge_p11_server::CryptokiConfig;
47

8+
use crate::cli::common::Cloud;
59
use crate::command::Command;
610
use crate::log::MaybeFancy;
711

@@ -10,6 +14,13 @@ pub struct CreateKeyCmd {
1014
pub curve: u16,
1115
pub label: String,
1216
pub r#type: KeyType,
17+
18+
/// The device identifier to be used as the common name for the certificate
19+
pub device_id: String,
20+
21+
pub csr_template: CsrTemplate,
22+
23+
pub csr_path: Utf8PathBuf,
1324
}
1425

1526
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
@@ -38,11 +49,40 @@ impl Command for CreateKeyCmd {
3849
token: None,
3950
label: self.label.clone(),
4051
};
52+
53+
// generate a keypair
54+
// TODO: don't assume it's RSA
4155
let pubkey_der = pkcs11client.create_key(None, params)?;
4256
let pubkey_pem = pem::Pem::new("PUBLIC KEY", pubkey_der);
4357
let pubkey_pem = pem::encode(&pubkey_pem);
4458

4559
eprintln!("New keypair was successfully created.");
60+
61+
// use returned private key to create a CSR
62+
63+
// isn't device_id the same as certificate_cn?
64+
let common_name = crate::certificate_cn(
65+
&config
66+
.device_cert_path(None::<&Cloud>)
67+
.unwrap()
68+
.to_path_buf(),
69+
)
70+
.await?;
71+
72+
let cryptoki_config = config.device.cryptoki_config(None).unwrap().unwrap();
73+
let key = super::create_csr::Key::Cryptoki {
74+
config: cryptoki_config,
75+
privkey_label: Some(self.label.clone()),
76+
pubkey_pem: Some(pubkey_pem.clone()),
77+
};
78+
let csr_path = config
79+
.device_csr_path(None::<&Cloud>)
80+
.unwrap()
81+
.to_path_buf();
82+
83+
super::create_device_csr(common_name, key, None, csr_path, self.csr_template.clone())
84+
.await?;
85+
4686
eprintln!("Public key:\n{pubkey_pem}\n");
4787

4888
Ok(())

0 commit comments

Comments
 (0)