Skip to content

Commit

Permalink
Merge pull request #73 from fortanix/ud/list-keys-feature
Browse files Browse the repository at this point in the history
New commands to fetch key details and list dsm keys
  • Loading branch information
uddhav-dave authored Feb 21, 2023
2 parents 9701479 + 60b54ed commit 6307b89
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 5 deletions.
121 changes: 119 additions & 2 deletions openpgp-dsm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
use core::fmt::Display;
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
use std::env;
use std::fs::File;
use std::io::Read;
Expand All @@ -44,7 +44,8 @@ use sdkms::api_model::{
DecryptResponse, DigestAlgorithm, EllipticCurve as ApiCurve, KeyLinks,
KeyOperations, ObjectType, RsaEncryptionPaddingPolicy, RsaEncryptionPolicy,
RsaOptions, RsaSignaturePaddingPolicy, RsaSignaturePolicy, SignRequest,
SignResponse, Sobject, SobjectDescriptor, SobjectRequest, Time as SdkmsTime
SignResponse, Sobject, SobjectDescriptor, SobjectRequest, Time as SdkmsTime,
ListSobjectsParams
};
use sdkms::operations::Operation;
use sdkms::{Error as DsmError, PendingApproval, SdkmsClient as DsmClient};
Expand Down Expand Up @@ -884,6 +885,122 @@ pub fn generate_key(
Ok(())
}

pub struct DsmKeyInfo {
name: String,
kid: Uuid,
object_type: ObjectType,
created_at: SdkmsTime,
last_used_at: SdkmsTime,
fingerprint: String,
}

impl TryFrom<&Sobject> for DsmKeyInfo {
type Error = anyhow::Error;

/// Expects `DSM_LABEL_PGP` key to be present in metadata.
fn try_from(key: &Sobject) -> Result<Self, Self::Error> {
let key_md = KeyMetadata::from_sobject(&key)?;
Ok(DsmKeyInfo {
name: key.name.as_ref()
.ok_or(anyhow::anyhow!("Key name not present"))?
.into(),
kid: key.kid
.ok_or(anyhow::anyhow!("Key ID not present"))?,
object_type: key.obj_type,
created_at: key.created_at,
last_used_at: key.lastused_at,
fingerprint: key_md.fingerprint,
})
}
}

impl DsmKeyInfo {
/// Prints key details in concise format, includes name, uuid, created_at
pub fn format_details_short(&self) -> String {
format!(
"{} {} {name:<20.*}",
self.kid,
self.created_at.to_datetime(),
20,
name = self.name,
)
}

/// Prints key details in verbose format, includes all fields.
pub fn format_details_long(&self) -> String {
format!(
"{}:
UUID: {}
Object Type: {:?}
Created at: {}
Last used at: {}
PGP fingerprint: {}
",
self.name,
self.kid,
self.object_type,
self.created_at.to_datetime(),
if self.last_used_at.eq(&SdkmsTime(0)) {
"NA".into()
} else {
self.last_used_at.to_datetime().to_string()
},
self.fingerprint
)
}
}

/// Gets info on a key and prints revelant PGP details for it.
/// Returns `Err` if key is not present.
pub fn dsm_key_info(cred: Credentials, key_name: &str) -> Result<Option<DsmKeyInfo>> {
info!("dsm key_info");
let dsm_client = cred.dsm_client()?;

let params = ListSobjectsParams {
name: Some(key_name.to_string()),
..Default::default()
};

let key: DsmKeyInfo = match dsm_client.list_sobjects(Some(&params))?.first() {
Some(key) => key.try_into()?,
None => return Err(anyhow::anyhow!("no key with name {} exists",
&key_name)),
};

Ok(Some(key))
}

/// Iterates through accessible groups and fetches all keys available to app.
/// Returns a sorted list of keys grouped by group ID.
pub fn list_keys(cred: Credentials) -> Result<Vec<DsmKeyInfo>> {
info!("dsm list_keys");
let dsm_client = cred.dsm_client()?;
let mut key_info_store: Vec<DsmKeyInfo> = Vec::new();

let groups = dsm_client.list_groups()?;


for group in groups {
let params = ListSobjectsParams {
group_id: Some(group.group_id),
..Default::default()
};

for key_details in dsm_client.list_sobjects(Some(&params))?
.iter()
.filter(|key|
match &key.custom_metadata {
Some(metadata) => metadata.contains_key(&DSM_LABEL_PGP.to_string()),
None => false,
})
.map(|key| DsmKeyInfo::try_from(key)) {
key_info_store.push(key_details?);
}
}

Ok(key_info_store)
}

