From 61fb5dbde17537c3bf9c8f9346eaf548e3e5fc8e Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Tue, 3 Dec 2024 19:36:58 -0800 Subject: [PATCH 1/6] [hsmtool] Add the `zeroize` crate Signed-off-by: Chris Frantz (cherry picked from commit dfd9f0876cf88a1e03c03a04b533a40fc6875ca7) --- third_party/rust/Cargo.lock | 5 +++-- third_party/rust/Cargo.toml | 1 + third_party/rust/crates/BUILD.bazel | 6 ++++++ .../rust/crates/BUILD.crypto-bigint-0.5.2.bazel | 2 +- third_party/rust/crates/BUILD.der-0.7.8.bazel | 2 +- .../rust/crates/BUILD.elliptic-curve-0.13.8.bazel | 2 +- .../rust/crates/BUILD.generic-array-0.14.7.bazel | 2 +- .../rust/crates/BUILD.num-bigint-dig-0.8.4.bazel | 2 +- third_party/rust/crates/BUILD.rsa-0.9.2.bazel | 2 +- third_party/rust/crates/BUILD.sec1-0.7.3.bazel | 2 +- third_party/rust/crates/BUILD.secrecy-0.8.0.bazel | 2 +- ....zeroize-1.7.0.bazel => BUILD.zeroize-1.8.1.bazel} | 2 +- third_party/rust/crates/defs.bzl | 11 ++++++----- 13 files changed, 25 insertions(+), 16 deletions(-) rename third_party/rust/crates/{BUILD.zeroize-1.7.0.bazel => BUILD.zeroize-1.8.1.bazel} (99%) diff --git a/third_party/rust/Cargo.lock b/third_party/rust/Cargo.lock index 9f557b3a8d654..8888900be4301 100644 --- a/third_party/rust/Cargo.lock +++ b/third_party/rust/Cargo.lock @@ -357,6 +357,7 @@ dependencies = [ "tiny-keccak", "typetag", "zerocopy", + "zeroize", ] [[package]] @@ -3010,6 +3011,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/third_party/rust/Cargo.toml b/third_party/rust/Cargo.toml index 30dec79753680..df0e048414c54 100644 --- a/third_party/rust/Cargo.toml +++ b/third_party/rust/Cargo.toml @@ -82,6 +82,7 @@ thiserror = "1.0" tiny-keccak = {version = "2.0.2", features = ["cshake"]} typetag = "0.2" zerocopy = { version = "0.7.1", features = ["derive"] } +zeroize = "1.8.1" # hsmtool dependencies cryptoki = "0.5.0" diff --git a/third_party/rust/crates/BUILD.bazel b/third_party/rust/crates/BUILD.bazel index 77040f5def0d3..5914f411f6792 100644 --- a/third_party/rust/crates/BUILD.bazel +++ b/third_party/rust/crates/BUILD.bazel @@ -523,6 +523,12 @@ alias( tags = ["manual"], ) +alias( + name = "zeroize", + actual = "@crate_index__zeroize-1.8.1//:zeroize", + tags = ["manual"], +) + # Binaries alias( name = "mdbook__mdbook", diff --git a/third_party/rust/crates/BUILD.crypto-bigint-0.5.2.bazel b/third_party/rust/crates/BUILD.crypto-bigint-0.5.2.bazel index ef627a48f9700..cce7043594334 100644 --- a/third_party/rust/crates/BUILD.crypto-bigint-0.5.2.bazel +++ b/third_party/rust/crates/BUILD.crypto-bigint-0.5.2.bazel @@ -82,6 +82,6 @@ rust_library( "@crate_index__generic-array-0.14.7//:generic_array", "@crate_index__rand_core-0.6.4//:rand_core", "@crate_index__subtle-2.5.0//:subtle", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.der-0.7.8.bazel b/third_party/rust/crates/BUILD.der-0.7.8.bazel index eddcf9a94eda5..2878e9fd74d61 100644 --- a/third_party/rust/crates/BUILD.der-0.7.8.bazel +++ b/third_party/rust/crates/BUILD.der-0.7.8.bazel @@ -83,6 +83,6 @@ rust_library( deps = [ "@crate_index__const-oid-0.9.6//:const_oid", "@crate_index__pem-rfc7468-0.7.0//:pem_rfc7468", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.elliptic-curve-0.13.8.bazel b/third_party/rust/crates/BUILD.elliptic-curve-0.13.8.bazel index 6bea6541465d3..9790861cf08d0 100644 --- a/third_party/rust/crates/BUILD.elliptic-curve-0.13.8.bazel +++ b/third_party/rust/crates/BUILD.elliptic-curve-0.13.8.bazel @@ -100,6 +100,6 @@ rust_library( "@crate_index__rand_core-0.6.4//:rand_core", "@crate_index__sec1-0.7.3//:sec1", "@crate_index__subtle-2.5.0//:subtle", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.generic-array-0.14.7.bazel b/third_party/rust/crates/BUILD.generic-array-0.14.7.bazel index 04d8a8c14fb18..c5ae7af9d017a 100644 --- a/third_party/rust/crates/BUILD.generic-array-0.14.7.bazel +++ b/third_party/rust/crates/BUILD.generic-array-0.14.7.bazel @@ -81,7 +81,7 @@ rust_library( deps = [ "@crate_index__generic-array-0.14.7//:build_script_build", "@crate_index__typenum-1.17.0//:typenum", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.num-bigint-dig-0.8.4.bazel b/third_party/rust/crates/BUILD.num-bigint-dig-0.8.4.bazel index bae210f6b7ae7..5c22c09af5e44 100644 --- a/third_party/rust/crates/BUILD.num-bigint-dig-0.8.4.bazel +++ b/third_party/rust/crates/BUILD.num-bigint-dig-0.8.4.bazel @@ -95,7 +95,7 @@ rust_library( "@crate_index__rand-0.8.5//:rand", "@crate_index__serde-1.0.189//:serde", "@crate_index__smallvec-1.11.0//:smallvec", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.rsa-0.9.2.bazel b/third_party/rust/crates/BUILD.rsa-0.9.2.bazel index b4f3579ae9ce4..7bffd902232fb 100644 --- a/third_party/rust/crates/BUILD.rsa-0.9.2.bazel +++ b/third_party/rust/crates/BUILD.rsa-0.9.2.bazel @@ -96,6 +96,6 @@ rust_library( "@crate_index__signature-2.1.0//:signature", "@crate_index__spki-0.7.2//:spki", "@crate_index__subtle-2.5.0//:subtle", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.sec1-0.7.3.bazel b/third_party/rust/crates/BUILD.sec1-0.7.3.bazel index 215c6938bdcff..fd8ed555e83b1 100644 --- a/third_party/rust/crates/BUILD.sec1-0.7.3.bazel +++ b/third_party/rust/crates/BUILD.sec1-0.7.3.bazel @@ -90,6 +90,6 @@ rust_library( "@crate_index__generic-array-0.14.7//:generic_array", "@crate_index__pkcs8-0.10.2//:pkcs8", "@crate_index__subtle-2.5.0//:subtle", - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.secrecy-0.8.0.bazel b/third_party/rust/crates/BUILD.secrecy-0.8.0.bazel index 2f9028bfeb6c4..3180d1efac935 100644 --- a/third_party/rust/crates/BUILD.secrecy-0.8.0.bazel +++ b/third_party/rust/crates/BUILD.secrecy-0.8.0.bazel @@ -78,6 +78,6 @@ rust_library( }), version = "0.8.0", deps = [ - "@crate_index__zeroize-1.7.0//:zeroize", + "@crate_index__zeroize-1.8.1//:zeroize", ], ) diff --git a/third_party/rust/crates/BUILD.zeroize-1.7.0.bazel b/third_party/rust/crates/BUILD.zeroize-1.8.1.bazel similarity index 99% rename from third_party/rust/crates/BUILD.zeroize-1.7.0.bazel rename to third_party/rust/crates/BUILD.zeroize-1.8.1.bazel index 3e2889912ea5c..3c60b122d3ff8 100644 --- a/third_party/rust/crates/BUILD.zeroize-1.7.0.bazel +++ b/third_party/rust/crates/BUILD.zeroize-1.8.1.bazel @@ -76,5 +76,5 @@ rust_library( "@rules_rust//rust/platform:x86_64-unknown-none": [], "//conditions:default": ["@platforms//:incompatible"], }), - version = "1.7.0", + version = "1.8.1", ) diff --git a/third_party/rust/crates/defs.bzl b/third_party/rust/crates/defs.bzl index 6afa6f6e01dd6..e9d3136242fd5 100644 --- a/third_party/rust/crates/defs.bzl +++ b/third_party/rust/crates/defs.bzl @@ -375,6 +375,7 @@ _NORMAL_DEPENDENCIES = { "tiny-keccak": "@crate_index__tiny-keccak-2.0.2//:tiny_keccak", "typetag": "@crate_index__typetag-0.2.13//:typetag", "zerocopy": "@crate_index__zerocopy-0.7.11//:zerocopy", + "zeroize": "@crate_index__zeroize-1.8.1//:zeroize", }, }, } @@ -3692,10 +3693,10 @@ def crate_repositories(): maybe( http_archive, - name = "crate_index__zeroize-1.7.0", - sha256 = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d", + name = "crate_index__zeroize-1.8.1", + sha256 = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde", type = "tar.gz", - urls = ["https://static.crates.io/crates/zeroize/1.7.0/download"], - strip_prefix = "zeroize-1.7.0", - build_file = Label("@lowrisc_opentitan//third_party/rust/crates:BUILD.zeroize-1.7.0.bazel"), + urls = ["https://static.crates.io/crates/zeroize/1.8.1/download"], + strip_prefix = "zeroize-1.8.1", + build_file = Label("@lowrisc_opentitan//third_party/rust/crates:BUILD.zeroize-1.8.1.bazel"), ) From c6bac44d21b54c7f0e76410ac72617e7c12ec010 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Wed, 20 Nov 2024 20:01:41 -0800 Subject: [PATCH 2/6] [hsmtool] Add support for CKO_PROFILE objects - Rust `cryptoki` doesn't know about CKO_PROFILE objects. - The `sc-hsm-embedded` PKCS#11 provider does not expose the profile object, however it also doesn't support CKO_DATA objects. It does support RSA3K keys, which were used in the ES chip. - The `opensc-pkcs11` provider does expose the profile object and does support CKO_DATA objects. It does _not_ support RSA3K keys. This hack adds support to `cryptoki` for the profile object (e.g. it simply recognizes the object). This allows the use of the `opensc-pkcs11` provider which will permit using CKO_DATA objects from `hsmtool`. Signed-off-by: Chris Frantz (cherry picked from commit 7c0210c4154403151f5eba054a7a75a6e28e15a8) --- third_party/rust/BUILD | 1 + third_party/rust/crates/defs.bzl | 1 + .../rust/patches/cryptoki-profile.patch | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 third_party/rust/patches/cryptoki-profile.patch diff --git a/third_party/rust/BUILD b/third_party/rust/BUILD index 50f25f65a6bbc..7ba9b4b5aab68 100644 --- a/third_party/rust/BUILD +++ b/third_party/rust/BUILD @@ -53,6 +53,7 @@ crates_vendor( patch_args = ["-p2"], patches = [ "@lowrisc_opentitan//third_party/rust/patches:cryptoki-vendor-defined-mechanism-type.patch", + "@lowrisc_opentitan//third_party/rust/patches:cryptoki-profile.patch", ], )], "cryptoki-sys": [crate.annotation( diff --git a/third_party/rust/crates/defs.bzl b/third_party/rust/crates/defs.bzl index e9d3136242fd5..af093d4001f84 100644 --- a/third_party/rust/crates/defs.bzl +++ b/third_party/rust/crates/defs.bzl @@ -1050,6 +1050,7 @@ def crate_repositories(): "-p2", ], patches = [ + "@lowrisc_opentitan//third_party/rust/patches:cryptoki-profile.patch", "@lowrisc_opentitan//third_party/rust/patches:cryptoki-vendor-defined-mechanism-type.patch", ], sha256 = "95d9fb68c88020896fa3741a10e41f206b2ace927724170a753a3f2ba5f77c2b", diff --git a/third_party/rust/patches/cryptoki-profile.patch b/third_party/rust/patches/cryptoki-profile.patch new file mode 100644 index 0000000000000..ac4fdcefac4aa --- /dev/null +++ b/third_party/rust/patches/cryptoki-profile.patch @@ -0,0 +1,22 @@ +diff --git a/cryptoki/src/object.rs b/cryptoki/src/object.rs +index 19c74de..f85ac60 100644 +--- a/cryptoki/src/object.rs ++++ b/cryptoki/src/object.rs +@@ -1007,6 +1007,9 @@ impl ObjectClass { + /// An OTP key object + pub const OTP_KEY: ObjectClass = ObjectClass { val: CKO_OTP_KEY }; + ++ /// A profile object ++ pub const PROFILE: ObjectClass = ObjectClass { val: 9 }; ++ + pub(crate) fn stringify(class: CK_OBJECT_CLASS) -> String { + match class { + CKO_DATA => String::from(stringify!(CKO_DATA)), +@@ -1057,6 +1060,7 @@ impl TryFrom for ObjectClass { + CKO_DOMAIN_PARAMETERS => Ok(ObjectClass::DOMAIN_PARAMETERS), + CKO_MECHANISM => Ok(ObjectClass::MECHANISM), + CKO_OTP_KEY => Ok(ObjectClass::OTP_KEY), ++ 9 => Ok(ObjectClass::PROFILE), + + _ => { + error!("Object class {} is not supported.", object_class); From 6361c83f88f4ce0cbe137ab8baa0410ed5420241 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Thu, 21 Nov 2024 06:15:38 -0800 Subject: [PATCH 3/6] [hsmtool] Support `CKO_DATA` objects 1. Add support for `CKO_DATA` objects (aka "elementary files"). Signed-off-by: Chris Frantz (cherry picked from commit bdab355cfe82879ef849303fb1ef2f15ec6d08f8) --- sw/host/hsmtool/BUILD | 2 + .../hsmtool/src/commands/ecdsa/generate.rs | 1 + sw/host/hsmtool/src/commands/ecdsa/import.rs | 1 + sw/host/hsmtool/src/commands/mod.rs | 4 + sw/host/hsmtool/src/commands/object/list.rs | 10 +- sw/host/hsmtool/src/commands/object/mod.rs | 8 ++ sw/host/hsmtool/src/commands/object/read.rs | 59 ++++++++++++ sw/host/hsmtool/src/commands/object/write.rs | 92 +++++++++++++++++++ sw/host/hsmtool/src/commands/rsa/generate.rs | 1 + sw/host/hsmtool/src/commands/rsa/import.rs | 1 + sw/host/hsmtool/src/commands/spx/generate.rs | 1 + sw/host/hsmtool/src/commands/spx/import.rs | 1 + sw/host/hsmtool/src/util/attribute/error.rs | 2 + sw/host/hsmtool/src/util/helper.rs | 12 ++- 14 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 sw/host/hsmtool/src/commands/object/read.rs create mode 100644 sw/host/hsmtool/src/commands/object/write.rs diff --git a/sw/host/hsmtool/BUILD b/sw/host/hsmtool/BUILD index 306722f59abfb..3dc55b331e678 100644 --- a/sw/host/hsmtool/BUILD +++ b/sw/host/hsmtool/BUILD @@ -20,6 +20,8 @@ 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/write.rs", "src/commands/object/show.rs", "src/commands/object/update.rs", "src/commands/rsa/decrypt.rs", diff --git a/sw/host/hsmtool/src/commands/ecdsa/generate.rs b/sw/host/hsmtool/src/commands/ecdsa/generate.rs index c3b8464822b21..954eb2f2fc57d 100644 --- a/sw/host/hsmtool/src/commands/ecdsa/generate.rs +++ b/sw/host/hsmtool/src/commands/ecdsa/generate.rs @@ -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, }); diff --git a/sw/host/hsmtool/src/commands/ecdsa/import.rs b/sw/host/hsmtool/src/commands/ecdsa/import.rs index 165fa3ecdd090..fa6a655b56552 100644 --- a/sw/host/hsmtool/src/commands/ecdsa/import.rs +++ b/sw/host/hsmtool/src/commands/ecdsa/import.rs @@ -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()); diff --git a/sw/host/hsmtool/src/commands/mod.rs b/sw/host/hsmtool/src/commands/mod.rs index f98b822dd3033..92690254f89e7 100644 --- a/sw/host/hsmtool/src/commands/mod.rs +++ b/sw/host/hsmtool/src/commands/mod.rs @@ -92,6 +92,8 @@ pub struct BasicResult { #[serde(skip_serializing_if = "AttrData::is_none")] label: AttrData, #[serde(skip_serializing_if = "Option::is_none")] + value: Option, + #[serde(skip_serializing_if = "Option::is_none")] error: Option, } @@ -111,6 +113,7 @@ impl Default for BasicResult { success: true, id: AttrData::None, label: AttrData::None, + value: None, error: None, } } @@ -122,6 +125,7 @@ impl BasicResult { success: false, id: AttrData::None, label: AttrData::None, + value: None, error: Some(format!("{:?}", e)), }) } diff --git a/sw/host/hsmtool/src/commands/object/list.rs b/sw/host/hsmtool/src/commands/object/list.rs index a25f3df93d108..2a0681ada854a 100644 --- a/sw/host/hsmtool/src/commands/object/list.rs +++ b/sw/host/hsmtool/src/commands/object/list.rs @@ -16,6 +16,10 @@ use crate::util::attribute::{AttributeMap, AttributeType}; #[derive(clap::Args, Debug, Serialize, Deserialize)] pub struct List { + /// Optional search attributes. + #[arg(short, long)] + #[serde(default)] + search: Option, /// Attributes to show. #[arg(short, long, value_parser=AttributeType::from_str)] #[serde(default)] @@ -36,6 +40,10 @@ impl Dispatch for List { session: Option<&Session>, ) -> Result> { let session = session.ok_or(HsmError::SessionRequired)?; + let search = self + .search + .as_ref() + .map_or(Ok(Vec::new()), |s| s.to_vec())?; let default_attr = [ AttributeType::Id, AttributeType::Label, @@ -53,7 +61,7 @@ impl Dispatch for List { .collect::>>()?; let mut result = Box::::default(); - for object in session.find_objects(&[])? { + for object in session.find_objects(&search)? { let attr = session.get_attributes(object, &attr)?; let attr = AttributeMap::from(attr.as_slice()); result.objects.push(attr); diff --git a/sw/host/hsmtool/src/commands/object/mod.rs b/sw/host/hsmtool/src/commands/object/mod.rs index 5f190a120239e..6ee4ebbcd818b 100644 --- a/sw/host/hsmtool/src/commands/object/mod.rs +++ b/sw/host/hsmtool/src/commands/object/mod.rs @@ -13,15 +13,19 @@ use crate::module::Module; mod destroy; mod list; +mod read; mod show; mod update; +mod write; #[derive(clap::Subcommand, Debug, Serialize, Deserialize)] pub enum Object { Destroy(destroy::Destroy), List(list::List), + Read(read::Read), Show(show::Show), Update(update::Update), + Write(write::Write), } #[typetag::serde(name = "__object__")] @@ -35,8 +39,10 @@ impl Dispatch for Object { match self { Object::Destroy(x) => x.run(context, hsm, session), Object::List(x) => x.run(context, hsm, session), + Object::Read(x) => x.run(context, hsm, session), Object::Show(x) => x.run(context, hsm, session), Object::Update(x) => x.run(context, hsm, session), + Object::Write(x) => x.run(context, hsm, session), } } fn leaf(&self) -> &dyn Dispatch @@ -46,8 +52,10 @@ impl Dispatch for Object { match self { Object::Destroy(x) => x.leaf(), Object::List(x) => x.leaf(), + Object::Read(x) => x.leaf(), Object::Show(x) => x.leaf(), Object::Update(x) => x.leaf(), + Object::Write(x) => x.leaf(), } } } diff --git a/sw/host/hsmtool/src/commands/object/read.rs b/sw/host/hsmtool/src/commands/object/read.rs new file mode 100644 index 0000000000000..b817b131e59f9 --- /dev/null +++ b/sw/host/hsmtool/src/commands/object/read.rs @@ -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 std::path::PathBuf; + +use crate::commands::{BasicResult, Dispatch}; +use crate::error::HsmError; +use crate::module::Module; +use crate::util::attribute::{AttributeError, AttributeMap, AttributeType}; +use crate::util::helper; + +#[derive(clap::Args, Debug, Serialize, Deserialize)] +pub struct Read { + #[arg(long)] + id: Option, + #[arg(short, long)] + label: Option, + /// Search spec + #[arg(short, long)] + search: Option, + #[arg()] + output: PathBuf, +} + +#[typetag::serde(name = "object-read")] +impl Dispatch for Read { + fn run( + &self, + _context: &dyn Any, + _hsm: &Module, + session: Option<&Session>, + ) -> Result> { + let session = session.ok_or(HsmError::SessionRequired)?; + let attr = helper::search_spec_ex( + self.id.as_deref(), + self.label.as_deref(), + self.search.as_ref(), + )?; + let object = helper::find_one_object(session, &attr)?; + let map = AttributeMap::from_object(session, object)?; + let value = map + .get(&AttributeType::Value) + .ok_or(AttributeError::AttributeNotFound(AttributeType::Value))?; + let value = Vec::::try_from(value)?; + let mut result = Box::::default(); + if self.output.to_str() == Some("-") { + result.value = Some(String::from_utf8(value)?); + } else { + std::fs::write(&self.output, value)?; + } + Ok(result) + } +} diff --git a/sw/host/hsmtool/src/commands/object/write.rs b/sw/host/hsmtool/src/commands/object/write.rs new file mode 100644 index 0000000000000..8528be0228714 --- /dev/null +++ b/sw/host/hsmtool/src/commands/object/write.rs @@ -0,0 +1,92 @@ +// 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, AttributeMap, AttributeType, ObjectClass}; + +#[derive(clap::Args, Debug, Serialize, Deserialize)] +pub struct Write { + #[arg(long)] + id: Option, + #[arg(short, long)] + label: Option, + #[arg(short, long, default_value = "false")] + private: bool, + #[arg(short, long)] + application: Option, + #[arg(short, long)] + template: Option, + #[arg()] + input: PathBuf, +} + +#[typetag::serde(name = "object-write")] +impl Dispatch for Write { + fn run( + &self, + _context: &dyn Any, + _hsm: &Module, + session: Option<&Session>, + ) -> Result> { + let session = session.ok_or(HsmError::SessionRequired)?; + + let mut attr = AttributeMap::default(); + let id = self + .id + .as_ref() + .map_or(AttrData::None, |id| AttrData::Str(id.into())); + let label = self + .label + .as_ref() + .map_or(AttrData::None, |label| AttrData::Str(label.into())); + if !id.is_none() { + attr.insert(AttributeType::Id, id.clone()); + } + if !label.is_none() { + attr.insert(AttributeType::Label, label.clone()); + } + if id.is_none() && label.is_none() { + return Err(HsmError::NoSearchCriteria.into()); + } + + let result = Box::new(BasicResult { + success: true, + id, + label, + value: None, + error: None, + }); + + attr.insert( + AttributeType::Class, + AttrData::ObjectClass(ObjectClass::Data), + ); + attr.insert(AttributeType::Token, AttrData::from(true)); + attr.insert(AttributeType::Private, AttrData::from(self.private)); + if let Some(application) = &self.application { + // Is this a bug in opensc-pkcs11 or in the Nitrokey? + // It seems the application string needs a nul terminator. + let mut val = application.clone(); + val.push(0 as char); + attr.insert(AttributeType::Application, AttrData::Str(val)); + } + if let Some(template) = &self.template { + attr.merge(template.clone()); + } + let value = std::fs::read(&self.input)?; + attr.insert(AttributeType::Value, AttrData::from(value.as_slice())); + let attr = attr.to_vec()?; + session.create_object(&attr)?; + Ok(result) + } +} diff --git a/sw/host/hsmtool/src/commands/rsa/generate.rs b/sw/host/hsmtool/src/commands/rsa/generate.rs index 908826bc360f1..58530c7c62a34 100644 --- a/sw/host/hsmtool/src/commands/rsa/generate.rs +++ b/sw/host/hsmtool/src/commands/rsa/generate.rs @@ -73,6 +73,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, }); diff --git a/sw/host/hsmtool/src/commands/rsa/import.rs b/sw/host/hsmtool/src/commands/rsa/import.rs index 266f3c7e65370..c8df6600be665 100644 --- a/sw/host/hsmtool/src/commands/rsa/import.rs +++ b/sw/host/hsmtool/src/commands/rsa/import.rs @@ -91,6 +91,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()); diff --git a/sw/host/hsmtool/src/commands/spx/generate.rs b/sw/host/hsmtool/src/commands/spx/generate.rs index 90b92d955066a..f36428ce67091 100644 --- a/sw/host/hsmtool/src/commands/spx/generate.rs +++ b/sw/host/hsmtool/src/commands/spx/generate.rs @@ -55,6 +55,7 @@ impl Dispatch for Generate { success: true, id: AttrData::Str(key.hash.expect("key hash")), label: AttrData::Str(key.alias), + value: None, error: None, })) } diff --git a/sw/host/hsmtool/src/commands/spx/import.rs b/sw/host/hsmtool/src/commands/spx/import.rs index b32630c66367a..f5494c534994e 100644 --- a/sw/host/hsmtool/src/commands/spx/import.rs +++ b/sw/host/hsmtool/src/commands/spx/import.rs @@ -50,6 +50,7 @@ impl Dispatch for Import { success: true, id: AttrData::Str(key.hash.expect("key hash")), label: AttrData::Str(key.alias), + value: None, error: None, })) } diff --git a/sw/host/hsmtool/src/util/attribute/error.rs b/sw/host/hsmtool/src/util/attribute/error.rs index 0833b2687e076..6d1d74b46330d 100644 --- a/sw/host/hsmtool/src/util/attribute/error.rs +++ b/sw/host/hsmtool/src/util/attribute/error.rs @@ -16,4 +16,6 @@ pub enum AttributeError { UnknownAttribute(Attribute), #[error("Unknown attribute type: {0:?}")] UnknownAttributeType(AttributeType), + #[error("Attribute not found: {0:?}")] + AttributeNotFound(AttributeType), } diff --git a/sw/host/hsmtool/src/util/helper.rs b/sw/host/hsmtool/src/util/helper.rs index 551cd6fcb77d8..418d3b876142f 100644 --- a/sw/host/hsmtool/src/util/helper.rs +++ b/sw/host/hsmtool/src/util/helper.rs @@ -17,8 +17,12 @@ use crate::util::attribute::{AttrData, AttributeMap, AttributeType}; use crate::util::escape::as_hex; /// Constructs a search template given an `id` or `label`. -pub fn search_spec(id: Option<&str>, label: Option<&str>) -> Result> { - let mut attr = AttributeMap::default(); +pub fn search_spec_ex( + id: Option<&str>, + label: Option<&str>, + attr: Option<&AttributeMap>, +) -> Result> { + let mut attr = attr.map_or(Default::default(), |s| s.clone()); if let Some(id) = id { attr.insert(AttributeType::Id, AttrData::Str(id.into())); } @@ -31,6 +35,10 @@ pub fn search_spec(id: Option<&str>, label: Option<&str>) -> Result, label: Option<&str>) -> Result> { + search_spec_ex(id, label, None) +} + /// Returns `true` if one or more objects specified by `id` or `label` exist. pub fn object_exists(session: &Session, id: Option<&str>, label: Option<&str>) -> Result { let attr = search_spec(id, label)?; From 596c2011e4f87d3818b03092d48d837a8639aa01 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Fri, 22 Nov 2024 18:03:40 -0800 Subject: [PATCH 4/6] [hsmtool] Prepare for different SPHINCS+ implementations 1. Create an `SpxInterace` trait. 2. Refactor the acorn library to implement the trait. Signed-off-by: Chris Frantz (cherry picked from commit 54e4af91ec4c439f06dddd3d64093e4d8b378956) --- sw/host/hsmtool/acorn/BUILD | 1 + sw/host/hsmtool/acorn/acorn.rs | 125 ++++++++------------- sw/host/hsmtool/acorn/lib.rs | 4 +- sw/host/hsmtool/acorn/spx.rs | 84 ++++++++++++++ sw/host/hsmtool/src/commands/spx/export.rs | 6 +- sw/host/hsmtool/src/commands/spx/list.rs | 6 +- sw/host/hsmtool/src/module.rs | 5 +- 7 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 sw/host/hsmtool/acorn/spx.rs diff --git a/sw/host/hsmtool/acorn/BUILD b/sw/host/hsmtool/acorn/BUILD index 49625d091fb5d..50bfa611c06e9 100644 --- a/sw/host/hsmtool/acorn/BUILD +++ b/sw/host/hsmtool/acorn/BUILD @@ -12,6 +12,7 @@ rust_library( srcs = [ "acorn.rs", "lib.rs", + "spx.rs", ], crate_root = "lib.rs", deps = [ diff --git a/sw/host/hsmtool/acorn/acorn.rs b/sw/host/hsmtool/acorn/acorn.rs index 507c2d7e7bed0..5e587d597cbb9 100644 --- a/sw/host/hsmtool/acorn/acorn.rs +++ b/sw/host/hsmtool/acorn/acorn.rs @@ -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}")] @@ -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, - /// Algorithm to be used with the key. - pub algorithm: String, - /// Opaque representation of the private key material. - pub private_blob: Vec, - /// Exported private key material (only when GenerateFlags::EXPORT_PRIVATE is set). - pub private_key: Vec, -} - -#[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, - /// Opaque representation of the private key material. - pub private_blob: Vec, -} - 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

