feat: add native scrypt password hashing
This commit is contained in:
@@ -421,3 +421,108 @@ bool crypto_equal_native(String a, String b)
|
||||
for(size_t i=0; i<n; i++) { u8 ca = i<a.size() ? (u8)a[i] : 0; u8 cb = i<b.size() ? (u8)b[i] : 0; diff |= ca ^ cb; }
|
||||
return(diff == 0);
|
||||
}
|
||||
|
||||
#ifndef __UCE_WASM_CORE__
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
namespace {
|
||||
const u64 UCE_PASSWORD_SCRYPT_N = 65536;
|
||||
const u64 UCE_PASSWORD_SCRYPT_R = 8;
|
||||
const u64 UCE_PASSWORD_SCRYPT_P = 1;
|
||||
|
||||
String uce_hex_encode(const unsigned char* bytes, size_t size)
|
||||
{
|
||||
String encoded;
|
||||
encoded.reserve(size * 2);
|
||||
for(size_t i = 0; i < size; i++)
|
||||
encoded += to_hex(bytes[i], 2);
|
||||
return(to_lower(encoded));
|
||||
}
|
||||
|
||||
bool uce_hex_decode(String encoded, String& decoded)
|
||||
{
|
||||
if(encoded.size() % 2 != 0)
|
||||
return(false);
|
||||
decoded.clear();
|
||||
decoded.reserve(encoded.size() / 2);
|
||||
for(size_t i = 0; i < encoded.size(); i += 2)
|
||||
{
|
||||
u8 value = 0;
|
||||
for(size_t j = 0; j < 2; j++)
|
||||
{
|
||||
char c = encoded[i + j];
|
||||
u8 digit = c >= '0' && c <= '9' ? (u8)(c - '0') : c >= 'a' && c <= 'f' ? (u8)(c - 'a' + 10) : c >= 'A' && c <= 'F' ? (u8)(c - 'A' + 10) : 255;
|
||||
if(digit == 255)
|
||||
return(false);
|
||||
value = (u8)((value << 4) | digit);
|
||||
}
|
||||
decoded.push_back((char)value);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool uce_decimal_u64(String value, u64& parsed)
|
||||
{
|
||||
if(value == "")
|
||||
return(false);
|
||||
parsed = 0;
|
||||
for(char c : value)
|
||||
{
|
||||
if(c < '0' || c > '9' || parsed > (UINT64_MAX - (u64)(c - '0')) / 10)
|
||||
return(false);
|
||||
parsed = parsed * 10 + (u64)(c - '0');
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool uce_password_parts(String encoded, u64& n, u64& r, u64& p, String& salt, String& digest)
|
||||
{
|
||||
const String prefix = "$uce$scrypt$";
|
||||
if(encoded.size() <= prefix.size() || encoded.substr(0, prefix.size()) != prefix)
|
||||
return(false);
|
||||
auto parts = split(encoded.substr(prefix.size()), "$");
|
||||
if(parts.size() != 5 || !uce_decimal_u64(parts[0], n) || !uce_decimal_u64(parts[1], r) || !uce_decimal_u64(parts[2], p))
|
||||
return(false);
|
||||
if(n < 16384 || n > UCE_PASSWORD_SCRYPT_N || (n & (n - 1)) != 0 || r < 1 || r > UCE_PASSWORD_SCRYPT_R || p != UCE_PASSWORD_SCRYPT_P || n * r > UCE_PASSWORD_SCRYPT_N * UCE_PASSWORD_SCRYPT_R)
|
||||
return(false);
|
||||
return(uce_hex_decode(parts[3], salt) && salt.size() == 16 && uce_hex_decode(parts[4], digest) && digest.size() == 32);
|
||||
}
|
||||
|
||||
bool uce_password_derive(String password, String salt, u64 n, u64 r, u64 p, unsigned char* output)
|
||||
{
|
||||
if(password.size() > 1024 * 1024)
|
||||
return(false);
|
||||
u64 max_memory = 128 * n * r + 2 * 1024 * 1024;
|
||||
return(EVP_PBE_scrypt(password.data(), password.size(), (const unsigned char*)salt.data(), salt.size(), n, r, p, max_memory, output, 32) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
String password_hash_native(String password)
|
||||
{
|
||||
unsigned char salt[16];
|
||||
unsigned char digest[32];
|
||||
if(RAND_bytes(salt, sizeof(salt)) != 1 || !uce_password_derive(password, String((const char*)salt, sizeof(salt)), UCE_PASSWORD_SCRYPT_N, UCE_PASSWORD_SCRYPT_R, UCE_PASSWORD_SCRYPT_P, digest))
|
||||
return("");
|
||||
return("$uce$scrypt$" + std::to_string(UCE_PASSWORD_SCRYPT_N) + "$" + std::to_string(UCE_PASSWORD_SCRYPT_R) + "$" + std::to_string(UCE_PASSWORD_SCRYPT_P) + "$" + uce_hex_encode(salt, sizeof(salt)) + "$" + uce_hex_encode(digest, sizeof(digest)));
|
||||
}
|
||||
|
||||
bool password_verify_native(String password, String encoded)
|
||||
{
|
||||
u64 n = 0, r = 0, p = 0;
|
||||
String salt, expected;
|
||||
if(!uce_password_parts(encoded, n, r, p, salt, expected))
|
||||
return(false);
|
||||
unsigned char digest[32];
|
||||
if(!uce_password_derive(password, salt, n, r, p, digest))
|
||||
return(false);
|
||||
return(crypto_equal_native(String((const char*)digest, sizeof(digest)), expected));
|
||||
}
|
||||
|
||||
bool password_needs_rehash_native(String encoded)
|
||||
{
|
||||
u64 n = 0, r = 0, p = 0;
|
||||
String salt, digest;
|
||||
return(!uce_password_parts(encoded, n, r, p, salt, digest) || n != UCE_PASSWORD_SCRYPT_N || r != UCE_PASSWORD_SCRYPT_R || p != UCE_PASSWORD_SCRYPT_P);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,9 @@ String sha256_hex_native(String data);
|
||||
String hmac_sha256_native(String key, String data);
|
||||
String hmac_sha256_hex_native(String key, String data);
|
||||
bool crypto_equal_native(String a, String b);
|
||||
String password_hash_native(String password);
|
||||
bool password_verify_native(String password, String encoded);
|
||||
bool password_needs_rehash_native(String encoded);
|
||||
String sha256(String data);
|
||||
String sha256_hex(String data);
|
||||
String hmac_sha256(String key, String data);
|
||||
|
||||
@@ -61,6 +61,9 @@ size_t uce_host_hmac_sha256_hex(const char* key, size_t key_len, const char* dat
|
||||
size_t uce_host_base64_encode(const char* data, size_t data_len, char* out, size_t cap);
|
||||
size_t uce_host_base64_decode(const char* data, size_t data_len, char* out, size_t cap);
|
||||
int uce_host_crypto_equal(const char* a, size_t a_len, const char* b, size_t b_len);
|
||||
size_t uce_host_password_hash(const char* password, size_t password_len, char* out, size_t cap);
|
||||
int uce_host_password_verify(const char* password, size_t password_len, const char* encoded, size_t encoded_len);
|
||||
int uce_host_password_needs_rehash(const char* encoded, size_t encoded_len);
|
||||
size_t uce_host_http_request(const char* in, size_t in_len, char* out, size_t cap);
|
||||
uint64_t uce_host_http_request_async(const char* in, size_t in_len);
|
||||
size_t uce_host_shell_exec_dv(const char* in, size_t in_len, char* out, size_t cap);
|
||||
@@ -270,6 +273,17 @@ String hmac_sha256_hex(String key, String data)
|
||||
size_t required = uce_host_hmac_sha256_hex(key.data(), key.size(), data.data(), data.size(), 0, 0);
|
||||
String out(required, 0); size_t got = required ? uce_host_hmac_sha256_hex(key.data(), key.size(), data.data(), data.size(), &out[0], required) : 0; out.resize(got <= required ? got : 0); return(out);
|
||||
}
|
||||
String password_hash(String password)
|
||||
{
|
||||
String encoded(256, 0);
|
||||
size_t got = uce_host_password_hash(password.data(), password.size(), &encoded[0], encoded.size());
|
||||
if(got > encoded.size())
|
||||
return("");
|
||||
encoded.resize(got);
|
||||
return(encoded);
|
||||
}
|
||||
bool password_verify(String password, String encoded) { return(uce_host_password_verify(password.data(), password.size(), encoded.data(), encoded.size()) != 0); }
|
||||
bool password_needs_rehash(String encoded) { return(uce_host_password_needs_rehash(encoded.data(), encoded.size()) != 0); }
|
||||
String base64_decode(String raw) { return(wasm_string_hostcall_1(uce_host_base64_decode, raw)); }
|
||||
String random_bytes(u64 n)
|
||||
{
|
||||
@@ -714,6 +728,9 @@ String hmac_sha256_hex(String key, String data) { return(hmac_sha256_hex_native(
|
||||
String base64_decode(String raw) { bool ok=false; return(::base64_decode(raw, ok)); }
|
||||
String random_bytes(u64 n) { if(n > 1024*1024) n = 1024*1024; String out(n, 0); int fd=open("/dev/urandom", O_RDONLY); if(fd<0) return(""); size_t off=0; while(off<n) { ssize_t got=read(fd, &out[off], n-off); if(got<0 && errno==EINTR) continue; if(got<=0) break; off += (size_t)got; } close(fd); out.resize(off); return(out); }
|
||||
bool crypto_equal(String a, String b) { return(crypto_equal_native(a, b)); }
|
||||
String password_hash(String password) { return(password_hash_native(password)); }
|
||||
bool password_verify(String password, String encoded) { return(password_verify_native(password, encoded)); }
|
||||
bool password_needs_rehash(String encoded) { return(password_needs_rehash_native(encoded)); }
|
||||
|
||||
// Single definitions for the native split build (declared extern in sys.h).
|
||||
pid_t parent_pid = 0;
|
||||
|
||||
@@ -43,6 +43,9 @@ String hmac_sha256_hex(String key, String data);
|
||||
String base64_decode(String raw);
|
||||
String random_bytes(u64 n);
|
||||
bool crypto_equal(String a, String b);
|
||||
String password_hash(String password);
|
||||
bool password_verify(String password, String encoded);
|
||||
bool password_needs_rehash(String encoded);
|
||||
String shell_escape(String raw);
|
||||
String basename(String fn);
|
||||
String dirname(String fn);
|
||||
|
||||
@@ -10,6 +10,9 @@ uce_host_hmac_sha256_hex
|
||||
uce_host_base64_encode
|
||||
uce_host_base64_decode
|
||||
uce_host_crypto_equal
|
||||
uce_host_password_hash
|
||||
uce_host_password_verify
|
||||
uce_host_password_needs_rehash
|
||||
uce_host_task_spawn
|
||||
uce_host_task_pid
|
||||
uce_host_task_kill
|
||||
|
||||
@@ -1767,6 +1767,12 @@ private:
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); bool ok=false; String out=base64_decode(in, ok); if(!ok) out=""; u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
||||
if(mod == "env" && name == "uce_host_crypto_equal")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String a,b; self->hostcall_read(args[0].i32(), args[1].i32(), a); self->hostcall_read(args[2].i32(), args[3].i32(), b); results[0]=Val((int32_t)(crypto_equal_native(a,b)?1:0)); return(std::monostate()); }));
|
||||
if(mod == "env" && name == "uce_host_password_hash")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password; self->hostcall_read(args[0].i32(), args[1].i32(), password); String out=password_hash_native(password); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
||||
if(mod == "env" && name == "uce_host_password_verify")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password,encoded; self->hostcall_read(args[0].i32(), args[1].i32(), password); self->hostcall_read(args[2].i32(), args[3].i32(), encoded); bool valid=password_verify_native(password,encoded); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)(valid?1:0)); return(std::monostate()); }));
|
||||
if(mod == "env" && name == "uce_host_password_needs_rehash")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); results[0]=Val((int32_t)(password_needs_rehash_native(encoded)?1:0)); return(std::monostate()); }));
|
||||
if(mod == "env" && name == "uce_host_log")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
|
||||
String text;
|
||||
|
||||
Reference in New Issue
Block a user