Skip to content

Commit a3658fb

Browse files
committed
Improve argument passing to C++ and fix function signatures
1 parent d75c23b commit a3658fb

File tree

5 files changed

+142
-128
lines changed

5 files changed

+142
-128
lines changed

argon2.cjs

+61-38
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const names = Object.freeze({
2929

3030
const defaults = Object.freeze({
3131
hashLength: 32,
32-
saltLength: 16,
3332
timeCost: 3,
3433
memoryCost: 1 << 16,
3534
parallelism: 4,
@@ -51,7 +50,6 @@ module.exports.limits = limits;
5150
* @property {number} [timeCost=3]
5251
* @property {number} [memoryCost=65536]
5352
* @property {number} [parallelism=4]
54-
* @property {number} [saltLength=16]
5553
* @property {keyof typeof names} [type=argon2id]
5654
* @property {number} [version=19]
5755
* @property {Buffer} [salt]
@@ -63,25 +61,24 @@ module.exports.limits = limits;
6361
* Hashes a password with Argon2, producing a raw hash
6462
*
6563
* @overload
66-
* @param {Buffer | string} plain The plaintext password to be hashed
64+
* @param {Buffer | string} password The plaintext password to be hashed
6765
* @param {Options & { raw: true }} options The parameters for Argon2
68-
* @return {Promise<Buffer>} The raw hash generated from `plain`
66+
* @returns {Promise<Buffer>} The raw hash generated from `plain`
6967
*/
7068
/**
7169
* Hashes a password with Argon2, producing an encoded hash
7270
*
7371
* @overload
74-
* @param {Buffer | string} plain The plaintext password to be hashed
72+
* @param {Buffer | string} password The plaintext password to be hashed
7573
* @param {Options & { raw?: boolean }} [options] The parameters for Argon2
76-
* @return {Promise<string>} The encoded hash generated from `plain`
74+
* @returns {Promise<string>} The encoded hash generated from `plain`
7775
*/
7876
/**
79-
* @param {Buffer | string} plain The plaintext password to be hashed
77+
* @param {Buffer | string} password The plaintext password to be hashed
8078
* @param {Options & { raw?: boolean }} [options] The parameters for Argon2
81-
* @returns {Promise<Buffer | string>} The raw or encoded hash generated from `plain`
8279
*/
83-
module.exports.hash = async function (plain, options) {
84-
const { raw, salt, saltLength, ...rest } = { ...defaults, ...options };
80+
async function hash(password, options) {
81+
let { raw, salt, ...rest } = { ...defaults, ...options };
8582

8683
for (const [key, { min, max }] of Object.entries(limits)) {
8784
const value = rest[key];
@@ -91,37 +88,54 @@ module.exports.hash = async function (plain, options) {
9188
);
9289
}
9390

94-
const salt_ = salt ?? (await generateSalt(saltLength));
95-
96-
const hash = await bindingsHash(Buffer.from(plain), salt_, rest);
97-
if (raw) {
98-
return hash;
99-
}
91+
salt = salt ?? (await generateSalt(16));
10092

10193
const {
94+
hashLength,
95+
secret = Buffer.alloc(0),
10296
type,
10397
version,
10498
memoryCost: m,
10599
timeCost: t,
106100
parallelism: p,
107-
associatedData: data,
101+
associatedData: data = Buffer.alloc(0),
108102
} = rest;
109103

104+
const hash = await bindingsHash({
105+
password: Buffer.from(password),
106+
salt,
107+
secret,
108+
data,
109+
hashLength,
110+
m,
111+
t,
112+
p,
113+
version,
114+
type,
115+
});
116+
if (raw) {
117+
return hash;
118+
}
119+
110120
return serialize({
111121
id: names[type],
112122
version,
113-
params: { m, t, p, ...(data ? { data } : {}) },
114-
salt: salt_,
123+
params: { m, t, p, ...(data.byteLength > 0 ? { data } : {}) },
124+
salt,
115125
hash,
116126
});
117-
};
127+
}
128+
module.exports.hash = hash;
118129

119130
/**
120131
* @param {string} digest The digest to be checked
121-
* @param {Options} [options] The current parameters for Argon2
122-
* @return {boolean} `true` if the digest parameters do not match the parameters in `options`, otherwise `false`
132+
* @param {Object} [options] The current parameters for Argon2
133+
* @param {number} [options.timeCost=3]
134+
* @param {number} [options.memoryCost=65536]
135+
* @param {number} [options.parallelism=4]
136+
* @returns {boolean} `true` if the digest parameters do not match the parameters in `options`, otherwise `false`
123137
*/
124-
module.exports.needsRehash = function (digest, options) {
138+
function needsRehash(digest, options = {}) {
125139
const { memoryCost, timeCost, version } = { ...defaults, ...options };
126140

127141
const {
@@ -130,38 +144,47 @@ module.exports.needsRehash = function (digest, options) {
130144
} = deserialize(digest);
131145

132146
return +v !== +version || +m !== +memoryCost || +t !== +timeCost;
133-
};
147+
}
148+
module.exports.needsRehash = needsRehash;
134149

135150
/**
136151
* @param {string} digest The digest to be checked
137-
* @param {Buffer | string} plain The plaintext password to be verified
138-
* @param {Options} [options] The current parameters for Argon2
139-
* @return {Promise<boolean>} `true` if the digest parameters matches the hash generated from `plain`, otherwise `false`
152+
* @param {Buffer | string} password The plaintext password to be verified
153+
* @param {Object} [options] The current parameters for Argon2
154+
* @param {Buffer} [options.secret]
155+
* @returns {Promise<boolean>} `true` if the digest parameters matches the hash generated from `plain`, otherwise `false`
140156
*/
141-
module.exports.verify = async function (digest, plain, options) {
157+
async function verify(
158+
digest,
159+
password,
160+
{ secret } = { secret: Buffer.alloc(0) },
161+
) {
142162
const { id, ...rest } = deserialize(digest);
143163
if (!(id in types)) {
144164
return false;
145165
}
146166

147167
const {
148168
version = 0x10,
149-
params: { m, t, p, data },
169+
params: { m, t, p, data = "" },
150170
salt,
151171
hash,
152172
} = rest;
153173

154174
return timingSafeEqual(
155-
await bindingsHash(Buffer.from(plain), salt, {
156-
...options,
157-
type: types[id],
175+
await bindingsHash({
176+
password: Buffer.from(password),
177+
salt,
178+
secret,
179+
data: Buffer.from(data, "base64"),
180+
hashLength: hash.byteLength,
181+
m: +m,
182+
t: +t,
183+
p: +p,
158184
version: +version,
159-
hashLength: hash.length,
160-
memoryCost: +m,
161-
timeCost: +t,
162-
parallelism: +p,
163-
...(data ? { associatedData: Buffer.from(data, "base64") } : {}),
185+
type: types[id],
164186
}),
165187
hash,
166188
);
167-
};
189+
}
190+
module.exports.verify = verify;

argon2_node.cpp

+67-79
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,52 @@
44
#include <napi.h>
55
#include <vector>
66

7-
using ustring = std::vector<uint8_t>;
8-
9-
static ustring from_buffer(const Napi::Value &value) {
10-
const auto &buf = value.As<Napi::Buffer<uint8_t>>();
11-
const auto &data = buf.Data();
12-
return {data, data + buf.Length()};
13-
}
14-
15-
struct Options {
16-
ustring secret;
17-
ustring ad;
18-
19-
uint32_t hash_length;
20-
uint32_t time_cost;
21-
uint32_t memory_cost;
22-
uint32_t parallelism;
23-
uint32_t version;
24-
25-
argon2_type type;
26-
};
27-
28-
static argon2_context make_context(uint8_t *buf, ustring &plain, ustring &salt,
29-
Options &opts) {
30-
argon2_context ctx;
31-
32-
ctx.out = buf;
33-
ctx.outlen = opts.hash_length;
34-
ctx.pwd = plain.data();
35-
ctx.pwdlen = static_cast<uint32_t>(plain.size());
36-
ctx.salt = salt.data();
37-
ctx.saltlen = static_cast<uint32_t>(salt.size());
38-
ctx.secret = opts.secret.empty() ? nullptr : opts.secret.data();
39-
ctx.secretlen = static_cast<uint32_t>(opts.secret.size());
40-
ctx.ad = opts.ad.empty() ? nullptr : opts.ad.data();
41-
ctx.adlen = static_cast<uint32_t>(opts.ad.size());
42-
ctx.t_cost = opts.time_cost;
43-
ctx.m_cost = opts.memory_cost;
44-
ctx.lanes = opts.parallelism;
45-
ctx.threads = opts.parallelism;
46-
ctx.allocate_cbk = nullptr;
47-
ctx.free_cbk = nullptr;
48-
ctx.flags = ARGON2_FLAG_CLEAR_PASSWORD | ARGON2_FLAG_CLEAR_SECRET;
49-
ctx.version = opts.version;
50-
51-
return ctx;
52-
}
7+
namespace {
538

549
class HashWorker final : public Napi::AsyncWorker {
5510
public:
56-
HashWorker(const Napi::Env &env, ustring &&plain, ustring &&salt,
57-
Options &&opts)
11+
HashWorker(const Napi::Env &env, const Napi::Buffer<uint8_t> &plain,
12+
const Napi::Buffer<uint8_t> &salt,
13+
const Napi::Buffer<uint8_t> &secret,
14+
const Napi::Buffer<uint8_t> &ad, uint32_t hash_length,
15+
uint32_t memory_cost, uint32_t time_cost, uint32_t parallelism,
16+
uint32_t version, uint32_t type)
5817
: AsyncWorker{env, "argon2:HashWorker"}, deferred{env},
59-
plain{std::move(plain)}, salt{std::move(salt)},
60-
opts{std::move(opts)} {}
18+
plain{plain.Data(), plain.Data() + plain.ByteLength()},
19+
salt{salt.Data(), salt.Data() + salt.ByteLength()},
20+
secret{secret.Data(), secret.Data() + secret.ByteLength()},
21+
ad{ad.Data(), ad.Data() + ad.ByteLength()}, hash_length{hash_length},
22+
memory_cost{memory_cost}, time_cost{time_cost},
23+
parallelism{parallelism}, version{version},
24+
type{static_cast<argon2_type>(type)} {}
6125

6226
Napi::Promise GetPromise() { return deferred.Promise(); }
6327

6428
protected:
6529
void Execute() override {
66-
hash = std::make_unique<uint8_t[]>(opts.hash_length);
67-
68-
auto ctx = make_context(hash.get(), plain, salt, opts);
69-
int result = argon2_ctx(&ctx, opts.type);
70-
71-
if (result != ARGON2_OK) {
30+
hash.resize(hash_length);
31+
32+
argon2_context ctx;
33+
ctx.out = hash.data();
34+
ctx.outlen = static_cast<uint32_t>(hash.size());
35+
ctx.pwd = plain.data();
36+
ctx.pwdlen = static_cast<uint32_t>(plain.size());
37+
ctx.salt = salt.data();
38+
ctx.saltlen = static_cast<uint32_t>(salt.size());
39+
ctx.secret = secret.empty() ? nullptr : secret.data();
40+
ctx.secretlen = static_cast<uint32_t>(secret.size());
41+
ctx.ad = ad.empty() ? nullptr : ad.data();
42+
ctx.adlen = static_cast<uint32_t>(ad.size());
43+
ctx.m_cost = memory_cost;
44+
ctx.t_cost = time_cost;
45+
ctx.lanes = parallelism;
46+
ctx.threads = parallelism;
47+
ctx.allocate_cbk = nullptr;
48+
ctx.free_cbk = nullptr;
49+
ctx.flags = ARGON2_FLAG_CLEAR_PASSWORD | ARGON2_FLAG_CLEAR_SECRET;
50+
ctx.version = version;
51+
52+
if (int result = argon2_ctx(&ctx, type); result != ARGON2_OK) {
7253
/* LCOV_EXCL_START */
7354
SetError(argon2_error_message(result));
7455
/* LCOV_EXCL_STOP */
@@ -77,43 +58,48 @@ class HashWorker final : public Napi::AsyncWorker {
7758

7859
void OnOK() override {
7960
deferred.Resolve(
80-
Napi::Buffer<uint8_t>::Copy(Env(), hash.get(), opts.hash_length));
61+
Napi::Buffer<uint8_t>::Copy(Env(), hash.data(), hash.size()));
8162
}
8263

8364
void OnError(const Napi::Error &err) override {
8465
deferred.Reject(err.Value());
8566
}
8667

8768
private:
69+
using ustring = std::basic_string<uint8_t>;
70+
8871
Napi::Promise::Deferred deferred;
72+
ustring hash;
73+
8974
ustring plain;
9075
ustring salt;
91-
Options opts;
76+
ustring secret;
77+
ustring ad;
9278

93-
std::unique_ptr<uint8_t[]> hash;
94-
};
79+
uint32_t hash_length;
80+
uint32_t memory_cost;
81+
uint32_t time_cost;
82+
uint32_t parallelism;
83+
uint32_t version;
9584

96-
static Options extract_opts(const Napi::Object &opts) {
97-
return {
98-
opts.Has("secret") ? from_buffer(opts["secret"]) : ustring{},
99-
opts.Has("associatedData") ? from_buffer(opts["associatedData"])
100-
: ustring{},
101-
opts["hashLength"].ToNumber(),
102-
opts["timeCost"].ToNumber(),
103-
opts["memoryCost"].ToNumber(),
104-
opts["parallelism"].ToNumber(),
105-
opts["version"].ToNumber(),
106-
argon2_type(int(opts["type"].ToNumber())),
107-
};
108-
}
85+
argon2_type type;
86+
};
10987

11088
static Napi::Value Hash(const Napi::CallbackInfo &info) {
111-
assert(info.Length() == 4 && info[0].IsBuffer() && info[1].IsBuffer() &&
112-
info[2].IsObject());
113-
114-
auto worker =
115-
new HashWorker{info.Env(), from_buffer(info[0]), from_buffer(info[1]),
116-
extract_opts(info[2].As<Napi::Object>())};
89+
NAPI_CHECK(info.Length() == 1, "Hash", "expected 1 argument");
90+
91+
const auto &args = info[0].As<Napi::Object>();
92+
auto worker = new HashWorker{info.Env(),
93+
args["password"].As<Napi::Buffer<uint8_t>>(),
94+
args["salt"].As<Napi::Buffer<uint8_t>>(),
95+
args["secret"].As<Napi::Buffer<uint8_t>>(),
96+
args["data"].As<Napi::Buffer<uint8_t>>(),
97+
args["hashLength"].ToNumber(),
98+
args["m"].ToNumber(),
99+
args["t"].ToNumber(),
100+
args["p"].ToNumber(),
101+
args["version"].ToNumber(),
102+
args["type"].ToNumber()};
117103

118104
worker->Queue();
119105
return worker->GetPromise();
@@ -124,4 +110,6 @@ static Napi::Object init(Napi::Env env, Napi::Object exports) {
124110
return exports;
125111
}
126112

113+
} // namespace
114+
127115
NODE_API_MODULE(argon2_lib, init)

binding.gyp

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
"cflags": ["--coverage"],
7070
"ldflags": ["-fprofile-arcs", "-ftest-coverage"]
7171
}]
72-
]
72+
],
73+
"defines+": ["NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS"]
7374
}
7475
}
7576
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@
6060
"node-gyp-build": "^4.8.0"
6161
},
6262
"devDependencies": {
63-
"@types/node": "^20.11.5",
63+
"@types/node": "^20.11.17",
6464
"node-gyp": "^10.0.1",
6565
"prebuildify": "^6.0.0",
66-
"prettier": "^3.2.4",
66+
"prettier": "^3.2.5",
6767
"typescript": "^5.3.3"
6868
},
6969
"engines": {

0 commit comments

Comments
 (0)