2
0

Compare commits

...

451 Commits

Author SHA1 Message Date
Maarten Billemont
0b6e43a18f The root of the users VC interferes with touches from the sites VC when it appears. 2021-11-03 21:13:04 -04:00
Maarten Billemont
c94c52f4b6 Fix migration tips no longer interactable when logged in. 2021-11-03 16:51:37 -04:00
Maarten Billemont
5de9b05299 Move Spectre migration dialogs fully global & make closeable. 2021-11-01 21:16:00 -04:00
Maarten Billemont
f27607e63c fixup! Support for unset login type. 2021-11-01 20:52:04 -04:00
Maarten Billemont
0b45dc584f Fix deadlock when loadStore posts notifications to the main thread. 2021-11-01 20:07:13 -04:00
Maarten Billemont
88a4d7ba4d Support for unset login type. 2021-11-01 20:06:37 -04:00
Maarten Billemont
94a6c925bc Update Sentry SDK. 2021-10-31 14:32:31 -04:00
Maarten Billemont
eda9749cf2 Time to crack script updates.
[ADDED]     Calculate cost for cracking a password.
[UPDATED]   Hardware cost data based on various GPUs with updated hashcat metrics.
[ADDED]     Ability to calculate the strength of an arbitrary password.
2021-10-31 14:17:21 -04:00
Maarten Billemont
4c096555d0 Spectre migration updates.
[ADDED]     Migration prompt to sign-in screen.
[FIXED]     Spectre Apple ID.
2021-10-31 14:15:46 -04:00
Maarten Billemont
403c45519a 2.7-java-12 2021-03-02 19:33:53 -05:00
Maarten Billemont
8d33ff8ec5 Fix password field manipulation bugs.
[FIXED]     By stubbing the password field's document, we broke some editing capabilities. Stub the document in a way that respects its length.
2021-03-02 19:31:47 -05:00
Maarten Billemont
c38f713f05 Update site after release. 2021-02-18 11:27:18 -05:00
Maarten Billemont
d59595824b Fix path for C release VERSION and TAG. 2021-02-18 10:15:40 -05:00
Maarten Billemont
2b78449a48 Update site after release. 2021-02-18 09:58:10 -05:00
Maarten Billemont
2eda9b1152 2.7-java-11 2021-02-18 09:44:11 -05:00
Maarten Billemont
8a032ba891 Master Password is moving to Spectre!
https://gitlab.com/spectre.app
https://spectre.app/

Master Password is no longer actively maintained from here on out.
2021-02-17 23:40:51 -05:00
Maarten Billemont
eda34f6b0b Update binaries for latest core API. 2021-02-11 15:19:51 -05:00
Maarten Billemont
6e1855b00c fixup! Fix ANSI C11 support. 2021-02-11 15:07:59 -05:00
Maarten Billemont
90aaf23bb5 Build script update.
- Fixed build on Windows
- Improved documentation
- Fixed arch logic, separate standard arch from host name
2021-02-11 14:12:06 -05:00
Maarten Billemont
2e9c79f6b3 Fix ANSI C11 support. 2021-02-11 14:11:09 -05:00
Maarten Billemont
83fa6c39bc Site toolbar buttons should toggle on site, not result. 2021-02-08 14:21:52 -05:00
Maarten Billemont
913208255e Temporarily disable findsecbugs due to bug.
https://stackoverflow.com/a/62894507/58803
2021-02-08 14:20:42 -05:00
Maarten Billemont
963a1222be Update SpotBugs. 2021-02-08 14:20:29 -05:00
Maarten Billemont
a1264e0f91 Move main thread assert to the right spot. 2020-10-14 09:24:15 -04:00
Maarten Billemont
4f0065fba8 Update to tighten warnings configuration. 2020-10-14 09:23:46 -04:00
Maarten Billemont
b2c688a1ce Always scope sites query to active user. 2020-09-03 14:31:10 -04:00
Maarten Billemont
aee1030758 Copy device identifier from macOS menu item. 2020-09-03 14:10:08 -04:00
Maarten Billemont
f665aeccc4 Load store synchronously to not return racy or invalid contexts. 2020-09-03 13:57:16 -04:00
Maarten Billemont
e58b9ef34f Fetch request syntax update. 2020-09-03 13:56:46 -04:00
Maarten Billemont
968de6026f Build fixes. 2020-09-03 13:56:10 -04:00
Maarten Billemont
2886e040a1 Warning fix. 2020-09-03 11:04:59 -04:00
Maarten Billemont
01cea659ca Bump Pearl for nullability fixes. 2020-09-03 10:52:56 -04:00
Maarten Billemont
3a18e02a87 Revert "Xcode 12 update of xcdatamodel."
This reverts commit 2de57984b2.

NSSecureUnarchiveFromDataTransformer is not compatible with iOS 9-11
2020-09-03 10:41:05 -04:00
Maarten Billemont
2de57984b2 Xcode 12 update of xcdatamodel. 2020-09-03 10:39:23 -04:00
Maarten Billemont
c7201c7d90 Update for Xcode 12 & build fixes. 2020-09-03 09:53:08 -04:00
Maarten Billemont
d62c6b4594 Sites no longer load with batch requests & load improvements. 2020-09-03 09:52:08 -04:00
Maarten Billemont
57f275c471 Update for Xcode 12 & add device identifier to UI. 2020-09-02 16:40:41 -04:00
Maarten Billemont
b1d8296396 Add nonstandard output type tests for i,r + fix indentation. 2020-08-29 09:48:14 -04:00
Maarten Billemont
6d25463de0 Rename next-gen to Spectre. 2020-07-21 21:21:52 -04:00
Maarten Billemont
029041dcf7 Expand the maximum length of query searches. 2020-07-11 21:43:34 -04:00
Maarten Billemont
cfbf1f5cac Use visibility instead of gone so constraints are managed by stack view. 2020-07-11 10:48:19 -04:00
Maarten Billemont
acbd2dc2cc Include purchased features in export file. 2020-07-06 22:28:15 -04:00
Maarten Billemont
8fcac65fd5 Additional documentation for parameter contracts. 2020-07-06 22:27:47 -04:00
Maarten Billemont
9904f4c715 Try to detect if cipherText is plainText.
In some situations, the cipherText that was passed in is actually
plainText.  Old mpsites files used to store the login name as plain text
even though the file was redacted.  Newer versions of the file store the
login name as ciphertext.  There is no clear way to distinguish between
the two cases.
2020-07-06 14:18:47 -04:00
Maarten Billemont
b51a3de32c Check pasteboard when app enters foreground, not activation. 2020-07-05 20:24:59 -04:00
Maarten Billemont
9e91f0a9d6 More reliable monitoring of changes using NSFetchedResultsController. 2020-07-05 20:24:18 -04:00
Maarten Billemont
7368b1be90 Source is button item, not a view. 2020-05-24 10:54:25 -04:00
Maarten Billemont
5db294bdb3 Show purchase transaction failures to the user. 2020-05-23 19:58:47 -04:00
Maarten Billemont
fee7bc7401 Resolve site cell sizing issues across window sizes. 2020-05-23 19:08:43 -04:00
Maarten Billemont
21968f4ba6 Fix messages for password reset. 2020-05-23 12:35:55 -04:00
Maarten Billemont
8582c934c2 Limit fuzzy searching to a depth of 10.
Avoids choking when query string becomes long and there are excessively
long site name entries.
2020-05-23 12:14:22 -04:00
Maarten Billemont
7091e2ee1b Disable automatic font scaling.
It's causing issues with pop-up alerts.
2020-05-23 10:12:07 -04:00
Maarten Billemont
d5d455ee57 Fix issues with content insets for sites across OS versions. 2020-05-22 23:04:36 -04:00
Maarten Billemont
e6ae06798b Handle store opening errors more gracefully.
Store opening can fail for example when hard-locking the device while
it's opening up.
2020-05-22 22:26:43 -04:00
Maarten Billemont
1cae4c754b Group MPErrors together, ignoring the actual inline values. 2020-05-22 22:26:18 -04:00
Maarten Billemont
93ad86e63c Remove PearlAppDelegate. 2020-05-22 17:34:04 -04:00
Maarten Billemont
cf74dc5cc2 Updated NSMenu API. 2020-05-19 13:28:23 -04:00
Maarten Billemont
981bdb3ab4 Fix isDescendantOfView bug & Sentry script error on failure. 2020-05-19 09:21:06 -04:00
Maarten Billemont
9bea8bcbdf Sheets need a source view on iPad. 2020-05-19 08:21:44 -04:00
Maarten Billemont
363d6f6639 Test configuration was removed for Release. 2020-05-18 13:01:16 -04:00
Maarten Billemont
eb1632cb62 Install cocoapods dependencies & gradle works on JDK 11 now. 2020-05-18 12:43:37 -04:00
Maarten Billemont
73fadaef7f iOS uses Xcode 11 now. 2020-05-18 12:15:36 -04:00
Maarten Billemont
60200f6302 Fix all versions advertising themselves as V0. 2020-05-18 12:14:44 -04:00
Maarten Billemont
cce8db5c48 Purge unused and deprecated UISearchDisplayController. 2020-05-18 11:10:23 -04:00
Maarten Billemont
6f3da5ccf0 Harmonize consent features. 2020-05-16 22:34:49 -04:00
Maarten Billemont
52c87eaeca Keep sites sorted by name on export to ensure consistency. 2020-05-16 16:03:42 -04:00
Maarten Billemont
1dccdd0a3c Improvements to algorithm and counter steppers. 2020-05-16 15:40:59 -04:00
Maarten Billemont
eb8d10ed05 Use new UN notification API. 2020-05-14 10:32:04 -04:00
Maarten Billemont
d9e5f77bee Resolve export state generation. 2020-05-14 00:04:29 -04:00
Maarten Billemont
60f60d087e Embed views using embed segue instead of MPRootSegue. 2020-05-12 23:27:56 -04:00
Maarten Billemont
df97dec2fe Sentry user login & MPError arguments. 2020-05-12 10:54:15 -04:00
Maarten Billemont
3bac8d9e0a iPad fixes & log initial start-up crashes. 2020-05-11 21:28:27 -04:00
Maarten Billemont
3fa7e1e8a1 Ensure Countly does not use the IDFA. 2020-05-02 12:31:54 -04:00
Maarten Billemont
d1104e4028 Need icon 1x icon for iPad. 2020-04-30 09:32:17 -04:00
Maarten Billemont
e9f2a25c9c Project icons update. 2020-04-28 13:49:34 -04:00
Maarten Billemont
171a3f0978 Icon update. 2020-04-28 12:24:41 -04:00
Maarten Billemont
8cfb9a83c5 mpjson support on iOS. 2020-04-27 16:33:10 -04:00
Maarten Billemont
5717375e75 Improve language. 2020-04-27 16:33:01 -04:00
Maarten Billemont
cc2dca3bd0 Report missing support when trying to parse unknown format. 2020-04-27 16:32:30 -04:00
Maarten Billemont
7575924d80 Bump site links to macOS app. 2020-04-26 00:39:08 -04:00
Maarten Billemont
8bedcedfaf Enable support for internal actions from URLs. 2020-04-25 17:13:48 -04:00
Maarten Billemont
10b205c541 Open URLs in external browser. 2020-04-25 11:44:27 -04:00
Maarten Billemont
774f183ac0 Improved web support & trigger notification URLs. 2020-04-25 10:44:59 -04:00
Maarten Billemont
2279aacb5a Change the user's default type from the Mac UI. 2020-04-21 13:27:06 -04:00
Maarten Billemont
1bd654621c Fix persistence of sendInfoDecided.
Don't log it as an event, event logging is probably disabled at this
point anyway.
2020-04-21 13:26:12 -04:00
Maarten Billemont
c4f60e325d Harmonize consent flow on both platforms. 2020-04-21 11:33:31 -04:00
Maarten Billemont
d4de3afb72 Show internal reason for why import fails. 2020-04-20 17:09:38 -04:00
Maarten Billemont
694b5ea227 Make marshal error messages owned by the file.
Error message lifecycle was limited to the static mpw_str buffer, which
is far too limited and also dangerous.  Own the message by the
MPMarshalFile object, freed in mpw_marshal_file_free.
2020-04-20 17:07:35 -04:00
Maarten Billemont
66dd78797b We use bounds-checked interfaces, so need ext1. 2020-04-20 17:05:37 -04:00
Maarten Billemont
61d1660560 Consent tweaks.
Don't conditional Countly events on sendInfo, it already turns Countly
on/off.

Keep Sentry enabled for longer while turning on/off for sendInfo to
cache more errors.

Use sendInfoDecided event only for initial sendInfo prompt, can use
Countly's censent tools to view overall opt-in/opt-outs.
2020-04-19 17:40:28 -04:00
Maarten Billemont
c3568e4744 Enable minor & build numbers > 9. 2020-04-18 22:13:36 -04:00
Maarten Billemont
0c921d4318 Update fonts to stay consistent with system UIs. 2020-04-18 21:50:06 -04:00
Maarten Billemont
0178efaaf7 Ask for consent to sendInfo. 2020-04-18 21:48:55 -04:00
Maarten Billemont
14f919584b Project update. 2020-04-16 17:46:39 -04:00
Maarten Billemont
16f6c3c593 Update preference menu item state. 2020-04-16 17:13:23 -04:00
Maarten Billemont
63ca2ae83e Can lower deployment target to 10.10 2020-04-16 17:13:08 -04:00
Maarten Billemont
1c3ea3826f Move identicon and toID to mpw native.
Clean out all unused Java MPAlgorithm stuff.

Fix master password entries stuck in memory.
2020-04-15 19:09:02 -04:00
Maarten Billemont
ff9596aef0 Update log sink API. 2020-04-15 19:08:11 -04:00
Maarten Billemont
b79ed1ca0b Fix JNI native symbol names after refactor. 2020-04-14 20:07:21 -04:00
Maarten Billemont
9a362f13a3 Use new mpw log sink logging mechanism for JNI logging. 2020-04-14 19:13:49 -04:00
Maarten Billemont
11d6660e5a Sinks report success now & fallback sink triggers on failure.
We now trigger the fallback sink (currently, mpw_log_sink_file) if no
sinks successfully handled the log message.
2020-04-14 19:12:28 -04:00
Maarten Billemont
62e1563fa6 Ensure b64Cursor chars are unsigned when used as index. 2020-04-14 19:11:08 -04:00
Maarten Billemont
9b8ff7ad0c Kill debug code. 2020-04-13 21:33:28 -04:00
Maarten Billemont
f1fc07cf9e FaceID privacy message. 2020-04-13 18:56:57 -04:00
Maarten Billemont
00ac788f4f Consistent flow for enabling notifications. 2020-04-13 18:12:41 -04:00
Maarten Billemont
514c383310 Obsolete sources. 2020-04-12 22:38:40 -04:00
Maarten Billemont
9a3bcd1c6f Fix bad override of forKeyedSubscript: 2020-04-12 21:39:13 -04:00
Maarten Billemont
d30d469663 Sites list scroll & keyboard fixes. 2020-04-12 19:16:13 -04:00
Maarten Billemont
b428ee0003 Honour animated property better.
Also: don't need to force a layout if not in an animation block.
2020-04-12 19:14:25 -04:00
Maarten Billemont
f80ffd078b Move dependencies to Podfile. 2020-04-12 19:12:45 -04:00
Maarten Billemont
7f1a28ffa7 Fixes and updates to iOS.
Move dependencies into Podfile where possible.

IASK doesn't wrap properly; just removed it.

Fixes to safe area and content insets.
2020-04-11 12:13:48 -04:00
Maarten Billemont
8eeba2e005 Don't crash if decrypt fails. 2020-04-09 21:46:09 -04:00
Maarten Billemont
fd6cbaa9a5 Ignore backup files. 2020-04-09 21:33:05 -04:00
Maarten Billemont
1651e9ad4a Automatic configuration cleanup. 2020-04-09 21:04:35 -04:00
Maarten Billemont
02c1e2af46 straf relies on a nil terminator. 2020-04-09 21:03:59 -04:00
Maarten Billemont
b275286b2d Consent and notification permissions on the main thread. 2020-04-09 21:03:11 -04:00
Maarten Billemont
7818feaf0b Disable the login field if login is generated. 2020-04-08 20:21:58 -04:00
Maarten Billemont
3970cd2eac Remove unused lvar. 2020-04-08 20:21:42 -04:00
Maarten Billemont
21b0053ccb Copy login name on shift & update UI to reflect it. 2020-04-08 19:36:19 -04:00
Maarten Billemont
6dd8790404 Enable/disable Sentry on iOS with sendInfo. 2020-04-07 16:05:53 -04:00
Maarten Billemont
508abec94e Toggle Sentry and Countly when sendInfo is enabled/disabled. 2020-04-07 16:04:17 -04:00
Maarten Billemont
b010432796 Toggle sendInfo through preferences & move user import/export. 2020-04-07 15:30:23 -04:00
Maarten Billemont
a91e65f72f Push notification support on macOS. 2020-04-06 23:11:36 -04:00
Maarten Billemont
4b5c696241 Give Countly consent if sendInfo is enabled. 2020-04-06 20:38:29 -04:00
Maarten Billemont
128c3dcf87 Use a material that is more compatible with light/dark themes. 2020-04-06 19:35:58 -04:00
Maarten Billemont
8e15be21ab Cross-platform translation of mpw logs into Pearl. 2020-04-06 19:27:17 -04:00
Maarten Billemont
c62ff63718 Image resource issues.
tip_alert_black needs to be sliced and scaleable.

Aspect-stretch background.png if device screen is too large.
2020-04-06 19:25:43 -04:00
Maarten Billemont
d564afe3ec Delegate main-thread activity on the main thread. 2020-04-06 19:24:46 -04:00
Maarten Billemont
340044e945 Icon and launch screen updates. 2020-04-05 01:17:13 -04:00
Maarten Billemont
078cd412a8 Update deprecated material. 2020-04-05 01:16:49 -04:00
Maarten Billemont
83b824a44e Project updates. 2020-04-04 20:19:22 -04:00
Maarten Billemont
24a6781687 Don't leak internal file on error if mpw_marshal_write file is NULL. 2020-04-04 20:17:14 -04:00
Maarten Billemont
b118bc4e9b Sentry SDK update to v5 2020-04-04 19:50:37 -04:00
Maarten Billemont
0405c1776c Remove obsolete Fabric support. 2020-04-04 18:15:51 -04:00
Maarten Billemont
81a92400fb pushTestMode doesn't support nil values but defaults to it. 2020-04-04 18:12:26 -04:00
Maarten Billemont
7085eaf726 Hang detector is an iOS-only thing. 2020-04-04 18:09:33 -04:00
Maarten Billemont
878970ff22 JSON support for Mac app. 2020-04-04 17:34:07 -04:00
Maarten Billemont
6a635491bd Fix MPW_COLOR test, if set to 0. 2020-04-04 17:33:32 -04:00
Maarten Billemont
fa0c80d6b3 Log out active user and key when user is changed or deleted or unset. 2020-04-04 16:30:14 -04:00
Maarten Billemont
975743adec Use the user, not a site algorithm, for generating and testing the keyID.
- Order of fall-back formats from most to least preferred.
- The keyID should be based on the user algorithm, not the site algorithm.
- Standardize mpw_format* function names.
2020-04-02 16:15:01 -04:00
Maarten Billemont
39c6d5668a Improved repeat generation of master password suggestions. 2020-04-02 16:13:12 -04:00
Maarten Billemont
c4abdb6d49 Test result output in export files. 2020-04-02 16:11:30 -04:00
Maarten Billemont
887d07f004 macOS update. 2020-03-25 13:12:06 -04:00
Maarten Billemont
a640b798b5 Resolve main thread access issues. 2020-03-25 12:16:19 -04:00
Maarten Billemont
07250d50fd Push notifications support & remove Test configuration. 2020-03-13 12:15:24 -04:00
Maarten Billemont
56f5870bbb Make white text white, not system, since background is always dark. 2020-03-13 11:55:13 -04:00
Maarten Billemont
ee16c4a66d Ensure alerts are triggered on main thread. 2020-03-05 11:29:10 -05:00
Maarten Billemont
9c3e272849 Case insensitive compare values that don't need to rely on exact case. 2020-03-04 12:50:59 -05:00
Maarten Billemont
131720eb8d Fix login/result state import/export, keyID; Volto export; Pearl log sink. 2020-02-28 17:33:38 -05:00
Maarten Billemont
b15417aa31 Marshal nulls as empty text & document data structures.
Documented the data fields in the marshal structure to avoid value
ambiguity.

NULL values for eg. identicon shouldn't serialize to flat (mpsites) file
as "(null)" but rather as empty "".

Parsing header values from the flat file that are empty yielded NULL
instead of the empty string since mpw_strtok was needlessly aggressive
on that front.
2020-02-28 17:25:20 -05:00
Maarten Billemont
f659c0936e Volto migration alerts. 2020-02-26 23:01:44 -05:00
Maarten Billemont
35e64ea9cf Swizzle fixes, type build fixes, disable hang timer for debug builds. 2020-02-16 18:49:06 -06:00
Maarten Billemont
d509c01572 Countly view tracking & Pearl API for C string length. 2020-02-13 13:02:19 -05:00
Maarten Billemont
485cf65694 Default to mpw_log_sink_file if no sinks are registered. 2020-02-12 22:32:14 -05:00
Maarten Billemont
f32f2a6726 Update CLI API for key providers. 2020-02-12 22:13:55 -05:00
Maarten Billemont
bd38840ed3 Import for standard time_t. 2020-02-12 22:12:48 -05:00
Maarten Billemont
6eaa491d67 Replace Crashlytics/Fabric with Countly & Sentry. 2020-01-27 13:27:10 -05:00
Maarten Billemont
b460e27696 Store preferences in user file using mpw data API. 2020-01-23 16:49:47 -05:00
Maarten Billemont
eb1e23311e API update, user export. 2020-01-23 16:47:10 -05:00
Maarten Billemont
38b3dcdba0 Android and macOS dependency build fixes. 2020-01-23 16:42:23 -05:00
Maarten Billemont
85f6c03500 More build standardization.
CPPFLAGS get passed to both C and C++ compilers (all compilers that do pre-processing)
2020-01-23 16:42:17 -05:00
Maarten Billemont
4521c4f919 Fix build script for libs that have no INSTALL file. 2020-01-23 16:41:38 -05:00
Maarten Billemont
feac039bc4 Fix for autoreconf overwriting repo files. 2020-01-23 16:41:33 -05:00
Maarten Billemont
dbda20ee8a Fix for building after SDK path changes. 2020-01-23 16:41:23 -05:00
Maarten Billemont
6832c05138 Fix AES padding not removed if % AES_BLOCKSIZE.
Don't memcpy beyond src buffer size in strcpy implementation.

Technically safe since the buffer overrun was replaced by NUL, but act
can trigger memory safety features.
2020-01-23 16:37:08 -05:00
Maarten Billemont
f999e75ebe Hide app secrets such as API keys in code.
Move secret keys out of the repository and hide them in the binary.

C-string -> String decoding for unsigned strings and buffers to
facilitate conversion of various C API strings into Swift.

Some cleanup in base64 coding utility.

A utility for converting hex strings into byte buffers.

Improved checks during AES routine and fix PKCS padding bug if
plain text fits block size.
2020-01-23 16:36:51 -05:00
Maarten Billemont
8886c6a6ef Bring timetocrack <x> in line with spec. 2020-01-23 16:33:57 -05:00
Maarten Billemont
51afed2fe0 More advanced mpw-internal logging mechanism.
Logging now happens at the mpw-core level, by default using sinks that
can be registered.

For iOS we forward log messages to os_log for unified logging.  We also
keep a record of log messages for future retrieval in a log view.

