seald_sdk_flutter 0.1.0-beta.mr99-16 seald_sdk_flutter: ^0.1.0-beta.mr99-16 copied to clipboard
Seald SDK for Flutter: simple end-to-end encryption for your app
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:seald_sdk_flutter/seald_sdk.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:path/path.dart' as path;
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:seald_sdk_flutter_example/ssks_backend.dart';
void main() {
runApp(const MyApp());
}
class BlinkingWidget extends StatefulWidget {
const BlinkingWidget({super.key});
@override
_BlinkingWidgetState createState() => _BlinkingWidgetState();
}
class _BlinkingWidgetState extends State<BlinkingWidget> {
bool _isVisible = true;
@override
void initState() {
super.initState();
// start the blinking animation
Timer.periodic(const Duration(milliseconds: 500), (timer) {
setState(() {
_isVisible = !_isVisible;
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: _isVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 50),
child: Container(
width: 20.0,
height: 20.0,
color: Colors.red,
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
const Map<String, String> testCredentials = {
"api_url": "https://api-dev.soyouz.seald.io/",
"app_id": "00000000-0000-1000-a000-7ea300000018",
"domain_validation_key_id": "00000000-0000-1000-a000-d11c00000020",
"domain_validation_key":
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"jwt_shared_secret_id": "00000000-0000-1000-a000-7ea300000019",
"jwt_shared_secret":
"o75u89og9rxc9me54qxaxvdutr2t4t25ozj4m64utwemm0osld0zdb02j7gv8t7x",
"debug_api_secret": "f0r_d3bu6",
"ssks_url": "https://ssks.soyouz.seald.io/",
"ssks_backend_app_id": "00000000-0000-0000-0000-000000000001",
"ssks_backend_app_key": "00000000-0000-0000-0000-000000000002",
"ssks_tmr_challenge": "aaaaaaaa"
};
// The Seald SDK uses a local database that will persist on disk.
// When instantiating a SealdSDK, it is highly recommended to set a symmetric key to encrypt this database.
// This demo will use a fixed key. In an actual app, it should be generated at signup,
// either on the server and retrieved from your backend at login,
// or on the client-side directly and stored in the system's keychain.
const String databaseEncryptionKeyB64 =
"V4olGDOE5bAWNa9HDCvOACvZ59hUSUdKmpuZNyl1eJQnWKs5/l+PGnKUv4mKjivL3BtU014uRAIF2sOl83o6vQ";
Uint8List randomBuffer(int length) {
final random = Random.secure();
final buffer = Uint8List(length);
for (var i = 0; i < length; ++i) {
buffer[i] = random.nextInt(256);
}
return buffer;
}
String randomString(int length) {
const chars = "abcdefghijklmnopqrstuvwxyz";
var random = Random();
return List.generate(length, (_) => chars[random.nextInt(chars.length)])
.join();
}
String getRegistrationJwt() {
// Create a json web token
final JWT jwt = JWT({
"iss": testCredentials["jwt_shared_secret_id"]!,
"jti": base64.encode(randomBuffer(16)),
"iat": DateTime.now().millisecondsSinceEpoch ~/ 1000,
"scopes": [3],
"join_team": true
});
return jwt.sign(SecretKey(testCredentials["jwt_shared_secret"]!),
algorithm: JWTAlgorithm.HS256);
}
String getConnectorJwt(customUserId) {
// Create a json web token
final JWT jwt = JWT({
"iss": testCredentials["jwt_shared_secret_id"]!,
"jti": base64.encode(randomBuffer(16)),
"iat": DateTime.now().millisecondsSinceEpoch ~/ 1000,
"scopes": [4],
"connector_add": {
"type": "AP",
"value": "$customUserId@${testCredentials["app_id"]}"
}
});
return jwt.sign(SecretKey(testCredentials["jwt_shared_secret"]!),
algorithm: JWTAlgorithm.HS256);
}
void assertEqual(actual, expected) {
if (actual != expected) {
print('Assert fail: expected $expected, got $actual');
throw AssertionError('Assertion failed');
}
}
void assertListEquals(actual, expected) {
if (!listEquals(actual, expected)) {
print('Assert fail: expected $expected, got $actual');
throw AssertionError('Assertion failed');
}
}
void assertNotEqual(actual, expected) {
if (actual == expected) {
print('Assert fail: expected to be not equal to $expected, got $actual');
throw AssertionError('Assertion failed');
}
}
void assertThrows(Function func) {
try {
func();
} catch (err) {
return; // Got expected error
}
print('Assert fail: expected function to throw, but succeeded');
throw AssertionError('Assertion failed');
}
Future<void> assertThrowsAsync(Future<void> Function() func) async {
try {
await func();
} catch (err) {
return; // Got expected error
}
print('Assert fail: expected async function to throw, but succeeded');
throw AssertionError('Assertion failed');
}
Future<bool> testSealdSdk() async {
print('Starting SDK tests...');
try {
final Directory tmpDir = await path_provider.getTemporaryDirectory();
final Directory dbDir = Directory(path.join(tmpDir.path, 'seald-test-db'));
// Delete local database from previous run
if (dbDir.existsSync()) {
dbDir.deleteSync(recursive: true);
}
final SealdSdk sdk1 = SealdSdk(
apiURL: testCredentials["api_url"]!,
appId: testCredentials["app_id"]!,
dbPath: Directory(path.join(dbDir.path, 'sdk1')).path,
logLevel: -1,
instanceName: "Dart1",
databaseEncryptionKeyB64: databaseEncryptionKeyB64,
);
final SealdSdk sdk2 = SealdSdk(
apiURL: testCredentials["api_url"]!,
appId: testCredentials["app_id"]!,
dbPath: Directory(path.join(dbDir.path, 'sdk2')).path,
logLevel: -1,
instanceName: "Dart2",
databaseEncryptionKeyB64: databaseEncryptionKeyB64,
);
final SealdSdk sdk3 = SealdSdk(
apiURL: testCredentials["api_url"]!,
appId: testCredentials["app_id"]!,
dbPath: Directory(path.join(dbDir.path, 'sdk3')).path,
logLevel: -1,
instanceName: "Dart3",
databaseEncryptionKeyB64: databaseEncryptionKeyB64,
);
// retrieve info about current user before creating a user should return null
final SealdAccountInfo? retrieveNoAccount = sdk1.getCurrentAccountInfo();
assertEqual(retrieveNoAccount, null);
// Create the 3 accounts. Again, the signupJWT should be generated by your backend
final SealdAccountInfo user1AccountInfo = await sdk1.createAccountAsync(
getRegistrationJwt(),
displayName: "User1",
deviceName: "deviceUser1");
final SealdAccountInfo user2AccountInfo = await sdk2.createAccountAsync(
getRegistrationJwt(),
displayName: "User2",
deviceName: "deviceUser2");
final SealdAccountInfo user3AccountInfo = await sdk3.createAccountAsync(
getRegistrationJwt(),
displayName: "User3",
deviceName: "deviceUser3");
// retrieve info about current user:
final SealdAccountInfo? retrieveAccountInfo = sdk1.getCurrentAccountInfo();
assertNotEqual(retrieveAccountInfo, null);
assertEqual(retrieveAccountInfo?.userId, user1AccountInfo.userId);
assertEqual(retrieveAccountInfo?.deviceId, user1AccountInfo.deviceId);
assertNotEqual(retrieveAccountInfo?.deviceExpires, 0);
assertEqual(
retrieveAccountInfo?.deviceExpires, user1AccountInfo.deviceExpires);
// Create group: https://docs.seald.io/sdk/guides/5-groups.html
final String groupId = await sdk1.createGroupAsync(
groupName: "group-1",
members: [user1AccountInfo.userId],
admins: [user1AccountInfo.userId]);
// Manage group members and admins
await sdk1.addGroupMembersAsync(groupId,
membersToAdd: [user2AccountInfo.userId]); // Add user2 as group member
await sdk1.addGroupMembersAsync(groupId, membersToAdd: [
user3AccountInfo.userId
], adminsToSet: [
user3AccountInfo.userId
]); // user1 add user3 as group member and group admin
await sdk3.removeGroupMembersAsync(groupId,
membersToRemove: [user2AccountInfo.userId]); // user3 can remove user2
await sdk3.setGroupAdminsAsync(groupId, addToAdmins: [], removeFromAdmins: [
user1AccountInfo.userId
]); // user3 can remove user1 from admins
// Create encryption session: https://docs.seald.io/sdk/guides/6-encryption-sessions.html
final EncryptionSession es1SDK1 = await sdk1.createEncryptionSessionAsync([
user1AccountInfo.userId,
user2AccountInfo.userId,
groupId
]); // user1, user2, and group as recipients
// The EncryptionSession object can encrypt and decrypt for user1
const String initialString = "a message that needs to be encrypted!";
final String encryptedMessage =
await es1SDK1.encryptMessageAsync(initialString);
final String decryptedMessage =
await es1SDK1.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessage, initialString);
final Directory directory =
await path_provider.getApplicationDocumentsDirectory();
await removeAllFilesInDirectory(directory.path);
final String testFilePath = "${directory.path}/test.txt";
final File testFile = File(testFilePath);
await testFile.writeAsString(initialString);
final String encryptedFilePath =
await es1SDK1.encryptFileFromPathAsync(testFilePath);
final String decryptedFilePath =
await es1SDK1.decryptFileFromPathAsync(encryptedFilePath);
final File decryptedFile = File(decryptedFilePath);
assertEqual(await decryptedFile.exists(), true);
final Uint8List decryptedFileBytes = await decryptedFile.readAsBytes();
final String decryptedFileString =
new String.fromCharCodes(decryptedFileBytes);
assertEqual(decryptedFileString, initialString);
// user1 can retrieve the EncryptionSession from the encrypted message
final EncryptionSession es1SDK1RetrieveFromMess =
await sdk1.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: true);
final String decryptedMessageFromMess =
await es1SDK1RetrieveFromMess.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageFromMess, initialString);
// user1 can retrieve the EncryptionSession from the encrypted file
final EncryptionSession es1SDK1RetrieveFromFile =
await sdk1.retrieveEncryptionSessionAsync(
filePath: encryptedFilePath, useCache: true);
final String decryptedMessageFromFile =
await es1SDK1RetrieveFromFile.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageFromFile, initialString);
// user1 can retrieve the EncryptionSession from the encrypted file bytes
final File encryptedFile = File(encryptedFilePath);
final Uint8List encryptedFileBytes = await encryptedFile.readAsBytes();
final EncryptionSession es1SDK1RetrieveFromFileBytes =
await sdk1.retrieveEncryptionSessionAsync(
fileBytes: encryptedFileBytes, useCache: true);
final String decryptedMessageFromFileBytes =
await es1SDK1RetrieveFromFileBytes
.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageFromFileBytes, initialString);
// user2 and user3 can retrieve the encryptionSession (from the encrypted message or the session ID).
final EncryptionSession es1SDK2 = await sdk2.retrieveEncryptionSessionAsync(
sessionId: es1SDK1.id, useCache: true);
final String decryptedMessageSDK2 =
await es1SDK2.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageSDK2, initialString);
final EncryptionSession es1SDK3FromGroup =
await sdk3.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: true);
final String decryptedMessageSDK3 =
await es1SDK3FromGroup.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageSDK3, initialString);
// user3 removes all members of "group-1". A group without member is deleted.
await sdk3.removeGroupMembersAsync(groupId,
membersToRemove: [user1AccountInfo.userId, user3AccountInfo.userId]);
// user3 could retrieve the previous encryption session only because "group-1" was set as recipient.
// As the group was deleted, it can no longer access it.
// user3 still has the encryption session in its cache, but we can disable it.
await assertThrowsAsync(() async => sdk3.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: false));
// user2 adds user3 as recipient of the encryption session.
final Map<String, SealdActionStatus> asMapAdd =
await es1SDK2.addRecipientsAsync([user3AccountInfo.userId]);
assertEqual(asMapAdd.length, 1);
assertEqual(asMapAdd.containsKey(user3AccountInfo.deviceId),
true); // Note that addRecipient return DeviceId, not UserId
assertEqual(asMapAdd[user3AccountInfo.deviceId]!.success, true);
// user3 can now retrieve it.
final EncryptionSession es1SDK3 = await sdk3.retrieveEncryptionSessionAsync(
sessionId: es1SDK1.id, useCache: false);
final String decryptedMessageAfterAdd =
await es1SDK3.decryptMessageAsync(encryptedMessage);
assertEqual(decryptedMessageAfterAdd, initialString);
// user1 revokes user3 from the encryption session.
final Map<String, SealdActionStatus> asMapRevoke = await es1SDK1
.revokeRecipientsAsync([
user3AccountInfo.userId
]); // TODO: used to be user2, but new ACLs break it
assertEqual(asMapRevoke.length, 1);
assertEqual(asMapRevoke.containsKey(user3AccountInfo.userId), true);
assertEqual(asMapRevoke[user3AccountInfo.userId]!.success, true);
// user3 cannot retrieve the session anymore
await assertThrowsAsync(() async => sdk3.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: false));
// user1 revokes all other recipients from the session
final Map<String, SealdActionStatus> asMapRevokeOthers =
await es1SDK1.revokeOthersAsync();
assertEqual(asMapRevokeOthers.length, 2);
assertEqual(asMapRevokeOthers.containsKey(user2AccountInfo.userId), true);
assertEqual(asMapRevokeOthers[user2AccountInfo.userId]!.success, true);
assertEqual(asMapRevokeOthers.containsKey(groupId), true);
assertEqual(asMapRevokeOthers[groupId]!.success, true);
// user2 cannot retrieve the session anymore
await assertThrowsAsync(() async => sdk2.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: false));
// user1 revokes all. It can no longer retrieve it.
final Map<String, SealdActionStatus> asMapRevokeAll =
await es1SDK1.revokeAllAsync();
assertEqual(asMapRevokeAll.length, 1);
assertEqual(asMapRevokeAll.containsKey(user1AccountInfo.userId), true);
assertEqual(asMapRevokeAll[user1AccountInfo.userId]!.success, true);
await assertThrowsAsync(() async => sdk1.retrieveEncryptionSessionAsync(
message: encryptedMessage, useCache: false));
// Create additional data for user1
final EncryptionSession es2SDK1 = await sdk1.createEncryptionSessionAsync(
[user1AccountInfo.userId],
useCache: true);
const String anotherMessage = "nobody should read that!";
final String secondEncryptedMessage =
await es2SDK1.encryptMessageAsync(anotherMessage);
// user1 can renew its key, and still decrypt old messages
await sdk1.renewKeysAsync(expireAfter: const Duration(days: 365 * 5));
final EncryptionSession es2SDK1AfterRenew = await sdk1
.retrieveEncryptionSessionAsync(sessionId: es2SDK1.id, useCache: false);
final String decryptedMessageAfterRenew =
await es2SDK1AfterRenew.decryptMessageAsync(secondEncryptedMessage);
assertEqual(decryptedMessageAfterRenew, anotherMessage);
// CONNECTORS https://docs.seald.io/en/sdk/guides/jwt.html#adding-a-userid
// we can add a custom userId using a JWT
const String customConnectorJWTValue = "user1-custom-id";
await sdk1.pushJWTAsync(getConnectorJwt(customConnectorJWTValue));
final List<SealdConnector> connectors = await sdk1.listConnectorsAsync();
assertEqual(connectors.length, 1);
assertEqual(connectors[0].state, "VO");
assertEqual(connectors[0].type, "AP");
assertEqual(connectors[0].sealdId, user1AccountInfo.userId);
assertEqual(connectors[0].value,
"$customConnectorJWTValue@${testCredentials['app_id']}");
// Retrieve connector by its id
final SealdConnector retrieveConnector =
await sdk1.retrieveConnectorAsync(connectors[0].id);
assertEqual(retrieveConnector.sealdId, user1AccountInfo.userId);
assertEqual(retrieveConnector.state, "VO");
assertEqual(retrieveConnector.type, "AP");
assertEqual(retrieveConnector.value,
"$customConnectorJWTValue@${testCredentials['app_id']}");
// Retrieve connectors from a user id.
final List<SealdConnector> connectorsFromSealdId =
await sdk1.getConnectorsFromSealdIdAsync(user1AccountInfo.userId);
assertEqual(connectorsFromSealdId.length, 1);
assertEqual(connectorsFromSealdId[0].state, "VO");
assertEqual(connectorsFromSealdId[0].type, "AP");
assertEqual(connectorsFromSealdId[0].sealdId, user1AccountInfo.userId);
assertEqual(connectorsFromSealdId[0].value,
"$customConnectorJWTValue@${testCredentials['app_id']}");
// Get sealdId of a user from a connector
final List<String> sealdIds = await sdk2.getSealdIdsFromConnectorsAsync([
SealdConnectorTypeValue(
type: "AP",
value: "$customConnectorJWTValue@${testCredentials['app_id']}")
]);
assertEqual(sealdIds.length, 1);
assertEqual(sealdIds[0], user1AccountInfo.userId);
// user1 can remove a connector
await sdk1.removeConnectorAsync(connectors[0].id);
// verify that only one connector left
final List<SealdConnector> connectorListAfterRevoke =
await sdk1.listConnectorsAsync();
assertEqual(connectorListAfterRevoke.length, 0);
// user1 can export its identity
final Uint8List exportIdentity = sdk1.exportIdentity();
// We can instantiate a new SealdSDK, import the exported identity
final SealdSdk sdk1Exported = SealdSdk(
apiURL: testCredentials["api_url"]!,
appId: testCredentials["app_id"]!,
dbPath: Directory(path.join(dbDir.path, 'sdk1exported')).path,
logLevel: -1,
instanceName: "Dart1Exported",
databaseEncryptionKeyB64: databaseEncryptionKeyB64,
);
await sdk1Exported.importIdentityAsync(exportIdentity);
// SDK with imported identity can decrypt
final EncryptionSession es2SDK1Exported = await sdk1Exported
.retrieveEncryptionSessionAsync(message: secondEncryptedMessage);
final String clearMessageExportedIdentity =
await es2SDK1Exported.decryptMessageAsync(secondEncryptedMessage);
assertEqual(clearMessageExportedIdentity, anotherMessage);
sdk1Exported.close();
// user1 can create sub identity
final SealdCreateSubIdentityResponse subIdentity =
await sdk1.createSubIdentityAsync(deviceName: "SUB-deviceName");
assertNotEqual(subIdentity.deviceId, "");
// first device needs to reencrypt for the new device
await sdk1.massReencryptAsync(subIdentity.deviceId);
// We can instantiate a new SealdSDK, import the sub-device identity
final SealdSdk sdk1SubDevice = SealdSdk(
apiURL: testCredentials["api_url"]!,
appId: testCredentials["app_id"]!,
dbPath: Directory(path.join(dbDir.path, 'sdk1SubDevice')).path,
logLevel: -1,
instanceName: "Dart1SubDevice",
databaseEncryptionKeyB64: databaseEncryptionKeyB64,
);
await sdk1SubDevice.importIdentityAsync(subIdentity.backupKey);
// sub device can decrypt
final EncryptionSession es2SDK1SubDevice =
await sdk1SubDevice.retrieveEncryptionSessionAsync(
message: secondEncryptedMessage, useCache: false);
final String clearMessageSubdIdentity =
await es2SDK1SubDevice.decryptMessageAsync(secondEncryptedMessage);
assertEqual(clearMessageSubdIdentity, anotherMessage);
sdk1SubDevice.close();
await sdk1.heartbeatAsync();
sdk1.close();
sdk2.close();
sdk3.close();
print('SDK tests success!');
return true;
} catch (err, stack) {
print('SDK tests failed');
print(err);
print(stack);
return false;
}
}
Future<bool> testSsksPassword() async {
print('Starting SsksPassword tests...');
try {
// Simulating a Seald identity with random data, for a simpler example.
Uint8List dummyIdentity =
randomBuffer(64); // should be: sdk.exportIdentity()
SealdSsksPasswordPlugin ssksPlugin = SealdSsksPasswordPlugin(
testCredentials["ssks_url"]!, testCredentials["app_id"]!);
// Test with standard password
String userIdPassword = "user-${randomString(11)}";
String userPassword = randomString(12);
// Saving the identity with a password
await ssksPlugin.saveIdentityFromPasswordAsync(
userIdPassword, userPassword, dummyIdentity);
// Retrieving the identity with the password
Uint8List retrievedIdentityPassword = await ssksPlugin
.retrieveIdentityFromPasswordAsync(userIdPassword, userPassword);
assertListEquals(retrievedIdentityPassword, dummyIdentity);
// Changing the password
String newPassword = "newPassword";
await ssksPlugin.changeIdentityPasswordAsync(
userIdPassword, userPassword, newPassword);
// The previous password does not work anymore
await assertThrowsAsync(() async => ssksPlugin
.retrieveIdentityFromPasswordAsync(userIdPassword, userPassword));
// Retrieving with the new password works
Uint8List retrievedIdentityNewPassword = await ssksPlugin
.retrieveIdentityFromPasswordAsync(userIdPassword, newPassword);
assertListEquals(retrievedIdentityNewPassword, dummyIdentity);
// Test with raw keys
String userIdRawKeys = "user-${randomString(11)}";
String rawStorageKey = randomString(32);
Uint8List rawEncryptionKey = randomBuffer(64);
// Saving identity with raw keys
await ssksPlugin.saveIdentityFromRawKeysAsync(
userIdRawKeys, rawStorageKey, rawEncryptionKey, dummyIdentity);
// Retrieving the identity with raw keys
Uint8List retrievedIdentityRawKeys =
await ssksPlugin.retrieveIdentityFromRawKeysAsync(
userIdRawKeys, rawStorageKey, rawEncryptionKey);
assertListEquals(retrievedIdentityRawKeys, dummyIdentity);
// Deleting the identity by saving an empty `Data`
ssksPlugin.saveIdentityFromRawKeys(
userIdRawKeys, rawStorageKey, rawEncryptionKey, Uint8List(0));
// After deleting the identity, cannot retrieve anymore
await assertThrowsAsync(() async =>
ssksPlugin.retrieveIdentityFromRawKeysAsync(
userIdRawKeys, rawStorageKey, rawEncryptionKey));
print('SsksPassword tests success!');
return true;
} catch (err, stack) {
print('SsksPassword tests failed');
print(err);
print(stack);
return false;
}
}
Future<bool> testSsksTMR() async {
print('Starting SsksTMR tests...');
try {
SsksBackend yourCompanyDummyBackend = SsksBackend(
testCredentials["ssks_url"]!,
testCredentials["ssks_backend_app_id"]!,
testCredentials["ssks_backend_app_key"]!);
// Simulating a Seald identity with random data, for a simpler example.
String userId = "user-${randomString(11)}";
Uint8List dummyIdentity =
randomBuffer(64); // should be: sdk.exportIdentity()
Uint8List rawTMRSymKey = randomBuffer(64);
String userEM = "email-${randomString(15)}@test.com";
SealdSsksTMRPlugin ssksPlugin = SealdSsksTMRPlugin(
testCredentials["ssks_url"]!, testCredentials["app_id"]!);
// The app backend creates a session to save the identity.
// This is the first time that this email is storing an identity, so `must_authenticate` is false.
ChallengeSendResponse authSessionSave = await yourCompanyDummyBackend
.challengeSend(userId, "EM", userEM, true, false);
assertEqual(authSessionSave.mustAuthenticate, false);
// Saving the identity. No challenge necessary because `must_authenticate` is false.
await ssksPlugin.saveIdentityAsync(authSessionSave.sessionId, "EM", userEM,
"", rawTMRSymKey, dummyIdentity);
// The app backend creates another session to retrieve the identity.
// The identity is already saved, so `must_authenticate` is true.
ChallengeSendResponse authSessionRetrieve = await yourCompanyDummyBackend
.challengeSend(userId, "EM", userEM, true, false);
assertEqual(authSessionRetrieve.mustAuthenticate, true);
SealdSsksTMRPluginRetrieveIdentityResponse retrieveNotAuth =
await ssksPlugin.retrieveIdentityAsync(authSessionRetrieve.sessionId,
"EM", userEM, testCredentials["ssks_tmr_challenge"]!, rawTMRSymKey);
assertEqual(retrieveNotAuth.shouldRenewKey, true);
assertListEquals(retrieveNotAuth.identity, dummyIdentity);
// If initial key has been saved without being fully authenticated, you should renew the user's private key, and save it again.
// Let's simulate the renew with another random identity
Uint8List identitySecondKey = randomBuffer(64);
await ssksPlugin.saveIdentityAsync(
// to save the newly renewed identity, you can use the `authenticatedSessionId` from the response to `retrieveIdentityAsync`, with no challenge
retrieveNotAuth.authenticatedSessionId,
"EM",
userEM,
testCredentials["ssks_tmr_challenge"]!,
rawTMRSymKey,
identitySecondKey);
// And now let's retrieve this new saved identity
ChallengeSendResponse authSessionRetrieve2 = await yourCompanyDummyBackend
.challengeSend(userId, "EM", userEM, false, false);
assertEqual(authSessionRetrieve2.mustAuthenticate, true);
SealdSsksTMRPluginRetrieveIdentityResponse retrievedSecondKey =
await ssksPlugin.retrieveIdentityAsync(authSessionRetrieve2.sessionId,
"EM", userEM, testCredentials["ssks_tmr_challenge"]!, rawTMRSymKey);
assertEqual(retrievedSecondKey.shouldRenewKey, false);
assertListEquals(retrievedSecondKey.identity, identitySecondKey);
// Try retrieving with another SealdSsksTMRPlugin instance
SealdSsksTMRPlugin ssksPluginInst2 = SealdSsksTMRPlugin(
testCredentials["ssks_url"]!, testCredentials["app_id"]!);
ChallengeSendResponse authSessionRetrieve3 = await yourCompanyDummyBackend
.challengeSend(userId, "EM", userEM, false, false);
assertEqual(authSessionRetrieve3.mustAuthenticate, true);
SealdSsksTMRPluginRetrieveIdentityResponse inst2Retrieve =
await ssksPluginInst2.retrieveIdentityAsync(
authSessionRetrieve3.sessionId,
"EM",
userEM,
testCredentials["ssks_tmr_challenge"]!,
rawTMRSymKey);
assertEqual(inst2Retrieve.shouldRenewKey, false);
assertListEquals(inst2Retrieve.identity, identitySecondKey);
print('SsksTMR tests success!');
return true;
} catch (err, stack) {
print('SsksTMR tests failed');
print(err);
print(stack);
return false;
}
}
Future<void> removeAllFilesInDirectory(String directoryPath) async {
Directory directory = Directory(directoryPath);
if (await directory.exists()) {
List<FileSystemEntity> files = directory.listSync();
for (var file in files) {
if (file is File) {
await file.delete();
print('File deleted: ${file.path}');
}
}
print('All files removed from directory.');
} else {
throw Exception('Directory not found.');
}
}
class _MyAppState extends State<MyApp> {
late Future<bool> sdkTestResult;
late Future<bool> ssksPasswordTestResult;
late Future<bool> ssksTMRTestResult;
@override
void initState() {
super.initState();
sdkTestResult = testSealdSdk();
ssksPasswordTestResult = testSsksPassword();
ssksTMRTestResult = testSsksTMR();
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Native Packages'),
),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
children: [
BlinkingWidget(),
spacerSmall,
FutureBuilder<bool>(
future: sdkTestResult,
builder: (BuildContext context, AsyncSnapshot<bool> value) {
final String displayValue = (value.hasData)
? (value.data! ? 'success' : 'fail')
: 'running';
return Text(
'test SDK: $displayValue',
style: textStyle,
textAlign: TextAlign.left,
);
},
),
FutureBuilder<bool>(
future: ssksPasswordTestResult,
builder: (BuildContext context, AsyncSnapshot<bool> value) {
final String displayValue = (value.hasData)
? (value.data! ? 'success' : 'fail')
: 'running';
return Text(
'test SSKS Password: $displayValue',
style: textStyle,
textAlign: TextAlign.left,
);
},
),
FutureBuilder<bool>(
future: ssksTMRTestResult,
builder: (BuildContext context, AsyncSnapshot<bool> value) {
final String displayValue = (value.hasData)
? (value.data! ? 'success' : 'fail')
: 'running';
return Text(
'test SSKS TMR: $displayValue',
style: textStyle,
textAlign: TextAlign.left,
);
},
),
],
),
),
),
),
);
}
}