Botan 3.9.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/ct_utils.h>
15#include <botan/internal/fmt.h>
16#include <botan/internal/hash_id.h>
17#include <botan/internal/mgf1.h>
18
19namespace Botan {
20
21namespace {
22
23std::vector<uint8_t> iso9796_hash(HashFunction& hash,
24 std::span<const uint8_t> msg1,
25 std::span<const uint8_t> hmsg2,
26 std::span<const uint8_t> salt) {
27 // Compute H(C || msg1 || H(msg2) || S) as described in the ISO text
28 hash.update_be(static_cast<uint64_t>(msg1.size()) * 8);
29 hash.update(msg1);
30 hash.update(hmsg2);
31 hash.update(salt);
32 return hash.final_stdvec();
33}
34
35std::vector<uint8_t> iso9796_encoding(std::span<const uint8_t> msg,
36 size_t output_bits,
37 std::unique_ptr<HashFunction>& hash,
38 size_t salt_len,
39 bool implicit,
41 const size_t output_length = (output_bits + 7) / 8;
42
43 //set trailer length
44 const size_t trailer_len = (implicit) ? 1 : 2;
45
46 const size_t hash_len = hash->output_length();
47
48 if(output_length <= hash_len + salt_len + trailer_len) {
49 throw Encoding_Error("ISO9796-2::encoding_of: Output length is too small");
50 }
51
52 //calculate message capacity
53 const size_t capacity = output_length - hash_len - salt_len - trailer_len - 1;
54
55 // msg1 is the recoverable part and hmsg2 is the hash of the unrecoverable message part.
56 const size_t msg1_len = std::min(capacity, msg.size());
57 const auto msg1 = msg.first(msg1_len); // the first capacity bytes
58 const auto msg2 = msg.subspan(msg1_len); // the rest; possibly empty
59
60 const auto hmsg2 = hash->process<std::vector<uint8_t>>(msg2);
61 const auto salt = rng.random_vec<std::vector<uint8_t>>(salt_len);
62
63 const auto H = iso9796_hash(*hash, msg1, hmsg2, salt);
64
65 std::vector<uint8_t> EM(output_length);
66
67 BufferStuffer stuffer(EM);
68 stuffer.append(0x00, stuffer.remaining_capacity() - (hash_len + salt_len + trailer_len + msg1_len + 1));
69 stuffer.append(0x01);
70 stuffer.append(msg1);
71 stuffer.append(salt);
72
73 //apply mask
74 mgf1_mask(*hash, H.data(), hash_len, EM.data(), output_length - hash_len - trailer_len);
75
76 //clear the leftmost bit (confer bouncy castle)
77 EM[0] &= 0x7F;
78
79 stuffer.append(H);
80
81 // set implicit/ISO trailer
82
83 if(implicit) {
84 stuffer.append(0xBC);
85 } else {
86 const uint8_t hash_id = ieee1363_hash_id(hash->name());
87 if(hash_id == 0) {
88 throw Encoding_Error("ISO-9796: no hash identifier for " + hash->name());
89 }
90 stuffer.append(hash_id);
91 stuffer.append(0xCC);
92 }
93
94 BOTAN_ASSERT_NOMSG(stuffer.full());
95
96 return EM;
97}
98
99bool iso9796_verification(std::span<const uint8_t> repr,
100 std::span<const uint8_t> raw,
101 size_t key_bits,
102 std::unique_ptr<HashFunction>& hash,
103 size_t salt_len) {
104 if(repr.size() != (key_bits + 7) / 8) {
105 return false;
106 }
107 //get trailer length
108
109 const uint8_t last = repr[repr.size() - 1];
110
111 if(last != 0xBC && last != 0xCC) {
112 return false;
113 }
114
115 const size_t trailer_len = last == 0xBC ? 1 : 2;
116
117 if(trailer_len == 2) {
118 uint8_t hash_id = ieee1363_hash_id(hash->name());
119 if(hash_id == 0) {
120 throw Decoding_Error("ISO-9796: no hash identifier for " + hash->name());
121 }
122
123 const uint8_t trailer_0 = repr[repr.size() - 2];
124 const uint8_t trailer_1 = repr[repr.size() - 1];
125
126 if(trailer_0 != hash_id || trailer_1 != 0xCC) {
127 return false;
128 }
129 }
130
131 const size_t hash_len = hash->output_length();
132
133 if(repr.size() < hash_len + trailer_len + salt_len) {
134 return false;
135 }
136
137 std::vector<uint8_t> coded(repr.begin(), repr.end());
138
139 CT::poison(coded.data(), coded.size());
140 //remove mask
141 uint8_t* DB = coded.data();
142 const size_t DB_size = coded.size() - hash_len - trailer_len;
143
144 const uint8_t* H = &coded[DB_size];
145
146 mgf1_mask(*hash, H, hash_len, DB, DB_size);
147 //clear the leftmost bit (confer bouncy castle)
148 DB[0] &= 0x7F;
149
150 //recover msg1 and salt
151 size_t msg1_offset = 1;
152
153 auto waiting_for_delim = CT::Mask<uint8_t>::set();
154 auto bad_input = CT::Mask<uint8_t>::cleared();
155
156 for(size_t j = 0; j < DB_size; ++j) {
157 const auto is_zero = CT::Mask<uint8_t>::is_zero(DB[j]);
158 const auto is_one = CT::Mask<uint8_t>::is_equal(DB[j], 0x01);
159
160 const auto add_m = waiting_for_delim & is_zero;
161
162 bad_input |= waiting_for_delim & ~(is_zero | is_one);
163 msg1_offset += add_m.if_set_return(1);
164
165 waiting_for_delim &= is_zero;
166 }
167
168 //invalid, if delimiter 0x01 was not found or msg1_offset is too big
169 bad_input |= waiting_for_delim;
170
171 const auto bad_offset = CT::Mask<size_t>::is_lt(coded.size(), trailer_len + hash_len + msg1_offset + salt_len);
172 bad_input |= CT::Mask<uint8_t>(bad_offset);
173
174 //in case that msg1_offset is too big, just continue with offset = 0.
175 msg1_offset = CT::Mask<size_t>::expand(bad_input.value()).if_not_set_return(msg1_offset);
176
177 CT::unpoison(coded.data(), coded.size());
178 CT::unpoison(msg1_offset);
179
180 const size_t msg1_len = coded.size() - (trailer_len + hash_len + msg1_offset + salt_len);
181
182 const auto msg1 = std::span(coded).subspan(msg1_offset, msg1_len);
183 const auto salt = std::span(coded).subspan(msg1_offset + msg1.size(), salt_len);
184
185 //compute H2(C||msg1||H(msg2)||S*). * indicates a recovered value
186 const size_t capacity = (key_bits - 2 + 7) / 8 - hash_len - salt_len - trailer_len - 1;
187
188 std::span<const uint8_t> msg1raw = raw;
189 if(msg1raw.size() > capacity) {
190 hash->update(msg1raw.subspan(capacity));
191 msg1raw = msg1raw.first(capacity);
192 }
193
194 const auto hmsg2 = hash->final_stdvec();
195
196 // Compute H(C*||msg1*||H(msg2)||S*) where '*' indicates a recovered value
197 const auto H2 = iso9796_hash(*hash, msg1, hmsg2, salt);
198
199 // Check if H == H2
200 bad_input |= CT::is_not_equal(H, H2.data(), hash_len);
201
202 // Check that msg after MGF1 matches msg in the original
203 bad_input |= ~CT::Mask<uint8_t>(CT::Mask<size_t>::is_equal(msg1.size(), msg1raw.size()));
204 bad_input |= ~CT::is_equal(msg1.data(), msg1raw.data(), std::min(msg1.size(), msg1raw.size()));
205
206 CT::unpoison(bad_input);
207 return (bad_input.as_bool() == false);
208}
209
210} // namespace
211
212/*
213 * ISO-9796-2 signature scheme 2
214 * DS 2 is probabilistic
215 */
216void ISO_9796_DS2::update(const uint8_t input[], size_t length) {
217 //need to buffer message completely, before digest
218 m_msg_buffer.insert(m_msg_buffer.end(), input, input + length);
219}
220
221/*
222 * Return the raw (unencoded) data
223 */
224std::vector<uint8_t> ISO_9796_DS2::raw_data() {
225 std::vector<uint8_t> retbuffer = m_msg_buffer;
226 m_msg_buffer.clear();
227 return retbuffer;
228}
229
230/*
231 * ISO-9796-2 scheme 2 encode operation
232 */
233std::vector<uint8_t> ISO_9796_DS2::encoding_of(std::span<const uint8_t> msg,
234 size_t output_bits,
236 return iso9796_encoding(msg, output_bits, m_hash, m_salt_len, m_implicit, rng);
237}
238
239/*
240 * ISO-9796-2 scheme 2 verify operation
241 */
242bool ISO_9796_DS2::verify(std::span<const uint8_t> repr, std::span<const uint8_t> raw, size_t key_bits) {
243 return iso9796_verification(repr, raw, key_bits, m_hash, m_salt_len);
244}
245
246std::string ISO_9796_DS2::hash_function() const {
247 return m_hash->name();
248}
249
250/*
251 * Return the SCAN name
252 */
253std::string ISO_9796_DS2::name() const {
254 return fmt("ISO_9796_DS2({},{},{})", m_hash->name(), (m_implicit ? "imp" : "exp"), m_salt_len);
255}
256
257/*
258 * ISO-9796-2 signature scheme 3
259 * DS 3 is deterministic and equals DS2 without salt
260 */
261void ISO_9796_DS3::update(const uint8_t input[], size_t length) {
262 //need to buffer message completely, before digest
263 m_msg_buffer.insert(m_msg_buffer.end(), input, input + length);
264}
265
266/*
267 * Return the raw (unencoded) data
268 */
269std::vector<uint8_t> ISO_9796_DS3::raw_data() {
270 std::vector<uint8_t> retbuffer = m_msg_buffer;
271 m_msg_buffer.clear();
272 return retbuffer;
273}
274
275/*
276 * ISO-9796-2 scheme 3 encode operation
277 */
278std::vector<uint8_t> ISO_9796_DS3::encoding_of(std::span<const uint8_t> msg,
279 size_t output_bits,
281 return iso9796_encoding(msg, output_bits, m_hash, 0, m_implicit, rng);
282}
283
284/*
285 * ISO-9796-2 scheme 3 verify operation
286 */
287bool ISO_9796_DS3::verify(std::span<const uint8_t> repr, std::span<const uint8_t> raw, size_t key_bits) {
288 return iso9796_verification(repr, raw, key_bits, m_hash, 0);
289}
290
291std::string ISO_9796_DS3::hash_function() const {
292 return m_hash->name();
293}
294
295/*
296 * Return the SCAN name
297 */
298std::string ISO_9796_DS3::name() const {
299 return fmt("ISO_9796_DS3({},{})", m_hash->name(), (m_implicit ? "imp" : "exp"));
300}
301
302} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
Helper class to ease in-place marshalling of concatenated fixed-length values.
Definition stl_util.h:134
static constexpr Mask< T > set()
Definition ct_utils.h:410
static constexpr Mask< T > expand(T v)
Definition ct_utils.h:420
static constexpr Mask< T > is_equal(T x, T y)
Definition ct_utils.h:470
static constexpr Mask< T > is_lt(T x, T y)
Definition ct_utils.h:478
static constexpr Mask< T > is_zero(T x)
Definition ct_utils.h:465
static constexpr Mask< T > cleared()
Definition ct_utils.h:415
std::string name() const override
Definition iso9796.cpp:253
std::string hash_function() const override
Definition iso9796.cpp:246
std::string hash_function() const override
Definition iso9796.cpp:291
std::string name() const override
Definition iso9796.cpp:298
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:866
constexpr void unpoison(const T *p, size_t n)
Definition ct_utils.h:65
constexpr void poison(const T *p, size_t n)
Definition ct_utils.h:54
void mgf1_mask(HashFunction &hash, const uint8_t in[], size_t in_len, uint8_t out[], size_t out_len)
Definition mgf1.cpp:16
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
uint8_t ieee1363_hash_id(std::string_view name)
Definition hash_id.cpp:144