Skip to content

Commit

Permalink
[hsmtool] Add SPHINCS+ operations
Browse files Browse the repository at this point in the history
1. Allow loading the Entrust PQ crypto module from a shared library.
2. Add a command hierarchy of SPHINCS+ (aka `spx`) commands:
   - list: List keys
   - generate: Generate a key
   - export: Export the public portion of an SPX key.
   - import: Import a private SPX key.
   - sign: Sign a message with an SPX key.
   - verify: Verify a message with an SPX key.

Signed-off-by: Chris Frantz <[email protected]>
(cherry picked from commit 605a457)
  • Loading branch information
cfrantz committed Dec 4, 2024
1 parent 7ea0aa9 commit 5427980
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 4 deletions.
10 changes: 10 additions & 0 deletions sw/host/hsmtool/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ rust_library(
"src/commands/rsa/mod.rs",
"src/commands/rsa/sign.rs",
"src/commands/rsa/verify.rs",
"src/commands/spx/export.rs",
"src/commands/spx/generate.rs",
"src/commands/spx/import.rs",
"src/commands/spx/list.rs",
"src/commands/spx/mod.rs",
"src/commands/spx/sign.rs",
"src/commands/spx/verify.rs",
"src/commands/token.rs",
"src/error.rs",
"src/lib.rs",
Expand All @@ -55,6 +62,8 @@ rust_library(
],
crate_name = "hsmtool",
deps = [
"//sw/host/hsmtool/acorn",
"//sw/host/sphincsplus",
"@crate_index//:anyhow",
"@crate_index//:clap",
"@crate_index//:cryptoki",
Expand All @@ -68,6 +77,7 @@ rust_library(
"@crate_index//:num_enum",
"@crate_index//:once_cell",
"@crate_index//:p256",
"@crate_index//:pem-rfc7468",
"@crate_index//:rand",
"@crate_index//:regex",
"@crate_index//:rsa",
Expand Down
5 changes: 5 additions & 0 deletions sw/host/hsmtool/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod ecdsa;
mod exec;
mod object;
mod rsa;
mod spx;
mod token;

#[typetag::serde(tag = "command")]
Expand Down Expand Up @@ -45,6 +46,8 @@ pub enum Commands {
#[command(subcommand)]
Rsa(rsa::Rsa),
#[command(subcommand)]
Spx(spx::Spx),
#[command(subcommand)]
Token(token::Token),
}

Expand All @@ -60,6 +63,7 @@ impl Dispatch for Commands {
Commands::Ecdsa(x) => x.run(context, hsm, session),
Commands::Exec(x) => x.run(context, hsm, session),
Commands::Object(x) => x.run(context, hsm, session),
Commands::Spx(x) => x.run(context, hsm, session),
Commands::Rsa(x) => x.run(context, hsm, session),
Commands::Token(x) => x.run(context, hsm, session),
}
Expand All @@ -73,6 +77,7 @@ impl Dispatch for Commands {
Commands::Ecdsa(x) => x.leaf(),
Commands::Exec(x) => x.leaf(),
Commands::Object(x) => x.leaf(),
Commands::Spx(x) => x.leaf(),
Commands::Rsa(x) => x.leaf(),
Commands::Token(x) => x.leaf(),
}
Expand Down
49 changes: 49 additions & 0 deletions sw/host/hsmtool/src/commands/spx/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;
use std::path::PathBuf;
use std::str::FromStr;

use crate::commands::{BasicResult, Dispatch};
use crate::error::HsmError;
use crate::module::Module;
use acorn::Acorn;
use sphincsplus::{EncodeKey, SphincsPlus, SpxPublicKey};

#[derive(clap::Args, Debug, Serialize, Deserialize)]
pub struct Export {
#[arg(short, long)]
label: String,
filename: PathBuf,
}

impl Export {
fn export(&self, acorn: &Acorn) -> Result<()> {
let key = acorn.get_key_info(&self.label)?;
let algorithm = SphincsPlus::from_str(&key.algorithm)?;
let pk = SpxPublicKey::from_bytes(algorithm, &key.public_key)?;
pk.write_pem_file(&self.filename)?;
Ok(())
}
}

#[typetag::serde(name = "spx-export")]
impl Dispatch for Export {
fn run(
&self,
_context: &dyn Any,
hsm: &Module,
_session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?;
let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?;
self.export(acorn)?;
Ok(Box::<BasicResult>::default())
}
}
61 changes: 61 additions & 0 deletions sw/host/hsmtool/src/commands/spx/generate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;
use std::path::PathBuf;

use crate::commands::{BasicResult, Dispatch};
use crate::error::HsmError;
use crate::module::Module;
use crate::util::attribute::AttrData;
use acorn::GenerateFlags;
use sphincsplus::{EncodeKey, SphincsPlus, SpxSecretKey};

#[derive(clap::Args, Debug, Serialize, Deserialize)]
pub struct Generate {
#[arg(short, long)]
label: String,
#[arg(short, long, default_value = "SHA2-128s-simple")]
algorithm: SphincsPlus,
#[arg(short, long, help = "Overwrite an existing key with the same label")]
overwrite: bool,
#[arg(short, long, help = "Export the private key material to a file")]
export: Option<PathBuf>,
}

#[typetag::serde(name = "spx-generate")]
impl Dispatch for Generate {
fn run(
&self,
_context: &dyn Any,
hsm: &Module,
_session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?;
let token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?;

#[rustfmt::skip]
let flags =
if self.overwrite { GenerateFlags::OVERWRITE } else { GenerateFlags::NONE }
| if self.export.is_some() { GenerateFlags::EXPORT_PRIVATE } else { GenerateFlags::NONE };

let key = acorn.generate_key(&self.label, &self.algorithm.to_string(), token, flags)?;

if let Some(path) = &self.export {
let sk = SpxSecretKey::from_bytes(self.algorithm, &key.private_key)?;
sk.write_pem_file(path)?;
}

Ok(Box::new(BasicResult {
success: true,
id: AttrData::Str(key.hash.expect("key hash")),
label: AttrData::Str(key.alias),
error: None,
}))
}
}
56 changes: 56 additions & 0 deletions sw/host/hsmtool/src/commands/spx/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;
use std::path::PathBuf;

use crate::commands::{BasicResult, Dispatch};
use crate::error::HsmError;
use crate::module::Module;
use crate::util::attribute::AttrData;
use sphincsplus::{DecodeKey, SpxPublicKey, SpxSecretKey};

#[derive(clap::Args, Debug, Serialize, Deserialize)]
pub struct Import {
#[arg(short, long)]
label: String,
#[arg(short, long, help = "Overwrite an existing key with the same label")]
overwrite: bool,
filename: PathBuf,
}

#[typetag::serde(name = "spx-import")]
impl Dispatch for Import {
fn run(
&self,
_context: &dyn Any,
hsm: &Module,
_session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?;
let token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?;

let sk = SpxSecretKey::read_pem_file(&self.filename)?;
let pk = SpxPublicKey::from(&sk);

let key = acorn.import_keypair(
&self.label,
&sk.algorithm().to_string(),
token,
self.overwrite,
pk.as_bytes(),
sk.as_bytes(),
)?;
Ok(Box::new(BasicResult {
success: true,
id: AttrData::Str(key.hash.expect("key hash")),
label: AttrData::Str(key.alias),
error: None,
}))
}
}
59 changes: 59 additions & 0 deletions sw/host/hsmtool/src/commands/spx/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;

use crate::commands::Dispatch;
use crate::error::HsmError;
use crate::module::Module;

#[derive(clap::Args, Debug, Serialize, Deserialize)]
pub struct List {}

#[derive(Default, Debug, Serialize)]
pub struct Key {
pub id: String,
pub label: String,
pub algorithm: String,
}

#[derive(Default, Debug, Serialize)]
pub struct ListResult {
host_version: String,
see_version: String,
objects: Vec<Key>,
}

#[typetag::serde(name = "spx-list")]
impl Dispatch for List {
fn run(
&self,
_context: &dyn Any,
hsm: &Module,
_session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?;
let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?;

let mut result = Box::new(ListResult {
host_version: acorn.get_version()?,
see_version: acorn.get_see_version()?,
..Default::default()
});
let keys = acorn.list_keys()?;
for key in keys {
let info = acorn.get_key_info(&key.alias)?;
result.objects.push(Key {
id: info.hash,
label: key.alias,
algorithm: info.algorithm,
});
}
Ok(result)
}
}
61 changes: 61 additions & 0 deletions sw/host/hsmtool/src/commands/spx/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;

use crate::commands::Dispatch;
use crate::module::Module;

pub mod export;
pub mod generate;
pub mod import;
pub mod list;
pub mod sign;
pub mod verify;

#[derive(clap::Subcommand, Debug, Serialize, Deserialize)]
pub enum Spx {
Generate(generate::Generate),
Export(export::Export),
Import(import::Import),
List(list::List),
Sign(sign::Sign),
Verify(verify::Verify),
}

#[typetag::serde(name = "__spx__")]
impl Dispatch for Spx {
fn run(
&self,
context: &dyn Any,
hsm: &Module,
session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
match self {
Spx::Generate(x) => x.run(context, hsm, session),
Spx::Export(x) => x.run(context, hsm, session),
Spx::Import(x) => x.run(context, hsm, session),
Spx::List(x) => x.run(context, hsm, session),
Spx::Sign(x) => x.run(context, hsm, session),
Spx::Verify(x) => x.run(context, hsm, session),
}
}
fn leaf(&self) -> &dyn Dispatch
where
Self: Sized,
{
match self {
Spx::Generate(x) => x.leaf(),
Spx::Export(x) => x.leaf(),
Spx::Import(x) => x.leaf(),
Spx::List(x) => x.leaf(),
Spx::Sign(x) => x.leaf(),
Spx::Verify(x) => x.leaf(),
}
}
}
44 changes: 44 additions & 0 deletions sw/host/hsmtool/src/commands/spx/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 cryptoki::session::Session;
use serde::{Deserialize, Serialize};
use serde_annotate::Annotate;
use std::any::Any;
use std::path::PathBuf;

use crate::commands::{BasicResult, Dispatch};
use crate::error::HsmError;
use crate::module::Module;
use crate::util::helper;

#[derive(clap::Args, Debug, Serialize, Deserialize)]
pub struct Sign {
#[arg(long)]
id: Option<String>,
#[arg(short, long)]
label: Option<String>,
#[arg(short, long)]
output: PathBuf,
input: PathBuf,
}

#[typetag::serde(name = "spx-sign")]
impl Dispatch for Sign {
fn run(
&self,
_context: &dyn Any,
hsm: &Module,
_session: Option<&Session>,
) -> Result<Box<dyn Annotate>> {
let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?;
let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?;

let data = helper::read_file(&self.input)?;
let result = acorn.sign(self.label.as_deref(), self.id.as_deref(), &data)?;
helper::write_file(&self.output, &result)?;
Ok(Box::<BasicResult>::default())
}
}
Loading

0 comments on commit 5427980

Please sign in to comment.