This obsoletes and removes Pearl's logger entirely.
2020-01-23 16:32:53 -05:00
Maarten Billemont
44a2a67417 Ensure inf+ log messages are free of private information. 2020-01-23 16:25:56 -05:00
Maarten Billemont
37532f0ce5 Fix unmarshalling with no sites. 2020-01-23 16:25:41 -05:00
Maarten Billemont
194c626aed Sync internals for marshalling with documentation. 2020-01-23 16:25:33 -05:00
Maarten Billemont
344cce084f Don't keep deleted sites & questions.
+ ability to filter / delete data children.
2020-01-23 16:25:27 -05:00
Maarten Billemont
023749049a Clean up now that implementation is Native only. 2020-01-23 16:25:19 -05:00
Maarten Billemont
aec5e371b8 Prefix versions with V to fix a Swift enum naming import issue. 2020-01-23 16:25:07 -05:00
Maarten Billemont
2af71fbc34 Refactor for safety. 2020-01-23 16:24:31 -05:00
Maarten Billemont
44fa32697a Huge refactor to do marshalling completely through mpw data object now. 2020-01-23 16:24:08 -05:00
Maarten Billemont
0fcdb2a5e6 Algorithm version C files need to be separately compiled now. 2020-01-23 16:23:48 -05:00
Maarten Billemont
7fd0172815 Refactor in preparation of reading ext data prior to auth. 2020-01-23 16:23:35 -05:00
Maarten Billemont
a2b1f22b53 More standard way to separate algorithm versions. 2020-01-23 16:23:15 -05:00
Maarten Billemont
ff9a5eaf04 pre-compiled libraries. 2020-01-23 16:17:16 -05:00
Maarten Billemont
256a0d2cb5 Don't need .cc symlinks anymore. 2020-01-23 16:17:10 -05:00
Maarten Billemont
a9ae10d08b Can't reuse original env. 2020-01-23 16:17:04 -05:00
Maarten Billemont
fabb9a032d Refactor gradle core build configuration. 2020-01-23 16:16:59 -05:00
Maarten Billemont
47ecf30b2d Move cross-compilation Dockerfile. 2020-01-23 16:16:49 -05:00
Maarten Billemont
bb5d693452 message / format success check was short-cut. 2020-01-23 16:16:44 -05:00
Maarten Billemont
6e7dc74bb3 Enable access to json-c for marshalling. 2020-01-23 16:16:38 -05:00
Maarten Billemont
068fdbbf32 Don't declare new symbols for each file that includes mpw-util.h 2020-01-23 16:16:27 -05:00
Maarten Billemont
08a06639d0 Cross compile build fixes. 2020-01-23 16:16:12 -05:00
Maarten Billemont
61611e108c Linux cross-compiling. 2020-01-23 16:16:07 -05:00
Maarten Billemont
64536f95c0 Linux cross compiling, linking & x86_64 arch standardization. 2020-01-23 16:14:33 -05:00
Maarten Billemont
a132e03b88 Fix vsnprintf args reuse, size & break before va_end. 2020-01-23 16:14:27 -05:00
Maarten Billemont
2bbd2549a1 Temporary hack because gradle's cpp-library plugin can't find C source files yet. 2020-01-23 16:14:19 -05:00
Maarten Billemont
87913326a5 Linux build fixes. 2020-01-23 16:14:12 -05:00
Maarten Billemont
bf5e30c2c7 JNI reference memory management. 2020-01-23 16:14:05 -05:00
Maarten Billemont
1c6a5256c1 Log native mpw into Java. 2020-01-23 16:13:58 -05:00
Maarten Billemont
fd1014926c Warning fixes and spotbug configuration tweaks. 2020-01-23 16:11:12 -05:00
Maarten Billemont
36692ac10d Consistent SpotBugs configuration & warning fixes. 2020-01-23 16:11:01 -05:00
Maarten Billemont
d0dc393d70 Gradle update. 2020-01-23 16:10:05 -05:00
Maarten Billemont
d0cf57105a Use the included words dictionary. 2020-01-23 16:07:52 -05:00
Maarten Billemont
55e30cb454 Build fixes. 2020-01-23 16:07:30 -05:00
Maarten Billemont
64577d5e0f API update. 2020-01-23 16:06:47 -05:00
Maarten Billemont
57f77e4f8e Fix leak of encryption buffer. 2020-01-23 16:06:41 -05:00
Maarten Billemont
360c0ade66 Improved maintenance of marshal file & abort on ftl. 2020-01-23 16:06:36 -05:00
Maarten Billemont
bf572a5e62 Missing accessory, fixes for data object lookup. 2020-01-23 16:06:26 -05:00
Maarten Billemont
4c6d7ac36c Marshal data API & fixes. 2020-01-23 16:06:17 -05:00
Maarten Billemont
ddb786c332 Persist unknown JSON properties, expose to API, safety improvements. 2020-01-23 16:06:07 -05:00
Maarten Billemont
fc1e86f0ca File holds data. 2020-01-23 16:06:02 -05:00
Maarten Billemont
9833f02339 Full file marshalling and prepare to make entire document available. 2020-01-23 16:05:55 -05:00
Maarten Billemont
2fdd9d2ca1 Allow marshalling without masterKey and record keyID in user. 2020-01-23 16:05:12 -05:00
Maarten Billemont
1b90b3deea Allow unauthenticated marshal reading to reset master key. 2020-01-23 16:04:53 -05:00
Maarten Billemont
a19df80a03 Enable annotation of library imports. 2020-01-23 16:03:40 -05:00
Maarten Billemont
0bbc6de2ab NULL if marshal content can't be understood. 2020-01-23 16:03:02 -05:00
Maarten Billemont
2af541a4d7 API documentation clarification. 2020-01-23 16:00:35 -05:00
Maarten Billemont
6d36f17e57 Change marshal API to return output directly.
Avoids an ambiguity between return type and out value (eg. true but
NULL), and improves Swift API access.
2020-01-23 15:59:21 -05:00
Maarten Billemont
f0acd1fed1 Use a rolling buffer of 10 for mpw_str too.
This way we can pass mpw_str results into mpw_strs, as we do with the
identicon marshalling.
2020-01-23 15:58:59 -05:00
Maarten Billemont
a37b095a66 site.name -> siteName, add identicon marshalling to iOS, missing resultState 2020-01-23 15:58:32 -05:00
Maarten Billemont
2637e9ba99 Add identicon to export + fix memory leaks for marshal failures. 2020-01-23 15:57:27 -05:00
Maarten Billemont
ccd9763649 Standardize UTF8 mbyte length, identicon encoding, be explicit about storage duration in C API. 2020-01-23 15:57:07 -05:00
Maarten Billemont
ad4081be61 Improved error checking, NULL handling and API documentation. 2020-01-23 15:55:03 -05:00
Maarten Billemont
b42bc732ac Ensure buffer is set. 2020-01-23 15:54:45 -05:00
Maarten Billemont
4166e88f92 Use compound literals. 2020-01-23 15:54:24 -05:00
Maarten Billemont
4b73508292 Harmonize API naming. 2020-01-23 15:53:55 -05:00
Maarten Billemont
dd123a431c Fix masterKeyProvider args & mem bugs + multiple format extensions. 2020-01-23 15:52:28 -05:00
Maarten Billemont
070f909a7f Clarify. 2020-01-23 15:51:57 -05:00
Maarten Billemont
65ad628641 Add avatar and lastUsed to marshal info. 2020-01-23 15:50:36 -05:00
Maarten Billemont
c11b63912d Harmonize naming of state variables. 2020-01-23 15:49:54 -05:00
Maarten Billemont
68e58cddd4 Fix loading user from flat format without sites. 2020-01-23 15:49:25 -05:00
Maarten Billemont
39eb0027b5 More flexible API for providing marshalling master key. 2020-01-23 15:48:51 -05:00
Maarten Billemont
44a9ee0f15 Well-defined initial identicon color. 2020-01-23 15:47:44 -05:00
Maarten Billemont
7f7910f0a9 Abbreviated type names. 2020-01-23 15:45:22 -05:00
Maarten Billemont
98d0d7a497 Present site details. 2020-01-23 15:42:54 -05:00
Maarten Billemont
14387a2310 Ensure updates happen on main thread. 2020-01-16 16:20:16 -05:00
Maarten Billemont
dacdf25fe8 More swizzling fixes. 2020-01-14 18:15:04 -05:00
Maarten Billemont
ec6625b800 Fix internal bugs.
Pass masterKey data safely by ensuring the NSData holder is owned.

nameOfType: threw an unrecougnized-type error always, including for
recougnized types.

Swizzling broke when triggered on multiple levels of the hierarchy.
2020-01-14 15:27:57 -05:00
Maarten Billemont
91b89aaf39 Pearl & API update. 2020-01-14 13:59:32 -05:00
Maarten Billemont
22796663dc Fix missing arguments. 2019-01-05 13:54:27 -05:00
Maarten Billemont
dbda330543 Portability fixes for Blowfish's code. 2019-01-05 13:48:29 -05:00
Maarten Billemont
3927d4e8b7 Remove Apple JDK 1.6 support. 2018-11-20 11:58:52 -05:00
Maarten Billemont
ef2494118b Minor cleanup of askpass code. 2018-11-20 11:58:38 -05:00
Maarten Billemont
554c0129a2 Pearl API update. 2018-10-15 18:09:46 -04:00
Maarten Billemont
78956beb08 2.7-java-10 2018-10-15 17:54:23 -04:00
Maarten Billemont
39dacc8e5a User config in _ext_mpw, global config.json & residence config.
Moved user preferences (default type & hide passwords) into _ext_mpw.
Fixed an issue with JSON serialization of any values.
Made update check & background residency globally configurable
preferences saved in config.json.
2018-10-15 02:21:30 -04:00
Maarten Billemont
34042e5462 Extract user preferences into a separate class. 2018-10-13 18:07:24 -04:00
Maarten Billemont
0a386d6fad Require JDK 10 or 9, gradle fails for JDK 11 currently. 2018-10-13 17:14:20 -04:00
Maarten Billemont
ff17a1d637 Fix file import & sane format migration. 2018-10-13 16:58:04 -04:00
Maarten Billemont
46fe919476 Centralize query searching. 2018-10-13 16:54:49 -04:00
Maarten Billemont
8f35ac5f64 Improved libxml2 cflags/ldflags. 2018-09-26 00:43:23 -04:00
Maarten Billemont
06100510c3 2.7-java-9 2018-09-26 00:03:36 -04:00
Maarten Billemont
1bf6109038 Hide passwords option & fix settings for new sites. 2018-09-26 00:00:05 -04:00
Maarten Billemont
f2fa2a25b2 Pass font resolution through the GraphicsEnvironment. 2018-09-25 20:55:04 -04:00
Maarten Billemont
2a0cfd3a32 Documentation and interface tweaks. 2018-09-22 19:43:13 -04:00
Maarten Billemont
3070967d34 Convenience flag for building a debuggable binary. 2018-09-22 14:22:17 -04:00
Maarten Billemont
e4837a284a Fall back to getline if ncurses cannot be initialized (eg. TERM not set). 2018-09-22 14:14:18 -04:00
Maarten Billemont
06ebe954f1 Clarify the interface a bit. 2018-09-22 14:13:00 -04:00
Maarten Billemont
48d4668575 Ensure all read utilities yield constant string pointers for safety. 2018-09-22 14:12:53 -04:00
Maarten Billemont
af768329a3 2.7-java-8 2018-09-13 22:38:45 -04:00
Maarten Billemont
9a04c28054 Fix initialization of text consumers & action handlers on sites list. 2018-09-13 22:37:28 -04:00
Maarten Billemont
ec9c55ec4d Access login names by holding shift. 2018-09-13 15:49:58 -04:00
Maarten Billemont
d8a735e1b1 Improve render speed of lists. 2018-09-13 15:49:42 -04:00
Maarten Billemont
a1eee88a54 Improved search query support. 2018-09-12 13:12:10 -04:00
Maarten Billemont
ac5286853a Write release JARs to site directory. 2018-08-28 02:43:24 -04:00
Maarten Billemont
39f6893742 2.7-java-7 2018-08-27 18:24:46 -04:00
Maarten Billemont
7bf7b8981c Fix duplication of user names in files list. 2018-08-27 18:24:00 -04:00
Maarten Billemont
09abe21fed Release masterpassword-gui-2.7.6 2018-08-27 13:22:03 -04:00
Maarten Billemont
6fae0fe425 2.7-java-6 2018-08-27 13:11:32 -04:00
Maarten Billemont
0558176847 Fix dependencies in native mpw DLLs. 2018-08-27 13:07:38 -04:00
Maarten Billemont
c553201cda Small site update. 2018-08-26 20:41:27 -04:00
Maarten Billemont
665be9494b Release masterpassword-gui-2.7.5 2018-08-26 18:19:01 -04:00
Maarten Billemont
5ca81b4aa7 Don't use daemon when setting release passwords in environment. 2018-08-25 11:57:36 -04:00
Maarten Billemont
3cbb063926 Refactor Native to try and load other architectures. 2018-08-24 16:33:14 -04:00
Maarten Billemont
d5551c8c8c Key calculator and access to the full algorithm. 2018-08-24 13:48:53 -04:00
Maarten Billemont
9a40e52d53 2.7-java-5 2018-08-19 16:13:39 -04:00
Maarten Billemont
6f0d768e69 Saving custom passwords and logins. 2018-08-19 16:11:43 -04:00
Maarten Billemont
40fdc8d248 Fix initialization dependency cycle & load files on init. 2018-08-18 13:43:41 -04:00
Maarten Billemont
6b9e1b8cb8 Standard label font & fix warnings. 2018-08-14 12:06:04 -04:00
Maarten Billemont
f41cdb8742 Site security questions and copy login name. 2018-08-13 17:53:31 -04:00
Maarten Billemont
10c6d203b8 Implement security answers & immediate site lookup. 2018-08-07 00:07:16 -04:00
Maarten Billemont
7d1aa9c9f4 Release 2.7.4 2018-08-02 12:34:24 -04:00
Maarten Billemont
c26281e3b7 2.7-java-4 2018-08-02 12:20:45 -04:00
Maarten Billemont
f0b1f0c9e0 Build for older glibc. 2018-08-02 12:19:49 -04:00
Maarten Billemont
9682efc7c9 Release masterpassword-gui-2.7.3 2018-08-02 01:44:50 -04:00
Maarten Billemont
1264cad377 2.7-java-3 2018-08-02 01:37:37 -04:00
Maarten Billemont
d185a0af14 Add mpw native binary for windows 32-bit. 2018-08-02 01:37:10 -04:00
Maarten Billemont
4275a6cc61 Fix build on Windows. 2018-08-02 01:32:55 -04:00
Maarten Billemont
c94ff429e8 Switch linux build of libmpw to debian for glibc instead of musl libc. 2018-08-01 20:13:42 -04:00
Maarten Billemont
00744cb264 Statically link the mpw library. 2018-08-01 14:20:47 -04:00
Maarten Billemont
7202fe6d1d Bump site for release of masterpassword-gui-2.7.2.jar 2018-07-31 15:35:57 -04:00
Maarten Billemont
63b4d9cd2e 2.7-java-2 2018-07-31 15:32:13 -04:00
Maarten Billemont
36a7c7f423 Clean up iconifying on copy. 2018-07-31 15:31:47 -04:00
Maarten Billemont
c2c4fb18bf Help improvements. 2018-07-31 15:16:33 -04:00
Maarten Billemont
3fc8acba70 Global hotkey, iconifying and application activation, help text. 2018-07-31 14:55:19 -04:00
Maarten Billemont
f5c0c4d787 Fix offsetting local time back to UTC. 2018-07-31 12:44:49 -04:00
Maarten Billemont
86775f1c75 Standardize epoch time calculation. 2018-07-31 09:27:41 -04:00
Maarten Billemont
2bb190f49a Bump site for masterpassword-gui-2.7.1 release. 2018-07-29 15:38:54 -04:00
Maarten Billemont
77c4a2af46 2.7-java-1 2018-07-29 15:29:19 -04:00
Maarten Billemont
3da82d30b1 Add support for creating incognito users. 2018-07-29 15:26:48 -04:00
Maarten Billemont
97532fdce6 Print UI dates in current time zone. 2018-07-29 15:18:54 -04:00
Maarten Billemont
fe63a2756a Fix default type for new sites & site UI updating. 2018-07-29 15:10:45 -04:00
Maarten Billemont
928b617ed0 Import & export users + improved user state tracking. 2018-07-29 14:01:07 -04:00
Maarten Billemont
18ecc41b39 Fix issues with UnsignedIntegerModel in a spinner. 2018-07-29 01:38:58 -04:00
Maarten Billemont
a6e9e89ace Update UI instead of resetting when site is updated. 2018-07-29 01:38:15 -04:00
Maarten Billemont
0b7494ecbf We use JDK9 APIs now. 2018-07-29 00:19:05 -04:00
Maarten Billemont
8377c9c615 JDK9 platform-independent way of managing application events. 2018-07-29 00:08:09 -04:00
Maarten Billemont
37a7cfa530 Support resetting user's master password. 2018-07-28 21:53:08 -04:00
Maarten Billemont
978b758079 New user fixes. 2018-07-28 19:56:20 -04:00
Maarten Billemont
38f09021b3 Button tooltips and improvements. 2018-07-28 18:11:36 -04:00
Maarten Billemont
7455fba55e Adding and deleting users and sites. 2018-07-28 17:52:43 -04:00
Maarten Billemont
8cd9755616 Update GradientPanel's opaque properly. 2018-07-28 14:30:36 -04:00
Maarten Billemont
46d301df94 Site settings & add sites. 2018-07-28 14:03:49 -04:00
Maarten Billemont
e639137304 Avatar configuration & move preferences into user panel. 2018-07-26 15:07:37 -04:00
Maarten Billemont
7c83a62f91 Support for building with JDK 10. 2018-07-26 15:07:17 -04:00
Maarten Billemont
513840e2c4 Read in site questions from json & don't serialize incomplete MPFileUser 2018-07-23 23:59:11 -04:00
Maarten Billemont
8f7faa9e4e User preferences. 2018-07-23 23:34:32 -04:00
Maarten Billemont
16cdcda94b Identicon support and UI improvements. 2018-07-23 11:23:26 -04:00
Maarten Billemont
400ebe59db Implement sites list and copy result. 2018-07-19 13:56:26 -04:00
Maarten Billemont
476a4046e7 Use standard control highlight color for password. 2018-07-18 17:02:51 -04:00
Maarten Billemont
3403449ca2 Cleanup and fix some warnings. 2018-07-18 15:46:06 -04:00
Maarten Billemont
596ace51ea WIP - new Java UI. 2018-07-18 12:27:19 -04:00
Maarten Billemont
80b5fcd785 Refactor model, improved isolation & access unauthenticated file metadata. 2018-07-18 12:23:53 -04:00
Maarten Billemont
a16bc9a318 Don't save changes made to model while it's being read from file. 2018-07-10 00:54:11 -04:00
Maarten Billemont
462dd4e89b Prepare for 2.7 release. 2018-07-10 00:39:52 -04:00
Maarten Billemont
e5ff374a9c Better way of checking implementation-version. 2018-07-10 00:39:35 -04:00
Maarten Billemont
954c4f8d63 Java improvements.
UI threading improvements.
Save user/site changes to file.
Ordering of user / site fixes.
Add questions to JSON output.
Bring JSON output format in line with C.
2018-07-09 01:13:25 -04:00
Maarten Billemont
529f1feace Omit empty _mpw_ext & unneeded escaping. 2018-07-09 01:12:28 -04:00
Maarten Billemont
5cdff6f155 Some fixes to search paths. 2018-07-08 20:50:32 -04:00
Maarten Billemont
81358c16f9 Omit questions if none present and fix parsing sites with no questions. 2018-07-08 20:49:42 -04:00
Maarten Billemont
4a555748cd UTC times were parsed in as local time by mktime. 2018-07-08 20:49:31 -04:00
Maarten Billemont
698566a914 Update stale GitHub references with new GitLab home. 2018-07-05 17:14:50 -04:00
Maarten Billemont
64a69856ac Fix libsodium include paths. 2018-07-04 14:14:49 -04:00
Maarten Billemont
f76de9520c Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-07-04 12:17:04 -04:00
Maarten Billemont
4b3662bbe9 Support for x86 Linux. 2018-07-04 12:16:37 -04:00
Maarten Billemont
b25130f4d2 Tweak include paths & add clean support to android libs. 2018-07-03 11:21:48 -04:00
Maarten Billemont
d6617563fc Clean libs when cleaning build. 2018-07-01 21:20:56 -04:00
Maarten Billemont
8d24ec3250 Merge branch 'patch-1' into 'master'
Update README.md Fix broken link

