Skip to content

Commit 2510b40

Browse files
anthonychen1251pamaury
authored andcommitted
[opentitanlib] Add PEM parsing for SPX+ signature
This introduces support for parsing SPX+ signatures from PEM-encoded files. A new `SpxRawSignature` struct is introduced to encapsulate raw SPX+ signature data. Its `read_from_file` method now detects whether a signature file is in raw binary format (based on size) or PEM format. The `opentitantool` commands (`image` and `spx`) have been updated to leverage the new `SpxSignature` struct and its file reading capabilities. Signed-off-by: Anthony Chen <[email protected]> (cherry picked from commit 4d3dd86)
1 parent 98b587a commit 2510b40

File tree

6 files changed

+91
-10
lines changed

6 files changed

+91
-10
lines changed

sw/host/opentitanlib/src/crypto/ecdsa.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,9 @@ impl EcdsaRawSignature {
141141

142142
// Let's try interpreting the file as ASN.1 DER.
143143
// If unsuccessful, attempt PEM decoding.
144-
EcdsaRawSignature::from_der(&data).or_else(|_| {
145-
EcdsaRawSignature::from_pem(&data)
146-
.with_context(|| format!("Failed parsing {path:?}"))
147-
})
144+
EcdsaRawSignature::from_der(&data)
145+
.or_else(|_| EcdsaRawSignature::from_pem(&data))
146+
.with_context(|| format!("Failed parsing {path:?}"))
148147
}
149148
}
150149

sw/host/opentitantool/src/command/image.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ use opentitanlib::image::manifest_def::ManifestSpec;
2626
use opentitanlib::image::manifest_ext::{ManifestExtEntry, ManifestExtId};
2727
use opentitanlib::util::file::{FromReader, ToWriter};
2828
use opentitanlib::util::parse_int::ParseInt;
29-
use sphincsplus::{DecodeKey, SpxDomain, SpxError, SpxPublicKey, SpxSecretKey};
29+
use sphincsplus::{
30+
DecodeKey, SphincsPlus, SpxDomain, SpxError, SpxPublicKey, SpxRawSignature, SpxSecretKey,
31+
};
3032

