authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2023-05-22 16:11:06+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-05-22 16:11:06+02:00
log89f622fc681d8848e3067d9de76fe3b3a8ea6d07
tree60ed0bbf87635867caa524559759101b17788e3b
parenta1bb9e94d402eb678635fc92fe7f990a69fd144c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.crypto.ff - Alloc-free, constant-time field arithmetic for crypto (#15795)

A minimal set of simple, safe functions for Montgomery arithmetic, designed for cryptographic primitives. Also update the current RSA cert validation to use it, getting rid of the FixedBuffer hack and the previous limitations. Make the check of the RSA public key a little bit more strict by the way.

4 files changed, 948 insertions(+), 147 deletions(-)

lib/std/crypto.zig+4
......@@ -179,6 +179,9 @@ pub const nacl = struct {
179179
180180pub const utils = @import("crypto/utils.zig");
181181
182/// Finite-field arithmetic.
183pub const ff = @import("crypto/ff.zig");
184
182185/// This is a thread-local, cryptographically secure pseudo random number generator.
183186pub const random = @import("crypto/tlcsprng.zig").interface;
184187
......@@ -296,6 +299,7 @@ test {
296299 _ = nacl.SealedBox;
297300
298301 _ = utils;
302 _ = ff;
299303 _ = random;
300304 _ = errors;
301305 _ = tls;
lib/std/crypto/Certificate.zig+34-146
......@@ -749,10 +749,6 @@ fn verifyRsa(
749749 var msg_hashed: [Hash.digest_length]u8 = undefined;
750750 Hash.hash(message, &msg_hashed, .{});
751751
752 var rsa_mem_buf: [512 * 64]u8 = undefined;
753 var fba = std.heap.FixedBufferAllocator.init(&rsa_mem_buf);
754 const ally = fba.allocator();
755
756752 switch (modulus.len) {
757753 inline 128, 256, 512 => |modulus_len| {
758754 const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
......@@ -763,16 +759,9 @@ fn verifyRsa(
763759 hash_der ++
764760 msg_hashed;
765761
766 const public_key = rsa.PublicKey.fromBytes(exponent, modulus, ally) catch |err| switch (err) {
767 error.OutOfMemory => unreachable, // rsa_mem_buf is big enough
768 };
769 const em_dec = rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, ally) catch |err| switch (err) {
770 error.OutOfMemory => unreachable, // rsa_mem_buf is big enough
771
762 const public_key = rsa.PublicKey.fromBytes(exponent, modulus) catch return error.CertificateSignatureInvalid;
763 const em_dec = rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key) catch |err| switch (err) {
772764 error.MessageTooLong => unreachable,
773 error.NegativeIntoUnsigned => @panic("TODO make RSA not emit this error"),
774 error.TargetTooSmall => @panic("TODO make RSA not emit this error"),
775 error.BufferTooSmall => @panic("TODO make RSA not emit this error"),
776765 };
777766
778767 if (!mem.eql(u8, &em, &em_dec)) {
......@@ -915,15 +904,11 @@ test {
915904 _ = Bundle;
916905}
917906
918/// TODO: replace this with Frank's upcoming RSA implementation. the verify
919/// function won't have the possibility of failure - it will either identify a
920/// valid signature or an invalid signature.
921/// This code is borrowed from https://github.com/shiguredo/tls13-zig
922/// which is licensed under the Apache License Version 2.0, January 2004
923/// http://www.apache.org/licenses/
924/// The code has been modified.
925907pub const rsa = struct {
926 const BigInt = std.math.big.int.Managed;
908 const max_modulus_bits = 4096;
909 const Uint = std.crypto.ff.Uint(max_modulus_bits);
910 const Modulus = std.crypto.ff.Modulus(max_modulus_bits);
911 const Fe = Modulus.Fe;
927912
928913 pub const PSSSignature = struct {
929914 pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
......@@ -933,10 +918,10 @@ pub const rsa = struct {
933918 }
934919
935920 pub fn verify(comptime modulus_len: usize, sig: [modulus_len]u8, msg: []const u8, public_key: PublicKey, comptime Hash: type, allocator: std.mem.Allocator) !void {
936 const mod_bits = try countBits(public_key.n.toConst(), allocator);
937 const em_dec = try encrypt(modulus_len, sig, public_key, allocator);
921 const mod_bits = public_key.n.bits();
922 const em_dec = try encrypt(modulus_len, sig, public_key);
938923
939 try EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash, allocator);
924 EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash, allocator) catch unreachable;
940925 }
941926
942927 fn EMSA_PSS_VERIFY(msg: []const u8, em: []const u8, emBit: usize, sLen: usize, comptime Hash: type, allocator: std.mem.Allocator) !void {
......@@ -1070,22 +1055,27 @@ pub const rsa = struct {
10701055 };
10711056
10721057 pub const PublicKey = struct {
1073 n: BigInt,
1074 e: BigInt,
1075
1076 pub fn deinit(self: *PublicKey) void {
1077 self.n.deinit();
1078 self.e.deinit();
1079 }
1080
1081 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey {
1082 var _n = try BigInt.init(allocator);
1083 errdefer _n.deinit();
1084 try setBytes(&_n, modulus_bytes, allocator);
1085
1086 var _e = try BigInt.init(allocator);
1087 errdefer _e.deinit();
1088 try setBytes(&_e, pub_bytes, allocator);
1058 n: Modulus,
1059 e: Fe,
1060
1061 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8) !PublicKey {
1062 // Reject modulus below 512 bits.
1063 // 512-bit RSA was factored in 1999, so this limit barely means anything,
1064 // but establish some limit now to ratchet in what we can.
1065 const _n = Modulus.fromBytes(modulus_bytes, .Big) catch return error.CertificatePublicKeyInvalid;
1066 if (_n.bits() < 512) return error.CertificatePublicKeyInvalid;
1067
1068 // Exponent must be odd and greater than 2.
1069 // Also, it must be less than 2^32 to mitigate DoS attacks.
1070 // Windows CryptoAPI doesn't support values larger than 32 bits [1], so it is
1071 // unlikely that exponents larger than 32 bits are being used for anything
1072 // Windows commonly does.
1073 // [1] https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-rsapubkey
1074 if (pub_bytes.len > 4) return error.CertificatePublicKeyInvalid;
1075 const _e = Fe.fromBytes(_n, pub_bytes, .Big) catch return error.CertificatePublicKeyInvalid;
1076 if (!_e.isOdd()) return error.CertificatePublicKeyInvalid;
1077 const e_v = _e.toPrimitive(u32) catch return error.CertificatePublicKeyInvalid;
1078 if (e_v < 2) return error.CertificatePublicKeyInvalid;
10891079
10901080 return .{
10911081 .n = _n,
......@@ -1112,113 +1102,11 @@ pub const rsa = struct {
11121102 }
11131103 };
11141104
1115 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 {
1116 var m = try BigInt.init(allocator);
1117 defer m.deinit();
1118
1119 try setBytes(&m, &msg, allocator);
1120
1121 if (m.order(public_key.n) != .lt) {
1122 return error.MessageTooLong;
1123 }
1124
1125 var e = try BigInt.init(allocator);
1126 defer e.deinit();
1127
1128 try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator);
1129
1105 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey) ![modulus_len]u8 {
1106 const m = Fe.fromBytes(public_key.n, &msg, .Big) catch return error.MessageTooLong;
1107 const e = public_key.n.powPublic(m, public_key.e) catch unreachable;
11301108 var res: [modulus_len]u8 = undefined;
1131
1132 try toBytes(&res, &e, allocator);
1133
1109 e.toBytes(&res, .Big) catch unreachable;
11341110 return res;
11351111 }
1136
1137 fn setBytes(r: *BigInt, bytes: []const u8, allocator: std.mem.Allocator) !void {
1138 try r.set(0);
1139 var tmp = try BigInt.init(allocator);
1140 defer tmp.deinit();
1141 for (bytes) |b| {
1142 try r.shiftLeft(r, 8);
1143 try tmp.set(b);
1144 try r.add(r, &tmp);
1145 }
1146 }
1147
1148 fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
1149 var bin_raw: [512]u8 = undefined;
1150 try toBytes(&bin_raw, x, allocator);
1151
1152 var i: usize = 0;
1153 while (bin_raw[i] == 0x00) : (i += 1) {}
1154 const bin = bin_raw[i..];
1155
1156 try r.set(1);
1157 var r1 = try BigInt.init(allocator);
1158 defer r1.deinit();
1159 try BigInt.copy(&r1, a.toConst());
1160 i = 0;
1161 while (i < bin.len * 8) : (i += 1) {
1162 if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) {
1163 try BigInt.mul(&r1, r, &r1);
1164 try mod(&r1, &r1, n, allocator);
1165 try BigInt.sqr(r, r);
1166 try mod(r, r, n, allocator);
1167 } else {
1168 try BigInt.mul(r, r, &r1);
1169 try mod(r, r, n, allocator);
1170 try BigInt.sqr(&r1, &r1);
1171 try mod(&r1, &r1, n, allocator);
1172 }
1173 }
1174 }
1175
1176 fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void {
1177 const Error = error{
1178 BufferTooSmall,
1179 };
1180
1181 var mask = try BigInt.initSet(allocator, 0xFF);
1182 defer mask.deinit();
1183 var tmp = try BigInt.init(allocator);
1184 defer tmp.deinit();
1185
1186 var a_copy = try BigInt.init(allocator);
1187 defer a_copy.deinit();
1188 try a_copy.copy(a.toConst());
1189
1190 // Encoding into big-endian bytes
1191 var i: usize = 0;
1192 while (i < out.len) : (i += 1) {
1193 try tmp.bitAnd(&a_copy, &mask);
1194 const b = try tmp.to(u8);
1195 out[out.len - i - 1] = b;
1196 try a_copy.shiftRight(&a_copy, 8);
1197 }
1198
1199 if (!a_copy.eqZero()) {
1200 return Error.BufferTooSmall;
1201 }
1202 }
1203
1204 fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
1205 var q = try BigInt.init(allocator);
1206 defer q.deinit();
1207
1208 try BigInt.divFloor(&q, rem, a, n);
1209 }
1210
1211 fn countBits(a: std.math.big.int.Const, allocator: std.mem.Allocator) !usize {
1212 var i: usize = 0;
1213 var a_copy = try BigInt.init(allocator);
1214 defer a_copy.deinit();
1215 try a_copy.copy(a);
1216
1217 while (!a_copy.eqZero()) {
1218 try a_copy.shiftRight(&a_copy, 1);
1219 i += 1;
1220 }
1221
1222 return i;
1223 }
12241112};
lib/std/crypto/ff.zig created+909
......@@ -0,0 +1,909 @@
1//! Allocation-free, (best-effort) constant-time, finite field arithmetic for large integers.
2//!
3//! Unlike `std.math.big`, these integers have a fixed maximum length and are only designed to be used for modular arithmetic.
4//! Arithmetic operations are meant to run in constant-time for a given modulus, making them suitable for cryptography.
5//!
6//! Parts of that code was ported from the BSD-licensed crypto/internal/bigmod/nat.go file in the Go language, itself inspired from BearSSL.
7
8const std = @import("std");
9const builtin = std.builtin;
10const crypto = std.crypto;
11const math = std.math;
12const mem = std.mem;
13const meta = std.meta;
14const testing = std.testing;
15const BoundedArray = std.BoundedArray;
16const assert = std.debug.assert;
17
18// A Limb is a single digit in a big integer.
19const Limb = usize;
20
21// The number of reserved bits in a Limb.
22const carry_bits = 1;
23
24// The number of active bits in a Limb.
25const t_bits: usize = @bitSizeOf(Limb) - carry_bits;
26
27// A TLimb is a Limb that is truncated to t_bits.
28const TLimb = meta.Int(.unsigned, t_bits);
29
30const native_endian = @import("builtin").target.cpu.arch.endian();
31
32// A WideLimb is a Limb that is twice as wide as a normal Limb.
33const WideLimb = struct {
34 hi: Limb,
35 lo: Limb,
36};
37
38/// Value is too large for the destination.
39pub const OverflowError = error{Overflow};
40
41/// Invalid modulus. Modulus must be odd.
42pub const InvalidModulusError = error{ EvenModulus, ModulusTooSmall };
43
44/// Exponentation with a null exponent.
45/// Exponentiation in cryptographic protocols is almost always a sign of a bug which can lead to trivial attacks.
46/// Therefore, this module returns an error when a null exponent is encountered, encouraging applications to handle this case explicitly.
47pub const NullExponentError = error{NullExponent};
48
49/// Invalid field element for the given modulus.
50pub const FieldElementError = error{NonCanonical};
51
52/// Invalid representation (Montgomery vs non-Montgomery domain.)
53pub const RepresentationError = error{UnexpectedRepresentation};
54
55/// The set of all possible errors `std.crypto.ff` functions can return.
56pub const Error = OverflowError || InvalidModulusError || NullExponentError || FieldElementError || RepresentationError;
57
58/// An unsigned big integer with a fixed maximum size (`max_bits`), suitable for cryptographic operations.
59/// Unless side-channels mitigations are explicitly disabled, operations are designed to be constant-time.
60pub fn Uint(comptime max_bits: comptime_int) type {
61 comptime assert(@bitSizeOf(Limb) % 8 == 0); // Limb size must be a multiple of 8
62
63 return struct {
64 const Self = @This();
65
66 const max_limbs_count = math.divCeil(usize, max_bits, t_bits) catch unreachable;
67 const Limbs = BoundedArray(Limb, max_limbs_count);
68 limbs: Limbs,
69
70 /// Number of bytes required to serialize an integer.
71 pub const encoded_bytes = math.divCeil(usize, max_bits, 8) catch unreachable;
72
73 // Returns the number of active limbs.
74 fn limbs_count(self: Self) usize {
75 return self.limbs.len;
76 }
77
78 // Removes limbs whose value is zero from the active limbs.
79 fn normalize(self: Self) Self {
80 var res = self;
81 if (self.limbs_count() < 2) {
82 return res;
83 }
84 var i = self.limbs_count() - 1;
85 while (i > 0 and res.limbs.get(i) == 0) : (i -= 1) {}
86 res.limbs.resize(i + 1) catch unreachable;
87 return res;
88 }
89
90 /// The zero integer.
91 pub const zero = zero: {
92 var limbs = Limbs.init(0) catch unreachable;
93 limbs.appendNTimesAssumeCapacity(0, max_limbs_count);
94 break :zero Self{ .limbs = limbs };
95 };
96
97 /// Creates a new big integer from a primitive type.
98 /// This function may not run in constant time.
99 pub fn fromPrimitive(comptime T: type, x_: T) OverflowError!Self {
100 var x = x_;
101 var out = Self.zero;
102 for (0..out.limbs.capacity()) |i| {
103 const t = if (@bitSizeOf(T) > t_bits) @truncate(TLimb, x) else x;
104 out.limbs.set(i, t);
105 x = math.shr(T, x, t_bits);
106 }
107 if (x != 0) {
108 return error.Overflow;
109 }
110 return out;
111 }
112
113 /// Converts a big integer to a primitive type.
114 /// This function may not run in constant time.
115 pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T {
116 var x: T = 0;
117 var i = self.limbs_count() - 1;
118 while (true) : (i -= 1) {
119 if (@bitSizeOf(T) >= t_bits and math.shr(T, x, @bitSizeOf(T) - t_bits) != 0) {
120 return error.Overflow;
121 }
122 x = math.shl(T, x, t_bits);
123 const v = math.cast(T, self.limbs.get(i)) orelse return error.Overflow;
124 x |= v;
125 if (i == 0) break;
126 }
127 return x;
128 }
129
130 /// Encodes a big integer into a byte array.
131 pub fn toBytes(self: Self, bytes: []u8, comptime endian: builtin.Endian) OverflowError!void {
132 if (bytes.len == 0) {
133 if (self.isZero()) return;
134 return error.Overflow;
135 }
136 @memset(bytes, 0);
137 var shift: usize = 0;
138 var out_i: usize = switch (endian) {
139 .Big => bytes.len - 1,
140 .Little => 0,
141 };
142 for (0..self.limbs.len) |i| {
143 var remaining_bits = t_bits;
144 var limb = self.limbs.get(i);
145 while (remaining_bits >= 8) {
146 bytes[out_i] |= math.shl(u8, @truncate(u8, limb), shift);
147 const consumed = 8 - shift;
148 limb >>= @truncate(u4, consumed);
149 remaining_bits -= consumed;
150 shift = 0;
151 switch (endian) {
152 .Big => {
153 if (out_i == 0) {
154 if (i != self.limbs.len - 1 or limb != 0) {
155 return error.Overflow;
156 }
157 return;
158 }
159 out_i -= 1;
160 },
161 .Little => {
162 out_i += 1;
163 if (out_i == bytes.len) {
164 if (i != self.limbs.len - 1 or limb != 0) {
165 return error.Overflow;
166 }
167 return;
168 }
169 },
170 }
171 }
172 bytes[out_i] |= @truncate(u8, limb);
173 shift = remaining_bits;
174 }
175 }
176
177 /// Creates a new big integer from a byte array.
178 pub fn fromBytes(bytes: []const u8, comptime endian: builtin.Endian) OverflowError!Self {
179 if (bytes.len == 0) return Self.zero;
180 var shift: usize = 0;
181 var out = Self.zero;
182 var out_i: usize = 0;
183 var i: usize = switch (endian) {
184 .Big => bytes.len - 1,
185 .Little => 0,
186 };
187 while (true) {
188 const bi = bytes[i];
189 out.limbs.set(out_i, out.limbs.get(out_i) | math.shl(Limb, bi, shift));
190 shift += 8;
191 if (shift >= t_bits) {
192 shift -= t_bits;
193 out.limbs.set(out_i, @truncate(TLimb, out.limbs.get(out_i)));
194 const overflow = math.shr(Limb, bi, 8 - shift);
195 out_i += 1;
196 if (out_i >= out.limbs.len) {
197 if (overflow != 0 or i != 0) {
198 return error.Overflow;
199 }
200 break;
201 }
202 out.limbs.set(out_i, overflow);
203 }
204 switch (endian) {
205 .Big => {
206 if (i == 0) break;
207 i -= 1;
208 },
209 .Little => {
210 i += 1;
211 if (i == bytes.len) break;
212 },
213 }
214 }
215 return out;
216 }
217
218 /// Returns `true` if both integers are equal.
219 pub fn eql(x: Self, y: Self) bool {
220 return crypto.utils.timingSafeEql([max_limbs_count]Limb, x.limbs.buffer, y.limbs.buffer);
221 }
222
223 /// Compares two integers.
224 pub fn compare(x: Self, y: Self) math.Order {
225 return crypto.utils.timingSafeCompare(
226 Limb,
227 x.limbs.constSlice(),
228 y.limbs.constSlice(),
229 .Little,
230 );
231 }
232
233 /// Returns `true` if the integer is zero.
234 pub fn isZero(x: Self) bool {
235 const x_limbs = x.limbs.constSlice();
236 var t: Limb = 0;
237 for (0..x.limbs_count()) |i| {
238 t |= x_limbs[i];
239 }
240 return ct.eql(t, 0);
241 }
242
243 /// Returns `true` if the integer is odd.
244 pub fn isOdd(x: Self) bool {
245 return @bitCast(bool, @truncate(u1, x.limbs.get(0)));
246 }
247
248 /// Adds `y` to `x`, and returns `true` if the operation overflowed.
249 pub fn addWithOverflow(x: *Self, y: Self) u1 {
250 return x.conditionalAddWithOverflow(true, y);
251 }
252
253 /// Subtracts `y` from `x`, and returns `true` if the operation overflowed.
254 pub fn subWithOverflow(x: *Self, y: Self) u1 {
255 return x.conditionalSubWithOverflow(true, y);
256 }
257
258 // Replaces the limbs of `x` with the limbs of `y` if `on` is `true`.
259 fn cmov(x: *Self, on: bool, y: Self) void {
260 const x_limbs = x.limbs.slice();
261 const y_limbs = y.limbs.constSlice();
262 for (0..y.limbs_count()) |i| {
263 x_limbs[i] = ct.select(on, y_limbs[i], x_limbs[i]);
264 }
265 }
266
267 // Adds `y` to `x` if `on` is `true`, and returns `true` if the operation overflowed.
268 fn conditionalAddWithOverflow(x: *Self, on: bool, y: Self) u1 {
269 assert(x.limbs_count() == y.limbs_count()); // Operands must have the same size.
270 const x_limbs = x.limbs.slice();
271 const y_limbs = y.limbs.constSlice();
272
273 var carry: u1 = 0;
274 for (0..x.limbs_count()) |i| {
275 const res = x_limbs[i] + y_limbs[i] + carry;
276 x_limbs[i] = ct.select(on, @truncate(TLimb, res), x_limbs[i]);
277 carry = @truncate(u1, res >> t_bits);
278 }
279 return carry;
280 }
281
282 // Subtracts `y` from `x` if `on` is `true`, and returns `true` if the operation overflowed.
283 fn conditionalSubWithOverflow(x: *Self, on: bool, y: Self) u1 {
284 assert(x.limbs_count() == y.limbs_count()); // Operands must have the same size.
285 const x_limbs = x.limbs.slice();
286 const y_limbs = y.limbs.constSlice();
287
288 var borrow: u1 = 0;
289 for (0..x.limbs_count()) |i| {
290 const res = x_limbs[i] -% y_limbs[i] -% borrow;
291 x_limbs[i] = ct.select(on, @truncate(TLimb, res), x_limbs[i]);
292 borrow = @truncate(u1, res >> t_bits);
293 }
294 return borrow;
295 }
296 };
297}
298
299/// A field element.
300fn Fe_(comptime bits: comptime_int) type {
301 return struct {
302 const Self = @This();
303
304 const FeUint = Uint(bits);
305
306 /// The element value as a `Uint`.
307 v: FeUint,
308
309 /// `true` is the element is in Montgomery form.
310 montgomery: bool = false,
311
312 /// The maximum number of bytes required to encode a field element.
313 pub const encoded_bytes = FeUint.encoded_bytes;
314
315 // The number of active limbs to represent the field element.
316 fn limbs_count(self: Self) usize {
317 return self.v.limbs_count();
318 }
319
320 /// Creates a field element from a primitive.
321 /// This function may not run in constant time.
322 pub fn fromPrimitive(comptime T: type, m: Modulus(bits), x: T) (OverflowError || FieldElementError)!Self {
323 comptime assert(@bitSizeOf(T) <= bits); // Primitive type is larger than the modulus type.
324 const v = try FeUint.fromPrimitive(T, x);
325 var fe = Self{ .v = v };
326 try m.shrink(&fe);
327 try m.rejectNonCanonical(fe);
328 return fe;
329 }
330
331 /// Converts the field element to a primitive.
332 /// This function may not run in constant time.
333 pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T {
334 return self.v.toPrimitive(T);
335 }
336
337 /// Creates a field element from a byte string.
338 pub fn fromBytes(m: Modulus(bits), bytes: []const u8, comptime endian: builtin.Endian) (OverflowError || FieldElementError)!Self {
339 const v = try FeUint.fromBytes(bytes, endian);
340 var fe = Self{ .v = v };
341 try m.shrink(&fe);
342 try m.rejectNonCanonical(fe);
343 return fe;
344 }
345
346 /// Converts the field element to a byte string.
347 pub fn toBytes(self: Self, bytes: []u8, comptime endian: builtin.Endian) OverflowError!void {
348 return self.v.toBytes(bytes, endian);
349 }
350
351 /// Returns `true` if the field elements are equal, in constant time.
352 pub fn eql(x: Self, y: Self) bool {
353 return x.v.eql(y.v);
354 }
355
356 /// Compares two field elements in constant time.
357 pub fn compare(x: Self, y: Self) math.Order {
358 return x.v.compare(y.v);
359 }
360
361 /// Returns `true` if the element is zero.
362 pub fn isZero(self: Self) bool {
363 return self.v.isZero();
364 }
365
366 /// Returns `true` is the element is odd.
367 pub fn isOdd(self: Self) bool {
368 return self.v.isOdd();
369 }
370 };
371}
372
373/// A modulus, defining a finite field.
374/// All operations within the field are performed modulo this modulus, without heap allocations.
375/// `max_bits` represents the number of bits in the maximum value the modulus can be set to.
376pub fn Modulus(comptime max_bits: comptime_int) type {
377 return struct {
378 const Self = @This();
379
380 /// A field element, representing a value within the field defined by this modulus.
381 pub const Fe = Fe_(max_bits);
382
383 const FeUint = Fe.FeUint;
384
385 /// The neutral element.
386 zero: Fe,
387
388 /// The modulus value.
389 v: FeUint,
390
391 /// R^2 for the Montgomery representation.
392 rr: Fe,
393 /// Inverse of the first limb
394 m0inv: Limb,
395 /// Number of leading zero bits in the modulus.
396 leading: usize,
397
398 // Number of active limbs in the modulus.
399 fn limbs_count(self: Self) usize {
400 return self.v.limbs_count();
401 }
402
403 /// Actual size of the modulus, in bits.
404 pub fn bits(self: Self) usize {
405 return self.limbs_count() * t_bits - self.leading;
406 }
407
408 /// Returns the element `1`.
409 pub fn one(self: Self) Fe {
410 var fe = self.zero;
411 fe.v.limbs.set(0, 1);
412 return fe;
413 }
414
415 /// Creates a new modulus from a `Uint` value.
416 /// The modulus must be odd and larger than 2.
417 pub fn fromUint(v_: FeUint) InvalidModulusError!Self {
418 if (!v_.isOdd()) return error.EvenModulus;
419
420 var v = v_.normalize();
421 const hi = v.limbs.get(v.limbs_count() - 1);
422 const lo = v.limbs.get(0);
423
424 if (v.limbs_count() < 2 and lo < 3) {
425 return error.ModulusTooSmall;
426 }
427
428 const leading = @clz(hi) - carry_bits;
429
430 var y = lo;
431
432 inline for (0..comptime math.log2_int(usize, t_bits)) |_| {
433 y = y *% (2 -% lo *% y);
434 }
435 const m0inv = (@as(Limb, 1) << t_bits) - (@truncate(TLimb, y));
436
437 const zero = Fe{ .v = FeUint.zero };
438
439 var m = Self{
440 .zero = zero,
441 .v = v,
442 .leading = leading,
443 .m0inv = m0inv,
444 .rr = undefined, // will be computed right after
445 };
446 m.shrink(&m.zero) catch unreachable;
447 computeRR(&m);
448
449 return m;
450 }
451
452 /// Creates a new modulus from a primitive value.
453 /// The modulus must be odd and larger than 2.
454 pub fn fromPrimitive(comptime T: type, x: T) (InvalidModulusError || OverflowError)!Self {
455 comptime assert(@bitSizeOf(T) <= max_bits); // Primitive type is larger than the modulus type.
456 const v = try FeUint.fromPrimitive(T, x);
457 return try Self.fromUint(v);
458 }
459
460 /// Creates a new modulus from a byte string.
461 pub fn fromBytes(bytes: []const u8, comptime endian: builtin.Endian) (InvalidModulusError || OverflowError)!Self {
462 const v = try FeUint.fromBytes(bytes, endian);
463 return try Self.fromUint(v);
464 }
465
466 /// Serializes the modulus to a byte string.
467 pub fn toBytes(self: Self, bytes: []u8, comptime endian: builtin.Endian) OverflowError!void {
468 return self.v.toBytes(bytes, endian);
469 }
470
471 /// Rejects field elements that are not in the canonical form.
472 pub fn rejectNonCanonical(self: Self, fe: Fe) error{NonCanonical}!void {
473 if (fe.limbs_count() != self.limbs_count() or ct.limbsCmpGeq(fe.v, self.v)) {
474 return error.NonCanonical;
475 }
476 }
477
478 // Makes the number of active limbs in a field element match the one of the modulus.
479 fn shrink(self: Self, fe: *Fe) OverflowError!void {
480 const new_len = self.limbs_count();
481 if (fe.limbs_count() < new_len) return error.Overflow;
482 var acc: Limb = 0;
483 for (fe.v.limbs.constSlice()[new_len..]) |limb| {
484 acc |= limb;
485 }
486 if (acc != 0) return error.Overflow;
487 try fe.v.limbs.resize(new_len);
488 }
489
490 // Computes R^2 for the Montgomery representation.
491 fn computeRR(self: *Self) void {
492 self.rr = self.zero;
493 const n = self.rr.limbs_count();
494 self.rr.v.limbs.set(n - 1, 1);
495 for ((n - 1)..(2 * n)) |_| {
496 self.shiftIn(&self.rr, 0);
497 }
498 self.shrink(&self.rr) catch unreachable;
499 }
500
501 /// Computes x << t_bits + y (mod m)
502 fn shiftIn(self: Self, x: *Fe, y: Limb) void {
503 var d = self.zero;
504 const x_limbs = x.v.limbs.slice();
505 const d_limbs = d.v.limbs.slice();
506 const m_limbs = self.v.limbs.constSlice();
507
508 var need_sub = false;
509 var i: usize = t_bits - 1;
510 while (true) : (i -= 1) {
511 var carry = @truncate(u1, math.shr(Limb, y, i));
512 var borrow: u1 = 0;
513 for (0..self.limbs_count()) |j| {
514 const l = ct.select(need_sub, d_limbs[j], x_limbs[j]);
515 var res = (l << 1) + carry;
516 x_limbs[j] = @truncate(TLimb, res);
517 carry = @truncate(u1, res >> t_bits);
518
519 res = x_limbs[j] -% m_limbs[j] -% borrow;
520 d_limbs[j] = @truncate(TLimb, res);
521
522 borrow = @truncate(u1, res >> t_bits);
523 }
524 need_sub = ct.eql(carry, borrow);
525 if (i == 0) break;
526 }
527 x.v.cmov(need_sub, d.v);
528 }
529
530 /// Adds two field elements (mod m).
531 pub fn add(self: Self, x: Fe, y: Fe) Fe {
532 var out = x;
533 const overflow = out.v.addWithOverflow(y.v);
534 const underflow = @bitCast(u1, ct.limbsCmpLt(out.v, self.v));
535 const need_sub = ct.eql(overflow, underflow);
536 _ = out.v.conditionalSubWithOverflow(need_sub, self.v);
537 return out;
538 }
539
540 /// Subtracts two field elements (mod m).
541 pub fn sub(self: Self, x: Fe, y: Fe) Fe {
542 var out = x;
543 const underflow = @bitCast(bool, out.v.subWithOverflow(y.v));
544 _ = out.v.conditionalAddWithOverflow(underflow, self.v);
545 return out;
546 }
547
548 /// Converts a field element to the Montgomery form.
549 pub fn toMontgomery(self: Self, x: *Fe) RepresentationError!void {
550 if (x.montgomery) {
551 return error.UnexpectedRepresentation;
552 }
553 self.shrink(x) catch unreachable;
554 x.* = self.montgomeryMul(x.*, self.rr);
555 x.montgomery = true;
556 }
557
558 /// Takes a field element out of the Montgomery form.
559 pub fn fromMontgomery(self: Self, x: *Fe) RepresentationError!void {
560 if (!x.montgomery) {
561 return error.UnexpectedRepresentation;
562 }
563 self.shrink(x) catch unreachable;
564 x.* = self.montgomeryMul(x.*, self.one());
565 x.montgomery = false;
566 }
567
568 /// Reduces an arbitrary `Uint`, converting it to a field element.
569 pub fn reduce(self: Self, x: anytype) Fe {
570 var out = self.zero;
571 var i = x.limbs_count() - 1;
572 if (self.limbs_count() >= 2) {
573 const start = math.min(i, self.limbs_count() - 2);
574 var j = start;
575 while (true) : (j -= 1) {
576 out.v.limbs.set(j, x.limbs.get(i));
577 i -= 1;
578 if (j == 0) break;
579 }
580 }
581 while (true) : (i -= 1) {
582 self.shiftIn(&out, x.limbs.get(i));
583 if (i == 0) break;
584 }
585 return out;
586 }
587
588 fn montgomeryLoop(self: Self, d: *Fe, x: Fe, y: Fe) u1 {
589 assert(d.limbs_count() == x.limbs_count());
590 assert(d.limbs_count() == y.limbs_count());
591 assert(d.limbs_count() == self.limbs_count());
592
593 const a_limbs = x.v.limbs.constSlice();
594 const b_limbs = y.v.limbs.constSlice();
595 const d_limbs = d.v.limbs.slice();
596 const m_limbs = self.v.limbs.constSlice();
597
598 var overflow: u1 = 0;
599 for (0..self.limbs_count()) |i| {
600 var carry: Limb = 0;
601
602 var wide = ct.mulWide(a_limbs[i], b_limbs[0]);
603 var z_lo = @addWithOverflow(d_limbs[0], wide.lo);
604 const f = @truncate(TLimb, z_lo[0] *% self.m0inv);
605 var z_hi = wide.hi +% z_lo[1];
606 wide = ct.mulWide(f, m_limbs[0]);
607 z_lo = @addWithOverflow(z_lo[0], wide.lo);
608 z_hi +%= z_lo[1];
609 z_hi +%= wide.hi;
610 carry = (z_hi << 1) | (z_lo[0] >> t_bits);
611
612 for (1..self.limbs_count()) |j| {
613 wide = ct.mulWide(a_limbs[i], b_limbs[j]);
614 z_lo = @addWithOverflow(d_limbs[j], wide.lo);
615 z_hi = wide.hi +% z_lo[1];
616 wide = ct.mulWide(f, m_limbs[j]);
617 z_lo = @addWithOverflow(z_lo[0], wide.lo);
618 z_hi +%= z_lo[1];
619 z_hi +%= wide.hi;
620 z_lo = @addWithOverflow(z_lo[0], carry);
621 z_hi +%= z_lo[1];
622 if (j > 0) {
623 d_limbs[j - 1] = @truncate(TLimb, z_lo[0]);
624 }
625 carry = (z_hi << 1) | (z_lo[0] >> t_bits);
626 }
627 const z = overflow + carry;
628 d_limbs[self.limbs_count() - 1] = @truncate(TLimb, z);
629 overflow = @truncate(u1, z >> t_bits);
630 }
631 return overflow;
632 }
633
634 // Montgomery multiplication.
635 fn montgomeryMul(self: Self, x: Fe, y: Fe) Fe {
636 var d = self.zero;
637 assert(x.limbs_count() == self.limbs_count());
638 assert(y.limbs_count() == self.limbs_count());
639 const overflow = self.montgomeryLoop(&d, x, y);
640 const underflow = 1 -% @boolToInt(ct.limbsCmpGeq(d.v, self.v));
641 const need_sub = ct.eql(overflow, underflow);
642 _ = d.v.conditionalSubWithOverflow(need_sub, self.v);
643 d.montgomery = x.montgomery == y.montgomery;
644 return d;
645 }
646
647 // Montgomery squaring.
648 fn montgomerySq(self: Self, x: Fe) Fe {
649 var d = self.zero;
650 assert(x.limbs_count() == self.limbs_count());
651 const overflow = self.montgomeryLoop(&d, x, x);
652 const underflow = 1 -% @boolToInt(ct.limbsCmpGeq(d.v, self.v));
653 const need_sub = ct.eql(overflow, underflow);
654 _ = d.v.conditionalSubWithOverflow(need_sub, self.v);
655 d.montgomery = true;
656 return d;
657 }
658
659 /// Multiplies two field elements.
660 pub fn mul(self: Self, x: Fe, y: Fe) Fe {
661 if (x.montgomery != y.montgomery) {
662 return self.montgomeryMul(x, y);
663 }
664 var a_ = x;
665 if (x.montgomery == false) {
666 self.toMontgomery(&a_) catch unreachable;
667 } else {
668 self.fromMontgomery(&a_) catch unreachable;
669 }
670 return self.montgomeryMul(a_, y);
671 }
672
673 /// Squares a field element.
674 pub fn sq(self: Self, x: Fe) Fe {
675 var out = x;
676 if (x.montgomery == true) {
677 self.fromMontgomery(&out) catch unreachable;
678 }
679 out = self.montgomerySq(out);
680 out.montgomery = false;
681 self.toMontgomery(&out) catch unreachable;
682 return out;
683 }
684
685 /// Returns x^e (mod m) in constant time.
686 pub fn pow(self: Self, x: Fe, e: Fe) NullExponentError!Fe {
687 var buf: [Fe.encoded_bytes]u8 = undefined;
688 e.toBytes(&buf, native_endian) catch unreachable;
689 return self.powWithEncodedExponent(x, &buf, native_endian);
690 }
691
692 /// Returns x^e (mod m), assuming that the exponent is public.
693 /// The function remains constant time with respect to `x`.
694 pub fn powPublic(self: Self, x: Fe, e: Fe) NullExponentError!Fe {
695 var e_normalized = Fe{ .v = e.v.normalize() };
696 var buf_: [Fe.encoded_bytes]u8 = undefined;
697 var buf = buf_[0 .. math.divCeil(usize, e_normalized.v.limbs_count() * t_bits, 8) catch unreachable];
698 e_normalized.toBytes(buf, .Little) catch unreachable;
699 const leading = @clz(e_normalized.v.limbs.get(e_normalized.v.limbs_count() - carry_bits));
700 buf = buf[0 .. buf.len - leading / 8];
701 return self.powWithEncodedExponent(x, buf, .Little);
702 }
703
704 /// Returns x^e (mod m), assuming that the exponent is public, and provided as a byte string.
705 /// Exponents are usually small, so this function is faster than `powPublic` as a field element
706 /// doesn't have to be created if a serialized representation is already available.
707 pub fn powWithEncodedExponent(self: Self, x: Fe, e: []const u8, endian: builtin.Endian) NullExponentError!Fe {
708 var acc: u8 = 0;
709 for (e) |b| acc |= b;
710 if (acc == 0) return error.NullExponent;
711
712 var pc = [1]Fe{x} ++ [_]Fe{self.zero} ** 14;
713 if (x.montgomery == false) {
714 self.toMontgomery(&pc[0]) catch unreachable;
715 }
716 for (1..pc.len) |i| {
717 pc[i] = self.montgomeryMul(pc[i - 1], pc[0]);
718 }
719 var out = self.one();
720 self.toMontgomery(&out) catch unreachable;
721 var t0 = self.zero;
722 var s = switch (endian) {
723 .Big => 0,
724 .Little => e.len - 1,
725 };
726 while (true) {
727 const b = e[s];
728 for ([_]u3{ 4, 0 }) |j| {
729 for (0..4) |_| {
730 out = self.montgomerySq(out);
731 }
732 const k = (b >> j) & 0b1111;
733 if (std.options.side_channels_mitigations == .none) {
734 if (k == 0) continue;
735 t0 = pc[k - 1];
736 } else {
737 for (pc, 0..) |t, i| {
738 t0.v.cmov(ct.eql(k, @truncate(u8, i + 1)), t.v);
739 }
740 }
741 const t1 = self.montgomeryMul(out, t0);
742 out.v.cmov(!ct.eql(k, 0), t1.v);
743 }
744 switch (endian) {
745 .Big => {
746 s += 1;
747 if (s == e.len) break;
748 },
749 .Little => {
750 if (s == 0) break;
751 s -= 1;
752 },
753 }
754 }
755 self.fromMontgomery(&out) catch unreachable;
756 return out;
757 }
758 };
759}
760
761const ct = if (std.options.side_channels_mitigations == .none) ct_unprotected else ct_protected;
762
763const ct_protected = struct {
764 // Returns x if on is true, otherwise y.
765 fn select(on: bool, x: Limb, y: Limb) Limb {
766 const mask = @as(Limb, 0) -% @boolToInt(on);
767 return y ^ (mask & (y ^ x));
768 }
769
770 // Compares two values in constant time.
771 fn eql(x: anytype, y: @TypeOf(x)) bool {
772 const c1 = @subWithOverflow(x, y)[1];
773 const c2 = @subWithOverflow(y, x)[1];
774 return @bitCast(bool, 1 - (c1 | c2));
775 }
776
777 // Compares two big integers in constant time, returning true if x < y.
778 fn limbsCmpLt(x: anytype, y: @TypeOf(x)) bool {
779 assert(x.limbs_count() == y.limbs_count());
780 const x_limbs = x.limbs.constSlice();
781 const y_limbs = y.limbs.constSlice();
782
783 var c: u1 = 0;
784 for (0..x.limbs_count()) |i| {
785 c = @truncate(u1, (x_limbs[i] -% y_limbs[i] -% c) >> t_bits);
786 }
787 return @bitCast(bool, c);
788 }
789
790 // Compares two big integers in constant time, returning true if x >= y.
791 fn limbsCmpGeq(x: anytype, y: @TypeOf(x)) bool {
792 return @bitCast(bool, 1 - @boolToInt(ct.limbsCmpLt(x, y)));
793 }
794
795 // Multiplies two limbs and returns the result as a wide limb.
796 fn mulWide(x: Limb, y: Limb) WideLimb {
797 const half_bits = @typeInfo(Limb).Int.bits / 2;
798 const Half = meta.Int(.unsigned, half_bits);
799 const x0 = @truncate(Half, x);
800 const x1 = @truncate(Half, x >> half_bits);
801 const y0 = @truncate(Half, y);
802 const y1 = @truncate(Half, y >> half_bits);
803 const w0 = math.mulWide(Half, x0, y0);
804 const t = math.mulWide(Half, x1, y0) + (w0 >> half_bits);
805 var w1: Limb = @truncate(Half, t);
806 const w2 = @truncate(Half, t >> half_bits);
807 w1 += math.mulWide(Half, x0, y1);
808 const hi = math.mulWide(Half, x1, y1) + w2 + (w1 >> half_bits);
809 const lo = x *% y;
810 return .{ .hi = hi, .lo = lo };
811 }
812};
813
814const ct_unprotected = struct {
815 // Returns x if on is true, otherwise y.
816 fn select(on: bool, x: Limb, y: Limb) Limb {
817 return if (on) x else y;
818 }
819
820 // Compares two values in constant time.
821 fn eql(x: anytype, y: @TypeOf(x)) bool {
822 return x == y;
823 }
824
825 // Compares two big integers in constant time, returning true if x < y.
826 fn limbsCmpLt(x: anytype, y: @TypeOf(x)) bool {
827 assert(x.limbs_count() == y.limbs_count());
828 const x_limbs = x.limbs.constSlice();
829 const y_limbs = y.limbs.constSlice();
830
831 var i = x.limbs_count();
832 while (i != 0) {
833 i -= 1;
834 if (x_limbs[i] != y_limbs[i]) {
835 return x_limbs[i] < y_limbs[i];
836 }
837 }
838 return false;
839 }
840
841 // Compares two big integers in constant time, returning true if x >= y.
842 fn limbsCmpGeq(x: anytype, y: @TypeOf(x)) bool {
843 return !ct.limbsCmpLt(x, y);
844 }
845
846 // Multiplies two limbs and returns the result as a wide limb.
847 fn mulWide(x: Limb, y: Limb) WideLimb {
848 const wide = math.mulWide(Limb, x, y);
849 return .{
850 .hi = @truncate(Limb, wide >> @typeInfo(Limb).Int.bits),
851 .lo = @truncate(Limb, wide),
852 };
853 }
854};
855
856test {
857 if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest;
858
859 const M = Modulus(256);
860 const m = try M.fromPrimitive(u256, 3429938563481314093726330772853735541133072814650493833233);
861 var x = try M.Fe.fromPrimitive(u256, m, 80169837251094269539116136208111827396136208141182357733);
862 var y = try M.Fe.fromPrimitive(u256, m, 24620149608466364616251608466389896540098571);
863
864 const x_ = try x.toPrimitive(u256);
865 try testing.expect((try M.Fe.fromPrimitive(@TypeOf(x_), m, x_)).eql(x));
866 try testing.expectError(error.Overflow, x.toPrimitive(u50));
867
868 const bits = m.bits();
869 try testing.expectEqual(bits, 192);
870
871 var x_y = m.mul(x, y);
872 try testing.expectEqual(x_y.toPrimitive(u256), 1666576607955767413750776202132407807424848069716933450241);
873
874 try m.toMontgomery(&x);
875 x_y = m.mul(x, y);
876 try testing.expectEqual(x_y.toPrimitive(u256), 1666576607955767413750776202132407807424848069716933450241);
877 try m.fromMontgomery(&x);
878
879 x = m.add(x, y);
880 try testing.expectEqual(x.toPrimitive(u256), 80169837251118889688724602572728079004602598037722456304);
881 x = m.sub(x, y);
882 try testing.expectEqual(x.toPrimitive(u256), 80169837251094269539116136208111827396136208141182357733);
883
884 const big = try Uint(512).fromPrimitive(u495, 77285373554113307281465049383342993856348131409372633077285373554113307281465049383323332333429938563481314093726330772853735541133072814650493833233);
885 const reduced = m.reduce(big);
886 try testing.expectEqual(reduced.toPrimitive(u495), 858047099884257670294681641776170038885500210968322054970);
887
888 const x_pow_y = try m.powPublic(x, y);
889 try testing.expectEqual(x_pow_y.toPrimitive(u256), 1631933139300737762906024873185789093007782131928298618473);
890 try m.toMontgomery(&x);
891 const x_pow_y2 = try m.powPublic(x, y);
892 try m.fromMontgomery(&x);
893 try testing.expect(x_pow_y2.eql(x_pow_y));
894 try testing.expectError(error.NullExponent, m.powPublic(x, m.zero));
895
896 try testing.expect(!x.isZero());
897 try testing.expect(!y.isZero());
898 try testing.expect(m.v.isOdd());
899
900 const x_sq = m.sq(x);
901 const x_sq2 = m.mul(x, x);
902 try testing.expect(x_sq.eql(x_sq2));
903 try m.toMontgomery(&x);
904 const x_sq3 = m.sq(x);
905 const x_sq4 = m.mul(x, x);
906 try testing.expect(x_sq.eql(x_sq3));
907 try testing.expect(x_sq3.eql(x_sq4));
908 try m.fromMontgomery(&x);
909}
lib/std/crypto/tls/Client.zig+1-1
......@@ -607,7 +607,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
607607 const ally = fba.allocator();
608608 switch (modulus.len) {
609609 inline 128, 256, 512 => |modulus_len| {
610 const key = try rsa.PublicKey.fromBytes(exponent, modulus, ally);
610 const key = try rsa.PublicKey.fromBytes(exponent, modulus);
611611 const sig = rsa.PSSSignature.fromBytes(modulus_len, encoded_sig);
612612 try rsa.PSSSignature.verify(modulus_len, sig, verify_bytes, key, Hash, ally);
613613 },