hsmtool/util/
signing.rs

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
5use anyhow::Result;
6use cryptoki::mechanism::Mechanism;
7use cryptoki::mechanism::vendor_defined::VendorDefinedMechanism;
8use rsa::pkcs1v15::Pkcs1v15Sign;
9use serde::{Deserialize, Serialize};
10use sha2::Sha256;
11use sha2::digest::Digest;
12use sha2::digest::const_oid::AssociatedOid;
13use sphincsplus::SpxDomain;
14use std::str::FromStr;
15
16use crate::error::HsmError;
17use crate::util::attribute::{KeyType, MechanismType};
18use crate::util::helper::parse_range;
19
20/// Specify the ML-DSA domain.
21#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[value(rename_all = "kebab-case")]
23pub enum MlDsaDomain {
24    /// Pure ML-DSA (sign the message directly).
25    Pure,
26    /// Pre-hashed ML-DSA (sign a hash of the message).
27    PreHashed,
28}
29
30/// Specify the type of data being signed or verified.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum SignData {
33    /// The data to be signed is plain text.
34    /// The data will be hashed and padded as specified by the signing algorithm.
35    #[serde(alias = "plain-text")]
36    PlainText,
37    /// The data to be signed is a SHA-256 hash.
38    /// The data will be padded as specified by the signing algorithm.
39    #[serde(alias = "sha256-hash")]
40    Sha256Hash,
41    /// The data to be signed is a SHA-256 hash with the bytes in reverse order.
42    /// The data will be padded as specified by the signing algorithm.
43    #[serde(alias = "sha256-hash-reversed")]
44    Sha256HashReversed,
45    /// The data is raw and will be passed directly to the signing functions.
46    #[serde(alias = "raw")]
47    Raw,
48    /// The data to be signed is plain text.
49    /// A slice of the data will be hashed and padded as specified by the signing algorithm.
50    #[serde(alias = "slice")]
51    Slice(usize, usize),
52}
53
54impl FromStr for SignData {
55    type Err = anyhow::Error;
56    fn from_str(input: &str) -> Result<Self> {
57        if input.eq_ignore_ascii_case("plain-text") {
58            Ok(SignData::PlainText)
59        } else if input.eq_ignore_ascii_case("sha256-hash") {
60            Ok(SignData::Sha256Hash)
61        } else if input.eq_ignore_ascii_case("sha256-hash-reversed") {
62            Ok(SignData::Sha256HashReversed)
63        } else if input.eq_ignore_ascii_case("raw") {
64            Ok(SignData::Raw)
65        } else if input[..6].eq_ignore_ascii_case("slice:") {
66            let r = parse_range(&input[6..])?;
67            Ok(SignData::Slice(r.start, r.end))
68        } else {
69            Err(HsmError::Unsupported(format!("invalid variant: {input}")).into())
70        }
71    }
72}
73
74impl SignData {
75    pub const HELP: &'static str = "[allowed values: plain-text, sha256-hash, raw, slice:m..n]";
76    /// Prepare `input` data for signing or verification.
77    pub fn prepare(&self, keytype: KeyType, input: &[u8]) -> Result<Vec<u8>> {
78        match keytype {
79            KeyType::Rsa => match self {
80                // Data is plaintext: hash, then add PKCSv1.5 padding.
81                SignData::PlainText => Self::pkcs15sign::<Sha256>(&Self::data_plain_text(input)?),
82                // Data is already hashed: add PKCSv1.5 padding.
83                SignData::Sha256Hash => Self::pkcs15sign::<Sha256>(&Self::data_raw(input, false)?),
84                SignData::Sha256HashReversed => {
85                    Self::pkcs15sign::<Sha256>(&Self::data_raw(input, true)?)
86                }
87                // Raw data requires no transformation.
88                SignData::Raw => Self::data_raw(input, false),
89                // Data is a slice of plaintext: hash, then add PKCSv1.5 padding.
90                SignData::Slice(a, b) => {
91                    Self::pkcs15sign::<Sha256>(&Self::data_plain_text(&input[*a..*b])?)
92                }
93            },
94            KeyType::Ec => match self {
95                // Data is plaintext: hash.
96                SignData::PlainText => Self::data_plain_text(input),
97                SignData::Sha256Hash => Self::data_raw(input, false),
98                SignData::Sha256HashReversed => Self::data_raw(input, true),
99                // Raw data requires no transformation.
100                SignData::Raw => Self::data_raw(input, false),
101                // Data is a slice of plaintext: hash.
102                SignData::Slice(a, b) => Self::data_plain_text(&input[*a..*b]),
103            },
104            _ => Err(HsmError::Unsupported(format!("SignData prepare for {keytype:?}")).into()),
105        }
106    }
107
108    pub fn mldsa_prepare(&self, domain: MlDsaDomain, input: &[u8]) -> Result<Vec<u8>> {
109        match self {
110            SignData::PlainText => match domain {
111                MlDsaDomain::Pure => Ok(input.into()),
112                MlDsaDomain::PreHashed => Ok(Sha256::digest(input).as_slice().to_vec()),
113            },
114            SignData::Sha256Hash => Ok(input.into()),
115            SignData::Sha256HashReversed => Self::data_raw(input, true),
116            SignData::Raw => Ok(input.into()),
117            SignData::Slice(a, b) => {
118                let input = &input[*a..*b];
119                match domain {
120                    MlDsaDomain::Pure => Ok(input.into()),
121                    MlDsaDomain::PreHashed => Ok(Sha256::digest(input).as_slice().to_vec()),
122                }
123            }
124        }
125    }
126
127    pub fn spx_prepare(&self, domain: SpxDomain, input: &[u8]) -> Result<Vec<u8>> {
128        match self {
129            SignData::PlainText => {
130                match domain {
131                    // Plaintext in domain None or Pure: nothing to do.
132                    SpxDomain::None | SpxDomain::Pure => Ok(input.into()),
133                    // Plaintext in domain PreHashed: compute hash.
134                    SpxDomain::PreHashedSha256 => Ok(Sha256::digest(input).as_slice().to_vec()),
135                }
136            }
137            SignData::Sha256Hash => {
138                // Sha256 input in any domain: nothing to do.
139                Ok(input.into())
140            }
141            SignData::Sha256HashReversed => {
142                // Sha256 that requires reversal in any domain: reverse the input.
143                Self::data_raw(input, /*reverse=*/ true)
144            }
145            SignData::Raw => {
146                // Raw data in any domain: nothing to do.
147                Ok(input.into())
148            }
149            SignData::Slice(a, b) => {
150                let input = &input[*a..*b];
151                match domain {
152                    // A slice of the input in domain None or Pure: nothing to do.
153                    SpxDomain::None | SpxDomain::Pure => Ok(input.into()),
154                    // A slice of the input in domain PreHashed: hash the input.
155                    SpxDomain::PreHashedSha256 => Ok(Sha256::digest(input).as_slice().to_vec()),
156                }
157            }
158        }
159    }
160
161    /// Return the `Mechanism` needed during signing or verification.
162    pub fn mechanism(&self, keytype: KeyType) -> Result<Mechanism<'_>> {
163        match keytype {
164            KeyType::Rsa => match self {
165                SignData::PlainText => Ok(Mechanism::RsaPkcs),
166                SignData::Sha256Hash => Ok(Mechanism::RsaPkcs),
167                SignData::Sha256HashReversed => Ok(Mechanism::RsaPkcs),
168                SignData::Raw => Err(HsmError::Unsupported(
169                    "rust-cryptoki Mechanism doesn't include RSA_X_509".into(),
170                )
171                .into()),
172                SignData::Slice(_, _) => Ok(Mechanism::RsaPkcs),
173            },
174            KeyType::Ec => match self {
175                SignData::PlainText => Ok(Mechanism::Ecdsa),
176                SignData::Sha256Hash => Ok(Mechanism::Ecdsa),
177                SignData::Sha256HashReversed => Ok(Mechanism::Ecdsa),
178                SignData::Raw => Ok(Mechanism::Ecdsa),
179                SignData::Slice(_, _) => Ok(Mechanism::Ecdsa),
180            },
181            KeyType::MlDsa => {
182                let mechanism = Mechanism::VendorDefined(VendorDefinedMechanism::new::<()>(
183                    MechanismType::MlDsa.try_into()?,
184                    None,
185                ));
186                match self {
187                    SignData::PlainText => Ok(mechanism),
188                    SignData::Sha256Hash => Ok(mechanism),
189                    SignData::Sha256HashReversed => Ok(mechanism),
190                    SignData::Raw => Ok(mechanism),
191                    SignData::Slice(_, _) => Ok(mechanism),
192                }
193            }
194            _ => Err(HsmError::Unsupported(format!("No mechanism for {keytype:?}")).into()),
195        }
196    }
197
198    fn data_raw(input: &[u8], reverse: bool) -> Result<Vec<u8>> {
199        let mut result = Vec::new();
200        result.extend_from_slice(input);
201        if reverse {
202            result.reverse();
203        }
204        Ok(result)
205    }
206
207    fn pkcs15sign<D>(input: &[u8]) -> Result<Vec<u8>>
208    where
209        D: Digest + AssociatedOid,
210    {
211        let s = Pkcs1v15Sign::new::<D>();
212        let hash_len = s.hash_len.unwrap();
213        if hash_len != input.len() {
214            return Err(HsmError::HashSizeError(hash_len, input.len()).into());
215        }
216        let mut result = Vec::new();
217        result.extend_from_slice(&s.prefix);
218        result.extend_from_slice(input);
219        Ok(result)
220    }
221
222    fn data_plain_text(input: &[u8]) -> Result<Vec<u8>> {
223        let result = Sha256::digest(input).as_slice().to_vec();
224        Ok(result)
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn test_raw() -> Result<()> {
234        let result = SignData::Raw.prepare(KeyType::Rsa, b"abc123")?;
235        assert_eq!(result, b"abc123");
236        Ok(())
237    }
238
239    #[test]
240    fn test_plain_text() -> Result<()> {
241        let result = SignData::PlainText.prepare(
242            KeyType::Rsa,
243            b"The quick brown fox jumped over the lazy dog",
244        )?;
245        assert_eq!(
246            hex::encode(result),
247            "3031300d0609608648016503040201050004207d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69"
248        );
249
250        let result = SignData::PlainText
251            .prepare(KeyType::Ec, b"The quick brown fox jumped over the lazy dog")?;
252        assert_eq!(
253            hex::encode(result),
254            "7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69",
255        );
256        Ok(())
257    }
258
259    #[test]
260    fn test_hashed() -> Result<()> {
261        let input =
262            hex::decode("7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69")?;
263        let result = SignData::Sha256Hash.prepare(KeyType::Rsa, &input)?;
264        assert_eq!(
265            hex::encode(result),
266            "3031300d0609608648016503040201050004207d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69"
267        );
268
269        assert!(SignData::Sha256Hash.prepare(KeyType::Rsa, b"").is_err());
270        Ok(())
271    }
272
273    #[test]
274    fn test_slice() -> Result<()> {
275        let result = SignData::Slice(0, 3)
276            .prepare(KeyType::Ec, b"The quick brown fox jumped over the lazy dog")?;
277        assert_eq!(
278            hex::encode(result),
279            // Hash of "The".
280            "b344d80e24a3679999fa964450b34bc24d1578a35509f934c1418b0a20d21a67",
281        );
282        Ok(())
283    }
284}