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;...@@ -4,6 +4,7 @@ const assert = std.debug.assert;
4const math = std.math;4const math = std.math;
5const mem = std.mem;5const mem = std.mem;
6const native_endian = builtin.cpu.arch.endian();6const native_endian = builtin.cpu.arch.endian();
7const mode = @import("builtin").mode;
78
8/// The Keccak-f permutation.9/// The Keccak-f permutation.
9pub fn KeccakF(comptime f: u11) type {10pub fn KeccakF(comptime f: u11) type {
...@@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type...@@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
199 comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size200 comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size
200 comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size201 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
202 return struct {243 return struct {
203 const Self = @This();244 const Self = @This();
204245
...@@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type...@@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
215256
216 st: KeccakF(f) = .{},257 st: KeccakF(f) = .{},
217258
259 transition: TransitionTracker = .{},
260
218 /// Absorb a slice of bytes into the sponge.261 /// Absorb a slice of bytes into the sponge.
219 pub fn absorb(self: *Self, bytes_: []const u8) void {262 pub fn absorb(self: *Self, bytes: []const u8) void {
220 var bytes = bytes_;263 self.transition.to(.absorb);
264 var i: usize = 0;
221 if (self.offset > 0) {265 if (self.offset > 0) {
222 const left = @min(rate - self.offset, bytes.len);266 const left = @min(rate - self.offset, bytes.len);
223 @memcpy(self.buf[self.offset..][0..left], bytes[0..left]);267 @memcpy(self.buf[self.offset..][0..left], bytes[0..left]);
224 self.offset += left;268 self.offset += left;
269 if (left == bytes.len) return;
225 if (self.offset == rate) {270 if (self.offset == rate) {
226 self.offset = 0;
227 self.st.addBytes(self.buf[0..]);271 self.st.addBytes(self.buf[0..]);
228 self.st.permuteR(rounds);272 self.st.permuteR(rounds);
273 self.offset = 0;
229 }274 }
230 if (left == bytes.len) return;275 i = left;
231 bytes = bytes[left..];
232 }276 }
233 while (bytes.len >= rate) {277 while (i + rate < bytes.len) : (i += rate) {
234 self.st.addBytes(bytes[0..rate]);278 self.st.addBytes(bytes[i..][0..rate]);
235 self.st.permuteR(rounds);279 self.st.permuteR(rounds);
236 bytes = bytes[rate..];
237 }280 }
238 if (bytes.len > 0) {281 const left = bytes.len - i;
239 @memcpy(self.buf[0..bytes.len], bytes);282 if (left > 0) {
240 self.offset = bytes.len;283 @memcpy(self.buf[0..left], bytes[i..][0..left]);
241 }284 }
285 self.offset = left;
242 }286 }
243287
244 /// Initialize the state from a slice of bytes.288 /// Initialize the state from a slice of bytes.
245 pub fn init(bytes: [f / 8]u8) Self {289 pub fn init(bytes: [f / 8]u8, delim: u8) Self {
246 return .{ .st = KeccakF(f).init(bytes) };290 var st = Self{ .st = KeccakF(f).init(bytes), .delim = delim };
291 st.transition.to(.initialized);
292 return st;
247 }293 }
248294
249 /// Permute the state295 /// Permute the state
250 pub fn permute(self: *Self) void {296 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);
251 self.st.permuteR(rounds);303 self.st.permuteR(rounds);
252 self.offset = 0;304 self.offset = 0;
253 }305 }
254306
255 /// Align the input to the rate boundary.307 /// Align the input to the rate boundary and permute.
256 pub fn fillBlock(self: *Self) void {308 pub fn fillBlock(self: *Self) void {
309 self.transition.to(.absorb);
257 self.st.addBytes(self.buf[0..self.offset]);310 self.st.addBytes(self.buf[0..self.offset]);
258 self.st.permuteR(rounds);311 self.st.permuteR(rounds);
259 self.offset = 0;312 self.offset = 0;
313 self.transition.to(.updated);
260 }314 }
261315
262 /// Mark the end of the input.316 /// Mark the end of the input.
263 pub fn pad(self: *Self) void {317 pub fn pad(self: *Self) void {
318 self.transition.to(.absorb);
264 self.st.addBytes(self.buf[0..self.offset]);319 self.st.addBytes(self.buf[0..self.offset]);
320 if (self.offset == rate) {
321 self.st.permuteR(rounds);
322 self.offset = 0;
323 }
265 self.st.addByte(self.delim, self.offset);324 self.st.addByte(self.delim, self.offset);
266 self.st.addByte(0x80, rate - 1);325 self.st.addByte(0x80, rate - 1);
267 self.st.permuteR(rounds);326 self.st.permuteR(rounds);
268 self.offset = 0;327 self.offset = 0;
328 self.transition.to(.updated);
269 }329 }
270330
271 /// Squeeze a slice of bytes from the sponge.331 /// Squeeze a slice of bytes from the sponge.
332 /// The function can be called multiple times.
272 pub fn squeeze(self: *Self, out: []u8) void {333 pub fn squeeze(self: *Self, out: []u8) void {
334 self.transition.to(.squeeze);
273 var i: usize = 0;335 var i: usize = 0;
274 while (i < out.len) : (i += rate) {336 if (self.offset == rate) {
275 const left = @min(rate, out.len - i);337 self.st.permuteR(rounds);
276 self.st.extractBytes(out[i..][0..left]);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]);
277 self.st.permuteR(rounds);354 self.st.permuteR(rounds);
278 }355 }
356 const left = out.len - i;
357 if (left > 0) {
358 self.st.extractBytes(out[i..][0..left]);
359 }
360 self.offset = left;
279 }361 }
280 };362 };
281}363}
...@@ -298,3 +380,26 @@ test "Keccak-f800" {...@@ -298,3 +380,26 @@ test "Keccak-f800" {
298 };380 };
299 try std.testing.expectEqualSlices(u32, &st.st, &expected);381 try std.testing.expectEqualSlices(u32, &st.st, &expected);
300}382}
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}