See merge request MasterPassword/MasterPassword!249
2018-07-01 20:31:14 +00:00
Jakob Kukla
bbcc250a5c Update README.md
Fix broken link to native CLI instructions
2018-07-01 18:04:19 +00:00
Maarten Billemont
4abb50ad9b Fix lipo regression in build script. 2018-06-30 23:54:35 -04:00
Maarten Billemont
30dac64d5d Support for building on Linux. 2018-06-30 23:35:29 -04:00
Maarten Billemont
e4e2aaad95 Ensure GitLab CI does clean builds. 2018-06-30 20:53:29 -04:00
Maarten Billemont
835acf45eb Improve check for platform-specific GUI support & url access for CF needs user-agent. 2018-06-30 15:24:58 -04:00
Maarten Billemont
c3f6796833 Pre-built library files for different platforms. 2018-06-30 12:35:40 -04:00
Maarten Billemont
86f4e8ec06 Fix gradle build. 2018-06-30 11:57:36 -04:00
Maarten Billemont
0dddcef28e libsodium and masterpassword-core must be linked statically + windows build configuration fixes. 2018-06-27 02:54:31 -04:00
Maarten Billemont
cc583c789d Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-06-25 13:22:36 -04:00
Maarten Billemont
42d78da74e Fix some bugs in the new mpw_strings & mpw_strncasecmp. 2018-06-25 13:19:26 -04:00
Maarten Billemont
b5040a7786 Harmonize standardized platform naming between Native class and gradle. 2018-06-25 12:25:38 -04:00
Maarten Billemont
b1698ee339 C type fixes. 2018-06-25 12:25:17 -04:00
Maarten Billemont
8ffc0ae350 Execute gradle scripts with bash. 2018-06-25 10:34:56 -04:00
Maarten Billemont
8276d2f4e5 Build dependencies with a task & all windows archs. 2018-06-25 02:02:51 -04:00
Maarten Billemont
11cf86bc73 Use brew version of libtoolize. 2018-06-24 16:48:22 -04:00
Maarten Billemont
5084511404 Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-06-24 16:31:22 -04:00
Maarten Billemont
fffec56d4e Build support for Microsoft Windows. 2018-06-24 16:20:42 -04:00
Maarten Billemont
9a0828c1eb Bump site for Android release. 2018-06-21 10:31:10 -04:00
Maarten Billemont
db967a1a16 Prepare 2.7-android-1 2018-06-19 17:15:42 -04:00
Maarten Billemont
f83896d89d Ignore build files. 2018-06-19 01:59:28 -04:00
Maarten Billemont
683c0165e6 Reset Android counter on long-touch. 2018-06-19 01:54:20 -04:00
Maarten Billemont
3853b6f180 Move Homebrew dependencies to a Brewfile. 2018-06-19 01:52:53 -04:00
Maarten Billemont
bff3577ada Also need automake installed. 2018-06-19 01:42:59 -04:00
Maarten Billemont
4c48bfb1af Rename native lib to libmpw & native library loading for eg. Android. 2018-06-19 01:32:07 -04:00
Maarten Billemont
a8263b276c Fix handling of nullable parameters in native code. 2018-06-19 01:31:52 -04:00
Maarten Billemont
11e32abb90 Install autoconf in GitLab CI. 2018-06-17 07:57:50 -04:00
Maarten Billemont
b6e6dce9f0 Added some missing build files. 2018-06-17 01:48:36 -04:00
Maarten Billemont
ada1c7f6ae Fix json-c remote back to upstream. 2018-06-17 01:24:41 -04:00
Maarten Billemont
1cbb584011 Android support for new native masterpassword-core. 2018-06-17 01:11:16 -04:00
Maarten Billemont
f9289a3e9e Standardize TRUE to true. 2018-06-17 00:36:31 -04:00
Maarten Billemont
9a37253461 Make dependencies private since they're only used by the implementation. 2018-06-16 19:47:53 -04:00
Maarten Billemont
5a4456bf46 Ensure all source files are treated as UTF-8. 2018-06-11 00:09:59 -04:00
Maarten Billemont
1d06dd65ed Ensure maven.lyndir.com is accessed securely. 2018-06-10 19:11:22 -04:00
Maarten Billemont
ed6c32811c !232 Fix value reset on logout. 2018-06-10 15:31:20 -04:00
Maarten Billemont
9a564ff35e JDK 8 support for Android platform. 2018-06-10 15:22:37 -04:00
Maarten Billemont
4909479b0f Merge branch 'master' of gitlab.com:MasterPassword/MasterPassword 2018-06-10 13:26:26 -04:00
Maarten Billemont
434a7cc280 Docker availability. 2018-06-06 01:49:45 +00:00
Maarten Billemont
50a48ae092 Migrate docker from ubuntu to alpine. 2018-06-05 21:25:38 -04:00
Maarten Billemont
c3017069b1 Remove __unused since it's non-standard.
1. It breaks compilers without support for __attribute__
2. It breaks system headers that declare variables named __unused (eg. musl libc).
2018-06-05 21:22:40 -04:00
Maarten Billemont
c7425be681 Misc Xcode files. 2018-06-05 20:26:44 -04:00
Maarten Billemont
249a1975cd Update path for mpw-jni.h. 2018-06-05 20:22:46 -04:00
Maarten Billemont
190a241a25 Java relocation. 2018-06-05 20:19:10 -04:00
Maarten Billemont
aef8422102 Update dependent paths to new core source location. 2018-06-05 20:09:13 -04:00
Maarten Billemont
c2aafd8602 Reorganize core source and add Docker support to CLI. 2018-06-05 20:04:43 -04:00
Maarten Billemont
8e41cba7ac Badges moved to the project's settings. 2018-06-05 20:48:21 +00:00
Maarten Billemont
9d29775b14 Remove some obsolete variables. 2018-06-05 16:17:19 -04:00
Maarten Billemont
55bd9382bc Fix a syntax error in genassets. 2018-06-05 15:36:27 -04:00
Maarten Billemont
687923da32 Don't fail stopProgress if there is no active logSpinner. 2018-06-05 15:24:45 -04:00
Maarten Billemont
66e893fd83 Drill down on what genassets command is failing. 2018-06-05 15:09:11 -04:00
Maarten Billemont
cc5c45e3aa Trace subshell errors. 2018-06-05 14:44:54 -04:00
Maarten Billemont
d472d975ce Small bashlib tweaks. 2018-06-05 14:19:12 -04:00
Maarten Billemont
7a97a0b0c8 Better exit status preservation on error. 2018-06-05 14:12:10 -04:00
Maarten Billemont
02aed778bc Output errors on stderr. 2018-06-05 14:06:55 -04:00
Maarten Billemont
b748e607ad Do we need SHLVL=0? 2018-06-05 13:32:08 -04:00
Maarten Billemont
c801ff546a Initialize TERM if not set. 2018-06-05 13:18:54 -04:00
Maarten Billemont
17185391ce Debug genassets failure. 2018-06-05 13:05:07 -04:00
Maarten Billemont
4579095afc Tweak GitLab CI requisites. 2018-06-05 12:58:00 -04:00
Maarten Billemont
788d85178d Scripts update to visualize errors better. 2018-06-05 12:49:03 -04:00
Maarten Billemont
af4d7c4bc9 Remove xcpretty from GitLab CI builds, has issues and doesn't handle external build output. 2018-06-05 12:23:27 -04:00
Maarten Billemont
a4bbfdf850 Retain xcodebuild's exit status in GitLab CI builds. 2018-06-05 12:11:23 -04:00
Maarten Billemont
cef3d470bd Configure GitLab CI to check out submodules. 2018-06-05 11:41:23 -04:00
Maarten Billemont
0d37c45dbe Remove rbenv-bundler from GitLab Pages config. 2018-06-05 11:37:31 -04:00
Maarten Billemont
e568b5a9da Fix GitLab Pages stage. 2018-06-05 11:33:29 -04:00
Maarten Billemont
e7ac8661f9 Begin setting up GitLab CI. 2018-06-05 11:30:19 -04:00
Maarten Billemont
882de547d0 Fully replace Java mpw algorithm implementation with proxy to standard C implementation. 2018-06-04 01:43:46 -04:00
Maarten Billemont
6957d46ef9 Fix Java algorithm type inconsistency. 2018-06-04 01:43:02 -04:00
Maarten Billemont
3a9a518cb1 Fixed bad AES PKCS7 block rounding. 2018-06-03 23:47:16 -04:00
Maarten Billemont
0900aff93a Fix disappeared link library. 2018-06-03 20:43:12 -04:00
Maarten Billemont
3974b70a83 Fix include paths. 2018-06-03 20:42:09 -04:00
Maarten Billemont
498b7caecb C core code moved into src/ 2018-06-03 19:46:35 -04:00
Maarten Billemont
0b26260124 Build platform libsodium dependency for Travis. 2018-06-03 18:45:30 -04:00
Maarten Billemont
bc0ffbd552 Fix JNI write-back, bad V3 api usage, duplicate length passing. 2018-06-03 17:58:24 -04:00
Maarten Billemont
c08d3a0e8b Fix format type. 2018-06-03 17:09:33 -04:00
Maarten Billemont
5501f1f97d Allow filtering Java test cases. 2018-06-03 16:50:19 -04:00
Maarten Billemont
073ef4f439 Update to masterpassword.app domain. 2018-06-03 16:26:08 -04:00
Maarten Billemont
a7f82d3148 Allow verbose and scoped tests output. 2018-06-03 16:19:14 -04:00
Maarten Billemont
831b475b28 Remove deprecated build configuration and libscrypt references. 2018-06-03 15:08:02 -04:00
Maarten Billemont
728a4486d3 Initial integration of JNI with C implementation. 2018-05-28 23:00:05 -04:00
Maarten Billemont
5035c52846 Fix library resolution. 2018-05-28 22:15:09 -04:00
Maarten Billemont
a0447298d3 Fix inter-project dependency and including libs into algorithm jar. 2018-05-27 23:43:35 -04:00
Maarten Billemont
0b044ab9a4 Archive and initial cross-compile support. 2018-05-26 01:22:41 -04:00
Maarten Billemont
3b24e1d1b8 Move native implementation build into masterpassword-core 2018-05-25 13:08:05 -04:00
Maarten Billemont
cc82e52c33 Initial native interface for scrypt. 2018-05-22 01:00:14 -04:00
Maarten Billemont
faf59875bf Support for building libsodium for Android. 2018-05-21 17:39:06 -04:00
Maarten Billemont
e12e14ef03 Move library builds into /lib so they can be shared.
Also made the library build script more generic.
2018-05-20 15:04:32 -04:00
Maarten Billemont
f41f07f0ae Replace lambdaworks:scrypt with libsodium-jni to match C implementation, remove dependency on opal-crypto and its providers. 2018-05-19 19:58:37 -04:00
Maarten Billemont
1cfc199541 Reorder some methods. 2018-05-19 12:47:27 -04:00
Maarten Billemont
c43cc73ad5 Sign masterpassword-gui under Gradle. 2018-05-19 11:45:57 -04:00
Maarten Billemont
1bd61759bf Wipe masterPassword on authentication & misc improvements. 2018-05-19 11:45:18 -04:00
Maarten Billemont
cbf277c493 Bump opal to stable. 2018-05-17 13:03:28 -04:00
Maarten Billemont
87b7afd587 Reformat. 2018-05-16 19:22:19 -04:00
Maarten Billemont
2db0bb35d5 Resolve warnings and inspections. 2018-05-16 12:30:28 -04:00
Maarten Billemont
c51262ccc2 Complete security questions model. 2018-05-16 12:00:42 -04:00
Maarten Billemont
1b703515dd Refactor code. 2018-05-16 00:16:06 -04:00
Maarten Billemont
0aa7baf59e Refactoring masterpassword-algorithm. 2018-05-16 00:12:31 -04:00
Maarten Billemont
8bdf1755b7 Migrating to JDK 1.8 2018-05-15 17:12:42 -04:00
Maarten Billemont
bda1ac3bd4 Refactor masterpassword-model 2018-05-15 13:53:27 -04:00
Maarten Billemont
8d7c351912 Initial implementation of questions support. 2018-05-14 12:24:15 -04:00
Maarten Billemont
38a357cb28 Transition to Jackson so we can retain unrecognized properties in the source JSON. 2018-05-14 11:27:49 -04:00
Maarten Billemont
f0d523fb35 Initial Java JSON serialization/deserialization. 2018-05-08 22:40:48 -04:00
Maarten Billemont
1cb720da32 Remove ssl prefix from masterpasswordapp.com URL. 2018-05-03 13:51:00 +02:00
Maarten Billemont
1031414ba2 WIP - JSON mpsites serialization. 2018-05-03 13:49:34 +02:00
Maarten Billemont
cb74b1f3fc Fix warnings and inspections. 2018-04-27 11:36:01 -04:00
Maarten Billemont
82e2d0b5ac Replace Version API with MPAlgorithm, make configuration instance-specific. 2018-04-26 15:56:12 -04:00
Maarten Billemont
33f2e0edda Optimize imports. 2018-04-26 13:26:16 -04:00
Maarten Billemont
9c8566b537 Reformat to code style. 2018-04-26 13:05:45 -04:00
Maarten Billemont
10698284d2 Format-specific marshalling. 2018-04-26 12:49:44 -04:00
Maarten Billemont
11185725d1 Make default counter algorithm-scoped, format-specific unmarshalling. 2018-04-26 12:45:29 -04:00
Maarten Billemont
71f1b3c130 Better exception logging to avoid hiding class initialization exceptions. 2018-04-26 12:45:02 -04:00
Maarten Billemont
dc19806e02 Improve rendering of user name. 2018-04-25 22:50:12 -04:00
Maarten Billemont
94ac8b1460 Fixed upstream revision check. 2018-04-25 22:02:41 -04:00
Maarten Billemont
40a807c6af Better scope preferences to the algorithm & clean up. 2018-04-25 21:50:17 -04:00
Maarten Billemont
c115e9149c Merge branch 'master' of github.com:Lyndir/MasterPassword 2018-04-25 17:51:48 -04:00
Maarten Billemont
7123e97ef9 Update site URLs. 2018-04-25 17:51:17 -04:00
Maarten Billemont
37fb672133 Publish masterpassword-android-2.5.1.apk 2018-04-24 16:52:27 -04:00
Maarten Billemont
5fe9106f6d
Merge pull request #191 from MidnightWonderer/bug/protocol-relative-css
fix protocol relative css import
2018-04-24 09:36:45 -04:00
Sarun Rattanasiri
4f708809e5 fix protocol relative css import 2017-08-14 22:50:07 +07:00
530 changed files with 18641 additions and 15385 deletions

28
.dockerignore Normal file
View File

@ -0,0 +1,28 @@
# OS-Specific junk.
.DS_Store
Thumbs.db
# IntelliJ
.idea
*.iml
*.ipr
*.iws
# Xcode IDE
xcuserdata/
DerivedData/
# Generated
/platform-darwin/Resources/Media/Images.xcassets/
/platform-darwin/Podfile.lock
/platform-darwin/Pods/
# Gradle
build
.gradle
local.properties
/builds
/platform-android/.externalNativeBuild
# Git
.git

16
.gitignore vendored
View File

@ -1,12 +1,14 @@
# OS-Specific junk.
.DS_Store
Thumbs.db
*~
# IntelliJ
.idea
*.iml
*.ipr
*.iws
out
# Xcode IDE
xcuserdata/
@ -14,18 +16,14 @@ DerivedData/
# Generated
/platform-darwin/Resources/Media/Images.xcassets/
# Media
public/Press/Background.png
public/Press/Front-Page.png
public/Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
/platform-darwin/Podfile.lock
/platform-darwin/Pods/
# Gradle
build
!/build
.gradle
local.properties
/builds
/platform-android/.externalNativeBuild
.cxx
# Maven
target
dependency-reduced-pom.xml

20
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,20 @@
variables:
GIT_DEPTH: 3
GIT_SUBMODULE_STRATEGY: recursive
build_project:
stage: build
script:
- "( brew bundle )"
- "( ./lib/bin/build_libsodium-macos clean && ./lib/bin/build_libsodium-macos )"
- "( ./lib/bin/build_libjson-c-macos clean && ./lib/bin/build_libjson-c-macos )"
- "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )"
- "( ./gradlew --stacktrace --info clean test )"
- "( cd platform-darwin && pod install )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Release' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Release' -scheme 'MasterPassword macOS' clean build )"
tags:
- brew
- java
- cocoapods
- xcode

27
.gitmodules vendored
View File

@ -1,33 +1,20 @@
[submodule "External/Pearl"]
path = platform-darwin/External/Pearl
url = git://github.com/Lyndir/Pearl.git
[submodule "External/InAppSettingsKit"]
path = platform-darwin/External/InAppSettingsKit
url = git://github.com/lhunath/InAppSettingsKit.git
[submodule "External/KCOrderedAccessorFix"]
path = platform-darwin/External/KCOrderedAccessorFix
url = https://github.com/lhunath/KCOrderedAccessorFix.git
[submodule "External/AttributedMarkdown"]
path = platform-darwin/External/AttributedMarkdown
url = https://github.com/dreamwieber/AttributedMarkdown.git
[submodule "External/uicolor-utilities"]
path = platform-darwin/External/uicolor-utilities
url = git://github.com/lhunath/uicolor-utilities.git
[submodule "External/jrswizzle"]
path = platform-darwin/External/jrswizzle
url = git://github.com/jonmarimba/jrswizzle.git
[submodule "MasterPassword/Web/js/mpw-js"]
path = platform-independent/web-js/js/mpw-js
path = platform-independent/web/js/mpw-js
url = https://github.com/tmthrgd/mpw-js.git
[submodule "platform-darwin/External/libsodium"]
path = platform-darwin/External/libsodium
[submodule "lib/libsodium"]
path = lib/libsodium
url = https://github.com/jedisct1/libsodium.git
[submodule "platform-darwin/External/libjson-c"]
path = platform-darwin/External/libjson-c
url = https://github.com/lhunath/json-c.git
[submodule "lib/libjson-c"]
path = lib/libjson-c
url = https://github.com/json-c/json-c.git
[submodule "public/site"]
path = public/site
url = https://github.com/Lyndir/MasterPassword.git
url = https://gitlab.com/MasterPassword/MasterPassword.git
branch = gh-pages
shallow = true
update = none

View File

@ -1,20 +0,0 @@
language: objective-c
os: osx
osx_image: xcode9.2
env: TERM=dumb SHLVL=0
git:
submodules: true
script:
- "( brew install libsodium json-c )"
- "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests && ./mpw-cli-tests )"
- "( cd ./gradle && ./gradlew --info clean test )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )"
notifications:
webhooks:
urls:
- "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGxodW5hdGglM0FseW5kaXIuY29tLyUyMWR2S1JpaW1uc0Z3dWdseEpHSyUzQWx5bmRpci5jb20"
on_success: change # always|never|change
on_failure: always
on_start: never

6
Brewfile Normal file
View File

@ -0,0 +1,6 @@
brew "libsodium"
brew "json-c"
brew "libtool"
brew "automake"
brew "autoconf"

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
# Set up a container for doing gradle cross-compiling.
#
# docker build -t lhunath/mp-gradle --file Dockerfile /var/empty
FROM debian:stable-slim
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
RUN mkdir -p /usr/share/man/man1
RUN apt-get update && apt-get install -y default-jdk-headless git-core bash libtool automake autoconf make g++-multilib
RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw
RUN cd /mpw && ./gradlew -i clean
RUN cd /mpw && git pull && git log -1 && ./gradlew -i check

200
README.md
View File

