Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reintroduce crypto_pwhash #52

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ import {
crypto_kdf_derive_from_key,
crypto_kdf_KEYBYTES,
crypto_kdf_keygen,
crypto_pwhash, // only with loadSumoVersion with react-native-web
crypto_pwhash_ALG_DEFAULT, // only with loadSumoVersion with react-native-web
crypto_pwhash_MEMLIMIT_INTERACTIVE, // only with loadSumoVersion with react-native-web
crypto_pwhash_OPSLIMIT_INTERACTIVE, // only with loadSumoVersion with react-native-web
crypto_pwhash_SALTBYTES, // only with loadSumoVersion with react-native-web
crypto_generichash,
crypto_generichash_BYTES,
crypto_generichash_BYTES_MIN,
Expand All @@ -83,11 +88,25 @@ import {
to_base64,
to_hex,
to_string,
ready, // only needed for react-native-web
loadSumoVersion, // only relevant for react-native-web
} from 'react-native-libsodium';

// ...
```

## React Native Web

For the web platform the constants and functions from the `libsodium-wrappers` package is exposed. This also means you need to wait for the `ready` Promise to be resolved before using any constant or function.

Certain constants and functions e.g. `crypto_pwhash` are only available in the `libsodium-wrappers-sumo` package. To load this package instead for web you can call `loadSumoVersion` right after importing the package.

```ts
import { loadSumoVersion, ready } from 'react-native-libsodium';

loadSumoVersion();
```

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
81 changes: 81 additions & 0 deletions cpp/react-native-libsodium.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ namespace ReactNativeLibsodium
{
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_secretbox_KEYBYTES", static_cast<int>(crypto_secretbox_KEYBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_secretbox_NONCEBYTES", static_cast<int>(crypto_secretbox_NONCEBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_SALTBYTES", static_cast<int>(crypto_pwhash_SALTBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_ALG_DEFAULT", static_cast<int>(crypto_pwhash_ALG_DEFAULT));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_OPSLIMIT_INTERACTIVE", static_cast<int>(crypto_pwhash_OPSLIMIT_INTERACTIVE));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_MEMLIMIT_INTERACTIVE", static_cast<int>(crypto_pwhash_MEMLIMIT_INTERACTIVE));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_PUBLICKEYBYTES", static_cast<int>(crypto_box_PUBLICKEYBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_SECRETKEYBYTES", static_cast<int>(crypto_box_SECRETKEYBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_NONCEBYTES", static_cast<int>(crypto_box_NONCEBYTES));
Expand All @@ -198,6 +202,8 @@ namespace ReactNativeLibsodium
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_generichash_BYTES_MIN", static_cast<int>(crypto_generichash_BYTES_MIN));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_generichash_BYTES_MAX", static_cast<int>(crypto_generichash_BYTES_MAX));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_generichash_KEYBYTES", static_cast<int>(crypto_generichash_KEYBYTES));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_BYTES_MAX", static_cast<int>(crypto_pwhash_BYTES_MAX));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash_BYTES_MIN", static_cast<int>(crypto_pwhash_BYTES_MIN));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_generichash_KEYBYTES_MIN", static_cast<int>(crypto_generichash_KEYBYTES_MIN));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_generichash_KEYBYTES_MAX", static_cast<int>(crypto_generichash_KEYBYTES_MAX));
jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_sign_SEEDBYTES", static_cast<int>(crypto_sign_SEEDBYTES));
Expand Down Expand Up @@ -959,6 +965,81 @@ namespace ReactNativeLibsodium

jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_box_open_easy", std::move(jsi_crypto_box_open_easy));

auto jsi_crypto_pwhash = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_pwhash"),
6,
[](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value
{
const std::string functionName = "crypto_pwhash";

std::string keyLengthArgumentName = "keyLength";
unsigned int keyLengthArgumentPosition = 0;
validateIsNumber(functionName, runtime, arguments[keyLengthArgumentPosition], keyLengthArgumentName, true);

std::string passwordArgumentName = "password";
unsigned int passwordArgumentPosition = 1;
JsiArgType passwordArgType = validateIsStringOrArrayBuffer(functionName, runtime, arguments[passwordArgumentPosition], passwordArgumentName, true);

std::string saltArgumentName = "salt";
unsigned int saltArgumentPosition = 2;
validateIsArrayBuffer(functionName, runtime, arguments[saltArgumentPosition], saltArgumentName, true);

std::string opsLimitArgumentName = "opsLimit";
unsigned int opsLimitArgumentPosition = 3;
validateIsNumber(functionName, runtime, arguments[opsLimitArgumentPosition], opsLimitArgumentName, true);

std::string memLimitArgumentName = "memLimit";
unsigned int memLimitArgumentPosition = 4;
validateIsNumber(functionName, runtime, arguments[memLimitArgumentPosition], memLimitArgumentName, true);

std::string algorithmArgumentName = "algorithm";
unsigned int algorithmArgumentPosition = 5;
validateIsNumber(functionName, runtime, arguments[algorithmArgumentPosition], algorithmArgumentName, true);

int keyLength = arguments[keyLengthArgumentPosition].asNumber();
auto saltDataArrayBuffer =
arguments[saltArgumentPosition].asObject(runtime).getArrayBuffer(runtime);
int opsLimit = arguments[opsLimitArgumentPosition].asNumber();
int memLimit = arguments[memLimitArgumentPosition].asNumber();
int algorithm = arguments[algorithmArgumentPosition].asNumber();
std::vector<uint8_t> key(keyLength);

int result = -1;
if (passwordArgType == JsiArgType::string)
{
std::string passwordString = arguments[passwordArgumentPosition].asString(runtime).utf8(runtime);
result = crypto_pwhash(
key.data(),
keyLength,
reinterpret_cast<const char *>(passwordString.data()),
passwordString.length(),
saltDataArrayBuffer.data(runtime),
opsLimit,
memLimit,
algorithm);
}
else
{
auto passwordArrayBuffer =
arguments[passwordArgumentPosition].asObject(runtime).getArrayBuffer(runtime);
result = crypto_pwhash(
key.data(),
keyLength,
reinterpret_cast<const char *>(passwordArrayBuffer.data(runtime)),
passwordArrayBuffer.length(runtime),
saltDataArrayBuffer.data(runtime),
opsLimit,
memLimit,
algorithm);
}

throwOnBadResult(functionName, runtime, result);
return arrayBufferAsObject(runtime, key);
});

jsiRuntime.global().setProperty(jsiRuntime, "jsi_crypto_pwhash", std::move(jsi_crypto_pwhash));

auto jsi_crypto_kdf_derive_from_key = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forUtf8(jsiRuntime, "jsi_crypto_kdf_derive_from_key"),
Expand Down
4 changes: 3 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react';
import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
import sodium, { ready } from 'react-native-libsodium';
import sodium, { loadSumoVersion, ready } from 'react-native-libsodium';
import { TestResults } from './components/TestResults';
import { VisualImageTest } from './components/VisualImageTest';

loadSumoVersion();

function LibsodiumTests() {
if (sodium.crypto_secretbox_KEYBYTES !== 32) {
throw new Error('export default not working');
Expand Down
1 change: 1 addition & 0 deletions example/src/components/TestResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import '../tests/crypto_generichash_test';
import '../tests/crypto_kdf_derive_from_key_test';
import '../tests/crypto_kdf_keygen_test';
import '../tests/crypto_pwhash_test';
import '../tests/crypto_secretbox_easy_test';
import '../tests/crypto_secretbox_keygen_test';
import '../tests/crypto_secretbox_open_easy_test';
Expand Down Expand Up @@ -43,9 +44,9 @@
}, []);

return (
<View style={{ flexDirection: 'column', gap: 8, padding: 16 }}>

Check warning on line 47 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { flexDirection: 'column', gap: 8, padding: 16 }
{allTestsPassed != null && (
<Text style={{ fontSize: 20 }}>

Check warning on line 49 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { fontSize: 20 }
{allTestsPassed ? 'Tests passed' : 'Tests failed'}
</Text>
)}
Expand All @@ -55,13 +56,13 @@
.concat(result.test.description)
.join(' / ');
return (
<View key={result.test.id} style={{ gap: 8 }}>

Check warning on line 59 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { gap: 8 }
<View style={{ flexDirection: 'row', gap: 8 }}>

Check warning on line 60 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { flexDirection: 'row', gap: 8 }
<Text>{!result.success ? '❌' : '✅'}</Text>
<Text style={{ fontSize: 16 }}>{title}</Text>

Check warning on line 62 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { fontSize: 16 }
</View>
{!result.success && (
<Text style={{ fontSize: 16, color: 'red' }}>

Check warning on line 65 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { fontSize: 16, color: 'red' }
{'' + result.error}
</Text>
)}
Expand All @@ -69,8 +70,8 @@
typeof result.error === 'object' &&
result.error &&
'stack' in result.error && (
<View style={{ backgroundColor: '#ddd', padding: 16 }}>

Check warning on line 73 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { backgroundColor: '#ddd', padding: 16 }
<Text style={{ fontFamily: 'monospace' }}>

Check warning on line 74 in example/src/components/TestResults.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { fontFamily: 'monospace' }
{result.error.stack}
</Text>
</View>
Expand Down
106 changes: 106 additions & 0 deletions example/src/tests/crypto_pwhash_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
crypto_pwhash,
crypto_pwhash_ALG_DEFAULT,
crypto_pwhash_BYTES_MIN,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_SALTBYTES,
randombytes_buf,
} from 'react-native-libsodium';
import { isEqualUint8Array } from '../utils/isEqualUint8Array';
import { expect, test } from '../utils/testRunner';

test('crypto_pwhash', () => {
const password = 'password123';
const salt = new Uint8Array([
149, 177, 121, 247, 17, 38, 67, 49, 150, 68, 118, 228, 16, 98, 110, 175,
]);
const randomSalt = randombytes_buf(crypto_pwhash_SALTBYTES);

expect(() =>
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
password,
salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
200
)
).toThrow();

expect(() =>
crypto_pwhash(
15,
password,
salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
)
).toThrow();

expect(() =>
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
password,
new Uint8Array([10]),
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
)
).toThrow();

expect(
isEqualUint8Array(
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
password,
salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
),
new Uint8Array([
75, 92, 252, 55, 217, 21, 210, 156, 216, 33, 97, 101, 153, 119, 14, 177,
])
)
).toBe(true);

expect(
isEqualUint8Array(
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
new Uint8Array([100, 100, 100]),
salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
),
new Uint8Array([
136, 232, 104, 134, 32, 193, 138, 249, 192, 236, 243, 239, 248, 70, 117,
160,
])
)
).toBe(true);

expect(
isEqualUint8Array(
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
password,
randomSalt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
),
crypto_pwhash(
crypto_pwhash_BYTES_MIN,
password,
randomSalt,
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT
)
)
).toBe(true);
});
2 changes: 2 additions & 0 deletions example/web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const babelLoaderConfiguration = {
path.resolve(appDirectory, 'index.js'),
path.resolve(appDirectory, 'src'),
path.resolve(appDirectory, 'node_modules/react-native-uncompiled'),
// make sure the lib src files are also compiled
path.resolve(appDirectory, '../src'),
],
use: {
loader: 'babel-loader',
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
},
"dependencies": {
"@types/libsodium-wrappers": "^0.7.13",
"libsodium-wrappers": "^0.7.13"
"@types/libsodium-wrappers-sumo": "^0.7.8",
"libsodium-wrappers": "^0.7.13",
"libsodium-wrappers-sumo": "^0.7.13"
}
}
Loading
Loading