Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hsmtool] Add a SPX+ implementation for PKCS11 Elementary Files #25379

Merged
merged 6 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sw/host/hsmtool/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ rust_library(
"src/commands/object/destroy.rs",
"src/commands/object/list.rs",
"src/commands/object/mod.rs",
"src/commands/object/read.rs",
"src/commands/object/show.rs",
"src/commands/object/update.rs",
"src/commands/object/write.rs",
"src/commands/rsa/decrypt.rs",
"src/commands/rsa/encrypt.rs",
"src/commands/rsa/export.rs",
Expand All @@ -42,6 +44,7 @@ rust_library(
"src/lib.rs",
"src/module.rs",
"src/profile.rs",
"src/spxef/mod.rs",
"src/util/attribute/attr.rs",
"src/util/attribute/attribute_type.rs",
"src/util/attribute/certificate_type.rs",
Expand All @@ -52,6 +55,7 @@ rust_library(
"src/util/attribute/mechanism_type.rs",
"src/util/attribute/mod.rs",
"src/util/attribute/object_class.rs",
"src/util/ef.rs",
"src/util/escape.rs",
"src/util/helper.rs",
"src/util/key/ecdsa.rs",
Expand Down Expand Up @@ -89,6 +93,7 @@ rust_library(
"@crate_index//:strum",
"@crate_index//:thiserror",
"@crate_index//:typetag",
"@crate_index//:zeroize",
"@lowrisc_serde_annotate//serde_annotate",
],
)
Expand Down
1 change: 1 addition & 0 deletions sw/host/hsmtool/acorn/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ rust_library(
srcs = [
"acorn.rs",
"lib.rs",
"spx.rs",
],
crate_root = "lib.rs",
deps = [
Expand Down
125 changes: 48 additions & 77 deletions sw/host/hsmtool/acorn/acorn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use bitflags::bitflags;
use libloading::Library;
use std::ffi::{CStr, CString};
use thiserror::Error;

use crate::{GenerateFlags, KeyEntry, KeyInfo, SpxInterface};

#[derive(Error, Debug)]
pub enum AcornError {
#[error("acorn library error: {0}")]
Expand Down Expand Up @@ -106,56 +107,21 @@ impl AcornLibrary {
}
}

#[derive(Debug, Default)]
pub struct KeyEntry {
/// Alias of the key.
pub alias: String,
/// Unique identifier for the key.
pub hash: Option<String>,
/// Algorithm to be used with the key.
pub algorithm: String,
/// Opaque representation of the private key material.
pub private_blob: Vec<u8>,
/// Exported private key material (only when GenerateFlags::EXPORT_PRIVATE is set).
pub private_key: Vec<u8>,
}

#[derive(Debug, Default)]
pub struct KeyInfo {
/// Unique identifier for the key.
pub hash: String,
/// Algorithm to be used with the key.
pub algorithm: String,
/// Public key material.
pub public_key: Vec<u8>,
/// Opaque representation of the private key material.
pub private_blob: Vec<u8>,
}

pub struct Acorn {
lib: AcornLibrary,
}

bitflags! {
#[derive(Debug)]
pub struct GenerateFlags: u32 {
const NONE = 0;
const OVERWRITE = 1 << acorn_bindgen::acorn_enum_generateFlags_acorn_enum_generateFlags_overwrite;
const EXPORT_PRIVATE = 1 << acorn_bindgen::acorn_enum_generateFlags_acorn_enum_generateFlags_exportPrivate;
}
}

impl Acorn {
pub fn new<P>(path: P) -> Result<Self>
pub fn new<P>(path: P) -> Result<Box<Self>>
where
P: AsRef<std::ffi::OsStr>,
{
let lib = AcornLibrary::new(path)?;
Ok(Acorn { lib })
Ok(Box::new(Acorn { lib }))
}

/// Get the version of the acorn host software.
pub fn get_version(&self) -> Result<String> {
pub fn get_host_version(&self) -> Result<String> {
let mut rsp = acorn_bindgen::acorn_response_getVersion::default();
let mut err = std::ptr::null_mut::<std::ffi::c_char>();
// SAFETY: The arguments to `getVersion` are of the correct type.
Expand Down Expand Up @@ -206,34 +172,6 @@ impl Acorn {
}
}

pub fn list_keys(&self) -> Result<Vec<KeyEntry>> {
let mut rsp = acorn_bindgen::acorn_response_list::default();
let mut err = std::ptr::null_mut::<std::ffi::c_char>();
// SAFETY: The entries returned by `list` are copied into rust types.
// The memory allocated by the acorn library is freed by the acorn library's
// free function.
unsafe {
let result =
if (self.lib.func.list.as_ref().expect("func.list"))(&mut rsp, &mut err) == 0 {
let entries = std::slice::from_raw_parts(rsp.entries, rsp.n_entries as usize);
let entries = entries
.iter()
.map(|e| KeyEntry {
alias: rust_string(e.alias),
hash: None,
algorithm: rust_string(e.algorithm),
..Default::default()
})
.collect::<Vec<_>>();
Ok(entries)
} else {
Err(AcornError::Message(rust_string(err)).into())
};
(self.lib.free.list.as_ref().expect("free.list"))(std::ptr::null_mut(), &mut rsp);
result
}
}

// TODO: get_public_key, but get_key_info provides the same data.
pub fn get_public_key(&self, alias: &str) -> Result<Vec<u8>> {
let alias = CString::new(alias)?;
Expand Down Expand Up @@ -298,8 +236,46 @@ impl Acorn {
result
}
}
}

pub fn get_key_info(&self, alias: &str) -> Result<KeyInfo> {
impl SpxInterface for Acorn {
fn get_version(&self) -> Result<String> {
Ok(format!(
"{} (see: {})",
self.get_host_version()?,
self.get_see_version()?
))
}

fn list_keys(&self) -> Result<Vec<KeyEntry>> {
let mut rsp = acorn_bindgen::acorn_response_list::default();
let mut err = std::ptr::null_mut::<std::ffi::c_char>();
// SAFETY: The entries returned by `list` are copied into rust types.
// The memory allocated by the acorn library is freed by the acorn library's
// free function.
unsafe {
let result =
if (self.lib.func.list.as_ref().expect("func.list"))(&mut rsp, &mut err) == 0 {
let entries = std::slice::from_raw_parts(rsp.entries, rsp.n_entries as usize);
let entries = entries
.iter()
.map(|e| KeyEntry {
alias: rust_string(e.alias),
hash: None,
algorithm: rust_string(e.algorithm),
..Default::default()
})
.collect::<Vec<_>>();
Ok(entries)
} else {
Err(AcornError::Message(rust_string(err)).into())
};
(self.lib.free.list.as_ref().expect("free.list"))(std::ptr::null_mut(), &mut rsp);
result
}
}

fn get_key_info(&self, alias: &str) -> Result<KeyInfo> {
let alias = CString::new(alias)?;
// SAFETY: The data returned by `get_key_info` are copied into rust data types.
// The memory allocated by the acorn library is freed by the acorn library's
Expand Down Expand Up @@ -334,7 +310,7 @@ impl Acorn {
}
}

pub fn generate_key(
fn generate_key(
&self,
alias: &str,
algorithm: &str,
Expand Down Expand Up @@ -389,7 +365,7 @@ impl Acorn {
}
}

pub fn import_keypair(
fn import_keypair(
&self,
alias: &str,
algorithm: &str,
Expand Down Expand Up @@ -453,12 +429,7 @@ impl Acorn {
}
}

pub fn sign(
&self,
alias: Option<&str>,
key_hash: Option<&str>,
message: &[u8],
) -> Result<Vec<u8>> {
fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result<Vec<u8>> {
let alias = alias.map(CString::new).transpose()?;
let key_hash = key_hash.map(CString::new).transpose()?;
// SAFETY: The signature returned by `sign` is copied into a rust Vec.
Expand Down Expand Up @@ -496,7 +467,7 @@ impl Acorn {
}
}

pub fn verify(
fn verify(
&self,
alias: Option<&str>,
key_hash: Option<&str>,
Expand Down
4 changes: 3 additions & 1 deletion sw/host/hsmtool/acorn/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
// SPDX-License-Identifier: Apache-2.0

mod acorn;
pub use acorn::{Acorn, GenerateFlags};
mod spx;
pub use acorn::Acorn;
pub use spx::{GenerateFlags, KeyEntry, KeyInfo, SpxInterface};
84 changes: 84 additions & 0 deletions sw/host/hsmtool/acorn/spx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use bitflags::bitflags;

#[derive(Debug, Default)]
pub struct KeyEntry {
/// Alias of the key.
pub alias: String,
/// Unique identifier for the key.
pub hash: Option<String>,
/// Algorithm to be used with the key.
pub algorithm: String,
/// Opaque representation of the private key material.
pub private_blob: Vec<u8>,
/// Exported private key material (only when GenerateFlags::EXPORT_PRIVATE is set).
pub private_key: Vec<u8>,
}

#[derive(Debug, Default)]
pub struct KeyInfo {
/// Unique identifier for the key.
pub hash: String,
/// Algorithm to be used with the key.
pub algorithm: String,
/// Public key material.
pub public_key: Vec<u8>,
/// Opaque representation of the private key material.
pub private_blob: Vec<u8>,
}

bitflags! {
#[derive(Debug)]
pub struct GenerateFlags: u32 {
const NONE = 0;
const OVERWRITE = 1 << acorn_bindgen::acorn_enum_generateFlags_acorn_enum_generateFlags_overwrite;
const EXPORT_PRIVATE = 1 << acorn_bindgen::acorn_enum_generateFlags_acorn_enum_generateFlags_exportPrivate;
}
}

pub trait SpxInterface {
/// Get the version of the backend.
fn get_version(&self) -> Result<String>;

/// Get the version of the backend.
fn list_keys(&self) -> Result<Vec<KeyEntry>>;

/// Get the public key info.
fn get_key_info(&self, alias: &str) -> Result<KeyInfo>;

/// Generate a key pair.
fn generate_key(
&self,
alias: &str,
algorithm: &str,
token: &str,
flags: GenerateFlags,
) -> Result<KeyEntry>;

/// Import a key pair.
fn import_keypair(
&self,
alias: &str,
algorithm: &str,
token: &str,
overwrite: bool,
public_key: &[u8],
private_key: &[u8],
) -> Result<KeyEntry>;

/// Sign a message.
fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result<Vec<u8>>;

/// Verify a message.
fn verify(
&self,
alias: Option<&str>,
key_hash: Option<&str>,
message: &[u8],
signature: &[u8],
) -> Result<bool>;
}
1 change: 1 addition & 0 deletions sw/host/hsmtool/src/commands/ecdsa/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl Dispatch for Generate {
success: true,
id: id.clone(),
label: AttrData::Str(self.label.as_ref().cloned().unwrap_or_default()),
value: None,
error: None,
});

Expand Down
1 change: 1 addition & 0 deletions sw/host/hsmtool/src/commands/ecdsa/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl Dispatch for Import {
success: true,
id: id.clone(),
label: AttrData::Str(self.label.as_ref().cloned().unwrap_or_default()),
value: None,
error: None,
});
public_attrs.insert(AttributeType::Id, id.clone());
Expand Down
8 changes: 7 additions & 1 deletion sw/host/hsmtool/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ impl Dispatch for Commands {
}
}

#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, Annotate)]
pub struct BasicResult {
success: bool,
#[serde(skip_serializing_if = "AttrData::is_none")]
id: AttrData,
#[serde(skip_serializing_if = "AttrData::is_none")]
label: AttrData,
#[serde(skip_serializing_if = "Option::is_none")]
#[annotate(format = block)]
value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[annotate(format = block)]
error: Option<String>,
}

Expand All @@ -111,6 +115,7 @@ impl Default for BasicResult {
success: true,
id: AttrData::None,
label: AttrData::None,
value: None,
error: None,
}
}
Expand All @@ -122,6 +127,7 @@ impl BasicResult {
success: false,
id: AttrData::None,
label: AttrData::None,
value: None,
error: Some(format!("{:?}", e)),
})
}
Expand Down
Loading
Loading