@ -1,207 +1,41 @@
[![Travis CI](http://img.shields.io/travis-ci/Lyndir/MasterPassword.png)](https://travis-ci.org/Lyndir/MasterPassword)
[![Join the chat at https://gitter.im/lyndir/MasterPassword](https://badges.gitter.im/lyndir/MasterPassword.svg)](https://gitter.im/lyndir/MasterPassword?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![license](https://img.shields.io/github/license/lyndir/masterpassword.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
# [Master Password •••|](http://masterpasswordapp.com)
# [Master Password •••|](http://masterpassword.app)
Master Password is a completely new way of thinking about passwords.
It consists of an algorithm that implements the core idea and applications for various platforms making the alogirthm available to users on a variety of devices and platforms.
To skip the intro and go straight to the information on how to use the code, [click here](#source-code).
Master Password is available for [📲 iOS](https://itunes.apple.com/app/id510296984), [🖥 macOS](https://ssl.masterpasswordapp.com/masterpassword-mac.zip), [📲 Android](https://ssl.masterpasswordapp.com/masterpassword-android.apk), [🖥 Desktop](https://ssl.masterpasswordapp.com/masterpassword-gui.jar), and [⌨ Console](https://ssl.masterpasswordapp.com/masterpassword-cli.tar.gz).
## PROJECT MOVED
Master Password is also available from the following package managers: [macOS: Homebrew](https://brew.sh/) (`brew install mpw`).
Get in touch if you are interested in adding Master Password to any other package managers.
Master Password is announcing a massive rewrite, modernizing the solution and clearing the way for exciting new capabilities.
There are many reasons for using Master Password instead of an ordinary password manager, read below for the details, but if you want my personal favourites, they would be:
The project is re-launching as [Spectre](https://gitlab.com/spectre.app), still fully open-source Free Software here on GitLab.
- I don't need to worry about keeping backups of my countless authentication credentials.
- I don't need to worry that when I travel, I might not have access to my passwords vault.
- I don't need to trust an external party, proprietary code or a service to be online and stay online.
- If I feel at risk of my device being stolen or confiscated, I can set a fake master password, delete my user or wipe it worry-free.
Any interested parties are invited to participate in [the alpha or beta programs](https://spectre.app/#beta), to participate in the new Spectre identity platform.
We also have a [Frequently Asked Questions](#faq).
## What is a password?
Ah, the "password". Somehow, passwords have become the default solution to authentication across the web. We've long since accepted this as the way things are, but let's stop to think for a moment about what passwords actually are:
A password is a secret that is known only to the party providing a service and the party that should be allowed access to this service.
Simple enough - a secret that you know and your website knows but nobody else, thereby guaranteeing that you and only you have access to your account on this website. Unfortunately, in practice, the ubiquitous use of passwords has us completely overwhelmed. And the only way we can cope with that is by finding ways of making the problem manageable.
## What's the problem?
Coming up with a secret password is pretty easy. Say you're organizing a secret meeting and will only let people in if they know the password at the door. You tell those you trust, the password for tonight's meeting is "purple oranges with a smile".
The problem we have in our daily lives, however, is the fact that we need secret passwords for almost everything now. A password for our email, twitter, 9gag, facebook, imgur, amazon, ebay, paypal, bank, reddit, etc. And every time we want to use a new site, we need another one. The problem now becomes clear: passwords are meant to be remembered and recalled with ease when needed, but this becomes impossible when we have secrets for every distinct activity in our lives.
We cannot recall passwords the way we are expected to when there are too many.
## Coping
Life gives us no advice on how to deal with this problem. So we find our own ways:
- We use a single personal secret for all our websites, thereby violating the secrecy of these passwords (eg. you've given your email secret to twitter).
- We use simple variations of a personal secret or pattern, thereby trivializing the complexity of these passwords (eg. google98, twitter98; reversals, eg. 8991elgoog)
- We use external means of remembering passwords, thereby increasing the risk of loss (both loss of access when we lose the tool and theft when a thief finds our tool)
These coping mechanisms come in various forms, and they all have down-sides, because at the root of each of these lies an undeniable truth:
Our passwords are no longer true to the original definition.
## Master Password's approach
The theory behind Master Password starts with accepting that it is impossible to keep track of passwords for all your accounts. Instead, we return to the core premise of the password: a secret phrase that you can remember easily, all by yourself.
Master Password solves this problem by letting you remember one and only one password. You use this password with Master Password only. Master Password then gives you access to any website or service you want by creating a website-specific key for it.
1. You sign into Master Password using your one password.
2. You ask Master Password for the key to enter your website, eg. twitter.
3. You log into twitter using your username and the key from Master Password.
Master Password is *not* a password manager. It does not store your website passwords. Therefore, there is zero risk of you losing your website passwords (or them falling in the wrong hands). Master Password simply uses your one password and the name of the site to generate a site-specific secret.
## Benefits
- You don't need to think up a new strong password every time you make a new account - Master Password gives you the key for it.
- You don't need to try remembering a password you created two years ago for that one account - Master Password just gives you the key for it.
- You don't need to worry about getting into that account you made at work after you come home because you don't have your office passwords with you - Master Password is availale everywhere, even offline.
- You don't need to try to keep password lists in sync or stored somewhere easily accessible - Master Password keys can be created anywhere.
- You don't need to worry what you'll do if your computer dies or you need to log into your bank while you're in the airport transit zone - your Master Password keys are always available, even when starting empty.
- You don't need to worry about your password manager website getting hacked, your phone getting duplicated, somebody taking a picture of your passwords book - Master Password stores no secrets.
## How does it work?
The details of how Master Password works [are available here](http://masterpasswordapp.com/algorithm.html).
In short:
master-key = SCRYPT( user-name, master-password )
site-key = HMAC-SHA-256( site-name . site-counter, master-key )
site-password = PW-TEMPLATE( site-key, site-template )
Master Password can derive the `site-password` in an entirely stateless manner. It is therefore better defined as a calculator than a manager. It is the user's responsibility to remember the inputs: `user-name`, `master-password`, `site-name`, `site-counter` and `site-template`.
We standardize `user-name` as your full name, `site-name` as the domain name of the site, `site-counter` to `1` (unless you explicitly increment it) and `site-template` to `Long Password`; as a result the only token the user really needs to remember actively is `master-password`.
The Beta program is now open for users with iOS devices. The Spectre Beta introduces a new app, rewritten under Swift, and new capabilities such as AutoFill.
All development effort has moved to the Spectre project. Master Password is no longer actively maintained.
## FAQ
1. If I lose my master password and need to set a new one, will I need to change all of my site passwords?
1. Has there been a change in ownership?
Yes. If your master password is compromised, it is only sensible for you to change all of your site passwords. Just like if you lose the keys in your pocket, you'll have to change all the locks they open. Master Password effectively enforces this security practice.
No. This project is still owned and maintained exclusively by [Maarten Billemont](https://gitlab.com/lhunath).
2. But what if I just forget my master password or I just want to change it to something else?
2. How can I trust Spectre?
Sorry, still yes. Your master password is the secret component to your Master Password identity. If it changes, your identity changes. I wholly encourage you to think very carefully about what makes for a really memorable and good master password before just diving in with something lazy. A short phrase works great, eg. `banana coloured duckling`.
Spectre's code-base is based on the Master Password code-base. The algorithm is exactly the same. The license is the same. The author is the same.
3. Doesn't this mean an attacker can reverse my master password from any of my site passwords?
The applications are being rewritten for modern platforms. Spectre has the exact same trust parameters as Master Password.
Technically, yes. Practically, no.
3. Why is the project changing?
You could argue that site passwords are "breadcrumbs" of your master password, but the same argument would suggest encrypted messages are breadcrumbs to the encryption key. Encryption works because it is computationally unfeasible to "guess" the encryption key that made the encrypted message, just like Master Password works because it is computationally unfeasible to "guess" your master password that made the site password.
Several reasons, in fact. Master Password as a name is too ubiquitous in popular culture, which is a cause for confusion. We are also looking to evolve the capabilities of the platform beyond simply passwords, into a fully decentralized identity platform. We're also looking to be socially inclusive and conscious of the implicit biases present in terminology we've inherited from past societies.
4. The second step is just a HMAC-SHA-256, doesn't that make the SCRYPT completely pointless?
All that said - Spectre is the mark of a complete refresh of the Master Password solution. Hope you'll love it!
No. They are used for different reasons and one is not weaker than the other.
4. How do I migrate?
HMAC-SHA-256 is much faster to compute than SCRYPT, which leads some people to think "all an attacker needs to do is brute-force the SHA and ignore the SCRYPT". The reality is that the HMAC-SHA-256 guards a 64-byte authentication key (the `master-key`) which makes the search space for brute-forcing the HMAC wildly too large to compute.
The `master-password` on the other hand, is only a simple phrase, which means its search space is much smaller. This is why it is guarded by a much tougher SCRYPT operation.
5. I have another question.
Please don't hesitate to [get in touch](#support), we're more than happy to answer all your Master Password questions. Any problems or suggestions can be reported [as GitHub issues](https://github.com/Lyndir/MasterPassword/issues).
# Source Code
Master Password's algorithm is [documented](http://masterpasswordapp.com/algorithm.html) and its implementation is Free Software (GPLv3).
## Components
There are several components available here. As an end-user, you can currently use the iOS app, the Android app, the OS X app, the Java desktop app, the C CLI app or the Java CLI app. There are also several components that are useful for developers:
- `core/c`: This is the reference implementation of the Master Password algorithm, written in C.
- `core/java/algorithm`: This is a Java implementation of the Master Password algorithm.
- `core/java/model`: This is an object model to simplify use of Master Password by Java applications.
- `core/java/tests`: These are Java integration tests designed to ensure Master Password performs as expected.
- `platform-android`: This is the official Android implementation of Master Password in Java.
- `platform-darwin`: This is the official iOS and OS X implementation of Master Password in Objective-C.
- `platform-independent/cli-c`: This is the platform-independent console implementation of Master Password, written in C.
- `platform-independent/cli-java`: This is the platform-independent console implementation of Master Password, written in Java.
- `platform-independent/gui-java`: This is the platform-independent desktop implementation of Master Password, written in Java.
- `platform-independent/web-js`: This is the platform-independent browser application for Master Password, written in JavaScript.
## Building and running
### macOS or iOS
Make sure you have all relevant submodules checked out.
Go into `platform-darwin` and open `MasterPassword.xcworkspace` in Xcode. Select the desired target from the Scheme Selector and build, run or archive.
### Web
Make sure you have all relevant submodules checked out.
Go into `platform-independent/web-js` and open `index.html` in your browser. You should be able to run this locally, there is no need for hosting or an application server.
### Java
Go into the `gradle` directory and run `./gradlew build`. All Java components will then be built:
- `platform-independent/gui-java/build/distributions`:
contains an archive with the Master Password Java GUI. Unpack it and run the `gui` script.
- `platform-independent/cli-java/build/distributions`:
contains an archive with the Master Password Java command-line interface. Unpack it and run the `cli` script.
- `platform-android/build/outputs/apk`:
contains the Android application package. Install it on your Android device.
Note that in order to build the Android application, you will need to have the Android SDK installed and either have the environment variable `ANDROID_HOME` set to its location or a `gradle/local.properties` file with its location, eg. (for Homebrew users who installed the SDK using `brew install android-sdk`):
sdk.dir=/usr/local/opt/android-sdk
### Native CLI
Go into the `platform-independent/cli-c` directory and run `./build`. The native command-line client will then be built.
For detailed instructions, see [the native CLI instructions](platform-independent/cli-c/README.md).
## Support
Feel free to contribute by forking the project, reporting issues or joining the discussion on:
- [Gitter](https://gitter.im/lyndir/MasterPassword)
- #masterpassword (on chat.freenode.net)
- #masterpassword:lyndir.com (on Matrix)
- masterpassword@lyndir.com
Master Password export files are fully supported by Spectre. Migration mechanism exist in Master Password which will trigger as soon as you install Spectre; for instance, as soon as you install Spectre on your iOS device, Master Password will show a pop-up which will copy your user over at a tap.

15
RELEASE.md Normal file
View File

@ -0,0 +1,15 @@
To build a release distribution:
Desktop:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle --no-daemon clean masterpassword-gui:shadowJar
Android:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle --no-daemon clean masterpassword-android:assembleRelease
Note:
- At the time of writing, Android does not build with JDK 9+. As such, the above command must be ran with JAVA_HOME pointing to JDK 7-8.
- The release keystores are not included in the repository. They are maintained by Maarten Billemont (lhunath@lyndir.com).

55
build.gradle Normal file
View File

@ -0,0 +1,55 @@
import com.github.spotbugs.SpotBugsTask
buildscript {
repositories {
google()
jcenter()
gradlePluginPortal()
}
dependencies {
classpath group: 'com.android.tools.build', name: 'gradle', version: '3.5.0'
classpath group: 'gradle.plugin.com.github.spotbugs', name: 'spotbugs-gradle-plugin', version: '2.0.0'
}
}
allprojects {
group = 'com.lyndir.masterpassword'
version = '2.7.12'
}
subprojects {
apply plugin: 'com.github.spotbugs'
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.lyndir.com' }
}
dependencies {
//spotbugsPlugins group: 'com.h3xstream.findsecbugs', name: 'findsecbugs-plugin', version: '1.11.0'
//spotbugsPlugins group: 'com.mebigfatguy.sb-contrib', name: 'sb-contrib', version: '7.4.6'
}
spotbugs {
effort 'max'
showProgress true
}
tasks.withType( JavaCompile ) {
options.encoding = 'UTF-8'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
options.compilerArgs << '-Xlint:unchecked'
if (it.name != JavaPlugin.COMPILE_JAVA_TASK_NAME) {
options.compilerArgs << '-Xlint:deprecation'
}
}
tasks.withType( SpotBugsTask ) {
reports {
xml.enabled = false
html.enabled = true
}
}
}

View File

@ -1,226 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include "mpw-algorithm.h"
#include "mpw-algorithm_v0.c"
#include "mpw-algorithm_v1.c"
#include "mpw-algorithm_v2.c"
#include "mpw-algorithm_v3.c"
MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
if (fullName && !strlen( fullName ))
fullName = NULL;
if (masterPassword && !strlen( masterPassword ))
masterPassword = NULL;
trc( "-- mpw_masterKey (algorithm: %u)", algorithmVersion );
trc( "fullName: %s", fullName );
trc( "masterPassword.id: %s", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL );
if (!fullName || !masterPassword)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_masterKey_v0( fullName, masterPassword );
case MPAlgorithmVersion1:
return mpw_masterKey_v1( fullName, masterPassword );
case MPAlgorithmVersion2:
return mpw_masterKey_v2( fullName, masterPassword );
case MPAlgorithmVersion3:
return mpw_masterKey_v3( fullName, masterPassword );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
MPSiteKey mpw_siteKey(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) {
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
trc( "-- mpw_siteKey (algorithm: %u)", algorithmVersion );
trc( "siteName: %s", siteName );
trc( "siteCounter: %d", siteCounter );
trc( "keyPurpose: %d (%s)", keyPurpose, mpw_nameForPurpose( keyPurpose ) );
trc( "keyContext: %s", keyContext );
if (!masterKey || !siteName)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion1:
return mpw_siteKey_v1( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion2:
return mpw_siteKey_v2( masterKey, siteName, siteCounter, keyPurpose, keyContext );
case MPAlgorithmVersion3:
return mpw_siteKey_v3( masterKey, siteName, siteCounter, keyPurpose, keyContext );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
const char *mpw_siteResult(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion) {
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey)
return NULL;
trc( "-- mpw_siteResult (algorithm: %u)", algorithmVersion );
trc( "resultType: %d (%s)", resultType, mpw_nameForType( resultType ) );
trc( "resultParam: %s", resultParam );
char *sitePassword = NULL;
if (resultType & MPResultTypeClassTemplate) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromTemplate_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromTemplate_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromTemplate_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromTemplate_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
else if (resultType & MPResultTypeClassStateful) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromCrypt_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromCrypt_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromCrypt_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromCrypt_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
else if (resultType & MPResultTypeClassDerive) {
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_sitePasswordFromDerive_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_sitePasswordFromDerive_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_sitePasswordFromDerive_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_sitePasswordFromDerive_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
else {
err( "Unsupported password type: %d", resultType );
}
return sitePassword;
}
const char *mpw_siteState(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
const MPResultType resultType, const char *resultParam,
const MPAlgorithmVersion algorithmVersion) {
if (keyContext && !strlen( keyContext ))
keyContext = NULL;
if (resultParam && !strlen( resultParam ))
resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey)
return NULL;
trc( "-- mpw_siteState (algorithm: %u)", algorithmVersion );
trc( "resultType: %d (%s)", resultType, mpw_nameForType( resultType ) );
trc( "resultParam: %zu bytes = %s", sizeof( resultParam ), resultParam );
if (!masterKey || !resultParam)
return NULL;
switch (algorithmVersion) {
case MPAlgorithmVersion0:
return mpw_siteState_v0( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion1:
return mpw_siteState_v1( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion2:
return mpw_siteState_v2( masterKey, siteKey, resultType, resultParam );
case MPAlgorithmVersion3:
return mpw_siteState_v3( masterKey, siteKey, resultType, resultParam );
default:
err( "Unsupported version: %d", algorithmVersion );
return NULL;
}
}
MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword) {
const char *leftArm[] = { "", "", "", "" };
const char *rightArm[] = { "", "", "", "" };
const char *body[] = { "", "", "", "", "", "" };
const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
const uint8_t *identiconSeed = NULL;
if (fullName && strlen( fullName ) && masterPassword && strlen( masterPassword ))
identiconSeed = mpw_hash_hmac_sha256(
(const uint8_t *)masterPassword, strlen( masterPassword ),
(const uint8_t *)fullName, strlen( fullName ) );
if (!identiconSeed)
return (MPIdenticon){
.leftArm = "",
.body = "",
.rightArm = "",
.accessory = "",
.color=0,
};
MPIdenticon identicon = {
.leftArm = leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( leftArm[0] ))],
.body = body[identiconSeed[1] % (sizeof( body ) / sizeof( body[0] ))],
.rightArm = rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( rightArm[0] ))],
.accessory = accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
.color = (uint8_t)(identiconSeed[4] % (MPIdenticonColorLast - MPIdenticonColorFirst + 1) + MPIdenticonColorFirst),
};
mpw_free( &identiconSeed, 32 );
return identicon;
}

View File

@ -1,115 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <string.h>
#include "mpw-marshal-util.h"
#include "mpw-util.h"
char *mpw_get_token(const char **in, const char *eol, char *delim) {
// Skip leading spaces.
for (; **in == ' '; ++*in);
// Find characters up to the first delim.
size_t len = strcspn( *in, delim );
char *token = len && len <= (size_t)(eol - *in)? mpw_strndup( *in, len ): NULL;
// Advance past the delimitor.
*in = min( eol, *in + len + 1 );
return token;
}
time_t mpw_mktime(
const char *time) {
struct tm tm = { .tm_isdst = -1 };
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
tm.tm_year -= 1900; // tm_year 0 = rfc3339 year 1900
tm.tm_mon -= 1; // tm_mon 0 = rfc3339 month 1
return mktime( &tm );
}
return false;
}
#if MPW_JSON
json_object *mpw_get_json_section(
json_object *obj, const char *section) {
json_object *json_value = obj;
char *sectionTokenizer = mpw_strdup( section ), *sectionToken = sectionTokenizer;
for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." ))
if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) {
trc( "While resolving: %s: Missing value for: %s", section, sectionToken );
json_value = NULL;
break;
}
free( sectionTokenizer );
return json_value;
}
const char *mpw_get_json_string(
json_object *obj, const char *section, const char *defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_string( json_value );
}
int64_t mpw_get_json_int(
json_object *obj, const char *section, int64_t defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_int64( json_value );
}
bool mpw_get_json_boolean(
json_object *obj, const char *section, bool defaultValue) {
json_object *json_value = mpw_get_json_section( obj, section );
if (!json_value)
return defaultValue;
return json_object_get_boolean( json_value ) == TRUE;
}
#endif
bool mpw_update_masterKey(MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
const char *fullName, const char *masterPassword) {
if (*masterKeyAlgorithm != targetKeyAlgorithm) {
mpw_free( masterKey, MPMasterKeySize );
*masterKeyAlgorithm = targetKeyAlgorithm;
*masterKey = mpw_masterKey( fullName, masterPassword, *masterKeyAlgorithm );
if (!*masterKey) {
err( "Couldn't derive master key for user %s, algorithm %d.", fullName, *masterKeyAlgorithm );
return false;
}
}
return true;
}

View File

@ -1,73 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_MARSHAL_UTIL_H
#define _MPW_MARSHAL_UTIL_H
#include <time.h>
#if MPW_JSON
#include "json-c/json.h"
#endif
#include "mpw-algorithm.h"
/// Type parsing.
/** Get a token from a string by searching until the first character in delim, no farther than eol.
* The input string reference is advanced beyond the token delimitor if one is found.
* @return A new string containing the token or NULL if the delim wasn't found before eol. */
char *mpw_get_token(
const char **in, const char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */
time_t mpw_mktime(
const char *time);
/// JSON parsing.
#if MPW_JSON
/** Search for a JSON child object in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return A new JSON object or NULL if one of the section's object keys was not found in the source object's tree. */
json_object *mpw_get_json_section(
json_object *obj, const char *section);
/** Search for a string in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return A new string or defaultValue if one of the section's object keys was not found in the source object's tree. */
const char *mpw_get_json_string(
json_object *obj, const char *section, const char *defaultValue);
/** Search for an integer in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return The integer value or defaultValue if one of the section's object keys was not found in the source object's tree. */
int64_t mpw_get_json_int(
json_object *obj, const char *section, int64_t defaultValue);
/** Search for a boolean in a JSON object tree.
* @param section A dot-delimited list of JSON object keys to walk toward the child object.
* @return The boolean value or defaultValue if one of the section's object keys was not found in the source object's tree. */
bool mpw_get_json_boolean(
json_object *obj, const char *section, bool defaultValue);
#endif
/// mpw.
/** Calculate a master key if the target master key algorithm is different from the given master key algorithm.
* @return false if an error occurred during the derivation of the master key. */
bool mpw_update_masterKey(
MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
const char *fullName, const char *masterPassword);
#endif // _MPW_MARSHAL_UTIL_H

View File

@ -1,924 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "mpw-marshal.h"
#include "mpw-util.h"
#include "mpw-marshal-util.h"
MPMarshalledUser *mpw_marshal_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
MPMarshalledUser *user;
if (!fullName || !masterPassword || !(user = malloc( sizeof( MPMarshalledUser ) )))
return NULL;
*user = (MPMarshalledUser){
.fullName = mpw_strdup( fullName ),
.masterPassword = mpw_strdup( masterPassword ),
.algorithm = algorithmVersion,
.redacted = true,
.avatar = 0,
.defaultType = MPResultTypeDefault,
.lastUsed = 0,
.sites_count = 0,
.sites = NULL,
};
return user;
}
MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user, const char *siteName, const MPResultType resultType,
const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) {
if (!siteName || !mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count ))
return NULL;
MPMarshalledSite *site = &user->sites[user->sites_count - 1];
*site = (MPMarshalledSite){
.name = mpw_strdup( siteName ),
.content = NULL,
.type = resultType,
.counter = siteCounter,
.algorithm = algorithmVersion,
.loginContent = NULL,
.loginType = MPResultTypeTemplateName,
.url = NULL,
.uses = 0,
.lastUsed = 0,
.questions_count = 0,
.questions = NULL,
};
return site;
}
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword) {
if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count ))
return NULL;
if (!keyword)
keyword = "";
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
*question = (MPMarshalledQuestion){
.keyword = mpw_strdup( keyword ),
.content = NULL,
.type = MPResultTypeTemplatePhrase,
};
return question;
}
bool mpw_marshal_info_free(
MPMarshalInfo **info) {
if (!info || !*info)
return true;
bool success = true;
success &= mpw_free_strings( &(*info)->fullName, &(*info)->keyID, NULL );
success &= mpw_free( info, sizeof( MPMarshalInfo ) );
return success;
}
bool mpw_marshal_free(
MPMarshalledUser **user) {
if (!user || !*user)
return true;
bool success = true;
success &= mpw_free_strings( &(*user)->fullName, &(*user)->masterPassword, NULL );
for (size_t s = 0; s < (*user)->sites_count; ++s) {
MPMarshalledSite *site = &(*user)->sites[s];
success &= mpw_free_strings( &site->name, &site->content, &site->loginContent, &site->url, NULL );
for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q];
success &= mpw_free_strings( &question->keyword, &question->content, NULL );
}
success &= mpw_free( &site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count );
}
success &= mpw_free( &(*user)->sites, sizeof( MPMarshalledSite ) * (*user)->sites_count );
success &= mpw_free( user, sizeof( MPMarshalledUser ) );
return success;
}
static bool mpw_marshal_write_flat(
char **out, const MPMarshalledUser *user, MPMarshalError *error) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
return false;
}
if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
return false;
}
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false;
}
mpw_string_pushf( out, "# Master Password site export\n" );
if (user->redacted)
mpw_string_pushf( out, "# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n" );
else
mpw_string_pushf( out, "# Export of site names and passwords in clear-text.\n" );
mpw_string_pushf( out, "# \n" );
mpw_string_pushf( out, "##\n" );
mpw_string_pushf( out, "# Format: %d\n", 1 );
char dateString[21];
time_t now = time( NULL );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
mpw_string_pushf( out, "# Date: %s\n", dateString );
mpw_string_pushf( out, "# User Name: %s\n", user->fullName );
mpw_string_pushf( out, "# Full Name: %s\n", user->fullName );
mpw_string_pushf( out, "# Avatar: %u\n", user->avatar );
mpw_string_pushf( out, "# Key ID: %s\n", mpw_id_buf( masterKey, MPMasterKeySize ) );
mpw_string_pushf( out, "# Algorithm: %d\n", user->algorithm );
mpw_string_pushf( out, "# Default Type: %d\n", user->defaultType );
mpw_string_pushf( out, "# Passwords: %s\n", user->redacted? "PROTECTED": "VISIBLE" );
mpw_string_pushf( out, "##\n" );
mpw_string_pushf( out, "#\n" );
mpw_string_pushf( out, "# Last Times Password Login\t Site\tSite\n" );
mpw_string_pushf( out, "# used used type name\t name\tpassword\n" );
// Sites.
for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s];
if (!site->name || !strlen( site->name ))
continue;
const char *content = NULL, *loginContent = NULL;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false;
}
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = mpw_strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = mpw_strdup( site->loginContent );
}
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n",
dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter,
loginContent?: "", site->name, content?: "" );
mpw_free_strings( &content, &loginContent, NULL );
}
mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return true;
}
#if MPW_JSON
static bool mpw_marshal_write_json(
char **out, const MPMarshalledUser *user, MPMarshalError *error) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
return false;
}
if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
return false;
}
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false;
}
// Section: "export"
json_object *json_file = json_object_new_object();
json_object *json_export = json_object_new_object();
json_object_object_add( json_file, "export", json_export );
json_object_object_add( json_export, "format", json_object_new_int( 1 ) );
json_object_object_add( json_export, "redacted", json_object_new_boolean( user->redacted ) );
char dateString[21];
time_t now = time( NULL );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &now ) ))
json_object_object_add( json_export, "date", json_object_new_string( dateString ) );
// Section: "user"
json_object *json_user = json_object_new_object();
json_object_object_add( json_file, "user", json_user );
json_object_object_add( json_user, "avatar", json_object_new_int( (int32_t)user->avatar ) );
json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) ))
json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) );
json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) );
json_object_object_add( json_user, "algorithm", json_object_new_int( (int32_t)user->algorithm ) );
json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)user->defaultType ) );
// Section "sites"
json_object *json_sites = json_object_new_object();
json_object_object_add( json_file, "sites", json_sites );
for (size_t s = 0; s < user->sites_count; ++s) {
MPMarshalledSite *site = &user->sites[s];
if (!site->name || !strlen( site->name ))
continue;
const char *content = NULL, *loginContent = NULL;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false;
}
content = mpw_siteResult( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm );
loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = mpw_strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = mpw_strdup( site->loginContent );
}
json_object *json_site = json_object_new_object();
json_object_object_add( json_sites, site->name, json_site );
json_object_object_add( json_site, "type", json_object_new_int( (int32_t)site->type ) );
json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) );
json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) );
if (content)
json_object_object_add( json_site, "password", json_object_new_string( content ) );
if (loginContent)
json_object_object_add( json_site, "login_name", json_object_new_string( loginContent ) );
json_object_object_add( json_site, "login_type", json_object_new_int( (int32_t)site->loginType ) );
json_object_object_add( json_site, "uses", json_object_new_int( (int32_t)site->uses ) );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) );
json_object *json_site_questions = json_object_new_object();
json_object_object_add( json_site, "questions", json_site_questions );
for (size_t q = 0; q < site->questions_count; ++q) {
MPMarshalledQuestion *question = &site->questions[q];
if (!question->keyword)
continue;
json_object *json_site_question = json_object_new_object();
json_object_object_add( json_site_questions, question->keyword, json_site_question );
json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) );
if (!user->redacted) {
// Clear Text
const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm );
json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) );
}
else {
// Redacted
if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content ))
json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) );
}
}
json_object *json_site_mpw = json_object_new_object();
json_object_object_add( json_site, "_ext_mpw", json_site_mpw );
if (site->url)
json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) );
mpw_free_strings( &content, &loginContent, NULL );
}
mpw_string_pushf( out, "%s\n", json_object_to_json_string_ext( json_file, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED ) );
mpw_free( &masterKey, MPMasterKeySize );
json_object_put( json_file );
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return true;
}
#endif
bool mpw_marshal_write(
char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error) {
switch (outFormat) {
case MPMarshalFormatNone:
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return false;
case MPMarshalFormatFlat:
return mpw_marshal_write_flat( out, user, error );
#if MPW_JSON
case MPMarshalFormatJSON:
return mpw_marshal_write_json( out, user, error );
#endif
default:
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
return false;
}
}
static void mpw_marshal_read_flat_info(
const char *in, MPMarshalInfo *info) {
info->algorithm = MPAlgorithmVersionCurrent;
// Parse import data.
bool headerStarted = false;
for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
// Comment or header
if (*positionInLine == '#') {
++positionInLine;
if (!headerStarted) {
if (*positionInLine == '#')
// ## starts header
headerStarted = true;
// Comment before header
continue;
}
if (*positionInLine == '#')
// ## ends header
break;
// Header
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue)
continue;
if (strcmp( headerName, "Algorithm" ) == 0)
info->algorithm = (MPAlgorithmVersion)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
info->fullName = mpw_strdup( headerValue );
if (strcmp( headerName, "Key ID" ) == 0)
info->keyID = mpw_strdup( headerValue );
if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0)
info->date = mpw_mktime( headerValue );
mpw_free_strings( &headerName, &headerValue, NULL );
continue;
}
}
}
static MPMarshalledUser *mpw_marshal_read_flat(
const char *in, const char *masterPassword, MPMarshalError *error) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshalErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// Parse import data.
MPMasterKey masterKey = NULL;
MPMarshalledUser *user = NULL;
unsigned int format = 0, avatar = 0;
char *fullName = NULL, *keyID = NULL;
MPAlgorithmVersion algorithm = MPAlgorithmVersionCurrent, masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPResultType defaultType = MPResultTypeDefault;
bool headerStarted = false, headerEnded = false, importRedacted = false;
for (const char *endOfLine, *positionInLine = in; (endOfLine = strstr( positionInLine, "\n" )); positionInLine = endOfLine + 1) {
// Comment or header
if (*positionInLine == '#') {
++positionInLine;
if (!headerStarted) {
if (*positionInLine == '#')
// ## starts header
headerStarted = true;
// Comment before header
continue;
}
if (headerEnded)
// Comment after header
continue;
if (*positionInLine == '#') {
// ## ends header
headerEnded = true;
continue;
}
// Header
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue) {
error->type = MPMarshalErrorStructure;
error->description = mpw_str( "Invalid header: %s", mpw_strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) );
return NULL;
}
if (strcmp( headerName, "Format" ) == 0)
format = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
fullName = mpw_strdup( headerValue );
if (strcmp( headerName, "Avatar" ) == 0)
avatar = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Key ID" ) == 0)
keyID = mpw_strdup( headerValue );
if (strcmp( headerName, "Algorithm" ) == 0) {
int value = atoi( headerValue );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
return NULL;
}
algorithm = (MPAlgorithmVersion)value;
}
if (strcmp( headerName, "Default Type" ) == 0) {
int value = atoi( headerValue );
if (!mpw_nameForType( (MPResultType)value )) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
return NULL;
}
defaultType = (MPResultType)value;
}
if (strcmp( headerName, "Passwords" ) == 0)
importRedacted = strcmp( headerValue, "VISIBLE" ) != 0;
mpw_free_strings( &headerName, &headerValue, NULL );
continue;
}
if (!headerEnded)
continue;
if (!fullName) {
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing header: Full Name" };
return NULL;
}
if (positionInLine >= endOfLine)
continue;
if (!user) {
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
return NULL;
}
if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
return NULL;
}
user->redacted = importRedacted;
user->avatar = avatar;
user->defaultType = defaultType;
}
// Site
char *siteLoginName = NULL, *siteName = NULL, *siteContent = NULL;
char *str_lastUsed = NULL, *str_uses = NULL, *str_type = NULL, *str_algorithm = NULL, *str_counter = NULL;
switch (format) {
case 0: {
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersion) {
str_type = mpw_strdup( strtok( typeAndVersion, ":" ) );
str_algorithm = mpw_strdup( strtok( NULL, "" ) );
mpw_free_string( &typeAndVersion );
}
str_counter = mpw_strdup( "1" );
siteLoginName = NULL;
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
break;
}
case 1: {
str_lastUsed = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersionAndCounter) {
str_type = mpw_strdup( strtok( typeAndVersionAndCounter, ":" ) );
str_algorithm = mpw_strdup( strtok( NULL, ":" ) );
str_counter = mpw_strdup( strtok( NULL, "" ) );
mpw_free_string( &typeAndVersionAndCounter );
}
siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
break;
}
default: {
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
return NULL;
}
}
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
MPResultType siteType = (MPResultType)atoi( str_type );
if (!mpw_nameForType( siteType )) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
return NULL;
}
long long int value = atoll( str_counter );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
return NULL;
}
MPCounterValue siteCounter = (MPCounterValue)value;
value = atoll( str_algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) };
return NULL;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL;
}
MPMarshalledSite *site = mpw_marshal_site(
user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
return NULL;
}
site->uses = (unsigned int)atoi( str_uses );
site->lastUsed = siteLastUsed;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = mpw_strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_strdup( siteLoginName );
}
}
else {
error->type = MPMarshalErrorMissing;
error->description = mpw_str(
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName );
return NULL;
}
mpw_free_strings( &str_lastUsed, &str_uses, &str_type, &str_algorithm, &str_counter, NULL );
mpw_free_strings( &siteLoginName, &siteName, &siteContent, NULL );
}
mpw_free_strings( &fullName, &keyID, NULL );
mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return user;
}
#if MPW_JSON
static void mpw_marshal_read_json_info(
const char *in, MPMarshalInfo *info) {
// Parse JSON.
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success)
return;
// Section: "export"
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1)
return;
info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true );
info->date = mpw_mktime( mpw_get_json_string( json_file, "export.date", NULL ) );
// Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
info->fullName = mpw_strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
info->keyID = mpw_strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
json_object_put( json_file );
}
static MPMarshalledUser *mpw_marshal_read_json(
const char *in, const char *masterPassword, MPMarshalError *error) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) {
error->type = MPMarshalErrorStructure;
error->description = mpw_str( "No input data." );
return NULL;
}
// Parse JSON.
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success) {
*error = (MPMarshalError){ MPMarshalErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) };
return NULL;
}
// Parse import data.
MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = (MPAlgorithmVersion)-1;
MPMarshalledUser *user = NULL;
// Section: "export"
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1) {
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) };
return NULL;
}
bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true );
// Section: "user"
unsigned int avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 );
const char *fullName = mpw_get_json_string( json_file, "user.full_name", NULL );
const char *str_lastUsed = mpw_get_json_string( json_file, "user.last_used", NULL );
const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL );
int64_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) };
return NULL;
}
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value;
MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault );
if (!mpw_nameForType( defaultType )) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
return NULL;
}
time_t lastUsed = mpw_mktime( str_lastUsed );
if (!lastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
return NULL;
}
if (!fullName || !strlen( fullName )) {
*error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." };
return NULL;
}
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
return NULL;
}
if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
return NULL;
}
user->redacted = fileRedacted;
user->avatar = avatar;
user->defaultType = defaultType;
user->lastUsed = lastUsed;
// Section "sites"
json_object_iter json_site;
json_object *json_sites = mpw_get_json_section( json_file, "sites" );
json_object_object_foreachC( json_sites, json_site ) {
const char *siteName = json_site.key;
value = mpw_get_json_int( json_site.val, "algorithm", (int32_t)user->algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) };
return NULL;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", (int32_t)user->defaultType );
if (!mpw_nameForType( siteType )) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
return NULL;
}
value = mpw_get_json_int( json_site.val, "counter", 1 );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) };
return NULL;
}
MPCounterValue siteCounter = (MPCounterValue)value;
const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL );
const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL );
MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName );
unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 );
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) {
*error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL;
}
json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" );
const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL );
MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
return NULL;
}
site->loginType = siteLoginType;
site->url = siteURL? mpw_strdup( siteURL ): NULL;
site->uses = siteUses;
site->lastUsed = siteLastUsed;
if (!user->redacted) {
// Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else {
// Redacted
if (siteContent && strlen( siteContent ))
site->content = mpw_strdup( siteContent );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_strdup( siteLoginName );
}
json_object_iter json_site_question;
json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" );
json_object_object_foreachC( json_site_questions, json_site_question ) {
MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key );
const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL );
question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase );
if (!user->redacted) {
// Clear Text
if (answerContent && strlen( answerContent ))
question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm );
}
else {
// Redacted
if (answerContent && strlen( answerContent ))
question->content = mpw_strdup( answerContent );
}
}
}
json_object_put( json_file );
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return user;
}
#endif
MPMarshalInfo *mpw_marshal_read_info(
const char *in) {
MPMarshalInfo *info = malloc( sizeof( MPMarshalInfo ) );
*info = (MPMarshalInfo){ .format = MPMarshalFormatNone };
if (in && strlen( in )) {
if (in[0] == '#') {
*info = (MPMarshalInfo){ .format = MPMarshalFormatFlat };
mpw_marshal_read_flat_info( in, info );
}
else if (in[0] == '{') {
*info = (MPMarshalInfo){ .format = MPMarshalFormatJSON };
#if MPW_JSON
mpw_marshal_read_json_info( in, info );
#endif
}
}
return info;
}
MPMarshalledUser *mpw_marshal_read(
const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error) {
switch (inFormat) {
case MPMarshalFormatNone:
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return false;
case MPMarshalFormatFlat:
return mpw_marshal_read_flat( in, masterPassword, error );
#if MPW_JSON
case MPMarshalFormatJSON:
return mpw_marshal_read_json( in, masterPassword, error );
#endif
default:
*error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
return NULL;
}
}
const MPMarshalFormat mpw_formatWithName(
const char *formatName) {
if (!formatName || !strlen( formatName ))
return MPMarshalFormatNone;
// Lower-case to standardize it.
size_t stdFormatNameSize = strlen( formatName );
char stdFormatName[stdFormatNameSize + 1];
for (size_t c = 0; c < stdFormatNameSize; ++c)
stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshalFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshalFormatNone;
if (strncmp( mpw_nameForFormat( MPMarshalFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshalFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshalFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshalFormatJSON;
dbg( "Not a format name: %s", stdFormatName );
return (MPMarshalFormat)ERR;
}
const char *mpw_nameForFormat(
const MPMarshalFormat format) {
switch (format) {
case MPMarshalFormatNone:
return "none";
case MPMarshalFormatFlat:
return "flat";
case MPMarshalFormatJSON:
return "json";
default: {
dbg( "Unknown format: %d", format );
return NULL;
}
}
}
const char *mpw_marshal_format_extension(
const MPMarshalFormat format) {
switch (format) {
case MPMarshalFormatNone:
return NULL;
case MPMarshalFormatFlat:
return "mpsites";
case MPMarshalFormatJSON:
return "mpsites.json";
default: {
dbg( "Unknown format: %d", format );
return NULL;
}
}
}

View File

@ -1,159 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_MARSHAL_H
#define _MPW_MARSHAL_H
#include <time.h>
#include "mpw-algorithm.h"
//// Types.
typedef mpw_enum( unsigned int, MPMarshalFormat ) {
/** Do not marshal. */
MPMarshalFormatNone,
/** Marshal using the line-based plain-text format. */
MPMarshalFormatFlat,
/** Marshal using the JSON structured format. */
MPMarshalFormatJSON,
#if MPW_JSON
MPMarshalFormatDefault = MPMarshalFormatJSON,
#else
MPMarshalFormatDefault = MPMarshalFormatFlat,
#endif
};
typedef mpw_enum( unsigned int, MPMarshalErrorType ) {
/** The marshalling operation completed successfully. */
MPMarshalSuccess,
/** An error in the structure of the marshall file interrupted marshalling. */
MPMarshalErrorStructure,
/** The marshall file uses an unsupported format version. */
MPMarshalErrorFormat,
/** A required value is missing or not specified. */
MPMarshalErrorMissing,
/** The given master password is not valid. */
MPMarshalErrorMasterPassword,
/** An illegal value was specified. */
MPMarshalErrorIllegal,
/** An internal system error interrupted marshalling. */
MPMarshalErrorInternal,
};
typedef struct MPMarshalError {
MPMarshalErrorType type;
const char *description;
} MPMarshalError;
typedef struct MPMarshalledQuestion {
const char *keyword;
const char *content;
MPResultType type;
} MPMarshalledQuestion;
typedef struct MPMarshalledSite {
const char *name;
const char *content;
MPResultType type;
MPCounterValue counter;
MPAlgorithmVersion algorithm;
const char *loginContent;
MPResultType loginType;
const char *url;
unsigned int uses;
time_t lastUsed;
size_t questions_count;
MPMarshalledQuestion *questions;
} MPMarshalledSite;
typedef struct MPMarshalledUser {
const char *fullName;
const char *masterPassword;
MPAlgorithmVersion algorithm;
bool redacted;
unsigned int avatar;
MPResultType defaultType;
time_t lastUsed;
size_t sites_count;
MPMarshalledSite *sites;
} MPMarshalledUser;
typedef struct MPMarshalInfo {
MPMarshalFormat format;
MPAlgorithmVersion algorithm;
const char *fullName;
const char *keyID;
bool redacted;
time_t date;
} MPMarshalInfo;
//// Marshalling.
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */
bool mpw_marshal_write(
char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error);
/** Try to read metadata on the sites in the input buffer. */
MPMarshalInfo *mpw_marshal_read_info(
const char *in);
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
MPMarshalledUser *mpw_marshal_read(
const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error);
//// Utilities.
/** Create a new user object ready for marshalling. */
MPMarshalledUser *mpw_marshal_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Create a new site attached to the given user object, ready for marshalling. */
MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
/** Create a new question attached to the given site object, ready for marshalling. */
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
/** Free the given user object and all associated data. */
bool mpw_marshal_info_free(
MPMarshalInfo **info);
bool mpw_marshal_free(
MPMarshalledUser **user);
//// Format.
/**
* @return The purpose represented by the given name.
*/
const MPMarshalFormat mpw_formatWithName(
const char *formatName);
/**
* @return The standard name for the given purpose.
*/
const char *mpw_nameForFormat(
const MPMarshalFormat format);
/**
* @return The file extension that's recommended for files that use the given marshalling format.
*/
const char *mpw_marshal_format_extension(
const MPMarshalFormat format);
#endif // _MPW_MARSHAL_H

View File

@ -1,211 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#ifndef _MPW_UTIL_H
#define _MPW_UTIL_H
#include <stdio.h>
#include <stdarg.h>
#include "mpw-types.h"
//// Logging.
extern int mpw_verbosity;
#ifndef mpw_log_do
#define mpw_log_do(level, format, ...) \
fprintf( stderr, format "\n", ##__VA_ARGS__ )
#endif
#ifndef mpw_log
#define mpw_log(level, ...) ({ \
if (mpw_verbosity >= level) { \
mpw_log_do( level, ##__VA_ARGS__ ); \
}; })
#endif
#ifndef trc
/** Logging internal state. */
#define trc_level 3
#define trc(...) mpw_log( trc_level, ##__VA_ARGS__ )
/** Logging state and events interesting when investigating issues. */
#define dbg_level 2
#define dbg(...) mpw_log( dbg_level, ##__VA_ARGS__ )
/** User messages. */
#define inf_level 1
#define inf(...) mpw_log( inf_level, ##__VA_ARGS__ )
/** Recoverable issues and user suggestions. */
#define wrn_level 0
#define wrn(...) mpw_log( wrn_level, ##__VA_ARGS__ )
/** Unrecoverable issues. */
#define err_level -1
#define err(...) mpw_log( err_level, ##__VA_ARGS__ )
/** Issues that lead to abortion. */
#define ftl_level -2
#define ftl(...) mpw_log( ftl_level, ##__VA_ARGS__ )
#endif
#ifndef min
#define min(a, b) ({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
#endif
#ifndef max
#define max(a, b) ({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
#endif
#ifndef ERR
#define ERR -1
#endif
#ifndef OK
#define OK 0
#endif
#ifndef stringify
#define stringify(s) #s
#endif
#ifndef stringify_def
#define stringify_def(s) stringify(s)
#endif
//// Buffers and memory.
/** Write a number to a byte buffer using mpw's endianness (big/network endian). */
void mpw_uint16(const uint16_t number, uint8_t buf[2]);
void mpw_uint32(const uint32_t number, uint8_t buf[4]);
void mpw_uint64(const uint64_t number, uint8_t buf[8]);
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
#define mpw_alloc_array(_count, _type, ...) ({ \
_type stackElements[] = { __VA_ARGS__ }; \
if (_count) \
*_count = sizeof( stackElements ) / sizeof( _type ); \
_type *allocElements = malloc( sizeof( stackElements ) ); \
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
allocElements; \
})
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
bool mpw_push_buf(
uint8_t **buffer, size_t *bufferSize, const void *pushBuffer, const size_t pushSize);
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
bool mpw_push_string(
uint8_t **buffer, size_t *bufferSize, const char *pushString);
/** Push a string onto another string. reallocs the target string and appends the source string. */
bool mpw_string_push(
char **string, const char *pushString);
bool mpw_string_pushf(
char **string, const char *pushFormat, ...);
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
bool mpw_push_int(
uint8_t **buffer, size_t *bufferSize, const uint32_t pushInt);
/** Reallocate the given buffer from the given size by adding the delta size.
* On success, the buffer size pointer will be updated to the buffer's new size
* and the buffer pointer may be updated to a new memory address.
* On failure, the buffer and pointers will remain unaffected.
* @param buffer A pointer to the buffer to reallocate.
* @param bufferSize A pointer to the buffer's actual size.
* @param deltaSize The amount to increase the buffer's size by.
* @return true if successful, false if reallocation failed.
*/
#define mpw_realloc(buffer, bufferSize, deltaSize) \
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); })
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize);
void mpw_zero(
void *buffer, size_t bufferSize);
/** Free a buffer after zero'ing its contents, then set the reference to NULL. */
#define mpw_free(buffer, bufferSize) \
({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (void **)_b, bufferSize ); })
bool __mpw_free(
void **buffer, size_t bufferSize);
/** Free a string after zero'ing its contents, then set the reference to NULL. */
#define mpw_free_string(string) \
({ __typeof__(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (char **)_s ); })
bool __mpw_free_string(
char **string);
/** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */
#define mpw_free_strings(strings, ...) \
({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); })
bool __mpw_free_strings(
char **strings, ...);
//// Cryptographic functions.
/** Derive a key from the given secret and salt using the scrypt KDF.
* @return A new keySize allocated buffer containing the key. */
uint8_t const *mpw_kdf_scrypt(
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p);
/** Derive a subkey from the given key using the blake2b KDF.
* @return A new keySize allocated buffer containing the key. */
uint8_t const *mpw_kdf_blake2b(
const size_t subkeySize, const uint8_t *key, const size_t keySize,
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal);
/** Calculate the MAC for the given message with the given key using SHA256-HMAC.
* @return A new 32-byte allocated buffer containing the MAC. */
uint8_t const *mpw_hash_hmac_sha256(
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
/** Encrypt a plainBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the cipherBuf. */
uint8_t const *mpw_aes_encrypt(
const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, size_t *bufSize);
/** Decrypt a cipherBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the plainBuf. */
uint8_t const *mpw_aes_decrypt(
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize);
/** Calculate an OTP using RFC-4226.
* @return A newly allocated string containing exactly `digits` decimal OTP digits. */
#if UNUSED
const char *mpw_hotp(
const uint8_t *key, size_t keySize, uint64_t movingFactor, uint8_t digits, uint8_t truncationOffset);
#endif
//// Visualizers.
/** Compose a formatted string.
* @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_str(const char *format, ...);
const char *mpw_vstr(const char *format, va_list args);
/** Encode a buffer as a string of hexadecimal characters.
* @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_hex(const void *buf, size_t length);
const char *mpw_hex_l(uint32_t number);
/** Encode a fingerprint for a buffer.
* @return A C-string in a reused buffer, do not free or store it. */
MPKeyID mpw_id_buf(const void *buf, size_t length);
/** Compare two fingerprints for equality.
* @return true if the buffers represent identical fingerprints. */
bool mpw_id_buf_equals(const char *id1, const char *id2);
//// String utilities.
/** @return The amount of display characters in the given UTF-8 string. */
const size_t mpw_utf8_strlen(const char *utf8String);
/** Drop-in for POSIX strdup(3). */
char *mpw_strdup(const char *src);
/** Drop-in for POSIX strndup(3). */
char *mpw_strndup(const char *src, size_t max);
#endif // _MPW_UTIL_H

View File

@ -1,16 +0,0 @@
plugins {
id 'java'
}
description = 'Master Password Algorithm Implementation'
dependencies {
compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p11') {
exclude( module: 'joda-time' )
}
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11'
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
}

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
</parent>
<name>Master Password Algorithm Implementation</name>
<description>The implementation of the Master Password algorithm</description>
<artifactId>masterpassword-algorithm</artifactId>
<packaging>jar</packaging>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-system</artifactId>
<version>1.6-p11</version>
<exclusions>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-crypto</artifactId>
<version>1.6-p11</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->
<dependency>
<groupId>com.lambdaworks</groupId>
<artifactId>scrypt</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>

