| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const debug = std.debug; |
| 3 | 3 | const mem = std.mem; |
| 4 | const random = std.crypto.random; |
| 4 | 5 | const testing = std.testing; |
| 5 | 6 | |
| 6 | 7 | const Endian = std.builtin.Endian; |
| ... | ... | @@ -77,6 +78,52 @@ pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: E |
| 77 | 78 | return Order.lt; |
| 78 | 79 | } |
| 79 | 80 | |
| 81 | /// Add two integers serialized as arrays of the same size, in constant time. |
| 82 | /// The result is stored into `result`, and `true` is returned if an overflow occurred. |
| 83 | pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { |
| 84 | const len = a.len; |
| 85 | debug.assert(len == b.len and len == result.len); |
| 86 | var carry: u1 = 0; |
| 87 | if (endian == .Little) { |
| 88 | var i: usize = 0; |
| 89 | while (i < len) : (i += 1) { |
| 90 | const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i])); |
| 91 | carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i])); |
| 92 | } |
| 93 | } else { |
| 94 | var i: usize = len; |
| 95 | while (i != 0) { |
| 96 | i -= 1; |
| 97 | const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i])); |
| 98 | carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i])); |
| 99 | } |
| 100 | } |
| 101 | return @bitCast(bool, carry); |
| 102 | } |
| 103 | |
| 104 | /// Subtract two integers serialized as arrays of the same size, in constant time. |
| 105 | /// The result is stored into `result`, and `true` is returned if an underflow occurred. |
| 106 | pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { |
| 107 | const len = a.len; |
| 108 | debug.assert(len == b.len and len == result.len); |
| 109 | var borrow: u1 = 0; |
| 110 | if (endian == .Little) { |
| 111 | var i: usize = 0; |
| 112 | while (i < len) : (i += 1) { |
| 113 | const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i])); |
| 114 | borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i])); |
| 115 | } |
| 116 | } else { |
| 117 | var i: usize = len; |
| 118 | while (i != 0) { |
| 119 | i -= 1; |
| 120 | const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i])); |
| 121 | borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i])); |
| 122 | } |
| 123 | } |
| 124 | return @bitCast(bool, borrow); |
| 125 | } |
| 126 | |
| 80 | 127 | /// Sets a slice to zeroes. |
| 81 | 128 | /// Prevents the store from being optimized out. |
| 82 | 129 | pub fn secureZero(comptime T: type, s: []T) void { |
| ... | ... | @@ -90,8 +137,8 @@ pub fn secureZero(comptime T: type, s: []T) void { |
| 90 | 137 | test "crypto.utils.timingSafeEql" { |
| 91 | 138 | var a: [100]u8 = undefined; |
| 92 | 139 | var b: [100]u8 = undefined; |
| 93 | | std.crypto.random.bytes(a[0..]); |
| 94 | | std.crypto.random.bytes(b[0..]); |
| 140 | random.bytes(a[0..]); |
| 141 | random.bytes(b[0..]); |
| 95 | 142 | try testing.expect(!timingSafeEql([100]u8, a, b)); |
| 96 | 143 | mem.copy(u8, a[0..], b[0..]); |
| 97 | 144 | try testing.expect(timingSafeEql([100]u8, a, b)); |
| ... | ... | @@ -100,8 +147,8 @@ test "crypto.utils.timingSafeEql" { |
| 100 | 147 | test "crypto.utils.timingSafeEql (vectors)" { |
| 101 | 148 | var a: [100]u8 = undefined; |
| 102 | 149 | var b: [100]u8 = undefined; |
| 103 | | std.crypto.random.bytes(a[0..]); |
| 104 | | std.crypto.random.bytes(b[0..]); |
| 150 | random.bytes(a[0..]); |
| 151 | random.bytes(b[0..]); |
| 105 | 152 | const v1: std.meta.Vector(100, u8) = a; |
| 106 | 153 | const v2: std.meta.Vector(100, u8) = b; |
| 107 | 154 | try testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2)); |
| ... | ... | @@ -122,6 +169,26 @@ test "crypto.utils.timingSafeCompare" { |
| 122 | 169 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); |
| 123 | 170 | } |
| 124 | 171 | |
| 172 | test "crypto.utils.timingSafe{Add,Sub}" { |
| 173 | const len = 32; |
| 174 | var a: [len]u8 = undefined; |
| 175 | var b: [len]u8 = undefined; |
| 176 | var c: [len]u8 = undefined; |
| 177 | const zero = [_]u8{0} ** len; |
| 178 | var iterations: usize = 100; |
| 179 | while (iterations != 0) : (iterations -= 1) { |
| 180 | random.bytes(&a); |
| 181 | random.bytes(&b); |
| 182 | const endian = if (iterations % 2 == 0) Endian.Big else Endian.Little; |
| 183 | _ = timingSafeSub(u8, &a, &b, &c, endian); // a-b |
| 184 | _ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b |
| 185 | try testing.expectEqualSlices(u8, &c, &a); |
| 186 | const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a |
| 187 | try testing.expectEqualSlices(u8, &c, &zero); |
| 188 | try testing.expectEqual(borrow, false); |
| 189 | } |
| 190 | } |
| 191 | |
| 125 | 192 | test "crypto.utils.secureZero" { |
| 126 | 193 | var a = [_]u8{0xfe} ** 8; |
| 127 | 194 | var b = [_]u8{0xfe} ** 8; |