(path: P) -> Result + pub fn new

(path: P) -> Result> where P: AsRef, { 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 { + pub fn get_host_version(&self) -> Result { let mut rsp = acorn_bindgen::acorn_response_getVersion::default(); let mut err = std::ptr::null_mut::(); // SAFETY: The arguments to `getVersion` are of the correct type. @@ -206,34 +172,6 @@ impl Acorn { } } - pub fn list_keys(&self) -> Result> { - let mut rsp = acorn_bindgen::acorn_response_list::default(); - let mut err = std::ptr::null_mut::(); - // 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::>(); - 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> { let alias = CString::new(alias)?; @@ -298,8 +236,46 @@ impl Acorn { result } } +} - pub fn get_key_info(&self, alias: &str) -> Result { +impl SpxInterface for Acorn { + fn get_version(&self) -> Result { + Ok(format!( + "{} (see: {})", + self.get_host_version()?, + self.get_see_version()? + )) + } + + fn list_keys(&self) -> Result> { + let mut rsp = acorn_bindgen::acorn_response_list::default(); + let mut err = std::ptr::null_mut::(); + // 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::>(); + 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 { 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 @@ -334,7 +310,7 @@ impl Acorn { } } - pub fn generate_key( + fn generate_key( &self, alias: &str, algorithm: &str, @@ -389,7 +365,7 @@ impl Acorn { } } - pub fn import_keypair( + fn import_keypair( &self, alias: &str, algorithm: &str, @@ -453,12 +429,7 @@ impl Acorn { } } - pub fn sign( - &self, - alias: Option<&str>, - key_hash: Option<&str>, - message: &[u8], - ) -> Result> { + fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result> { 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. @@ -496,7 +467,7 @@ impl Acorn { } } - pub fn verify( + fn verify( &self, alias: Option<&str>, key_hash: Option<&str>, diff --git a/sw/host/hsmtool/acorn/lib.rs b/sw/host/hsmtool/acorn/lib.rs index e16c50030546c..d4fbb4ab24d75 100644 --- a/sw/host/hsmtool/acorn/lib.rs +++ b/sw/host/hsmtool/acorn/lib.rs @@ -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}; diff --git a/sw/host/hsmtool/acorn/spx.rs b/sw/host/hsmtool/acorn/spx.rs new file mode 100644 index 0000000000000..8862cde48561f --- /dev/null +++ b/sw/host/hsmtool/acorn/spx.rs @@ -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, + /// Algorithm to be used with the key. + pub algorithm: String, + /// Opaque representation of the private key material. + pub private_blob: Vec, + /// Exported private key material (only when GenerateFlags::EXPORT_PRIVATE is set). + pub private_key: Vec, +} + +#[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, + /// Opaque representation of the private key material. + pub private_blob: Vec, +} + +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; + + /// Get the version of the backend. + fn list_keys(&self) -> Result>; + + /// Get the public key info. + fn get_key_info(&self, alias: &str) -> Result; + + /// Generate a key pair. + fn generate_key( + &self, + alias: &str, + algorithm: &str, + token: &str, + flags: GenerateFlags, + ) -> Result; + + /// Import a key pair. + fn import_keypair( + &self, + alias: &str, + algorithm: &str, + token: &str, + overwrite: bool, + public_key: &[u8], + private_key: &[u8], + ) -> Result; + + /// Sign a message. + fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result>; + + /// Verify a message. + fn verify( + &self, + alias: Option<&str>, + key_hash: Option<&str>, + message: &[u8], + signature: &[u8], + ) -> Result; +} diff --git a/sw/host/hsmtool/src/commands/spx/export.rs b/sw/host/hsmtool/src/commands/spx/export.rs index 67d7a0b669ad9..d76cdab16a7a2 100644 --- a/sw/host/hsmtool/src/commands/spx/export.rs +++ b/sw/host/hsmtool/src/commands/spx/export.rs @@ -13,7 +13,7 @@ use std::str::FromStr; use crate::commands::{BasicResult, Dispatch}; use crate::error::HsmError; use crate::module::Module; -use acorn::Acorn; +use acorn::SpxInterface; use sphincsplus::{EncodeKey, SphincsPlus, SpxPublicKey}; #[derive(clap::Args, Debug, Serialize, Deserialize)] @@ -24,7 +24,7 @@ pub struct Export { } impl Export { - fn export(&self, acorn: &Acorn) -> Result<()> { + fn export(&self, acorn: &dyn SpxInterface) -> 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)?; @@ -41,7 +41,7 @@ impl Dispatch for Export { hsm: &Module, _session: Option<&Session>, ) -> Result> { - let acorn = hsm.acorn.as_ref().ok_or(HsmError::AcornUnavailable)?; + let acorn = hsm.acorn.as_deref().ok_or(HsmError::AcornUnavailable)?; let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?; self.export(acorn)?; Ok(Box::::default()) diff --git a/sw/host/hsmtool/src/commands/spx/list.rs b/sw/host/hsmtool/src/commands/spx/list.rs index d13c3f68adced..b86ec36a4f7a6 100644 --- a/sw/host/hsmtool/src/commands/spx/list.rs +++ b/sw/host/hsmtool/src/commands/spx/list.rs @@ -24,8 +24,7 @@ pub struct Key { #[derive(Default, Debug, Serialize)] pub struct ListResult { - host_version: String, - see_version: String, + version: String, objects: Vec, } @@ -41,8 +40,7 @@ impl Dispatch for List { 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()?, + version: acorn.get_version()?, ..Default::default() }); let keys = acorn.list_keys()?; diff --git a/sw/host/hsmtool/src/module.rs b/sw/host/hsmtool/src/module.rs index b9baae8f48769..659d9a1dd9253 100644 --- a/sw/host/hsmtool/src/module.rs +++ b/sw/host/hsmtool/src/module.rs @@ -12,11 +12,11 @@ use cryptoki::types::AuthPin; use serde::de::{Deserialize, Deserializer}; use crate::error::HsmError; -use acorn::Acorn; +use acorn::{Acorn, SpxInterface}; pub struct Module { pub pkcs11: Pkcs11, - pub acorn: Option, + pub acorn: Option>, pub token: Option, } @@ -25,6 +25,7 @@ impl Module { let pkcs11 = Pkcs11::new(module)?; pkcs11.initialize(CInitializeArgs::OsThreads)?; let acorn = acorn.map(Acorn::new).transpose()?; + let acorn = acorn.map(|a| a as Box); Ok(Module { pkcs11, acorn, From 26b488d46808665022bd1c40b09a9274700691db Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sat, 23 Nov 2024 14:37:53 -0800 Subject: [PATCH 5/6] [hsmtool] Add a SPX+ implementation for PKCS11 Elementary Files Create a SPHINCS+ implementation that uses key material from PKCS#11 Elementary Files (aka CKO_DATA objects). This implementation stores key material on a secure token, and loads the key material to perform the sphincs+ operations. This is not as secure as signing within an HSM security boundary, but it provides token-protected storage for key material when an HSM-based implementation is not available. Signed-off-by: Chris Frantz (cherry picked from commit 6a0c87792fceaf04db89b34a8609f7819c5ad02e) --- sw/host/hsmtool/BUILD | 5 +- sw/host/hsmtool/src/commands/mod.rs | 4 +- sw/host/hsmtool/src/error.rs | 4 + sw/host/hsmtool/src/hsmtool.rs | 24 +-- sw/host/hsmtool/src/lib.rs | 1 + sw/host/hsmtool/src/module.rs | 60 +++++- sw/host/hsmtool/src/spxef/mod.rs | 219 +++++++++++++++++++++ sw/host/hsmtool/src/util/attribute/data.rs | 7 + sw/host/hsmtool/src/util/ef.rs | 138 +++++++++++++ sw/host/hsmtool/src/util/mod.rs | 1 + sw/host/sphincsplus/BUILD | 1 + sw/host/sphincsplus/key.rs | 8 + 12 files changed, 452 insertions(+), 20 deletions(-) create mode 100644 sw/host/hsmtool/src/spxef/mod.rs create mode 100644 sw/host/hsmtool/src/util/ef.rs diff --git a/sw/host/hsmtool/BUILD b/sw/host/hsmtool/BUILD index 3dc55b331e678..8285793c5ad10 100644 --- a/sw/host/hsmtool/BUILD +++ b/sw/host/hsmtool/BUILD @@ -21,9 +21,9 @@ rust_library( "src/commands/object/list.rs", "src/commands/object/mod.rs", "src/commands/object/read.rs", - "src/commands/object/write.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", @@ -44,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", @@ -54,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", @@ -91,6 +93,7 @@ rust_library( "@crate_index//:strum", "@crate_index//:thiserror", "@crate_index//:typetag", + "@crate_index//:zeroize", "@lowrisc_serde_annotate//serde_annotate", ], ) diff --git a/sw/host/hsmtool/src/commands/mod.rs b/sw/host/hsmtool/src/commands/mod.rs index 92690254f89e7..2cd4c9ffe04c7 100644 --- a/sw/host/hsmtool/src/commands/mod.rs +++ b/sw/host/hsmtool/src/commands/mod.rs @@ -84,7 +84,7 @@ impl Dispatch for Commands { } } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Annotate)] pub struct BasicResult { success: bool, #[serde(skip_serializing_if = "AttrData::is_none")] @@ -92,8 +92,10 @@ pub struct BasicResult { #[serde(skip_serializing_if = "AttrData::is_none")] label: AttrData, #[serde(skip_serializing_if = "Option::is_none")] + #[annotate(format = block)] value: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[annotate(format = block)] error: Option, } diff --git a/sw/host/hsmtool/src/error.rs b/sw/host/hsmtool/src/error.rs index 0162cde2984dc..066976ee7303f 100644 --- a/sw/host/hsmtool/src/error.rs +++ b/sw/host/hsmtool/src/error.rs @@ -36,4 +36,8 @@ pub enum HsmError { DerError(String), #[error("This operation requires the acorn library")] AcornUnavailable, + #[error("Parse error: {0}")] + ParseError(String), + #[error("Unknown application: {0}")] + UnknownApplication(String), } diff --git a/sw/host/hsmtool/src/hsmtool.rs b/sw/host/hsmtool/src/hsmtool.rs index 88de8199035b9..05f9517f9a7c1 100644 --- a/sw/host/hsmtool/src/hsmtool.rs +++ b/sw/host/hsmtool/src/hsmtool.rs @@ -10,7 +10,7 @@ use log::LevelFilter; use std::path::PathBuf; use hsmtool::commands::{print_command, print_result, Commands, Dispatch, Format}; -use hsmtool::module::{self, Module}; +use hsmtool::module::{self, Module, SpxModule}; use hsmtool::profile::Profile; use hsmtool::util::attribute::AttributeMap; @@ -47,9 +47,8 @@ struct Args { #[arg(long, env = "HSMTOOL_MODULE")] module: String, - /// Path to the `acorn` shared library. - #[arg(long, env = "HSMTOOL_ACORN")] - acorn: Option, + #[arg(long, env = "HSMTOOL_SPX_MODULE", help=SpxModule::HELP)] + spx_module: Option, /// HSM Token to use. #[arg(short, long, env = "HSMTOOL_TOKEN")] @@ -85,7 +84,7 @@ fn main() -> Result<()> { .transpose()?; // Initialize the HSM module interface. - let mut hsm = Module::initialize(&args.module, args.acorn.as_deref()).context( + let mut hsm = Module::initialize(&args.module).context( "Loading the PKCS11 module usually depends on several environent variables. Check HSMTOOL_MODULE, SOFTHSM2_CONF or your HSM's documentation.")?; // Initialize the list of all valid attribute types early. Disable logging @@ -99,18 +98,19 @@ fn main() -> Result<()> { return print_command(args.format, args.color, args.command.leaf()); } - let session = if let Some(profile) = &args.profile { + if let Some(profile) = &args.profile { let profiles = Profile::load(&args.profiles)?; let profile = profiles .get(profile) .ok_or_else(|| anyhow!("Profile {profile:?} not found."))?; - Some(hsm.connect(&profile.token, Some(profile.user), profile.pin.as_deref())?) + hsm.connect(&profile.token, Some(profile.user), profile.pin.as_deref())?; } else if let Some(token) = &args.token { - Some(hsm.connect(token, args.user, args.pin.as_deref())?) - } else { - None - }; + hsm.connect(token, args.user, args.pin.as_deref())?; + } + if let Some(spx_module) = &args.spx_module { + hsm.initialize_spx(spx_module)?; + } - let result = args.command.run(&(), &hsm, session.as_ref()); + let result = args.command.run(&(), &hsm, hsm.get_session()); print_result(args.format, args.color, args.quiet, result) } diff --git a/sw/host/hsmtool/src/lib.rs b/sw/host/hsmtool/src/lib.rs index ff0134171ffac..0e3e082cd39bf 100644 --- a/sw/host/hsmtool/src/lib.rs +++ b/sw/host/hsmtool/src/lib.rs @@ -7,4 +7,5 @@ pub mod commands; pub mod error; pub mod module; pub mod profile; +pub mod spxef; pub mod util; diff --git a/sw/host/hsmtool/src/module.rs b/sw/host/hsmtool/src/module.rs index 659d9a1dd9253..fc63670daa5b9 100644 --- a/sw/host/hsmtool/src/module.rs +++ b/sw/host/hsmtool/src/module.rs @@ -10,29 +10,76 @@ use cryptoki::session::UserType; use cryptoki::slot::Slot; use cryptoki::types::AuthPin; use serde::de::{Deserialize, Deserializer}; +use std::rc::Rc; +use std::str::FromStr; use crate::error::HsmError; +use crate::spxef::SpxEf; use acorn::{Acorn, SpxInterface}; +#[derive(Debug, Clone)] +pub enum SpxModule { + Acorn(String), + Pkcs11Ef, +} + +impl SpxModule { + pub const HELP: &'static str = + "Type of sphincs+ module [allowed values: acorn:, pkcs11-ef]"; +} + +impl FromStr for SpxModule { + type Err = HsmError; + fn from_str(s: &str) -> Result { + if s[..6].eq_ignore_ascii_case("acorn:") { + Ok(SpxModule::Acorn(s[6..].into())) + } else if s.eq_ignore_ascii_case("pkcs11-ef") { + Ok(SpxModule::Pkcs11Ef) + } else { + Err(HsmError::ParseError(format!("unknown SpxModule {s:?}"))) + } + } +} + pub struct Module { pub pkcs11: Pkcs11, + pub session: Option>, pub acorn: Option>, pub token: Option, } impl Module { - pub fn initialize(module: &str, acorn: Option<&str>) -> Result { + pub fn initialize(module: &str) -> Result { let pkcs11 = Pkcs11::new(module)?; pkcs11.initialize(CInitializeArgs::OsThreads)?; - let acorn = acorn.map(Acorn::new).transpose()?; - let acorn = acorn.map(|a| a as Box); Ok(Module { pkcs11, - acorn, + session: None, + acorn: None, token: None, }) } + pub fn initialize_spx(&mut self, module: &SpxModule) -> Result<()> { + let module = match module { + SpxModule::Acorn(libpath) => Acorn::new(libpath)? as Box, + SpxModule::Pkcs11Ef => { + let session = self + .session + .as_ref() + .map(Rc::clone) + .ok_or(HsmError::SessionRequired)?; + SpxEf::new(session) as Box + } + }; + self.acorn = Some(module); + Ok(()) + } + + pub fn get_session(&self) -> Option<&Session> { + self.session.as_ref().map(Rc::as_ref) + } + pub fn get_token(&self, label: &str) -> Result { let slots = self.pkcs11.get_slots_with_token()?; for slot in slots { @@ -49,7 +96,7 @@ impl Module { token: &str, user: Option, pin: Option<&str>, - ) -> Result { + ) -> Result<()> { let slot = self.get_token(token)?; let session = self.pkcs11.open_rw_session(slot)?; if let Some(user) = user { @@ -59,7 +106,8 @@ impl Module { .context("Failed HSM Login")?; } self.token = Some(token.into()); - Ok(session) + self.session = Some(Rc::new(session)); + Ok(()) } } diff --git a/sw/host/hsmtool/src/spxef/mod.rs b/sw/host/hsmtool/src/spxef/mod.rs new file mode 100644 index 0000000000000..d64c1fd3389d4 --- /dev/null +++ b/sw/host/hsmtool/src/spxef/mod.rs @@ -0,0 +1,219 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use acorn::{GenerateFlags, KeyEntry, KeyInfo, SpxInterface}; +use anyhow::Result; +use cryptoki::session::Session; +use sphincsplus::{DecodeKey, EncodeKey}; +use sphincsplus::{SphincsPlus, SpxDomain, SpxError, SpxPublicKey, SpxSecretKey}; +use std::rc::Rc; +use std::str::FromStr; +use zeroize::Zeroizing; + +use crate::error::HsmError; +use crate::util::attribute::{AttrData, AttributeMap, AttributeType}; +use crate::util::ef::ElementaryFile; + +/// SpxEf implements host-based SPHINCS+ signing with elementary files stored +/// on a PKCS#11 token. +/// +/// This is not as secure as signing on an HSM, but allows secure storage of +/// the key material on a token. Every effort is made to destroy secret key +/// material loaded to host RAM after use to prevent unintentional leaking of +/// keys. +pub struct SpxEf { + session: Rc, +} + +impl SpxEf { + const APPLICATION: &'static str = "hsmtool-spx"; + + pub fn new(session: Rc) -> Box { + Box::new(Self { session }) + } + + fn load_key(&self, alias: &str) -> Result { + let mut search = AttributeMap::default(); + search.insert(AttributeType::Label, AttrData::Str(alias.into())); + let mut ef = ElementaryFile::find(&self.session, search)?; + if ef.is_empty() { + return Err(HsmError::ObjectNotFound(alias.into()).into()); + } else if ef.len() > 1 { + return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into()); + } + let ef = ef.remove(0); + if let Some(app) = &ef.application { + match app.split_once(':') { + Some((Self::APPLICATION, _algo)) => { + let data = Zeroizing::new(String::from_utf8(ef.read(&self.session)?)?); + return Ok(SpxSecretKey::from_pem(data.as_str())?); + } + Some((_, _)) | None => { + return Err(HsmError::UnknownApplication(app.into()).into()); + } + } + } + Err(HsmError::UnknownApplication("".into()).into()) + } +} + +impl SpxInterface for SpxEf { + /// Get the version of the backend. + fn get_version(&self) -> Result { + Ok(String::from("PKCS#11 ElementaryFiles 0.0.1")) + } + + /// List keys known to the backend. + fn list_keys(&self) -> Result> { + let mut result = Vec::new(); + for file in ElementaryFile::list(&self.session)? { + if let Some(app) = file.application { + match app.split_once(':') { + Some((Self::APPLICATION, algo)) => { + result.push(KeyEntry { + alias: file.name.clone(), + hash: None, + algorithm: algo.into(), + ..Default::default() + }); + } + Some((_, _)) | None => {} + } + } + } + Ok(result) + } + + /// Get the public key info. + fn get_key_info(&self, alias: &str) -> Result { + let sk = self.load_key(alias)?; + let pk = SpxPublicKey::from(&sk); + + Ok(KeyInfo { + hash: "".into(), + algorithm: pk.algorithm().to_string(), + public_key: pk.as_bytes().to_vec(), + private_blob: Vec::new(), + }) + } + + /// Generate a key pair. + fn generate_key( + &self, + alias: &str, + algorithm: &str, + _token: &str, + flags: GenerateFlags, + ) -> Result { + let mut search = AttributeMap::default(); + search.insert(AttributeType::Label, AttrData::Str(alias.into())); + let ef = ElementaryFile::find(&self.session, search)?; + if flags.contains(GenerateFlags::OVERWRITE) { + if ef.len() <= 1 { + // delete files + } else { + return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into()); + } + } else if !ef.is_empty() { + return Err(HsmError::ObjectExists("".into(), alias.into()).into()); + } + + let (sk, _) = SpxSecretKey::new_keypair(SphincsPlus::from_str(algorithm)?)?; + let app = format!("{}:{}", Self::APPLICATION, sk.algorithm()); + let skf = ElementaryFile::new(alias.into()) + .application(app) + .private(true); + let encoded = Zeroizing::new(sk.to_pem()?); + skf.write(&self.session, encoded.as_bytes())?; + + let private_key = if flags.contains(GenerateFlags::EXPORT_PRIVATE) { + sk.as_bytes().to_vec() + } else { + Vec::new() + }; + + Ok(KeyEntry { + alias: alias.into(), + hash: Some("".into()), + algorithm: sk.algorithm().to_string(), + private_blob: Vec::new(), + private_key, + }) + } + + /// Import a key pair. + fn import_keypair( + &self, + alias: &str, + algorithm: &str, + _token: &str, + overwrite: bool, + public_key: &[u8], + private_key: &[u8], + ) -> Result { + let mut search = AttributeMap::default(); + search.insert(AttributeType::Label, AttrData::Str(alias.into())); + let ef = ElementaryFile::find(&self.session, search)?; + if overwrite { + if ef.len() <= 1 { + // delete files + } else { + return Err(HsmError::TooManyObjects(ef.len(), alias.into()).into()); + } + } else if !ef.is_empty() { + return Err(HsmError::ObjectExists("".into(), alias.into()).into()); + } + + let sk = SpxSecretKey::from_bytes(SphincsPlus::from_str(algorithm)?, private_key)?; + let pk = SpxPublicKey::from(&sk); + if public_key != pk.as_bytes() { + return Err(HsmError::KeyError("secret/public key mismatch".into()).into()); + } + let app = format!("{}:{}", Self::APPLICATION, sk.algorithm()); + let skf = ElementaryFile::new(alias.into()) + .application(app) + .private(true); + let encoded = Zeroizing::new(sk.to_pem()?); + skf.write(&self.session, encoded.as_bytes())?; + + Ok(KeyEntry { + alias: alias.into(), + hash: None, + algorithm: sk.algorithm().to_string(), + private_blob: Vec::new(), + private_key: Vec::new(), + }) + } + + /// Sign a message. + fn sign(&self, alias: Option<&str>, key_hash: Option<&str>, message: &[u8]) -> Result> { + let alias = alias.ok_or(HsmError::NoSearchCriteria)?; + if key_hash.is_some() { + log::warn!("ignored key_hash {key_hash:?}"); + } + let sk = self.load_key(alias)?; + Ok(sk.sign(SpxDomain::None, message)?) + } + + /// Verify a message. + fn verify( + &self, + alias: Option<&str>, + key_hash: Option<&str>, + message: &[u8], + signature: &[u8], + ) -> Result { + let alias = alias.ok_or(HsmError::NoSearchCriteria)?; + if key_hash.is_some() { + log::warn!("ignored key_hash {key_hash:?}"); + } + let sk = self.load_key(alias)?; + let pk = SpxPublicKey::from(&sk); + match pk.verify(SpxDomain::None, signature, message) { + Ok(()) => Ok(true), + Err(SpxError::BadSignature) => Ok(false), + Err(e) => Err(e.into()), + } + } +} diff --git a/sw/host/hsmtool/src/util/attribute/data.rs b/sw/host/hsmtool/src/util/attribute/data.rs index 9b58c4a499c2a..89f47fe029062 100644 --- a/sw/host/hsmtool/src/util/attribute/data.rs +++ b/sw/host/hsmtool/src/util/attribute/data.rs @@ -138,6 +138,13 @@ impl AttrData { _ => Err(AttributeError::InvalidDataType), } } + + pub fn try_string(&self) -> Result { + match self { + AttrData::Str(v) => Ok(v.clone()), + _ => Err(AttributeError::InvalidDataType), + } + } } #[cfg(test)] diff --git a/sw/host/hsmtool/src/util/ef.rs b/sw/host/hsmtool/src/util/ef.rs new file mode 100644 index 0000000000000..b1d046d019970 --- /dev/null +++ b/sw/host/hsmtool/src/util/ef.rs @@ -0,0 +1,138 @@ +// 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 crate::util::attribute::{AttrData, AttributeError, AttributeMap, AttributeType, ObjectClass}; +use crate::util::helper; + +#[derive(Clone, Debug, Default)] +pub struct ElementaryFile { + pub name: String, + pub application: Option, + pub private: bool, +} + +impl ElementaryFile { + pub fn new(name: String) -> Self { + Self { + name, + ..Default::default() + } + } + + pub fn application(mut self, app: String) -> Self { + self.application = Some(app); + self + } + + pub fn private(mut self, private: bool) -> Self { + self.private = private; + self + } + + pub fn find(session: &Session, search: AttributeMap) -> Result> { + let mut search = search; + search.insert( + AttributeType::Class, + AttrData::ObjectClass(ObjectClass::Data), + ); + let search = search.to_vec()?; + let attr = [ + AttributeType::Label, + AttributeType::Application, + AttributeType::Private, + ]; + let attr = attr + .iter() + .map(|&a| Ok(a.try_into()?)) + .collect::>>()?; + + let mut result = Vec::new(); + for object in session.find_objects(&search)? { + let data = session.get_attributes(object, &attr)?; + let data = AttributeMap::from(data.as_slice()); + result.push(Self { + name: data + .get(&AttributeType::Label) + .map(|x| x.try_string()) + .transpose()? + .unwrap_or_else(|| String::from("")), + application: data + .get(&AttributeType::Application) + .map(|x| x.try_string()) + .transpose()?, + private: data + .get(&AttributeType::Private) + .map(|x| x.try_into()) + .transpose()? + .unwrap_or(false), + }); + } + Ok(result) + } + + pub fn list(session: &Session) -> Result> { + Self::find(session, AttributeMap::default()) + } + + pub fn exists(self, session: &Session) -> Result { + let mut attr = AttributeMap::default(); + attr.insert( + AttributeType::Class, + AttrData::ObjectClass(ObjectClass::Data), + ); + attr.insert(AttributeType::Label, AttrData::Str(self.name.clone())); + if let Some(app) = &self.application { + attr.insert(AttributeType::Application, AttrData::Str(app.clone())); + } + let attr = attr.to_vec()?; + let objects = session.find_objects(&attr)?; + Ok(!objects.is_empty()) + } + + pub fn read(self, session: &Session) -> Result> { + let mut attr = AttributeMap::default(); + attr.insert( + AttributeType::Class, + AttrData::ObjectClass(ObjectClass::Data), + ); + attr.insert(AttributeType::Label, AttrData::Str(self.name.clone())); + if let Some(app) = &self.application { + attr.insert(AttributeType::Application, AttrData::Str(app.clone())); + } + let attr = attr.to_vec()?; + + let object = helper::find_one_object(session, &attr)?; + let data = AttributeMap::from_object(session, object)?; + let value = data + .get(&AttributeType::Value) + .ok_or(AttributeError::AttributeNotFound(AttributeType::Value))?; + let value = Vec::::try_from(value)?; + Ok(value) + } + + pub fn write(self, session: &Session, data: &[u8]) -> Result<()> { + let mut attr = AttributeMap::default(); + attr.insert( + AttributeType::Class, + AttrData::ObjectClass(ObjectClass::Data), + ); + attr.insert(AttributeType::Label, AttrData::Str(self.name.clone())); + if let Some(application) = &self.application { + // Is this a bug in opensc-pkcs11 or in the Nitrokey? + // It seems the application string needs a nul terminator. + let mut val = application.clone(); + val.push(0 as char); + attr.insert(AttributeType::Application, AttrData::Str(val)); + } + attr.insert(AttributeType::Token, AttrData::from(true)); + attr.insert(AttributeType::Private, AttrData::from(self.private)); + attr.insert(AttributeType::Value, AttrData::from(data)); + let attr = attr.to_vec()?; + session.create_object(&attr)?; + Ok(()) + } +} diff --git a/sw/host/hsmtool/src/util/mod.rs b/sw/host/hsmtool/src/util/mod.rs index c84cc4c72270e..9ef525f64df30 100644 --- a/sw/host/hsmtool/src/util/mod.rs +++ b/sw/host/hsmtool/src/util/mod.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod attribute; +pub mod ef; pub mod escape; pub mod helper; pub mod key; diff --git a/sw/host/sphincsplus/BUILD b/sw/host/sphincsplus/BUILD index aba44de7ea228..5ec209a36806f 100644 --- a/sw/host/sphincsplus/BUILD +++ b/sw/host/sphincsplus/BUILD @@ -64,6 +64,7 @@ rust_library( "@crate_index//:serde", "@crate_index//:strum", "@crate_index//:thiserror", + "@crate_index//:zeroize", ], ) diff --git a/sw/host/sphincsplus/key.rs b/sw/host/sphincsplus/key.rs index 900c156821e3a..8a8463ea191e8 100644 --- a/sw/host/sphincsplus/key.rs +++ b/sw/host/sphincsplus/key.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::fmt; use std::str::FromStr; use strum::{Display, EnumString}; +use zeroize::Zeroize; #[derive(Clone, PartialEq, Eq, Debug)] pub struct SpxPublicKey { @@ -104,6 +105,13 @@ impl SpxSecretKey { } } +impl Drop for SpxSecretKey { + fn drop(&mut self) { + // Destroy the secret key value upon drop. + self.key.zeroize(); + } +} + impl DecodeKey for SpxSecretKey { /// Decodes a SPHINCS+ secret key from a PEM encoded string. fn from_pem(s: &str) -> Result { From 8c050cd5c77eb35ad332d0f74e667af72645fca0 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sat, 23 Nov 2024 20:38:10 -0800 Subject: [PATCH 6/6] [hsmtool] Support sphincs+ signing domains Prepare data according to the input format (plain-text, sha256-hash, etc) and the sphincs+ signing domain (none, pure, prehashedsha256). Signed-off-by: Chris Frantz (cherry picked from commit fbd2358a90e4c9b3f9df545f1ce49aafac8c596f) --- sw/host/hsmtool/src/commands/spx/sign.rs | 13 ++++++ sw/host/hsmtool/src/commands/spx/verify.rs | 13 ++++++ sw/host/hsmtool/src/util/signing.rs | 46 ++++++++++++++++++++++ sw/host/sphincsplus/key.rs | 3 +- 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/sw/host/hsmtool/src/commands/spx/sign.rs b/sw/host/hsmtool/src/commands/spx/sign.rs index 7a1dc0b024f7c..7fb85ead29e63 100644 --- a/sw/host/hsmtool/src/commands/spx/sign.rs +++ b/sw/host/hsmtool/src/commands/spx/sign.rs @@ -6,6 +6,7 @@ use anyhow::Result; use cryptoki::session::Session; use serde::{Deserialize, Serialize}; use serde_annotate::Annotate; +use sphincsplus::SpxDomain; use std::any::Any; use std::path::PathBuf; @@ -13,6 +14,7 @@ use crate::commands::{BasicResult, Dispatch}; use crate::error::HsmError; use crate::module::Module; use crate::util::helper; +use crate::util::signing::SignData; #[derive(clap::Args, Debug, Serialize, Deserialize)] pub struct Sign { @@ -20,6 +22,14 @@ pub struct Sign { id: Option, #[arg(short, long)] label: Option, + #[arg(short, long, default_value = "plain-text", help=SignData::HELP)] + format: SignData, + /// Reverse the input data (for little-endian targets). + #[arg(short = 'r', long)] + little_endian: bool, + /// The SPHINCS+ signing domain. + #[arg(short = 'd', long, default_value = "pure")] + domain: SpxDomain, #[arg(short, long)] output: PathBuf, input: PathBuf, @@ -37,6 +47,9 @@ impl Dispatch for Sign { let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?; let data = helper::read_file(&self.input)?; + let data = self + .format + .spx_prepare(self.domain, &data, self.little_endian)?; let result = acorn.sign(self.label.as_deref(), self.id.as_deref(), &data)?; helper::write_file(&self.output, &result)?; Ok(Box::::default()) diff --git a/sw/host/hsmtool/src/commands/spx/verify.rs b/sw/host/hsmtool/src/commands/spx/verify.rs index e7d9b60f12677..54de012125486 100644 --- a/sw/host/hsmtool/src/commands/spx/verify.rs +++ b/sw/host/hsmtool/src/commands/spx/verify.rs @@ -6,6 +6,7 @@ use anyhow::Result; use cryptoki::session::Session; use serde::{Deserialize, Serialize}; use serde_annotate::Annotate; +use sphincsplus::SpxDomain; use std::any::Any; use std::path::PathBuf; @@ -13,6 +14,7 @@ use crate::commands::{BasicResult, Dispatch}; use crate::error::HsmError; use crate::module::Module; use crate::util::helper; +use crate::util::signing::SignData; #[derive(clap::Args, Debug, Serialize, Deserialize)] pub struct Verify { @@ -20,6 +22,14 @@ pub struct Verify { id: Option, #[arg(short, long)] label: Option, + #[arg(short, long, default_value = "plain-text", help=SignData::HELP)] + format: SignData, + /// Reverse the input data (for little-endian targets). + #[arg(short = 'r', long)] + little_endian: bool, + /// The SPHINCS+ signing domain. + #[arg(short = 'd', long, default_value = "pure")] + domain: SpxDomain, input: PathBuf, signature: PathBuf, } @@ -36,6 +46,9 @@ impl Dispatch for Verify { let _token = hsm.token.as_deref().ok_or(HsmError::SessionRequired)?; let data = helper::read_file(&self.input)?; + let data = self + .format + .spx_prepare(self.domain, &data, self.little_endian)?; let signature = helper::read_file(&self.signature)?; let result = acorn.verify(self.label.as_deref(), self.id.as_deref(), &data, &signature)?; Ok(Box::new(BasicResult { diff --git a/sw/host/hsmtool/src/util/signing.rs b/sw/host/hsmtool/src/util/signing.rs index da11890ca2e01..02837a13800b1 100644 --- a/sw/host/hsmtool/src/util/signing.rs +++ b/sw/host/hsmtool/src/util/signing.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use sha2::digest::const_oid::AssociatedOid; use sha2::digest::Digest; use sha2::Sha256; +use sphincsplus::SpxDomain; use std::str::FromStr; use crate::error::HsmError; @@ -94,6 +95,51 @@ impl SignData { } } + pub fn spx_prepare( + &self, + domain: SpxDomain, + input: &[u8], + little_endian: bool, + ) -> Result> { + match self { + SignData::PlainText => { + match domain { + // Plaintext in domain None or Pure: prepare. + SpxDomain::None | SpxDomain::Pure => Ok(domain.prepare(input).into()), + // Plaintext in domain PreHashed: hash first, then prepare. + SpxDomain::PreHashedSha256 => { + let digest = Sha256::digest(input).as_slice().to_vec(); + Ok(domain.prepare(&digest).into()) + } + } + } + SignData::Sha256Hash => { + // Sha256 input in any domain: perform the domain preparation. + // If the `little_endian` flag is true, we assume the pre-hashed input came + // from opentitantool, which writes out the hash in little endian order, + // and therefore, needs to be reversed before the signing operation. + let digest = Self::data_raw(input, little_endian)?; + Ok(domain.prepare(&digest).into()) + } + SignData::Raw => { + // Raw data in any domain: perform the domain preparation. + Ok(domain.prepare(input).into()) + } + SignData::Slice(a, b) => { + let input = &input[*a..*b]; + match domain { + // A slice of the input in domain None or Pure: prepare. + SpxDomain::None | SpxDomain::Pure => Ok(domain.prepare(input).into()), + // A slice of the input in domain PreHashed: hash first, then prepare. + SpxDomain::PreHashedSha256 => { + let digest = Sha256::digest(input).as_slice().to_vec(); + Ok(domain.prepare(&digest).into()) + } + } + } + } + } + /// Return the `Mechanism` needed during signing or verification. pub fn mechanism(&self, keytype: KeyType) -> Result { match keytype { diff --git a/sw/host/sphincsplus/key.rs b/sw/host/sphincsplus/key.rs index 8a8463ea191e8..9f0b90399d3f8 100644 --- a/sw/host/sphincsplus/key.rs +++ b/sw/host/sphincsplus/key.rs @@ -4,6 +4,7 @@ use crate::{DecodeKey, EncodeKey, SphincsPlus, SpxError}; use pem_rfc7468::LineEnding; +use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt; use std::str::FromStr; @@ -31,7 +32,7 @@ impl fmt::Debug for SpxSecretKey { } } -#[derive(Default, Debug, Clone, Copy, EnumString, Display)] +#[derive(Default, Debug, Clone, Copy, EnumString, Display, Serialize, Deserialize)] #[strum(ascii_case_insensitive)] pub enum SpxDomain { None,