View File

@ -1,99 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.MessageDigests;
import java.io.Serializable;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version
*/
public interface MPAlgorithm {
/**
* mpw: validity for the time-based rolling counter.
*/
int mpw_otp_window = 5 * 60 /* s */;
/**
* mpw: Key ID hash.
*/
MessageDigests mpw_hash = MessageDigests.SHA256;
/**
* mpw: Site digest.
*/
MessageAuthenticationDigests mpw_digest = MessageAuthenticationDigests.HmacSHA256;
/**
* mpw: Platform-agnostic byte order.
*/
ByteOrder mpw_byteOrder = ByteOrder.BIG_ENDIAN;
/**
* mpw: Input character encoding.
*/
Charset mpw_charset = Charsets.UTF_8;
/**
* mpw: Master key size (byte).
*/
int mpw_dkLen = 64;
/**
* scrypt: Parallelization parameter.
*/
int scrypt_p = 2;
/**
* scrypt: Memory cost parameter.
*/
int scrypt_r = 8;
/**
* scrypt: CPU cost parameter.
*/
int scrypt_N = 32768;
MPMasterKey.Version getAlgorithmVersion();
byte[] masterKey(String fullName, char[] masterPassword);
byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext);
String siteResult(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromCrypt(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String siteState(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, String resultParam);
}

View File

@ -1,248 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.*;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.crypto.CryptUtils;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.nio.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
/**
* @author lhunath, 2014-08-30
* @see MPMasterKey.Version#V0
*/
public class MPAlgorithmV0 implements MPAlgorithm {
protected final Logger logger = Logger.get( getClass() );
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V0;
}
@Override
public byte[] masterKey(final String fullName, final char[] masterPassword) {
byte[] fullNameBytes = fullName.getBytes( mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
String keyScope = MPKeyPurpose.Authentication.getScope();
logger.trc( "keyScope: %s", keyScope );
// Calculate the master key salt.
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
// Calculate the master key.
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
scrypt_N, scrypt_r, scrypt_p );
byte[] masterPasswordBytes = bytesForChars( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes );
Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( masterPasswordBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
return masterKey;
}
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try {
//if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
//else
// return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
}
catch (final GeneralSecurityException e) {
throw logger.bug( e );
}
}
@Override
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) {
String keyScope = keyPurpose.getScope();
logger.trc( "keyScope: %s", keyScope );
// OTP counter value.
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (mpw_otp_window * 1000)) * mpw_otp_window );
// Calculate the site seed.
byte[] siteNameBytes = siteName.getBytes( mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( mpw_charset );
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (keyContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
return sitePasswordSeedBytes;
}
@Override
public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) {
switch (resultType.getTypeClass()) {
case Template:
return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam );
case Stateful:
return sitePasswordFromCrypt( masterKey, siteKey, resultType, resultParam );
case Derive:
return sitePasswordFromDerive( masterKey, siteKey, resultType, resultParam );
}
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
}
@Override
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
int[] _siteKey = new int[siteKey.length];
for (int i = 0; i < siteKey.length; ++i) {
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder );
Arrays.fill( buf.array(), (byte) ((siteKey[i] > 0)? 0x00: 0xFF) );
buf.position( 2 );
buf.put( siteKey[i] ).rewind();
_siteKey[i] = buf.getInt() & 0xFFFF;
}
// Determine the template.
Preconditions.checkState( _siteKey.length > 0 );
int templateIndex = _siteKey[0];
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
// Encode the password from the seed using the template.
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = _siteKey[i + 1];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( " - class: %c, index: %5d (0x%2H) => character: %c",
characterClass.getIdentifier(), characterIndex, _siteKey[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
logger.trc( " => password: %s", password );
return password.toString();
}
@Override
public String sitePasswordFromCrypt(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() );
try {
// Base64-decode
byte[] cipherBuf = CryptUtils.decodeBase64( resultParam );
logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Decrypt
byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true );
String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString();
logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) );
return plainText;
}
catch (final BadPaddingException e) {
throw Throwables.propagate( e );
}
}
@Override
public String sitePasswordFromDerive(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
if (resultType == MPResultType.DeriveKey) {
int resultParamInt = ConversionUtils.toIntegerNN( resultParam );
if (resultParamInt == 0)
resultParamInt = 512;
if ((resultParamInt < 128) || (resultParamInt > 512) || ((resultParamInt % 8) != 0))
throw logger.bug( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam );
int keySize = resultParamInt / 8;
logger.trc( "keySize: %d", keySize );
// Derive key
byte[] resultKey = null; // TODO: mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
if (resultKey == null)
throw logger.bug( "Could not derive result key." );
// Base64-encode
String b64Key = Verify.verifyNotNull( CryptUtils.encodeBase64( resultKey ) );
logger.trc( "b64 encoded -> key: %s", b64Key );
return b64Key;
} else
throw logger.bug( "Unsupported derived password type: %s", resultType );
}
@Override
public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, final String resultParam) {
try {
// Encrypt
byte[] cipherBuf = CryptUtils.encrypt( resultParam.getBytes( mpw_charset ), masterKey, true );
logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Base64-encode
String cipherText = Verify.verifyNotNull( CryptUtils.encodeBase64( cipherBuf ) );
logger.trc( "b64 encoded -> cipherText: %s", cipherText );
return cipherText;
}
catch (final IllegalBlockSizeException e) {
throw logger.bug( e );
}
}
}

