Skip to content
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
22 changes: 22 additions & 0 deletions ios/m_security/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "m_security",
platforms: [
.iOS("11.0")
],
products: [
.library(name: "m-security", targets: ["m_security"])
],
dependencies: [],
targets: [
.target(
name: "m_security",
dependencies: [],
resources: [
.process("PrivacyInfo.xcprivacy")
]
)
]
)
14 changes: 14 additions & 0 deletions ios/m_security/Sources/m_security/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
1 change: 1 addition & 0 deletions ios/m_security/Sources/m_security/dummy_file.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This is an empty file to satisfy Swift Package Manager requirements.
2 changes: 1 addition & 1 deletion lib/m_security.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export 'src/kdf/hkdf.dart';
export 'src/rust/frb_generated.dart' show RustLib;
export 'src/streaming/streaming_service.dart';
export 'src/compression/compression_service.dart';
export 'src/evfs/vault_service.dart';
export 'src/evfs/vault_service.dart';
61 changes: 34 additions & 27 deletions lib/src/compression/compression_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ class CompressionService {
algorithm: CompressionAlgorithm.zstd,
),
}) {
return _guardedStream(() => rust_streaming.streamCompressEncryptFile(
cipher: cipher,
compression: config,
inputPath: inputPath,
outputPath: outputPath,
));
return _guardedStream(
() => rust_streaming.streamCompressEncryptFile(
cipher: cipher,
compression: config,
inputPath: inputPath,
outputPath: outputPath,
),
);
}

/// Decrypt then decompress a file.
Expand All @@ -37,31 +39,36 @@ class CompressionService {
required String outputPath,
required rust_encryption.CipherHandle cipher,
}) {
return _guardedStream(() => rust_streaming.streamDecryptDecompressFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
));
return _guardedStream(
() => rust_streaming.streamDecryptDecompressFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
),
);
}

static Stream<double> _guardedStream(Stream<double> Function() factory) {
final controller = StreamController<double>();
runZonedGuarded(() {
factory().listen(
controller.add,
onError: controller.addError,
onDone: () {
Future(() {
if (!controller.isClosed) controller.close();
});
},
);
}, (error, stack) {
if (!controller.isClosed) {
controller.addError(error, stack);
controller.close();
}
});
runZonedGuarded(
() {
factory().listen(
controller.add,
onError: controller.addError,
onDone: () {
Future(() {
if (!controller.isClosed) controller.close();
});
},
);
},
(error, stack) {
if (!controller.isClosed) {
controller.addError(error, stack);
controller.close();
}
},
);
return controller.stream;
}
}
9 changes: 6 additions & 3 deletions lib/src/encryption/aes_gcm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:typed_data';
import 'package:m_security/src/rust/api/encryption.dart' as rust_encryption;

