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