// This is basically a straight port from OpenSSH.
//
// Copyright (c) 2013 Ted Unangst <tedu@openbsd.org>
// Copyright (c) 2022 Drew DeVault <sir@cmpwn.com>
//
// Permission to use, copy, modify, and distribute this software for any purpose
// with or without fee is hereby granted, provided that the above copyright
// notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
use bytes;
use crypto::bcrypt;
use crypto::blowfish;
use crypto::cipher;
use crypto::sha512;
use hash;
use strings;
def BCRYPT_WORDS: size = 8;
def BCRYPT_HASHSZ: size = BCRYPT_WORDS * 4;
// Performs the silly OpenBSD-specific bcrypt key derivation function. See
// openbsd-compat/bcrypt_pbkdf.c in the OpenSSH portable tree.
fn bcrypt_pbkdf(key: []u8, pass: []u8, salt: []u8, rounds: uint) void = {
// Same sanity checks as OpenBSD
assert(rounds >= 1 && len(pass) != 0 && len(salt) != 0
&& len(key) != 0 && len(key) <= BCRYPT_HASHSZ * BCRYPT_HASHSZ
&& len(salt) <= 1 << 20);
let out: [BCRYPT_HASHSZ]u8 = [0...];
let tmpout: [BCRYPT_HASHSZ]u8 = [0...];
defer bytes::zero(out);
defer bytes::zero(tmpout);
const stride = (len(key) + len(out) - 1) / len(out);
let amt = (len(key) + stride - 1) / stride;
let countsalt: []u8 = alloc([0...], len(salt) + 4);
defer free(countsalt);
countsalt[..len(salt)] = salt[..];
let shapass: [sha512::SZ]u8 = [0...];
const sha = sha512::sha512();
hash::write(&sha, pass);
hash::sum(&sha, shapass);
let shasalt: [sha512::SZ]u8 = [0...];
let keylen = len(key);
for (let count = 1u32; keylen > 0; count += 1) {
countsalt[len(salt) + 0] = (count >> 24): u8;
countsalt[len(salt) + 1] = (count >> 16): u8;
countsalt[len(salt) + 2] = (count >> 8): u8;
countsalt[len(salt) + 3] = count: u8;
const sha = sha512::sha512();
hash::write(&sha, countsalt);
hash::sum(&sha, shasalt);
bcrypt_hash(tmpout, shapass, shasalt);
out[..] = tmpout[..];
for (let i = 1u; i < rounds; i += 1) {
hash::reset(&sha);
hash::write(&sha, tmpout);
hash::sum(&sha, shasalt);
bcrypt_hash(tmpout, shapass, shasalt);
for (let j = 0z; j < len(out); j += 1) {
out[j] ^= tmpout[j];
};
};
amt = if (amt < keylen) amt else keylen;
let i = 0z;
for (i < amt; i += 1) {
const dest = i * stride + (count - 1);
if (dest >= len(key)) {
break;
};
key[dest] = out[i];
};
keylen -= i;
};
};
fn bcrypt_hash(out: []u8, pass: []u8, salt: []u8) void = {
const magic = strings::toutf8("OxychromaticBlowfishSwatDynamite");
out[..len(magic)] = magic[..];
let bf = blowfish::new();
blowfish::init_salt(&bf, pass, salt);
for (let i = 0z; i < 64; i += 1) {
blowfish::init(&bf, salt);
blowfish::init(&bf, pass);
};
for (let i = 0z; i < 32; i += 8) {
for (let j = 0z; j < 64; j += 1) {
cipher::encrypt(&bf, out[i..i+8], out[i..i+8]);
};
};
// Endianness
for (let i = 0z; i < 32; i += 4) {
let x = 0u8;
x = out[i + 3]; out[i + 3] = out[i + 0]; out[i + 0] = x;
x = out[i + 2]; out[i + 2] = out[i + 1]; out[i + 1] = x;
};
};