class AesGcmService {

rust_encryption.CipherHandle? _cipher;

Future<void> initWithRandomKey() async {
Expand All @@ -14,7 +13,9 @@ class AesGcmService {
Future<Uint8List> encrypt(Uint8List data) async {
final cipher = _cipher;
if (cipher == null) {
throw StateError('Cipher not initialized. Call initWithRandomKey() first.');
throw StateError(
'Cipher not initialized. Call initWithRandomKey() first.',
);
}

return rust_encryption.encrypt(
Expand All @@ -27,7 +28,9 @@ class AesGcmService {
Future<Uint8List> decrypt(Uint8List encrypted) async {
final cipher = _cipher;
if (cipher == null) {
throw StateError('Cipher not initialized. Call initWithRandomKey() first.');
throw StateError(
'Cipher not initialized. Call initWithRandomKey() first.',
);
}

return rust_encryption.decrypt(
Expand Down
9 changes: 6 additions & 3 deletions lib/src/encryption/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:typed_data';
import 'package:m_security/src/rust/api/encryption.dart' as rust_encryption;

class Chacha20Service {

rust_encryption.CipherHandle? _cipher;

Future<void> initWithRandomKey() async {
Expand All @@ -14,7 +13,9 @@ class Chacha20Service {
Future<Uint8List> encrypt(Uint8List plaintext, {Uint8List? aad}) async {
final cipher = _cipher;
if (cipher == null) {
throw StateError('Cipher not initialized. Call initWithRandomKey() first.');
throw StateError(
'Cipher not initialized. Call initWithRandomKey() first.',
);
}

return rust_encryption.encrypt(
Expand All @@ -27,7 +28,9 @@ class Chacha20Service {
Future<Uint8List> decrypt(Uint8List ciphertext, {Uint8List? aad}) async {
final cipher = _cipher;
if (cipher == null) {
throw StateError('Cipher not initialized. Call initWithRandomKey() first.');
throw StateError(
'Cipher not initialized. Call initWithRandomKey() first.',
);
}

return rust_encryption.decrypt(
Expand Down
12 changes: 4 additions & 8 deletions lib/src/evfs/vault_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class VaultService {
}) {
return rust_evfs.vaultOpen(path: path, key: key);
}

/// Write (or overwrite) a named segment.
///
/// [compression] is optional — defaults to no compression.
Expand Down Expand Up @@ -70,9 +70,7 @@ class VaultService {
}

/// List all segment names.
static Future<List<String>> list({
required rust_evfs.VaultHandle handle,
}) {
static Future<List<String>> list({required rust_evfs.VaultHandle handle}) {
return rust_evfs.vaultList(handle: handle);
}

Expand All @@ -84,9 +82,7 @@ class VaultService {
}

/// Close the vault (release lock, zeroize keys).
static Future<void> close({
required rust_evfs.VaultHandle handle,
}) {
static Future<void> close({required rust_evfs.VaultHandle handle}) {
return rust_evfs.vaultClose(handle: handle);
}
}
}
14 changes: 6 additions & 8 deletions lib/src/hashing/argon2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export '../rust/api/hashing/argon2.dart' show Argon2Preset;
// Compile-time flag: pass -DIS_DESKTOP=true for desktop/server builds
const bool _isDesktop = bool.fromEnvironment('IS_DESKTOP');

const ffi.Argon2Preset _defaultPreset =
_isDesktop ? ffi.Argon2Preset.desktop : ffi.Argon2Preset.mobile;
const ffi.Argon2Preset _defaultPreset = _isDesktop
? ffi.Argon2Preset.desktop
: ffi.Argon2Preset.mobile;

/// Hash a password using Argon2id.
///
Expand All @@ -18,8 +19,7 @@ const ffi.Argon2Preset _defaultPreset =
Future<String> argon2IdHash({
required String password,
ffi.Argon2Preset preset = _defaultPreset,
}) =>
ffi.argon2IdHash(password: password, preset: preset);
}) => ffi.argon2IdHash(password: password, preset: preset);

/// Hash a password using Argon2id with an explicit salt.
///
Expand All @@ -28,12 +28,10 @@ Future<String> argon2IdHashWithSalt({
required String password,
required String salt,
ffi.Argon2Preset preset = _defaultPreset,
}) =>
ffi.argon2IdHashWithSalt(password: password, salt: salt, preset: preset);
}) => ffi.argon2IdHashWithSalt(password: password, salt: salt, preset: preset);

/// Verify a password against an Argon2id PHC hash string.
Future<void> argon2IdVerify({
required String phcHash,
required String password,
}) =>
ffi.argon2IdVerify(phcHash: phcHash, password: password);
}) => ffi.argon2IdVerify(phcHash: phcHash, password: password);
14 changes: 5 additions & 9 deletions lib/src/kdf/hkdf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,8 @@ class MHKDF {

/// Performs HKDF-Extract.
/// Produces a pseudorandom key (PRK) from input key material.
static Uint8List extract({
required Uint8List ikm,
Uint8List? salt,
}) {
return rust_hkdf.hkdfExtract(
ikm: ikm,
salt: salt ?? Uint8List(0),
);
static Uint8List extract({required Uint8List ikm, Uint8List? salt}) {
return rust_hkdf.hkdfExtract(ikm: ikm, salt: salt ?? Uint8List(0));
}

/// Performs HKDF-Expand.
Expand All @@ -58,7 +52,9 @@ class MHKDF {
// SHA-256 HKDF max: 255 * 32 = 8160
if (outputLen > 8160) {
throw ArgumentError.value(
outputLen, 'outputLen', 'must be <= 8160 bytes (RFC 5869 limit for SHA-256)',
outputLen,
'outputLen',
'must be <= 8160 bytes (RFC 5869 limit for SHA-256)',
);
}
}
Expand Down
70 changes: 38 additions & 32 deletions lib/src/streaming/streaming_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ class StreamingService {
required String outputPath,
required rust_encryption.CipherHandle cipher,
}) {
return _guardedStream(() => rust_streaming.streamEncryptFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
));
return _guardedStream(
() => rust_streaming.streamEncryptFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
),
);
}

/// Decrypt a streaming-encrypted file.
Expand All @@ -32,11 +34,13 @@ class StreamingService {
required String outputPath,
required rust_encryption.CipherHandle cipher,
}) {
return _guardedStream(() => rust_streaming.streamDecryptFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
));
return _guardedStream(
() => rust_streaming.streamDecryptFile(
cipher: cipher,
inputPath: inputPath,
outputPath: outputPath,
),
);
}

/// Hash a file without loading it into memory.
Expand All @@ -45,10 +49,9 @@ class StreamingService {
required String filePath,
required rust_hashing.HasherHandle hasher,
}) async {
await _guardedStream(() => rust_streaming.streamHashFile(
hasher: hasher,
filePath: filePath,
)).drain();
await _guardedStream(
() => rust_streaming.streamHashFile(hasher: hasher, filePath: filePath),
).drain();
return await rust_hashing.hasherFinalize(handle: hasher);
}

Expand All @@ -58,24 +61,27 @@ class StreamingService {
// closing the controller to let the zone handler forward it first.
static Stream<double> _guardedStream(Stream<double> Function() factory) {
final controller = StreamController<double>();
runZonedGuarded(() {
factory().listen(
controller.add,
onError: controller.addError,
onDone: () {
// FRB delivers errors after the stream closes — schedule close
// in the event loop so pending microtasks (zone errors) run first.
Future(() {
if (!controller.isClosed) controller.close();
});
},
);
}, (error, stack) {
if (!controller.isClosed) {
controller.addError(error, stack);
controller.close();
}
});
runZonedGuarded(
() {
factory().listen(
controller.add,
onError: controller.addError,
onDone: () {
// FRB delivers errors after the stream closes — schedule close
// in the event loop so pending microtasks (zone errors) run first.
Future(() {
if (!controller.isClosed) controller.close();
});
},
);
},
(error, stack) {
if (!controller.isClosed) {
controller.addError(error, stack);
controller.close();
}
},
);
return controller.stream;
}
}
Loading