authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2024-11-20 11:16:09+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-11-20 11:16:09+01:00
loga5d4ad17b716508c2e1a2c1c0cf0b32bed08e26f
treea602295b1c5f4d79476054e62a23a5d38afebcaa
parentdafe1a910d1b236e7cfba40231f3c730f18f5d1f
signaturebadge-check Signed by PGP key B5690EEEBB952194

crypto.keccak.State: add checks to prevent insecure transitions (#22020)

* crypto.keccak.State: don't unconditionally permute after a squeeze() Now, squeeze() behaves like absorb() Namely, squeeze(x[0..t]); squeeze(x[t..n)); with t <= n becomes equivalent to squeeze(x[0..n]). * keccak: in debug mode, track transitions to prevent insecure ones. Fixes #22019

1 files changed, 122 insertions(+), 17 deletions(-)

lib/std/crypto/keccak_p.zig+122-17
......@@ -4,6 +4,7 @@ const assert = std.debug.assert;
44const math = std.math;
55const mem = std.mem;
66const native_endian = builtin.cpu.arch.endian();
7const mode = @import("builtin").mode;
78
89/// The Keccak-f permutation.
910pub fn KeccakF(comptime f: u11) type {
......@@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
199200 comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size
200201 comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size
201202
203 // In debug mode, track transitions to prevent insecure ones.
204 const Op = enum { uninitialized, initialized, updated, absorb, squeeze };
205 const TransitionTracker = if (mode == .Debug) struct {
206 op: Op = .uninitialized,
207
208 fn to(tracker: *@This(), next_op: Op) void {
209 switch (next_op) {
210 .updated => {
211 switch (tracker.op) {
212 .uninitialized => @panic("cannot permute before initializing"),
213 else => {},
214 }
215 },
216 .absorb => {
217 switch (tracker.op) {
218 .squeeze => @panic("cannot absorb right after squeezing"),
219 else => {},
220 }
221 },
222 .squeeze => {
223 switch (tracker.op) {
224 .uninitialized => @panic("cannot squeeze before initializing"),
225 .initialized => @panic("cannot squeeze right after initializing"),
226 .absorb => @panic("cannot squeeze right after absorbing"),
227 else => {},
228 }
229 },
230 .uninitialized => @panic("cannot transition to uninitialized"),
231 .initialized => {},
232 }
233 tracker.op = next_op;
234 }
235 } else struct {
236 // No-op in non-debug modes.
237 inline fn to(tracker: *@This(), next_op: Op) void {
238 _ = tracker; // no-op
239 _ = next_op; // no-op
240 }
241 };
242
202243 return struct {
203244 const Self = @This();
204245
......@@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
215256
216257 st: KeccakF(f) = .{},
217258
259 transition: TransitionTracker = .{},
260
218261 /// Absorb a slice of bytes into the sponge.
219 pub fn absorb(self: *Self, bytes_: []const u8) void {
220 var bytes = bytes_;
262 pub fn absorb(self: *Self, bytes: []const u8) void {
263 self.transition.to(.absorb);
264 var i: usize = 0;
221265 if (self.offset > 0) {
222266 const left = @min(rate - self.offset, bytes.len);
223267 @memcpy(self.buf[self.offset..][0..left], bytes[0..left]);
224268 self.offset += left;
269 if (left == bytes.len) return;
225270 if (self.offset == rate) {
226 self.offset = 0;
227271 self.st.addBytes(self.buf[0..]);
228272 self.st.permuteR(rounds);
273 self.offset = 0;
229274 }
230 if (left == bytes.len) return;
231 bytes = bytes[left..];
275 i = left;
232276 }
233 while (bytes.len >= rate) {
234 self.st.addBytes(bytes[0..rate]);
277 while (i + rate < bytes.len) : (i += rate) {
278 self.st.addBytes(bytes[i..][0..rate]);
235279 self.st.permuteR(rounds);
236 bytes = bytes[rate..];
237280 }
238 if (bytes.len > 0) {
239 @memcpy(self.buf[0..bytes.len], bytes);
240 self.offset = bytes.len;
281 const left = bytes.len - i;
282 if (left > 0) {
283 @memcpy(self.buf[0..left], bytes[i..][0..left]);
241284 }
285 self.offset = left;
242286 }
243287
244288 /// Initialize the state from a slice of bytes.
245 pub fn init(bytes: [f / 8]u8) Self {
246 return .{ .st = KeccakF(f).init(bytes) };
289 pub fn init(bytes: [f / 8]u8, delim: u8) Self {
290 var st = Self{ .st = KeccakF(f).init(bytes), .delim = delim };
291 st.transition.to(.initialized);
292 return st;
247293 }
248294
249295 /// Permute the state
250296 pub fn permute(self: *Self) void {
297 if (mode == .Debug) {
298 if (self.transition.op == .absorb and self.offset > 0) {
299 @panic("cannot permute with pending input - call fillBlock() or pad() instead");
300 }
301 }
302 self.transition.to(.updated);
251303 self.st.permuteR(rounds);
252304 self.offset = 0;
253305 }
254306
255 /// Align the input to the rate boundary.
307 /// Align the input to the rate boundary and permute.
256308 pub fn fillBlock(self: *Self) void {
309 self.transition.to(.absorb);
257310 self.st.addBytes(self.buf[0..self.offset]);
258311 self.st.permuteR(rounds);
259312 self.offset = 0;
313 self.transition.to(.updated);
260314 }
261315
262316 /// Mark the end of the input.
263317 pub fn pad(self: *Self) void {
318 self.transition.to(.absorb);
264319 self.st.addBytes(self.buf[0..self.offset]);
320 if (self.offset == rate) {
321 self.st.permuteR(rounds);
322 self.offset = 0;
323 }
265324 self.st.addByte(self.delim, self.offset);
266325 self.st.addByte(0x80, rate - 1);
267326 self.st.permuteR(rounds);
268327 self.offset = 0;
328 self.transition.to(.updated);
269329 }
270330
271331 /// Squeeze a slice of bytes from the sponge.
332 /// The function can be called multiple times.
272333 pub fn squeeze(self: *Self, out: []u8) void {
334 self.transition.to(.squeeze);
273335 var i: usize = 0;
274 while (i < out.len) : (i += rate) {
275 const left = @min(rate, out.len - i);
276 self.st.extractBytes(out[i..][0..left]);
336 if (self.offset == rate) {
337 self.st.permuteR(rounds);
338 } else if (self.offset > 0) {
339 @branchHint(.unlikely);
340 var buf: [rate]u8 = undefined;
341 self.st.extractBytes(buf[0..]);
342 const left = @min(rate - self.offset, out.len);
343 @memcpy(out[0..left], buf[self.offset..][0..left]);
344 self.offset += left;
345 if (left == out.len) return;
346 if (self.offset == rate) {
347 self.offset = 0;
348 self.st.permuteR(rounds);
349 }
350 i = left;
351 }
352 while (i + rate < out.len) : (i += rate) {
353 self.st.extractBytes(out[i..][0..rate]);
277354 self.st.permuteR(rounds);
278355 }
356 const left = out.len - i;
357 if (left > 0) {
358 self.st.extractBytes(out[i..][0..left]);
359 }
360 self.offset = left;
279361 }
280362 };
281363}
......@@ -298,3 +380,26 @@ test "Keccak-f800" {
298380 };
299381 try std.testing.expectEqualSlices(u32, &st.st, &expected);
300382}
383
384test "squeeze" {
385 var st = State(800, 256, 22).init([_]u8{0x80} ** 100, 0x01);
386
387 var out0: [15]u8 = undefined;
388 var out1: [out0.len]u8 = undefined;
389 st.permute();
390 var st0 = st;
391 st0.squeeze(out0[0..]);
392 var st1 = st;
393 st1.squeeze(out1[0 .. out1.len / 2]);
394 st1.squeeze(out1[out1.len / 2 ..]);
395 try std.testing.expectEqualSlices(u8, &out0, &out1);
396
397 var out2: [100]u8 = undefined;
398 var out3: [out2.len]u8 = undefined;
399 var st2 = st;
400 st2.squeeze(out2[0..]);
401 var st3 = st;
402 st3.squeeze(out3[0 .. out2.len / 2]);
403 st3.squeeze(out3[out2.len / 2 ..]);
404 try std.testing.expectEqualSlices(u8, &out2, &out3);
405}