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 @@...@@ -17,10 +17,11 @@
17//! https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/17//! https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
1818
19const std = @import("std");19const std = @import("std");
20const crypto = std.crypto;
20const mem = std.mem;21const mem = std.mem;
21const assert = std.debug.assert;22const assert = std.debug.assert;
22const AesBlock = std.crypto.core.aes.Block;23const AesBlock = crypto.core.aes.Block;
23const AuthenticationError = std.crypto.errors.AuthenticationError;24const AuthenticationError = crypto.errors.AuthenticationError;
2425
25/// AEGIS-128L with a 128-bit authentication tag.26/// AEGIS-128L with a 128-bit authentication tag.
26pub const Aegis128L = Aegis128LGeneric(128);27pub const Aegis128L = Aegis128LGeneric(128);
...@@ -169,12 +170,15 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {...@@ -169,12 +170,15 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {
169 tag.* = state.mac(tag_bits, ad.len, m.len);170 tag.* = state.mac(tag_bits, ad.len, m.len);
170 }171 }
171172
172 /// m: message: output buffer should be of size c.len173 /// `m`: Message
173 /// c: ciphertext174 /// `c`: Ciphertext
174 /// tag: authentication tag175 /// `tag`: Authentication tag
175 /// ad: Associated Data176 /// `ad`: Associated data
176 /// npub: public nonce177 /// `npub`: Public nonce
177 /// k: private key178 /// `k`: Private key
179 /// Asserts `c.len == m.len`.
180 ///
181 /// Contents of `m` are undefined if an error is returned.
178 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 {182 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 {
179 assert(c.len == m.len);183 assert(c.len == m.len);
180 var state = State128L.init(key, npub);184 var state = State128L.init(key, npub);
...@@ -203,12 +207,10 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {...@@ -203,12 +207,10 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type {
203 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16]));207 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(dst[0..16]));
204 blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32]));208 blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32]));
205 }209 }
206 const computed_tag = state.mac(tag_bits, ad.len, m.len);210 var computed_tag = state.mac(tag_bits, ad.len, m.len);
207 var acc: u8 = 0;211 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
208 for (computed_tag, 0..) |_, j| {212 if (!verify) {
209 acc |= (computed_tag[j] ^ tag[j]);213 crypto.utils.secureZero(u8, &computed_tag);
210 }
211 if (acc != 0) {
212 @memset(m, undefined);214 @memset(m, undefined);
213 return error.AuthenticationFailed;215 return error.AuthenticationFailed;
214 }216 }
...@@ -351,12 +353,15 @@ fn Aegis256Generic(comptime tag_bits: u9) type {...@@ -351,12 +353,15 @@ fn Aegis256Generic(comptime tag_bits: u9) type {
351 tag.* = state.mac(tag_bits, ad.len, m.len);353 tag.* = state.mac(tag_bits, ad.len, m.len);
352 }354 }
353355
354 /// m: message: output buffer should be of size c.len356 /// `m`: Message
355 /// c: ciphertext357 /// `c`: Ciphertext
356 /// tag: authentication tag358 /// `tag`: Authentication tag
357 /// ad: Associated Data359 /// `ad`: Associated data
358 /// npub: public nonce360 /// `npub`: Public nonce
359 /// k: private key361 /// `k`: Private key
362 /// Asserts `c.len == m.len`.
363 ///
364 /// Contents of `m` are undefined if an error is returned.
360 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 {365 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 {
361 assert(c.len == m.len);366 assert(c.len == m.len);
362 var state = State256.init(key, npub);367 var state = State256.init(key, npub);
...@@ -384,12 +389,10 @@ fn Aegis256Generic(comptime tag_bits: u9) type {...@@ -384,12 +389,10 @@ fn Aegis256Generic(comptime tag_bits: u9) type {
384 const blocks = &state.blocks;389 const blocks = &state.blocks;
385 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst));390 blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst));
386 }391 }
387 const computed_tag = state.mac(tag_bits, ad.len, m.len);392 var computed_tag = state.mac(tag_bits, ad.len, m.len);
388 var acc: u8 = 0;393 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
389 for (computed_tag, 0..) |_, j| {394 if (!verify) {
390 acc |= (computed_tag[j] ^ tag[j]);395 crypto.utils.secureZero(u8, &computed_tag);
391 }
392 if (acc != 0) {
393 @memset(m, undefined);396 @memset(m, undefined);
394 return error.AuthenticationFailed;397 return error.AuthenticationFailed;
395 }398 }
lib/std/crypto/aes_gcm.zig+12-5
...@@ -55,6 +55,15 @@ fn AesGcm(comptime Aes: anytype) type {...@@ -55,6 +55,15 @@ fn AesGcm(comptime Aes: anytype) type {
55 }55 }
56 }56 }
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.
58 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 {67 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 {
59 assert(c.len == m.len);68 assert(c.len == m.len);
6069
...@@ -86,11 +95,9 @@ fn AesGcm(comptime Aes: anytype) type {...@@ -86,11 +95,9 @@ fn AesGcm(comptime Aes: anytype) type {
86 computed_tag[i] ^= x;95 computed_tag[i] ^= x;
87 }96 }
8897
89 var acc: u8 = 0;98 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
90 for (computed_tag, 0..) |_, p| {99 if (!verify) {
91 acc |= (computed_tag[p] ^ tag[p]);100 crypto.utils.secureZero(u8, &computed_tag);
92 }
93 if (acc != 0) {
94 @memset(m, undefined);101 @memset(m, undefined);
95 return error.AuthenticationFailed;102 return error.AuthenticationFailed;
96 }103 }
lib/std/crypto/aes_ocb.zig+11-7
...@@ -168,12 +168,15 @@ fn AesOcb(comptime Aes: anytype) type {...@@ -168,12 +168,15 @@ fn AesOcb(comptime Aes: anytype) type {
168 tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));168 tag.* = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
169 }169 }
170170
171 /// m: message: output buffer should be of size c.len171 /// `m`: Message
172 /// c: ciphertext172 /// `c`: Ciphertext
173 /// tag: authentication tag173 /// `tag`: Authentication tag
174 /// ad: Associated Data174 /// `ad`: Associated data
175 /// npub: public nonce175 /// `npub`: Public nonce
176 /// k: secret key176 /// `k`: Private key
177 /// Asserts `c.len == m.len`.
178 ///
179 /// Contents of `m` are undefined if an error is returned.
177 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 {180 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 {
178 assert(c.len == m.len);181 assert(c.len == m.len);
179182
...@@ -232,8 +235,9 @@ fn AesOcb(comptime Aes: anytype) type {...@@ -232,8 +235,9 @@ fn AesOcb(comptime Aes: anytype) type {
232 aes_enc_ctx.encrypt(&e, &e);235 aes_enc_ctx.encrypt(&e, &e);
233 var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));236 var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad));
234 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);237 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
235 crypto.utils.secureZero(u8, &computed_tag);
236 if (!verify) {238 if (!verify) {
239 crypto.utils.secureZero(u8, &computed_tag);
240 @memset(m, undefined);
237 return error.AuthenticationFailed;241 return error.AuthenticationFailed;
238 }242 }
239 }243 }
lib/std/crypto/chacha20.zig+27-22
...@@ -2,13 +2,14 @@...@@ -2,13 +2,14 @@
22
3const std = @import("../std.zig");3const std = @import("../std.zig");
4const builtin = @import("builtin");4const builtin = @import("builtin");
5const crypto = std.crypto;
5const math = std.math;6const math = std.math;
6const mem = std.mem;7const mem = std.mem;
7const assert = std.debug.assert;8const assert = std.debug.assert;
8const testing = std.testing;9const testing = std.testing;
9const maxInt = math.maxInt;10const maxInt = math.maxInt;
10const Poly1305 = std.crypto.onetimeauth.Poly1305;11const Poly1305 = crypto.onetimeauth.Poly1305;
11const AuthenticationError = std.crypto.errors.AuthenticationError;12const AuthenticationError = crypto.errors.AuthenticationError;
1213
13/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.14/// IETF-variant of the ChaCha20 stream cipher, as designed for TLS.
14pub const ChaCha20IETF = ChaChaIETF(20);15pub const ChaCha20IETF = ChaChaIETF(20);
...@@ -675,13 +676,15 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {...@@ -675,13 +676,15 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
675 mac.final(tag);676 mac.final(tag);
676 }677 }
677678
678 /// m: message: output buffer should be of size c.len679 /// `m`: Message
679 /// c: ciphertext680 /// `c`: Ciphertext
680 /// tag: authentication tag681 /// `tag`: Authentication tag
681 /// ad: Associated Data682 /// `ad`: Associated data
682 /// npub: public nonce683 /// `npub`: Public nonce
683 /// k: private key684 /// `k`: Private key
684 /// NOTE: the check of the authentication tag is currently not done in constant time685 /// Asserts `c.len == m.len`.
686 ///
687 /// Contents of `m` are undefined if an error is returned.
685 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 {688 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 {
686 assert(c.len == m.len);689 assert(c.len == m.len);
687690
...@@ -706,14 +709,13 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {...@@ -706,14 +709,13 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
706 mem.writeIntLittle(u64, lens[0..8], ad.len);709 mem.writeIntLittle(u64, lens[0..8], ad.len);
707 mem.writeIntLittle(u64, lens[8..16], c.len);710 mem.writeIntLittle(u64, lens[8..16], c.len);
708 mac.update(lens[0..]);711 mac.update(lens[0..]);
709 var computedTag: [16]u8 = undefined;712 var computed_tag: [16]u8 = undefined;
710 mac.final(computedTag[0..]);713 mac.final(computed_tag[0..]);
711714
712 var acc: u8 = 0;715 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
713 for (computedTag, 0..) |_, i| {716 if (!verify) {
714 acc |= computedTag[i] ^ tag[i];717 crypto.utils.secureZero(u8, &computed_tag);
715 }718 @memset(m, undefined);
716 if (acc != 0) {
717 return error.AuthenticationFailed;719 return error.AuthenticationFailed;
718 }720 }
719 ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);721 ChaChaIETF(rounds_nb).xor(m[0..c.len], c, 1, k, npub);
...@@ -738,12 +740,15 @@ fn XChaChaPoly1305(comptime rounds_nb: usize) type {...@@ -738,12 +740,15 @@ fn XChaChaPoly1305(comptime rounds_nb: usize) type {
738 return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);740 return ChaChaPoly1305(rounds_nb).encrypt(c, tag, m, ad, extended.nonce, extended.key);
739 }741 }
740742
741 /// m: message: output buffer should be of size c.len743 /// `m`: Message
742 /// c: ciphertext744 /// `c`: Ciphertext
743 /// tag: authentication tag745 /// `tag`: Authentication tag
744 /// ad: Associated Data746 /// `ad`: Associated data
745 /// npub: public nonce747 /// `npub`: Public nonce
746 /// k: private key748 /// `k`: Private key
749 /// Asserts `c.len == m.len`.
750 ///
751 /// Contents of `m` are undefined if an error is returned.
747 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 {752 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 {
748 const extended = extend(k, npub, rounds_nb);753 const extended = extend(k, npub, rounds_nb);
749 return ChaChaPoly1305(rounds_nb).decrypt(m, c, tag, ad, extended.nonce, extended.key);754 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 {...@@ -147,11 +147,21 @@ pub const IsapA128A = struct {
147 tag.* = mac(c, ad, npub, key);147 tag.* = mac(c, ad, npub, key);
148 }148 }
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.
150 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 {159 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 {
151 var computed_tag = mac(c, ad, npub, key);160 var computed_tag = mac(c, ad, npub, key);
152 const res = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);161 const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
153 crypto.utils.secureZero(u8, &computed_tag);162 if (!verify) {
154 if (!res) {163 crypto.utils.secureZero(u8, &computed_tag);
164 @memset(m, undefined);
155 return error.AuthenticationFailed;165 return error.AuthenticationFailed;
156 }166 }
157 xor(m, c, npub, key);167 xor(m, c, npub, key);
lib/std/crypto/salsa20.zig+16-14
...@@ -394,12 +394,15 @@ pub const XSalsa20Poly1305 = struct {...@@ -394,12 +394,15 @@ pub const XSalsa20Poly1305 = struct {
394 mac.final(tag);394 mac.final(tag);
395 }395 }
396396
397 /// m: message: output buffer should be of size c.len397 /// `m`: Message
398 /// c: ciphertext398 /// `c`: Ciphertext
399 /// tag: authentication tag399 /// `tag`: Authentication tag
400 /// ad: Associated Data400 /// `ad`: Associated data
401 /// npub: public nonce401 /// `npub`: Public nonce
402 /// k: private key402 /// `k`: Private key
403 /// Asserts `c.len == m.len`.
404 ///
405 /// Contents of `m` are undefined if an error is returned.
403 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 {406 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 {
404 debug.assert(c.len == m.len);407 debug.assert(c.len == m.len);
405 const extended = extend(rounds, k, npub);408 const extended = extend(rounds, k, npub);
...@@ -410,14 +413,13 @@ pub const XSalsa20Poly1305 = struct {...@@ -410,14 +413,13 @@ pub const XSalsa20Poly1305 = struct {
410 var mac = Poly1305.init(block0[0..32]);413 var mac = Poly1305.init(block0[0..32]);
411 mac.update(ad);414 mac.update(ad);
412 mac.update(c);415 mac.update(c);
413 var computedTag: [tag_length]u8 = undefined;416 var computed_tag: [tag_length]u8 = undefined;
414 mac.final(&computedTag);417 mac.final(&computed_tag);
415 var acc: u8 = 0;418
416 for (computedTag, 0..) |_, i| {419 const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag);
417 acc |= computedTag[i] ^ tag[i];420 if (!verify) {
418 }421 utils.secureZero(u8, &computed_tag);
419 if (acc != 0) {422 @memset(m, undefined);
420 utils.secureZero(u8, &computedTag);
421 return error.AuthenticationFailed;423 return error.AuthenticationFailed;
422 }424 }
423 @memcpy(m[0..mlen0], block0[32..][0..mlen0]);425 @memcpy(m[0..mlen0], block0[32..][0..mlen0]);