Add decryption of credential encrypted fields; refactored config;

add app icon
This commit is contained in:
mitchell 2019-07-08 22:03:44 -04:00
parent 910bdeae12
commit 27215e6596
60 changed files with 370 additions and 151 deletions

View File

@ -33,7 +33,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.selfpass_mobile"
applicationId "com.mjfs.selfpass"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.selfpass_mobile">
package="com.mjfs.selfpass">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.selfpass_mobile">
package="com.mjfs.selfpass">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
@ -8,7 +8,7 @@
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="selfpass_mobile"
android:label="selfpass_client"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.selfpass_mobile">
package="com.mjfs.selfpass">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

BIN
app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
app.xcf Normal file

Binary file not shown.

View File

@ -4,12 +4,12 @@ PODS:
- Flutter
DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios`)
- Flutter (from `.symlinks/flutter/ios-release`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
EXTERNAL SOURCES:
Flutter:
:path: ".symlinks/flutter/ios"
:path: ".symlinks/flutter/ios-release"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"

View File

@ -253,7 +253,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -3,116 +3,134 @@
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"filename" : "40.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"filename" : "60.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"filename" : "29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"filename" : "58.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"filename" : "87.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"filename" : "80.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"filename" : "120.png",
"scale" : "3x"
},
{
"size" : "60x60",
"size" : "57x57",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"filename" : "57.png",
"scale" : "1x"
},
{
"size" : "57x57",
"idiom" : "iphone",
"filename" : "114.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"filename" : "120.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "180.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"filename" : "1024.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "1024-1.png",
"scale" : "2x"
}
],
"info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>selfpass_mobile</string>
<string>selfpass_client</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

View File

@ -60,7 +60,9 @@ class Selfpass extends StatelessWidget {
break;
case '/config':
final ConfigScreenArguments arguments = settings.arguments;
final ConfigScreenArguments arguments = settings.arguments == null
? ConfigScreenArguments()
: settings.arguments;
title = 'Configuration';
builder = (BuildContext context) =>
Config(arguments.connectionConfig, arguments.privateKey);

View File

@ -4,25 +4,32 @@
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
import 'dart:core' as $core show bool, Deprecated, double, int, List, Map, override, pragma, String;
import 'dart:core' as $core
show bool, Deprecated, double, int, List, Map, override, pragma, String;
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' as $pb;
import 'dart:core' as $core show DateTime, Duration;
class Timestamp extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp', package: const $pb.PackageName('google.protobuf'))
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp',
package: const $pb.PackageName('google.protobuf'))
..aInt64(1, 'seconds')
..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
..hasRequiredFields = false;
Timestamp._() : super();
factory Timestamp() => create();
factory Timestamp.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Timestamp.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
factory Timestamp.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory Timestamp.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
Timestamp clone() => Timestamp()..mergeFromMessage(this);
Timestamp copyWith(void Function(Timestamp) updates) => super.copyWith((message) => updates(message as Timestamp));
Timestamp copyWith(void Function(Timestamp) updates) =>
super.copyWith((message) => updates(message as Timestamp));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Timestamp create() => Timestamp._();
@ -32,30 +39,36 @@ class Timestamp extends $pb.GeneratedMessage {
static Timestamp _defaultInstance;
Int64 get seconds => $_getI64(0);
set seconds(Int64 v) { $_setInt64(0, v); }
set seconds(Int64 v) {
$_setInt64(0, v);
}
$core.bool hasSeconds() => $_has(0);
void clearSeconds() => clearField(1);
$core.int get nanos => $_get(1, 0);
set nanos($core.int v) { $_setSignedInt32(1, v); }
set nanos($core.int v) {
$_setSignedInt32(1, v);
}
$core.bool hasNanos() => $_has(1);
void clearNanos() => clearField(2);
/// Converts an instance to [DateTime].
///
/// The result is in UTC time zone and has microsecond precision, as
/// [DateTime] does not support nanosecond precision.
$core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch(
seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000,
isUtc: true);
/// Creates a new instance from [dateTime].
///
/// Time zone information will not be preserved.
static Timestamp fromDateTime($core.DateTime dateTime) {
$core.int micros = dateTime.microsecondsSinceEpoch;
return Timestamp()
..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond)
..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000;
}
/// Converts an instance to [DateTime].
///
/// The result is in UTC time zone and has microsecond precision, as
/// [DateTime] does not support nanosecond precision.
$core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch(
seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000,
isUtc: true);
/// Creates a new instance from [dateTime].
///
/// Time zone information will not be preserved.
static Timestamp fromDateTime($core.DateTime dateTime) {
$core.int micros = dateTime.microsecondsSinceEpoch;
return Timestamp()
..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond)
..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000;
}
}

View File

@ -11,4 +11,3 @@ const Timestamp$json = const {
const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
],
};

View File

@ -29,7 +29,14 @@ const UpdateRequest$json = const {
'1': 'UpdateRequest',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {'1': 'credential', '3': 2, '4': 1, '5': 11, '6': '.selfpass.credentials.CredentialRequest', '10': 'credential'},
const {
'1': 'credential',
'3': 2,
'4': 1,
'5': 11,
'6': '.selfpass.credentials.CredentialRequest',
'10': 'credential'
},
],
};
@ -48,8 +55,22 @@ const Metadata$json = const {
'1': 'Metadata',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {'1': 'created_at', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'createdAt'},
const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'},
const {
'1': 'created_at',
'3': 2,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'createdAt'
},
const {
'1': 'updated_at',
'3': 3,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'updatedAt'
},
const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
@ -61,8 +82,22 @@ const Credential$json = const {
'1': 'Credential',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {'1': 'created_at', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'createdAt'},
const {'1': 'updated_at', '3': 3, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '10': 'updatedAt'},
const {
'1': 'created_at',
'3': 2,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'createdAt'
},
const {
'1': 'updated_at',
'3': 3,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'updatedAt'
},
const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'},
const {'1': 'email', '3': 6, '4': 1, '5': 9, '10': 'email'},
@ -87,4 +122,3 @@ const CredentialRequest$json = const {
const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'},
],
};

View File

@ -12,11 +12,17 @@ class Config implements ConfigRepo {
static const _keyConnectionConfig = "connection_config";
static const _keyPassword = "password";
final FlutterSecureStorage _storage = FlutterSecureStorage();
var _passwordMatched = false;
bool _passwordMatched = false;
String _password;
String get password {
_checkIfPasswordMatched();
return _password;
}
Future<void> setPrivateKey(String key) {
_checkIfPasswordMatched();
return _storage.write(key: _keyPrivateKey, value: key);
return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', ''));
}
Future<String> get privateKey {
@ -26,6 +32,7 @@ class Config implements ConfigRepo {
Future<void> setPassword(String password) {
_checkIfPasswordMatched();
_password = password;
return _storage.write(
key: _keyPassword, value: crypto.hashPassword(password));
}
@ -42,9 +49,16 @@ class Config implements ConfigRepo {
return false;
}
Future<bool> matchesPasswordHash(String password) async =>
_passwordMatched = crypto.matchHashedPassword(
await _storage.read(key: _keyPassword), password);
Future<bool> matchesPasswordHash(String password) async {
_passwordMatched = crypto.matchHashedPassword(
await _storage.read(key: _keyPassword), password);
if (_passwordMatched) {
_password = password;
}
return _passwordMatched;
}
Future<void> setConnectionConfig(ConnectionConfig config) {
_checkIfPasswordMatched();

View File

@ -105,10 +105,8 @@ class _AuthenticationState extends State<Authentication> {
return () async {
if (await _passwordIsSet) {
if (await _config.matchesPasswordHash(_passwordController.text)) {
Navigator.of(context).pushReplacementNamed(
'/home',
arguments: await _config.connectionConfig,
);
Navigator.of(context).pushReplacementNamed('/home',
arguments: await _config.connectionConfig);
return;
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:otp/otp.dart';
import '../types/credential.dart' as types;
@ -19,17 +20,14 @@ class _CredentialState extends State<Credential> {
Map<String, _FieldBuildConfig> _fieldMap;
types.Credential _credential;
_CredentialState(this._credential) : super() {
_controllers = _CredentialControllers.fromCredential(_credential);
_fieldMap = _buildFieldMap(_controllers, _credential);
}
_CredentialState(this._credential) : super();
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Container(
margin: const EdgeInsets.only(top: 30, left: 30),
padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
child: ListView(
children: _buildFieldRows(context),
),
@ -43,12 +41,12 @@ class _CredentialState extends State<Credential> {
) {
final fieldMap = {
'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
'Created: ': _FieldBuildConfig(
'Created:': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.createdAt.toString(),
),
'Updated: ': _FieldBuildConfig(
'Updated:': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.updatedAt.toString(),
@ -69,15 +67,26 @@ class _CredentialState extends State<Credential> {
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
}
fieldMap['Password:'] =
_FieldBuildConfig(controller: controllers.password, obscured: true);
if (credential.otpSecret != null && credential.otpSecret != '') {
fieldMap['OTP Secret:'] = _FieldBuildConfig(
controller: controllers.otpSecret, obscured: true, otp: true);
}
return fieldMap;
}
List<Widget> _buildFieldRows(BuildContext context) {
List<Widget> rows = [];
_controllers = _CredentialControllers.fromCredential(_credential);
_fieldMap = _buildFieldMap(_controllers, _credential);
_fieldMap.forEach((key, value) {
rows.add(Container(
margin: EdgeInsets.only(top: 10),
margin: EdgeInsets.only(top: 2.5),
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
));
@ -85,20 +94,27 @@ class _CredentialState extends State<Credential> {
Expanded(
flex: 3,
child: value.mutable
? TextField(maxLines: 1, controller: value.controller)
? TextField(
maxLines: 1,
controller: value.controller,
obscure: value.obscured)
: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Text(value.text),
),
child: Text(value.text)),
),
];
if (value.copyable) {
widgets.add(Flexible(
widgets.add(Expanded(
child: CupertinoButton(
child: Text('Copy'),
child: Text(value.otp ? 'OTP' : 'Copy'),
onPressed: () => Clipboard.setData(ClipboardData(
text: value.mutable ? value.controller.text : value.text,
text: value.otp
? OTP
.generateTOTPCode(value.controller.text,
DateTime.now().millisecondsSinceEpoch)
.toString()
: value.mutable ? value.controller.text : value.text,
)),
),
));
@ -116,10 +132,14 @@ class _FieldBuildConfig {
final String text;
final bool mutable;
final bool copyable;
final bool obscured;
final bool otp;
const _FieldBuildConfig({
this.mutable = true,
this.copyable = true,
this.obscured = false,
this.otp = false,
this.controller,
this.text,
});
@ -151,5 +171,7 @@ class _CredentialControllers {
tag: TextEditingController(text: credential.meta.tag),
username: TextEditingController(text: credential.username),
email: TextEditingController(text: credential.email),
password: TextEditingController(text: credential.password),
otpSecret: TextEditingController(text: credential.otpSecret),
);
}

View File

@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart';
import '../utils/crypto.dart' as crypto;
import '../widgets/tappable_text_list.dart';
class Credentials extends StatelessWidget {
@ -20,10 +22,39 @@ class Credentials extends StatelessWidget {
}
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
var makeOnTapHandler = (String id) => () async {
final credential =
await Provider.of<CredentialsRepo>(context).get(id);
Navigator.of(context).pushNamed('/credential', arguments: credential);
final makeOnTapHandler = (String id) => () async {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
content: Column(
children: [
Text('Decrypting credential...'),
Container(
margin: EdgeInsets.only(top: 10),
child: CupertinoActivityIndicator()),
],
)),
);
final config = Provider.of<ConfigRepo>(context);
final client = Provider.of<CredentialsRepo>(context);
final Future<String> privateKey = config.privateKey;
final String password = config.password;
final credential = await client.get(id);
credential.password = crypto.decryptPassword(
password, await privateKey, credential.password);
if (credential.otpSecret != null && credential.otpSecret != '') {
credential.otpSecret = crypto.decryptPassword(
password, await privateKey, credential.otpSecret);
}
Navigator.of(context)
..pop()
..pushNamed('/credential', arguments: credential);
};
Map<String, GestureTapCallback> tappableText = {};

View File

@ -91,6 +91,7 @@ class _HomeState extends State<Home> {
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
return () async => Navigator.of(context).pushNamed('/config',
arguments: ConfigScreenArguments(
await _config.connectionConfig, await _config.privateKey));
connectionConfig: await _config.connectionConfig,
privateKey: await _config.privateKey));
}
}

View File

@ -15,6 +15,7 @@ abstract class ConfigRepo {
Future<void> setPrivateKey(String key);
Future<String> get privateKey;
String get password;
Future<void> setPassword(String password);
Future<bool> get passwordSet;
Future<bool> matchesPasswordHash(String password);

View File

@ -4,5 +4,5 @@ class ConfigScreenArguments {
final ConnectionConfig connectionConfig;
final String privateKey;
const ConfigScreenArguments(this.connectionConfig, this.privateKey);
const ConfigScreenArguments({this.connectionConfig, this.privateKey});
}

View File

@ -1,11 +1,18 @@
import 'dart:math';
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypt/crypt.dart';
import 'package:encrypt/encrypt.dart';
import 'package:password_hash/password_hash.dart';
String hashPassword(String password) {
const saltSize = 16;
const saltIntMax = 256;
final random = Random.secure();
final saltInts = List<int>.generate(16, (_) => random.nextInt(256));
final saltInts =
List<int>.generate(saltSize, (_) => random.nextInt(saltIntMax));
final salt = base64.encode(saltInts);
return Crypt.sha256(password, salt: salt).toString();
@ -13,3 +20,23 @@ String hashPassword(String password) {
bool matchHashedPassword(String hashedPassword, String password) =>
Crypt(hashedPassword).match(password);
String decryptPassword(String masterpass, privateKey, ciphertext) {
final key =
PBKDF2().generateKey(masterpass, privateKey, pbkdf2Rounds, keySize);
var cipherbytes = base64.decode(ciphertext);
final iv =
IV(Uint8List.fromList(cipherbytes.getRange(0, aesBlockSize).toList()));
cipherbytes = Uint8List.fromList(
cipherbytes.getRange(aesBlockSize, cipherbytes.length).toList());
final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc));
return encrypter.decrypt(Encrypted(cipherbytes), iv: iv);
}
const pbkdf2Rounds = 4096;
const keySize = 32;
const aesBlockSize = 16;

View File

@ -7,6 +7,7 @@ typedef OnSubmittedBuilder = ValueChanged<String> Function(
class TextField extends StatelessWidget {
final OnSubmittedBuilder onSubmittedBuilder;
final TextEditingController controller;
final OverlayVisibilityMode clearButtonMode;
final Widget prefix;
final Widget suffix;
final bool obscure;
@ -25,6 +26,7 @@ class TextField extends StatelessWidget {
this.autocorrect = false,
this.prefix,
this.suffix,
this.clearButtonMode = OverlayVisibilityMode.editing,
});
@override
@ -36,7 +38,7 @@ class TextField extends StatelessWidget {
border: Border.all(color: CupertinoColors.black),
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
),
clearButtonMode: OverlayVisibilityMode.editing,
clearButtonMode: clearButtonMode,
textAlign: TextAlign.start,
onSubmitted: this.onSubmittedBuilder != null
? onSubmittedBuilder(context)

View File

@ -1,5 +1,5 @@
# Generated by pub
# See https://www.dartlang.org/tools/pub/glossary#lockfile
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_discoveryapis_commons:
dependency: transitive
@ -8,13 +8,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.8+1"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
asn1lib:
dependency: transitive
description:
name: asn1lib
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.8"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.2.0"
base32:
dependency: transitive
description:
name: base32
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
boolean_selector:
dependency: transitive
description:
@ -64,6 +85,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
encrypt:
dependency: "direct main"
description:
name: encrypt
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.0"
fixnum:
dependency: transitive
description:
@ -144,6 +172,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
otp:
dependency: "direct main"
description:
name: otp
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
password_hash:
dependency: "direct main"
description:
name: password_hash
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
path:
dependency: transitive
description:
@ -157,7 +199,14 @@ packages:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
version: "1.7.0"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
protobuf:
dependency: "direct main"
description:
@ -178,7 +227,7 @@ packages:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.0.3"
sky_engine:
dependency: transitive
description: flutter
@ -225,7 +274,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
version: "0.2.5"
typed_data:
dependency: transitive
description:
@ -241,4 +290,4 @@ packages:
source: hosted
version: "2.0.8"
sdks:
dart: ">=2.2.0 <3.0.0"
dart: ">=2.2.2 <3.0.0"

View File

@ -30,15 +30,17 @@ dependencies:
crypt: ^1.0.7
flutter_secure_storage: ^3.2.1
password_hash: ^2.0.0
encrypt: ^3.2.0
otp: ^1.0.3
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:

View File

@ -8,12 +8,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:selfpass_mobile/main.dart';
import 'package:selfpass_client/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
await tester.pumpWidget(Selfpass());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);