View File

@ -1,62 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version#V1
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV1 extends MPAlgorithmV0 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V1;
}
@Override
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
// Determine the template.
Preconditions.checkState( siteKey.length > 0 );
int templateIndex = siteKey[0] & 0xFF; // Convert to unsigned int.
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
// Encode the password from the seed using the template.
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = siteKey[i + 1] & 0xFF; // Convert to unsigned int.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( " - class: %c, index: %3d (0x%2H) => character: %c",
characterClass.getIdentifier(), characterIndex, siteKey[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
logger.trc( " => password: %s", password );
return password.toString();
}
}

View File

@ -1,74 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version#V2
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV2 extends MPAlgorithmV1 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V2;
}
@Override
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) {
String keyScope = keyPurpose.getScope();
logger.trc( "keyScope: %s", keyScope );
// OTP counter value.
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPAlgorithm.mpw_otp_window * 1000)) * MPAlgorithm.mpw_otp_window );
// Calculate the site seed.
byte[] siteNameBytes = siteName.getBytes( MPAlgorithm.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MPAlgorithm.mpw_charset );
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (keyContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
byte[] sitePasswordSeedBytes = MPAlgorithm.mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
return sitePasswordSeedBytes;
}
}

View File

@ -1,67 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import java.util.Arrays;
/**
* @see MPMasterKey.Version#V3
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV3 extends MPAlgorithmV2 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V3;
}
@Override
public byte[] masterKey(final String fullName, final char[] masterPassword) {
byte[] fullNameBytes = fullName.getBytes( MPAlgorithm.mpw_charset );
byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
String keyScope = MPKeyPurpose.Authentication.getScope();
logger.trc( "keyScope: %s", keyScope );
// Calculate the master key salt.
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
// Calculate the master key.
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
MPAlgorithm.scrypt_N, MPAlgorithm.scrypt_r, MPAlgorithm.scrypt_p );
byte[] mpBytes = bytesForChars( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, mpBytes );
Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( mpBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
return masterKey;
}
}

View File

@ -1,98 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* @author lhunath, 15-03-29
*/
public class MPIdenticon {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPIdenticon.class );
private static final Charset charset = Charsets.UTF_8;
private static final Color[] colors = {
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
private static final char[] leftArm = { '╔', '╚', '╰', '═' };
private static final char[] rightArm = { '╗', '╝', '╯', '═' };
private static final char[] body = { '█', '░', '▒', '▓', '☺', '☻' };
private static final char[] accessory = {
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
private final String fullName;
private final Color color;
private final String text;
public MPIdenticon(final String fullName, final String masterPassword) {
this( fullName, masterPassword.toCharArray() );
}
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MPIdenticon(final String fullName, final char[] masterPassword) {
this.fullName = fullName;
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
Arrays.fill( masterPasswordBytes, (byte) 0 );
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
while (identiconSeedBytes.hasRemaining())
identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
int[] identiconSeed = identiconSeedBuffer.array();
color = colors[identiconSeed[4] % colors.length];
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
}
public String getFullName() {
return fullName;
}
public String getText() {
return text;
}
public Color getColor() {
return color;
}
public enum Color {
RED,
GREEN,
YELLOW,
BLUE,
MAGENTA,
CYAN,
MONO
}
}

View File

@ -1,246 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import java.util.EnumMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2014-08-30
*/
public class MPMasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPMasterKey.class );
private final EnumMap<Version, byte[]> keyByVersion = new EnumMap<>( Version.class );
private final String fullName;
private final char[] masterPassword;
private boolean invalidated;
/**
* @param masterPassword The characters of the user's master password. Note: this array is held by reference and its contents
* invalidated on {@link #invalidate()}.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
public MPMasterKey(final String fullName, final char[] masterPassword) {
this.fullName = fullName;
this.masterPassword = masterPassword;
}
/**
* Derive the master key for a user based on their name and master password.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
private byte[] masterKey(final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkArgument( masterPassword.length > 0 );
if (invalidated)
throw new MPInvalidatedException();
byte[] key = keyByVersion.get( algorithmVersion );
if (key == null) {
logger.trc( "-- mpw_masterKey (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex( idForBytes( bytesForChars( masterPassword ) ) ) );
keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().masterKey( fullName, masterPassword ) );
}
return key;
}
/**
* Derive the master key for a user based on their name and master password.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
private byte[] siteKey(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkArgument( !siteName.isEmpty() );
byte[] masterKey = masterKey( algorithmVersion );
logger.trc( "-- mpw_siteKey (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %s", siteCounter );
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
logger.trc( "keyContext: %s", keyContext );
return algorithmVersion.getAlgorithm().siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
}
/**
* Generate a site result token.
*
* @param siteName A site identifier.
* @param siteCounter The result identifier.
* @param keyPurpose The intended purpose for this site result.
* @param keyContext A site-scoped result modifier.
* @param resultType The type of result to generate.
* @param resultParam A parameter for the resultType. For stateful result types, the output of
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion)
throws MPInvalidatedException {
byte[] masterKey = masterKey( algorithmVersion );
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
logger.trc( "-- mpw_siteResult (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %s", resultParam );
return algorithmVersion.getAlgorithm().siteResult(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
}
/**
* Encrypt a stateful site token for persistence.
*
* @param siteName A site identifier.
* @param siteCounter The result identifier.
* @param keyPurpose The intended purpose for the site token.
* @param keyContext A site-scoped key modifier.
* @param resultType The type of result token to encrypt.
* @param resultParam The result token desired from
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() );
byte[] masterKey = masterKey( algorithmVersion );
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
logger.trc( "-- mpw_siteState (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( MPAlgorithm.mpw_charset ).length, resultParam );
return algorithmVersion.getAlgorithm().siteState(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
}
@Nonnull
public String getFullName() {
return fullName;
}
/**
* Calculate an identifier for the master key.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public byte[] getKeyID(final Version algorithmVersion)
throws MPInvalidatedException {
return idForBytes( masterKey( algorithmVersion ) );
}
/**
* Wipe this key's secrets from memory, making the object permanently unusable.
*/
public void invalidate() {
invalidated = true;
for (final byte[] key : keyByVersion.values())
Arrays.fill( key, (byte) 0 );
Arrays.fill( masterPassword, (char) 0 );
}
/**
* The algorithm iterations.
*/
public enum Version {
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte user names.
*/
V0( new MPAlgorithmV0() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte user names.
*/
V1( new MPAlgorithmV1() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte user names.
*/
V2( new MPAlgorithmV2() ),
/**
* bugs:
* - no known issues.
*/
V3( new MPAlgorithmV3() );
public static final Version CURRENT = V3;
private final MPAlgorithm algorithm;
Version(final MPAlgorithm algorithm) {
this.algorithm = algorithm;
}
public MPAlgorithm getAlgorithm() {
return algorithm;
}
public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion];
}
public int toInt() {
return ordinal();
}
}
}

View File

@ -1,53 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.primitives.UnsignedInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
/**
* @author lhunath, 2017-09-20
*/
public final class MPUtils {
public static byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number ).array();
}
public static byte[] bytesForInt(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number.intValue() ).array();
}
public static byte[] bytesForChars(final char[] characters) {
ByteBuffer byteBuffer = MPAlgorithm.mpw_charset.encode( CharBuffer.wrap( characters ) );
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get( bytes );
Arrays.fill( byteBuffer.array(), (byte) 0 );
return bytes;
}
public static byte[] idForBytes(final byte[] bytes) {
return MPAlgorithm.mpw_hash.of( bytes );
}
}

View File

@ -1,18 +0,0 @@
plugins {
id 'java'
id 'net.ltgt.apt' version '0.9'
}
description = 'Master Password Site Model'
dependencies {
compile project(':masterpassword-algorithm')
compile group: 'joda-time', name: 'joda-time', version:'2.4'
compileOnly group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
}
test.useTestNG()

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
</parent>
<name>Master Password Site Model</name>
<description>A persistence model for Master Password sites.</description>
<artifactId>masterpassword-model</artifactId>
<packaging>jar</packaging>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>1.0-rc1</version>
<scope>provided</scope>
</dependency>
<!-- TESTING -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,205 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*;
import javax.annotation.Nullable;
import org.joda.time.Instant;
/**
* @author lhunath, 14-12-05
*/
public class MPFileSite extends MPSite {
private final MPFileUser user;
private String siteName;
@Nullable
private String siteContent;
private UnsignedInteger siteCounter;
private MPResultType resultType;
private MPMasterKey.Version algorithmVersion;
@Nullable
private String loginContent;
@Nullable
private MPResultType loginType;
@Nullable
private String url;
private int uses;
private Instant lastUsed;
public MPFileSite(final MPFileUser user, final String siteName) {
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT, MPMasterKey.Version.CURRENT );
}
public MPFileSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
final MPMasterKey.Version algorithmVersion) {
this.user = user;
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.lastUsed = new Instant();
}
protected MPFileSite(final MPFileUser user, final String siteName, @Nullable final String siteContent,
final UnsignedInteger siteCounter,
final MPResultType resultType, final MPMasterKey.Version algorithmVersion,
@Nullable final String loginContent, @Nullable final MPResultType loginType,
@Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user;
this.siteName = siteName;
this.siteContent = siteContent;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.loginContent = loginContent;
this.loginType = loginType;
this.url = url;
this.uses = uses;
this.lastUsed = lastUsed;
}
public String resultFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
return resultFor( masterKey, MPKeyPurpose.Authentication, null );
}
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPInvalidatedException {
return resultFor( masterKey, keyPurpose, keyContext, getSiteContent() );
}
public String loginFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
if (loginType == null)
loginType = MPResultType.GeneratedName;
return loginFor( masterKey, loginType, loginContent );
}
public MPFileUser getUser() {
return user;
}
@Override
public String getSiteName() {
return siteName;
}
@Override
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
@Nullable
public String getSiteContent() {
return siteContent;
}
public void setSitePassword(final MPMasterKey masterKey, @Nullable final MPResultType resultType, @Nullable final String result)
throws MPInvalidatedException {
this.resultType = resultType;
if (result == null)
this.siteContent = null;
else
this.siteContent = masterKey.siteState(
getSiteName(), getSiteCounter(), MPKeyPurpose.Authentication, null, getResultType(), result, getAlgorithmVersion() );
}
@Override
public UnsignedInteger getSiteCounter() {
return siteCounter;
}
@Override
public void setSiteCounter(final UnsignedInteger siteCounter) {
this.siteCounter = siteCounter;
}
@Override
public MPResultType getResultType() {
return resultType;
}
@Override
public void setResultType(final MPResultType resultType) {
this.resultType = resultType;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Nullable
public MPResultType getLoginType() {
return loginType;
}
@Nullable
public String getLoginContent() {
return loginContent;
}
public void setLoginName(final MPMasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result)
throws MPInvalidatedException {
this.loginType = loginType;
if (this.loginType != null)
if (result == null)
this.loginContent = null;
else
this.loginContent = masterKey.siteState(
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
}
@Nullable
public String getUrl() {
return url;
}
public void setUrl(@Nullable final String url) {
this.url = url;
}
public int getUses() {
return uses;
}
public Instant getLastUsed() {
return lastUsed;
}
public void use() {
uses++;
lastUsed = new Instant();
user.use();
}
}

View File

@ -1,172 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.*;
/**
* @author lhunath, 14-12-07
*/
public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileUser> {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPFileUser.class );
private final String fullName;
private final Collection<MPFileSite> sites = Sets.newHashSet();
@Nullable
private byte[] keyID;
private MPMasterKey.Version algorithmVersion;
private int avatar;
private MPResultType defaultType;
private ReadableInstant lastUsed;
public MPFileUser(final String fullName) {
this( fullName, null, MPMasterKey.Version.CURRENT );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion) {
this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion, final int avatar,
final MPResultType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = (keyID == null)? null: keyID.clone();
this.algorithmVersion = algorithmVersion;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
@Override
public String getFullName() {
return fullName;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Override
public int getAvatar() {
return avatar;
}
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public MPResultType getDefaultType() {
return defaultType;
}
public void setDefaultType(final MPResultType defaultType) {
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() {
return lastUsed;
}
public void use() {
lastUsed = new Instant();
}
public Iterable<MPFileSite> getSites() {
return sites;
}
@Override
public void addSite(final MPFileSite site) {
sites.add( site );
}
@Override
public void deleteSite(final MPFileSite site) {
sites.remove( site );
}
@Override
public Collection<MPFileSite> findSites(final String query) {
ImmutableList.Builder<MPFileSite> results = ImmutableList.builder();
for (final MPFileSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( site );
return results.build();
}
/**
* Performs an authentication attempt against the keyID for this user.
*
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull
@Override
public MPMasterKey authenticate(final char[] masterPassword)
throws MPIncorrectMasterPasswordException {
try {
key = new MPMasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
keyID = key.getKeyID( algorithmVersion );
else if (!Arrays.equals( key.getKeyID( algorithmVersion ), keyID ))
throw new MPIncorrectMasterPasswordException( this );
return key;
}
catch (final MPInvalidatedException e) {
throw logger.bug( e );
}
}
void save()
throws MPInvalidatedException {
MPFileUserManager.get().save( this, getMasterKey() );
}
@Override
public int compareTo(final MPFileUser o) {
int comparison = getLastUsed().compareTo( o.getLastUsed() );
if (comparison == 0)
comparison = getFullName().compareTo( o.getFullName() );
return comparison;
}
}

View File

@ -1,139 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.*;
import com.google.common.collect.*;
import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import java.io.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
*
* @author lhunath, 14-12-07
*/
public class MPFileUserManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPFileUserManager.class );
private static final MPFileUserManager instance;
static {
String rcDir = System.getenv( MPConstant.env_rcDir );
if (rcDir != null)
instance = create( new File( rcDir ) );
else
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
}
private final File userFilesDirectory;
public static MPFileUserManager get() {
MPUserManager.instance = instance;
return instance;
}
public static MPFileUserManager create(final File userFilesDirectory) {
return new MPFileUserManager( userFilesDirectory );
}
protected MPFileUserManager(final File userFilesDirectory) {
super( unmarshallUsers( userFilesDirectory ) );
this.userFilesDirectory = userFilesDirectory;
}
private static Iterable<MPFileUser> unmarshallUsers(final File userFilesDirectory) {
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
return ImmutableList.of();
}
return FluentIterable.from( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPFileUser>() {
@Nullable
@Override
public MPFileUser apply(@Nullable final File file) {
try {
return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
}
catch (final IOException e) {
logger.err( e, "Couldn't read user from: %s", file );
return null;
}
}
} ).filter( Predicates.notNull() );
}
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ), new File[0] ) );
}
@Override
public void deleteUser(final MPFileUser user) {
super.deleteUser( user );
// Remove deleted users.
File userFile = getUserFile( user );
if (userFile.exists() && !userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
}
/**
* Write the current user state to disk.
*/
public void save(final MPFileUser user, final MPMasterKey masterKey)
throws MPInvalidatedException {
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new OutputStreamWriter( new FileOutputStream( getUserFile( user ) ), Charsets.UTF_8 );
}
}.write( new MPFlatMarshaller().marshall( user, masterKey, MPMarshaller.ContentMode.PROTECTED ) );
}
catch (final IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
}
@Nonnull
private File getUserFile(final MPFileUser user) {
return new File( userFilesDirectory, user.getFullName() + ".mpsites" );
}
/**
* @return The location on the file system where the user models are stored.
*/
public File getPath() {
return userFilesDirectory;
}
}

View File

@ -1,139 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.base.*;
import com.google.common.io.CharStreams;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.*;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.joda.time.DateTime;
/**
* @author lhunath, 14-12-07
*/
public class MPFlatUnmarshaller implements MPUnmarshaller {
private static final Pattern[] unmarshallFormats = {
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
private static final Pattern colon = Pattern.compile( ":" );
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final File file)
throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.toString( reader ) );
}
}
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final String content) {
MPFileUser user = null;
byte[] keyID = null;
String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
MPResultType defaultType = MPResultType.DEFAULT;
//noinspection HardcodedLineSeparator
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
// Header delimitor.
if (line.startsWith( "##" ))
if (!headerStarted)
// Starts the header.
headerStarted = true;
else
// Ends the header.
user = new MPFileUser( fullName, keyID, MPMasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
// Comment.
else if (line.startsWith( "#" )) {
if (headerStarted && (user == null)) {
// In header.
Matcher headerMatcher = headerFormat.matcher( line );
if (headerMatcher.matches()) {
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
fullName = value;
else if ("Key ID".equalsIgnoreCase( name ))
keyID = CodeUtils.decodeHex( value );
else if ("Algorithm".equalsIgnoreCase( name ))
mpVersion = ConversionUtils.toIntegerNN( value );
else if ("Format".equalsIgnoreCase( name ))
importFormat = ConversionUtils.toIntegerNN( value );
else if ("Avatar".equalsIgnoreCase( name ))
avatar = ConversionUtils.toIntegerNN( value );
else if ("Passwords".equalsIgnoreCase( name ))
clearContent = "visible".equalsIgnoreCase( value );
else if ("Default Type".equalsIgnoreCase( name ))
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
}
}
}
// No comment.
else if (user != null) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
if (!siteMatcher.matches())
return null;
MPFileSite site;
switch (importFormat) {
case 0:
site = new MPFileSite( user, //
siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPFileSite.DEFAULT_COUNTER,
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
case 1:
site = new MPFileSite( user, //
siteMatcher.group( 7 ), siteMatcher.group( 8 ),
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
default:
throw new UnsupportedOperationException( "Unexpected format: " + importFormat );
}
user.addSite( site );
}
return Preconditions.checkNotNull( user, "No full header found in import file." );
}
}

View File

@ -1,81 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* @author lhunath, 14-12-16
*/
public abstract class MPSite {
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE;
public abstract String getSiteName();
public abstract void setSiteName(String siteName);
public abstract UnsignedInteger getSiteCounter();
public abstract void setSiteCounter(UnsignedInteger siteCounter);
public abstract MPResultType getResultType();
public abstract void setResultType(MPResultType resultType);
public abstract MPMasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(MPMasterKey.Version algorithmVersion);
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
@Nullable final String siteContent)
throws MPInvalidatedException {
return masterKey.siteResult(
getSiteName(), getSiteCounter(), keyPurpose, keyContext, getResultType(), siteContent, getAlgorithmVersion() );
}
public String loginFor(final MPMasterKey masterKey, final MPResultType loginType, @Nullable final String loginContent)
throws MPInvalidatedException {
return masterKey.siteResult(
getSiteName(), DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent, getAlgorithmVersion() );
}
@Override
public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite) obj).getSiteName() ));
}
@Override
public int hashCode() {
return Objects.hashCode( getSiteName() );
}
@Override
public String toString() {
return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
}
}

View File

@ -1,86 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Preconditions;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MPMasterKey;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2014-06-08
*/
public abstract class MPUser<S extends MPSite> {
@Nullable
protected MPMasterKey key;
public abstract String getFullName();
public boolean isMasterKeyAvailable() {
return key != null;
}
@Nonnull
public MPMasterKey getMasterKey() {
return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
}
public String exportKeyID()
throws MPInvalidatedException {
return CodeUtils.encodeHex( getMasterKey().getKeyID( getAlgorithmVersion() ) );
}
public abstract MPMasterKey.Version getAlgorithmVersion();
public int getAvatar() {
return 0;
}
public abstract void addSite(S site);
public abstract void deleteSite(S site);
public abstract Collection<S> findSites(String query);
@Nonnull
public abstract MPMasterKey authenticate(char[] masterPassword)
throws MPIncorrectMasterPasswordException;
@Override
public int hashCode() {
return Objects.hashCode( getFullName() );
}
@Override
public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
}
@Override
public String toString() {
return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
}
}

View File

@ -1,58 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.collect.*;
import com.lyndir.masterpassword.MPInvalidatedException;
import java.util.*;
/**
* @author lhunath, 14-12-05
*/
public abstract class MPUserManager {
private final Map<String, MPFileUser> usersByName = Maps.newHashMap();
static MPUserManager instance;
public static MPUserManager get() {
return instance;
}
protected MPUserManager(final Iterable<MPFileUser> users) {
for (final MPFileUser user : users)
usersByName.put( user.getFullName(), user );
}
public SortedSet<MPFileUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
}
public MPFileUser getUserNamed(final String fullName) {
return usersByName.get( fullName );
}
public void addUser(final MPFileUser user) {
usersByName.put( user.getFullName(), user );
}
public void deleteUser(final MPFileUser user) {
usersByName.remove( user.getFullName() );
}
}

View File

@ -1,13 +0,0 @@
plugins {
id 'java'
}
description = 'Master Password Test Suite'
dependencies {
compile project(':masterpassword-algorithm')
testCompile group: 'org.testng', name: 'testng', version:'6.8.5'
testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
}
test.useTestNG()

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
</parent>
<name>Master Password Test Suite</name>
<description>The standard test suite to ensure the Master Password algorithm is operating as it should</description>
<artifactId>masterpassword-tests</artifactId>
<packaging>jar</packaging>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<!-- TESTING -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1 +0,0 @@
../../../../../mpw_tests.xml

4
gradle.properties Normal file
View File

@ -0,0 +1,4 @@
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx1536M
android.enableD8.desugaring=true

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value />
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Lhunath" />
</component>
</project>

View File

@ -1,7 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="keyword" value="Copyright|License|WARRANTY" />
<option name="myName" value="GPLv3" />
<option name="notice" value="This file is part of &amp;#36;project.name.&#10;Copyright (c) &amp;#36;today.year.&#10;&#10;&amp;#36;project.name is free software: you can redistribute it and/or modify&#10;it under the terms of the GNU General Public License as published by&#10;the Free Software Foundation, either version 3 of the License, or&#10;(at your option) any later version.&#10;&#10;&amp;#36;project.name is distributed in the hope that it will be useful,&#10;but WITHOUT ANY WARRANTY; without even the implied warranty of&#10;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#10;GNU General Public License for more details.&#10;&#10;You can find a copy of the GNU General Public License in the&#10;LICENSE file. Alternatively, see &lt;http://www.gnu.org/licenses/&gt;." />
</copyright>
</component>

View File

@ -1,13 +0,0 @@
<component name="CopyrightManager">
<settings>
<module2copyright>
<element module="masterpassword" copyright="Master Password" />
</module2copyright>
<LanguageOptions name="__TEMPLATE__">
<option name="block" value="false" />
<option name="separateBefore" value="true" />
<option name="separateAfter" value="true" />
<option name="filler" value="=" />
</LanguageOptions>
</settings>
</component>

View File

@ -1,9 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="projectProfile" value="Lhunath" />
<option name="useProjectProfile" value="false" />
<option name="PROJECT_PROFILE" value="Lhunath" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/../../opal/pom.xml" />
</list>
</option>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="javax.annotation.Nullable" />
<option name="myDefaultNotNull" value="javax.annotation.Nonnull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
<component name="ThriftCompiler">
<compilers />
</component>
</project>

View File

@ -1,28 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Android" type="AndroidRunConfigurationType" factoryName="Android App">
<module name="android" />
<option name="DEPLOY" value="true" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="PREFERRED_AVD" value="" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="false" />
<option name="PREFERRED_AVD" value="" />
<option name="DEBUGGER_TYPE" value="Java" />
<Java />
<Profilers>
<option name="ENABLE_ADVANCED_PROFILING" value="false" />
<option name="SUPPORT_LIB_ENABLED" value="true" />
<option name="INSTRUMENTATION_ENABLED" value="true" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" />
<method />
</configuration>
</component>

