This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
5
2
[PATCH himitsu v2 1/5] move encryption into separate fn
---
v2: Minor update on error handling in secstore
secstore/secstore.ha | 88 ++++++++++++++++++++++++++-----------------
secstore/serialize.ha | 28 ++++----------
2 files changed, 62 insertions(+), 54 deletions(-)
diff --git a/secstore/secstore.ha b/secstore/secstore.ha
index 2c82d57..d4ca53a 100644
--- a/secstore/secstore.ha
+++ b/secstore/secstore.ha
@@ -69,6 +69,52 @@ export fn create(passphrase: []u8) (secstore | error) = {
};
};
+// Encrypts msg and writes it to sink
+fn secbox(
+ sink: io::handle,
+ key: (keystore::key | []u8),
+ msg: []u8
+) (void | io::error) = {
+ let keybuf: [32]u8 = [0...];
+ defer bytes::zero(keybuf);
+ match (key) {
+ case let key: keystore::key =>
+ keystore::read(key, keybuf);
+ case let key: []u8 =>
+ keybuf[..] = key[..];
+ };
+
+ let nonce: crypto::nonce = [0...];
+ random::buffer(nonce);
+
+ const box = crypto::encrypt(&keybuf, &nonce, msg);
+
+ io::write(sink, box.0)?;
+ io::write(sink, box.1)?;
+ io::write(sink, box.2)?;
+};
+
+// Reads the ciphertext created with [[secbox]] from 'src' and returns the
+// plaintext. The user must free the plaintext after use.
+fn secunbox(src: io::handle, key: (keystore::key | []u8)) ([]u8 | io::error) = {
+ let keybuf: [32]u8 = [0...];
+ defer bytes::zero(keybuf);
+ match (key) {
+ case let key: keystore::key =>
+ keystore::read(key, keybuf);
+ case let key: []u8 =>
+ keybuf[..] = key[..];
+ };
+
+ let mac: crypto::mac = [0...];
+ let nonce: crypto::nonce = [0...];
+ io::readall(src, mac)?;
+ io::readall(src, nonce)?;
+ let ciphertext = io::drain(src)?;
+ let box: crypto::box = (mac, nonce, ciphertext);
+ return crypto::decrypt(&keybuf, &box)?;
+};
+
// Opens the secstore. The caller should call [[unlock]] to decrypt it, and
// [[close]] when they're done with it.
export fn open() (secstore | error) = {
@@ -242,18 +288,12 @@ fn add_secret(store: *secstore, val: str) uuid::uuid = {
const file = os::create(path::string(&buf), 0o600)!;
defer io::close(file)!;
- let key: [32]u8 = [0...];
- defer bytes::zero(key);
- keystore::read(store.key as keystore::key, key);
-
- let nonce: crypto::nonce = [0...];
- random::buffer(nonce);
- let ciphertext = strings::toutf8(strings::dup(val));
- defer free(ciphertext);
- const box = crypto::encrypt(&key, &nonce, ciphertext);
- io::write(file, box.0)!;
- io::write(file, box.1)!;
- io::write(file, box.2)!;
+ let plain = strings::toutf8(strings::dup(val));
+ defer {
+ bytes::zero(plain);
+ free(plain);
+ };
+ secbox(file, store.key as keystore::key, plain)!;
return id;
};
@@ -284,18 +324,8 @@ fn add_index(store: *secstore, entry: *entry) void = {
const buf = bufio::buffer(&buf);
- let key: [32]u8 = [0...];
- defer bytes::zero(key);
- keystore::read(store.key as keystore::key, key);
-
- let nonce: crypto::nonce = [0...];
- random::buffer(nonce);
-
- const box = crypto::encrypt(&key, &nonce, buf);
const enc = base64::newencoder(&base64::std_encoding, store.index);
- io::write(&enc, box.0)!;
- io::write(&enc, box.1)!;
- io::write(&enc, box.2)!;
+ secbox(&enc, store.key as keystore::key, buf)!;
io::close(&enc)!;
fmt::fprintln(store.index)!;
};
@@ -320,18 +350,8 @@ fn load_index(store: *secstore) (void | io::error | errors::invalid) = {
const buf = bufio::fixed(line, io::mode::READ);
const dec = base64::newdecoder(&base64::std_encoding, &buf);
- let mac: crypto::mac = [0...];
- let nonce: crypto::nonce = [0...];
- io::readall(&dec, mac)?;
- io::readall(&dec, nonce)?;
- let ciphertext = io::drain(&dec)?;
- defer {
- bytes::zero(ciphertext);
- free(ciphertext);
- };
+ const plaintext = secunbox(&dec, key)?;
- let box: crypto::box = (mac, nonce, ciphertext);
- const plaintext = crypto::decrypt(&key, &box)?;
const buf = bufio::fixed(plaintext, io::mode::READ);
const q = query::parse(&buf)!;
defer query::finish(&q);
diff --git a/secstore/serialize.ha b/secstore/serialize.ha
index 1e1a3c8..7523189 100644
--- a/secstore/serialize.ha
+++ b/secstore/serialize.ha
@@ -67,28 +67,16 @@ fn write_private(
const file = os::open(path::string(&buf))?;
defer io::close(file)!;
- let mac: crypto::mac = [0...];
- let nonce: crypto::nonce = [0...];
- io::readall(file, mac)?;
- io::readall(file, nonce)?;
- let ciphertext = io::drain(file)?;
- defer {
- bytes::zero(ciphertext);
- free(ciphertext);
- };
-
- let key: [32]u8 = [0...];
- defer bytes::zero(key);
- keystore::read(store.key as keystore::key, key);
-
- let box: crypto::box = (mac, nonce, ciphertext);
- const plaintext = match (crypto::decrypt(&key, &box)) {
- case errors::invalid =>
+ let plaintext = match (secunbox(file, store.key as keystore::key)) {
+ case let b: []u8 =>
+ yield b;
+ case =>
return badstore;
- case let buf: []u8 =>
- yield buf;
};
- defer bytes::zero(plaintext);
+ defer {
+ bytes::zero(plaintext);
+ free(plaintext);
+ };
io::writeall(sink, plaintext)?;
};
--
2.40.0
[PATCH himitsu v2 2/5] secstore: encrypted master key
---
secstore/secstore.ha | 89 +++++++++++++++++++++++++++++++++++++-------
1 file changed, 76 insertions(+), 13 deletions(-)
diff --git a/secstore/secstore.ha b/secstore/secstore.ha
index d4ca53a..d01eb0f 100644
--- a/secstore/secstore.ha
+++ b/secstore/secstore.ha
@@ -21,6 +21,14 @@ use uuid;
def KEY_MEM: u32 = 100000;
def KEY_PASSES: u32 = 0;
+type keyversion = enum u8 {
+ // Buggy argon2 key
+ V0 = 0,
+
+ // argon2 key encrypted master key
+ V1 = 1,
+};
+
// Initializes a new secstore using the provided passphrase. The caller should
// call [[close]] when they're done with it.
export fn create(passphrase: []u8) (secstore | error) = {
@@ -29,7 +37,6 @@ export fn create(passphrase: []u8) (secstore | error) = {
let salt: [16]u8 = [0...];
random::buffer(salt);
- // TODO: Switch to argon2id by default
const config = argon2::config {
mem = KEY_MEM,
parallel = 1,
@@ -37,10 +44,9 @@ export fn create(passphrase: []u8) (secstore | error) = {
version = argon2::VERSION,
...
};
- argon2::argon2i(key, passphrase, salt, &config)!;
+ argon2::argon2id(key, passphrase, salt, &config)!;
const verify = key[32..];
- const key = keystore::newkey(key[..32], "secstore")!;
const dir = strings::dup(dirs::data("himitsu"));
let buf = path::init();
@@ -51,17 +57,29 @@ export fn create(passphrase: []u8) (secstore | error) = {
const keyfile = &bufio::buffered(keyfile, [], wbuf);
defer io::close(keyfile)!;
- fmt::fprintf(keyfile, "argon2i:{}:{}:", KEY_MEM, KEY_PASSES)?;
+ fmt::fprintf(keyfile, "argon2id:{}:{}:", KEY_MEM, KEY_PASSES)?;
base64::encode(keyfile, &base64::std_encoding, salt)?;
fmt::fprintf(keyfile, ":")?;
base64::encode(keyfile, &base64::std_encoding, verify)?;
- fmt::fprintln(keyfile)?;
+ fmt::fprintf(keyfile, ":{}:", keyversion::V1: u8)?;
+
+ let masterkeybuf: [32]u8 = [0...];
+ random::buffer(masterkeybuf);
+ defer bytes::zero(masterkeybuf);
+ let masterkey = keystore::newkey(masterkeybuf, "secstore")!;
+
+ let mkkey = keystore::newkey(key[..32], "masterkeykey")!;
+ defer keystore::destroy(mkkey);
+
+ let enc = base64::newencoder(&base64::std_encoding, keyfile);
+ secbox(&enc, mkkey, masterkeybuf)!;
+ io::close(&enc)!;
path::set(&buf, dir, "index")!;
const index = os::create(path::string(&buf), 0o600)?;
return secstore {
- key = key,
+ key = masterkey,
state = state::UNLOCKED,
dir = dir,
index = index,
@@ -151,13 +169,10 @@ export fn unlock(store: *secstore, passphrase: []u8) (void | error) = {
defer free(keydata);
const items = strings::split(keydata, ":");
defer free(items);
- if (len(items) != 5) {
+ if (len(items) != 5 && len(items) != 7) {
return badstore;
};
- if (items[0] != "argon2i") {
- return badstore;
- };
const mem = match (strconv::stou32(items[1])) {
case let u: u32 =>
yield u;
@@ -191,10 +206,23 @@ export fn unlock(store: *secstore, passphrase: []u8) (void | error) = {
return badstore;
};
+ let kv = if (len(items) == 5) {
+ yield keyversion::V0;
+ } else {
+ yield match (strconv::stou8(items[5])) {
+ case let v: u8 =>
+ if (v > keyversion::V1) {
+ return badstore;
+ };
+ yield v: keyversion;
+ case =>
+ return badstore;
+ };
+ };
+
let key: [32 + 16]u8 = [0...];
defer bytes::zero(key);
- // TODO: Switch to argon2id by default
const config = argon2::config {
mem = mem,
parallel = 1,
@@ -202,13 +230,28 @@ export fn unlock(store: *secstore, passphrase: []u8) (void | error) = {
version = argon2::VERSION,
...
};
- argon2::argon2i(key, passphrase, salt, &config)!;
+ switch (items[0]) {
+ case "argon2i" =>
+ argon2::argon2i(key, passphrase, salt, &config)!;
+ case "argon2id" =>
+ argon2::argon2id(key, passphrase, salt, &config)!;
+ case =>
+ return badstore;
+ };
if (!crypto::compare(key[32..], verify)) {
return badpass;
};
- store.key = keystore::newkey(key[..32], "secstore")!;
+ store.key = switch (kv) {
+ case keyversion::V0 =>
+ yield loadv0key(key[..32]);
+ case keyversion::V1 =>
+ yield loadv1key(key[..32], items[6])?;
+ case =>
+ abort();
+ };
+
store.state = state::UNLOCKED;
match (load_index(store)) {
case void => yield;
@@ -219,6 +262,26 @@ export fn unlock(store: *secstore, passphrase: []u8) (void | error) = {
};
};
+fn loadv0key(key: []u8) keystore::key =
+ keystore::newkey(key[..32], "secstore")!;
+
+fn loadv1key(key: []u8, enckey: str) (keystore::key | error) = {
+ const buf = bufio::fixed(strings::toutf8(enckey), io::mode::READ);
+ const dec = base64::newdecoder(&base64::std_encoding, &buf);
+ let masterkey = match (secunbox(&dec, key)) {
+ case let b: []u8 =>
+ yield b;
+ case =>
+ return badstore;
+ };
+ defer {
+ bytes::zero(masterkey);
+ free(masterkey);
+ };
+
+ return keystore::newkey(masterkey, "secstore")!;
+};
+
// Locks the key store, unloading the decryption keys.
export fn lock(store: *secstore) void = {
match (store.key) {
--
2.40.0
[PATCH himitsu v2 3/5] secstore: vendor buggy argon2 implementation
---
crypto/argon2bug/+test.ha | 131 ++++++++++
crypto/argon2bug/README | 30 +++
crypto/argon2bug/argon2.ha | 508 +++++++++++++++++++++++++++++++++++++
secstore/secstore.ha | 7 +-
4 files changed, 675 insertions(+), 1 deletion(-)
create mode 100644 crypto/argon2bug/+test.ha
create mode 100644 crypto/argon2bug/README
create mode 100644 crypto/argon2bug/argon2.ha
diff --git a/crypto/argon2bug/+test.ha b/crypto/argon2bug/+test.ha
new file mode 100644
index 0000000..e9038e4
--- /dev/null
+++ b/crypto/argon2bug/+test.ha
@@ -0,0 +1,131 @@
+// License: MPL-2.0
+// (c) 2021-2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2022 Drew DeVault <sir@cmpwn.com>
+use bytes;
+
+@test fn mode_d_one_pass() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let expected: [_]u8 = [
+ 0xfa, 0x17, 0x75, 0xca, 0x80, 0x90, 0x64, 0x66, 0x18, 0xbe,
+ 0x70, 0xeb, 0x0f, 0xc9, 0xde, 0x43, 0x67, 0x58, 0xed, 0x0c,
+ 0xa5, 0x36, 0x83, 0x1a, 0xe9, 0xe1, 0x03, 0x48, 0x93, 0x81,
+ 0xc1, 0x79,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 32,
+ ...
+ };
+
+ argon2d(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+@test fn rfc_d_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97, 0x53, 0x71,
+ 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94, 0xf8, 0x68, 0xe3, 0xbe,
+ 0x39, 0x84, 0xf3, 0xc1, 0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe,
+ 0x4a, 0xcb,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2d(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+
+@test fn rfc_i_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa, 0x13, 0xf0,
+ 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1, 0xc8, 0xde, 0x6b, 0x01,
+ 0x6d, 0xd3, 0x88, 0xd2, 0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b,
+ 0x6c, 0xe8,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2i(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+@test fn rfc_id_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, 0x08, 0xc0,
+ 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9, 0xd0, 0x1e, 0xf0, 0x45,
+ 0x2d, 0x75, 0xb6, 0x5e, 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01,
+ 0xe6, 0x59,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2id(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
diff --git a/crypto/argon2bug/README b/crypto/argon2bug/README
new file mode 100644
index 0000000..b06abdf
--- /dev/null
+++ b/crypto/argon2bug/README
@@ -0,0 +1,30 @@
+This module provides an implementation of the argon2 key derivation function as
+described by RFC 9106. This is the recommended algorithm for password hashing in
+Hare programs, and for deriving keys for use with other cryptographic
+algorithms. Some thought must be given to the appropriate configuration for your
+use case. Some general advice is provided here; if in doubt, consult the RFC.
+
+The argon2 parameters are configured via the [[config]] structure. To determine
+the appropriate configuration parameters for a particular use-case, consult
+section 4 of the RFC. Otherwise, sane defaults for common scenarios are provided
+via [[default_config]] and [[low_mem_config]]; consult the docs of each
+configuration for details.
+
+Once a suitable configuration has been selected, the user must provide a salt.
+This salt should be stored alongside the hash, should be unique for each
+password, and should be random: see [[crypto::random]]. The salt and hash
+lengths are configurable, the recommended defaults are 16 and 32 bytes
+respectively.
+
+Equipped with the necessary parameters, the user may call the appropriate argon2
+variant via [[argon2d]], [[argon2i]], or [[argon2id]]. If unsure which to use,
+choose [[argon2id]]. The RFC is the authoratative source on the appropriate
+argon2 variant and configuration parameters for your use-case.
+
+This is a low-level module which implements cryptographic primitives. Direct use
+of cryptographic primitives is not recommended for non-experts, as incorrect use
+of these primitives can easily lead to the introduction of security
+vulnerabilities. Non-experts are advised to use the high-level operations
+available in the top-level [[crypto]] module.
+
+Be advised that Hare's cryptography implementations have not been audited.
diff --git a/crypto/argon2bug/argon2.ha b/crypto/argon2bug/argon2.ha
new file mode 100644
index 0000000..558e161
--- /dev/null
+++ b/crypto/argon2bug/argon2.ha
@@ -0,0 +1,508 @@
+// License: MPL-2.0
+// (c) 2022 Alexey Yerin <yyp@disroot.org>
+// (c) 2021-2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2021-2022 Drew DeVault <sir@cmpwn.com>
+use bufio;
+use bytes;
+use crypto::blake2b;
+use crypto::math;
+use endian;
+use errors::{nomem};
+use hash;
+use io;
+use types;
+
+// Latest version of argon2 supported by this implementation (1.3).
+export def VERSION: u8 = 0x13;
+
+// Number of u64 elements of one block.
+export def BLOCKSIZE: u32 = 128;
+
+def SLICES: size = 4;
+
+type block64 = [BLOCKSIZE]u64;
+
+const zeroblock: block64 = [0...];
+
+type mode = enum {
+ D = 0,
+ I = 1,
+ ID = 2,
+};
+
+// This type provides configuration options for the argon2 algorithm. Most users
+// will find [[default_config]] or [[low_mem_config]] suitable for their needs
+// without providing a custom configuration. If writing a custom configuration,
+// consult the RFC for advice on selecting suitable values for your use-case.
+//
+// 'parallel' specifies the number of parallel processes. 'pass' configures the
+// number of iterations. Both values must be at least one. Note: the Hare
+// implementation of argon2 does not process hashes in parallel, though it will
+// still compute the correct hash if this value is greater than one.
+//
+// 'version' specifies the version of the argon2 function. The implementation
+// currently only supports version 1.3. Use [[VERSION]] here.
+//
+// 'secret' and 'data' are optional byte arrays that are applied to the initial
+// state. Consult the RFC for details.
+//
+// The 'mem' parameter is used to configure working memory used during the
+// computation. The argon2 algorithm requires a large amount of memory to
+// compute hashes. If 'mem' set to a u32, it is interpreted as the desired
+// number of 1024-byte blocks the implementation shall allocate for you. If the
+// caller wants to manage the allocation itself, provide a []u8 instead. The
+// length of this slice must be at least 8 times the value of 'parallel' in
+// blocks, and must be a multiple of [[BLOCKSIZE]]. To have the implementation
+// allocate 64 KiB, set 'mem' to 64. To use the same amount of caller-provided
+// memory, provide a slice of length 64 * [[BLOCKSIZE]].
+export type config = struct {
+ mem: (u32 | []u64),
+ parallel: u32,
+ passes: u32,
+ version: u8,
+ secret: []u8,
+ data: []u8
+};
+
+// The default recommended configuration for most use cases. This configuration
+// uses 2 GiB of working memory. A 16-byte 'salt' and 32-byte 'dest' parameter
+// is recommended in combination with this configuration.
+export const default_config: config = config {
+ mem = 2 * 1024 * 1024,
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ ...
+};
+
+// The default recommended configuration for memory-constrained use cases. This
+// configuration uses 64 MiB of working memory. A 16-byte 'salt' and 32-byte
+// 'dest' parameter is recommended in combination with this configuration.
+export const low_mem_config: config = config {
+ mem = 64 * 1024,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ ...
+};
+
+type context = struct {
+ mode: mode,
+ cols: size,
+ rows: size,
+ sliceblocks: size,
+ mem: []u64,
+ pass: u32,
+ seedsinit: block64,
+ seedblock: block64,
+};
+
+// Computes an argon2d hash, writing the digest to 'dest'. A 'salt' length of 16
+// bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32 bytes
+// is recommended, and 4 bytes is the minimum.
+//
+// The argon2d mode uses data-dependent memory access and is suitable for
+// applications with no threats of side-channel timing attacks.
+export fn argon2d(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::D);
+};
+
+// Computes an argon2i hash, writing the digest to 'dest'. A 'salt' length of 16
+// bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32 bytes
+// is recommended, and 4 bytes is the minimum.
+//
+// The argon2i mode uses data-independent memory access and is suitable for
+// password hashing and key derivation. It makes more passes over memory to
+// protect from trade-off attacks.
+export fn argon2i(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::I);
+};
+
+// Computes an argon2id hash, writing the digest to 'dest'. A 'salt' length of
+// 16 bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32
+// bytes is recommended, and 4 bytes is the minimum.
+//
+// The argon2id mode works by using argon2i for the first half of the first pass
+// and argon2d further on. It provides therefore protection from side-channel
+// attacks and brute-force cost savings due to memory trade-offs.
+//
+// If you are unsure which variant to use, argon2id is recommended.
+export fn argon2id(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::ID);
+};
+
+fn argon2(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+ mode: mode,
+) (void | nomem) = {
+ assert(endian::host == &endian::little, "TODO big endian support");
+
+ assert(len(dest) >= 4 && len(dest) <= types::U32_MAX);
+ assert(len(password) <= types::U32_MAX);
+ assert(len(salt) >= 8 && len(salt) <= types::U32_MAX);
+ assert(cfg.parallel >= 1);
+ assert(cfg.passes >= 1);
+ assert(len(cfg.secret) <= types::U32_MAX);
+ assert(len(cfg.data) <= types::U32_MAX);
+
+ let mem: []u64 = match (cfg.mem) {
+ case let mem: []u64 =>
+ assert(len(mem) >= 8 * cfg.parallel * BLOCKSIZE
+ && len(mem) % BLOCKSIZE == 0
+ && len(mem) / BLOCKSIZE <= types::U32_MAX);
+ yield mem;
+ case let memsize: u32 =>
+ assert(memsize >= 8 * cfg.parallel
+ && memsize <= types::U32_MAX);
+
+ let membytes = memsize * BLOCKSIZE * 8;
+ let mem: []u64 = alloc([0...], membytes);
+ yield mem[..membytes / 8];
+ };
+ // round down memory to nearest multiple of 4 times parallel
+ mem = mem[..len(mem) - len(mem) % (4 * cfg.parallel * BLOCKSIZE)];
+ const memsize = (len(mem) / BLOCKSIZE): u32;
+
+ let h0: [64]u8 = [0...];
+ inithash(&h0, len(dest): u32, password, salt, cfg, mode, memsize);
+
+ const cols = 4 * (memsize / (4 * cfg.parallel));
+ let ctx = context {
+ rows = cfg.parallel,
+ cols = cols,
+ sliceblocks = cols / 4,
+ pass = 0,
+ mem = mem,
+ mode = mode,
+ seedsinit = [0...],
+ seedblock = [0...],
+ ...
+ };
+
+ // hash first and second blocks of each row
+ for (let i = 0z; i < ctx.rows; i += 1) {
+ let src: [72]u8 = [0...];
+ src[..64] = h0[..];
+
+ endian::leputu32(src[64..68], 0);
+ endian::leputu32(src[68..], i: u32);
+ varhash(blocku8(&ctx, i, 0), src);
+
+ endian::leputu32(src[64..68], 1);
+ endian::leputu32(src[68..], i: u32);
+ varhash(blocku8(&ctx, i, 1), src);
+ };
+
+ // process segments
+ for (ctx.pass < cfg.passes; ctx.pass += 1) {
+ for (let s = 0z; s < SLICES; s += 1) {
+ for (let i = 0z; i < ctx.rows; i += 1) {
+ segproc(cfg, &ctx, i, s);
+ };
+ };
+ };
+
+ // final hash
+ let b = blocku8(&ctx, 0, ctx.cols - 1);
+ for (let i = 1z; i < ctx.rows; i += 1) {
+ math::xor(b, b, blocku8(&ctx, i, ctx.cols - 1));
+ };
+
+ varhash(dest, b);
+
+ bytes::zero((h0: []u64: *[*]u8)[..len(h0) * size(u64)]);
+ bytes::zero((ctx.mem: *[*]u8)[..len(ctx.mem) * size(u64)]);
+
+ if (cfg.mem is u32) {
+ // mem was allocated internally
+ free(ctx.mem);
+ };
+};
+
+fn block(ctx: *context, i: size, j: size) []u64 = {
+ let index = (ctx.cols * i + j) * BLOCKSIZE;
+ return ctx.mem[index..index + BLOCKSIZE];
+};
+
+fn blocku8(ctx: *context, i: size, j: size) []u8 = {
+ return (block(ctx, i, j): *[*]u8)[..BLOCKSIZE * size(u64)];
+};
+
+fn refblock(cfg: *config, ctx: *context, seed: u64, i: size, j: size) []u64 = {
+ const j1: u64 = seed & 0xffffffff;
+ const segstart = j - (j % ctx.sliceblocks);
+
+ const l: u64 = if (segstart == 0 && ctx.pass == 0) {
+ yield 0;
+ } else {
+ yield (seed >> 32) % cfg.parallel;
+ };
+
+ let poolstart: u64 = 0;
+ let poolsize: u64 = segstart;
+ if (ctx.pass > 0) {
+ poolstart = (segstart + ctx.sliceblocks) % ctx.cols;
+ poolsize = 3 * ctx.sliceblocks;
+ };
+
+ if (l == i: u64) {
+ poolsize += (j - segstart);
+ };
+
+ if ((j - segstart) == 0 || l == i: u64) {
+ poolsize -= 1;
+ };
+
+ const x: u64 = (j1 * j1) >> 32;
+ const y: u64 = (poolsize * x) >> 32;
+ const z: u64 = (poolstart + poolsize - (y+1)) % ctx.cols: u64;
+
+ return block(ctx, l: size, z: size);
+};
+
+fn inithash(
+ dest: *[64]u8,
+ taglen: u32,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+ mode: mode,
+ memsize: u32,
+) void = {
+ let u32buf: [4]u8 = [0...];
+ let h = blake2b::blake2b([], 64);
+ defer hash::close(&h);
+
+ hash_leputu32(&h, cfg.parallel);
+ hash_leputu32(&h, taglen);
+ hash_leputu32(&h, memsize);
+ hash_leputu32(&h, cfg.passes);
+ hash_leputu32(&h, cfg.version);
+ hash_leputu32(&h, mode: u32);
+ hash_leputu32(&h, len(password): u32);
+ hash::write(&h, password);
+
+ hash_leputu32(&h, len(salt): u32);
+ hash::write(&h, salt);
+
+ hash_leputu32(&h, len(cfg.secret): u32);
+ hash::write(&h, cfg.secret);
+
+ hash_leputu32(&h, len(cfg.data): u32);
+ hash::write(&h, cfg.data);
+
+ hash::sum(&h, dest[..]);
+};
+
+fn hash_leputu32(h: *hash::hash, u: u32) void = {
+ let buf: [4]u8 = [0...];
+ endian::leputu32(buf, u);
+ hash::write(h, buf[..]);
+};
+
+// The variable hash function H'
+fn varhash(dest: []u8, block: []u8) void = {
+ let u32buf: [4]u8 = [0...];
+
+ if (len(dest) <= 64) {
+ let h = blake2b::blake2b([], len(dest));
+ defer hash::close(&h);
+ hash_leputu32(&h, len(dest): u32);
+ hash::write(&h, block);
+ hash::sum(&h, dest);
+ return;
+ };
+
+ // TODO this may be replaced with a constant time divceil in future to
+ // avoid leaking the dest len.
+ const r = divceil(len(dest): u32, 32) - 2;
+ let v: [64]u8 = [0...];
+
+ let destbuf = bufio::fixed(dest, io::mode::WRITE);
+
+ let h = blake2b::blake2b([], 64);
+ hash_leputu32(&h, len(dest): u32);
+ hash::write(&h, block);
+ hash::sum(&h, v[..]);
+ hash::close(&h);
+
+ io::writeall(&destbuf, v[..32])!;
+
+ for (let i = 1z; i < r; i += 1) {
+ let h = blake2b::blake2b([], 64);
+ hash::write(&h, v[..]);
+ hash::sum(&h, v[..]);
+ hash::close(&h);
+ io::writeall(&destbuf, v[..32])!;
+ };
+
+ const remainder = len(dest) - 32 * r;
+ let hend = blake2b::blake2b([], remainder);
+ defer hash::close(&hend);
+ hash::write(&hend, v[..]);
+ hash::sum(&hend, v[..remainder]);
+ io::writeall(&destbuf, v[..remainder])!;
+};
+
+fn divceil(dividend: u32, divisor: u32) u32 = {
+ let result = dividend / divisor;
+ if (dividend % divisor > 0) {
+ result += 1;
+ };
+ return result;
+};
+
+fn xorblock(dest: []u64, x: []u64, y: []u64) void = {
+ math::xor((dest: *[*]u8)[..len(dest) * size(u64)],
+ (x: *[*]u8)[..len(dest) * size(u64)],
+ (y: *[*]u8)[..len(dest) * size(u64)]);
+};
+
+fn segproc(cfg: *config, ctx: *context, i: size, slice: size) void = {
+ const init = switch (ctx.mode) {
+ case mode::I =>
+ yield true;
+ case mode::ID =>
+ yield ctx.pass == 0 && slice <= 2;
+ case mode::D =>
+ yield false;
+ };
+ if (init) {
+ ctx.seedsinit[0] = ctx.pass;
+ ctx.seedsinit[1] = i;
+ ctx.seedsinit[2] = slice;
+ ctx.seedsinit[3] = len(ctx.mem) / BLOCKSIZE;
+ ctx.seedsinit[4] = cfg.passes;
+ ctx.seedsinit[5] = ctx.mode: u64;
+ ctx.seedsinit[6] = 0;
+
+ if (ctx.pass == 0 && slice == 0) {
+ ctx.seedsinit[6] += 1;
+ compress(ctx.seedblock, ctx.seedsinit, zeroblock,
+ false);
+ compress(ctx.seedblock, ctx.seedblock, zeroblock,
+ false);
+ };
+ };
+
+ for (let b = 0z; b < ctx.sliceblocks; b += 1) {
+ const j = slice * ctx.sliceblocks + b;
+ if (ctx.pass == 0 && j < 2) {
+ continue;
+ };
+
+ const dmodeseed = switch (ctx.mode) {
+ case mode::D =>
+ yield true;
+ case mode::ID =>
+ yield ctx.pass > 0 || slice > 1;
+ case mode::I =>
+ yield false;
+ };
+ const seed: u64 = if (dmodeseed) {
+ yield block(ctx, i, (j - 1) % ctx.cols)[0];
+ } else {
+ if (b % BLOCKSIZE == 0) {
+ ctx.seedsinit[6] += 1;
+ compress(ctx.seedblock, ctx.seedsinit,
+ zeroblock, false);
+ compress(ctx.seedblock, ctx.seedblock,
+ zeroblock, false);
+ };
+ yield ctx.seedblock[b % BLOCKSIZE];
+ };
+ compress(block(ctx, i, j), block(ctx, i, (j - 1) % ctx.cols),
+ refblock(cfg, ctx, seed, i, j), ctx.pass > 0);
+ };
+};
+
+fn compress(dest: []u64, x: []u64, y: []u64, xor: bool) void = {
+ let r: block64 = [0...];
+ xorblock(r, x, y);
+
+ let z: block64 = [0...];
+ z[..] = r[..];
+
+ for (let i = 0z; i < 128; i += 16) {
+ perm(&z[i], &z[i + 1], &z[i + 2], &z[i + 3], &z[i + 4],
+ &z[i + 5], &z[i + 6], &z[i + 7], &z[i + 8], &z[i + 9],
+ &z[i + 10], &z[i + 11], &z[i + 12], &z[i + 13],
+ &z[i + 14], &z[i + 15]);
+ };
+
+ for (let i = 0z; i < 16; i += 2) {
+ perm(&z[i], &z[i + 1], &z[i + 16], &z[i + 17], &z[i + 32],
+ &z[i + 33], &z[i + 48], &z[i + 49], &z[i + 64],
+ &z[i + 65], &z[i + 80], &z[i + 81], &z[i + 96],
+ &z[i + 97], &z[i + 112], &z[i + 113]);
+ };
+
+ if (xor) {
+ xorblock(r, r, dest);
+ };
+
+ xorblock(dest, z, r);
+};
+
+fn perm(
+ x0: *u64,
+ x1: *u64,
+ x2: *u64,
+ x3: *u64,
+ x4: *u64,
+ x5: *u64,
+ x6: *u64,
+ x7: *u64,
+ x8: *u64,
+ x9: *u64,
+ x10: *u64,
+ x11: *u64,
+ x12: *u64,
+ x13: *u64,
+ x14: *u64,
+ x15: *u64,
+) void = {
+ mix(x0, x4, x8, x12);
+ mix(x1, x5, x9, x13);
+ mix(x2, x6, x10, x14);
+ mix(x3, x7, x11, x15);
+
+ mix(x0, x5, x10, x15);
+ mix(x1, x6, x11, x12);
+ mix(x2, x7, x8, x13);
+ mix(x3, x4, x9, x14);
+};
+
+fn mix(a: *u64, b: *u64, c: *u64, d: *u64) void = {
+ *a = *a + *b + 2 * trunc(*a) * trunc(*b);
+ *d = math::rotr64(*d ^ *a, 32);
+ *c = *c + *d + 2 * trunc(*c) * trunc(*d);
+ *b = math::rotr64(*b ^ *c, 24);
+
+ *a = *a + *b + 2 * trunc(*a) * trunc(*b);
+ *d = math::rotr64(*d ^ *a, 16);
+ *c = *c + *d + 2 * trunc(*c) * trunc(*d);
+ *b = math::rotr64(*b ^ *c, 63);
+};
+
+fn trunc(a: u64) u64 = {
+ return a & 0xffffffff;
+};
diff --git a/secstore/secstore.ha b/secstore/secstore.ha
index d01eb0f..50b8a9a 100644
--- a/secstore/secstore.ha
+++ b/secstore/secstore.ha
@@ -1,6 +1,7 @@
use bufio;
use bytes;
use crypto::argon2;
+use crypto::argon2bug;
use crypto::keystore;
use crypto::random;
use crypto;
@@ -232,7 +233,11 @@ export fn unlock(store: *secstore, passphrase: []u8) (void | error) = {
};
switch (items[0]) {
case "argon2i" =>
- argon2::argon2i(key, passphrase, salt, &config)!;
+ if (kv == keyversion::V0) {
+ argon2bug::argon2i(key, passphrase, salt, &config)!;
+ } else {
+ argon2::argon2i(key, passphrase, salt, &config)!;
+ };
case "argon2id" =>
argon2::argon2id(key, passphrase, salt, &config)!;
case =>
--
2.40.0
[PATCH himitsu v2 4/5] add re-encryption support
Tested-by: Sam Nystrom <sam@samnystrom.dev>
---
cmd/himitsu-init/main.ha | 52 +++++++++++++++++++++++++++++++
secstore/secstore.ha | 66 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 115 insertions(+), 3 deletions(-)
diff --git a/cmd/himitsu-init/main.ha b/cmd/himitsu-init/main.ha
index 0989c6d..39f7f56 100644
--- a/cmd/himitsu-init/main.ha
+++ b/cmd/himitsu-init/main.ha
@@ -10,12 +10,29 @@ use path;
use secstore;
use strings;
use unix::tty;
+use getopt;
// XXX: Distros may want to modify the default config
const conf: str = `[himitsud]
prompter=hiprompt-gtk`;
export fn main() void = {
+
+ const cmd = getopt::parse(os::args,
+ "himitsu-init",
+ ('r', "Change master key and re-encrypt the secstore."),
+ );
+ defer getopt::finish(&cmd);
+
+ let re = false;
+ for (let i = 0z; i < len(cmd.opts); i += 1) {
+ const opt = cmd.opts[i];
+ switch (opt.0) {
+ case 'r' =>
+ re = true;
+ };
+ };
+
const tty = match (tty::open()) {
case let file: io::file =>
yield file;
@@ -32,6 +49,11 @@ export fn main() void = {
const tty = &bufio::buffered(tty, rbuf, wbuf);
defer io::close(tty)!;
+ if (re) {
+ reencrypt(tty);
+ return;
+ };
+
// TODO: Prompt before overwriting existing secstore
fmt::errorln("Initializing a new himitsu secstore.")!;
fmt::error("Please enter a passphrase: ")!;
@@ -103,3 +125,33 @@ fn writeconf() void = {
fmt::println("Wrote config file to", confpath)!;
};
+
+fn reencrypt(tty: io::handle) void = {
+ fmt::errorln("Re-encrypting the secstore.\n")!;
+ fmt::errorln("There are not many safeguards in effect, so please read carefully.")!;
+ fmt::errorln()!;
+ fmt::errorfln(
+ "Make sure that no himitsud process is currently running. The current\n"
+ "secstore will be moved to himitsu.old in our data directory:\n\n"
+ "\t{}\n\nIf the process fails in between, make sure that himitsu.new in the \n"
+ "data directory is removed and try again.\n",
+ dirs::data(""),
+ )!;
+
+ fmt::error("Please enter your passphrase: ")!;
+ const pass = match (bufio::scanline(tty)!) {
+ case let buf: []u8 =>
+ yield buf;
+ case io::EOF =>
+ fmt::fatal("Error: no passphrase supplied");
+ };
+ defer free(pass);
+ fmt::errorln()!;
+
+ secstore::reencrypt(pass)!;
+
+ fmt::errorln(
+ "\nSecstore has been re-encrypted. If everything works,you may remove the\n"
+ "himitsu.old folder from your data directory"
+ )!;
+};
diff --git a/secstore/secstore.ha b/secstore/secstore.ha
index 50b8a9a..76419e8 100644
--- a/secstore/secstore.ha
+++ b/secstore/secstore.ha
@@ -33,6 +33,11 @@ type keyversion = enum u8 {
// Initializes a new secstore using the provided passphrase. The caller should
// call [[close]] when they're done with it.
export fn create(passphrase: []u8) (secstore | error) = {
+ let dir = strings::dup(dirs::data("himitsu"));
+ return createat(passphrase, dir);
+};
+
+fn createat(passphrase: []u8, dir: const str) (secstore | error) = {
let key: [32 + 16]u8 = [0...];
defer bytes::zero(key);
@@ -48,7 +53,6 @@ export fn create(passphrase: []u8) (secstore | error) = {
argon2::argon2id(key, passphrase, salt, &config)!;
const verify = key[32..];
- const dir = strings::dup(dirs::data("himitsu"));
let buf = path::init();
path::set(&buf, dir, "key")!;
@@ -67,7 +71,7 @@ export fn create(passphrase: []u8) (secstore | error) = {
let masterkeybuf: [32]u8 = [0...];
random::buffer(masterkeybuf);
defer bytes::zero(masterkeybuf);
- let masterkey = keystore::newkey(masterkeybuf, "secstore")!;
+ let masterkey = keystore::newkey(masterkeybuf, "secstoreinit")!;
let mkkey = keystore::newkey(key[..32], "masterkeykey")!;
defer keystore::destroy(mkkey);
@@ -88,6 +92,58 @@ export fn create(passphrase: []u8) (secstore | error) = {
};
};
+// Generates a new master key and re-encrypts the secstore.
+export fn reencrypt(passphrase: []u8) (secstore | error) = {
+ let dir = strings::dup(dirs::data("himitsu"));
+ let dirnew = strings::dup(dirs::data("himitsu.new"));
+
+ let oldstore = openat(dir)?;
+ unlock(&oldstore, passphrase)?;
+ defer close(&oldstore);
+
+ let store = createat(passphrase, dirnew)!;
+ defer close(&store);
+
+ const q = query::query { ... };
+ const iter = query(&oldstore, &q);
+ for (true) {
+ const item = match (next(&oldstore, &iter)) {
+ case let item: *entry =>
+ let buf = bufio::dynamic(io::mode::RDWR);
+ defer io::close(&buf)!;
+ write(&oldstore, &buf, item, true)!;
+
+ io::seek(&buf, 0, io::whence::SET)!;
+
+ let iq = query::parse(&buf)!;
+ defer query::finish(&iq);
+ add(&store, &iq)!;
+ yield item;
+ case void =>
+ break;
+ };
+ };
+
+ let dirold = strings::dup(dirs::data("himitsu.old"));
+ match (os::rename(dir, dirold)) {
+ case let e: fs::error =>
+ fmt::fatalf("Moving himitsu to himitsu.old failed: {}",
+ fs::strerror(e));
+ case =>
+ yield;
+ };
+
+ match (os::rename(dirnew, dir)) {
+ case let e: fs::error =>
+ fmt::fatalf("Moving himitsu.new to himitsu failed: {}",
+ fs::strerror(e));
+ case =>
+ yield;
+ };
+
+ return store;
+};
+
// Encrypts msg and writes it to sink
fn secbox(
sink: io::handle,
@@ -137,7 +193,11 @@ fn secunbox(src: io::handle, key: (keystore::key | []u8)) ([]u8 | io::error) = {
// Opens the secstore. The caller should call [[unlock]] to decrypt it, and
// [[close]] when they're done with it.
export fn open() (secstore | error) = {
- const dir = strings::dup(dirs::data("himitsu"));
+ let dir = strings::dup(dirs::data("himitsu"));
+ return openat(dir);
+};
+
+fn openat(dir: str) (secstore | error) = {
let buf = path::init();
path::set(&buf, dir, "index")!;
--
2.40.0
[PATCH himitsu v2 5/5] vendor fixed argon2 module
Vendor the fixed argon2 module also for a while to make sure the new keys will
be derived properly, if the new himitsu version is accidently build with an
outdated hare stack.
---
crypto/argon2/+test.ha | 246 +++++++++++++++++++
crypto/argon2/README | 30 +++
crypto/argon2/argon2.ha | 521 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 797 insertions(+)
create mode 100644 crypto/argon2/+test.ha
create mode 100644 crypto/argon2/README
create mode 100644 crypto/argon2/argon2.ha
diff --git a/crypto/argon2/+test.ha b/crypto/argon2/+test.ha
new file mode 100644
index 0000000..aa2b3eb
--- /dev/null
+++ b/crypto/argon2/+test.ha
@@ -0,0 +1,246 @@
+// License: MPL-2.0
+// (c) 2021-2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2022 Drew DeVault <sir@cmpwn.com>
+use bytes;
+use encoding::hex;
+use strings;
+
+@test fn mode_d_one_pass() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let expected: [_]u8 = [
+ 0xfa, 0x17, 0x75, 0xca, 0x80, 0x90, 0x64, 0x66, 0x18, 0xbe,
+ 0x70, 0xeb, 0x0f, 0xc9, 0xde, 0x43, 0x67, 0x58, 0xed, 0x0c,
+ 0xa5, 0x36, 0x83, 0x1a, 0xe9, 0xe1, 0x03, 0x48, 0x93, 0x81,
+ 0xc1, 0x79,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 32,
+ ...
+ };
+
+ argon2d(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+@test fn rfc_d_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97, 0x53, 0x71,
+ 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94, 0xf8, 0x68, 0xe3, 0xbe,
+ 0x39, 0x84, 0xf3, 0xc1, 0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe,
+ 0x4a, 0xcb,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2d(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+
+@test fn rfc_i_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa, 0x13, 0xf0,
+ 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1, 0xc8, 0xde, 0x6b, 0x01,
+ 0x6d, 0xd3, 0x88, 0xd2, 0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b,
+ 0x6c, 0xe8,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2i(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+@test fn rfc_id_test_vector() void = {
+ let pass: [32]u8 = [1...];
+ let salt: [16]u8 = [2...];
+ let secret: [8]u8 = [3...];
+ let data: [12]u8 = [4...];
+ let result: [32]u8 = [0...];
+
+ let mem: []u64 = alloc([0...], 32z * BLOCKSIZE);
+ defer free(mem);
+
+ let expected: [_]u8 = [
+ 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, 0x08, 0xc0,
+ 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9, 0xd0, 0x1e, 0xf0, 0x45,
+ 0x2d, 0x75, 0xb6, 0x5e, 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01,
+ 0xe6, 0x59,
+ ];
+
+ let cfg = config {
+ secret = secret,
+ data = data,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ mem = mem[..],
+ ...
+ };
+
+ argon2id(result[..], pass, salt, &cfg)!;
+
+ assert(bytes::equal(result, expected));
+};
+
+type tcase = struct {
+ c: config,
+ m: mode,
+ h: str,
+};
+
+@test fn samples() void = {
+ const pass = strings::toutf8("trustno1");
+ const salt = strings::toutf8("abcdefgh");
+
+ const tests: [_]tcase = [
+ // XXX disabled for now because it's slow
+ // tcase {
+ // c = low_mem_config,
+ // m = mode::ID,
+ // h = "8974537c53677aae532b319af700bb4232a0d74eee7d57296b2a3f8303a6bafe",
+ // },
+ // tcase {
+ // c = default_config,
+ // m = mode::ID,
+ // h = "3b282cbf435b0e022f7041549583ddc802e519109f1da8f12d2054910913d660",
+ // },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 3,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::ID,
+ h = "c7ada5ba3222fa45a3802249b509dcfb10e68a50e3faad2a6377eeca8395ab47",
+ },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::ID,
+ h = "21543b2017ede3f865ea5cb88295628ba25eb3be53a8c4aeb0ac1a264be0110a",
+ },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::I,
+ h = "5c3124ce5f3556e5e25f06b5108718f2cd72afee98a3249656eb85ecc0e5b314",
+ },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::D,
+ h = "d75524ad0b899363ce77f2d1e1040763dc01cfc725db635391bba163001f08cb",
+ },
+ tcase {
+ c = config {
+ passes = 3,
+ parallel = 3,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::ID,
+ h = "226c3ca6caba42b102035d332a11b350f1e19675fccb6e24aa33ca8c31d588c1",
+ },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 8,
+ version = 0x13,
+ mem = 64,
+ ...
+ },
+ m = mode::ID,
+ h = "fadf598b70708f4d91b0e98f038fd25a73950f1f85d57fb250740d817f95e9a9",
+ },
+ tcase {
+ c = config {
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ mem = 96,
+ ...
+ },
+ m = mode::ID,
+ h = "c99aa41cb53cc4919d336c19d38b30d8633c71faa9475293f3fbe0aa6ccd65b2",
+ },
+ ];
+
+ for (let i = 0z; i < len(tests); i += 1) {
+ const t = tests[i];
+ const expected = hex::decodestr(t.h)!;
+ defer free(expected);
+ let dest: []u8 = alloc([0...], len(expected));
+ defer free(dest);
+
+ argon2(dest, pass, salt, &t.c, t.m)!;
+ assert(bytes::equal(expected, dest));
+ };
+};
+
diff --git a/crypto/argon2/README b/crypto/argon2/README
new file mode 100644
index 0000000..b06abdf
--- /dev/null
+++ b/crypto/argon2/README
@@ -0,0 +1,30 @@
+This module provides an implementation of the argon2 key derivation function as
+described by RFC 9106. This is the recommended algorithm for password hashing in
+Hare programs, and for deriving keys for use with other cryptographic
+algorithms. Some thought must be given to the appropriate configuration for your
+use case. Some general advice is provided here; if in doubt, consult the RFC.
+
+The argon2 parameters are configured via the [[config]] structure. To determine
+the appropriate configuration parameters for a particular use-case, consult
+section 4 of the RFC. Otherwise, sane defaults for common scenarios are provided
+via [[default_config]] and [[low_mem_config]]; consult the docs of each
+configuration for details.
+
+Once a suitable configuration has been selected, the user must provide a salt.
+This salt should be stored alongside the hash, should be unique for each
+password, and should be random: see [[crypto::random]]. The salt and hash
+lengths are configurable, the recommended defaults are 16 and 32 bytes
+respectively.
+
+Equipped with the necessary parameters, the user may call the appropriate argon2
+variant via [[argon2d]], [[argon2i]], or [[argon2id]]. If unsure which to use,
+choose [[argon2id]]. The RFC is the authoratative source on the appropriate
+argon2 variant and configuration parameters for your use-case.
+
+This is a low-level module which implements cryptographic primitives. Direct use
+of cryptographic primitives is not recommended for non-experts, as incorrect use
+of these primitives can easily lead to the introduction of security
+vulnerabilities. Non-experts are advised to use the high-level operations
+available in the top-level [[crypto]] module.
+
+Be advised that Hare's cryptography implementations have not been audited.
diff --git a/crypto/argon2/argon2.ha b/crypto/argon2/argon2.ha
new file mode 100644
index 0000000..a9c50fa
--- /dev/null
+++ b/crypto/argon2/argon2.ha
@@ -0,0 +1,521 @@
+// (c) 2022 Alexey Yerin <yyp@disroot.org>
+// (c) 2021-2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2021-2022 Drew DeVault <sir@cmpwn.com>
+use bufio;
+use bytes;
+use crypto::blake2b;
+use crypto::math;
+use endian;
+use errors::{nomem};
+use hash;
+use io;
+use types;
+
+// Latest version of argon2 supported by this implementation (1.3).
+export def VERSION: u8 = 0x13;
+
+// Number of u64 elements of one block.
+export def BLOCKSIZE: u32 = 128;
+
+def SLICES: size = 4;
+
+type block64 = [BLOCKSIZE]u64;
+
+const zeroblock: block64 = [0...];
+
+type mode = enum {
+ D = 0,
+ I = 1,
+ ID = 2,
+};
+
+// This type provides configuration options for the argon2 algorithm. Most users
+// will find [[default_config]] or [[low_mem_config]] suitable for their needs
+// without providing a custom configuration. If writing a custom configuration,
+// consult the RFC for advice on selecting suitable values for your use-case.
+//
+// 'parallel' specifies the number of parallel processes. 'pass' configures the
+// number of iterations. Both values must be at least one. Note: the Hare
+// implementation of argon2 does not process hashes in parallel, though it will
+// still compute the correct hash if this value is greater than one.
+//
+// 'version' specifies the version of the argon2 function. The implementation
+// currently only supports version 1.3. Use [[VERSION]] here.
+//
+// 'secret' and 'data' are optional byte arrays that are applied to the initial
+// state. Consult the RFC for details.
+//
+// The 'mem' parameter is used to configure working memory used during the
+// computation. The argon2 algorithm requires a large amount of memory to
+// compute hashes. If 'mem' set to a u32, it is interpreted as the desired
+// number of 1024-byte blocks the implementation shall allocate for you. If the
+// caller wants to manage the allocation itself, provide a []u8 instead. The
+// length of this slice must be at least 8 times the value of 'parallel' in
+// blocks, and must be a multiple of [[BLOCKSIZE]]. To have the implementation
+// allocate 64 KiB, set 'mem' to 64. To use the same amount of caller-provided
+// memory, provide a slice of length 64 * [[BLOCKSIZE]].
+export type config = struct {
+ mem: (u32 | []u64),
+ parallel: u32,
+ passes: u32,
+ version: u8,
+ secret: []u8,
+ data: []u8
+};
+
+// The default recommended configuration for most use cases. This configuration
+// uses 2 GiB of working memory. A 16-byte 'salt' and 32-byte 'dest' parameter
+// is recommended in combination with this configuration.
+export const default_config: config = config {
+ mem = 2 * 1024 * 1024,
+ passes = 1,
+ parallel = 4,
+ version = 0x13,
+ ...
+};
+
+// The default recommended configuration for memory-constrained use cases. This
+// configuration uses 64 MiB of working memory. A 16-byte 'salt' and 32-byte
+// 'dest' parameter is recommended in combination with this configuration.
+export const low_mem_config: config = config {
+ mem = 64 * 1024,
+ passes = 3,
+ parallel = 4,
+ version = 0x13,
+ ...
+};
+
+type context = struct {
+ mode: mode,
+ cols: size,
+ rows: size,
+ sliceblocks: size,
+ mem: []u64,
+ pass: u32,
+ seedsinit: block64,
+ seedblock: block64,
+};
+
+// Computes an argon2d hash, writing the digest to 'dest'. A 'salt' length of 16
+// bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32 bytes
+// is recommended, and 4 bytes is the minimum.
+//
+// The argon2d mode uses data-dependent memory access and is suitable for
+// applications with no threats of side-channel timing attacks.
+export fn argon2d(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::D);
+};
+
+// Computes an argon2i hash, writing the digest to 'dest'. A 'salt' length of 16
+// bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32 bytes
+// is recommended, and 4 bytes is the minimum.
+//
+// The argon2i mode uses data-independent memory access and is suitable for
+// password hashing and key derivation. It makes more passes over memory to
+// protect from trade-off attacks.
+export fn argon2i(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::I);
+};
+
+// Computes an argon2id hash, writing the digest to 'dest'. A 'salt' length of
+// 16 bytes is recommended, and 8 bytes is the minimum. A 'dest' length of 32
+// bytes is recommended, and 4 bytes is the minimum.
+//
+// The argon2id mode works by using argon2i for the first half of the first pass
+// and argon2d further on. It provides therefore protection from side-channel
+// attacks and brute-force cost savings due to memory trade-offs.
+//
+// If you are unsure which variant to use, argon2id is recommended.
+export fn argon2id(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+) (void | nomem) = {
+ return argon2(dest, password, salt, cfg, mode::ID);
+};
+
+fn argon2(
+ dest: []u8,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+ mode: mode,
+) (void | nomem) = {
+ assert(endian::host == &endian::little, "TODO big endian support");
+
+ assert(len(dest) >= 4 && len(dest) <= types::U32_MAX);
+ assert(len(password) <= types::U32_MAX);
+ assert(len(salt) >= 8 && len(salt) <= types::U32_MAX);
+ assert(cfg.parallel >= 1);
+ assert(cfg.passes >= 1);
+ assert(len(cfg.secret) <= types::U32_MAX);
+ assert(len(cfg.data) <= types::U32_MAX);
+
+ let initmemsize = 0u32;
+ let mem: []u64 = match (cfg.mem) {
+ case let mem: []u64 =>
+ assert(len(mem) >= 8 * cfg.parallel * BLOCKSIZE
+ && len(mem) % BLOCKSIZE == 0
+ && len(mem) / BLOCKSIZE <= types::U32_MAX);
+ initmemsize = (len(mem) / BLOCKSIZE): u32;
+
+ // round down memory to nearest multiple of 4 times parallel
+ const memsize = len(mem) - len(mem)
+ % (4 * cfg.parallel * BLOCKSIZE);
+ yield mem[..memsize];
+ case let memsize: u32 =>
+ assert(memsize >= 8 * cfg.parallel
+ && memsize <= types::U32_MAX);
+
+ initmemsize = memsize;
+ const memsize = memsize - memsize % (4 * cfg.parallel);
+ yield alloc([0...], memsize * BLOCKSIZE): []u64;
+ };
+
+ let h0: [64]u8 = [0...];
+ inithash(&h0, len(dest): u32, password, salt, cfg, mode, initmemsize);
+
+ const memsize = (len(mem) / BLOCKSIZE): u32;
+ const cols = 4 * (memsize / (4 * cfg.parallel));
+ let ctx = context {
+ rows = cfg.parallel,
+ cols = cols,
+ sliceblocks = cols / 4,
+ pass = 0,
+ mem = mem,
+ mode = mode,
+ seedsinit = [0...],
+ seedblock = [0...],
+ ...
+ };
+
+ // hash first and second blocks of each row
+ for (let i = 0z; i < ctx.rows; i += 1) {
+ let src: [72]u8 = [0...];
+ src[..64] = h0[..];
+
+ endian::leputu32(src[64..68], 0);
+ endian::leputu32(src[68..], i: u32);
+ varhash(blocku8(&ctx, i, 0), src);
+
+ endian::leputu32(src[64..68], 1);
+ endian::leputu32(src[68..], i: u32);
+ varhash(blocku8(&ctx, i, 1), src);
+ };
+
+ // process segments
+ for (ctx.pass < cfg.passes; ctx.pass += 1) {
+ for (let s = 0z; s < SLICES; s += 1) {
+ for (let i = 0z; i < ctx.rows; i += 1) {
+ segproc(cfg, &ctx, i, s);
+ };
+ };
+ };
+
+ // final hash
+ let b = blocku8(&ctx, 0, ctx.cols - 1);
+ for (let i = 1z; i < ctx.rows; i += 1) {
+ math::xor(b, b, blocku8(&ctx, i, ctx.cols - 1));
+ };
+
+ varhash(dest, b);
+
+ bytes::zero((h0: []u64: *[*]u8)[..len(h0) * size(u64)]);
+ bytes::zero((ctx.mem: *[*]u8)[..len(ctx.mem) * size(u64)]);
+
+ if (cfg.mem is u32) {
+ // mem was allocated internally
+ free(ctx.mem);
+ };
+};
+
+fn block(ctx: *context, i: size, j: size) []u64 = {
+ let index = (ctx.cols * i + j) * BLOCKSIZE;
+ return ctx.mem[index..index + BLOCKSIZE];
+};
+
+fn blocku8(ctx: *context, i: size, j: size) []u8 = {
+ return (block(ctx, i, j): *[*]u8)[..BLOCKSIZE * size(u64)];
+};
+
+fn refblock(cfg: *config, ctx: *context, seed: u64, i: size, j: size) []u64 = {
+ const segstart = (j - (j % ctx.sliceblocks)) / ctx.sliceblocks;
+ const index = j % ctx.sliceblocks;
+
+ const l: size = if (segstart == 0 && ctx.pass == 0) {
+ yield i;
+ } else {
+ yield (seed >> 32) % cfg.parallel;
+ };
+
+ let poolstart: u64 = ((segstart + 1) % SLICES) * ctx.sliceblocks;
+ let poolsize: u64 = 3 * ctx.sliceblocks;
+
+ if (i == l) {
+ poolsize += index;
+ };
+
+ if (ctx.pass == 0) {
+ poolstart = 0;
+ poolsize = segstart * ctx.sliceblocks;
+ if (segstart == 0 || i == l) {
+ poolsize += index;
+ };
+ };
+
+ if (index == 0 || i == l) {
+ poolsize -= 1;
+ };
+
+ const j1: u64 = seed & 0xffffffff;
+ const x: u64 = (j1 * j1) >> 32;
+ const y: u64 = (poolsize * x) >> 32;
+ const z: u64 = (poolstart + poolsize - (y+1)) % ctx.cols: u64;
+
+ return block(ctx, l: size, z: size);
+};
+
+fn inithash(
+ dest: *[64]u8,
+ taglen: u32,
+ password: []u8,
+ salt: []u8,
+ cfg: *config,
+ mode: mode,
+ memsize: u32,
+) void = {
+ let u32buf: [4]u8 = [0...];
+ let h = blake2b::blake2b([], 64);
+ defer hash::close(&h);
+
+ hash_leputu32(&h, cfg.parallel);
+ hash_leputu32(&h, taglen);
+ hash_leputu32(&h, memsize);
+ hash_leputu32(&h, cfg.passes);
+ hash_leputu32(&h, cfg.version);
+ hash_leputu32(&h, mode: u32);
+ hash_leputu32(&h, len(password): u32);
+ hash::write(&h, password);
+
+ hash_leputu32(&h, len(salt): u32);
+ hash::write(&h, salt);
+
+ hash_leputu32(&h, len(cfg.secret): u32);
+ hash::write(&h, cfg.secret);
+
+ hash_leputu32(&h, len(cfg.data): u32);
+ hash::write(&h, cfg.data);
+
+ hash::sum(&h, dest[..]);
+};
+
+fn hash_leputu32(h: *hash::hash, u: u32) void = {
+ let buf: [4]u8 = [0...];
+ endian::leputu32(buf, u);
+ hash::write(h, buf[..]);
+};
+
+// The variable hash function H'
+fn varhash(dest: []u8, block: []u8) void = {
+ let u32buf: [4]u8 = [0...];
+
+ if (len(dest) <= 64) {
+ let h = blake2b::blake2b([], len(dest));
+ defer hash::close(&h);
+ hash_leputu32(&h, len(dest): u32);
+ hash::write(&h, block);
+ hash::sum(&h, dest);
+ return;
+ };
+
+ // TODO this may be replaced with a constant time divceil in future to
+ // avoid leaking the dest len.
+ const r = divceil(len(dest): u32, 32) - 2;
+ let v: [64]u8 = [0...];
+
+ let destbuf = bufio::fixed(dest, io::mode::WRITE);
+
+ let h = blake2b::blake2b([], 64);
+ hash_leputu32(&h, len(dest): u32);
+ hash::write(&h, block);
+ hash::sum(&h, v[..]);
+ hash::close(&h);
+
+ io::writeall(&destbuf, v[..32])!;
+
+ for (let i = 1z; i < r; i += 1) {
+ let h = blake2b::blake2b([], 64);
+ hash::write(&h, v[..]);
+ hash::sum(&h, v[..]);
+ hash::close(&h);
+ io::writeall(&destbuf, v[..32])!;
+ };
+
+ const remainder = len(dest) - 32 * r;
+ let hend = blake2b::blake2b([], remainder);
+ defer hash::close(&hend);
+ hash::write(&hend, v[..]);
+ hash::sum(&hend, v[..remainder]);
+ io::writeall(&destbuf, v[..remainder])!;
+};
+
+fn divceil(dividend: u32, divisor: u32) u32 = {
+ let result = dividend / divisor;
+ if (dividend % divisor > 0) {
+ result += 1;
+ };
+ return result;
+};
+
+fn xorblock(dest: []u64, x: []u64, y: []u64) void = {
+ math::xor((dest: *[*]u8)[..len(dest) * size(u64)],
+ (x: *[*]u8)[..len(dest) * size(u64)],
+ (y: *[*]u8)[..len(dest) * size(u64)]);
+};
+
+fn segproc(cfg: *config, ctx: *context, i: size, slice: size) void = {
+ const init = switch (ctx.mode) {
+ case mode::I =>
+ yield true;
+ case mode::ID =>
+ yield ctx.pass == 0 && slice < 2;
+ case mode::D =>
+ yield false;
+ };
+ if (init) {
+ ctx.seedsinit[0] = ctx.pass;
+ ctx.seedsinit[1] = i;
+ ctx.seedsinit[2] = slice;
+ ctx.seedsinit[3] = len(ctx.mem) / BLOCKSIZE;
+ ctx.seedsinit[4] = cfg.passes;
+ ctx.seedsinit[5] = ctx.mode: u64;
+ ctx.seedsinit[6] = 0;
+
+ if (ctx.pass == 0 && slice == 0) {
+ ctx.seedsinit[6] += 1;
+ compress(ctx.seedblock, ctx.seedsinit, zeroblock,
+ false);
+ compress(ctx.seedblock, ctx.seedblock, zeroblock,
+ false);
+ };
+ };
+
+ for (let b = 0z; b < ctx.sliceblocks; b += 1) {
+ const j = slice * ctx.sliceblocks + b;
+ if (ctx.pass == 0 && j < 2) {
+ continue;
+ };
+
+ const dmodeseed = switch (ctx.mode) {
+ case mode::D =>
+ yield true;
+ case mode::ID =>
+ yield ctx.pass > 0 || slice > 1;
+ case mode::I =>
+ yield false;
+ };
+
+ const pj = if (j == 0) ctx.cols - 1 else j - 1;
+ let prev = block(ctx, i, pj);
+
+ const seed: u64 = if (dmodeseed) {
+ yield prev[0];
+ } else {
+ if (b % BLOCKSIZE == 0) {
+ ctx.seedsinit[6] += 1;
+ compress(ctx.seedblock, ctx.seedsinit,
+ zeroblock, false);
+ compress(ctx.seedblock, ctx.seedblock,
+ zeroblock, false);
+ };
+ yield ctx.seedblock[b % BLOCKSIZE];
+ };
+
+ let ref = refblock(cfg, ctx, seed, i, j);
+ compress(block(ctx, i, j), prev, ref, ctx.pass > 0);
+ };
+};
+
+fn compress(dest: []u64, x: []u64, y: []u64, xor: bool) void = {
+ let r: block64 = [0...];
+ xorblock(r, x, y);
+
+ let z: block64 = [0...];
+ z[..] = r[..];
+
+ for (let i = 0z; i < 128; i += 16) {
+ perm(&z[i], &z[i + 1], &z[i + 2], &z[i + 3], &z[i + 4],
+ &z[i + 5], &z[i + 6], &z[i + 7], &z[i + 8], &z[i + 9],
+ &z[i + 10], &z[i + 11], &z[i + 12], &z[i + 13],
+ &z[i + 14], &z[i + 15]);
+ };
+
+ for (let i = 0z; i < 16; i += 2) {
+ perm(&z[i], &z[i + 1], &z[i + 16], &z[i + 17], &z[i + 32],
+ &z[i + 33], &z[i + 48], &z[i + 49], &z[i + 64],
+ &z[i + 65], &z[i + 80], &z[i + 81], &z[i + 96],
+ &z[i + 97], &z[i + 112], &z[i + 113]);
+ };
+
+ if (xor) {
+ xorblock(r, r, dest);
+ };
+
+ xorblock(dest, z, r);
+};
+
+fn perm(
+ x0: *u64,
+ x1: *u64,
+ x2: *u64,
+ x3: *u64,
+ x4: *u64,
+ x5: *u64,
+ x6: *u64,
+ x7: *u64,
+ x8: *u64,
+ x9: *u64,
+ x10: *u64,
+ x11: *u64,
+ x12: *u64,
+ x13: *u64,
+ x14: *u64,
+ x15: *u64,
+) void = {
+ mix(x0, x4, x8, x12);
+ mix(x1, x5, x9, x13);
+ mix(x2, x6, x10, x14);
+ mix(x3, x7, x11, x15);
+
+ mix(x0, x5, x10, x15);
+ mix(x1, x6, x11, x12);
+ mix(x2, x7, x8, x13);
+ mix(x3, x4, x9, x14);
+};
+
+fn mix(a: *u64, b: *u64, c: *u64, d: *u64) void = {
+ *a = *a + *b + 2 * trunc(*a) * trunc(*b);
+ *d = math::rotr64(*d ^ *a, 32);
+ *c = *c + *d + 2 * trunc(*c) * trunc(*d);
+ *b = math::rotr64(*b ^ *c, 24);
+
+ *a = *a + *b + 2 * trunc(*a) * trunc(*b);
+ *d = math::rotr64(*d ^ *a, 16);
+ *c = *c + *d + 2 * trunc(*c) * trunc(*d);
+ *b = math::rotr64(*b ^ *c, 63);
+};
+
+fn trunc(a: u64) u64 = {
+ return a & 0xffffffff;
+};
--
2.40.0
Re: [PATCH himitsu v2 5/5] vendor fixed argon2 module