3133
/// Bootstrap the target device.
3234
#[derive(Debug, Args)]
@@ -144,6 +146,9 @@ pub struct ManifestUpdateCommand {
144146
/// The signature domain (None, Pure, PreHashedSha256)
145147
#[arg(long, default_value_t = SpxDomain::default())]
146148
domain: SpxDomain,
149+
/// The signature algorithm (Shake128sSimple, Sha2128sSimple)
150+
#[arg(long, default_value_t = SphincsPlus::Sha2128sSimple)]
151+
spx_algorithm: SphincsPlus,
147152
/// Set to true if the firmware uses a byte-reversed representation of the hash.
148153
#[arg(long, action = clap::ArgAction::Set, default_value = "false")]
149154
spx_hash_reversal_bug: bool,
@@ -343,8 +348,10 @@ impl CommandDispatch for ManifestUpdateCommand {
343348
}
344349
// Attach SPX+ signature.
345350
if let Some(spx_signature) = &self.spx_signature {
346-
let signature = std::fs::read(spx_signature)?;
347-
image.add_manifest_extension(ManifestExtEntry::new_spx_signature_entry(&signature)?)?;
351+
let signature = SpxRawSignature::read_from_file(spx_signature, self.spx_algorithm)?;
352+
image.add_manifest_extension(ManifestExtEntry::new_spx_signature_entry(
353+
signature.as_bytes(),
354+
)?)?;
348355
}
349356

350357
image.write_to_file(self.output.as_ref().unwrap_or(&self.image))?;

sw/host/opentitantool/src/command/spx.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use std::path::PathBuf;
1010

1111
use opentitanlib::app::TransportWrapper;
1212
use opentitanlib::app::command::CommandDispatch;
13-
use sphincsplus::{DecodeKey, EncodeKey, SphincsPlus, SpxDomain, SpxPublicKey, SpxSecretKey};
13+
use sphincsplus::{
14+
DecodeKey, EncodeKey, SphincsPlus, SpxDomain, SpxPublicKey, SpxRawSignature, SpxSecretKey,
15+
};
1416

1517
#[derive(Annotate)]
1618
pub struct SpxPublicKeyInfo {
@@ -155,6 +157,9 @@ pub struct SpxVerifyCommand {
155157
/// The signature domain (Raw, Pure, PreHashedSha256)
156158
#[arg(long, default_value_t = SpxDomain::default())]
157159
domain: SpxDomain,
160+
/// The signature algorithm (Shake128sSimple, Sha2128sSimple)
161+
#[arg(long, default_value_t = SphincsPlus::Sha2128sSimple)]
162+
spx_algorithm: SphincsPlus,
158163
/// The file containing the SPHINCS+ raw public key in PEM format.
159164
#[arg(value_name = "KEY")]
160165
public_key: PathBuf,
@@ -175,8 +180,8 @@ impl CommandDispatch for SpxVerifyCommand {
175180
message.reverse();
176181
}
177182
let public_key = SpxPublicKey::read_pem_file(&self.public_key)?;
178-
let signature = std::fs::read(&self.signature)?;
179-
public_key.verify(self.domain, &signature, &message)?;
183+
let signature = SpxRawSignature::read_from_file(&self.signature, self.spx_algorithm)?;
184+
public_key.verify(self.domain, signature.as_bytes(), &message)?;
180185
Ok(None)
181186
}
182187
}

sw/host/sphincsplus/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ rust_library(
5252
"error.rs",
5353
"key.rs",
5454
"lib.rs",
55+
"signature.rs",
5556
"variants.rs",
5657
],
5758
proc_macro_deps = [
@@ -60,6 +61,7 @@ rust_library(
6061
deps = [
6162
":bindgen_sha2_128s_simple",
6263
":bindgen_shake_128s_simple",
64+
"//sw/host/opentitanlib/util",
6365
"@crate_index//:asn1",
6466
"@crate_index//:pem-rfc7468",
6567
"@crate_index//:serde",

sw/host/sphincsplus/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
mod error;
66
mod key;
7+
mod signature;
78
mod variants;
89

910
pub use error::SpxError;
1011
pub use key::{SpxDomain, SpxPublicKey, SpxSecretKey};
12+
pub use signature::SpxRawSignature;
1113
pub use variants::SphincsPlus;
1214

1315
use std::path::Path;

sw/host/sphincsplus/signature.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use pem_rfc7468::Decoder;
6+
use std::fs::File;
7+
use std::io::Read;
8+
use std::path::Path;
9+
10+
use crate::{SphincsPlus, SpxError};
11+
use util::clean_pem_bytes_for_parsing;
12+
13+
#[derive(Clone, PartialEq, Eq, Debug)]
14+
pub struct SpxRawSignature {
15+
raw_data: Vec<u8>,
16+
}
17+
18+
impl SpxRawSignature {
19+
pub fn read(src: &mut impl Read, algorithm: SphincsPlus) -> Result<Self, SpxError> {
20+
let mut raw_data = Vec::new();
21+
raw_data.resize(algorithm.signature_len(), 0);
22+
src.read_exact(&mut raw_data)?;
23+
Ok(SpxRawSignature { raw_data })
24+
}
25+
26+
pub fn read_from_file(path: &Path, algorithm: SphincsPlus) -> Result<Self, SpxError> {
27+
let mut file = File::open(path)?;
28+
let file_size = std::fs::metadata(path)?.len() as usize;
29+
30+
if file_size == algorithm.signature_len() {
31+
// This must be a raw signature, just read it as is.
32+
SpxRawSignature::read(&mut file, algorithm)
33+
} else {
34+
let mut data = Vec::<u8>::new();
35+
36+
file.read_to_end(&mut data)?;
37+
38+
// Try parsing as PEM decoding.
39+
SpxRawSignature::from_pem(&data, algorithm)
40+
}
41+
}
42+
43+
fn from_pem(data: &[u8], algorithm: SphincsPlus) -> Result<Self, SpxError> {
44+
// Ensures valid PEM markers and a recognized label are present.
45+
let _ = pem_rfc7468::decode_label(data)?;
46+
let mut raw_data = Vec::new();
47+
let result = Decoder::new(data);
48+
match result {
49+
Ok(mut decoder) => decoder.decode_to_end(&mut raw_data)?,
50+
_ => {
51+
let cleaned_data = clean_pem_bytes_for_parsing(data)?;
52+
let mut decoder = Decoder::new(&cleaned_data)?;
53+
decoder.decode_to_end(&mut raw_data)?
54+
}
55+
};
56+
if algorithm.signature_len() != raw_data.len() {
57+
return Err(SpxError::BadSigLength(raw_data.len()));
58+
}
59+
Ok(SpxRawSignature { raw_data })
60+
}
61+
62+
/// Returns the raw signature bytes.
63+
pub fn as_bytes(&self) -> &[u8] {
64+
self.raw_data.as_slice()
65+
}
66+
}

0 commit comments

Comments
 (0)