Compare commits
82 Commits
2.0.254-ap
...
2.1-appsto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b24efa65c | ||
|
|
3e217d5a69 | ||
|
|
c8ca1c80e6 | ||
|
|
88c18db010 | ||
|
|
f909cdbae4 | ||
|
|
8b8d5d325e | ||
|
|
c7670f47db | ||
|
|
f3f25f5890 | ||
|
|
3065433a37 | ||
|
|
41b3964363 | ||
|
|
5e8810c535 | ||
|
|
8c3dfc8510 | ||
|
|
b4b9ee3cb9 | ||
|
|
da4bad7977 | ||
|
|
984434cca4 | ||
|
|
064122f36d | ||
|
|
5db083bf7c | ||
|
|
44f91e0618 | ||
|
|
6050b5d6fd | ||
|
|
8e3e77c2c1 | ||
|
|
a2e71aa94d | ||
|
|
a5bc2eb584 | ||
|
|
9bb613a3b6 | ||
|
|
466863f8fd | ||
|
|
fe5828c724 | ||
|
|
b3ec7a848d | ||
|
|
17734652b4 | ||
|
|
9e742fa40f | ||
|
|
d03b1746e0 | ||
|
|
58156be793 | ||
|
|
d5a5cd7de4 | ||
|
|
2100662fb3 | ||
|
|
248627aa92 | ||
|
|
449ccaa3d4 | ||
|
|
0a7465282b | ||
|
|
5b85ba3a4b | ||
|
|
b3a0b6a7c0 | ||
|
|
4396ce436e | ||
|
|
68e6106ee7 | ||
|
|
4c12f368f5 | ||
|
|
0156f8c3c8 | ||
|
|
2e5cbac761 | ||
|
|
a043b7c049 | ||
|
|
06c62f70ed | ||
|
|
c97546a232 | ||
|
|
88fdc89f27 | ||
|
|
9109a59410 | ||
|
|
61bed8b29c | ||
|
|
72b1d36626 | ||
|
|
6e14554f95 | ||
|
|
fecbd2ea1c | ||
|
|
1c5f5675a5 | ||
|
|
76b717e06d | ||
|
|
eb48f749e2 | ||
|
|
398f7bdb66 | ||
|
|
d40ccee0fe | ||
|
|
a481626f80 | ||
|
|
24c48a78f8 | ||
|
|
edda3cf12a | ||
|
|
6c2cd01015 | ||
|
|
c9988d8cc2 | ||
|
|
bc88daf08d | ||
|
|
a8bb434ded | ||
|
|
1e8a832cba | ||
|
|
4f70e0f676 | ||
|
|
9d7799c814 | ||
|
|
2adb74c971 | ||
|
|
cc80a66331 | ||
|
|
cf8ecb2952 | ||
|
|
adcea94a37 | ||
|
|
3ebef16007 | ||
|
|
3225985e1e | ||
|
|
76280ac71c | ||
|
|
f47ff67ba9 | ||
|
|
65cef6d8ed | ||
|
|
89145d6e13 | ||
|
|
63b4c605e2 | ||
|
|
b3b7858c1d | ||
|
|
eb4ea08a8b | ||
|
|
51d61b8bc0 | ||
|
|
bddbd199e2 | ||
|
|
b579dac180 |
3
.gitignore
vendored
@@ -16,6 +16,9 @@
|
|||||||
xcuserdata/
|
xcuserdata/
|
||||||
/DerivedData/
|
/DerivedData/
|
||||||
|
|
||||||
|
# Generated
|
||||||
|
MasterPassword/Resources/Media/Images.xcassets/
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
Press/Background.png
|
Press/Background.png
|
||||||
Press/Front-Page.png
|
Press/Front-Page.png
|
||||||
|
|||||||
9
.gitmodules
vendored
@@ -4,9 +4,12 @@
|
|||||||
[submodule "External/InAppSettingsKit"]
|
[submodule "External/InAppSettingsKit"]
|
||||||
path = External/InAppSettingsKit
|
path = External/InAppSettingsKit
|
||||||
url = git://github.com/lhunath/InAppSettingsKit.git
|
url = git://github.com/lhunath/InAppSettingsKit.git
|
||||||
[submodule "External/UbiquityStoreManager"]
|
|
||||||
path = External/UbiquityStoreManager
|
|
||||||
url = git://github.com/lhunath/UbiquityStoreManager.git
|
|
||||||
[submodule "External/RHStatusItemView"]
|
[submodule "External/RHStatusItemView"]
|
||||||
path = External/RHStatusItemView
|
path = External/RHStatusItemView
|
||||||
url = git://github.com/lhunath/RHStatusItemView.git
|
url = git://github.com/lhunath/RHStatusItemView.git
|
||||||
|
[submodule "External/KCOrderedAccessorFix"]
|
||||||
|
path = External/KCOrderedAccessorFix
|
||||||
|
url = https://github.com/CFKevinRef/KCOrderedAccessorFix.git
|
||||||
|
[submodule "External/AttributedMarkdown"]
|
||||||
|
path = External/AttributedMarkdown
|
||||||
|
url = https://github.com/dreamwieber/AttributedMarkdown.git
|
||||||
|
|||||||
1
External/AttributedMarkdown
vendored
Submodule
1
External/KCOrderedAccessorFix
vendored
Submodule
2
External/Pearl
vendored
1
External/UbiquityStoreManager
vendored
6
External/iOS/Crashlytics.framework/Modules/module.modulemap
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
framework module Crashlytics {
|
||||||
|
umbrella header "Crashlytics.h"
|
||||||
|
|
||||||
|
export *
|
||||||
|
module * { export * }
|
||||||
|
}
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.2</string>
|
<string>2.2.4</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>36</string>
|
<string>38</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
BIN
External/iOS/Crashlytics.framework/run
vendored
BIN
External/iOS/Crashlytics.framework/submit
vendored
BIN
External/iOS/Reveal.framework/Versions/A/Reveal
vendored
@@ -10,57 +10,85 @@
|
|||||||
<string>MasterPassword</string>
|
<string>MasterPassword</string>
|
||||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</key>
|
||||||
|
<string>git://github.com/lhunath/InAppSettingsKit.git</string>
|
||||||
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
|
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
|
||||||
<string>git://github.com/Lyndir/love-lyndir.client.git</string>
|
<string>git://github.com/Lyndir/love-lyndir.client.git</string>
|
||||||
<key>42C94803-87A2-403E-896C-D9AC3A807E1B</key>
|
<key>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</key>
|
||||||
<string>git://github.com/lhunath/UbiquityStoreManager.git</string>
|
<string>github.com:Lyndir/Lyndir.git</string>
|
||||||
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
|
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
||||||
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
|
|
||||||
<key>ADA0D7F9-4871-4128-8FEE-FD1021EEF3AC</key>
|
|
||||||
<string>ssh://github.com/Lyndir/Pearl.git</string>
|
|
||||||
<key>AE3786C7-912B-4651-A73F-2E1DACBFB604</key>
|
|
||||||
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
||||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
|
||||||
|
<string>https://github.com/CFKevinRef/KCOrderedAccessorFix.git</string>
|
||||||
|
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
||||||
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
||||||
<key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
|
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
|
||||||
<string>git://github.com/lhunath/InAppSettingsKit.git</string>
|
<string>https://github.com/dreamwieber/AttributedMarkdown.git</string>
|
||||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
||||||
|
<string>ssh://github.com/Lyndir/Pearl.git</string>
|
||||||
|
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
||||||
<string>git://github.com/jonmarimba/jrswizzle.git</string>
|
<string>git://github.com/jonmarimba/jrswizzle.git</string>
|
||||||
|
<key>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</key>
|
||||||
|
<string>git://github.com/lhunath/UbiquityStoreManager.git</string>
|
||||||
|
<key>F788B28042EDBEF29EFE34687DA79A778C2CC260</key>
|
||||||
|
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>IDESourceControlProjectPath</key>
|
<key>IDESourceControlProjectPath</key>
|
||||||
<string>MasterPassword.xcworkspace</string>
|
<string>MasterPassword.xcworkspace</string>
|
||||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</key>
|
||||||
|
<string>../External/InAppSettingsKit</string>
|
||||||
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
|
<key>1AA8C0BE-EEC3-4FBC-A801-8939A1AC093A</key>
|
||||||
<string>../External/LoveLyndir</string>
|
<string>../External/LoveLyndir</string>
|
||||||
<key>42C94803-87A2-403E-896C-D9AC3A807E1B</key>
|
<key>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</key>
|
||||||
<string>../External/UbiquityStoreManager</string>
|
<string>../..</string>
|
||||||
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
|
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
||||||
<string>..</string>
|
|
||||||
<key>ADA0D7F9-4871-4128-8FEE-FD1021EEF3AC</key>
|
|
||||||
<string>../External/Pearl</string>
|
|
||||||
<key>AE3786C7-912B-4651-A73F-2E1DACBFB604</key>
|
|
||||||
<string>../External/Pearl/External/uicolor-utilities</string>
|
<string>../External/Pearl/External/uicolor-utilities</string>
|
||||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
|
||||||
|
<string>../External/KCOrderedAccessorFix</string>
|
||||||
|
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
||||||
<string>../External/RHStatusItemView</string>
|
<string>../External/RHStatusItemView</string>
|
||||||
<key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
|
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
|
||||||
<string>../External/InAppSettingsKit</string>
|
<string>../External/AttributedMarkdown</string>
|
||||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
||||||
|
<string>../External/Pearl</string>
|
||||||
|
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
||||||
<string>../External/Pearl/External/jrswizzle</string>
|
<string>../External/Pearl/External/jrswizzle</string>
|
||||||
|
<key>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</key>
|
||||||
|
<string>../External/UbiquityStoreManager</string>
|
||||||
|
<key>F788B28042EDBEF29EFE34687DA79A778C2CC260</key>
|
||||||
|
<string>..</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>IDESourceControlProjectURL</key>
|
<key>IDESourceControlProjectURL</key>
|
||||||
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
|
<string>ssh://github.com/Lyndir/MasterPassword.git</string>
|
||||||
<key>IDESourceControlProjectVersion</key>
|
<key>IDESourceControlProjectVersion</key>
|
||||||
<integer>110</integer>
|
<integer>111</integer>
|
||||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||||
<string>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</string>
|
<string>F788B28042EDBEF29EFE34687DA79A778C2CC260</string>
|
||||||
<key>IDESourceControlProjectWCConfigurations</key>
|
<key>IDESourceControlProjectWCConfigurations</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</string>
|
<string>2A70319CE0F91B35406CA7D970AE7CB4957B0A75</string>
|
||||||
|
<key>IDESourceControlWCCName</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
|
<string>public.vcs.git</string>
|
||||||
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
|
<string>3ED8592497DB6A564366943C9AAD5A46341B5076</string>
|
||||||
|
<key>IDESourceControlWCCName</key>
|
||||||
|
<string>AttributedMarkdown</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
|
<string>public.vcs.git</string>
|
||||||
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
|
<string>1712FC0BC3C9AABD8B7B5376E310E93FBDB3BCFA</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>InAppSettingsKit</string>
|
<string>InAppSettingsKit</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -68,10 +96,18 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>E4C8E206-229C-4DA8-A130-0C544DEC7E07</string>
|
<string>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>jrswizzle</string>
|
<string>jrswizzle</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
|
<string>public.vcs.git</string>
|
||||||
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
|
<string>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</string>
|
||||||
|
<key>IDESourceControlWCCName</key>
|
||||||
|
<string>KCOrderedAccessorFix</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
@@ -84,7 +120,7 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</string>
|
<string>F788B28042EDBEF29EFE34687DA79A778C2CC260</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>MasterPassword</string>
|
<string>MasterPassword</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -92,7 +128,7 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>ADA0D7F9-4871-4128-8FEE-FD1021EEF3AC</string>
|
<string>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>Pearl</string>
|
<string>Pearl</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -100,7 +136,7 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</string>
|
<string>3E67FB08419C920516AAC3B00DAAF23073B8CF77</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>RHStatusItemView</string>
|
<string>RHStatusItemView</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -108,7 +144,7 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>42C94803-87A2-403E-896C-D9AC3A807E1B</string>
|
<string>E47DEC29CB0D0FDE3560EF46E1808FA1C723D657</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>UbiquityStoreManager</string>
|
<string>UbiquityStoreManager</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -116,7 +152,7 @@
|
|||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
<key>IDESourceControlWCCIdentifierKey</key>
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
<string>AE3786C7-912B-4651-A73F-2E1DACBFB604</string>
|
<string>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</string>
|
||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>uicolor-utilities</string>
|
<string>uicolor-utilities</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -38,15 +38,21 @@ void usage() {
|
|||||||
fprintf(stderr, " -u name Specify the full name of the user.\n"
|
fprintf(stderr, " -u name Specify the full name of the user.\n"
|
||||||
" Defaults to %s in env.\n\n", MP_env_username);
|
" Defaults to %s in env.\n\n", MP_env_username);
|
||||||
fprintf(stderr, " -t type Specify the password's template.\n"
|
fprintf(stderr, " -t type Specify the password's template.\n"
|
||||||
" Defaults to %s in env or 'long'.\n"
|
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
|
||||||
" x, max, maximum | 20 characters, contains symbols.\n"
|
" x, max, maximum | 20 characters, contains symbols.\n"
|
||||||
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
|
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
|
||||||
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
|
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
|
||||||
" b, basic | 8 characters, no symbols.\n"
|
" b, basic | 8 characters, no symbols.\n"
|
||||||
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
||||||
" p, pin | 4 numbers.\n\n", MP_env_sitetype);
|
" i, pin | 4 numbers.\n"
|
||||||
|
" n, name | 9 letter name.\n"
|
||||||
|
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype);
|
||||||
fprintf(stderr, " -c counter The value of the counter.\n"
|
fprintf(stderr, " -c counter The value of the counter.\n"
|
||||||
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
|
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
|
||||||
|
fprintf(stderr, " -v variant The kind of content to generate.\n"
|
||||||
|
" Defaults to 'password'.\n"
|
||||||
|
" p, password | The password to log in with.\n"
|
||||||
|
" l, login | The username to log in as.\n\n");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +92,14 @@ int main(int argc, char *const argv[]) {
|
|||||||
const char *siteName = NULL;
|
const char *siteName = NULL;
|
||||||
MPElementType siteType = MPElementTypeGeneratedLong;
|
MPElementType siteType = MPElementTypeGeneratedLong;
|
||||||
const char *siteTypeString = getenv( MP_env_sitetype );
|
const char *siteTypeString = getenv( MP_env_sitetype );
|
||||||
|
MPElementVariant siteVariant = MPElementVariantPassword;
|
||||||
|
const char *siteVariantString = NULL;
|
||||||
uint32_t siteCounter = 1;
|
uint32_t siteCounter = 1;
|
||||||
const char *siteCounterString = getenv( MP_env_sitecounter );
|
const char *siteCounterString = getenv( MP_env_sitecounter );
|
||||||
|
|
||||||
// Read the options.
|
// Read the options.
|
||||||
char opt;
|
char opt;
|
||||||
while ((opt = getopt(argc, argv, "u:t:c:h")) != -1)
|
while ((opt = getopt(argc, argv, "u:t:c:v:h")) != -1)
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'h':
|
case 'h':
|
||||||
usage();
|
usage();
|
||||||
@@ -102,6 +110,9 @@ int main(int argc, char *const argv[]) {
|
|||||||
case 't':
|
case 't':
|
||||||
siteTypeString = optarg;
|
siteTypeString = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'v':
|
||||||
|
siteVariantString = optarg;
|
||||||
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
siteCounterString = optarg;
|
siteCounterString = optarg;
|
||||||
break;
|
break;
|
||||||
@@ -144,6 +155,11 @@ int main(int argc, char *const argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
trc("siteCounter: %d\n", siteCounter);
|
trc("siteCounter: %d\n", siteCounter);
|
||||||
|
if (siteVariantString)
|
||||||
|
siteVariant = VariantWithName( siteVariantString );
|
||||||
|
trc("siteVariant: %d (%s)\n", siteVariant, siteVariantString);
|
||||||
|
if (siteVariant == MPElementVariantLogin)
|
||||||
|
siteType = MPElementTypeGeneratedName;
|
||||||
if (siteTypeString)
|
if (siteTypeString)
|
||||||
siteType = TypeWithName( siteTypeString );
|
siteType = TypeWithName( siteTypeString );
|
||||||
trc("siteType: %d (%s)\n", siteType, siteTypeString);
|
trc("siteType: %d (%s)\n", siteType, siteTypeString);
|
||||||
@@ -176,9 +192,10 @@ int main(int argc, char *const argv[]) {
|
|||||||
trc("masterPassword: %s\n", masterPassword);
|
trc("masterPassword: %s\n", masterPassword);
|
||||||
|
|
||||||
// Calculate the master key salt.
|
// Calculate the master key salt.
|
||||||
char *mpNameSpace = "com.lyndir.masterpassword";
|
const char *mpKeyScope = ScopeForVariant(MPElementVariantPassword);
|
||||||
|
trc("key scope: %s\n", mpKeyScope);
|
||||||
const uint32_t n_userNameLength = htonl(strlen(userName));
|
const uint32_t n_userNameLength = htonl(strlen(userName));
|
||||||
size_t masterKeySaltLength = strlen(mpNameSpace) + sizeof(n_userNameLength) + strlen(userName);
|
size_t masterKeySaltLength = strlen(mpKeyScope) + sizeof(n_userNameLength) + strlen(userName);
|
||||||
char *masterKeySalt = malloc( masterKeySaltLength );
|
char *masterKeySalt = malloc( masterKeySaltLength );
|
||||||
if (!masterKeySalt) {
|
if (!masterKeySalt) {
|
||||||
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
|
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
|
||||||
@@ -186,7 +203,7 @@ int main(int argc, char *const argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *mKS = masterKeySalt;
|
char *mKS = masterKeySalt;
|
||||||
memcpy(mKS, mpNameSpace, strlen(mpNameSpace)); mKS += strlen(mpNameSpace);
|
memcpy(mKS, mpKeyScope, strlen(mpKeyScope)); mKS += strlen(mpKeyScope);
|
||||||
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
|
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
|
||||||
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
|
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
|
||||||
if (mKS - masterKeySalt != masterKeySaltLength)
|
if (mKS - masterKeySalt != masterKeySaltLength)
|
||||||
@@ -210,9 +227,11 @@ int main(int argc, char *const argv[]) {
|
|||||||
trc("masterKey ID: %s\n", IDForBuf(masterKey, MP_dkLen));
|
trc("masterKey ID: %s\n", IDForBuf(masterKey, MP_dkLen));
|
||||||
|
|
||||||
// Calculate the site seed.
|
// Calculate the site seed.
|
||||||
|
const char *mpSiteScope = ScopeForVariant(siteVariant);
|
||||||
|
trc("site scope: %s\n", mpSiteScope);
|
||||||
const uint32_t n_siteNameLength = htonl(strlen(siteName));
|
const uint32_t n_siteNameLength = htonl(strlen(siteName));
|
||||||
const uint32_t n_siteCounter = htonl(siteCounter);
|
const uint32_t n_siteCounter = htonl(siteCounter);
|
||||||
size_t sitePasswordInfoLength = strlen(mpNameSpace) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
|
size_t sitePasswordInfoLength = strlen(mpSiteScope) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
|
||||||
char *sitePasswordInfo = malloc( sitePasswordInfoLength );
|
char *sitePasswordInfo = malloc( sitePasswordInfoLength );
|
||||||
if (!sitePasswordInfo) {
|
if (!sitePasswordInfo) {
|
||||||
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
|
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
|
||||||
@@ -220,13 +239,13 @@ int main(int argc, char *const argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *sPI = sitePasswordInfo;
|
char *sPI = sitePasswordInfo;
|
||||||
memcpy(sPI, mpNameSpace, strlen(mpNameSpace)); sPI += strlen(mpNameSpace);
|
memcpy(sPI, mpSiteScope, strlen(mpSiteScope)); sPI += strlen(mpSiteScope);
|
||||||
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
|
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
|
||||||
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
|
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
|
||||||
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
|
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
|
||||||
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
|
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
|
||||||
abort();
|
abort();
|
||||||
trc("seed from: hmac-sha256(masterKey, 'com.lyndir.masterpassword' | %s | %s | %s)\n", Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)));
|
trc("seed from: hmac-sha256(masterKey, %s | %s | %s | %s)\n", mpSiteScope, Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)));
|
||||||
trc("sitePasswordInfo ID: %s\n", IDForBuf(sitePasswordInfo, sitePasswordInfoLength));
|
trc("sitePasswordInfo ID: %s\n", IDForBuf(sitePasswordInfo, sitePasswordInfoLength));
|
||||||
|
|
||||||
uint8_t sitePasswordSeed[32];
|
uint8_t sitePasswordSeed[32];
|
||||||
|
|||||||
@@ -31,8 +31,12 @@ const MPElementType TypeWithName(const char *typeName) {
|
|||||||
return MPElementTypeGeneratedBasic;
|
return MPElementTypeGeneratedBasic;
|
||||||
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
|
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
|
||||||
return MPElementTypeGeneratedShort;
|
return MPElementTypeGeneratedShort;
|
||||||
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "pin"))
|
if (0 == strcmp(lowerTypeName, "i") || 0 == strcmp(lowerTypeName, "pin"))
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPElementTypeGeneratedPIN;
|
||||||
|
if (0 == strcmp(lowerTypeName, "n") || 0 == strcmp(lowerTypeName, "name"))
|
||||||
|
return MPElementTypeGeneratedName;
|
||||||
|
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "phrase"))
|
||||||
|
return MPElementTypeGeneratedPhrase;
|
||||||
|
|
||||||
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
|
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
|
||||||
abort();
|
abort();
|
||||||
@@ -67,6 +71,13 @@ const char *CipherForType(MPElementType type, uint8_t seedByte) {
|
|||||||
case MPElementTypeGeneratedPIN: {
|
case MPElementTypeGeneratedPIN: {
|
||||||
return "nnnn";
|
return "nnnn";
|
||||||
}
|
}
|
||||||
|
case MPElementTypeGeneratedName: {
|
||||||
|
return "cvccvcvcv";
|
||||||
|
}
|
||||||
|
case MPElementTypeGeneratedPhrase: {
|
||||||
|
char *ciphers[] = { "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
|
||||||
|
return ciphers[seedByte % 3];
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
fprintf(stderr, "Unknown generated type: %d", type);
|
fprintf(stderr, "Unknown generated type: %d", type);
|
||||||
abort();
|
abort();
|
||||||
@@ -74,6 +85,36 @@ const char *CipherForType(MPElementType type, uint8_t seedByte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MPElementVariant VariantWithName(const char *variantName) {
|
||||||
|
char lowerVariantName[strlen(variantName)];
|
||||||
|
strcpy(lowerVariantName, variantName);
|
||||||
|
for (char *vN = lowerVariantName; *vN; ++vN)
|
||||||
|
*vN = tolower(*vN);
|
||||||
|
|
||||||
|
if (0 == strcmp(lowerVariantName, "p") || 0 == strcmp(lowerVariantName, "password"))
|
||||||
|
return MPElementVariantPassword;
|
||||||
|
if (0 == strcmp(lowerVariantName, "l") || 0 == strcmp(lowerVariantName, "login"))
|
||||||
|
return MPElementVariantLogin;
|
||||||
|
|
||||||
|
fprintf(stderr, "Not a variant name: %s", lowerVariantName);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ScopeForVariant(MPElementVariant variant) {
|
||||||
|
switch (variant) {
|
||||||
|
case MPElementVariantPassword: {
|
||||||
|
return "com.lyndir.masterpassword";
|
||||||
|
}
|
||||||
|
case MPElementVariantLogin: {
|
||||||
|
return "com.lyndir.masterpassword.login";
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
fprintf(stderr, "Unknown variant: %d", variant);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char CharacterFromClass(char characterClass, uint8_t seedByte) {
|
const char CharacterFromClass(char characterClass, uint8_t seedByte) {
|
||||||
const char *classCharacters;
|
const char *classCharacters;
|
||||||
switch (characterClass) {
|
switch (characterClass) {
|
||||||
@@ -113,6 +154,10 @@ const char CharacterFromClass(char characterClass, uint8_t seedByte) {
|
|||||||
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ' ': {
|
||||||
|
classCharacters = " ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
fprintf(stderr, "Unknown character class: %c", characterClass);
|
fprintf(stderr, "Unknown character class: %c", characterClass);
|
||||||
abort();
|
abort();
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MPElementContentTypePassword,
|
/** Generate the password to log in with. */
|
||||||
MPElementContentTypeNote,
|
MPElementVariantPassword,
|
||||||
MPElementContentTypePicture,
|
/** Generate the login name to log in as. */
|
||||||
} MPElementContentType;
|
MPElementVariantLogin,
|
||||||
|
} MPElementVariant;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/** Generate the password. */
|
/** Generate the password. */
|
||||||
@@ -33,6 +34,8 @@ typedef enum {
|
|||||||
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedName = 0xE | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedPhrase = 0xF | MPElementTypeClassGenerated | 0x0,
|
||||||
|
|
||||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||||
@@ -44,6 +47,8 @@ typedef enum {
|
|||||||
#define trc(...) do {} while (0)
|
#define trc(...) do {} while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const MPElementVariant VariantWithName(const char *variantName);
|
||||||
|
const char *ScopeForVariant(MPElementVariant variant);
|
||||||
const MPElementType TypeWithName(const char *typeName);
|
const MPElementType TypeWithName(const char *typeName);
|
||||||
const char *CipherForType(MPElementType type, uint8_t seedByte);
|
const char *CipherForType(MPElementType type, uint8_t seedByte);
|
||||||
const char CharacterFromClass(char characterClass, uint8_t seedByte);
|
const char CharacterFromClass(char characterClass, uint8_t seedByte);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
<!-- PROJECT METADATA -->
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<name>Master Password Algorithm Implementation</name>
|
<name>Master Password Algorithm Implementation</name>
|
||||||
<description>The implementation of the Master Password algorithm</description>
|
<description>The implementation of the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||||
<artifactId>opal-system</artifactId>
|
<artifactId>opal-system</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>1.6-p6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||||
<artifactId>opal-crypto</artifactId>
|
<artifactId>opal-crypto</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>1.6-p6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
<!-- EXTERNAL DEPENDENCIES -->
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lambdaworks</groupId>
|
<groupId>com.lambdaworks</groupId>
|
||||||
<artifactId>scrypt</artifactId>
|
<artifactId>scrypt</artifactId>
|
||||||
<version>1.3.2</version>
|
<version>1.4.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <i>07 04, 2012</i>
|
* <i>07 04, 2012</i>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -79,11 +80,9 @@ public enum MPElementType {
|
|||||||
*/
|
*/
|
||||||
public static MPElementType forName(final String name) {
|
public static MPElementType forName(final String name) {
|
||||||
|
|
||||||
for (final MPElementType type : values()) {
|
for (final MPElementType type : values())
|
||||||
if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name )) {
|
if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name ))
|
||||||
return type;
|
return type;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw logger.bug( "Element type not known: %s", name );
|
throw logger.bug( "Element type not known: %s", name );
|
||||||
}
|
}
|
||||||
@@ -93,14 +92,12 @@ public enum MPElementType {
|
|||||||
*
|
*
|
||||||
* @return All types that support the given class.
|
* @return All types that support the given class.
|
||||||
*/
|
*/
|
||||||
public static ImmutableSet<MPElementType> forClass(final MPElementTypeClass typeClass) {
|
public static ImmutableList<MPElementType> forClass(final MPElementTypeClass typeClass) {
|
||||||
|
|
||||||
ImmutableSet.Builder<MPElementType> types = ImmutableSet.builder();
|
ImmutableList.Builder<MPElementType> types = ImmutableList.builder();
|
||||||
for (final MPElementType type : values()) {
|
for (final MPElementType type : values())
|
||||||
if (type.getTypeClass() == typeClass) {
|
if (type.getTypeClass() == typeClass)
|
||||||
types.add( type );
|
types.add( type );
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.build();
|
return types.build();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.lyndir.lhunath.masterpassword.entity.*;
|
import com.lyndir.masterpassword.entity.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||||
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.io.CharSource;
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.lambdaworks.crypto.SCrypt;
|
import com.lambdaworks.crypto.SCrypt;
|
||||||
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
||||||
@@ -11,18 +13,17 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import javax.xml.stream.events.Characters;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the Master Password algorithm.
|
* @author lhunath, 2014-08-30
|
||||||
*
|
|
||||||
* <i>07 04, 2012</i>
|
|
||||||
*
|
|
||||||
* @author lhunath
|
|
||||||
*/
|
*/
|
||||||
public abstract class MasterPassword {
|
public class MasterKey {
|
||||||
|
|
||||||
static final Logger logger = Logger.get( MasterPassword.class );
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( MasterKey.class );
|
||||||
private static final int MP_N = 32768;
|
private static final int MP_N = 32768;
|
||||||
private static final int MP_r = 8;
|
private static final int MP_r = 8;
|
||||||
private static final int MP_p = 2;
|
private static final int MP_p = 2;
|
||||||
@@ -33,52 +34,60 @@ public abstract class MasterPassword {
|
|||||||
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
|
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
|
||||||
private static final MPTemplates templates = MPTemplates.load();
|
private static final MPTemplates templates = MPTemplates.load();
|
||||||
|
|
||||||
public static byte[] keyForPassword(final String password, final String username) {
|
private final String userName;
|
||||||
|
private final byte[] key;
|
||||||
|
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
public MasterKey(final String userName, final String masterPassword) {
|
||||||
|
|
||||||
|
this.userName = userName;
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
byte[] nusernameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
|
byte[] userNameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
|
||||||
.order( MP_byteOrder )
|
.order( MP_byteOrder )
|
||||||
.putInt( username.length() )
|
.putInt( userName.length() )
|
||||||
.array();
|
.array();
|
||||||
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||||
nusernameLengthBytes, //
|
userNameLengthBytes, userName.getBytes( MP_charset ) );
|
||||||
username.getBytes( MP_charset ) );
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] key = SCrypt.scrypt( password.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen );
|
key = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen );
|
||||||
logger.trc( "User: %s, password: %s derives to key ID: %s (took %.2fs)", username, password,
|
valid = true;
|
||||||
CodeUtils.encodeHex( keyIDForKey( key ) ), (double) (System.currentTimeMillis() - start) / 1000 );
|
|
||||||
|
|
||||||
return key;
|
logger.trc( "User: %s, master password derives to key ID: %s (took %.2fs)", //
|
||||||
|
userName, getKeyID(), (double) (System.currentTimeMillis() - start) / 1000 );
|
||||||
}
|
}
|
||||||
catch (GeneralSecurityException e) {
|
catch (GeneralSecurityException e) {
|
||||||
throw logger.bug( e );
|
throw logger.bug( e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] subkeyForKey(final byte[] key, final int subkeyLength) {
|
public String getUserName() {
|
||||||
|
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyID() {
|
||||||
|
|
||||||
|
Preconditions.checkState( valid );
|
||||||
|
return CodeUtils.encodeHex( MP_hash.of( key ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getSubkey(final int subkeyLength) {
|
||||||
|
|
||||||
|
Preconditions.checkState( valid );
|
||||||
byte[] subkey = new byte[Math.min( subkeyLength, key.length )];
|
byte[] subkey = new byte[Math.min( subkeyLength, key.length )];
|
||||||
System.arraycopy( key, 0, subkey, 0, subkey.length );
|
System.arraycopy( key, 0, subkey, 0, subkey.length );
|
||||||
|
|
||||||
return subkey;
|
return subkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] keyIDForPassword(final String password, final String username) {
|
public String encode(final String name, final MPElementType type, int counter) {
|
||||||
|
|
||||||
return keyIDForKey( keyForPassword( password, username ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] keyIDForKey(final byte[] key) {
|
|
||||||
|
|
||||||
return MP_hash.of( key );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String generateContent(final MPElementType type, final String name, final byte[] key, int counter) {
|
|
||||||
|
|
||||||
|
Preconditions.checkState( valid );
|
||||||
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
|
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
|
||||||
Preconditions.checkArgument( !name.isEmpty() );
|
Preconditions.checkArgument( !name.isEmpty() );
|
||||||
Preconditions.checkArgument( key.length > 0 );
|
|
||||||
|
|
||||||
if (counter == 0)
|
if (counter == 0)
|
||||||
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||||
@@ -112,17 +121,9 @@ public abstract class MasterPassword {
|
|||||||
return password.toString();
|
return password.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(final String... arguments) {
|
public void invalidate() {
|
||||||
|
|
||||||
String masterPassword = "test-mp";
|
valid = false;
|
||||||
String username = "test-user";
|
Arrays.fill( key, (byte) 0 );
|
||||||
String siteName = "test-site";
|
|
||||||
MPElementType siteType = MPElementType.GeneratedLong;
|
|
||||||
int siteCounter = 42;
|
|
||||||
|
|
||||||
String sitePassword = generateContent( siteType, siteName, keyForPassword( masterPassword, username ), siteCounter );
|
|
||||||
|
|
||||||
logger.inf( "master password: %s, username: %s\nsite name: %s, site type: %s, site counter: %d\n => site password: %s",
|
|
||||||
masterPassword, username, siteName, siteType, siteCounter, sitePassword );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.entity;
|
package com.lyndir.masterpassword.entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <i>07 04, 2012</i>
|
* <i>07 04, 2012</i>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.entity;
|
package com.lyndir.masterpassword.entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <i>07 04, 2012</i>
|
* <i>07 04, 2012</i>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.entity;
|
package com.lyndir.masterpassword.entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <i>07 04, 2012</i>
|
* <i>07 04, 2012</i>
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.lyndir.lhunath.masterpassword" android:versionCode="1" android:versionName="GIT-SNAPSHOT">
|
package="com.lyndir.masterpassword"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="GIT-SNAPSHOT">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="11"
|
<uses-sdk
|
||||||
android:targetSdkVersion="16" />
|
android:minSdkVersion="14"
|
||||||
|
android:targetSdkVersion="19" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
<application
|
||||||
<activity android:name=".UsersActivity">
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:allowBackup="true">
|
||||||
|
<activity android:name=".EmergencyActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".UsersActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*___Generated_by_IDEA___*/
|
/*___Generated_by_IDEA___*/
|
||||||
|
|
||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
|
/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
|
||||||
public final class BuildConfig {
|
public final class BuildConfig {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*___Generated_by_IDEA___*/
|
/*___Generated_by_IDEA___*/
|
||||||
|
|
||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
|
/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
|
||||||
public final class Manifest {
|
public final class Manifest {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*___Generated_by_IDEA___*/
|
/*___Generated_by_IDEA___*/
|
||||||
|
|
||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
|
/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
|
||||||
public final class R {
|
public final class R {
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
<!-- PROJECT METADATA -->
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<name>Master Password Android</name>
|
<name>Master Password Android</name>
|
||||||
<description>An Android application to the Master Password algorithm</description>
|
<description>An Android application to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-android</artifactId>
|
<artifactId>masterpassword-android</artifactId>
|
||||||
<packaging>apk</packaging>
|
<packaging>apk</packaging>
|
||||||
|
|
||||||
@@ -24,22 +24,73 @@
|
|||||||
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
||||||
<artifactId>android-maven-plugin</artifactId>
|
<artifactId>android-maven-plugin</artifactId>
|
||||||
|
|
||||||
<!--configuration>
|
<configuration>
|
||||||
<proguard>
|
<zipalign>
|
||||||
|
<verbose>true</verbose>
|
||||||
<skip>false</skip>
|
<skip>false</skip>
|
||||||
<config>proguard.cfg</config>
|
</zipalign>
|
||||||
</proguard>
|
<sdk>
|
||||||
</configuration-->
|
<platform>19</platform>
|
||||||
|
</sdk>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>sign</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jarsigner-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>signing</id>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>package</phase>
|
||||||
|
<inherited>true</inherited>
|
||||||
|
<configuration>
|
||||||
|
<archiveDirectory></archiveDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>target/*.apk</include>
|
||||||
|
</includes>
|
||||||
|
<keystore>release.jks</keystore>
|
||||||
|
<storepass>${env.PASSWORD}</storepass>
|
||||||
|
<keypass>${env.PASSWORD}</keypass>
|
||||||
|
<alias>android</alias>
|
||||||
|
<arguments>
|
||||||
|
<argument>-sigalg</argument><argument>MD5withRSA</argument>
|
||||||
|
<argument>-digestalg</argument><argument>SHA1</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
||||||
|
<artifactId>android-maven-plugin</artifactId>
|
||||||
|
<inherited>true</inherited>
|
||||||
|
<configuration>
|
||||||
|
<sign>
|
||||||
|
<debug>false</debug>
|
||||||
|
</sign>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
<!-- DEPENDENCY MANAGEMENT -->
|
<!-- DEPENDENCY MANAGEMENT -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
<!-- PROJECT REFERENCES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -47,18 +98,27 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.jakewharton</groupId>
|
<groupId>com.jakewharton</groupId>
|
||||||
<artifactId>butterknife</artifactId>
|
<artifactId>butterknife</artifactId>
|
||||||
<version>5.1.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>slf4j-android</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
|
||||||
|
run mvn install -P 4.4 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.android</groupId>
|
<groupId>android</groupId>
|
||||||
<artifactId>android</artifactId>
|
<artifactId>android</artifactId>
|
||||||
<version>4.1.1.4</version>
|
</dependency>
|
||||||
<scope>provided</scope>
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lambdaworks</groupId>
|
||||||
|
<artifactId>libscrypt</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<type>so</type>
|
||||||
|
<classifier>android</classifier>
|
||||||
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:background="@drawable/background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/userNameField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textCapWords|textPersonName"
|
||||||
|
android:hint="@string/userName.hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/masterPasswordField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textPassword"
|
||||||
|
android:hint="@string/masterPassword.hint"
|
||||||
|
android:password="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/double_"
|
||||||
|
android:contentDescription="@string/empty" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/siteNameField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textNoSuggestions|textUri"
|
||||||
|
android:hint="@string/siteName.hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sitePasswordField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@null"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:text="LuxdZozvDuma4["
|
||||||
|
android:onClick="copySitePassword" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/typeField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/counterField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="hello">Hello masterpassword-android!</string>
|
<string name="app_name">Master Password</string>
|
||||||
<string name="app_name">masterpassword-android</string>
|
|
||||||
<string name="avatar">User Avatar</string>
|
<string name="avatar">User Avatar</string>
|
||||||
|
<string name="siteName.hint">Site Name</string>
|
||||||
|
<string name="userName.hint">Your Name</string>
|
||||||
|
<string name="masterPassword.hint">Your Master Password</string>
|
||||||
|
<string name="empty" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.*;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.*;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.InjectView;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.util.concurrent.*;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class EmergencyActivity extends Activity {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||||
|
|
||||||
|
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||||
|
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
|
||||||
|
@Override
|
||||||
|
void update() {
|
||||||
|
updateMasterKey();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final ValueChangedListener updateSitePassword = new ValueChangedListener() {
|
||||||
|
@Override
|
||||||
|
void update() {
|
||||||
|
updateSitePassword();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private ListenableFuture<MasterKey> masterKeyFuture;
|
||||||
|
|
||||||
|
@InjectView(R.id.progressView)
|
||||||
|
ProgressBar progressView;
|
||||||
|
|
||||||
|
@InjectView(R.id.userNameField)
|
||||||
|
EditText userNameField;
|
||||||
|
|
||||||
|
@InjectView(R.id.masterPasswordField)
|
||||||
|
EditText masterPasswordField;
|
||||||
|
|
||||||
|
@InjectView(R.id.siteNameField)
|
||||||
|
EditText siteNameField;
|
||||||
|
|
||||||
|
@InjectView(R.id.typeField)
|
||||||
|
Spinner typeField;
|
||||||
|
|
||||||
|
@InjectView(R.id.counterField)
|
||||||
|
NumberPicker counterField;
|
||||||
|
|
||||||
|
@InjectView(R.id.sitePasswordField)
|
||||||
|
TextView sitePasswordField;
|
||||||
|
|
||||||
|
private int hc_userName;
|
||||||
|
private int hc_masterPassword;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate( savedInstanceState );
|
||||||
|
Res.init( getResources() );
|
||||||
|
|
||||||
|
setContentView( R.layout.activity_emergency );
|
||||||
|
ButterKnife.inject( this );
|
||||||
|
|
||||||
|
userNameField.setOnFocusChangeListener( updateMasterKey );
|
||||||
|
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
|
||||||
|
siteNameField.addTextChangedListener( updateSitePassword );
|
||||||
|
typeField.setOnItemSelectedListener( updateSitePassword );
|
||||||
|
counterField.setOnValueChangedListener( updateSitePassword );
|
||||||
|
|
||||||
|
userNameField.setTypeface( Res.exo_Thin );
|
||||||
|
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
|
||||||
|
masterPasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
siteNameField.setTypeface( Res.exo_Regular );
|
||||||
|
siteNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||||
|
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
|
||||||
|
typeField.setAdapter(
|
||||||
|
new ArrayAdapter<MPElementType>( this, R.layout.type_item, MPElementType.forClass( MPElementTypeClass.Generated ) ) );
|
||||||
|
typeField.setSelection( MPElementType.GeneratedLong.ordinal() );
|
||||||
|
|
||||||
|
counterField.setMinValue( 1 );
|
||||||
|
counterField.setMaxValue( Integer.MAX_VALUE );
|
||||||
|
counterField.setWrapSelectorWheel( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) );
|
||||||
|
masterPasswordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
synchronized (this) {
|
||||||
|
hc_userName = hc_masterPassword = 0;
|
||||||
|
if (masterKeyFuture != null) {
|
||||||
|
masterKeyFuture.cancel( true );
|
||||||
|
masterKeyFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void updateMasterKey() {
|
||||||
|
final String userName = userNameField.getText().toString();
|
||||||
|
final String masterPassword = masterPasswordField.getText().toString();
|
||||||
|
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword)
|
||||||
|
return;
|
||||||
|
hc_userName = userName.hashCode();
|
||||||
|
hc_masterPassword = masterPassword.hashCode();
|
||||||
|
|
||||||
|
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
|
||||||
|
pref.putString( "userName", userName );
|
||||||
|
pref.commit();
|
||||||
|
|
||||||
|
if (masterKeyFuture != null)
|
||||||
|
masterKeyFuture.cancel( true );
|
||||||
|
|
||||||
|
if (userName.isEmpty() || masterPassword.isEmpty()) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressView.setVisibility( View.VISIBLE );
|
||||||
|
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
|
||||||
|
@Override
|
||||||
|
public MasterKey call()
|
||||||
|
throws Exception {
|
||||||
|
try {
|
||||||
|
return new MasterKey( userName, masterPassword );
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating master key." );
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )).addListener( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateSitePassword();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}, executor );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSitePassword() {
|
||||||
|
final String siteName = siteNameField.getText().toString();
|
||||||
|
final MPElementType type = (MPElementType) typeField.getSelectedItem();
|
||||||
|
final int counter = counterField.getValue();
|
||||||
|
|
||||||
|
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressView.setVisibility( View.VISIBLE );
|
||||||
|
executor.submit( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter );
|
||||||
|
|
||||||
|
runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sitePasswordField.setText( sitePassword );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
}
|
||||||
|
catch (ExecutionException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating site password." );
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating site password." );
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copySitePassword(View view) {
|
||||||
|
String sitePassword = sitePasswordField.getText().toString();
|
||||||
|
if (sitePassword.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),
|
||||||
|
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
||||||
|
((ClipboardManager) getSystemService( CLIPBOARD_SERVICE )).setPrimaryClip(
|
||||||
|
new ClipData( description, new ClipData.Item( sitePassword ) ) );
|
||||||
|
|
||||||
|
Intent startMain = new Intent(Intent.ACTION_MAIN);
|
||||||
|
startMain.addCategory(Intent.CATEGORY_HOME);
|
||||||
|
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(startMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class ValueChangedListener
|
||||||
|
implements TextWatcher, NumberPicker.OnValueChangeListener, AdapterView.OnItemSelectedListener, View.OnFocusChangeListener {
|
||||||
|
|
||||||
|
abstract void update();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValueChange(final NumberPicker picker, final int oldVal, final int newVal) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(final AdapterView<?> parent) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFocusChange(final View v, final boolean hasFocus) {
|
||||||
|
if (!hasFocus)
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-08-25
|
||||||
|
*/
|
||||||
|
public class Res {
|
||||||
|
|
||||||
|
public static Typeface sourceCodePro_Black;
|
||||||
|
public static Typeface sourceCodePro_ExtraLight;
|
||||||
|
public static Typeface exo_Bold;
|
||||||
|
public static Typeface exo_ExtraBold;
|
||||||
|
public static Typeface exo_Regular;
|
||||||
|
public static Typeface exo_Thin;
|
||||||
|
|
||||||
|
private static boolean initialized;
|
||||||
|
|
||||||
|
public static void init(Resources resources) {
|
||||||
|
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
sourceCodePro_Black = Typeface.createFromAsset( resources.getAssets(), "SourceCodePro-Black.otf" );
|
||||||
|
sourceCodePro_ExtraLight = Typeface.createFromAsset( resources.getAssets(), "SourceCodePro-ExtraLight.otf" );
|
||||||
|
exo_Bold = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Bold.otf" );
|
||||||
|
exo_ExtraBold = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-ExtraBold.otf" );
|
||||||
|
exo_Regular = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Regular.otf" );
|
||||||
|
exo_Thin = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Thin.otf" );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.InjectView;
|
import butterknife.InjectView;
|
||||||
import com.lyndir.lhunath.masterpassword.model.Avatar;
|
import com.lyndir.masterpassword.model.Avatar;
|
||||||
import com.lyndir.lhunath.masterpassword.model.User;
|
import com.lyndir.masterpassword.model.User;
|
||||||
import com.lyndir.lhunath.masterpassword.view.AvatarView;
|
import com.lyndir.masterpassword.view.AvatarView;
|
||||||
|
|
||||||
|
|
||||||
public class UsersActivity extends Activity {
|
public class UsersActivity extends Activity {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.lyndir.lhunath.masterpassword.R;
|
import com.lyndir.masterpassword.R;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2014-08-20
|
* @author lhunath, 2014-08-20
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.view;
|
package com.lyndir.masterpassword.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import butterknife.ButterKnife;
|
import com.lyndir.masterpassword.R;
|
||||||
import com.lyndir.lhunath.masterpassword.R;
|
import com.lyndir.masterpassword.model.User;
|
||||||
import com.lyndir.lhunath.masterpassword.model.User;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
<!-- PROJECT METADATA -->
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<name>Master Password CLI</name>
|
<name>Master Password CLI</name>
|
||||||
<description>A CLI interface to the Master Password algorithm</description>
|
<description>A CLI interface to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-cli</artifactId>
|
<artifactId>masterpassword-cli</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>com.lyndir.lhunath.masterpassword.CLI</mainClass>
|
<mainClass>com.lyndir.masterpassword.CLI</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
</transformers>
|
</transformers>
|
||||||
<filters>
|
<filters>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
<!-- PROJECT REFERENCES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -13,13 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.lyndir.lhunath.masterpassword;
|
|
||||||
|
|
||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
|
|
||||||
import com.google.common.io.LineReader;
|
import com.google.common.io.LineReader;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
@@ -30,22 +33,24 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class CLI {
|
public class CLI {
|
||||||
|
|
||||||
static final Logger logger = Logger.get( CLI.class );
|
|
||||||
private static final String ENV_USERNAME = "MP_USERNAME";
|
private static final String ENV_USERNAME = "MP_USERNAME";
|
||||||
private static final String ENV_PASSWORD = "MP_PASSWORD";
|
private static final String ENV_PASSWORD = "MP_PASSWORD";
|
||||||
|
private static final String ENV_SITETYPE = "MP_SITETYPE";
|
||||||
|
private static final String ENV_SITECOUNTER = "MP_SITECOUNTER";
|
||||||
|
|
||||||
public static void main(final String[] args)
|
public static void main(final String[] args)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
String userName, masterPassword, siteName = null;
|
// Read information from the environment.
|
||||||
|
String siteName = null;
|
||||||
|
String userName = System.getenv().get( ENV_USERNAME );
|
||||||
|
String masterPassword = System.getenv().get( ENV_PASSWORD );
|
||||||
|
String siteTypeName = ifNotNullElse( System.getenv().get( ENV_SITETYPE ), "" );
|
||||||
|
MPElementType siteType = siteTypeName.isEmpty()? MPElementType.GeneratedLong: MPElementType.forName( siteTypeName );
|
||||||
|
String siteCounterName = ifNotNullElse( System.getenv().get( ENV_SITECOUNTER ), "" );
|
||||||
|
int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName );
|
||||||
|
|
||||||
/* Environment. */
|
// Parse information from option arguments.
|
||||||
userName = System.getenv().get( ENV_USERNAME );
|
|
||||||
masterPassword = System.getenv().get( ENV_PASSWORD );
|
|
||||||
|
|
||||||
/* Arguments. */
|
|
||||||
int counter = 1;
|
|
||||||
MPElementType type = MPElementType.GeneratedLong;
|
|
||||||
boolean typeArg = false, counterArg = false, userNameArg = false;
|
boolean typeArg = false, counterArg = false, userNameArg = false;
|
||||||
for (final String arg : Arrays.asList( args ))
|
for (final String arg : Arrays.asList( args ))
|
||||||
if ("-t".equals( arg ) || "--type".equals( arg ))
|
if ("-t".equals( arg ) || "--type".equals( arg ))
|
||||||
@@ -58,12 +63,12 @@ public class CLI {
|
|||||||
System.exit( 0 );
|
System.exit( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
type = MPElementType.forName( arg );
|
siteType = MPElementType.forName( arg );
|
||||||
typeArg = false;
|
typeArg = false;
|
||||||
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
||||||
counterArg = true;
|
counterArg = true;
|
||||||
else if (counterArg) {
|
else if (counterArg) {
|
||||||
counter = ConversionUtils.toIntegerNN( arg );
|
siteCounter = ConversionUtils.toIntegerNN( arg );
|
||||||
counterArg = false;
|
counterArg = false;
|
||||||
} else if ("-u".equals( arg ) || "--username".equals( arg ))
|
} else if ("-u".equals( arg ) || "--username".equals( arg ))
|
||||||
userNameArg = true;
|
userNameArg = true;
|
||||||
@@ -80,12 +85,12 @@ public class CLI {
|
|||||||
System.out.println( "Available options:" );
|
System.out.println( "Available options:" );
|
||||||
|
|
||||||
System.out.println( "\t-t | --type [site password type]" );
|
System.out.println( "\t-t | --type [site password type]" );
|
||||||
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", type.getName() );
|
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", siteType.getName() );
|
||||||
System.out.println( "\t\tUse 'list' to see the available types." );
|
System.out.println( "\t\tUse 'list' to see the available types." );
|
||||||
|
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println( "\t-c | --counter [site counter]" );
|
System.out.println( "\t-c | --counter [site counter]" );
|
||||||
System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", counter );
|
System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", siteCounter );
|
||||||
System.out.println( "\t\tIncrement the counter if you need a new password." );
|
System.out.println( "\t\tIncrement the counter if you need a new password." );
|
||||||
|
|
||||||
System.out.println();
|
System.out.println();
|
||||||
@@ -106,28 +111,33 @@ public class CLI {
|
|||||||
} else
|
} else
|
||||||
siteName = arg;
|
siteName = arg;
|
||||||
|
|
||||||
InputStreamReader inReader = new InputStreamReader( System.in );
|
// Read missing information from the console.
|
||||||
try {
|
Console console = System.console();
|
||||||
|
try (InputStreamReader inReader = new InputStreamReader( System.in )) {
|
||||||
LineReader lineReader = new LineReader( inReader );
|
LineReader lineReader = new LineReader( inReader );
|
||||||
|
|
||||||
if (siteName == null) {
|
if (siteName == null) {
|
||||||
System.err.format( "Site name: " );
|
System.err.format( "Site name: " );
|
||||||
siteName = lineReader.readLine();
|
siteName = lineReader.readLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userName == null) {
|
if (userName == null) {
|
||||||
System.err.format( "User's name: " );
|
System.err.format( "User's name: " );
|
||||||
userName = lineReader.readLine();
|
userName = lineReader.readLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (masterPassword == null) {
|
if (masterPassword == null) {
|
||||||
|
if (console != null)
|
||||||
|
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
|
||||||
|
|
||||||
|
else {
|
||||||
System.err.format( "%s's master password: ", userName );
|
System.err.format( "%s's master password: ", userName );
|
||||||
masterPassword = lineReader.readLine();
|
masterPassword = lineReader.readLine();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] masterKey = MasterPassword.keyForPassword( masterPassword, userName );
|
// Encode and write out the site password.
|
||||||
String sitePassword = MasterPassword.generateContent( type, siteName, masterKey, counter );
|
System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter ) );
|
||||||
System.out.println( sitePassword );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
inReader.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<configuration scan="true">
|
<configuration scan="false">
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="com.lyndir" level="TRACE" />
|
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT METADATA -->
|
<!-- PROJECT METADATA -->
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<name>Master Password GUI</name>
|
<name>Master Password GUI</name>
|
||||||
<description>A GUI interface to the Master Password algorithm</description>
|
<description>A GUI interface to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-gui</artifactId>
|
<artifactId>masterpassword-gui</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -26,6 +26,14 @@
|
|||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>buildnumber-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
@@ -39,7 +47,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>com.lyndir.lhunath.masterpassword.GUI</mainClass>
|
<mainClass>com.lyndir.masterpassword.GUI</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
</transformers>
|
</transformers>
|
||||||
<filters>
|
<filters>
|
||||||
@@ -64,7 +72,7 @@
|
|||||||
|
|
||||||
<!-- PROJECT REFERENCES -->
|
<!-- PROJECT REFERENCES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008, Maarten Billemont
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* http://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.
|
|
||||||
*/
|
|
||||||
package com.lyndir.lhunath.masterpassword;
|
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p> <i>Jun 10, 2008</i> </p>
|
|
||||||
*
|
|
||||||
* @author mbillemo
|
|
||||||
*/
|
|
||||||
public class GUI implements UnlockFrame.SignInCallback {
|
|
||||||
|
|
||||||
private UnlockFrame unlockFrame = new UnlockFrame( this );
|
|
||||||
private PasswordFrame passwordFrame;
|
|
||||||
|
|
||||||
public static void main(final String[] args)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
// Apple
|
|
||||||
Optional<? extends GUI> appleGUI = TypeUtils.newInstance( AppleGUI.class );
|
|
||||||
if (appleGUI.isPresent()) {
|
|
||||||
appleGUI.get().open();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All others
|
|
||||||
new GUI().open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void open() {
|
|
||||||
SwingUtilities.invokeLater( new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (passwordFrame == null) {
|
|
||||||
unlockFrame.setVisible( true );
|
|
||||||
} else {
|
|
||||||
passwordFrame.setVisible( true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean signedIn(final User user) {
|
|
||||||
if (!user.hasKey()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
user.getKey();
|
|
||||||
|
|
||||||
passwordFrame = new PasswordFrame( user );
|
|
||||||
|
|
||||||
open();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.apple.eawt.*;
|
import com.apple.eawt.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,4 +30,12 @@ public class AppleGUI extends GUI {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PasswordFrame newPasswordFrame(final User user) {
|
||||||
|
PasswordFrame frame = super.newPasswordFrame( user );
|
||||||
|
frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-08-31
|
||||||
|
*/
|
||||||
|
public class Config {
|
||||||
|
|
||||||
|
private static final Config instance = new Config();
|
||||||
|
|
||||||
|
public static Config get() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkForUpdates() {
|
||||||
|
return ConversionUtils.toBoolean( System.getProperty( "mp.update.check" ) ).or( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ public class ConfigAuthenticationPanel extends AuthenticationPanel implements It
|
|||||||
return selectedUser;
|
return selectedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User( selectedUser.getName(), new String( masterPasswordField.getPassword() ) );
|
return new User( selectedUser.getUserName(), new String( masterPasswordField.getPassword() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008, Maarten Billemont
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.*;
|
||||||
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
|
import com.lyndir.lhunath.opal.system.MessageDigests;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p> <i>Jun 10, 2008</i> </p>
|
||||||
|
*
|
||||||
|
* @author mbillemo
|
||||||
|
*/
|
||||||
|
public class GUI implements UnlockFrame.SignInCallback {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( GUI.class );
|
||||||
|
|
||||||
|
private UnlockFrame unlockFrame = new UnlockFrame( this );
|
||||||
|
private PasswordFrame passwordFrame;
|
||||||
|
|
||||||
|
public static void main(final String[] args)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
if (Config.get().checkForUpdates())
|
||||||
|
checkUpdate();
|
||||||
|
|
||||||
|
GUI gui;
|
||||||
|
try {
|
||||||
|
gui = TypeUtils.newInstance( AppleGUI.class );
|
||||||
|
}
|
||||||
|
catch (NoClassDefFoundError e) {
|
||||||
|
gui = new GUI();
|
||||||
|
}
|
||||||
|
gui.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkUpdate() {
|
||||||
|
try {
|
||||||
|
Enumeration<URL> manifestURLs = Thread.currentThread().getContextClassLoader().getResources( JarFile.MANIFEST_NAME );
|
||||||
|
while (manifestURLs.hasMoreElements()) {
|
||||||
|
InputStream manifestStream = manifestURLs.nextElement().openStream();
|
||||||
|
Attributes attributes = new Manifest( manifestStream ).getMainAttributes();
|
||||||
|
if (!GUI.class.getCanonicalName().equals( attributes.getValue( Attributes.Name.MAIN_CLASS ) ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String manifestRevision = attributes.getValue( Attributes.Name.IMPLEMENTATION_VERSION );
|
||||||
|
String upstreamRevisionURL = "http://masterpasswordapp.com/masterpassword-gui.jar.rev";
|
||||||
|
CharSource upstream = Resources.asCharSource( URI.create( upstreamRevisionURL ).toURL(), Charsets.UTF_8 );
|
||||||
|
String upstreamRevision = upstream.readFirstLine();
|
||||||
|
logger.inf( "Local Revision: <%s>", manifestRevision );
|
||||||
|
logger.inf( "Upstream Revision: <%s>", upstreamRevision );
|
||||||
|
if (!manifestRevision.equalsIgnoreCase( upstreamRevision )) {
|
||||||
|
logger.wrn( "You are not running the current official version. Please update from:\n"
|
||||||
|
+ "http://masterpasswordapp.com/masterpassword-gui.jar" );
|
||||||
|
JOptionPane.showMessageDialog( null, "A new version of Master Password is available.\n"
|
||||||
|
+ "Please download the latest version from http://masterpasswordapp.com",
|
||||||
|
"Update Available", JOptionPane.WARNING_MESSAGE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.wrn( e, "Couldn't check for version update." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void open() {
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (passwordFrame == null) {
|
||||||
|
unlockFrame.setVisible( true );
|
||||||
|
} else {
|
||||||
|
passwordFrame.setVisible( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean signedIn(final User user) {
|
||||||
|
if (!user.hasKey()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
user.getKey();
|
||||||
|
|
||||||
|
passwordFrame = newPasswordFrame( user );
|
||||||
|
|
||||||
|
open();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PasswordFrame newPasswordFrame(final User user) {
|
||||||
|
PasswordFrame frame = new PasswordFrame( user );
|
||||||
|
frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.lyndir.lhunath.masterpassword.util.Components;
|
import com.lyndir.masterpassword.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
@@ -21,7 +21,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
private final JTextField siteNameField;
|
private final JTextField siteNameField;
|
||||||
private final JComboBox<MPElementType> siteTypeField;
|
private final JComboBox<MPElementType> siteTypeField;
|
||||||
private final JSpinner siteCounterField;
|
private final JSpinner siteCounterField;
|
||||||
private final JLabel passwordLabel;
|
private final JTextField passwordField;
|
||||||
|
private final JLabel tipLabel;
|
||||||
|
|
||||||
public PasswordFrame(User user)
|
public PasswordFrame(User user)
|
||||||
throws HeadlessException {
|
throws HeadlessException {
|
||||||
@@ -30,7 +31,6 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
|
|
||||||
JLabel label;
|
JLabel label;
|
||||||
|
|
||||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
|
||||||
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
||||||
{
|
{
|
||||||
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
||||||
@@ -38,7 +38,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// User
|
// User
|
||||||
add( label = new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH );
|
add( label = new JLabel( strf( "Generating passwords for: %s", user.getUserName() ) ), BorderLayout.NORTH );
|
||||||
|
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
|
||||||
// Site
|
// Site
|
||||||
@@ -49,6 +50,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
|
|
||||||
// Site Name
|
// Site Name
|
||||||
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
||||||
|
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
|
||||||
sitePanel.add( siteNameField = new JTextField() {
|
sitePanel.add( siteNameField = new JTextField() {
|
||||||
@@ -57,6 +59,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
siteNameField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||||
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
|
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
siteNameField.getDocument().addDocumentListener( this );
|
siteNameField.getDocument().addDocumentListener( this );
|
||||||
siteNameField.addActionListener( new ActionListener() {
|
siteNameField.addActionListener( new ActionListener() {
|
||||||
@@ -71,6 +74,12 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
SwingUtilities.invokeLater( new Runnable() {
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
passwordField.setText( null );
|
||||||
|
siteNameField.setText( null );
|
||||||
|
|
||||||
|
if (getDefaultCloseOperation() == WindowConstants.EXIT_ON_CLOSE)
|
||||||
|
System.exit( 0 );
|
||||||
|
else
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@@ -92,6 +101,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
} );
|
} );
|
||||||
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
sitePanel.add( siteSettings );
|
sitePanel.add( siteSettings );
|
||||||
|
siteTypeField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||||
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
||||||
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
||||||
siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
|
siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
|
||||||
@@ -102,6 +112,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
siteCounterField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||||
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
||||||
siteCounterField.addChangeListener( new ChangeListener() {
|
siteCounterField.addChangeListener( new ChangeListener() {
|
||||||
@@ -112,9 +123,18 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH );
|
passwordField = new JTextField( " " );
|
||||||
passwordLabel.setAlignmentX( LEFT_ALIGNMENT );
|
passwordField.setFont( Res.sourceCodeProBlack().deriveFont( 40f ) );
|
||||||
passwordLabel.setFont( Res.sourceCodeProBlack().deriveFont( 40f ) );
|
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||||
|
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
|
passwordField.setEditable( false );
|
||||||
|
|
||||||
|
// Tip
|
||||||
|
tipLabel = new JLabel( " ", JLabel.CENTER );
|
||||||
|
tipLabel.setFont( Res.exoThin().deriveFont( 9f ) );
|
||||||
|
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
|
|
||||||
|
add( Components.boxLayout( BoxLayout.PAGE_AXIS, passwordField, tipLabel ), BorderLayout.SOUTH );
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
setMinimumSize( getSize() );
|
setMinimumSize( getSize() );
|
||||||
@@ -131,22 +151,26 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||||
|
|
||||||
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) {
|
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) {
|
||||||
passwordLabel.setText( null );
|
passwordField.setText( null );
|
||||||
|
tipLabel.setText( null );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Res.execute( new Runnable() {
|
Res.execute( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final String sitePassword = MasterPassword.generateContent( siteType, siteName, user.getKey(), siteCounter );
|
final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter );
|
||||||
if (callback != null) {
|
if (callback != null)
|
||||||
callback.passwordGenerated( siteName, sitePassword );
|
callback.passwordGenerated( siteName, sitePassword );
|
||||||
}
|
|
||||||
|
|
||||||
SwingUtilities.invokeLater( new Runnable() {
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
passwordLabel.setText( sitePassword );
|
if (!siteName.equals( siteNameField.getText() ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
passwordField.setText( sitePassword );
|
||||||
|
tipLabel.setText( "Press [Enter] to copy the password." );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
@@ -26,6 +26,10 @@ public abstract class Res {
|
|||||||
private static final Logger logger = Logger.get( Res.class );
|
private static final Logger logger = Logger.get( Res.class );
|
||||||
|
|
||||||
private static Font sourceCodeProBlack;
|
private static Font sourceCodeProBlack;
|
||||||
|
private static Font exoBold;
|
||||||
|
private static Font exoExtraBold;
|
||||||
|
private static Font exoRegular;
|
||||||
|
private static Font exoThin;
|
||||||
|
|
||||||
public static void execute(final Runnable job) {
|
public static void execute(final Runnable job) {
|
||||||
executor.submit( new Runnable() {
|
executor.submit( new Runnable() {
|
||||||
@@ -61,6 +65,54 @@ public abstract class Res {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Font exoBold() {
|
||||||
|
try {
|
||||||
|
URL resource = Resources.getResource( "fonts/Exo2.0-Bold.otf" );
|
||||||
|
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||||
|
return exoBold != null? exoBold: //
|
||||||
|
(exoBold = font);
|
||||||
|
}
|
||||||
|
catch (FontFormatException | IOException e) {
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font exoExtraBold() {
|
||||||
|
try {
|
||||||
|
URL resource = Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" );
|
||||||
|
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||||
|
return exoExtraBold != null? exoExtraBold: //
|
||||||
|
(exoExtraBold = font);
|
||||||
|
}
|
||||||
|
catch (FontFormatException | IOException e) {
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font exoRegular() {
|
||||||
|
try {
|
||||||
|
URL resource = Resources.getResource( "fonts/Exo2.0-Regular.otf" );
|
||||||
|
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||||
|
return exoRegular != null? exoRegular: //
|
||||||
|
(exoRegular = font);
|
||||||
|
}
|
||||||
|
catch (FontFormatException | IOException e) {
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font exoThin() {
|
||||||
|
try {
|
||||||
|
URL resource = Resources.getResource( "fonts/Exo2.0-Thin.otf" );
|
||||||
|
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||||
|
return exoThin != null? exoThin: //
|
||||||
|
(exoThin = font);
|
||||||
|
}
|
||||||
|
catch (FontFormatException | IOException e) {
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class RetinaIcon extends ImageIcon {
|
private static final class RetinaIcon extends ImageIcon {
|
||||||
|
|
||||||
private static final Pattern scalePattern = Pattern.compile(".*@(\\d+)x.[^.]+$");
|
private static final Pattern scalePattern = Pattern.compile(".*@(\\d+)x.[^.]+$");
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
import com.lyndir.lhunath.masterpassword.util.Components;
|
import com.lyndir.masterpassword.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -126,7 +126,7 @@ public class UnlockFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean checkSignIn() {
|
boolean checkSignIn() {
|
||||||
boolean enabled = user != null && !user.getName().isEmpty() && user.hasKey();
|
boolean enabled = user != null && !user.getUserName().isEmpty() && user.hasKey();
|
||||||
signInButton.setEnabled( enabled );
|
signInButton.setEnabled( enabled );
|
||||||
|
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
@@ -8,29 +8,29 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|||||||
*/
|
*/
|
||||||
public class User {
|
public class User {
|
||||||
|
|
||||||
private final String name;
|
private final String userName;
|
||||||
private final String masterPassword;
|
private final String masterPassword;
|
||||||
private byte[] key;
|
private MasterKey key;
|
||||||
|
|
||||||
public User(final String name, final String masterPassword) {
|
public User(final String userName, final String masterPassword) {
|
||||||
this.name = name;
|
this.userName = userName;
|
||||||
this.masterPassword = masterPassword;
|
this.masterPassword = masterPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getUserName() {
|
||||||
return name;
|
return userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasKey() {
|
public boolean hasKey() {
|
||||||
return key != null || (masterPassword != null && !masterPassword.isEmpty());
|
return key != null || (masterPassword != null && !masterPassword.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getKey() {
|
public MasterKey getKey() {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
if (!hasKey()) {
|
if (!hasKey()) {
|
||||||
throw new IllegalStateException( strf( "Master password unknown for user: %s", name ) );
|
throw new IllegalStateException( strf( "Master password unknown for user: %s", userName ) );
|
||||||
} else {
|
} else {
|
||||||
key = MasterPassword.keyForPassword( masterPassword, name );
|
key = new MasterKey( userName, masterPassword );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ public class User {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return name.hashCode();
|
return userName.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return userName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lyndir.lhunath.masterpassword.util;
|
package com.lyndir.masterpassword.util;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -9,7 +9,7 @@ import javax.swing.*;
|
|||||||
*/
|
*/
|
||||||
public abstract class Components {
|
public abstract class Components {
|
||||||
|
|
||||||
public static JComponent boxLayout(int axis, Component... components) {
|
public static JPanel boxLayout(int axis, Component... components) {
|
||||||
JPanel container = new JPanel();
|
JPanel container = new JPanel();
|
||||||
container.setLayout( new BoxLayout( container, axis ) );
|
container.setLayout( new BoxLayout( container, axis ) );
|
||||||
for (Component component : components)
|
for (Component component : components)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<configuration scan="true">
|
<configuration scan="false">
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="com.lyndir" level="TRACE" />
|
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
3269
|
|
||||||
@@ -7,13 +7,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.lhunath</groupId>
|
<groupId>com.lyndir.lhunath</groupId>
|
||||||
<artifactId>lyndir</artifactId>
|
<artifactId>lyndir</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>1.20</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<name>Master Password</name>
|
<name>Master Password</name>
|
||||||
<description>A Java implementation of the Master Password algorithm.</description>
|
<description>A Java implementation of the Master Password algorithm.</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.lhunath.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|||||||
@@ -16,10 +16,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPKey.h"
|
#import "MPKey.h"
|
||||||
#import "MPElementStoredEntity.h"
|
#import "MPStoredSiteEntity.h"
|
||||||
#import "MPElementGeneratedEntity.h"
|
#import "MPGeneratedSiteEntity.h"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
|
|
||||||
#define MPAlgorithmDefaultVersion 1
|
#define MPAlgorithmDefaultVersion 2
|
||||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||||
|
|
||||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||||
@@ -43,37 +44,56 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
|
|
||||||
@required
|
@required
|
||||||
- (NSUInteger)version;
|
- (NSUInteger)version;
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||||
|
|
||||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPElementType)type;
|
- (NSString *)scopeForVariant:(MPSiteVariant)variant;
|
||||||
- (NSString *)shortNameOfType:(MPElementType)type;
|
- (NSString *)nameOfType:(MPSiteType)type;
|
||||||
- (NSString *)classNameOfType:(MPElementType)type;
|
- (NSString *)shortNameOfType:(MPSiteType)type;
|
||||||
- (Class)classOfType:(MPElementType)type;
|
- (NSString *)classNameOfType:(MPSiteType)type;
|
||||||
|
- (Class)classOfType:(MPSiteType)type;
|
||||||
- (NSArray *)allTypes;
|
- (NSArray *)allTypes;
|
||||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType;
|
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
||||||
- (MPElementType)nextType:(MPElementType)type;
|
- (MPSiteType)nextType:(MPSiteType)type;
|
||||||
- (MPElementType)previousType:(MPElementType)type;
|
- (MPSiteType)previousType:(MPSiteType)type;
|
||||||
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
||||||
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
|
||||||
|
|
||||||
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
|
|
||||||
|
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
|
||||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
||||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element
|
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
|
||||||
usingKey:(MPKey *)elementKey;
|
usingKey:(MPKey *)siteKey;
|
||||||
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
|
|||||||
versionToAlgorithm = [NSMutableDictionary dictionary];
|
versionToAlgorithm = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
||||||
if (!algorithm) if ((algorithm = [NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
if (!algorithm && (algorithm = (id<MPAlgorithm>)[NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
||||||
versionToAlgorithm[@(version)] = algorithm;
|
versionToAlgorithm[@(version)] = algorithm;
|
||||||
|
|
||||||
return algorithm;
|
return algorithm;
|
||||||
@@ -35,6 +35,9 @@ id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
|
|||||||
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
||||||
// Pre-1.3
|
// Pre-1.3
|
||||||
return MPAlgorithmForVersion( 0 );
|
return MPAlgorithmForVersion( 0 );
|
||||||
|
if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending)
|
||||||
|
// Pre-2.1
|
||||||
|
return MPAlgorithmForVersion( 1 );
|
||||||
|
|
||||||
return MPAlgorithmDefault;
|
return MPAlgorithmDefault;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
|
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
|
||||||
|
|
||||||
- (NSDictionary *)allCiphers;
|
- (NSDictionary *)allCiphers;
|
||||||
- (NSArray *)ciphersForType:(MPElementType)type;
|
- (NSArray *)ciphersForType:(MPSiteType)type;
|
||||||
- (NSArray *)cipherClasses;
|
- (NSArray *)cipherClasses;
|
||||||
- (NSArray *)cipherClassCharacters;
|
- (NSArray *)cipherClassCharacters;
|
||||||
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;
|
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
#import "MPAlgorithmV0.h"
|
#import "MPAlgorithmV0.h"
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
#import "MPAppDelegate_InApp.h"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
#include <openssl/bn.h>
|
#include <openssl/bn.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
@@ -70,40 +73,40 @@
|
|||||||
return [(id<MPAlgorithm>)other version] == [self version];
|
return [(id<MPAlgorithm>)other version] == [self version];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
||||||
NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
|
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
|
||||||
if (!migrationElements) {
|
if (!migrationSites) {
|
||||||
err( @"While looking for elements to migrate: %@", error );
|
err( @"While looking for sites to migrate: %@", [error fullDescription] );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL requiresExplicitMigration = NO;
|
BOOL success = YES;
|
||||||
for (MPElementEntity *migrationElement in migrationElements)
|
for (MPSiteEntity *migrationSite in migrationSites)
|
||||||
if (![migrationElement migrateExplicitly:NO])
|
if (![migrationSite tryMigrateExplicitly:NO])
|
||||||
requiresExplicitMigration = YES;
|
success = NO;
|
||||||
|
|
||||||
return requiresExplicitMigration;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (element.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
// This migration requires explicit permission.
|
// This migration requires explicit permission.
|
||||||
element.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply migration.
|
// Apply migration.
|
||||||
element.requiresExplicitMigration = NO;
|
site.requiresExplicitMigration = NO;
|
||||||
element.version = [self version];
|
site.version = [self version];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,108 +139,140 @@
|
|||||||
return [keyData hashWith:MP_hash];
|
return [keyData hashWith:MP_hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPElementType)type {
|
- (NSString *)scopeForVariant:(MPSiteVariant)variant {
|
||||||
|
|
||||||
|
switch (variant) {
|
||||||
|
case MPSiteVariantPassword:
|
||||||
|
return @"com.lyndir.masterpassword";
|
||||||
|
case MPSiteVariantLogin:
|
||||||
|
return @"com.lyndir.masterpassword.login";
|
||||||
|
case MPSiteVariantAnswer:
|
||||||
|
return @"com.lyndir.masterpassword.answer";
|
||||||
|
}
|
||||||
|
|
||||||
|
Throw( @"Unsupported variant: %ld", (long)variant );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)nameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return @"Maximum Security Password";
|
return @"Maximum Security Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return @"Long Password";
|
return @"Long Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return @"Medium Password";
|
return @"Medium Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return @"Basic Password";
|
return @"Basic Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return @"Short Password";
|
return @"Short Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedName:
|
||||||
|
return @"Login Name";
|
||||||
|
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
|
return @"Phrase";
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal:
|
||||||
return @"Personal Password";
|
return @"Personal Password";
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return @"Device Private Password";
|
return @"Device Private Password";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)shortNameOfType:(MPElementType)type {
|
- (NSString *)shortNameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return @"Maximum";
|
return @"Maximum";
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return @"Long";
|
return @"Long";
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return @"Medium";
|
return @"Medium";
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return @"Basic";
|
return @"Basic";
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return @"Short";
|
return @"Short";
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedName:
|
||||||
|
return @"Name";
|
||||||
|
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
|
return @"Phrase";
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal:
|
||||||
return @"Personal";
|
return @"Personal";
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return @"Device";
|
return @"Device";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)classNameOfType:(MPElementType)type {
|
- (NSString *)classNameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
return NSStringFromClass( [self classOfType:type] );
|
return NSStringFromClass( [self classOfType:type] );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (Class)classOfType:(MPElementType)type {
|
- (Class)classOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
Throw( @"No type given." );
|
Throw( @"No type given." );
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedName:
|
||||||
return [MPElementStoredEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeGeneratedPhrase:
|
||||||
return [MPElementStoredEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal:
|
||||||
|
return [MPStoredSiteEntity class];
|
||||||
|
|
||||||
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
|
return [MPStoredSiteEntity class];
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
@@ -245,13 +280,13 @@
|
|||||||
|
|
||||||
- (NSArray *)allTypes {
|
- (NSArray *)allTypes {
|
||||||
|
|
||||||
return [self allTypesStartingWith:MPElementTypeGeneratedMaximum];
|
return [self allTypesStartingWith:MPSiteTypeGeneratedMaximum];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType {
|
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
|
||||||
|
|
||||||
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
||||||
MPElementType currentType = startingType;
|
MPSiteType currentType = startingType;
|
||||||
do {
|
do {
|
||||||
[allTypes addObject:@(currentType)];
|
[allTypes addObject:@(currentType)];
|
||||||
} while ((currentType = [self nextType:currentType]) != startingType);
|
} while ((currentType = [self nextType:currentType]) != startingType);
|
||||||
@@ -259,33 +294,33 @@
|
|||||||
return allTypes;
|
return allTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)nextType:(MPElementType)type {
|
- (MPSiteType)nextType:(MPSiteType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return MPElementTypeGeneratedMedium;
|
return MPSiteTypeGeneratedMedium;
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return MPElementTypeGeneratedBasic;
|
return MPSiteTypeGeneratedBasic;
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return MPElementTypeGeneratedShort;
|
return MPSiteTypeGeneratedShort;
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPSiteTypeGeneratedPIN;
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return MPElementTypeStoredPersonal;
|
return MPSiteTypeStoredPersonal;
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeStoredPersonal:
|
||||||
return MPElementTypeStoredDevicePrivate;
|
return MPSiteTypeStoredDevicePrivate;
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return MPElementTypeGeneratedMaximum;
|
return MPSiteTypeGeneratedMaximum;
|
||||||
default:
|
default:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)previousType:(MPElementType)type {
|
- (MPSiteType)previousType:(MPSiteType)type {
|
||||||
|
|
||||||
MPElementType previousType = type, nextType = type;
|
MPSiteType previousType = type, nextType = type;
|
||||||
while ((nextType = [self nextType:nextType]) != type)
|
while ((nextType = [self nextType:nextType]) != type)
|
||||||
previousType = nextType;
|
previousType = nextType;
|
||||||
|
|
||||||
@@ -304,7 +339,7 @@
|
|||||||
return ciphers;
|
return ciphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)ciphersForType:(MPElementType)type {
|
- (NSArray *)ciphersForType:(MPSiteType)type {
|
||||||
|
|
||||||
NSString *typeClass = [self classNameOfType:type];
|
NSString *typeClass = [self classNameOfType:type];
|
||||||
NSString *typeName = [self nameOfType:type];
|
NSString *typeName = [self nameOfType:type];
|
||||||
@@ -326,27 +361,53 @@
|
|||||||
return [NSNullToNil( [NSNullToNil( [[self allCiphers] valueForKey:@"MPCharacterClasses"] ) valueForKey:cipherClass] ) copy];
|
return [NSNullToNil( [NSNullToNil( [[self allCiphers] valueForKey:@"MPCharacterClasses"] ) valueForKey:cipherClass] ) copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
||||||
|
variant:MPSiteVariantLogin context:nil usingKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
||||||
|
variant:MPSiteVariantPassword context:nil usingKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
|
||||||
|
variant:MPSiteVariantAnswer context:question usingKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
trc( @"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64],
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
[nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex], context );
|
||||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding],
|
nameLengthBytes,
|
||||||
counterBytes, nil]
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeBase64] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
const char *seedBytes = seed.bytes;
|
const char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first seed byte.
|
||||||
NSAssert( [seed length], @"Missing seed." );
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
NSArray *typeCiphers = [self ciphersForType:type];
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
|
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
|
||||||
trc( @"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher );
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||||
@@ -364,68 +425,80 @@
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self decryptContent:element.contentObject usingKey:key];
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
return [self decryptContent:site.contentObject usingKey:key];
|
||||||
switch (element.type) {
|
|
||||||
case MPElementTypeGeneratedMaximum:
|
|
||||||
case MPElementTypeGeneratedLong:
|
|
||||||
case MPElementTypeGeneratedMedium:
|
|
||||||
case MPElementTypeGeneratedBasic:
|
|
||||||
case MPElementTypeGeneratedShort:
|
|
||||||
case MPElementTypeGeneratedPIN: {
|
|
||||||
NSAssert( NO, @"Cannot save content to element with generated type %lu.", (long)element.type );
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
(long)element.type, [element class] );
|
switch (site.type) {
|
||||||
break;
|
case MPSiteTypeGeneratedMaximum:
|
||||||
|
case MPSiteTypeGeneratedLong:
|
||||||
|
case MPSiteTypeGeneratedMedium:
|
||||||
|
case MPSiteTypeGeneratedBasic:
|
||||||
|
case MPSiteTypeGeneratedShort:
|
||||||
|
case MPSiteTypeGeneratedPIN:
|
||||||
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
|
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal: {
|
||||||
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
|
(long)site.type, [site class] );
|
||||||
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
((MPElementStoredEntity *)element).contentObject = encryptedContent;
|
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
||||||
break;
|
return NO;
|
||||||
|
|
||||||
|
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
if (!encryptedContent)
|
if (!encryptedContent)
|
||||||
[PearlKeyChain deleteItemForQuery:elementQuery];
|
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||||
else
|
else
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
|
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||||
(__bridge id)kSecValueData : encryptedContent,
|
(__bridge id)kSecValueData : encryptedContent,
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
#endif
|
#endif
|
||||||
}];
|
}];
|
||||||
((MPElementStoredEntity *)element).contentObject = nil;
|
((MPStoredSiteEntity *)site).contentObject = nil;
|
||||||
break;
|
return YES;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
Throw( @"Unsupported type: %ld", (long)site.type );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
dispatch_group_t group = dispatch_group_create();
|
||||||
dispatch_group_enter( group );
|
dispatch_group_enter( group );
|
||||||
__block NSString *result = nil;
|
__block NSString *result = nil;
|
||||||
[self resolveContentForElement:element usingKey:elementKey result:^(NSString *result_) {
|
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
result = result_;
|
result = result_;
|
||||||
dispatch_group_leave( group );
|
dispatch_group_leave( group );
|
||||||
}];
|
}];
|
||||||
@@ -434,65 +507,131 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
dispatch_group_t group = dispatch_group_create();
|
||||||
switch (element.type) {
|
dispatch_group_enter( group );
|
||||||
case MPElementTypeGeneratedMaximum:
|
__block NSString *result = nil;
|
||||||
case MPElementTypeGeneratedLong:
|
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
case MPElementTypeGeneratedMedium:
|
result = result_;
|
||||||
case MPElementTypeGeneratedBasic:
|
dispatch_group_leave( group );
|
||||||
case MPElementTypeGeneratedShort:
|
}];
|
||||||
case MPElementTypeGeneratedPIN: {
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
if (![element isKindOfClass:[MPElementGeneratedEntity class]]) {
|
|
||||||
wrn( @"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.",
|
return result;
|
||||||
(long)element.type, [element class] );
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *name = element.name;
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
MPElementType type = element.type;
|
|
||||||
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
dispatch_group_enter( group );
|
||||||
|
__block NSString *result = nil;
|
||||||
|
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
|
result = result_;
|
||||||
|
dispatch_group_leave( group );
|
||||||
|
}];
|
||||||
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
dispatch_group_enter( group );
|
||||||
|
__block NSString *result = nil;
|
||||||
|
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||||
|
result = result_;
|
||||||
|
dispatch_group_leave( group );
|
||||||
|
}];
|
||||||
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = site.name;
|
||||||
|
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
|
||||||
|
NSString *loginName = loginGenerated? nil: site.loginName;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!element.name.length)
|
if (!name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!elementKey.keyData.length)
|
else if (!siteKey.keyData.length)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else
|
else
|
||||||
algorithm = element.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [algorithm generateContentNamed:name ofType:type withCounter:counter usingKey:elementKey];
|
if (loginGenerated)
|
||||||
|
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||||
|
else
|
||||||
|
resultBlock( loginName );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
switch (site.type) {
|
||||||
|
case MPSiteTypeGeneratedMaximum:
|
||||||
|
case MPSiteTypeGeneratedLong:
|
||||||
|
case MPSiteTypeGeneratedMedium:
|
||||||
|
case MPSiteTypeGeneratedBasic:
|
||||||
|
case MPSiteTypeGeneratedShort:
|
||||||
|
case MPSiteTypeGeneratedPIN:
|
||||||
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
|
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||||
|
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
|
||||||
|
(long)site.type, [site class] );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *name = site.name;
|
||||||
|
MPSiteType type = site.type;
|
||||||
|
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!site.name.length)
|
||||||
|
err( @"Missing name." );
|
||||||
|
else if (!siteKey.keyData.length)
|
||||||
|
err( @"Missing key." );
|
||||||
|
else
|
||||||
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
|
||||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
|
||||||
[element class] );
|
[site class] );
|
||||||
|
|
||||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
@@ -500,91 +639,135 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
NSString *name = site.name;
|
||||||
case MPElementTypeGeneratedMaximum:
|
id<MPAlgorithm> algorithm = nil;
|
||||||
case MPElementTypeGeneratedLong:
|
if (!site.name.length)
|
||||||
case MPElementTypeGeneratedMedium:
|
err( @"Missing name." );
|
||||||
case MPElementTypeGeneratedBasic:
|
else if (!siteKey.keyData.length)
|
||||||
case MPElementTypeGeneratedShort:
|
err( @"Missing key." );
|
||||||
case MPElementTypeGeneratedPIN:
|
else
|
||||||
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:question.site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = question.site.name;
|
||||||
|
NSString *keyword = question.keyword;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!name.length)
|
||||||
|
err( @"Missing name." );
|
||||||
|
else if (!siteKey.keyData.length)
|
||||||
|
err( @"Missing key." );
|
||||||
|
else
|
||||||
|
algorithm = question.site.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||||
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
switch (site.type) {
|
||||||
|
case MPSiteTypeGeneratedMaximum:
|
||||||
|
case MPSiteTypeGeneratedLong:
|
||||||
|
case MPSiteTypeGeneratedMedium:
|
||||||
|
case MPSiteTypeGeneratedBasic:
|
||||||
|
case MPSiteTypeGeneratedShort:
|
||||||
|
case MPSiteTypeGeneratedPIN:
|
||||||
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
if ([importKey.keyID isEqualToData:siteKey.keyID])
|
||||||
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
|
||||||
|
|
||||||
else {
|
else {
|
||||||
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
||||||
[self importClearTextContent:clearContent intoElement:element usingKey:elementKey];
|
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
[self saveContent:clearContent toElement:element usingKey:elementKey];
|
[self savePassword:clearContent toSite:site usingKey:siteKey];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
if (!(element.type & MPElementFeatureExportContent))
|
if (!(site.type & MPSiteFeatureExportContent))
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
NSString *result = nil;
|
NSString *result = nil;
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN: {
|
case MPSiteTypeGeneratedPIN:
|
||||||
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
result = nil;
|
result = nil;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
result = nil;
|
result = nil;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -598,7 +781,7 @@
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
|
||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:@{
|
attributes:@{
|
||||||
@@ -619,7 +802,7 @@
|
|||||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker {
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return NO;
|
return NO;
|
||||||
|
|||||||
@@ -25,49 +25,54 @@
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (element.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
if (element.type & MPElementTypeClassGenerated) {
|
if (site.type & MPSiteTypeClassGenerated) {
|
||||||
// This migration requires explicit permission for types of the generated class.
|
// This migration requires explicit permission for types of the generated class.
|
||||||
element.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply migration.
|
// Apply migration.
|
||||||
element.requiresExplicitMigration = NO;
|
site.requiresExplicitMigration = NO;
|
||||||
element.version = [self version];
|
site.version = [self version];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
trc( @"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex],
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
name, [counterBytes encodeHex] );
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nameLengthBytes,
|
nameLengthBytes,
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
counterBytes,
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nil]
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeBase64] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
const unsigned char *seedBytes = seed.bytes;
|
const unsigned char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first seed byte.
|
||||||
NSAssert( [seed length], @"Missing seed." );
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
NSArray *typeCiphers = [self ciphersForType:type];
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||||
trc( @"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher );
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||||
|
|||||||
21
MasterPassword/ObjC/MPAlgorithmV2.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||||
|
*
|
||||||
|
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||||
|
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*
|
||||||
|
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||||
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPAlgorithmV1
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 17/07/12.
|
||||||
|
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPAlgorithmV1.h"
|
||||||
|
|
||||||
|
@interface MPAlgorithmV2 : MPAlgorithmV1
|
||||||
|
@end
|
||||||
97
MasterPassword/ObjC/MPAlgorithmV2.m
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||||
|
*
|
||||||
|
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||||
|
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*
|
||||||
|
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||||
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPAlgorithmV1
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 17/07/12.
|
||||||
|
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#import "MPAlgorithmV2.h"
|
||||||
|
#import "MPEntities.h"
|
||||||
|
|
||||||
|
@implementation MPAlgorithmV2
|
||||||
|
|
||||||
|
- (NSUInteger)version {
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
|
if (site.version != [self version] - 1)
|
||||||
|
// Only migrate from previous version.
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
if (!explicit) {
|
||||||
|
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||||
|
// This migration requires explicit permission for types of the generated class.
|
||||||
|
site.requiresExplicitMigration = YES;
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply migration.
|
||||||
|
site.requiresExplicitMigration = NO;
|
||||||
|
site.version = [self version];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
|
NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length );
|
||||||
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
|
NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
|
scopeBytes,
|
||||||
|
nameLengthBytes,
|
||||||
|
nameBytes,
|
||||||
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
contextBytes,
|
||||||
|
nil]
|
||||||
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
|
const unsigned char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
|
// Determine the cipher from the first seed byte.
|
||||||
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
|
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||||
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
|
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||||
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
|
uint16_t keyByte = seedBytes[c + 1];
|
||||||
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||||
|
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||||
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||||
|
|
||||||
|
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||||
|
[content appendString:character];
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
39
MasterPassword/ObjC/MPAppDelegate_InApp.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MPAppDelegate_Key.h
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 24/11/11.
|
||||||
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <StoreKit/StoreKit.h>
|
||||||
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
|
||||||
|
#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins"
|
||||||
|
#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers"
|
||||||
|
#define MPProductOSIntegration @"com.lyndir.masterpassword.products.osintegration"
|
||||||
|
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
|
||||||
|
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
|
||||||
|
|
||||||
|
#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */
|
||||||
|
|
||||||
|
@protocol MPInAppDelegate
|
||||||
|
|
||||||
|
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
|
||||||
|
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPAppDelegate_Shared(InApp)
|
||||||
|
|
||||||
|
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
|
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
|
|
||||||
|
- (void)reloadProducts;
|
||||||
|
- (BOOL)canMakePayments;
|
||||||
|
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier;
|
||||||
|
|
||||||
|
- (void)restoreCompletedTransactions;
|
||||||
|
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity;
|
||||||
|
|
||||||
|
@end
|
||||||