From df661268a2f7273789f825191d24b4c099bcbf45 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 16 Nov 2021 16:51:44 -0600 Subject: [PATCH] Initial commit Signed-off-by: jolheiser --- .gitignore | 7 + CHANGELOG.md | 3 + README.md | 1 + analysis_options.yaml | 30 ++++ lib/spectre.dart | 3 + lib/src/scope.dart | 44 +++++ lib/src/spectre.dart | 84 ++++++++++ lib/src/template.dart | 77 +++++++++ pubspec.lock | 362 ++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 16 ++ test/spectre_test.dart | 17 ++ test/spectre_tests.dart | 11 ++ test/spectre_tests.xml | 75 +++++++++ 13 files changed, 730 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 lib/spectre.dart create mode 100644 lib/src/scope.dart create mode 100644 lib/src/spectre.dart create mode 100644 lib/src/template.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/spectre_test.dart create mode 100644 test/spectre_tests.dart create mode 100644 test/spectre_tests.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c001c12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a307539 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A simple command-line application. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/lib/spectre.dart b/lib/spectre.dart new file mode 100644 index 0000000..790545d --- /dev/null +++ b/lib/spectre.dart @@ -0,0 +1,3 @@ +export "src/spectre.dart" show Spectre; +export "src/template.dart" show Template; +export "src/scope.dart" show Scope, Scoper, SimpleScoper, defaultScoper; \ No newline at end of file diff --git a/lib/src/scope.dart b/lib/src/scope.dart new file mode 100644 index 0000000..6d0b503 --- /dev/null +++ b/lib/src/scope.dart @@ -0,0 +1,44 @@ +import "template.dart"; + +enum Scope { + authentication, + identification, + recovery, +} + +Template scopeDefaultTemplate(Scope scope) { + switch (scope) { + case Scope.identification: + return Template.name; + case Scope.recovery: + return Template.phrase; + case Scope.authentication: + default: + return Template.long; + } +} + +abstract class Scoper { + String scope(Scope scope); +} + +class SimpleScoper implements Scoper { + final String _key; + + SimpleScoper(this._key); + + @override + String scope(Scope scope) { + switch (scope) { + case Scope.identification: + return "$_key.login"; + case Scope.recovery: + return "$_key.answer"; + case Scope.authentication: + default: + return _key; + } + } +} + +var defaultScoper = SimpleScoper("com.lyndir.masterpassword"); \ No newline at end of file diff --git a/lib/src/spectre.dart b/lib/src/spectre.dart new file mode 100644 index 0000000..eb18df8 --- /dev/null +++ b/lib/src/spectre.dart @@ -0,0 +1,84 @@ +import 'dart:typed_data'; +import 'dart:convert'; + +import 'package:dart_spectre/src/scope.dart'; +import 'package:dart_spectre/src/template.dart'; + +import "package:pointycastle/export.dart"; + +final scrypt = Scrypt(); +final hmac = HMac(SHA256Digest(), 64); + +class Spectre { + final String _name; + final String _secret; + late Scoper _scoper; + + late Uint8List _key; + + Spectre(this._name, this._secret, [Scoper? scoper]) { + _scoper = scoper ?? defaultScoper; + _key = _userKey(); + } + + Uint8List _userKey() { + var nameBytes = utf8.encode(_name); + var secretBytes = utf8.encode(_secret); + var keyScope = utf8.encode(_scoper.scope(Scope.authentication)); + + var nameBytesLen = nameBytes.length; + var keySalt = keyScope + + List.from([ + nameBytesLen >> 24, + nameBytesLen >> 16, + nameBytesLen >> 8, + nameBytesLen, + ]) + + nameBytes; + + scrypt.init(ScryptParameters(32768, 8, 2, 64, Uint8List.fromList(keySalt))); + return scrypt.process(Uint8List.fromList(secretBytes)); + } + + Uint8List _siteKey(String name, int counter, Scope scope) { + var nameBytes = utf8.encode(name); + var scopeBytes = utf8.encode(_scoper.scope(scope)); + + var nameBytesLen = nameBytes.length; + var keySalt = scopeBytes + + List.from([ + nameBytesLen >> 24, + nameBytesLen >> 16, + nameBytesLen >> 8, + nameBytesLen, + ]) + + nameBytes + + List.from([ + counter >> 24, + counter >> 16, + counter >> 8, + counter, + ]); + + hmac.init(KeyParameter(_key)); + return hmac.process(Uint8List.fromList(keySalt)); + } + + String site(String siteName, {Template? templateType, int counter = 1, Scope scope = Scope.authentication}) { + templateType = templateType ?? scopeDefaultTemplate(scope); + + var siteKey = _siteKey(siteName, counter, scope); + + var templateSet = templates[templateType]!; + var template = templateSet[siteKey[0]%templateSet.length]; + + var out = StringBuffer(); + var idx = 0; + for (var rune in template.runes) { + var chars = characters[String.fromCharCode(rune)]!; + var char = chars[siteKey[++idx]%chars.length]; + out.write(char); + } + return out.toString(); + } +} diff --git a/lib/src/template.dart b/lib/src/template.dart new file mode 100644 index 0000000..257a9f0 --- /dev/null +++ b/lib/src/template.dart @@ -0,0 +1,77 @@ +enum Template { + maximum, + long, + medium, + short, + pin, + name, + phrase, + basic, +} + +const Map> templates = { + Template.maximum: [ + "anoxxxxxxxxxxxxxxxxx", + "axxxxxxxxxxxxxxxxxno", + ], + Template.long: [ + "CvcvnoCvcvCvcv", + "CvcvCvcvnoCvcv", + "CvcvCvcvCvcvno", + "CvccnoCvcvCvcv", + "CvccCvcvnoCvcv", + "CvccCvcvCvcvno", + "CvcvnoCvccCvcv", + "CvcvCvccnoCvcv", + "CvcvCvccCvcvno", + "CvcvnoCvcvCvcc", + "CvcvCvcvnoCvcc", + "CvcvCvcvCvccno", + "CvccnoCvccCvcv", + "CvccCvccnoCvcv", + "CvccCvccCvcvno", + "CvcvnoCvccCvcc", + "CvcvCvccnoCvcc", + "CvcvCvccCvccno", + "CvccnoCvcvCvcc", + "CvccCvcvnoCvcc", + "CvccCvcvCvccno", + ], + Template.medium: [ + "CvcnoCvc", + "CvcCvcno", + ], + Template.short: [ + "Cvcn", + ], + Template.pin: [ + "nnnn", + ], + Template.name: [ + "cvccvcvcv", + ], + Template.phrase: [ + "cvcc cvc cvccvcv cvc", + "cvc cvccvcvcv cvcv", + "cv cvccv cvc cvcvccv", + ], + Template.basic: [ + "aaanaaan", + "aannaaan", + "aaannaaa", + ], +}; + +const Map characters = { + "V": "AEIOU", + "C": "BCDFGHJKLMNPQRSTVWXYZ", + "v": "aeiou", + "c": "bcdfghjklmnpqrstvwxyz", + "A": "AEIOUBCDFGHJKLMNPQRSTVWXYZ", + "a": "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", + "n": "0123456789", + "o": r"@&%?,=[]_:-+*$#!'^~;()/.", + "x": + r"AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()", + " ": " ", +}; diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..e818145 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,362 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "30.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + lints: + dependency: "direct dev" + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + pointycastle: + dependency: "direct main" + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.10" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.19.3" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.7" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "7.4.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + xml: + dependency: "direct dev" + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.14.4 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..46ba9cf --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,16 @@ +name: dart_spectre +description: A simple command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.14.4 <3.0.0' + + +dependencies: + pointycastle: ^3.4.0 + +dev_dependencies: + lints: ^1.0.0 + test: ^1.19.3 + xml: ^5.3.1 diff --git a/test/spectre_test.dart b/test/spectre_test.dart new file mode 100644 index 0000000..40d40de --- /dev/null +++ b/test/spectre_test.dart @@ -0,0 +1,17 @@ +import 'package:dart_spectre/spectre.dart'; +import 'package:test/test.dart'; + +void main() { + test("Example", () { + var spectre = Spectre("Robert Lee Mitchell", "banana colored duckling"); + var pw = spectre.site("masterpasswordapp.com"); + expect(pw, equals("Jejr5[RepuSosp")); + }); + + test("Example_second", () { + var scoper = SimpleScoper("com.jojodev.jolheiser"); + var spectre = Spectre("Robert Lee Mitchell", "banana colored duckling", scoper); + var pw = spectre.site("jojodev.com", scope: Scope.identification, templateType: Template.maximum, counter: 2); + expect(pw, equals("Ig^JIcxD!*)TbefJBi6-")); + }); +} \ No newline at end of file diff --git a/test/spectre_tests.dart b/test/spectre_tests.dart new file mode 100644 index 0000000..c71399a --- /dev/null +++ b/test/spectre_tests.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +import 'package:xml/xml.dart'; + +import 'package:dart_spectre/spectre.dart'; +import 'package:test/test.dart'; + +void main() async { + var tests = await File("spectre_tests.xml").readAsString(); + +} \ No newline at end of file diff --git a/test/spectre_tests.xml b/test/spectre_tests.xml new file mode 100644 index 0000000..ace009f --- /dev/null +++ b/test/spectre_tests.xml @@ -0,0 +1,75 @@ + + + + -1 + Robert Lee Mitchell + banana colored duckling + spectre.app + Long + 1 + Authentication + + + + + + 3 + ReqoCenuXonu1? + + + + 1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8 + Wewa9$YaqdDaje + + + + 351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08 + TeweBacg0$Tobe + + + + LiheCuwhSerz6) + + + Identification + Name + wesruqori + + + Recovery + Phrase + zowp quy roxzuyu qim + + + Maximum + FB22U#U*LPFWlWxaxK2. + + + Medium + ReqMon0) + + + Basic + FLI88cDK + + + Short + Req8 + + + PIN + 3648 + + + Name + reqmonajo + + + Phrase + re monnu mit jededda + + + 4294967295 + ZurdYoda6:Jogs + +