/// Extracts the certificate of the corresponding PGP key. Note that this
/// certificate, created at key-generation time, is stored in the custom
/// metadata of the Security Object representing the primary key.
Expand Down
1 change: 1 addition & 0 deletions sq/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ install: build-release

test-dsm:
cargo build
./tests/dsm/print_dsm_key_info.sh
./tests/dsm/knownkeys_import_dsm.sh
./tests/dsm/generate_gpg_import_dsm_auto.tcl
./tests/dsm/key_expiration.sh -c rsa2k
Expand Down
66 changes: 63 additions & 3 deletions sq/src/commands/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ use crate::openpgp::types::SignatureType;

use openpgp_dsm as dsm;

use crate::{
open_or_stdin,
};
use crate::open_or_stdin;
use crate::Config;
use crate::SECONDS_IN_YEAR;
use crate::parse_duration;
Expand All @@ -33,6 +31,8 @@ pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> {
("dsm-import", Some(m)) => dsm_import(config, m)?,
("password", Some(m)) => password(config, m)?,
("extract-cert", Some(m)) => extract_cert(config, m)?,
("info", Some(m)) => print_dsm_key_info(config, m)?,
("list-dsm-keys", Some(m)) => list_dsm_keys(config, m)?,
("extract-dsm-secret", Some(m)) => extract_dsm(config, m)?,
("adopt", Some(m)) => adopt(config, m)?,
("attest-certifications", Some(m)) =>
Expand Down Expand Up @@ -325,6 +325,66 @@ fn _unlock(key: Cert) -> Result<Cert> {
Ok(key)
}

fn print_dsm_key_info(_config: Config, m: &ArgMatches) -> Result<()> {
let dsm_secret = dsm::Auth::from_options_or_env(
m.value_of("api-key"),
m.value_of("client-cert"),
m.value_of("app-uuid"),
m.value_of("pkcs12-passphrase"),
)?;
let dsm_auth = dsm::Credentials::new(dsm_secret)?;

let output = match m.value_of("dsm-key") {
Some(key_name) => {
// Fortanix DSM
dsm::dsm_key_info(dsm_auth, key_name)?
},
None => return Err(anyhow::anyhow!(
"No Key name provided"))
};

print!("{}\n",output.iter()
.map(|key| key.format_details_long())
.join("\n"));

Ok(())
}

fn list_dsm_keys(_config: Config, m: &ArgMatches) -> Result<()> {
let dsm_secret = dsm::Auth::from_options_or_env(
m.value_of("api-key"),
m.value_of("client-cert"),
m.value_of("app-uuid"),
m.value_of("pkcs12-passphrase"),
)?;
let dsm_auth = dsm::Credentials::new(dsm_secret)?;
let verbose = m.is_present("long");
let output = dsm::list_keys(dsm_auth)?;

print!("{header}\n{body}\n{footer}\n",
header = if verbose {
// Long details are not columnar, hence no column headers
"".to_string()
} else {
format!("\n{}{}Name",
format!("{:width$}", "UUID", width = 38),
format!("{:width$}", "Date Created", width = 25))
},
body = output
.iter()
.map( |key|
if verbose {
key.format_details_long()
}else {
key.format_details_short()
})
.join("\n"),
footer = format!("\nTOTAL OBJECTS: {}\n", output.len()),
);

Ok(())
}

fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> {
let mut output = config.create_or_stdout_safe(m.value_of("output"))?;

Expand Down
61 changes: 61 additions & 0 deletions sq/src/sq-usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@
//! Imports a Transferable Secret Key into Fortanix DSM
//!
//! attest-certifications Attests to third-party certifications
//! info List details on DSM key
//! list-dsm-keys List all accessible keys for the App
//! adopt Binds keys from one certificate to another
//! help
//! Prints this message or the help of the given subcommand(s)
Expand Down Expand Up @@ -768,6 +770,65 @@
//! $ sq key attest-certifications --none juliet.pgp
//! ```
//!
//! ### Subcommand key info
//!
//! ```text
//!
//! This command prints data on a given DSM key name, if the key is present.
//!
//! USAGE:
//! sq key info --dsm-key <DSM-KEY-NAME>
//!
//! FLAGS:
//! -h, --help
//! Prints help information
//!
//! -V, --version
//! Prints version information
//!
//!
//! OPTIONS:
//! --dsm-key <DSM-KEY-NAME>
//! Name of the DSM key
//!
//!
//! EXAMPLES:
//!
//! # Prints details on given key
//! $ sq key info --dsm-key 0123456789A
//! ```
//!
//! ### Subcommand key list-dsm-keys
//!
//! ```text
//!
//! This command prints details about all the keys accessible to the app.
//! Command will query DSM list keys API for each group, and club the outputs
//! to print on STDOUT.
//!
//! USAGE:
//! sq key list-dsm-keys [FLAGS]
//!
//! FLAGS:
//! -h, --help
//! Prints help information
//!
//! -l, --long
//! prints long details of key
//!
//! -V, --version
//! Prints version information
//!
//!
//! EXAMPLES:
//!
//! # Print list of keys which app can access
//! $ sq key list-dsm-keys
//!
//! # Print detailed list of keys which app can access
//! $ sq key list-dsm-keys -l
//! ```
//!
//! ### Subcommand key adopt
//!
//! ```text
Expand Down
43 changes: 43 additions & 0 deletions sq/src/sq_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,49 @@ $ sq key adopt --keyring juliet-old.pgp --key 0123456789ABCDEF -- juliet-new.pgp
.short("B").long("binary")
.help("Emits binary data"))
)
.subcommand(
SubCommand::with_name("info")
.display_order(300)
.about("List details on DSM key")
.long_about(
"
This command prints data on a given DSM key name, if the key is present.
")
.after_help(
"EXAMPLES:
# Prints details on given key
$ sq key info --dsm-key 0123456789A
")
.arg(Arg::with_name("dsm-key")
.long("dsm-key").value_name("DSM-KEY-NAME")
.required(true)
.help("Name of the DSM key"))
)
.subcommand(
SubCommand::with_name("list-dsm-keys")
.display_order(400)
.about("List all accessible keys for the App")
.long_about(
"
This command prints details about all the keys accessible to the app.
Command will query DSM list keys API for each group, and club the outputs
to print on STDOUT.
")
.after_help(
"EXAMPLES:
# Print list of keys which app can access
$ sq key list-dsm-keys
# Print detailed list of keys which app can access
$ sq key list-dsm-keys -l
")
.arg(Arg::with_name("long")
.short("l").long("long")
.help("prints long details of key")
)
)
.subcommand(
SubCommand::with_name("attest-certifications")
.display_order(200)
Expand Down
26 changes: 26 additions & 0 deletions sq/tests/dsm/print_dsm_key_info.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash -e

sq=""

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/common.sh

random=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w "10" | head -n 1)

array=( rsa2k rsa3k rsa4k nistp256 nistp384 nistp521 cv25519 )
for alg in "${array[@]}"
do
dsm_name="generate-knownkeys-test-$random-$alg"
user_id="Knownkey-Test-$alg (sq-dsm $v) <[email protected]>"
$sq key generate --userid="$user_id" --dsm-key="$dsm_name" --cipher-suite="$alg" --dsm-exportable
$sq key info --dsm-key="$dsm_name" | awk '{print}'
done

ldk_cmd_long_resp=$($sq key list-dsm-keys -l | tail -2 | head -1 | awk '{print $NF}')
ldk_cmd_short_resp=$($sq key list-dsm-keys | tail -2 | head -1 | awk '{print $NF}')

if [[ ! $ldk_cmd_short_resp -eq $ldk_cmd_short_resp ]]
then
echo "long response objects($ldk_cmd_long_resp) != short response objects($ldk_cmd_short_resp)"
exit 1
fi

0 comments on commit 6307b89

Please sign in to comment.