lib/reticulum/src/crypto/pkcs7.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 
 3 pub const block_length: u8 = 16;
 4 pub const max_data_length: u16 = 65_519;
 5 pub const max_padded_length: u16 = 65_520;
 6 
 7 pub const PadError = error{
 8     InputTooLong,
 9     OutputTooSmall,
10 };
11 
12 pub const UnpadError = error{
13     EmptyInput,
14     InvalidPadding,
15 };
16 
17 pub fn paddedLength(data_length: u17) PadError!u16 {
18     if (data_length > max_data_length) return error.InputTooLong;
19     const remainder: u8 = @intCast(data_length % block_length);
20     const padding: u8 = block_length - remainder;
21     const result = data_length + padding;
22     std.debug.assert(result <= max_padded_length);
23     return @intCast(result);
24 }
25 
26 /// Writes the padded data into `out` and returns it, with between one and
27 /// sixteen copies of the padding count appended, so a caller fills a plaintext
28 /// out to whole blocks before the cipher runs, following Reticulum@1.5.0
29 /// RNS/Cryptography/PKCS7.py:32-39. Data already a whole number of blocks long
30 /// gains a full block of padding, so the count is never zero. The call returns
31 /// `error.InputTooLong` past 65,519 bytes and `error.OutputTooSmall` when `out`
32 /// is shorter than the padded length.
33 pub fn pad(data: []const u8, out: []u8) PadError![]u8 {
34     if (data.len > max_data_length) return error.InputTooLong;
35     const result_length = try paddedLength(@intCast(data.len));
36     const result_length_usize: usize = result_length;
37     if (out.len < result_length_usize) return error.OutputTooSmall;
38     const result = out[0..result_length_usize];
39     @memmove(result[0..data.len], data);
40     const padding: u8 = @intCast(result.len - data.len);
41     @memset(result[data.len..], padding);
42     return result;
43 }
44 
45 /// Returns the data with as many trailing bytes removed as its final byte
46 /// counts, so a caller cuts the padding back off a plaintext the cipher
47 /// produced, following Reticulum@1.5.0 RNS/Cryptography/PKCS7.py:41-48. A final
48 /// byte counting more than sixteen returns `error.InvalidPadding`, and empty
49 /// data returns `error.EmptyInput`. A final byte counting more than the data
50 /// holds is handled the way the reference's Python slicing handles it: that
51 /// slicing leaves twice the length less the count, or nothing at all once the
52 /// count reaches twice the length. A final byte of zero removes nothing.
53 pub fn unpad(data: []const u8) UnpadError![]const u8 {
54     if (data.len == 0) return error.EmptyInput;
55     const padding = data[data.len - 1];
56     if (padding > block_length) return error.InvalidPadding;
57     const stop = pythonSliceStop(data.len, padding);
58     return data[0..stop];
59 }
60 
61 fn pythonSliceStop(length: usize, padding: u8) usize {
62     if (padding <= length) return length - padding;
63     std.debug.assert(length < block_length);
64     const doubled = length * 2;
65     if (padding >= doubled) return 0;
66     return doubled - padding;
67 }