lib/reticulum/src/properties/crypto.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const hypothesis = @import("hypothesis");
  3 const reticulum = @import("reticulum");
  4 
  5 const crypto = reticulum.crypto;
  6 const plaintext_max: usize = 500;
  7 const padded_max: usize = 512;
  8 const token_max: usize = 560;
  9 
 10 fn settings(seed: u64) hypothesis.Settings {
 11     return hypothesis.Settings.quick()
 12         .withSeed(seed)
 13         .withDatabase("zig-out/hypothesis-failures/reticulum");
 14 }
 15 
 16 fn drawLength(data: *hypothesis.ConjectureData, maximum: usize) !usize {
 17     return @intCast(try data.drawInteger(0, @intCast(maximum), 0));
 18 }
 19 
 20 fn expectTokenRoundTrip(key: []const u8, iv: [16]u8, plaintext: []const u8) !void {
 21     const token = try crypto.token.Token.init(key);
 22     var encrypted_storage: [token_max]u8 = undefined;
 23     const encrypted = try token.encrypt(iv, plaintext, &encrypted_storage);
 24     try std.testing.expect(token.verify(encrypted));
 25     var decrypted_storage: [padded_max]u8 = undefined;
 26     const decrypted = try token.decrypt(encrypted, &decrypted_storage);
 27     try std.testing.expectEqualSlices(u8, plaintext, decrypted);
 28 }
 29 
 30 const TokenRoundTrip = struct {
 31     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 32         const length = try drawLength(data, plaintext_max);
 33         const plaintext = try data.drawBytes(length, length);
 34         const iv_bytes = try data.drawBytes(16, 16);
 35         const iv: [16]u8 = iv_bytes[0..16].*;
 36         const key_128: [32]u8 = @splat(0x12);
 37         const key_256: [64]u8 = @splat(0x34);
 38         try expectTokenRoundTrip(&key_128, iv, plaintext);
 39         try expectTokenRoundTrip(&key_256, iv, plaintext);
 40     }
 41 };
 42 
 43 const CbcRoundTrip = struct {
 44     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 45         const block_count = try drawLength(data, plaintext_max / crypto.cbc.block_length);
 46         const length = block_count * crypto.cbc.block_length;
 47         const plaintext = try data.drawBytes(length, length);
 48         const key_128: [16]u8 = @splat(0x56);
 49         const key_256: [32]u8 = @splat(0x78);
 50         const iv: [16]u8 = @splat(0x9a);
 51         var ciphertext: [plaintext_max]u8 = undefined;
 52         var output: [plaintext_max]u8 = undefined;
 53         const encrypted_128 = try crypto.cbc.Aes128Cbc.encrypt(
 54             key_128,
 55             iv,
 56             plaintext,
 57             &ciphertext,
 58         );
 59         const decrypted_128 = try crypto.cbc.Aes128Cbc.decrypt(
 60             key_128,
 61             iv,
 62             encrypted_128,
 63             &output,
 64         );
 65         try std.testing.expectEqualSlices(u8, plaintext, decrypted_128);
 66         const encrypted_256 = try crypto.cbc.Aes256Cbc.encrypt(
 67             key_256,
 68             iv,
 69             plaintext,
 70             &ciphertext,
 71         );
 72         const decrypted_256 = try crypto.cbc.Aes256Cbc.decrypt(
 73             key_256,
 74             iv,
 75             encrypted_256,
 76             &output,
 77         );
 78         try std.testing.expectEqualSlices(u8, plaintext, decrypted_256);
 79     }
 80 };
 81 
 82 const PaddingRoundTrip = struct {
 83     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 84         const length = try drawLength(data, plaintext_max);
 85         const plaintext = try data.drawBytes(length, length);
 86         var padded_storage: [padded_max]u8 = undefined;
 87         const padded = try crypto.pkcs7.pad(plaintext, &padded_storage);
 88         const unpadded = try crypto.pkcs7.unpad(padded);
 89         try std.testing.expectEqualSlices(u8, plaintext, unpadded);
 90     }
 91 };
 92 
 93 const TotalDecoders = struct {
 94     pub fn property(data: *hypothesis.ConjectureData, _: std.mem.Allocator) !void {
 95         const length = try drawLength(data, token_max);
 96         const bytes = try data.drawBytes(length, length);
 97         const token_key: [32]u8 = @splat(0xbc);
 98         const token = try crypto.token.Token.init(&token_key);
 99         var output: [token_max]u8 = undefined;
100         if (token.decrypt(bytes, &output)) |_| {} else |_| {}
101         if (crypto.pkcs7.unpad(bytes)) |_| {} else |_| {}
102         const key_128: [16]u8 = @splat(0xde);
103         const key_256: [32]u8 = @splat(0xf0);
104         const iv: [16]u8 = @splat(0x11);
105         if (crypto.cbc.Aes128Cbc.decrypt(key_128, iv, bytes, &output)) |_| {} else |_| {}
106         if (crypto.cbc.Aes256Cbc.decrypt(key_256, iv, bytes, &output)) |_| {} else |_| {}
107     }
108 };
109 
110 test "pbt: token decrypt after encrypt is identity for both AES key sizes" {
111     try hypothesis.checkNamed(TokenRoundTrip, "reticulum-token-round-trip", settings(0x5242_0001));
112 }
113 
114 test "pbt: CBC decrypt after encrypt is identity for both AES key sizes" {
115     try hypothesis.checkNamed(CbcRoundTrip, "reticulum-cbc-round-trip", settings(0x5242_0002));
116 }
117 
118 test "pbt: PKCS#7 unpad after pad is identity" {
119     try hypothesis.checkNamed(
120         PaddingRoundTrip,
121         "reticulum-pkcs7-round-trip",
122         settings(0x5242_0003),
123     );
124 }
125 
126 test "pbt: arbitrary bytes keep token, PKCS#7, and CBC decoders total" {
127     try hypothesis.checkNamed(TotalDecoders, "reticulum-crypto-total", settings(0x5242_0004));
128 }