authorgravatar for git@e4m2.come4m2 <git@e4m2.com> 2023-08-14 21:39:51+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-08-14 21:39:51+02:00
log9135115573051eff58ffcf1ba0a3cce51ed0b413
tree2fb1cc52c2e7c0fbda59bf4b7af80b5117df710a
parent8f3ccbbe367bea66d7f0f364a957870eb2cc95a0
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.crypto.aead: Consistent decryption tail and doc fixes (#16781)

* Consistent decryption tail for all AEADs * Remove outdated note This was previously copied here from another function. There used to be another comment on the tag verification linking to issue #1776, but that one was not copied over. As it stands, this note seems fairly misleading/irrelevant. * Prettier docs * Add note about plaintext contents to docs * Capitalization * Fixup missing XChaChaPoly docs

6 files changed, 108 insertions(+), 77 deletions(-)

lib/std/crypto/aegis.zig+29-26
......@@ -17,10 +17,11 @@
1717//! https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
1818
1919const std = @import("std");
20const crypto = std.crypto;
2021const mem = std.mem;
2122const assert = std.debug.assert;
22const AesBlock = std.crypto.core.aes.Block;
23const AuthenticationError = std.crypto.errors.AuthenticationError;
23const AesBlock = crypto.core.aes.Block;
24const AuthenticationError = crypto.errors.AuthenticationError;
2425
2526/// AEGIS-128L with a 128-bit authentication tag.
2627pub const Aegis128L = Aegis128LGeneric(128);
......@@ -169,12 +170,15 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {
169170 tag.* = state.mac(tag_bits, ad.len, m.len);
170171 }
171172
172 /// m: message: output buffer should be of size c.len
173 /// c: ciphertext
174 /// tag: authentication tag
175 /// ad: Associated Data
176 /// npub: public nonce
177 /// k: private key
173 /// `m`: Message
174 /// `c`: Ciphertext
175 /// `tag`: Authentication tag
176 /// `ad`: Associated data
177 /// `npub`: Public nonce
178 /// `k`: Private key
179 /// Asserts `c.len == m.len`.
180 ///
181 /// Contents of `m` are undefined if an error is returned.
178182 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
179183 assert(c.len == m.len);
180184 var state = State128L.init(key, npub);
......@@ -203,12 +207,10 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {
203207 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16]));
204208 blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32]));
205209 }
206 const computed_tag = state.mac(tag_bits, ad.len, m.len);
207 var acc: u8 = 0;
208 for (computed_tag, 0..) |_, j| {
209 acc |= (computed_tag[j] ^ tag[j]);
210 }
211 if (acc != 0) {
210 var computed_tag = state.mac(tag_bits, ad.len, m.len);
211 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
212 if (!verify) {
213 crypto.utils.secureZero(u8, &computed_tag);
212214 @memset(m, undefined);
213215 return error.AuthenticationFailed;
214216 }
......@@ -351,12 +353,15 @@ fn Aegis256Generic(comptime tag_bits: u9) type {
351353 tag.* = state.mac(tag_bits, ad.len, m.len);
352354 }
353355
354 /// m: message: output buffer should be of size c.len
355 /// c: ciphertext
356 /// tag: authentication tag
357 /// ad: Associated Data
358 /// npub: public nonce
359 /// k: private key
356 /// `m`: Message
357 /// `c`: Ciphertext
358 /// `tag`: Authentication tag
359 /// `ad`: Associated data
360 /// `npub`: Public nonce
361 /// `k`: Private key
362 /// Asserts `c.len == m.len`.
363 ///
364 /// Contents of `m` are undefined if an error is returned.
360365 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
361366 assert(c.len == m.len);
362367 var state = State256.init(key, npub);
......@@ -384,12 +389,10 @@ fn Aegis256Generic(comptime tag_bits: u9) type {
384389 const blocks = &state.blocks;
385390 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst));
386391 }
387 const computed_tag = state.mac(tag_bits, ad.len, m.len);
388 var acc: u8 = 0;
389 for (computed_tag, 0..) |_, j| {
390 acc |= (computed_tag[j] ^ tag[j]);
391 }
392 if (acc != 0) {
392 var computed_tag = state.mac(tag_bits, ad.len, m.len);
393 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
394 if (!verify) {
395 crypto.utils.secureZero(u8, &computed_tag);
393396 @memset(m, undefined);
394397 return error.AuthenticationFailed;
395398 }
lib/std/crypto/aes_gcm.zig+12-5
......@@ -55,6 +55,15 @@ fn AesGcm(comptime Aes: anytype) type {
5555 }
5656 }
5757
58 /// `m`: Message
59 /// `c`: Ciphertext
60 /// `tag`: Authentication tag
61 /// `ad`: Associated data
62 /// `npub`: Public nonce
63 /// `k`: Private key
64 /// Asserts `c.len == m.len`.
65 ///
66 /// Contents of `m` are undefined if an error is returned.
5867 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
5968 assert(c.len == m.len);
6069
......@@ -86,11 +95,9 @@ fn AesGcm(comptime Aes: anytype) type {
8695 computed_tag[i] ^= x;
8796 }
8897
89 var acc: u8 = 0;
90 for (computed_tag, 0..) |_, p| {
91 acc |= (computed_tag[p] ^ tag[p]);
92 }
93 if (acc != 0) {
98 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
99 if (!verify) {
100 crypto.utils.secureZero(u8, &computed_tag);
94101 @memset(m, undefined);
95102 return error.AuthenticationFailed;
96103 }
lib/std/crypto/aes_ocb.zig+11-7
......@@ -168,12 +168,15 @@ fn AesOcb(comptime Aes: anytype) type {
168168 tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
169169 }
170170
171 /// m: message: output buffer should be of size c.len
172 /// c: ciphertext
173 /// tag: authentication tag
174 /// ad: Associated Data
175 /// npub: public nonce
176 /// k: secret key
171 /// `m`: Message
172 /// `c`: Ciphertext
173 /// `tag`: Authentication tag
174 /// `ad`: Associated data
175 /// `npub`: Public nonce
176 /// `k`: Private key
177 /// Asserts `c.len == m.len`.
178 ///
179 /// Contents of `m` are undefined if an error is returned.
177180 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
178181 assert(c.len == m.len);
179182
......@@ -232,8 +235,9 @@ fn AesOcb(comptime Aes: anytype) type {
232235 aes_enc_ctx.encrypt(&e, &e);
233236 var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
234237 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
235 crypto.utils.secureZero(u8, &computed_tag);
236238 if (!verify) {
239 crypto.utils.secureZero(u8, &computed_tag);
240 @memset(m, undefined);
237241 return error.AuthenticationFailed;
238242 }
239243 }
lib/std/crypto/chacha20.zig+27-22
......@@ -2,13 +2,14 @@
22
33const std = @import("../std.zig");
44const builtin = @import("builtin");
5const crypto = std.crypto;
56const math = std.math;
67const mem = std.mem;
78const assert = std.debug.assert;
89const testing = std.testing;
910const maxInt = math.maxInt;
10const Poly1305 = std.crypto.onetimeauth.Poly1305;
11const AuthenticationError = std.crypto.errors.AuthenticationError;
11const Poly1305 = crypto.onetimeauth.Poly1305;
12const AuthenticationError = crypto.errors.AuthenticationError;
1213
1314/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.
1415pub const ChaCha20IETF = ChaChaIETF(20);
......@@ -675,13 +676,15 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
675676 mac.final(tag);
676677 }
677678
678 /// m: message: output buffer should be of size c.len
679 /// c: ciphertext
680 /// tag: authentication tag
681 /// ad: Associated Data
682 /// npub: public nonce
683 /// k: private key
684 /// NOTE: the check of the authentication tag is currently not done in constant time
679 /// `m`: Message
680 /// `c`: Ciphertext
681 /// `tag`: Authentication tag
682 /// `ad`: Associated data
683 /// `npub`: Public nonce
684 /// `k`: Private key
685 /// Asserts `c.len == m.len`.
686 ///
687 /// Contents of `m` are undefined if an error is returned.
685688 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
686689 assert(c.len == m.len);
687690
......@@ -706,14 +709,13 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
706709 mem.writeIntLittle(u64, lens[0..8], ad.len);
707710 mem.writeIntLittle(u64, lens[8..16], c.len);
708711 mac.update(lens[0..]);
709 var computedTag: [16]u8 = undefined;
710 mac.final(computedTag[0..]);
712 var computed_tag: [16]u8 = undefined;
713 mac.final(computed_tag[0..]);
711714
712 var acc: u8 = 0;
713 for (computedTag, 0..) |_, i| {
714 acc |= computedTag[i] ^ tag[i];
715 }
716 if (acc != 0) {
715 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
716 if (!verify) {
717 crypto.utils.secureZero(u8, &computed_tag);
718 @memset(m, undefined);
717719 return error.AuthenticationFailed;
718720 }
719721 ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);
......@@ -738,12 +740,15 @@ fn XChaChaPoly1305(comptime rounds_nb: usize) type {
738740 return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);
739741 }
740742
741 /// m: message: output buffer should be of size c.len
742 /// c: ciphertext
743 /// tag: authentication tag
744 /// ad: Associated Data
745 /// npub: public nonce
746 /// k: private key
743 /// `m`: Message
744 /// `c`: Ciphertext
745 /// `tag`: Authentication tag
746 /// `ad`: Associated data
747 /// `npub`: Public nonce
748 /// `k`: Private key
749 /// Asserts `c.len == m.len`.
750 ///
751 /// Contents of `m` are undefined if an error is returned.
747752 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
748753 const extended = extend(k, npub, rounds_nb);
749754 return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key);
lib/std/crypto/isap.zig+13-3
......@@ -147,11 +147,21 @@ pub const IsapA128A = struct {
147147 tag.* = mac(c, ad, npub, key);
148148 }
149149
150 /// `m`: Message
151 /// `c`: Ciphertext
152 /// `tag`: Authentication tag
153 /// `ad`: Associated data
154 /// `npub`: Public nonce
155 /// `k`: Private key
156 /// Asserts `c.len == m.len`.
157 ///
158 /// Contents of `m` are undefined if an error is returned.
150159 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
151160 var computed_tag = mac(c, ad, npub, key);
152 const res = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
153 crypto.utils.secureZero(u8, &computed_tag);
154 if (!res) {
161 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
162 if (!verify) {
163 crypto.utils.secureZero(u8, &computed_tag);
164 @memset(m, undefined);
155165 return error.AuthenticationFailed;
156166 }
157167 xor(m, c, npub, key);
lib/std/crypto/salsa20.zig+16-14
......@@ -394,12 +394,15 @@ pub const XSalsa20Poly1305 = struct {
394394 mac.final(tag);
395395 }
396396
397 /// m: message: output buffer should be of size c.len
398 /// c: ciphertext
399 /// tag: authentication tag
400 /// ad: Associated Data
401 /// npub: public nonce
402 /// k: private key
397 /// `m`: Message
398 /// `c`: Ciphertext
399 /// `tag`: Authentication tag
400 /// `ad`: Associated data
401 /// `npub`: Public nonce
402 /// `k`: Private key
403 /// Asserts `c.len == m.len`.
404 ///
405 /// Contents of `m` are undefined if an error is returned.
403406 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
404407 debug.assert(c.len == m.len);
405408 const extended = extend(rounds, k, npub);
......@@ -410,14 +413,13 @@ pub const XSalsa20Poly1305 = struct {
410413 var mac = Poly1305.init(block0[0..32]);
411414 mac.update(ad);
412415 mac.update(c);
413 var computedTag: [tag_length]u8 = undefined;
414 mac.final(&computedTag);
415 var acc: u8 = 0;
416 for (computedTag, 0..) |_, i| {
417 acc |= computedTag[i] ^ tag[i];
418 }
419 if (acc != 0) {
420 utils.secureZero(u8, &computedTag);
416 var computed_tag: [tag_length]u8 = undefined;
417 mac.final(&computed_tag);
418
419 const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag);
420 if (!verify) {
421 utils.secureZero(u8, &computed_tag);
422 @memset(m, undefined);
421423 return error.AuthenticationFailed;
422424 }
423425 @memcpy(m[0..mlen0], block0[32..][0..mlen0]);