From c2b02d01d5c0bb684565d5b23b34022b752e9507 Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Fri, 11 Sep 2020 17:10:27 -0400 Subject: Add crypto.kdf.pbkdf2 --- lib/std/crypto.zig | 6 ++ lib/std/crypto/pbkdf2.zig | 227 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 lib/std/crypto/pbkdf2.zig (limited to 'lib/std') diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 5de2f13896..2b42942824 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -35,6 +35,11 @@ pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; }; +/// Key derivation functions +pub const kdf = struct { + pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; +}; + /// Core functions, that should rarely be used directly by applications. pub const core = struct { pub const aes = @import("crypto/aes.zig"); @@ -77,6 +82,7 @@ test "crypto" { _ = @import("crypto/gimli.zig"); _ = @import("crypto/hmac.zig"); _ = @import("crypto/md5.zig"); + _ = @import("crypto/pbkdf2.zig"); _ = @import("crypto/poly1305.zig"); _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig new file mode 100644 index 0000000000..fe00f7ee08 --- /dev/null +++ b/lib/std/crypto/pbkdf2.zig @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +const std = @import("std"); +const crypto = std.crypto; +const debug = std.debug; +const assert = debug.assert; +const mem = std.mem; + +// RFC 2898 Section 5.2 +// +// FromSpec: +// +// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an +// example) to derive keys. The length of the derived key is essentially +// unbounded. (However, the maximum effective search space for the +// derived key may be limited by the structure of the underlying +// pseudorandom function. See Appendix B.1 for further discussion.) +// PBKDF2 is recommended for new applications. +// +// PBKDF2 (P, S, c, dkLen) +// +// Options: PRF underlying pseudorandom function (hLen +// denotes the length in octets of the +// pseudorandom function output) +// +// Input: P password, an octet string +// S salt, an octet string +// c iteration count, a positive integer +// dkLen intended length in octets of the derived +// key, a positive integer, at most +// (2^32 - 1) * hLen +// +// Output: DK derived key, a dkLen-octet string + +// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. + +pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Hash: type) void { + assert(rounds >= 1); + + const dkLen = derivedKey.len; + const hLen = Hash.digest_length; + const Prf = crypto.auth.hmac.Hmac(Hash); + + // FromSpec: + // + // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and + // stop. + // + assert(dkLen > 0 and dkLen <= (1 << 32 - 1) * hLen); + + // FromSpec: + // + // 2. Let l be the number of hLen-octet blocks in the derived key, + // rounding up, and let r be the number of octets in the last + // block + // + const l = (dkLen + hLen - 1) / hLen; + var r = dkLen % hLen; + r = if (r != 0) r else hLen; + + // FromSpec: + // + // 3. For each block of the derived key apply the function F defined + // below to the password P, the salt S, the iteration count c, and + // the block index to compute the block: + // + // T_1 = F (P, S, c, 1) , + // T_2 = F (P, S, c, 2) , + // ... + // T_l = F (P, S, c, l) , + // + // where the function F is defined as the exclusive-or sum of the + // first c iterates of the underlying pseudorandom function PRF + // applied to the password P and the concatenation of the salt S + // and the block index i: + // + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + // + // where + // + // U_1 = PRF (P, S || INT (i)) , + // U_2 = PRF (P, U_1) , + // ... + // U_c = PRF (P, U_{c-1}) . + // + // Here, INT (i) is a four-octet encoding of the integer i, most + // significant octet first. + // + // 4. Concatenate the blocks and extract the first dkLen octets to + // produce a derived key DK: + // + // DK = T_1 || T_2 || ... || T_l<0..r-1> + + var prevBlock: [hLen]u8 = undefined; + var newBlock: [hLen]u8 = undefined; + + var block: u32 = 0; // Spec limits to u32 + while (block < l) : (block += 1) { + + // U_1 = PRF (P, S || INT (i)) + const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 + var ctx = Prf.init(password); + ctx.update(salt); + ctx.update(blockIndex[0..]); + ctx.final(prevBlock[0..]); + + // Choose portion of DK to write into (T_n) and initialize + const offset = block * hLen; + const blockLen = if (block != l - 1) hLen else r; + var dkBlock = derivedKey[offset..(offset + blockLen)]; + mem.copy(u8, dkBlock[0..], prevBlock[0..dkBlock.len]); + + var i: u32 = 1; + while (i < rounds) : (i += 1) { + // U_c = PRF (P, U_{c-1}) + Prf.create(newBlock[0..], prevBlock[0..], password); + mem.copy(u8, prevBlock[0..], newBlock[0..]); + + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + for (dkBlock) |_, j| { + dkBlock[j] ^= newBlock[j]; + } + } + } +} + +const htest = @import("test.zig"); + +// RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors +test "RFC 6070 one iteration" { + const p = "password"; + const s = "salt"; + const c = 1; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 two iterations" { + const p = "password"; + const s = "salt"; + const c = 2; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 4096 iterations" { + const p = "password"; + const s = "salt"; + const c = 4096; + const dkLen = 20; + + var derivedKey: [dkLen]u8 = undefined; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "4b007901b765489abead49d926f721d065a429c1"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 16,777,216 iterations" { + // These iteration tests are slow so we always skip them. Results have been verified. + if (true) { + return error.SkipZigTest; + } + + const p = "password"; + const s = "salt"; + const c = 16777216; + const dkLen = 20; + + var derivedKey = [_]u8{0} ** dkLen; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 multi-block salt and password" { + const p = "passwordPASSWORDpassword"; + const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; + const c = 4096; + const dkLen = 25; + + var derivedKey: [dkLen]u8 = undefined; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; + + htest.assertEqual(expected, derivedKey[0..]); +} + +test "RFC 6070 embedded NUL" { + const p = "pass\x00word"; + const s = "sa\x00lt"; + const c = 4096; + const dkLen = 16; + + var derivedKey: [dkLen]u8 = undefined; + + pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "56fa6aa75548099dcc37d7f03425e0c3"; + + htest.assertEqual(expected, derivedKey[0..]); +} -- cgit v1.2.3 From 37db93e4260dfcb90ac1553cf096a35ada1825ca Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sat, 12 Sep 2020 15:02:00 -0400 Subject: Review comments from pbkdf2.zig Move block definitions inside while loop. Use usize for offset. (This still crashes on overflow) Remove unneeded slice syntax. Add slow test for Very large dkLen --- lib/std/crypto/pbkdf2.zig | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index fe00f7ee08..d0eaca1b0a 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -95,11 +95,10 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: // // DK = T_1 || T_2 || ... || T_l<0..r-1> - var prevBlock: [hLen]u8 = undefined; - var newBlock: [hLen]u8 = undefined; - var block: u32 = 0; // Spec limits to u32 while (block < l) : (block += 1) { + var prevBlock: [hLen]u8 = undefined; + var newBlock: [hLen]u8 = undefined; // U_1 = PRF (P, S || INT (i)) const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 @@ -109,15 +108,15 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: ctx.final(prevBlock[0..]); // Choose portion of DK to write into (T_n) and initialize - const offset = block * hLen; + const offset: usize = block * hLen; const blockLen = if (block != l - 1) hLen else r; var dkBlock = derivedKey[offset..(offset + blockLen)]; - mem.copy(u8, dkBlock[0..], prevBlock[0..dkBlock.len]); + mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); var i: u32 = 1; while (i < rounds) : (i += 1) { // U_c = PRF (P, U_{c-1}) - Prf.create(newBlock[0..], prevBlock[0..], password); + Prf.create(&newBlock, prevBlock[0..], password); mem.copy(u8, prevBlock[0..], newBlock[0..]); // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c @@ -225,3 +224,26 @@ test "RFC 6070 embedded NUL" { htest.assertEqual(expected, derivedKey[0..]); } + +test "Very large dkLen" { + // These iteration tests are slow so we always skip them. Results have been verified. + if (true) { + return error.SkipZigTest; + } + + const p = "password"; + const s = "salt"; + const c = 1; + const dkLen = 1 << 33; + + var derivedKey = try std.testing.allocator.alloc(u8, dkLen); + defer { + std.testing.allocator.free(derivedKey); + } + + pbkdf2(derivedKey, p, s, c, crypto.hash.Sha1); + + const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + + htest.assertEqual(expected, derivedKey[0..]); +} -- cgit v1.2.3 From 3f450b7e931b76b20e36d2bab2e2d1fed3ee19ec Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sat, 12 Sep 2020 18:17:04 -0400 Subject: Replace Hash function with Prf. Correct offset bit-width. --- lib/std/crypto/pbkdf2.zig | 51 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 15 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index d0eaca1b0a..ceed9beb6d 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -10,6 +10,14 @@ const debug = std.debug; const assert = debug.assert; const mem = std.mem; +//! PBKDF2 (Password-Based Key Derivation Function 2) is a specific Key Derivation Function, +//! intended to turn a weak, human generated password into a strong key, suitable for cryptographic +//! uses. It does this by salting and stretching the password. Salting injects non-secret random +//! data, so that identical passwords will be converted into unique keys. Stretching applies a +//! deliberately slow hashing function to frustrate brute-force guessing. +//! +//! PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. + // RFC 2898 Section 5.2 // // FromSpec: @@ -38,19 +46,33 @@ const mem = std.mem; // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. -pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Hash: type) void { +/// Given a password, salt, iteration count (rounds), and a pseudo-random function, generates a +/// derived key in the provided buffer slice. +/// +/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. +/// May be uninitialized. All bytes will be written. +/// Maximum size is (2^32 - 1) * Hash.digest_length +/// It is a programming error to pass buffer longer than the maximum size. +/// +/// password: Arbitrary sequence of bytes of any length, including empty. +/// +/// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes. +/// +/// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. +/// +/// Prf: Pseudo-random function to use. The most common choice is std.crypto.auth.hmac.HmacSha256. +pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) void { assert(rounds >= 1); - const dkLen = derivedKey.len; - const hLen = Hash.digest_length; - const Prf = crypto.auth.hmac.Hmac(Hash); + const dkLen: u64 = derivedKey.len; + const hLen: u32 = Prf.mac_length; // Force type to ensure multiplications can't overflow // FromSpec: // // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and // stop. // - assert(dkLen > 0 and dkLen <= (1 << 32 - 1) * hLen); + assert(dkLen > 0 and dkLen <= @as(u64, 1 << 32 - 1) * hLen); // FromSpec: // @@ -108,7 +130,7 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: ctx.final(prevBlock[0..]); // Choose portion of DK to write into (T_n) and initialize - const offset: usize = block * hLen; + const offset: u64 = @as(u64, block) * hLen; const blockLen = if (block != l - 1) hLen else r; var dkBlock = derivedKey[offset..(offset + blockLen)]; mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); @@ -138,7 +160,7 @@ test "RFC 6070 one iteration" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; @@ -153,7 +175,7 @@ test "RFC 6070 two iterations" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; @@ -168,7 +190,7 @@ test "RFC 6070 4096 iterations" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "4b007901b765489abead49d926f721d065a429c1"; @@ -188,7 +210,7 @@ test "RFC 6070 16,777,216 iterations" { var derivedKey = [_]u8{0} ** dkLen; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; @@ -203,7 +225,7 @@ test "RFC 6070 multi-block salt and password" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; @@ -218,7 +240,7 @@ test "RFC 6070 embedded NUL" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "56fa6aa75548099dcc37d7f03425e0c3"; @@ -226,11 +248,10 @@ test "RFC 6070 embedded NUL" { } test "Very large dkLen" { - // These iteration tests are slow so we always skip them. Results have been verified. + // This test allocates 8GB of memory and is expected to take several hours to run. if (true) { return error.SkipZigTest; } - const p = "password"; const s = "salt"; const c = 1; @@ -241,7 +262,7 @@ test "Very large dkLen" { std.testing.allocator.free(derivedKey); } - pbkdf2(derivedKey, p, s, c, crypto.hash.Sha1); + pbkdf2(derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; -- cgit v1.2.3 From 17156e1775f60893b4c5040d7a0073659ea9157d Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sat, 12 Sep 2020 18:33:53 -0400 Subject: pbkdf2 "very large dklen test" should just check for crashes --- lib/std/crypto/pbkdf2.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index ceed9beb6d..6cf1e8a75e 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -59,8 +59,10 @@ const mem = std.mem; /// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes. /// /// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. +/// Larger iteration counts improve security by increasing the time required to compute +/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. /// -/// Prf: Pseudo-random function to use. The most common choice is std.crypto.auth.hmac.HmacSha256. +/// Prf: Pseudo-random function to use. A common choice is std.crypto.auth.hmac.HmacSha256. pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) void { assert(rounds >= 1); @@ -263,8 +265,5 @@ test "Very large dkLen" { } pbkdf2(derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); - - const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; - - htest.assertEqual(expected, derivedKey[0..]); + // Just verify this doesn't crash with an overflow } -- cgit v1.2.3 From 0f85b85acb0bd322ff7408c25dc09a84ff6621dc Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 13 Sep 2020 09:59:36 -0400 Subject: Improve doc text --- lib/std/crypto/pbkdf2.zig | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 6cf1e8a75e..2f9b720220 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -10,11 +10,11 @@ const debug = std.debug; const assert = debug.assert; const mem = std.mem; -//! PBKDF2 (Password-Based Key Derivation Function 2) is a specific Key Derivation Function, -//! intended to turn a weak, human generated password into a strong key, suitable for cryptographic -//! uses. It does this by salting and stretching the password. Salting injects non-secret random -//! data, so that identical passwords will be converted into unique keys. Stretching applies a -//! deliberately slow hashing function to frustrate brute-force guessing. +//! PBKDF2 (Password-Based Key Derivation Function 2) is intended to turn a weak, human generated +//! password into a strong key, suitable for cryptographic uses. It does this by salting and +//! stretching the password. Salting injects non-secret random data, so that identical passwords +//! will be converted into unique keys. Stretching applies a deliberately slow hashing function to +//! frustrate brute-force guessing. //! //! PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. @@ -46,8 +46,7 @@ const mem = std.mem; // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. -/// Given a password, salt, iteration count (rounds), and a pseudo-random function, generates a -/// derived key in the provided buffer slice. +/// Apply PBKDF2 to generate a key from a password. /// /// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. /// May be uninitialized. All bytes will be written. -- cgit v1.2.3 From 257c5b534839d75092909f604bb2663e883a290f Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 13 Sep 2020 10:50:46 -0400 Subject: Explicitly reference std.crypto.kdf in test case --- lib/std/crypto.zig | 6 +++++- lib/std/crypto/pbkdf2.zig | 14 +++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 2b42942824..c375c02906 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -35,7 +35,11 @@ pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; }; -/// Key derivation functions +/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a +/// strong key, suitable for cryptographic uses. It does this by salting and stretching the +/// password. Salting injects non-secret random data, so that identical passwords will be converted +/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate +/// brute-force guessing. pub const kdf = struct { pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; }; diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 2f9b720220..dfa6b1c022 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -10,14 +10,6 @@ const debug = std.debug; const assert = debug.assert; const mem = std.mem; -//! PBKDF2 (Password-Based Key Derivation Function 2) is intended to turn a weak, human generated -//! password into a strong key, suitable for cryptographic uses. It does this by salting and -//! stretching the password. Salting injects non-secret random data, so that identical passwords -//! will be converted into unique keys. Stretching applies a deliberately slow hashing function to -//! frustrate brute-force guessing. -//! -//! PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. - // RFC 2898 Section 5.2 // // FromSpec: @@ -48,6 +40,8 @@ const mem = std.mem; /// Apply PBKDF2 to generate a key from a password. /// +/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. +/// /// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. /// May be uninitialized. All bytes will be written. /// Maximum size is (2^32 - 1) * Hash.digest_length @@ -62,6 +56,8 @@ const mem = std.mem; /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. /// /// Prf: Pseudo-random function to use. A common choice is std.crypto.auth.hmac.HmacSha256. +/// +/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) void { assert(rounds >= 1); @@ -161,7 +157,7 @@ test "RFC 6070 one iteration" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + std.crypto.kdf.pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; -- cgit v1.2.3 From 8a1a40276fb1577f46b89e3aefc17f9e82a933d6 Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 13 Sep 2020 11:08:06 -0400 Subject: Extract kdf.zig to provide namespace documentation --- lib/std/crypto.zig | 11 ++--------- lib/std/crypto/kdf.zig | 17 +++++++++++++++++ lib/std/crypto/pbkdf2.zig | 2 -- 3 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 lib/std/crypto/kdf.zig (limited to 'lib/std') diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index c375c02906..64ec22894c 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -35,14 +35,7 @@ pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; }; -/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a -/// strong key, suitable for cryptographic uses. It does this by salting and stretching the -/// password. Salting injects non-secret random data, so that identical passwords will be converted -/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate -/// brute-force guessing. -pub const kdf = struct { - pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; -}; +pub const kdf = @import("crypto/kdf.zig"); /// Core functions, that should rarely be used directly by applications. pub const core = struct { @@ -86,7 +79,7 @@ test "crypto" { _ = @import("crypto/gimli.zig"); _ = @import("crypto/hmac.zig"); _ = @import("crypto/md5.zig"); - _ = @import("crypto/pbkdf2.zig"); + _ = @import("crypto/kdf.zig"); _ = @import("crypto/poly1305.zig"); _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); diff --git a/lib/std/crypto/kdf.zig b/lib/std/crypto/kdf.zig new file mode 100644 index 0000000000..06bf67bbbd --- /dev/null +++ b/lib/std/crypto/kdf.zig @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a +//! strong key, suitable for cryptographic uses. It does this by salting and stretching the +//! password. Salting injects non-secret random data, so that identical passwords will be converted +//! into unique keys. Stretching applies a deliberately slow hashing function to frustrate +//! brute-force guessing. + +pub const pbkdf2 = @import("pbkdf2.zig").pbkdf2; + +test "kdf" { + _ = @import("pbkdf2.zig"); +} diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index dfa6b1c022..424e2d6f62 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -56,8 +56,6 @@ const mem = std.mem; /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. /// /// Prf: Pseudo-random function to use. A common choice is std.crypto.auth.hmac.HmacSha256. -/// -/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) void { assert(rounds >= 1); -- cgit v1.2.3 From 2f9c9662ba0bdc306c1b80534187251491ffdb17 Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 13 Sep 2020 11:15:28 -0400 Subject: Use comptime to expose public method to doc system --- lib/std/crypto/pbkdf2.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 424e2d6f62..809fff13ad 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -10,6 +10,11 @@ const debug = std.debug; const assert = debug.assert; const mem = std.mem; +// Exports +comptime { + _ = crypto.kdf.pbkdf2; +} + // RFC 2898 Section 5.2 // // FromSpec: @@ -155,7 +160,7 @@ test "RFC 6070 one iteration" { var derivedKey: [dkLen]u8 = undefined; - std.crypto.kdf.pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; -- cgit v1.2.3 From 85366771ea21a0dcd93e58b35738489d773590fc Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 13 Sep 2020 12:36:32 -0400 Subject: pbkdf2 offset into dk should be usize, not u64. --- lib/std/crypto/pbkdf2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 809fff13ad..2bbf0f15d8 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -130,7 +130,7 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: ctx.final(prevBlock[0..]); // Choose portion of DK to write into (T_n) and initialize - const offset: u64 = @as(u64, block) * hLen; + const offset: usize = @as(usize, block) * hLen; const blockLen = if (block != l - 1) hLen else r; var dkBlock = derivedKey[offset..(offset + blockLen)]; mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); -- cgit v1.2.3 From b6385870d0e65c5d7c6d7c6ef8c8ed33780b71e4 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Sun, 13 Sep 2020 22:39:54 +0300 Subject: Convert asserts to errors, make sure nothing overflows --- lib/std/crypto/pbkdf2.zig | 69 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 32 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 2bbf0f15d8..813535f3ff 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -5,15 +5,8 @@ // and substantial portions of the software. const std = @import("std"); -const crypto = std.crypto; -const debug = std.debug; -const assert = debug.assert; const mem = std.mem; - -// Exports -comptime { - _ = crypto.kdf.pbkdf2; -} +const maxInt = std.math.maxInt; // RFC 2898 Section 5.2 // @@ -48,8 +41,8 @@ comptime { /// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. /// /// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. -/// May be uninitialized. All bytes will be written. -/// Maximum size is (2^32 - 1) * Hash.digest_length +/// May be uninitialized. All bytes will be overwritten. +/// Maximum size is `maxInt(u32) * Hash.digest_length` /// It is a programming error to pass buffer longer than the maximum size. /// /// password: Arbitrary sequence of bytes of any length, including empty. @@ -60,29 +53,41 @@ comptime { /// Larger iteration counts improve security by increasing the time required to compute /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. /// -/// Prf: Pseudo-random function to use. A common choice is std.crypto.auth.hmac.HmacSha256. -pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) void { - assert(rounds >= 1); +/// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. +pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) !void { + if (rounds < 1) return error.TooFewRounds; - const dkLen: u64 = derivedKey.len; - const hLen: u32 = Prf.mac_length; // Force type to ensure multiplications can't overflow + const dkLen = derivedKey.len; + const hLen = Prf.mac_length; // FromSpec: // - // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and + // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and // stop. // - assert(dkLen > 0 and dkLen <= @as(u64, 1 << 32 - 1) * hLen); + if (comptime (maxInt(usize) < maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { + // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds + // This also asserts hLen >= 1 + return error.DerivedKeyTooLong; + } // FromSpec: // - // 2. Let l be the number of hLen-octet blocks in the derived key, - // rounding up, and let r be the number of octets in the last + // 2. Let l be the number of hLen-long blocks of bytes in the derived key, + // rounding up, and let r be the number of bytes in the last // block // - const l = (dkLen + hLen - 1) / hLen; - var r = dkLen % hLen; - r = if (r != 0) r else hLen; + + // l will not overflow, proof: + // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen` + // then `L^-1(l, hLen) = l*hLen - hLen + 1` + // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen + // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1 + // 3) -hLen <= -1 // multiply by -1 + // 4) hLen >= 1 + const r_ = dkLen % hLen; + const l = @intCast(u32, (dkLen / hLen) + if (r_ == 0) 0 else 1); // original: (dkLen + hLen - 1) / hLen + const r = if (r_ == 0) hLen else r_; // FromSpec: // @@ -116,7 +121,6 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: // produce a derived key DK: // // DK = T_1 || T_2 || ... || T_l<0..r-1> - var block: u32 = 0; // Spec limits to u32 while (block < l) : (block += 1) { var prevBlock: [hLen]u8 = undefined; @@ -130,9 +134,9 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: ctx.final(prevBlock[0..]); // Choose portion of DK to write into (T_n) and initialize - const offset: usize = @as(usize, block) * hLen; + const offset = block * hLen; const blockLen = if (block != l - 1) hLen else r; - var dkBlock = derivedKey[offset..(offset + blockLen)]; + const dkBlock: []u8 = derivedKey[offset..][0..blockLen]; mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); var i: u32 = 1; @@ -150,6 +154,7 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: } const htest = @import("test.zig"); +const HmacSha1 = std.crypto.auth.hmac.HmacSha1; // RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors test "RFC 6070 one iteration" { @@ -160,7 +165,7 @@ test "RFC 6070 one iteration" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; @@ -175,7 +180,7 @@ test "RFC 6070 two iterations" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; @@ -190,7 +195,7 @@ test "RFC 6070 4096 iterations" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "4b007901b765489abead49d926f721d065a429c1"; @@ -210,7 +215,7 @@ test "RFC 6070 16,777,216 iterations" { var derivedKey = [_]u8{0} ** dkLen; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; @@ -225,7 +230,7 @@ test "RFC 6070 multi-block salt and password" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; @@ -240,7 +245,7 @@ test "RFC 6070 embedded NUL" { var derivedKey: [dkLen]u8 = undefined; - pbkdf2(&derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(&derivedKey, p, s, c, ); const expected = "56fa6aa75548099dcc37d7f03425e0c3"; @@ -262,6 +267,6 @@ test "Very large dkLen" { std.testing.allocator.free(derivedKey); } - pbkdf2(derivedKey, p, s, c, crypto.auth.hmac.HmacSha1); + try pbkdf2(derivedKey, p, s, c, HmacSha1); // Just verify this doesn't crash with an overflow } -- cgit v1.2.3 From d75cbb01db4f7fb73c4382af4351dc0e393d5d39 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Sun, 13 Sep 2020 23:00:33 +0300 Subject: Reference all crypto declarations --- lib/std/crypto.zig | 25 +++++++++++++++++++++++-- lib/std/crypto/kdf.zig | 17 ----------------- 2 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 lib/std/crypto/kdf.zig (limited to 'lib/std') diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 64ec22894c..9df96dec7d 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -35,7 +35,14 @@ pub const onetimeauth = struct { pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; }; -pub const kdf = @import("crypto/kdf.zig"); +/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a +/// strong key, suitable for cryptographic uses. It does this by salting and stretching the +/// password. Salting injects non-secret random data, so that identical passwords will be converted +/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate +/// brute-force guessing. +pub const kdf = struct { + pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; +}; /// Core functions, that should rarely be used directly by applications. pub const core = struct { @@ -72,6 +79,20 @@ const std = @import("std.zig"); pub const randomBytes = std.os.getrandom; test "crypto" { + inline for (std.meta.declarations(std)) |decl| { + switch (decl.data) { + .Type => |t| { + std.meta.refAllDecls(t); + }, + .Var => |v| { + _ = v; + }, + .Fn => |f| { + _ = f; + }, + } + } + _ = @import("crypto/aes.zig"); _ = @import("crypto/blake2.zig"); _ = @import("crypto/blake3.zig"); @@ -79,7 +100,7 @@ test "crypto" { _ = @import("crypto/gimli.zig"); _ = @import("crypto/hmac.zig"); _ = @import("crypto/md5.zig"); - _ = @import("crypto/kdf.zig"); + _ = @import("crypto/pbkdf2.zig"); _ = @import("crypto/poly1305.zig"); _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); diff --git a/lib/std/crypto/kdf.zig b/lib/std/crypto/kdf.zig deleted file mode 100644 index 06bf67bbbd..0000000000 --- a/lib/std/crypto/kdf.zig +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. - -//! A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a -//! strong key, suitable for cryptographic uses. It does this by salting and stretching the -//! password. Salting injects non-secret random data, so that identical passwords will be converted -//! into unique keys. Stretching applies a deliberately slow hashing function to frustrate -//! brute-force guessing. - -pub const pbkdf2 = @import("pbkdf2.zig").pbkdf2; - -test "kdf" { - _ = @import("pbkdf2.zig"); -} -- cgit v1.2.3 From f6195be99770e3f9cc4e8a89bcf451a640bb4b95 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Sun, 13 Sep 2020 23:31:59 +0300 Subject: fix ref --- lib/std/crypto.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 9df96dec7d..3a1ae599a0 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -79,7 +79,7 @@ const std = @import("std.zig"); pub const randomBytes = std.os.getrandom; test "crypto" { - inline for (std.meta.declarations(std)) |decl| { + inline for (std.meta.declarations(@This())) |decl| { switch (decl.data) { .Type => |t| { std.meta.refAllDecls(t); -- cgit v1.2.3 From 73863cf72be02020886d9f8daf15754a8d3b2567 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Sun, 13 Sep 2020 23:59:36 +0300 Subject: fix build --- lib/std/crypto/pbkdf2.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 813535f3ff..8ff2b4f640 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -86,7 +86,7 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: // 3) -hLen <= -1 // multiply by -1 // 4) hLen >= 1 const r_ = dkLen % hLen; - const l = @intCast(u32, (dkLen / hLen) + if (r_ == 0) 0 else 1); // original: (dkLen + hLen - 1) / hLen + const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen const r = if (r_ == 0) hLen else r_; // FromSpec: @@ -245,7 +245,7 @@ test "RFC 6070 embedded NUL" { var derivedKey: [dkLen]u8 = undefined; - try pbkdf2(&derivedKey, p, s, c, ); + try pbkdf2(&derivedKey, p, s, c, HmacSha1); const expected = "56fa6aa75548099dcc37d7f03425e0c3"; -- cgit v1.2.3 From 988fc6f9d1419a63d7ecd3507966ba0b4c15eac5 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Mon, 14 Sep 2020 02:27:09 +0300 Subject: flip condition --- lib/std/crypto/pbkdf2.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 8ff2b4f640..1ba176ab9b 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -59,15 +59,15 @@ pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: const dkLen = derivedKey.len; const hLen = Prf.mac_length; + comptime std.debug.assert(hLen >= 1); // FromSpec: // // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and // stop. // - if (comptime (maxInt(usize) < maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { + if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds - // This also asserts hLen >= 1 return error.DerivedKeyTooLong; } -- cgit v1.2.3 From c35703825f17bb2b1108a371bb0559b89ff2b2a0 Mon Sep 17 00:00:00 2001 From: Rocknest <35231115+Rocknest@users.noreply.github.com> Date: Wed, 16 Sep 2020 01:58:48 +0300 Subject: Add an error set --- lib/std/crypto/pbkdf2.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 1ba176ab9b..85c8e01105 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -36,6 +36,14 @@ const maxInt = std.math.maxInt; // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. +pub const Pbkdf2Error = error{ + /// At least one round is required + TooFewRounds, + + /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length` + DerivedKeyTooLong, +}; + /// Apply PBKDF2 to generate a key from a password. /// /// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. @@ -54,7 +62,7 @@ const maxInt = std.math.maxInt; /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. /// /// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. -pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) !void { +pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void { if (rounds < 1) return error.TooFewRounds; const dkLen = derivedKey.len; -- cgit v1.2.3 From 281fc10ec5aa8052490b9f951e0ceed1b7008ff4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Sep 2020 02:24:36 -0700 Subject: std.crypto siphash: fix assertion on the size of output buffer the logic was backwards --- lib/std/crypto/siphash.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/crypto/siphash.zig b/lib/std/crypto/siphash.zig index 26c892fdd7..ae059b2567 100644 --- a/lib/std/crypto/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) } /// Return an authentication tag for the current state + /// Assumes `out` is less than or equal to `mac_length`. pub fn final(self: *Self, out: []u8) void { - std.debug.assert(out.len >= mac_length); + std.debug.assert(out.len <= mac_length); mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); } -- cgit v1.2.3 From bb9a4ad6e903ad2f9c137bca56bdf2dd6fc65d03 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 16 Sep 2020 13:45:54 +0200 Subject: std: Fix {*} printing of non-pointer types Fixes a regression introduced in #6246. Adds a test to make sure this won't happen again. --- lib/std/fmt.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 8d31733959..b18c9f2f44 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -327,7 +327,7 @@ pub fn formatType( max_depth: usize, ) @TypeOf(writer).Error!void { if (comptime std.mem.eql(u8, fmt, "*")) { - try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child)); + try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value)))); try writer.writeAll("@"); try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); return; @@ -1246,6 +1246,10 @@ test "optional" { const value: ?i32 = null; try testFmt("optional: null\n", "optional: {}\n", .{value}); } + { + const value = @intToPtr(?*i32, 0xf000d000); + try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); + } } test "error" { -- cgit v1.2.3 From 27adb82fda516e95c3c03df80a95b895969fdd56 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 17 Sep 2020 00:41:26 +0200 Subject: std: Respect user-specified alignment when formatting ints This implementation tries to do the right thing (TM) by treating the sign as part of the number itself, therefore the alignment parameter applies to both the sign and the digits. In other words the format string `{:>4}` with -1 as input will not output `- 1` but ` -1`. And let's default to right alignment for everything as that's what users want, especially when printing numbers. Many implementations use different defaults for numeric vs non-numeric types, let's strive for a consistent behaviour here. --- lib/std/fmt.zig | 133 ++++++++++++++++++++++---------------------------------- 1 file changed, 51 insertions(+), 82 deletions(-) (limited to 'lib/std') diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index b18c9f2f44..56a1aba217 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -22,7 +22,7 @@ pub const Alignment = enum { pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, - alignment: Alignment = .Left, + alignment: Alignment = .Right, fill: u8 = ' ', }; @@ -631,26 +631,22 @@ pub fn formatBuf( writer: anytype, ) !void { const width = options.width orelse buf.len; - var padding = if (width > buf.len) (width - buf.len) else 0; - const pad_byte = [1]u8{options.fill}; + const padding = if (width > buf.len) (width - buf.len) else 0; + switch (options.alignment) { .Left => { try writer.writeAll(buf); - while (padding > 0) : (padding -= 1) { - try writer.writeAll(&pad_byte); - } + try writer.writeByteNTimes(options.fill, padding); }, .Center => { - const padl = padding / 2; - var i: usize = 0; - while (i < padl) : (i += 1) try writer.writeAll(&pad_byte); + const left_padding = padding / 2; + const right_padding = (padding + 1) / 2; + try writer.writeByteNTimes(options.fill, left_padding); try writer.writeAll(buf); - while (i < padding) : (i += 1) try writer.writeAll(&pad_byte); + try writer.writeByteNTimes(options.fill, right_padding); }, .Right => { - while (padding > 0) : (padding -= 1) { - try writer.writeAll(&pad_byte); - } + try writer.writeByteNTimes(options.fill, padding); try writer.writeAll(buf); }, } @@ -941,61 +937,27 @@ pub fn formatInt( options: FormatOptions, writer: anytype, ) !void { + assert(base >= 2); + const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); } else value; - if (@typeInfo(@TypeOf(int_value)).Int.is_signed) { - return formatIntSigned(int_value, base, uppercase, options, writer); - } else { - return formatIntUnsigned(int_value, base, uppercase, options, writer); - } -} + const value_info = @typeInfo(@TypeOf(int_value)).Int; -fn formatIntSigned( - value: anytype, - base: u8, - uppercase: bool, - options: FormatOptions, - writer: anytype, -) !void { - const new_options = FormatOptions{ - .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, - .precision = options.precision, - .fill = options.fill, - }; - const bit_count = @typeInfo(@TypeOf(value)).Int.bits; - const Uint = std.meta.Int(false, bit_count); - if (value < 0) { - try writer.writeAll("-"); - const new_value = math.absCast(value); - return formatIntUnsigned(new_value, base, uppercase, new_options, writer); - } else if (options.width == null or options.width.? == 0) { - return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer); - } else { - try writer.writeAll("+"); - const new_value = @intCast(Uint, value); - return formatIntUnsigned(new_value, base, uppercase, new_options, writer); - } -} + // The type must have the same size as `base` or be wider in order for the + // division to work + const min_int_bits = comptime math.max(value_info.bits, 8); + const MinInt = std.meta.Int(false, min_int_bits); -fn formatIntUnsigned( - value: anytype, - base: u8, - uppercase: bool, - options: FormatOptions, - writer: anytype, -) !void { - assert(base >= 2); - const value_info = @typeInfo(@TypeOf(value)).Int; - var buf: [math.max(value_info.bits, 1)]u8 = undefined; - const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits); - const MinInt = std.meta.Int(value_info.is_signed, min_int_bits); - var a: MinInt = value; - var index: usize = buf.len; + const abs_value = math.absCast(int_value); + // The worst case in terms of space needed is base 2, plus 1 for the sign + var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined; + var a: MinInt = abs_value; + var index: usize = buf.len; while (true) { const digit = a % base; index -= 1; @@ -1004,25 +966,21 @@ fn formatIntUnsigned( if (a == 0) break; } - const digits_buf = buf[index..]; - const width = options.width orelse 0; - const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; - - if (padding > index) { - const zero_byte: u8 = options.fill; - var leftover_padding = padding - index; - while (true) { - try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]); - leftover_padding -= 1; - if (leftover_padding == 0) break; + if (value_info.is_signed) { + if (value < 0) { + // Negative integer + index -= 1; + buf[index] = '-'; + } else if (options.width == null or options.width.? == 0) { + // Positive integer, omit the plus sign + } else { + // Positive integer + index -= 1; + buf[index] = '+'; } - mem.set(u8, buf[0..index], options.fill); - return writer.writeAll(&buf); - } else { - const padded_buf = buf[index - padding ..]; - mem.set(u8, padded_buf[0..padding], options.fill); - return writer.writeAll(padded_buf); } + + return formatBuf(buf[index..], options, writer); } pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize { @@ -1287,7 +1245,17 @@ test "int.specifier" { test "int.padded" { try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); - try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)}); + try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); + try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); + try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); + try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); + try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); + try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); + try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); + try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); + try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); + try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); + try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); } test "buffer" { @@ -1333,7 +1301,7 @@ test "slice" { try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); } - try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"}); + try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); } @@ -1366,7 +1334,7 @@ test "cstr" { .{@ptrCast([*c]const u8, "Test C")}, ); try testFmt( - "cstr: Test C \n", + "cstr: Test C\n", "cstr: {s:10}\n", .{@ptrCast([*c]const u8, "Test C")}, ); @@ -1809,7 +1777,7 @@ test "vector" { try testFmt("{ true, false, true, false }", "{}", .{vbool}); try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); - try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64}); + try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); @@ -1822,15 +1790,16 @@ test "enum-literal" { test "padding" { try testFmt("Simple", "{}", .{"Simple"}); - try testFmt("true ", "{:10}", .{true}); + try testFmt(" true", "{:10}", .{true}); try testFmt(" true", "{:>10}", .{true}); try testFmt("======true", "{:=>10}", .{true}); try testFmt("true======", "{:=<10}", .{true}); try testFmt(" true ", "{:^10}", .{true}); try testFmt("===true===", "{:=^10}", .{true}); - try testFmt("Minimum width", "{:18} width", .{"Minimum"}); + try testFmt(" Minimum width", "{:18} width", .{"Minimum"}); try testFmt("==================Filled", "{:=>24}", .{"Filled"}); try testFmt(" Centered ", "{:^24}", .{"Centered"}); + try testFmt("-", "{:-^1}", .{""}); } test "decimal float padding" { -- cgit v1.2.3 From 5e3fa0e94f947c632aa584b9e13bfa2fe241fae1 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 16 Sep 2020 20:59:45 -0700 Subject: Add rename to std.fs API - Moves fs.rename functions to fs.renameAbsolute to match other functions outside of fs.Dir - Adds fs.Dir.rename that takes two paths relative to the given Dir - Adds fs.rename that takes two separate Dir's that the given paths are relative to (for renaming across directories without having to make the second path relative to a single directory) - Fixes FileNotFound error return in std.os.windows.MoveFileExW - Returns error.RenameAcrossMountPoints from renameatW + Matches the RenameAcrossMountPoints error return in renameatWasi/renameatZ --- lib/std/fs.zig | 66 ++++++++++++++++++-- lib/std/fs/test.zig | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/std/os.zig | 3 +- lib/std/os/windows.zig | 3 +- 4 files changed, 226 insertions(+), 7 deletions(-) (limited to 'lib/std') diff --git a/lib/std/fs.zig b/lib/std/fs.zig index a217fb3e9b..1890d7e136 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths -pub const rename = os.rename; -pub const renameZ = os.renameZ; -pub const renameC = @compileError("deprecated: renamed to renameZ"); -pub const renameW = os.renameW; pub const realpath = os.realpath; pub const realpathZ = os.realpathZ; pub const realpathC = @compileError("deprecated: renamed to realpathZ"); @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); if (cwd().symLink(existing_path, tmp_path, .{})) { - return rename(tmp_path, new_path); + return cwd().rename(tmp_path, new_path); } else |err| switch (err) { error.PathAlreadyExists => continue, else => return err, // TODO zig should know this set does not include PathAlreadyExists @@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { return os.rmdirW(dir_path); } +pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ"); + +/// Same as `Dir.rename` except the paths are absolute. +pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { + assert(path.isAbsolute(old_path)); + assert(path.isAbsolute(new_path)); + return os.rename(old_path, new_path); +} + +/// Same as `renameAbsolute` except the path parameters are null-terminated. +pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { + assert(path.isAbsoluteZ(old_path)); + assert(path.isAbsoluteZ(new_path)); + return os.renameZ(old_path, new_path); +} + +/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows. +pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void { + assert(path.isAbsoluteWindowsW(old_path)); + assert(path.isAbsoluteWindowsW(new_path)); + return os.renameW(old_path, new_path); +} + +/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` +pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { + return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); +} + +/// Same as `rename` except the parameters are null-terminated. +pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { + return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); +} + +/// Same as `rename` except the parameters are UTF16LE, NT prefixed. +/// This function is Windows-only. +pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { + return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); +} + pub const Dir = struct { fd: os.fd_t, @@ -1338,6 +1373,27 @@ pub const Dir = struct { }; } + pub const RenameError = os.RenameError; + + /// Change the name or location of a file or directory. + /// If new_sub_path already exists, it will be replaced. + /// Renaming a file over an existing directory or a directory + /// over an existing file will fail with `error.IsDir` or `error.NotDir` + pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { + return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path); + } + + /// Same as `rename` except the parameters are null-terminated. + pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void { + return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); + } + + /// Same as `rename` except the parameters are UTF16LE, NT prefixed. + /// This function is Windows-only. + pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { + return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w); + } + /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a59bc46245..b3cc1fe569 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -274,6 +274,167 @@ test "file operations on directories" { dir.close(); } +test "Dir.rename files" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else")); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try tmp_dir.dir.rename(test_file_name, renamed_test_file_name); + + // Ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); + file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); + file.close(); + + // Rename to self succeeds + try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name); + + // Rename to existing file succeeds + var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true }); + existing_file.close(); + try tmp_dir.dir.rename(renamed_test_file_name, "existing_file"); + + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{})); + file = try tmp_dir.dir.openFile("existing_file", .{}); + file.close(); +} + +test "Dir.rename directories" { + // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + // Renaming directories + try tmp_dir.dir.makeDir("test_dir"); + try tmp_dir.dir.rename("test_dir", "test_dir_renamed"); + + // Ensure the directory was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); + var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{}); + + // Put a file in the directory + var file = try dir.createFile("test_file", .{ .read = true }); + file.close(); + dir.close(); + + try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again"); + + // Ensure the directory was renamed and the file still exists in it + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{})); + dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); + file = try dir.openFile("test_file", .{}); + file.close(); + dir.close(); + + // Try to rename to a non-empty directory now + var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{}); + file = try target_dir.createFile("filler", .{ .read = true }); + file.close(); + + testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir")); + + // Ensure the directory was not renamed + dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); + file = try dir.openFile("test_file", .{}); + file.close(); + dir.close(); +} + +test "Dir.rename file <-> dir" { + // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); + file.close(); + try tmp_dir.dir.makeDir("test_dir"); + testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir")); + testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file")); +} + +test "rename" { + var tmp_dir1 = tmpDir(.{}); + defer tmp_dir1.cleanup(); + + var tmp_dir2 = tmpDir(.{}); + defer tmp_dir2.cleanup(); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); + + // ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); + file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); + file.close(); +} + +test "renameAbsolute" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + // Get base abs path + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = &arena.allocator; + + const base_path = blk: { + const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] }); + break :blk try fs.realpathAlloc(&arena.allocator, relative_path); + }; + + testing.expectError(error.FileNotFound, fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }), + try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }), + )); + + // Renaming files + const test_file_name = "test_file"; + const renamed_test_file_name = "test_file_renamed"; + var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + try fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }), + try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }), + ); + + // ensure the file was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); + file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); + const stat = try file.stat(); + testing.expect(stat.kind == .File); + file.close(); + + // Renaming directories + const test_dir_name = "test_dir"; + const renamed_test_dir_name = "test_dir_renamed"; + try tmp_dir.dir.makeDir(test_dir_name); + try fs.renameAbsolute( + try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }), + try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }), + ); + + // ensure the directory was renamed + testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); + var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); + dir.close(); +} + test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; diff --git a/lib/std/os.zig b/lib/std/os.zig index bdc746419a..7fc63d7853 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); } -const RenameError = error{ +pub const RenameError = error{ /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to rename a resource by path relative to it. AccessDenied, @@ -2107,6 +2107,7 @@ pub fn renameatW( .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_SAME_DEVICE => error.RenameAcrossMountPoints, else => return windows.unexpectedStatus(rc), } } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index bd9dc8b32e..de0d0ea45f 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -828,7 +828,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil } } -pub const MoveFileError = error{Unexpected}; +pub const MoveFileError = error{ FileNotFound, Unexpected }; pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { const old_path_w = try sliceToPrefixedFileW(old_path); @@ -839,6 +839,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { switch (kernel32.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, else => |err| return unexpectedError(err), } } -- cgit v1.2.3 From fbde15fdf41024d977401ab2a5c52e60b67d48fa Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 17 Sep 2020 15:47:09 -0700 Subject: Fix compile error in os.renameatW Introduced in 5e3fa0e94f947c632aa584b9e13bfa2fe241fae1 Whoops! --- lib/std/os.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/os.zig b/lib/std/os.zig index 7fc63d7853..0b09b1f82a 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2107,7 +2107,7 @@ pub fn renameatW( .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => error.RenameAcrossMountPoints, + .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, else => return windows.unexpectedStatus(rc), } } -- cgit v1.2.3 From 4fbf9f7f79c8e0df525f9932878995e6a6782ac4 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 18 Sep 2020 18:58:54 +0200 Subject: Add "emit_docs" field to LibExeObjStep. --- lib/std/build.zig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/std') diff --git a/lib/std/build.zig b/lib/std/build.zig index 30ecf98b76..69f44bad32 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct { emit_llvm_ir: bool = false, emit_asm: bool = false, emit_bin: bool = true, + emit_docs: bool = false, emit_h: bool = false, bundle_compiler_rt: bool, disable_stack_probing: bool, @@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct { if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir"); if (self.emit_asm) try zig_args.append("-femit-asm"); if (!self.emit_bin) try zig_args.append("-fno-emit-bin"); + if (self.emit_docs) try zig_args.append("-femit-docs"); if (self.emit_h) try zig_args.append("-femit-h"); if (self.strip) { -- cgit v1.2.3 From 58ee5f4e61cd9b7a9ba65798e2214efa3753a733 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 20 Sep 2020 00:10:53 +0200 Subject: std: Fix metadata corruption in HeapAllocator HeapAllocator stores the pointer returned by HeapAlloc right after the data block and, after the recent allocator refactoring, the space for this pointer was not taken into account in the calculation of the final block size. Fixes #5830 --- lib/std/heap.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/std') diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 6db1be539c..16de215cc2 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); assert(full_len != std.math.maxInt(usize)); assert(full_len >= amt); - break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align); + break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align); }; const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; getRecordPtr(buf).* = root_addr; -- cgit v1.2.3