View File

@ -1,16 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="GUI" type="Application" factoryName="Application" show_console_on_std_err="true">
<option name="MAIN_CLASS_NAME" value="com.lyndir.masterpassword.gui.GUI" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="ENABLE_SWING_INSPECTOR" value="false" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="masterpassword-gui" />
<envs />
<method />
</configuration>
</component>

View File

@ -1,29 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tests" type="TestNG" factoryName="TestNG" show_console_on_std_err="true">
<module name="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="SUITE_NAME" value="" />
<option name="PACKAGE_NAME" value="com.lyndir.masterpassword" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="GROUP_NAME" value="" />
<option name="TEST_OBJECT" value="PACKAGE" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../core/java/tests" />
<option name="OUTPUT_DIRECTORY" value="" />
<option name="ANNOTATION_TYPE" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" />
</option>
<option name="USE_DEFAULT_REPORTERS" value="false" />
<option name="PROPERTIES_FILE" value="" />
<envs />
<properties />
<listeners />
<method />
</configuration>
</component>

View File

@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="masterpassword" pattern="com.lyndir.masterpassword..*" />
</component>

View File

@ -1,7 +0,0 @@
To build a release distribution:
STORE_PW=$(mpw masterpassword.keystore) KEY_PW=$(mpw masterpassword-android) gradle assembleRelease
Note:
- At the time of writing, Android does not build with JDK 9+. As such, the above command must be ran with JAVA_HOME pointing to JDK 7-8.

View File

@ -1,34 +0,0 @@
allprojects {
//apply plugin: 'findbugs'
group = 'com.lyndir.masterpassword'
version = 'GIT-SNAPSHOT'
tasks.withType(JavaCompile) {
sourceCompatibility = '1.7'
targetCompatibility = '1.7'
}
tasks.withType(FindBugs) {
reports {
xml.enabled false
html.enabled true
}
}
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath group: 'com.android.tools.build', name: 'gradle', version: '2.3.2'
}
}
subprojects {
repositories {
mavenCentral()
maven { url 'http://maven.lyndir.com' }
}
}

View File

@ -1,5 +0,0 @@
<root>
<item name='com.google.common.base.Preconditions T checkNotNull(T, java.lang.Object) 1'>
<annotation name='org.jetbrains.annotations.NonNls' />
</item>
</root>

View File

@ -1,5 +0,0 @@
<root>
<item name='com.google.common.io.Resources java.net.URL getResource(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NonNls' />
</item>
</root>

View File

@ -1,5 +0,0 @@
<root>
<item name='org.testng.Assert void assertEquals(java.lang.String, java.lang.String, java.lang.String) 2'>
<annotation name='org.jetbrains.annotations.NonNls' />
</item>
</root>

View File

@ -1 +0,0 @@
org.gradle.jvmargs=-Xmx1536M

Binary file not shown.

View File

@ -1,31 +0,0 @@
class Mpw < Formula
homepage "http://masterpasswordapp.com"
url "https://ssl.masterpasswordapp.com/mpw-2.1-cli4-0-gf6b2287.tar.gz"
sha1 "036b3d8f4bd6f0676ae16e7e9c3de65f6030874f"
version "2.1-cli4"
depends_on "automake" => :build
depends_on "autoconf" => :build
depends_on "openssl"
resource "libscrypt" do
url "http://masterpasswordapp.com/libscrypt-b12b554.tar.gz"
sha1 "ee871e0f93a786c4e3622561f34565337cfdb815"
end
def install
resource("libscrypt").stage buildpath/"lib/scrypt"
touch "lib/scrypt/.unpacked"
ENV["targets"] = "mpw mpw-tests"
system "./build"
system "./mpw-tests"
bin.install "mpw"
end
test do
assert_equal "Jejr5[RepuSosp",
shell_output("mpw -u 'Robert Lee Mitchell' -P 'banana colored duckling' masterpasswordapp.com").strip
end
end

View File

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.lhunath</groupId>
<artifactId>lyndir</artifactId>
<version>1.22</version>
</parent>
<name>Master Password</name>
<description>A Java implementation of the Master Password algorithm.</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>masterpassword-tests</module>
<module>masterpassword-algorithm</module>
<module>masterpassword-model</module>
<module>masterpassword-gui</module>
</modules>
<profiles>
<profile>
<id>release</id>
<modules>
<module>masterpassword-android</module>
</modules>
</profile>
<profile>
<id>mod:android</id>
<modules>
<module>masterpassword-android</module>
</modules>
</profile>
</profiles>
<!-- REMOTE ARTIFACT REPOSITORIES -->
<repositories>
<repository>
<id>lyndir</id>
<name>Lyndir Repository</name>
<url>http://maven.lyndir.com</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
</repository>
</repositories>
</project>

View File

@ -1,26 +0,0 @@
rootProject.name = 'masterpassword'
def local = new Properties();
try {
local.load(file('local.properties').newDataInputStream())
} catch (FileNotFoundException ignored) {
}
include 'masterpassword-algorithm'
project(':masterpassword-algorithm').projectDir = new File( '../core/java/algorithm' )
include 'masterpassword-model'
project(':masterpassword-model').projectDir = new File( '../core/java/model' )
include 'masterpassword-tests'
project(':masterpassword-tests').projectDir = new File( '../core/java/tests' )
include 'masterpassword-gui'
project(':masterpassword-gui').projectDir = new File( '../platform-independent/gui-java' )
if (local.containsKey('sdk.dir')) {
include 'masterpassword-android'
project(':masterpassword-android').projectDir = new File( '../platform-android' )
} else {
logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." )
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -1,5 +1,6 @@
#Mon Sep 23 12:55:35 EDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

40
homebrew-mpw.rb Normal file
View File

@ -0,0 +1,40 @@
class Mpw < Formula
desc "Stateless/deterministic password and identity manager"
homepage "https://masterpassword.app/"
url "https://masterpassword.app/mpw-2.6-cli-5-0-g344771db.tar.gz"
version "2.6-cli-5"
sha256 "954c07b1713ecc2b30a07bead9c11e6204dd774ca67b5bdf7d2d6ad1c4eec170"
revision 1
head "https://gitlab.com/MasterPassword/MasterPassword.git"
bottle do
cellar :any
sha256 "46677cf8649983d5b77103d2ca56d9ad3697808ecc406f626a3462a089f932da" => :high_sierra
sha256 "19bf22915b3c534ad3ee6f1dfc20f142d53ae6c0c88757ae2632b7b1daa6667f" => :sierra
sha256 "7090c3d31289d2ac5529bd0a6bae2632a36ba7fcd4bb7974248bb36a15f67c7e" => :el_capitan
end
option "without-json-c", "Disable JSON configuration support"
option "without-ncurses", "Disable colorized identicon support"
depends_on "libsodium"
depends_on "json-c" => :recommended
depends_on "ncurses" => :recommended
def install
cd "platform-independent/cli-c" if build.head?
ENV["targets"] = "mpw"
ENV["mpw_json"] = build.with?("json-c") ? "1" : "0"
ENV["mpw_color"] = build.with?("ncurses") ? "1" : "0"
system "./build"
system "./mpw-cli-tests"
bin.install "mpw"
end
test do
assert_equal "Jejr5[RepuSosp",
shell_output("#{bin}/mpw -q -Fnone -u 'Robert Lee Mitchell' -M 'banana colored duckling' -tlong -c1 -a3 'masterpasswordapp.com'").strip
end
end

451
lib/bin/build_lib Executable file
View File

@ -0,0 +1,451 @@
#!/usr/bin/env bash
#
# Your build script should simply source this script, optionally override any build hooks and then invoke `build`.
# The build product should be available under `build-<platform>~/out`, under the library path.
#
# Hook lifecycle:
# - build
# - initialize
# - needs
# - clean & exit (only if script was ran with "clean" argument)
# - check & exit (only if target has already been successfully built)
# - prepare
# - create
# - config
# - target
# - prepare
# - configure
# - build
# - finalize
# - merge
# - clean
#
# You can override any of these hooks to provide a custom implementation or call their underscore variant to delegate to the default implementation.
# For example:
# target_prepare() { make -s distclean; }
# target_configure() { _target_configure "$@" --enable-minimal; }
#
set -e
PATH+=:/usr/local/bin
# needs <binary> ...
#
# Utility for ensuring all tools needed by the script are installed prior to starting.
#
needs() { _needs "$@"; }
_needs() {
local failed=0
for spec; do
IFS=: read pkg tools <<< "$spec"
IFS=, read -a tools <<< "${tools:-$pkg}"
for tool in "${tools[@]}"; do
hash "$tool" 2>/dev/null && continue 2
done
echo >&2 "Missing: $pkg. Please install this package."
(( failed++ ))
done
return $failed
}
# initialize <prefix> <platform>
#
# The build script invokes this once prior to all other actions.
#
initialize() { _initialize "$@"; }
_initialize() {
initialize_needs "$@"
}
# initialize_needs <prefix> <platform>
#
# Check if all tools required to configure and build for the platform are available.
#
# By default, this will check for:
# - Windows: MSBuild
# - Other: `libtool` (for libtoolize), `automake` (for aclocal), `autoconf` (for autoreconf) and make
#
initialize_needs() { _initialize_needs "$@"; }
_initialize_needs() {
if [[ $platform = windows ]]; then
needs cmd
for dir in "$VSINSTALLDIR" "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/..; do
dir=$( [[ $dir ]] && cd "$dir" && [[ -e "Common7/Tools/VsMSBuildCmd.bat" ]] && cygpath -w "$PWD" ) && \
export VSINSTALLDIR=$dir && echo "Using MSBuild: $VSINSTALLDIR" && return
done
echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'. See https://visualstudio.microsoft.com/downloads/?q=build+tools"
return 1
else
needs libtool:libtoolize,glibtoolize automake autoconf make
fi
}
# clean <prefix> <platform>
#
# Fully clean up the library code, restoring it to a pristine state.
#
# By default, this will:
# - Windows: `msbuild /t:Clean`, or
# - Makefile: run `make distclean`, or
# - GIT: `git clean -fdx`
#
# Finally, it will wipe the prefix.
#
clean() { _clean "$@"; }
_clean() {
if [[ $platform = windows ]]; then
PATH="$(cygpath "$VSINSTALLDIR")/Common7/Tools:$PATH" \
cmd /v /c 'VsMSBuildCmd && for %s in (*.sln) do msbuild /t:Clean %s'
elif [[ -e Makefile ]] && make -s distclean; then :
elif [[ -e .git ]] && git clean -fdx; then :
fi
rm -rf "$prefix"
}
# prepare <prefix> <platform> [ <arch:host> ... ]
#
# Initialize the prefix in anticipation for building the <arch>s on this machine.
# The build script invokes this once prior to building each of its targets.
#
prepare() { _prepare "$@"; }
_prepare() {
prepare_create "$@"
prepare_config "$@"
}
# prepare_create <prefix> <platform> [ <arch:host> ... ]
#
# Perform any necessary clean-up of the library code prior to building.
#
# By default, this will wipe the build configuration and re-create the prefix.
# TODO: Should this differ from clean()?
#
prepare_create() { _prepare_create "$@"; }
_prepare_create() {
local prefix=$1 platform=$2; shift 2
if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s distclean || git clean -fdx
fi
rm -rf "$prefix"
install -d "$prefix/out"
}
# prepare_config <prefix> <platform> [ <arch:host> ... ]
#
# Generate build solution for configuring a build on this machine.
# The <prefix> has been newly created.
#
# TODO: cmake support?
# By default, this will:
# - Windows: do nothing
# - Other: run `autoreconf`.
#
prepare_config() { _prepare_config "$@"; }
_prepare_config() {
local prefix=$1 platform=$2; shift 2
[[ -e "$prefix/out/.prepared" ]] && return
if [[ $platform = windows ]]; then :
else
# autoreconf installs a useless INSTALL documentation stub that can overwrite repo docs.
[[ -e INSTALL ]] && mv INSTALL{,~}
autoreconf --verbose --install --force 2> >(sed 's/^\([^:]*\):[0-9]\{1,\}: /\1: /')
[[ -e INSTALL~ ]] && mv INSTALL{~,}
fi
touch "$prefix/out/.prepared"
}
# target <prefix> <platform> <arch> <host>
#
# Build the library to the <arch> binary for the <host> architecture on <platform> into the given <prefix>.
# The build script invokes this function when it's ready to build the library's code.
#
target() { _target "$@"; }
_target() {
target_prepare "$@"
target_configure "$@"
target_build "$@"
}
# target_prepare <prefix> <platform> <arch> <host>
#
# Any build-related work to be done in the prefix prior to building.
#
# By default, this will:
# - Windows: do nothing
# - macOS/iOS: Discover SDKROOT & build flags
# - Android: Prepare an NDK toolchain & build flags
# - Makefile: run `make clean`
#
target_prepare() { _target_prepare "$@"; }
_target_prepare() {
local prefix=$1 platform=$2 arch=$3 host=$4; shift 3
case "$platform" in
'windows')
;;
'macos')
SDKROOT="$(xcrun --show-sdk-path --sdk macosx)"
export PATH="$(xcrun --show-sdk-platform-path --sdk macosx)/usr/bin:$PATH"
export CPPFLAGS="-arch $host -flto -O2 -g -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} $CPPFLAGS"
export LDFLAGS="-arch $host -flto -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} $LDFLAGS"
;;
'ios')
case "$arch" in
*'arm'*)
SDKROOT="$(xcrun --show-sdk-path --sdk iphoneos)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphoneos)/usr/bin:$PATH"
export CPPFLAGS="-arch $host -mthumb -fembed-bitcode -flto -O2 -g -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $CPPFLAGS"
export LDFLAGS="-arch $host -mthumb -fembed-bitcode -flto -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
;;
*)
SDKROOT="$(xcrun --show-sdk-path --sdk iphonesimulator)"
export PATH="$(xcrun --show-sdk-platform-path --sdk iphonesimulator)/usr/bin:$PATH"
export CPPFLAGS="-arch $host -flto -O2 -g -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $CPPFLAGS"
export LDFLAGS="-arch $host -flto -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} $LDFLAGS"
;;
esac
;;
'android')
[[ -x $ANDROID_NDK_HOME/build/ndk-build ]] || { echo >&2 "Android NDK not found. Please set ANDROID_NDK_HOME."; return 1; }
SDKROOT="$prefix/$arch/ndk"
# Platform 21 is lowest that supports x86_64
"$ANDROID_NDK_HOME/build/tools/make-standalone-toolchain.sh" --force --install-dir="$SDKROOT" --platform='android-21' --arch="$arch"
export PATH="$SDKROOT/bin:$PATH"
export CPPFLAGS="-O2 -g $CPPFLAGS"
export LDFLAGS="-avoid-version $LDFLAGS"
export CC='clang'
;;
*)
case "$arch" in
x86)
export CPPFLAGS="-m32 $CPPFLAGS" LDFLAGS="-m32 $LDFLAGS"
;;
x86_64)
export CPPFLAGS="-m64 $CPPFLAGS" LDFLAGS="-m64 $LDFLAGS"
;;
esac
;;
esac
if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean
fi
}
# target_configure <prefix> <platform> <arch> <host> [ <args> ... ]
#
# Configure the library for building the target. This generates the compiler configuration.
#
# By default, this will:
# - Windows: do nothing
# - Other: run `./configure --host=<host> --prefix=<prefix>/<arch> <args>`.
#
# Some platform-specific configure arguments will be passed in as well.
# --enable-pic --disable-pie to ensure the resulting library can be linked again.
#
target_configure() { _target_configure "$@"; }
_target_configure() {
local prefix=$1 platform=$2 arch=$3 host=$4; shift 4
local build=
[[ -x config.guess ]] && build=$(./config.guess)
[[ -x build-aux/config.guess ]] && build=$(build-aux/config.guess)
case "$platform" in
'windows')
# doesn't use ./configure
return 0
;;
'ios'|'macos')
host+=-apple
set -- --enable-static --disable-shared "$@"
;;
'android')
host=( "$SDKROOT/$host"*-android* ) host=${host##*/}
set -- --disable-static --enable-shared --with-sysroot="$SDKROOT/sysroot" "$@"
;;
*)
set -- --enable-static --disable-shared "$@"
;;
esac
./configure ${build:+--build="$build"} ${host:+--host="$host"} --prefix="$prefix/$arch" --enable-pic --disable-pie "$@"
}
# target_build <prefix> <platform> <arch> <host>
#
# Build the library code for the target. This runs the compiler per the previous configuration.
#
# By default, this will:
# - Windows: run `msbuild /t:Rebuild /p:Configuration:Release;Platform=<host>`
# - Other: run `make check install`.
#
target_build() { _target_build "$@"; }
_target_build() {
local prefix=$1 platform=$2 arch=$3 host=$4; shift 4
if [[ $platform = windows ]]; then
if [[ -e CMakeLists.txt ]]; then
( projdir=$PWD; mkdir -p "$prefix/$arch/"; cd "$prefix/$arch/"
PATH="$(cygpath "$VSINSTALLDIR")/Common7/Tools:$(cygpath "$VSINSTALLDIR")/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH" \
cmd /v /c "$(printf 'VsMSBuildCmd && cmake -A %s %s && for %%s in (*.sln) do msbuild /m /t:Rebuild /p:Configuration=Release;Platform=%s;OutDir=. %%s' \
"$host" "$(cygpath -w "$projdir")" "$host")" )
else
PATH="$(cygpath "$VSINSTALLDIR")/Common7/Tools:$PATH" \
cmd /v /c "$(printf 'VsMSBuildCmd && for %%s in (*.sln) do msbuild /m /t:Rebuild /p:Configuration=Release;Platform=%s;OutDir=%s %%s' \
"$host" "$(cygpath -w "${prefix##$PWD/}/$arch/")")"
fi
else
local cores=$(getconf NPROCESSORS_ONLN 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null ||:)
#make -j"${cores:-3}" check install # TODO: libjson-c breaks on parallel build for check and install
#make check install # TODO: libjson-c has a failing test atm
make install
fi
}
# finalize <prefix> <platform> [ <arch> ... ]
#
# Prepare the final build product.
# The build script invokes this once after a successful build of all targets.
#
finalize() { _finalize "$@"; }
_finalize() {
finalize_merge "$@"
finalize_clean "$@"
}
# finalize_merge <prefix> <platform> [ <arch> ... ]
#
# Merge all targets into a product the application can use, available at `<prefix>/out`.
#
# By default, this will copy the headers to `<prefix>/out/include`, install libraries into `<prefix>/out/lib` and mark the output product as successful.
#
finalize_merge() { _finalize_merge "$@"; }
_finalize_merge() {
local prefix=$1 platform=$2; shift 2
local archs=( "$@" )
[[ -e "$prefix/$archs/include" ]] && cp -a -- "$prefix/$archs/include" "$prefix/out/"
install -d "$prefix/out/lib"
case "$platform" in
'linux')
for arch in "${archs[@]}"; do
install -d "$prefix/out/lib/$arch"
install -p "$prefix/$arch/lib/"*.a "$prefix/out/lib/$arch/"
done
;;
'windows')
for arch in "${archs[@]}"; do
install -d "$prefix/out/lib/$arch"
install -p "$prefix/$arch/"*.lib "$prefix/out/lib/$arch/"
done
;;
'macos'|'ios')
for arch in "${archs[@]}"; do
install -d "$prefix/out/lib/$arch"
install -p "$prefix/$arch/lib/"*.a "$prefix/out/lib/$arch/"
done
local libs=( "$prefix/out/lib/"*/* )
lipo -create "${libs[@]}" -output "$prefix/out/lib/${libs##*/}"
;;
'android')
for arch in "${archs[@]}"; do
local abi=$arch
case "$arch" in
'arm') abi='armeabi-v7a' ;;
'arm64') abi='arm64-v8a' ;;
esac
install -d "$prefix/out/lib/$abi"
install -p "$prefix/$arch/lib/"*.so "$prefix/out/lib/$abi/"
done
;;
esac
touch "$prefix/out/.success"
}
# finalize_clean <prefix> [ <arch> ... ]
#
# Clean up the library after a successful build (eg. housekeeping of temporary files).
#
# By default, this will run `make clean`.
#
finalize_clean() { _finalize_clean "$@"; }
_finalize_clean() {
if [[ $platform = windows ]]; then :
else
[[ ! -e Makefile ]] || make -s clean
fi
}
# build <name> [<platform>]
#
# Build the library <name> (found at ../<name>) for platform <platform> (or "host" if unspecified).
#
build() { _build "$@"; }
_build() {
local name=$1 platform=${2:-host}
local path="../$name"
[[ $path = /* ]] || path="${BASH_SOURCE%/*}/$path"
cd "$path"
if [[ $platform = host ]]; then
case "$(uname -s)" in
'Darwin') platform='macos' archs=( "$(uname -m)" ) ;;
esac
fi
if (( ! ${#archs[@]} )); then
case "$platform" in
'macos') archs=( 'x86_64' ) ;;
'ios') archs=( 'x86:i386' 'x86_64' 'armv7' 'armv7s' 'arm64' ) ;;
'android') archs=( 'arm' 'arm64:aarch64' 'x86:i686' 'x86_64' ) ;;
'windows') archs=( 'x86:Win32' 'x86_64:x64' ) ;;
*) archs=( 'x86:i686' 'x86_64' ) ;;
esac
fi
local prefix="$PWD/build-$platform~"
echo
echo " # $name ($platform: ${archs[*]}) into $prefix ..."
initialize "$prefix" "$platform"
# "clean" argument wipes the lib clean and exits. If .success exists in prefix output, skip build.
if [[ ${BASH_ARGV[@]:(-1)} = clean ]]; then
clean "$prefix" "$platform"
exit
elif [[ -e "$prefix"/out/.success ]]; then
echo >&2 "Skipping build for $platform: output product already built successfully."
exit
fi
# Prepare the output location and build configuration.
prepare "$prefix" "$platform" "${archs[@]}"
# Repeat the build for each individual architecture.
for arch in "${archs[@]}"; do (
local host=${arch#*:} arch=${arch%%:*}
echo
echo " # $name [$platform: $arch ($host)] ..."
target "$prefix" "$platform" "$arch" "$host"
); done
finalize "$prefix" "$platform" "${archs[@]%%:*}"
}

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c android

8
lib/bin/build_libjson-c-ios Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c ios

8
lib/bin/build_libjson-c-linux Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c linux

8
lib/bin/build_libjson-c-macos Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c macos

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
autoreconf() {
command autoreconf -Iautoconf-archive/m4 "$@"
}
build libjson-c windows

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
build libsodium android

4
lib/bin/build_libsodium-ios Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
build libsodium ios

4
lib/bin/build_libsodium-linux Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
build libsodium linux

4
lib/bin/build_libsodium-macos Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
build libsodium macos

View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/build_lib"
finalize_merge() {
local prefix=$1 platform=$2; shift 2
local archs=( "$@" )
cp -a "src/libsodium/include" "$prefix/out"
_finalize_merge "$prefix" "$platform" "${archs[@]}"
}
build libsodium windows

1
lib/libjson-c Submodule

@ -0,0 +1 @@
Subproject commit 3df1f98b4ab52f271dba5e13ec59cf4d1d093e1a

1
lib/libsodium Submodule

@ -0,0 +1 @@
Subproject commit 850edc1175c78ed72124cfbef073c7ecc655c476

View File

@ -0,0 +1,28 @@
project( mpw-core C )
cmake_minimum_required( VERSION 3.0.0 )
add_library( mpw SHARED
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/base64.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/aes.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v0.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v1.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v2.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v3.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-types.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-util.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-marshal-util.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-marshal.c"
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-jni.c" )
add_library( sodium SHARED IMPORTED )
set_target_properties( sodium PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/lib/${ANDROID_ABI}/libsodium.so" )
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libsodium/build-android~/out/include" )
target_compile_definitions( mpw PRIVATE -DMPW_SODIUM=1 )
target_link_libraries( mpw PRIVATE sodium )
add_library( json-c SHARED IMPORTED )
set_target_properties( json-c PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/lib/${ANDROID_ABI}/libjson-c.so" )
target_include_directories( mpw PRIVATE "${PROJECT_SOURCE_DIR}/../lib/libjson-c/build-android~/out/include" )
target_compile_definitions( mpw PRIVATE -DMPW_JSON=1 )
target_link_libraries( mpw PRIVATE json-c )

View File

@ -1,46 +1,78 @@
apply plugin: 'com.android.application'
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 25
buildToolsVersion '25.0.0'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
compileSdkVersion 28
defaultConfig {
applicationId 'com.lyndir.masterpassword'
minSdkVersion 19
targetSdkVersion 25
versionCode 20501
versionName '2.5.1'
minSdkVersion 24
targetSdkVersion 28
versionCode 20701
versionName '2.7.1'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
sourceSets {
main {
jniLibs.srcDirs "$rootDir/lib/libsodium/build-android~/out/lib",
"$rootDir/lib/libjson-c/build-android~/out/lib"
}
}
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW=$(mpw masterpassword-android) gradle assembleRelease
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle masterpassword-android:assembleRelease
signingConfigs {
release {
storeFile file( 'masterpassword.keystore' )
storePassword System.getenv( 'STORE_PW' )
keyAlias 'masterpassword-android'
keyPassword System.getenv( 'KEY_PW' )
keyPassword System.getenv( 'KEY_PW_ANDROID' )
}
}
buildTypes {
release {
if (System.getenv( 'STORE_PW' ) != null)
if (System.getenv( 'KEY_PW_ANDROID' ) != null)
signingConfig signingConfigs.release
}
}
}
dependencies {
compile project( ':masterpassword-algorithm' )
compile project( ':masterpassword-tests' )
api project( ':masterpassword-algorithm' )
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
compile group: 'org.slf4j', name: 'slf4j-android', version:'1.7.13-underscore'
compile group: 'com.jakewharton', name: 'butterknife', version:'8.5.1'
annotationProcessor group: 'com.jakewharton', name: 'butterknife-compiler', version:'8.5.1'
compile files( 'libs/scrypt-1.4.0-native.jar' )
implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.13-underscore'
implementation group: 'com.jakewharton', name: 'butterknife', version: '10.2.0'
annotationProcessor group: 'com.jakewharton', name: 'butterknife-compiler', version: '10.2.0'
}
preBuild {
dependsOn task( type: Exec, 'build_libsodium-android', {
commandLine 'bash', "$rootDir/lib/bin/build_libsodium-android"
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
dependsOn task( type: Exec, 'build_libjson-c-android', {
commandLine 'bash', "$rootDir/lib/bin/build_libjson-c-android"
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
}
clean {
dependsOn task( type: Exec, 'clean_libsodium-android', {
commandLine 'bash', "$rootDir/lib/bin/build_libsodium-android", 'clean'
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
dependsOn task( type: Exec, 'clean_libjson-c-android', {
commandLine 'bash', "$rootDir/lib/bin/build_libjson-c-android", 'clean'
environment 'ANDROID_NDK_HOME', android.ndkDirectory
} )
}

View File

@ -1,145 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
</parent>
<name>Master Password Android</name>
<description>An Android application to the Master Password algorithm</description>
<artifactId>masterpassword-android</artifactId>
<packaging>apk</packaging>
<!-- BUILD CONFIGURATION -->
<build>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<zipalign>
<verbose>true</verbose>
<skip>false</skip>
</zipalign>
<sdk>
<platform>21</platform>
</sdk>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
<executions>
<execution>
<id>manifest-update</id>
<phase>process-resources</phase>
<goals>
<goal>manifest-update</goal>
</goals>
<configuration>
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>signing</id>
<goals>
<goal>sign</goal>
</goals>
<phase>package</phase>
<inherited>true</inherited>
<configuration>
<archiveDirectory />
<includes>
<include>target/*.apk</include>
</includes>
<keystore>release.jks</keystore>
<storepass>${env.PASSWORD}</storepass>
<keypass>${env.PASSWORD}</keypass>
<alias>masterpassword-android</alias>
<arguments>
<argument>-sigalg</argument><argument>MD5withRSA</argument>
<argument>-digestalg</argument><argument>SHA1</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-tests</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->
<dependency>
<groupId>com.jakewharton</groupId>
<artifactId>butterknife</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-android</artifactId>
<version>1.7.13-underscore</version>
</dependency>
<dependency>
<groupId>android</groupId>
<artifactId>android</artifactId>
<version>5.0.1_r2</version>
</dependency>
<dependency>
<groupId>com.lambdaworks</groupId>
<artifactId>scrypt</artifactId>
<version>1.4.0-android</version>
<type>jar</type>
<classifier>native</classifier>
</dependency>
</dependencies>
</project>

View File

@ -18,7 +18,7 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import android.app.*;
import android.content.*;
@ -35,11 +35,12 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.Executors;
import javax.annotation.Nullable;
@ -49,13 +50,16 @@ public class EmergencyActivity extends Activity {
private static final Logger logger = Logger.get( EmergencyActivity.class );
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
private static final int PASSWORD_NOTIFICATION = 0;
public static final int CLIPBOARD_CLEAR_DELAY = 20 /* s */ * MPConstant.MS_PER_S;
private static final int CLIPBOARD_CLEAR_DELAY = 20 /* s */ * MPConstants.MS_PER_S;
private final Preferences preferences = Preferences.get( this );
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList<MPMasterKey.Version> allVersions = ImmutableList.copyOf( MPMasterKey.Version.values() );
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
Executors.newSingleThreadExecutor() );
private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf(
MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList<MPAlgorithm.Version> allVersions = ImmutableList.copyOf( MPAlgorithm.Version.values() );
@Nullable
private MPMasterKey masterKey;
@BindView(R.id.progressView)
@ -96,6 +100,7 @@ public class EmergencyActivity extends Activity {
private int id_userName;
private int id_masterPassword;
@Nullable
private String sitePassword;
public static void start(final Context context) {
@ -125,7 +130,7 @@ public class EmergencyActivity extends Activity {
siteNameField.addTextChangedListener( new ValueChangedListener() {
@Override
void update() {
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
updateSitePassword();
}
} );
@ -150,11 +155,22 @@ public class EmergencyActivity extends Activity {
updateSitePassword();
}
} );
siteCounterButton.setOnLongClickListener( new View.OnLongClickListener() {
@Override
public boolean onLongClick(final View v) {
if (UnsignedInteger.valueOf( siteCounterButton.getText().toString() ).equals( UnsignedInteger.ONE ))
return false;
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
updateSitePassword();
return true;
}
} );
siteVersionButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls")
MPMasterKey.Version siteVersion =
MPAlgorithm.Version siteVersion =
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
preferences.setDefaultVersion( siteVersion );
siteVersionButton.setTag( siteVersion );
@ -173,13 +189,13 @@ public class EmergencyActivity extends Activity {
}
} );
fullNameField.setTypeface( Res.get( this ).exo_Thin );
fullNameField.setTypeface( Res.get( this ).exo_Thin() );
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
masterPasswordField.setTypeface( Res.get( this ).sourceCodePro_ExtraLight );
masterPasswordField.setTypeface( Res.get( this ).sourceCodePro_ExtraLight() );
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
siteNameField.setTypeface( Res.get( this ).exo_Regular );
siteNameField.setTypeface( Res.get( this ).exo_Regular() );
siteNameField.setPaintFlags( siteNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
sitePasswordField.setTypeface( Res.get( this ).sourceCodePro_Black );
sitePasswordField.setTypeface( Res.get( this ).sourceCodePro_Black() );
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@ -211,7 +227,7 @@ public class EmergencyActivity extends Activity {
protected void onResume() {
super.onResume();
// FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
// FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
fullNameField.setText( preferences.getFullName() );
rememberFullNameField.setChecked( preferences.isRememberFullName() );
@ -221,10 +237,10 @@ public class EmergencyActivity extends Activity {
MPResultType defaultResultType = preferences.getDefaultResultType();
resultTypeButton.setTag( defaultResultType );
resultTypeButton.setText( defaultResultType.getShortName() );
MPMasterKey.Version defaultVersion = preferences.getDefaultVersion();
MPAlgorithm.Version defaultVersion = preferences.getDefaultVersion();
siteVersionButton.setTag( defaultVersion );
siteVersionButton.setText( defaultVersion.name() );
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
siteCounterButton.setText( MessageFormat.format( "{0}", UnsignedInteger.ONE ) );
if (TextUtils.isEmpty( fullNameField.getText() ))
fullNameField.requestFocus();
@ -254,8 +270,8 @@ public class EmergencyActivity extends Activity {
}
private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
String fullName = fullNameField.getText().toString();
char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
if ((id_userName == fullName.hashCode())
&& (id_masterPassword == Arrays.hashCode( masterPassword )))
if (masterKey != null)
@ -283,7 +299,7 @@ public class EmergencyActivity extends Activity {
final String siteName = siteNameField.getText().toString();
final MPResultType type = (MPResultType) resultTypeButton.getTag();
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
final MPMasterKey.Version version = (MPMasterKey.Version) siteVersionButton.getTag();
final MPAlgorithm.Version version = (MPAlgorithm.Version) siteVersionButton.getTag();
if ((masterKey == null) || siteName.isEmpty() || (type == null)) {
sitePasswordField.setText( "" );
@ -300,7 +316,8 @@ public class EmergencyActivity extends Activity {
@Override
public void run() {
try {
sitePassword = masterKey.siteResult( siteName, counter, MPKeyPurpose.Authentication, null, type, null, version );
sitePassword = masterKey.siteResult(
siteName, version, counter, MPKeyPurpose.Authentication, null, type, null );
runOnUiThread( new Runnable() {
@Override
@ -310,59 +327,59 @@ public class EmergencyActivity extends Activity {
}
} );
}
catch (final MPInvalidatedException ignored) {
catch (final MPKeyUnavailableException ignored) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
}
catch (final RuntimeException e) {
catch (final MPAlgorithmException e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating site password." );
throw e;
}
}
} );
}
public void integrityTests(final View view) {
if (masterKey != null)
masterKey = null;
TestActivity.startNoSkip( this );
}
public void copySitePassword(final View view) {
final String currentSitePassword = sitePassword;
if (TextUtils.isEmpty( currentSitePassword ))
return;
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
if (clipboardManager == null)
return;
String title = strf( "Password for %s", siteNameField.getText() );
String title = strf( "Password for %s", siteNameField.getText() );
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
.setContentText( "Paste the password into your app." )
.setSmallIcon( R.drawable.icon )
.setAutoCancel( true );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
.setCategory( Notification.CATEGORY_RECOMMENDATION )
.setLocalOnly( true );
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
if (notificationManager != null) {
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
.setContentText(
"Paste the password into your app." )
.setSmallIcon( R.drawable.icon )
.setAutoCancel( true );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
.setCategory( Notification.CATEGORY_RECOMMENDATION )
.setLocalOnly( true );
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
}
final Timer timer = new Timer();
timer.schedule( new TimerTask() {
@Override
public void run() {
ClipData clip = clipboardManager.getPrimaryClip();
for (int i = 0; i < clip.getItemCount(); ++i)
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
if (currentSitePassword.contentEquals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
clipboardManager.setPrimaryClip( EMPTY_CLIP );
break;
}
notificationManager.cancel( PASSWORD_NOTIFICATION );
if (notificationManager != null)
notificationManager.cancel( PASSWORD_NOTIFICATION );
timer.cancel();
}
}, CLIPBOARD_CLEAR_DELAY );

View File

@ -19,7 +19,9 @@
package com.lyndir.masterpassword;
/**
* @author lhunath, 2017-09-21
* @author lhunath, 2018-06-10
*/
public class MPInvalidatedException extends Exception {
public class MPConstants {
public static final int MS_PER_S = 1000;
}

View File

@ -22,8 +22,10 @@ import android.os.Handler;
import android.os.Looper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import javax.annotation.Nonnull;
/**
@ -33,7 +35,7 @@ public class MainThreadExecutor extends AbstractExecutorService {
private final Handler mHandler = new Handler( Looper.getMainLooper() );
private final Set<Runnable> commands = Sets.newLinkedHashSet();
private boolean shutdown;
private boolean shutdown;
@Override
public void execute(final Runnable command) {
@ -63,6 +65,7 @@ public class MainThreadExecutor extends AbstractExecutorService {
shutdown = true;
}
@Nonnull
@Override
public List<Runnable> shutdownNow() {
shutdown = true;

View File

@ -32,15 +32,15 @@ import javax.annotation.Nullable;
*/
public final class Preferences {
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
private static final String PREF_NATIVE_KDF = "nativeKDF";
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
private static final String PREF_MASK_PASSWORD = "maskPassword";
private static final String PREF_FULL_NAME = "fullName";
private static final String PREF_RESULT_TYPE = "resultType";
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
private static Preferences instance;
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
private static final String PREF_NATIVE_KDF = "nativeKDF";
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
private static final String PREF_MASK_PASSWORD = "maskPassword";
private static final String PREF_FULL_NAME = "fullName";
private static final String PREF_RESULT_TYPE = "resultType";
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
private static Preferences instance;
private Context context;
@Nullable
@ -86,7 +86,7 @@ public final class Preferences {
}
public Set<String> getTestsPassed() {
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.<String>of() );
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.of() );
}
public boolean setRememberFullName(final boolean enabled) {
@ -148,10 +148,11 @@ public final class Preferences {
@Nonnull
public MPResultType getDefaultResultType() {
return MPResultType.values()[prefs().getInt( PREF_RESULT_TYPE, MPResultType.DEFAULT.ordinal() )];
return MPResultType.values()[
prefs().getInt( PREF_RESULT_TYPE, getDefaultVersion().mpw_default_result_type().ordinal() )];
}
public boolean setDefaultVersion(final MPMasterKey.Version value) {
public boolean setDefaultVersion(final MPAlgorithm.Version value) {
if (getDefaultVersion() == value)
return false;
@ -160,7 +161,8 @@ public final class Preferences {
}
@Nonnull
public MPMasterKey.Version getDefaultVersion() {
return MPMasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MPMasterKey.Version.CURRENT.ordinal() )];
public MPAlgorithm.Version getDefaultVersion() {
return MPAlgorithm.Version.values()[
prefs().getInt( PREF_ALGORITHM_VERSION, MPAlgorithm.Version.CURRENT.ordinal() )];
}
}

View File

@ -19,21 +19,21 @@
package com.lyndir.masterpassword;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
/**
* @author lhunath, 2014-08-25
*/
@SuppressWarnings("NewMethodNamingConvention")
public final class Res {
public final Typeface sourceCodePro_Black;
public final Typeface sourceCodePro_ExtraLight;
public final Typeface exo_Bold;
public final Typeface exo_ExtraBold;
public final Typeface exo_Regular;
public final Typeface exo_Thin;
private final Typeface sourceCodePro_Black;
private final Typeface sourceCodePro_ExtraLight;
private final Typeface exo_Bold;
private final Typeface exo_ExtraBold;
private final Typeface exo_Regular;
private final Typeface exo_Thin;
private static Res res;
@ -54,4 +54,28 @@ public final class Res {
exo_Regular = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Regular.otf" );
exo_Thin = Typeface.createFromAsset( context.getResources().getAssets(), "Exo2.0-Thin.otf" );
}
public Typeface sourceCodePro_Black() {
return sourceCodePro_Black;
}
public Typeface sourceCodePro_ExtraLight() {
return sourceCodePro_ExtraLight;
}
public Typeface exo_Bold() {
return exo_Bold;
}
public Typeface exo_ExtraBold() {
return exo_ExtraBold;
}
public Typeface exo_Regular() {
return exo_Regular;
}
public Typeface exo_Thin() {
return exo_Thin;
}
}

View File

@ -1,194 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.*;
import android.content.Context;
import android.content.Intent;
import android.os.*;
import android.view.View;
import android.widget.*;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.common.base.*;
import com.google.common.collect.*;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.concurrent.*;
import javax.annotation.Nullable;
public class TestActivity extends Activity implements MPTestSuite.Listener {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( TestActivity.class );
private final Preferences preferences = Preferences.get( this );
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
@BindView(R.id.progressView)
ProgressBar progressView;
@BindView(R.id.statusView)
TextView statusView;
@BindView(R.id.logView)
TextView logView;
@BindView(R.id.actionButton)
Button actionButton;
@BindView(R.id.nativeKDFField)
CheckBox nativeKDFField;
private MPTestSuite testSuite;
private ListenableFuture<Boolean> testFuture;
private Runnable action;
private ImmutableSet<String> testNames;
public static void startNoSkip(final Context context) {
context.startActivity( new Intent( context, TestActivity.class ) );
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_test );
ButterKnife.bind( this );
nativeKDFField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
preferences.setNativeKDFEnabled( isChecked );
// TODO: MasterKey.setAllowNativeByDefault( isChecked );
}
} );
try {
setStatus( 0, 0, null );
testSuite = new MPTestSuite();
testSuite.setListener( this );
testNames = FluentIterable.from( testSuite.getTests().getCases() ).transform(
new Function<MPTests.Case, String>() {
@Nullable
@Override
public String apply(@Nullable final MPTests.Case input) {
return (input == null)? null: input.identifier;
}
} ).filter( Predicates.notNull() ).toSet();
}
catch (final MPTestSuite.UnavailableException e) {
logger.err( e, "While loading test suite" );
setStatus( R.string.tests_unavailable, R.string.tests_btn_unavailable, new Runnable() {
@Override
public void run() {
finish();
}
} );
}
}
@Override
protected void onResume() {
super.onResume();
nativeKDFField.setChecked( preferences.isAllowNativeKDF() );
if (testFuture == null)
startTestSuite();
}
private void startTestSuite() {
if (testFuture != null)
testFuture.cancel( true );
// TODO: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
@Override
public void onSuccess(@Nullable final Boolean result) {
if ((result != null) && result)
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
@Override
public void run() {
preferences.setTestsPassed( testNames );
finish();
}
} );
else
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
@Override
public void run() {
startTestSuite();
}
} );
}
@Override
public void onFailure(final Throwable t) {
logger.err( t, "While running test suite" );
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
@Override
public void run() {
finish();
}
} );
}
}, mainExecutor );
}
public void onAction(final View v) {
if (action != null)
action.run();
}
private void setStatus(final int statusId, final int buttonId, @Nullable final Runnable action) {
this.action = action;
if (statusId == 0)
statusView.setText( null );
else
statusView.setText( statusId );
if (buttonId == 0)
actionButton.setText( null );
else
actionButton.setText( buttonId );
actionButton.setEnabled( action != null );
}
@Override
public void progress(final int current, final int max, final String messageFormat, final Object... args) {
runOnUiThread( new Runnable() {
@Override
public void run() {
logView.append( strf( "%n" + messageFormat, args ) );
progressView.setMax( max );
progressView.setProgress( current );
}
} );
}
}

