Skip to content

Commit 5cfe216

Browse files
authored
Merge pull request #3709 from Bravo555/feat/pkcs11-create-key
feat: `tedge cert create-key` command
2 parents 43a8d11 + 6e657e5 commit 5cfe216

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")]
@@ -294,6 +295,7 @@ impl rcgen::SigningKey for RemoteKeyPair {
294295
// the error here is not PEM-related, but we need to return a foreign error type, and there
295296
// are no other better variants that could let us return context, so we'll have to use this
296297
// until `rcgen::Error::RemoteKeyError` can take a parameter
298+
trace!(?self.cryptoki_config, msg = %String::from_utf8_lossy(msg), "sign");
297299
let signer = tedge_p11_server::signing_key(self.cryptoki_config.clone())
298300
.map_err(|e| rcgen::Error::PemError(e.to_string()))?;
299301
signer
@@ -304,7 +306,7 @@ impl rcgen::SigningKey for RemoteKeyPair {
304306

305307
/// Signature algorithms that can be used for generating a CSR
306308
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307-
enum SignatureAlgorithm {
309+
pub enum SignatureAlgorithm {
308310
RsaPkcs1Sha256,
309311
EcdsaP256Sha256,
310312
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,
@@ -220,6 +293,42 @@ impl BuildCommand for TEdgeCertCli {
220293
cmd.into_boxed()
221294
}
222295

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

0 commit comments

Comments
 (0)