Skip to content

Commit 9047a1a

Browse files
committed
seal box works
1 parent 8cebb52 commit 9047a1a

File tree

3 files changed

+154
-1
lines changed

3 files changed

+154
-1
lines changed

example/lib/main.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,39 @@ class MyApp extends StatefulWidget {
1515

1616
class _MyAppState extends State<MyApp> {
1717
final wrapper = LibsodiumWrapper();
18+
final String serverPublicKeyBase64Encoded = "lKSTP8K5YQoHMZOn2+mTLunP3yMgqN1O8GyaqRvHbQE=";
19+
final plaintextController = TextEditingController();
1820

1921
String _sodiumVersion = 'Unknown Sodium Version';
22+
String _encryptedData = '';
2023

2124
@override
2225
void initState() {
2326
super.initState();
2427
getSodiumVersion();
2528
}
2629

30+
@override
31+
void dispose() {
32+
plaintextController.dispose();
33+
super.dispose();
34+
}
35+
2736
Future<void> getSodiumVersion() async {
2837
final sodiumVersion = await compute(getSodiumVersionString, wrapper);
2938
setState(() {
3039
_sodiumVersion = sodiumVersion;
3140
});
3241
}
3342

43+
Future<void> encryptData(final String plaintext) async {
44+
final encryptedData = await compute(
45+
cryptoBoxSeal, CryptoBoxSealCall(wrapper, serverPublicKeyBase64Encoded, plaintext));
46+
setState(() {
47+
_encryptedData = encryptedData;
48+
});
49+
}
50+
3451
@override
3552
Widget build(BuildContext context) {
3653
return MaterialApp(
@@ -39,7 +56,35 @@ class _MyAppState extends State<MyApp> {
3956
title: const Text('Plugin example app'),
4057
),
4158
body: Center(
42-
child: Text('Using libsodium: $_sodiumVersion', key: Key('version')),
59+
child: Column(
60+
children: [
61+
Text('Using libsodium: $_sodiumVersion', key: Key('version')),
62+
TextField(
63+
decoration: InputDecoration(
64+
border: OutlineInputBorder(),
65+
labelText: 'Text to encrypt',
66+
),
67+
key: Key('plaintextTextField'),
68+
controller: plaintextController,
69+
),
70+
SelectableText('Encrypted data: $_encryptedData', key: Key('encryptedData')),
71+
FlatButton(
72+
color: Colors.blue,
73+
textColor: Colors.white,
74+
disabledColor: Colors.grey,
75+
disabledTextColor: Colors.black,
76+
padding: EdgeInsets.all(8.0),
77+
splashColor: Colors.blueAccent,
78+
onPressed: () async {
79+
encryptData(plaintextController.text);
80+
},
81+
child: Text(
82+
"Encrypt",
83+
style: TextStyle(fontSize: 20.0),
84+
),
85+
)
86+
],
87+
),
4388
),
4489
),
4590
);

lib/libsodium_bindings.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,37 @@ typedef NativeVersionString = Pointer<Utf8> Function();
2626
typedef VersionString = Pointer<Utf8> Function();
2727
final VersionString sodiumVersionString =
2828
libsodium.lookupFunction<NativeVersionString, VersionString>('sodium_version_string');
29+
30+
// IntPtr is sign-extended, but we know size_t is unsigned so that's OK
31+
// see: https://github.com/dart-lang/sdk/issues/39372
32+
// see: https://github.com/dart-lang/sdk/issues/36140
33+
// see: https://github.com/jedisct1/libsodium/blob/927dfe8/src/libsodium/crypto_secretbox/crypto_secretbox.c#L6
34+
// see: https://github.com/dart-lang/sdk/blob/48f7636798260bcf84bab46fd3c92102c508e959/runtime/tools/dartfuzz/dartfuzz_ffi_api.dart
35+
typedef NativeReturnSizeT = IntPtr Function();
36+
37+
final int Function() crypto_box_SEALBYTES =
38+
libsodium.lookup<NativeFunction<NativeReturnSizeT>>('crypto_box_sealbytes').asFunction();
39+
40+
// libsodium uses canary pages, guard pages, memory locking, and fills malloc'd memory with a
41+
// specific byte pattern [1]. Whenever allocating memory in order to interact with libsodium
42+
// we prefer to use libsodium's sodium_malloc and sodium_free.
43+
//
44+
// [1] https://doc.libsodium.org/memory_management
45+
typedef Malloc = Pointer<Uint8> Function(int size);
46+
typedef NativeMalloc = Pointer<Uint8> Function(IntPtr size);
47+
final Malloc sodiumMalloc = libsodium.lookupFunction<NativeMalloc, Malloc>('sodium_malloc');
48+
49+
// https://doc.libsodium.org/memory_management
50+
typedef Free = void Function(Pointer<Uint8> ptr);
51+
typedef NativeFree = Void Function(Pointer<Uint8> ptr);
52+
final Free sodiumFree = libsodium.lookupFunction<NativeFree, Free>('sodium_free');
53+
54+
// https://doc.libsodium.org/public-key_cryptography/sealed_boxes
55+
// int crypto_box_seal(unsigned char *c, const unsigned char *m,
56+
// unsigned long long mlen, const unsigned char *pk);
57+
typedef CryptoBoxSeal = int Function(
58+
Pointer<Uint8> c, Pointer<Uint8> m, int mlen, Pointer<Uint8> pk);
59+
typedef NativeCryptoBoxSeal = Int32 Function(
60+
Pointer<Uint8> c, Pointer<Uint8> m, Uint64 mlen, Pointer<Uint8> pk);
61+
final CryptoBoxSeal cryptoBoxSeal =
62+
libsodium.lookupFunction<NativeCryptoBoxSeal, CryptoBoxSeal>('crypto_box_seal');

lib/libsodium_wrapper.dart

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import 'dart:convert';
2+
import 'dart:ffi';
3+
import 'dart:io';
4+
import 'dart:typed_data';
5+
16
import 'package:ffi/ffi.dart';
27
import 'package:flutter_libsodium/libsodium_bindings.dart' as bindings;
38

@@ -19,6 +24,75 @@ class LibsodiumWrapper {
1924
String sodiumVersionString() {
2025
return Utf8.fromUtf8(bindings.sodiumVersionString());
2126
}
27+
28+
// https://doc.libsodium.org/public-key_cryptography/sealed_boxes
29+
String cryptoBoxSeal(final String recipientPublicKeyBase64Encoded, final String plaintext) {
30+
final int cryptoBoxSealBytes = bindings.crypto_box_SEALBYTES();
31+
final cLength = plaintext.length + cryptoBoxSealBytes;
32+
final c = bindings.sodiumMalloc(cLength);
33+
final m = plaintext.toUint8Pointer();
34+
final Uint8List recipientPublicKey = base64.decode(recipientPublicKeyBase64Encoded);
35+
final pk = recipientPublicKey.toPointer();
36+
try {
37+
bindings.cryptoBoxSeal(c, m, plaintext.length, pk);
38+
final Uint8List result = c.toList(cLength);
39+
return base64.encode(result);
40+
} finally {
41+
bindings.sodiumFree(c);
42+
bindings.sodiumFree(m);
43+
bindings.sodiumFree(pk);
44+
}
45+
}
2246
}
2347

2448
String getSodiumVersionString(final LibsodiumWrapper wrapper) => wrapper.sodiumVersionString();
49+
50+
// Can't pass more than one argument via compute, so use a custom object instead.
51+
//
52+
// See: https://github.com/flutter/flutter/issues/34540
53+
// See: https://stackoverflow.com/questions/54074857/send-multiple-arguments-to-the-compute-function-in-flutter
54+
class CryptoBoxSealCall {
55+
final LibsodiumWrapper wrapper;
56+
final String recipientPublicKeyBase64Encoded;
57+
final String plaintext;
58+
59+
CryptoBoxSealCall(this.wrapper, this.recipientPublicKeyBase64Encoded, this.plaintext);
60+
}
61+
62+
String cryptoBoxSeal(final CryptoBoxSealCall call) =>
63+
call.wrapper.cryptoBoxSeal(call.recipientPublicKeyBase64Encoded, call.plaintext);
64+
65+
extension Uint8PointerExtensions on Pointer<Uint8> {
66+
Uint8List toList(int length) {
67+
final builder = BytesBuilder();
68+
for (int i = 0; i < length; i++) {
69+
builder.addByte(this[i]);
70+
}
71+
return builder.takeBytes();
72+
}
73+
}
74+
75+
extension Uint8ListExtensions on Uint8List {
76+
Pointer<Uint8> toPointer() {
77+
if (this == null) {
78+
return Pointer<Uint8>.fromAddress(0);
79+
}
80+
final p = bindings.sodiumMalloc(this.length);
81+
final pList = p.asTypedList(this.length);
82+
pList.setAll(0, this);
83+
return p;
84+
}
85+
}
86+
87+
extension StringExtensions on String {
88+
Pointer<Uint8> toUint8Pointer() {
89+
if (this == null) {
90+
return Pointer<Uint8>.fromAddress(0);
91+
}
92+
final units = utf8.encode(this);
93+
final Pointer<Uint8> result = bindings.sodiumMalloc(units.length);
94+
final Uint8List nativeString = result.asTypedList(units.length);
95+
nativeString.setAll(0, units);
96+
return result;
97+
}
98+
}

0 commit comments

Comments
 (0)