View File

@ -253,15 +253,6 @@
</LinearLayout>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:text="@string/btn_tests"
android:onClick="integrityTests"
android:background="@android:color/transparent" />
<View
android:layout_width="1dp"
android:layout_height="0dp"

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:orientation="vertical"
android:gravity="center">
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/img_stats" />
<ProgressBar
android:id="@+id/progressView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
tools:max="100"
tools:progress="80"
style="?android:progressBarStyleHorizontal" />
<TextView
android:id="@+id/statusView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/sitePasswordField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/tests_testing" />
<TextView
android:id="@+id/logView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="20dp"
android:gravity="bottom"
android:background="@android:color/transparent"
android:textIsSelectable="true"
android:textSize="9sp"
android:textColor="@android:color/tertiary_text_dark" />
<Button
android:id="@+id/actionButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:enabled="false"
android:text="@string/tests_btn_testing"
android:onClick="onAction" />
<CheckBox
android:id="@+id/nativeKDFField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/nativeKDF" />
</LinearLayout>
</ScrollView>

@ -1 +1 @@
Subproject commit d598fb4f5e29f5aaa66e7e880a9857019865881b
Subproject commit 4aef3c80e0ffe6c7011c20c7f703ea56af774861

@ -1 +0,0 @@
Subproject commit 2dcb598d18a64a61509e418079bc448ce6b5268a

@ -1 +0,0 @@
Subproject commit 1b8f8b79ad12b70976c7a417ff1a9d29e8c0ed73

View File

@ -1 +0,0 @@
Versions/Current/Crashlytics

View File

@ -1 +0,0 @@
Versions/Current/Headers

View File

@ -1 +0,0 @@
Versions/Current/Modules

View File

@ -1 +0,0 @@
Versions/Current/Resources

View File

@ -1,31 +0,0 @@
//
// ANSCompatibility.h
// AnswersKit
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define ANS_GENERIC_NSARRAY(type) NSArray
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@ -1,210 +0,0 @@
//
// Answers.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ANSCompatibility.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This class exposes the Answers Events API, allowing you to track key
* user user actions and metrics in your app.
*/
@interface Answers : NSObject
/**
* Log a Sign Up event to see users signing up for your app in real-time, understand how
* many users are signing up with different methods and their success rate signing up.
*
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
* @param signUpSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
success:(nullable NSNumber *)signUpSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Log In event to see users logging into your app in real-time, understand how many
* users are logging in with different methods and their success rate logging into your app.
*
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
* @param loginSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
success:(nullable NSNumber *)loginSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Share event to see users sharing from your app in real-time, letting you
* understand what content they're sharing from the type or genre down to the specific id.
*
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Invite Event to track how users are inviting other users into
* your application.
*
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
* items are most popular, and track plenty of other important purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param purchaseSucceededOrNil Was the purchase succesful or unsuccesful
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
*/
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
success:(nullable NSNumber *)purchaseSucceededOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level Start Event to track where users are in your game.
*
* @param levelNameOrNil The level name
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
*/
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level End event to track how users are completing levels in your game.
*
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
* @param scoreOrNil The score the user completed the level with.
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed succesfully.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
score:(nullable NSNumber *)scoreOrNil
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
* many users start the purchase flow, see which items are most popular, and track plenty of other important
* purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
* users are doing this and how much they're spending per checkout, and see how it related to other important
* purchase-related metrics.
*
* @param totalPriceOrNil The total price of the cart.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemCountOrNil The number of items in the cart.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemCount:(nullable NSNumber *)itemCountOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Rating event to see users rating content within your app in real-time and understand what
* content is most engaging, from the type or genre down to the specific id.
*
* @param ratingOrNil The integer rating given by the user.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logRating:(nullable NSNumber *)ratingOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Content View event to see users viewing content within your app in real-time and
* understand what content is most engaging, from the type or genre down to the specific id.
*
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Search event allows you to see users searching within your app in real-time and understand
* exactly what they're searching for.
*
* @param queryOrNil The user's query.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
* the name of the event, since this is how the event will appear in Answers.
*
* @param eventName The human-readable name for the event.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
* must be <code>NSString</code> and and values must be <code>NSNumber</code> or <code>NSString</code>.
* @discussion How we treat <code>NSNumbers</code>:
* We will provide information about the distribution of values over time.
*
* How we treat <code>NSStrings</code>:
* NSStrings are used as categorical data, allowing comparison across different category values.
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
* truncated.
*
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
* engagement.
*/
+ (void)logCustomEventWithName:(NSString *)eventName
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,33 +0,0 @@
//
// CLSAttributes.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define CLS_GENERIC_NSARRAY(type) NSArray
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@ -1,64 +0,0 @@
//
// CLSLogging.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#ifdef __OBJC__
#import "CLSAttributes.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#endif
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef __OBJC__
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
#ifdef __OBJC__
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not recommended for Release builds.
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
NS_ASSUME_NONNULL_END
#endif

Some files were not shown because too many files have changed in this diff Show More