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