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 }