Skip to content

Commit 6e657e5

Browse files
committed
feat: Add tedge cert create-key command
Signed-off-by: Marcel Guzik <[email protected]>
1 parent 506ea39 commit 6e657e5

File tree

14 files changed

+969
-34
lines changed

14 files changed

+969
-34
lines changed

Cargo.lock

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

crates/common/certificate/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use tedge_p11_server::service::ChooseSchemeRequest;
1111
use tedge_p11_server::CryptokiConfig;
1212
use time::Duration;
1313
use time::OffsetDateTime;
14+
use tracing::trace;
1415
use x509_parser::public_key::PublicKey;
1516
pub use zeroize::Zeroizing;
1617
#[cfg(feature = "reqwest")]
@@ -274,6 +275,7 @@ impl rcgen::SigningKey for RemoteKeyPair {
274275
// the error here is not PEM-related, but we need to return a foreign error type, and there
275276
// are no other better variants that could let us return context, so we'll have to use this
276277
// until `rcgen::Error::RemoteKeyError` can take a parameter
278+
trace!(?self.cryptoki_config, msg = %String::from_utf8_lossy(msg), "sign");
277279
let signer = tedge_p11_server::signing_key(self.cryptoki_config.clone())
278280
.map_err(|e| rcgen::Error::PemError(e.to_string()))?;
279281
signer
@@ -284,7 +286,7 @@ impl rcgen::SigningKey for RemoteKeyPair {
284286

285287
/// Signature algorithms that can be used for generating a CSR
286288
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287-
enum SignatureAlgorithm {
289+
pub enum SignatureAlgorithm {
288290
RsaPkcs1Sha256,
289291
EcdsaP256Sha256,
290292
EcdsaP384Sha384,

crates/core/tedge/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ default-run = "tedge"
1414
[dependencies]
1515
anstyle = { workspace = true }
1616
anyhow = { workspace = true }
17+
asn1-rs.workspace = true
1718
async-trait = { workspace = true }
1819
base64 = { workspace = true }
1920
c8y-firmware-plugin = { workspace = true }
@@ -32,7 +33,7 @@ mime_guess = { workspace = true }
3233
mqtt_channel = { workspace = true }
3334
nix = { workspace = true }
3435
pad = { workspace = true }
35-
pem = { workspace = true }
36+
pem.workspace = true
3637
rasn = { workspace = true }
3738
rasn-cms = { workspace = true }
3839
reqwest = { workspace = true, features = [
@@ -51,6 +52,7 @@ tedge-agent = { workspace = true }
5152
tedge-apt-plugin = { workspace = true }
5253
tedge-file-log-plugin = { workspace = true }
5354
tedge-mapper = { workspace = true, default-features = false }
55+
tedge-p11-server = { workspace = true }
5456
tedge-watchdog = { workspace = true }
5557
tedge-write = { workspace = true }
5658
tedge_api = { workspace = true }

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ use super::show::ShowCertCmd;
66
use crate::certificate_is_self_signed;
77
use crate::cli::certificate::c8y;
88
use crate::cli::certificate::create_csr::Key;
9+
use crate::cli::certificate::create_key::CreateKeyHsmCmd;
10+
use crate::cli::certificate::create_key::EcCurve;
11+
use crate::cli::certificate::create_key::KeyType;
12+
use crate::cli::certificate::create_key::RsaBits;
913
use crate::cli::common::Cloud;
1014
use crate::cli::common::CloudArg;
1115
use crate::command::BuildCommand;
1216
use crate::command::Command;
1317
use crate::CertificateShift;
1418
use crate::ConfigError;
1519
use anyhow::anyhow;
20+
use anyhow::Context;
1621
use c8y_api::http_proxy::C8yEndPoint;
22+
use camino::Utf8Path;
1723
use camino::Utf8PathBuf;
1824
use certificate::CsrTemplate;
1925
use clap::ValueHint;
@@ -51,6 +57,73 @@ pub enum TEdgeCertCli {
5157
cloud: Option<CloudArg>,
5258
},
5359

60+
/// Generate a new keypair on the PKCS #11 token and select it to be used.
61+
///
62+
/// Can be used to generate a keypair on the TOKEN. If TOKEN argument is not provided, the
63+
/// command prints the available tokens.
64+
///
65+
/// If TOKEN is provided, the command generates an RSA or an ECDSA keypair on the token. When
66+
/// using RSA, `--bits` is used to set the size of the key, when using ECDSA, `--curve` is used.
67+
///
68+
/// After the key is generated, tedge config is updated to use the new key using
69+
/// `device.key_uri` property. Depending on the selected cloud, we use `device.key_uri` setting
70+
/// for that cloud, e.g. `create-key-hsm c8y` will write to `c8y.device.key_uri`.
71+
CreateKeyHsm {
72+
/// Human readable description (CKA_LABEL attribute) for the key.
73+
#[arg(long, default_value = "tedge")]
74+
label: String,
75+
76+
/// Key identifier for the keypair (CKA_ID attribute).
77+
///
78+
/// If provided and no object exists on the token with the same ID, this will be the ID of
79+
/// the new keypair. If an object with this ID already exists, the operation will return an
80+
/// error. If not provided, a random ID will be generated and used by the keypair.
81+
///
82+
/// The id shall be provided as a sequence of hex digits without `0x` prefix, optionally
83+
/// separated by spaces, e.g. `--id 010203` or `--id "01 02 03"`.
84+
#[arg(long)]
85+
id: Option<String>,
86+
87+
/// The type of the key.
88+
#[arg(long, default_value = "ecdsa")]
89+
r#type: KeyType,
90+
91+
/// The size of the RSA keys in bits. Should only be used with --type rsa.
92+
#[arg(long, default_value = "2048", group = "key_params")]
93+
bits: RsaBits,
94+
95+
/// The curve (size) of the ECDSA key. Should only be used with --type ecdsa.
96+
#[arg(long, default_value = "p256", group = "key_params")]
97+
curve: EcCurve,
98+
99+
/// User PIN value for logging into the PKCS #11 token.
100+
///
101+
/// This flag can be used to provide a PIN when creating a new key without needing to update
102+
/// tedge-config, which can be helpful when initializing keys on new tokens.
103+
///
104+
/// Note that in contrast to the URI of the key, which will be written to tedge-config
105+
/// automatically when the keypair is created, PIN will not be written automatically and may
106+
/// be needed to written manually using tedge config set (if not using tedge-p11-server with
107+
/// the correct default PIN).
108+
#[arg(long)]
109+
pin: Option<String>,
110+
111+
/// Path where public key will be saved when a keypair is generated.
112+
#[arg(long)]
113+
outfile_pubkey: Option<Box<Utf8Path>>,
114+
115+
// can't document subcommands here because one would have to document variants of the enum
116+
// but this type is used in other places
117+
#[clap(subcommand)]
118+
cloud: Option<CloudArg>,
119+
120+
/// The URI of the token where the keypair should be created.
121+
///
122+
/// If this argument is missing, a list of available initialized tokens will be shown. The
123+
/// token needs to be initialized to be able to generate keys.
124+
token: Option<String>,
125+
},
126+
54127
/// Renew the device certificate
55128
///
56129
/// The current certificate is left unchanged and a new certificate file is created,
@@ -214,6 +287,42 @@ impl BuildCommand for TEdgeCertCli {
214287
cmd.into_boxed()
215288
}
216289

290+
TEdgeCertCli::CreateKeyHsm {
291+
bits,
292+
label,
293+
r#type,
294+
curve,
295+
id,
296+
pin,
297+
outfile_pubkey,
298+
299+
cloud,
300+
token,
301+
} => {
302+
let cloud: Option<Cloud> = cloud.map(<_>::try_into).transpose()?;
303+
let cloud_config = cloud
304+
.as_ref()
305+
.map(|c| config.as_cloud_config((c).into()))
306+
.transpose()?;
307+
let cryptoki_config = config
308+
.device
309+
.cryptoki_config(cloud_config)?
310+
.context("Cryptoki config is not enabled")?;
311+
312+
CreateKeyHsmCmd {
313+
cryptoki_config,
314+
label,
315+
r#type,
316+
bits,
317+
curve,
318+
id,
319+
pin,
320+
outfile_pubkey,
321+
cloud,
322+
token,
323+
}
324+
.into_boxed()
325+
}
217326
TEdgeCertCli::Show {
218327
cloud,
219328
cert_path,

0 commit comments

Comments
 (0)