Botan 3.13.0
Crypto and TLS for C&
iso9796.cpp
Go to the documentation of this file.
1/*
2 * ISO-9796-2 - Digital signature schemes giving message recovery schemes 2 and 3
3 * (C) 2016 Tobias Niemann, Hackmanit GmbH
4 * 2025 Jack Lloyd
5 *
6 * Botan is released under the Simplified BSD License (see license.txt)
7 */
8
9#include <botan/internal/iso9796.h>
10
11#include <botan/exceptn.h>
12#include <botan/hash.h>
13#include <botan/rng.h>
14#include <botan/internal/buffer_stuffer.h>
15#include <botan/internal/ct_utils.h>
16#include <botan/internal/fmt.h>
17#include <botan/internal/hash_id.h>
18#include <botan/internal/mgf1.h>
19
20namespace Botan {
21
22namespace {
23
24std::vector<uint8_t> iso9796_hash(HashFunction& hash,
25 std::span<const uint8_t> msg1,
26 std::span<const uint8_t> hmsg2,
27 std::span<const uint8_t> salt) {
28 // Compute H(C || msg1 || H(msg2) || S) as described in the ISO text
29 hash.update_be(static_cast<uint64_t>(msg1.size()) * 8);
30 hash.update(msg1);
31 hash.update(hmsg2);
32 hash.update(salt);
33 return hash.final_stdvec();
34}
35
36std::vector<uint8_t> iso9796_encoding(std::span<const uint8_t> msg,
37 size_t output_bits,
38 std::unique_ptr<HashFunction>& hash,
39 size_t salt_len,
40 bool implicit,
42 const size_t output_length = (output_bits + 7) / 8;
43
44 //set trailer length
45 const size_t trailer_len = (implicit) ? 1 : 2;
46
47 const size_t hash_len = hash->output_length();
48
49 if(output_length <= hash_len + salt_len + trailer_len) {
50 throw Encoding_Error("ISO9796-2::encoding_of: Output length is too small");
51 }
52
53 //calculate message capacity
54 const size_t capacity = output_length - hash_len - salt_len - trailer_len - 1;
55
56 // msg1 is the recoverable part and hmsg2 is the hash of the unrecoverable message part.
57 const size_t msg1_len = std::min(capacity, msg.size());
58 const auto msg1 = msg.first(msg1_len); // the first capacity bytes
59 const auto msg2 = msg.subspan(msg1_len); // the rest; possibly empty
60
61 const auto hmsg2 = hash->process<std::vector<uint8_t>>(msg2);
62 const auto salt = rng.random_vec<std::vector<uint8_t>>(salt_len);
63
64 const auto H = iso9796_hash(*hash, msg1, hmsg2, salt);
65
66 std::vector<uint8_t> EM(output_length);
67
68 BufferStuffer stuffer(EM);
69 stuffer.append(0x00, stuffer.remaining_capacity() - (hash_len + salt_len + trailer_len + msg1_len + 1));
70 stuffer.append(0x01);
71 stuffer.append(msg1);
72 stuffer.append(salt);
73
74 //apply mask
75 const size_t mgf1_bytes = EM.size() - hash_len - trailer_len;
76 mgf1_mask(*hash, H, std::span{EM}.first(mgf1_bytes));
77
78 //clear the unused leftmost bits so the representative is < the modulus
79 EM[0] &= 0xFF >> (8 * output_length - output_bits);
80
81 stuffer.append(H);
82
83 // set implicit/ISO trailer
84
85 if(implicit) {
86 stuffer.append(0xBC);
87 } else {
88 const uint8_t hash_id = ieee1363_hash_id(hash->name());
89 if(hash_id == 0) {
90 throw Encoding_Error("ISO-9796: no hash identifier for " + hash->name());
91 }
92 stuffer.append(hash_id);
93 stuffer.append(0xCC);
94 }
95
96 BOTAN_ASSERT_NOMSG(stuffer.full());
97
98 return EM;
99}
100
101bool iso9796_verification(std::span<const uint8_t> repr,
102 std::span<const uint8_t> raw,
103 size_t key_bits,
104 std::unique_ptr<HashFunction>& hash,
105 size_t salt_len,
106 bool implicit) {
107 const size_t key_bytes = ceil_tobytes(key_bits);
108
109 if(repr.size() > key_bytes) {
110 return false;
111 }
112
113 // The recovered representative is minimally encoded, so left-pad a value
114 // with leading zero bytes back to the full width before parsing (cf PSS).
115 std::vector<uint8_t> coded(key_bytes);
116 {
117 BufferStuffer stuffer(coded);
118 stuffer.append(0x00, key_bytes - repr.size());
119 stuffer.append(repr);
120 }
121
122 if(coded.empty()) {
123 return false;
124 }
125
126 //the trailer length is fixed by the configured mode, and the verifier
127 //rejects a signature whose trailer does not match that mode
128
129 const size_t trailer_len = implicit ? 1 : 2;
130
131 const size_t hash_len = hash->output_length();
132
133 // The encoder requires output_length > hash_len + salt_len + trailer_len so
134 // that the message capacity is at least zero; match that here (the <= rejects
135 // the boundary case where the later capacity computation would underflow).
136 if(coded.size() <= hash_len + trailer_len + salt_len) {
137 return false;
138 }
139
140 if(implicit) {
141 if(coded[coded.size() - 1] != 0xBC) {
142 return false;
143 }
144 } else {
145 const uint8_t hash_id = ieee1363_hash_id(hash->name());
146 if(hash_id == 0) {
147 throw Decoding_Error("ISO-9796: no hash identifier for " + hash->name());
148 }
149
150 const uint8_t trailer_0 = coded[coded.size() - 2];
151 const uint8_t trailer_1 = coded[coded.size() - 1];
152
153 if(trailer_0 != hash_id || trailer_1 != 0xCC) {
154 return false;
155 }
156 }
157
158 // The encoder clears the unused leftmost bits of the representative, so a
159 // valid signature always has them zero; reject otherwise (cf PSS).
160 const size_t top_bits = 8 * key_bytes - key_bits;
161 if(top_bits > 8 - high_bit(coded[0])) {
162 return false;
163 }
164
165 CT::poison(coded.data(), coded.size());
166 //remove mask
167 uint8_t* DB = coded.data();
168 const size_t DB_size = coded.size() - hash_len - trailer_len;
169
170 const uint8_t* H = &coded[DB_size];
171
172 mgf1_mask(*hash, {H, hash_len}, {DB, DB_size});
173 //clear the unused leftmost bits (matching the encoding mask)
174 DB[0] &= 0xFF >> top_bits;
175
176 //recover msg1 and salt
177 size_t msg1_offset = 1;
178
179 auto waiting_for_delim = CT::Mask<uint8_t>::set();
180 auto bad_input = CT::Mask<uint8_t>::cleared();
181
182 for(size_t j = 0; j < DB_size; ++j) {
183 const auto is_zero = CT::Mask<uint8_t>::is_zero(DB[j]);
184 const auto is_one = CT::Mask<uint8_t>::is_equal(DB[j], 0x01);
185
186 const auto add_m = waiting_for_delim & is_zero;
187
188 bad_input |= waiting_for_delim & ~(is_zero | is_one);
189 msg1_offset += add_m.if_set_return(1);
190
191 waiting_for_delim &= is_zero;
192 }
193
194 //invalid, if delimiter 0x01 was not found or msg1_offset is too big
195 bad_input |= waiting_for_delim;
196
197 const auto bad_offset = CT::Mask<size_t>::is_lt(coded.size(), trailer_len + hash_len + msg1_offset + salt_len);
198 bad_input |= CT::Mask<uint8_t>(bad_offset);
199
200 //in case that msg1_offset is too big, just continue with offset = 0.
201 msg1_offset = CT::Mask<size_t>::expand(bad_input.value()).if_not_set_return(msg1_offset);
202
203 CT::unpoison(coded.data(), coded.size());
204 CT::unpoison(msg1_offset);
205
206 const size_t msg1_len = coded.size() - (trailer_len + hash_len + msg1_offset + salt_len);
207
208 const auto msg1 = std::span(coded).subspan(msg1_offset, msg1_len);
209 const auto salt = std::span(coded).subspan(msg1_offset + msg1.size(), salt_len);
210
211 //compute H2(C||msg1||H(msg2)||S*). * indicates a recovered value
212 // key_bytes == ceil_tobytes(key_bits) == the encoder's output_length, so the
213 // verifier splits the message at exactly the capacity the encoder used.
214 const size_t capacity = key_bytes - hash_len - salt_len - trailer_len - 1;
215
216 std::span<const uint8_t> msg1raw = raw;
217 if(msg1raw.size() > capacity) {
218 hash->update(msg1raw.subspan(capacity));
219 msg1raw = msg1raw.first(capacity);
220 }
221
222 const auto hmsg2 = hash->final_stdvec();
223
224 // Compute H(C*||msg1*||H(msg2)||S*) where '*' indicates a recovered value
225 const auto H2 = iso9796_hash(*hash, msg1, hmsg2, salt);
226
227 // Check if H == H2
228 bad_input |= CT::is_not_equal(H, H2.data(), hash_len);
229
230 // Check that msg after MGF1 matches msg in the original
231 bad_input |= ~CT::Mask<uint8_t>(CT::Mask<size_t>::is_equal(msg1.size(), msg1raw.size()));
232 bad_input |= ~CT::is_equal(msg1.data(), msg1raw.data(), std::min(msg1.size(), msg1raw.size()));
233
234 CT::unpoison(bad_input);
235 return (bad_input.as_bool() == false);
236}
237
238} // namespace
239
240/*
241 * ISO-9796-2 signature scheme 2
242 * DS 2 is probabilistic
243 */
244void ISO_9796_DS2::update(const uint8_t input[], size_t length) {
245 //need to buffer message completely, before digest
246 if(length > 0) {
247 m_msg_buffer.insert(m_msg_buffer.end(), input, input + length);
248 }
249}
250
251/*
252 * Return the raw (unencoded) data
253 */
254std::vector<uint8_t> ISO_9796_DS2::raw_data() {
255 std::vector<uint8_t> retbuffer = m_msg_buffer;
256 m_msg_buffer.clear();
257 return retbuffer;
258}
259
260/*
261 * ISO-9796-2 scheme 2 encode operation
262 */
263std::vector<uint8_t> ISO_9796_DS2::encoding_of(std::span<const uint8_t> msg,
264 size_t output_bits,
266 return iso9796_encoding(msg, output_bits, m_hash, m_salt_len, m_implicit, rng);
267}
268
269/*
270 * ISO-9796-2 scheme 2 verify operation
271 */
272bool ISO_9796_DS2::verify(std::span<const uint8_t> repr, std::span<const uint8_t> raw, size_t key_bits) {
273 return iso9796_verification(repr, raw, key_bits, m_hash, m_salt_len, m_implicit);
274}
275
276std::string ISO_9796_DS2::hash_function() const {
277 return m_hash->name();
278}
279
280/*
281 * Return the SCAN name
282 */
283std::string ISO_9796_DS2::name() const {
284 return fmt("ISO_9796_DS2({},{},{})", m_hash->name(), (m_implicit ? "imp" : "exp"), m_salt_len);
285}
286
287/*
288 * ISO-9796-2 signature scheme 3
289 * DS 3 is deterministic and equals DS2 without salt
290 */
291void ISO_9796_DS3::update(const uint8_t input[], size_t length) {
292 //need to buffer message completely, before digest
293 if(length > 0) {
294 m_msg_buffer.insert(m_msg_buffer.end(), input, input + length);
295 }
296}
297
298/*
299 * Return the raw (unencoded) data
300 */
301std::vector<uint8_t> ISO_9796_DS3::raw_data() {
302 std::vector<uint8_t> retbuffer = m_msg_buffer;
303 m_msg_buffer.clear();
304 return retbuffer;
305}
306
307/*
308 * ISO-9796-2 scheme 3 encode operation
309 */
310std::vector<uint8_t> ISO_9796_DS3::encoding_of(std::span<const uint8_t> msg,
311 size_t output_bits,
313 return iso9796_encoding(msg, output_bits, m_hash, 0, m_implicit, rng);
314}
315
316/*
317 * ISO-9796-2 scheme 3 verify operation
318 */
319bool ISO_9796_DS3::verify(std::span<const uint8_t> repr, std::span<const uint8_t> raw, size_t key_bits) {
320 return iso9796_verification(repr, raw, key_bits, m_hash, 0, m_implicit);
321}
322
323std::string ISO_9796_DS3::hash_function() const {
324 return m_hash->name();
325}
326
327/*
328 * Return the SCAN name
329 */
330std::string ISO_9796_DS3::name() const {
331 return fmt("ISO_9796_DS3({},{})", m_hash->name(), (m_implicit ? "imp" : "exp"));
332}
333
334} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
Helper class to ease in-place marshalling of concatenated fixed-length values.
static constexpr Mask< T > set()
Definition ct_utils.h:382
static constexpr Mask< T > expand(T v)
Definition ct_utils.h:392
static constexpr Mask< T > is_equal(T x, T y)
Definition ct_utils.h:442
static constexpr Mask< T > is_lt(T x, T y)
Definition ct_utils.h:450
static constexpr Mask< T > is_zero(T x)
Definition ct_utils.h:437
static constexpr Mask< T > cleared()
Definition ct_utils.h:387
std::string name() const override
Definition iso9796.cpp:283
std::string hash_function() const override
Definition iso9796.cpp:276
std::string hash_function() const override
Definition iso9796.cpp:323
std::string name() const override
Definition iso9796.cpp:330
virtual std::vector< uint8_t > raw_data()=0
constexpr CT::Mask< T > is_not_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:838
constexpr void unpoison(const T *p, size_t n)
Definition ct_utils.h:67
constexpr void poison(const T *p, size_t n)
Definition ct_utils.h:56
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
void mgf1_mask(HashFunction &hash, std::span< const uint8_t > input, std::span< uint8_t > output)
Definition mgf1.cpp:15
BOTAN_FORCE_INLINE constexpr T ceil_tobytes(T bits)
Definition bit_ops.h:175
uint8_t ieee1363_hash_id(std::string_view name)
Definition hash_id.cpp:144
BOTAN_FORCE_INLINE constexpr size_t high_bit(T n)
Definition bit_ops.h:73