Initial implementation of using YubiKey for decryption (#533)
This commit is contained in:
parent
13804b79e6
commit
955e50c3d3
23 changed files with 606 additions and 118 deletions
66
Gemfile.lock
66
Gemfile.lock
|
|
@ -2,16 +2,8 @@ GEM
|
|||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
activesupport (4.2.11.3)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.345.0)
|
||||
|
|
@ -31,48 +23,10 @@ GEM
|
|||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.9.3)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.9.3)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.2, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.6)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.14.0, < 2.0)
|
||||
cocoapods-core (1.9.3)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.4.0)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.1.0)
|
||||
cocoapods-trunk (1.5.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
concurrent-ruby (1.1.7)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.6.1)
|
||||
|
|
@ -81,9 +35,6 @@ GEM
|
|||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.0.0)
|
||||
escape (0.0.4)
|
||||
ethon (0.12.0)
|
||||
ffi (>= 1.3.0)
|
||||
excon (0.75.0)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
|
|
@ -129,9 +80,6 @@ GEM
|
|||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
ffi (1.13.1)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.38.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
|
|
@ -166,8 +114,6 @@ GEM
|
|||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.4.0)
|
||||
json (2.3.1)
|
||||
jwt (2.2.1)
|
||||
|
|
@ -177,12 +123,9 @@ GEM
|
|||
mime-types-data (3.2020.0512)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
minitest (5.14.2)
|
||||
molinillo (0.6.6)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.0)
|
||||
netrc (0.11.0)
|
||||
os (1.1.0)
|
||||
|
|
@ -200,7 +143,6 @@ GEM
|
|||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.4.0)
|
||||
rubyzip (2.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.14.0)
|
||||
|
|
@ -215,15 +157,10 @@ GEM
|
|||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.8)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
|
|
@ -245,10 +182,9 @@ PLATFORMS
|
|||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods
|
||||
fastlane
|
||||
rest-client
|
||||
xcodeproj
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
2.2.25
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@
|
|||
9A1D1CE526E5D1CE0052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE426E5D1CE0052028E /* OneTimePassword */; };
|
||||
9A1D1CE726E5D2230052028E /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1D1CE626E5D2230052028E /* OneTimePassword */; };
|
||||
9A1F47FA26E5CF4B000C0E01 /* OneTimePassword in Frameworks */ = {isa = PBXBuildFile; productRef = 9A1F47F926E5CF4B000C0E01 /* OneTimePassword */; };
|
||||
9A2C7D822782CB2F00BD9AF3 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9A2C7D812782CB2F00BD9AF3 /* YubiKit */; };
|
||||
9A2C7D842783FF5200BD9AF3 /* YubiKeyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2C7D832783FF5200BD9AF3 /* YubiKeyConnection.swift */; };
|
||||
9A2C7D862783FF9600BD9AF3 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9A2C7D852783FF9600BD9AF3 /* YubiKit */; };
|
||||
9A2C7D8B2784139200BD9AF3 /* YubiKeyAPDU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2C7D8A2784139200BD9AF3 /* YubiKeyAPDU.swift */; };
|
||||
9A55C158259E785600FA8FD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC917BDD1E2E8231000FDF54 /* Assets.xcassets */; };
|
||||
9A55C15F259E785700FA8FD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC917BDD1E2E8231000FDF54 /* Assets.xcassets */; };
|
||||
9A55C185259E8C5600FA8FD9 /* PasswordsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A55C184259E8C5600FA8FD9 /* PasswordsViewController.swift */; };
|
||||
|
|
@ -122,13 +126,20 @@
|
|||
9A58662925AAAA79006719C2 /* PasswordSelectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9ECB259ECB410027CE15 /* PasswordSelectionDelegate.swift */; };
|
||||
9A58664825AAAB7E006719C2 /* SearchPassword.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A5865EF25AA944B006719C2 /* SearchPassword.storyboard */; };
|
||||
9A58665125AADB76006719C2 /* CredentialProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58665025AADB76006719C2 /* CredentialProvider.swift */; };
|
||||
9A5C6EF42786CA5F0003F340 /* AlertPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5C6EEF2786C8710003F340 /* AlertPresenting.swift */; };
|
||||
9A5C6EF92786CE170003F340 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9A5C6EF82786CE170003F340 /* YubiKit */; };
|
||||
9A5C6EFB2786CE5E0003F340 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9A5C6EFA2786CE5E0003F340 /* YubiKit */; };
|
||||
9A5C6EFF2787F0980003F340 /* Gopenpgp.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ADAB21C26DDA52400900F10 /* Gopenpgp.xcframework */; };
|
||||
9A5C6F022787F09A0003F340 /* passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A26075781EEC6F34005DB03E /* passKit.framework */; };
|
||||
9A5C6F042787F09D0003F340 /* Gopenpgp.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ADAB21C26DDA52400900F10 /* Gopenpgp.xcframework */; };
|
||||
9A5C6F082787F0C20003F340 /* SwiftyUserDefaults in Frameworks */ = {isa = PBXBuildFile; productRef = 9A5C6F072787F0C20003F340 /* SwiftyUserDefaults */; };
|
||||
9A5D06EE25A56F0800FA59D4 /* PasswordTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9EE1259EDD520027CE15 /* PasswordTableViewCell.swift */; };
|
||||
9A5D06F525A56F0E00FA59D4 /* PasswordTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9EE1259EDD520027CE15 /* PasswordTableViewCell.swift */; };
|
||||
9A5D070225A5769A00FA59D4 /* PasswordTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9EE1259EDD520027CE15 /* PasswordTableViewCell.swift */; };
|
||||
9A652414244BB33300DA0A41 /* UIAlertActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */; };
|
||||
9A74D2E0277D2F8C00F7BC44 /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D2DF277D2F8C00F7BC44 /* UIAlertControllerExtension.swift */; };
|
||||
9A78A7CC277BECE80093222D /* SVProgressHUD.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30F6C1B327664C7200BE5AB2 /* SVProgressHUD.xcframework */; };
|
||||
9A78A7CD277BECE80093222D /* SVProgressHUD.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 30F6C1B327664C7200BE5AB2 /* SVProgressHUD.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
9A78A7CD277BECE80093222D /* SVProgressHUD.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 30F6C1B327664C7200BE5AB2 /* SVProgressHUD.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9A8F9EBD259EA4C50027CE15 /* PasswordsTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9EBC259EA4C50027CE15 /* PasswordsTableDataSource.swift */; };
|
||||
9A8F9ECC259ECB410027CE15 /* PasswordSelectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9ECB259ECB410027CE15 /* PasswordSelectionDelegate.swift */; };
|
||||
9A8F9F4025A1A91F0027CE15 /* CredentialProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8F9F3F25A1A91F0027CE15 /* CredentialProvider.swift */; };
|
||||
|
|
@ -141,7 +152,6 @@
|
|||
9A996C6826DEB96B00A4485D /* passShortcuts.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 30A69945240EED5E00B7D967 /* passShortcuts.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
9A996C6B26DEB97600A4485D /* passExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A26700241EEC466A00176B8A /* passExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
9A996C6E26DEB99200A4485D /* passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A26075781EEC6F34005DB03E /* passKit.framework */; };
|
||||
9A996C7126DEB99500A4485D /* passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A26075781EEC6F34005DB03E /* passKit.framework */; };
|
||||
9ADAB21D26DDA52400900F10 /* Gopenpgp.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ADAB21C26DDA52400900F10 /* Gopenpgp.xcframework */; };
|
||||
9ADC954124418A5F0005402E /* PasswordStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADC954024418A5F0005402E /* PasswordStoreTest.swift */; };
|
||||
9AFC87D325B39FF3008D6060 /* PasswordNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFC87D225B39FF2008D6060 /* PasswordNavigationViewController.swift */; };
|
||||
|
|
@ -407,9 +417,12 @@
|
|||
9A1EF0B424C50E780074FEAC /* passBetaAutoFillExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaAutoFillExtension.entitlements; sourceTree = "<group>"; };
|
||||
9A1EF0B524C50EE00074FEAC /* passBetaExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaExtension.entitlements; sourceTree = "<group>"; };
|
||||
9A1EF0B624C50FEA0074FEAC /* passBetaShortcuts.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaShortcuts.entitlements; sourceTree = "<group>"; };
|
||||
9A2C7D832783FF5200BD9AF3 /* YubiKeyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YubiKeyConnection.swift; sourceTree = "<group>"; };
|
||||
9A2C7D8A2784139200BD9AF3 /* YubiKeyAPDU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YubiKeyAPDU.swift; sourceTree = "<group>"; };
|
||||
9A55C184259E8C5600FA8FD9 /* PasswordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordsViewController.swift; sourceTree = "<group>"; };
|
||||
9A5865EF25AA944B006719C2 /* SearchPassword.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SearchPassword.storyboard; sourceTree = "<group>"; };
|
||||
9A58665025AADB76006719C2 /* CredentialProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProvider.swift; sourceTree = "<group>"; };
|
||||
9A5C6EEF2786C8710003F340 /* AlertPresenting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenting.swift; sourceTree = "<group>"; };
|
||||
9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertActionExtension.swift; sourceTree = "<group>"; };
|
||||
9A74D2DF277D2F8C00F7BC44 /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtension.swift; sourceTree = "<group>"; };
|
||||
9A8F9EBC259EA4C50027CE15 /* PasswordsTableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordsTableDataSource.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -506,8 +519,10 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9A5C6EF92786CE170003F340 /* YubiKit in Frameworks */,
|
||||
9A996C6E26DEB99200A4485D /* passKit.framework in Frameworks */,
|
||||
30A3001A26DA697C002A734E /* SwiftyUserDefaults in Frameworks */,
|
||||
9A5C6F042787F09D0003F340 /* Gopenpgp.xcframework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -521,6 +536,7 @@
|
|||
9A1D1CE526E5D1CE0052028E /* OneTimePassword in Frameworks */,
|
||||
30A3001626DA6697002A734E /* SwiftyUserDefaults in Frameworks */,
|
||||
3032DA5626DAF4E500A7728C /* ObjectivePGP in Frameworks */,
|
||||
9A2C7D862783FF9600BD9AF3 /* YubiKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -539,7 +555,10 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9A996C7126DEB99500A4485D /* passKit.framework in Frameworks */,
|
||||
9A5C6F022787F09A0003F340 /* passKit.framework in Frameworks */,
|
||||
9A5C6EFB2786CE5E0003F340 /* YubiKit in Frameworks */,
|
||||
9A5C6EFF2787F0980003F340 /* Gopenpgp.xcframework in Frameworks */,
|
||||
9A5C6F082787F0C20003F340 /* SwiftyUserDefaults in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -560,6 +579,7 @@
|
|||
9A996C5326DDF61F00A4485D /* Base32 in Frameworks */,
|
||||
9A78A7CC277BECE80093222D /* SVProgressHUD.xcframework in Frameworks */,
|
||||
3032DA5426DAF4C200A7728C /* ObjectivePGP in Frameworks */,
|
||||
9A2C7D822782CB2F00BD9AF3 /* YubiKit in Frameworks */,
|
||||
3010CB6626DA500F008964D2 /* KeychainAccess in Frameworks */,
|
||||
9A996C5826DEB0D100A4485D /* passKit.framework in Frameworks */,
|
||||
30ED1777276F8842009BA876 /* ObjectiveGit in Frameworks */,
|
||||
|
|
@ -724,6 +744,14 @@
|
|||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A5C6EF32786C9C00003F340 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A5C6EEF2786C8710003F340 /* AlertPresenting.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A8F9EBB259EA4A80027CE15 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -806,6 +834,7 @@
|
|||
A26075791EEC6F34005DB03E /* passKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A5C6EF32786C9C00003F340 /* Protocols */,
|
||||
A2C532B9201DD07500DB9F53 /* Controllers */,
|
||||
30CCA90C232584560048CA51 /* Crypto */,
|
||||
30B6AABA21F49095006B352D /* Extensions */,
|
||||
|
|
@ -877,6 +906,8 @@
|
|||
A2F4E20F1EED7F0A0011986E /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A2C7D832783FF5200BD9AF3 /* YubiKeyConnection.swift */,
|
||||
9A2C7D8A2784139200BD9AF3 /* YubiKeyAPDU.swift */,
|
||||
30697C2921F63C590064FCAC /* AppError.swift */,
|
||||
302B2C9722C2BDE700D831EE /* AppKeychain.swift */,
|
||||
3087574E2343E42A00B971A2 /* Colors.swift */,
|
||||
|
|
@ -1075,6 +1106,7 @@
|
|||
name = passAutoFillExtension;
|
||||
packageProductDependencies = (
|
||||
30A3001926DA697C002A734E /* SwiftyUserDefaults */,
|
||||
9A5C6EF82786CE170003F340 /* YubiKit */,
|
||||
);
|
||||
productName = passAutoFillExtension;
|
||||
productReference = A239F5952158C08B00576CBF /* passAutoFillExtension.appex */;
|
||||
|
|
@ -1100,6 +1132,7 @@
|
|||
3032DA5526DAF4E500A7728C /* ObjectivePGP */,
|
||||
9A996C5626DDF65900A4485D /* Base32 */,
|
||||
9A1D1CE426E5D1CE0052028E /* OneTimePassword */,
|
||||
9A2C7D852783FF9600BD9AF3 /* YubiKit */,
|
||||
);
|
||||
productName = passKit;
|
||||
productReference = A26075781EEC6F34005DB03E /* passKit.framework */;
|
||||
|
|
@ -1142,6 +1175,10 @@
|
|||
dependencies = (
|
||||
);
|
||||
name = passExtension;
|
||||
packageProductDependencies = (
|
||||
9A5C6EFA2786CE5E0003F340 /* YubiKit */,
|
||||
9A5C6F072787F0C20003F340 /* SwiftyUserDefaults */,
|
||||
);
|
||||
productName = passExtension;
|
||||
productReference = A26700241EEC466A00176B8A /* passExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
|
|
@ -1197,6 +1234,7 @@
|
|||
9A996C5226DDF61F00A4485D /* Base32 */,
|
||||
9A1F47F926E5CF4B000C0E01 /* OneTimePassword */,
|
||||
30ED1776276F8842009BA876 /* ObjectiveGit */,
|
||||
9A2C7D812782CB2F00BD9AF3 /* YubiKit */,
|
||||
);
|
||||
productName = pass;
|
||||
productReference = DC917BD31E2E8231000FDF54 /* Pass.app */;
|
||||
|
|
@ -1300,6 +1338,7 @@
|
|||
3032DA5226DAF4C200A7728C /* XCRemoteSwiftPackageReference "ObjectivePGP" */,
|
||||
9A1F47F826E5CF4B000C0E01 /* XCRemoteSwiftPackageReference "OneTimePassword" */,
|
||||
30ED1775276F8842009BA876 /* XCRemoteSwiftPackageReference "objective-git-swift-package" */,
|
||||
9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */,
|
||||
);
|
||||
productRefGroup = DC917BD41E2E8231000FDF54 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -1491,6 +1530,7 @@
|
|||
30697C3C21F63C990064FCAC /* UITextFieldExtension.swift in Sources */,
|
||||
302E85632125EE550031BA64 /* Constants.swift in Sources */,
|
||||
9A652414244BB33300DA0A41 /* UIAlertActionExtension.swift in Sources */,
|
||||
9A2C7D842783FF5200BD9AF3 /* YubiKeyConnection.swift in Sources */,
|
||||
301F6463216162550071A4CE /* AdditionField.swift in Sources */,
|
||||
30697C3021F63C5A0064FCAC /* AppError.swift in Sources */,
|
||||
30697C2B21F63C5A0064FCAC /* Globals.swift in Sources */,
|
||||
|
|
@ -1513,6 +1553,7 @@
|
|||
30B4C7BA24084AAA008B86F7 /* PasswordGenerator.swift in Sources */,
|
||||
30A1D2A621B2D46100E2D1F7 /* OTPType.swift in Sources */,
|
||||
3032328E22CBD4CD009EBD9C /* CryptographicKeys.swift in Sources */,
|
||||
9A5C6EF42786CA5F0003F340 /* AlertPresenting.swift in Sources */,
|
||||
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
|
||||
30CCA91623258C380048CA51 /* PGPInterface.swift in Sources */,
|
||||
30DAFD4A240985A7002456E7 /* Array+Slices.swift in Sources */,
|
||||
|
|
@ -1527,6 +1568,7 @@
|
|||
30697C3A21F63C990064FCAC /* UIViewControllerExtension.swift in Sources */,
|
||||
30697C2E21F63C5A0064FCAC /* Utils.swift in Sources */,
|
||||
30CCA91823258E760048CA51 /* GopenPGPInterface.swift in Sources */,
|
||||
9A2C7D8B2784139200BD9AF3 /* YubiKeyAPDU.swift in Sources */,
|
||||
30697C4521F63CAB0064FCAC /* Password.swift in Sources */,
|
||||
30697C4421F63CAB0064FCAC /* PasswordEntity.swift in Sources */,
|
||||
30BAC8CD22E3BB9700438475 /* KeyStore.swift in Sources */,
|
||||
|
|
@ -2796,6 +2838,14 @@
|
|||
revision = 8d59e4abba762d0f1e9aed161081f7b3fe21daa0;
|
||||
};
|
||||
};
|
||||
9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Yubico/yubikit-ios";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 4.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
|
@ -2874,6 +2924,31 @@
|
|||
package = 9A1F47F826E5CF4B000C0E01 /* XCRemoteSwiftPackageReference "OneTimePassword" */;
|
||||
productName = OneTimePassword;
|
||||
};
|
||||
9A2C7D812782CB2F00BD9AF3 /* YubiKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */;
|
||||
productName = YubiKit;
|
||||
};
|
||||
9A2C7D852783FF9600BD9AF3 /* YubiKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */;
|
||||
productName = YubiKit;
|
||||
};
|
||||
9A5C6EF82786CE170003F340 /* YubiKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */;
|
||||
productName = YubiKit;
|
||||
};
|
||||
9A5C6EFA2786CE5E0003F340 /* YubiKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9A2C7D802782CB2F00BD9AF3 /* XCRemoteSwiftPackageReference "yubikit-ios" */;
|
||||
productName = YubiKit;
|
||||
};
|
||||
9A5C6F072787F0C20003F340 /* SwiftyUserDefaults */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 3010CB5E26DA4F87008964D2 /* XCRemoteSwiftPackageReference "SwiftyUserDefaults" */;
|
||||
productName = SwiftyUserDefaults;
|
||||
};
|
||||
9A996C5226DDF61F00A4485D /* Base32 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 30A3000C26DA62F4002A734E /* XCRemoteSwiftPackageReference "Base32" */;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,15 @@
|
|||
"revision": "f66bcd04088582c8fbb5cb8554d577e303bae396",
|
||||
"version": "5.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "YubiKit",
|
||||
"repositoryURL": "https://github.com/Yubico/yubikit-ios",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "7e75fe8f057acf9bf7ac134d375783a1d409f79e",
|
||||
"version": "4.1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
}
|
||||
UNUserNotificationCenter.current().delegate = NotificationCenterDispatcher.shared
|
||||
#if !targetEnvironment(simulator)
|
||||
_ = passKit.YubiKeyConnection.shared
|
||||
#endif
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ extension PGPKeyArmorImportTableViewController: PGPKeyImporter {
|
|||
Utils.alert(title: "CannotSave".localize(), message: "SetPublicKey.".localize(), controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
guard !armorPrivateKeyTextView.text.isEmpty else {
|
||||
guard Defaults.isYubiKeyEnabled || !armorPrivateKeyTextView.text.isEmpty else {
|
||||
Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKey.".localize(), controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import passKit
|
||||
|
||||
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
||||
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController, AlertPresenting {
|
||||
@IBOutlet var pgpPublicKeyFile: UITableViewCell!
|
||||
@IBOutlet var pgpPrivateKeyFile: UITableViewCell!
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
|||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
let picker = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .open)
|
||||
let picker = UIDocumentPickerViewController(documentTypes: ["public.item"], in: .open)
|
||||
cell?.isSelected = false
|
||||
if cell == pgpPublicKeyFile {
|
||||
currentlyPicking = .public
|
||||
|
|
@ -71,7 +71,7 @@ extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
|
|||
}
|
||||
} catch {
|
||||
let message = "FileCannotBeImported.".localize(fileName) | "UnderlyingError".localize(error.localizedDescription)
|
||||
Utils.alert(title: "CannotImportFile".localize(), message: message, controller: self)
|
||||
presentFailureAlert(title: "CannotImportFile".localize(), message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,19 +81,20 @@ extension PGPKeyFileImportTableViewController: PGPKeyImporter {
|
|||
static let label = "LoadFromFiles".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
validate(key: publicKey) && validate(key: privateKey)
|
||||
validate(key: publicKey) && (Defaults.isYubiKeyEnabled || validate(key: privateKey))
|
||||
}
|
||||
|
||||
func importKeys() throws {
|
||||
guard let publicKey = publicKey, let privateKey = privateKey else {
|
||||
return
|
||||
if let publicKey = publicKey {
|
||||
try KeyFileManager.PublicPGP.importKey(from: publicKey)
|
||||
}
|
||||
if let privateKey = privateKey {
|
||||
try KeyFileManager.PrivatePGP.importKey(from: privateKey)
|
||||
}
|
||||
try KeyFileManager.PublicPGP.importKey(from: publicKey)
|
||||
try KeyFileManager.PrivatePGP.importKey(from: privateKey)
|
||||
}
|
||||
|
||||
func doAfterImport() {
|
||||
Utils.alert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromLocation.".localize(), controller: self)
|
||||
presentAlert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromLocation.".localize())
|
||||
}
|
||||
|
||||
func saveImportedKeys() {
|
||||
|
|
@ -102,7 +103,7 @@ extension PGPKeyFileImportTableViewController: PGPKeyImporter {
|
|||
|
||||
private func validate(key: String?) -> Bool {
|
||||
guard key != nil else {
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "KeyFileNotSet.".localize(), controller: self)
|
||||
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "KeyFileNotSet.".localize())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import passKit
|
||||
import UIKit
|
||||
|
||||
class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController {
|
||||
class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController, AlertPresenting {
|
||||
@IBOutlet var pgpPublicKeyURLTextField: UITextField!
|
||||
@IBOutlet var pgpPrivateKeyURLTextField: UITextField!
|
||||
|
||||
|
|
@ -24,18 +24,11 @@ class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController {
|
|||
|
||||
@IBAction
|
||||
private func save(_: Any) {
|
||||
guard let publicKeyURLText = pgpPublicKeyURLTextField.text,
|
||||
let publicKeyURL = URL(string: publicKeyURLText),
|
||||
let privateKeyURLText = pgpPrivateKeyURLTextField.text,
|
||||
let privateKeyURL = URL(string: privateKeyURLText) else {
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
|
||||
return
|
||||
pgpPublicKeyURL = validate(pgpKeyURLText: pgpPublicKeyURLTextField.text)
|
||||
|
||||
if !Defaults.isYubiKeyEnabled {
|
||||
pgpPrivateKeyURL = validate(pgpKeyURLText: pgpPrivateKeyURLTextField.text)
|
||||
}
|
||||
if privateKeyURL.scheme?.lowercased() == "http" || publicKeyURL.scheme?.lowercased() == "http" {
|
||||
Utils.alert(title: "HttpNotSecure".localize(), message: "ReallyUseHttp.".localize(), controller: self)
|
||||
}
|
||||
pgpPrivateKeyURL = privateKeyURL
|
||||
pgpPublicKeyURL = publicKeyURL
|
||||
saveImportedKeys()
|
||||
}
|
||||
}
|
||||
|
|
@ -45,35 +38,39 @@ extension PGPKeyURLImportTableViewController: PGPKeyImporter {
|
|||
static let label = "DownloadFromUrl".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
validate(pgpKeyURL: pgpPublicKeyURLTextField.text ?? "")
|
||||
&& validate(pgpKeyURL: pgpPrivateKeyURLTextField.text ?? "")
|
||||
validate(pgpKeyURLText: pgpPublicKeyURLTextField.text) != nil
|
||||
&& (Defaults.isYubiKeyEnabled || validate(pgpKeyURLText: pgpPrivateKeyURLTextField.text ?? "") != nil)
|
||||
}
|
||||
|
||||
func importKeys() throws {
|
||||
Defaults.pgpPrivateKeyURL = pgpPrivateKeyURL
|
||||
Defaults.pgpPublicKeyURL = pgpPublicKeyURL
|
||||
if let pgpPrivateKeyURL = pgpPrivateKeyURL {
|
||||
Defaults.pgpPrivateKeyURL = pgpPrivateKeyURL
|
||||
try KeyFileManager.PrivatePGP.importKey(from: pgpPrivateKeyURL)
|
||||
}
|
||||
|
||||
try KeyFileManager.PublicPGP.importKey(from: Defaults.pgpPublicKeyURL!)
|
||||
try KeyFileManager.PrivatePGP.importKey(from: Defaults.pgpPrivateKeyURL!)
|
||||
if let pgpPublicKeyURL = pgpPublicKeyURL {
|
||||
Defaults.pgpPublicKeyURL = pgpPublicKeyURL
|
||||
try KeyFileManager.PublicPGP.importKey(from: pgpPublicKeyURL)
|
||||
}
|
||||
}
|
||||
|
||||
func doAfterImport() {
|
||||
Utils.alert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromServer.".localize(), controller: self)
|
||||
presentAlert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromServer.".localize())
|
||||
}
|
||||
|
||||
func saveImportedKeys() {
|
||||
performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
}
|
||||
|
||||
private func validate(pgpKeyURL: String) -> Bool {
|
||||
guard let url = URL(string: pgpKeyURL) else {
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
|
||||
return false
|
||||
private func validate(pgpKeyURLText: String?) -> URL? {
|
||||
guard let pgpKeyURL = pgpKeyURLText, let url = URL(string: pgpKeyURL) else {
|
||||
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize())
|
||||
return nil
|
||||
}
|
||||
guard url.scheme == "https" || url.scheme == "http" else {
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "UseEitherHttpsOrHttp.".localize(), controller: self)
|
||||
return false
|
||||
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "UseEitherHttpsOrHttp.".localize())
|
||||
return nil
|
||||
}
|
||||
return true
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
//
|
||||
|
||||
import FavIcon
|
||||
import Gopenpgp
|
||||
import passAutoFillExtension
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate, AlertPresenting {
|
||||
var passwordEntity: PasswordEntity?
|
||||
private var password: Password?
|
||||
private var passwordImage: UIImage?
|
||||
|
|
@ -87,7 +89,15 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
decryptThenShowPassword()
|
||||
}
|
||||
|
||||
private func decryptThenShowPassword(keyID: String? = nil) {
|
||||
private func decryptThenShowPassword() {
|
||||
if Defaults.isYubiKeyEnabled {
|
||||
decryptThenShowPasswordYubiKey()
|
||||
} else {
|
||||
decryptThenShowPasswordLocalKey()
|
||||
}
|
||||
}
|
||||
|
||||
private func decryptThenShowPasswordLocalKey(keyID: String? = nil) {
|
||||
guard let passwordEntity = passwordEntity else {
|
||||
Utils.alert(title: "CannotShowPassword".localize(), message: "PasswordDoesNotExist".localize(), controller: self, completion: {
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
|
|
@ -106,7 +116,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.pgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||
self.decryptThenShowPassword(keyID: action.title)
|
||||
self.decryptThenShowPasswordLocalKey(keyID: action.title)
|
||||
}
|
||||
alert.addAction(selectKey)
|
||||
|
||||
|
|
@ -119,7 +129,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
alert.addAction(
|
||||
UIAlertAction(title: "TryAgain".localize(), style: .default) { _ in
|
||||
self.decryptThenShowPassword()
|
||||
self.decryptThenShowPasswordLocalKey()
|
||||
}
|
||||
)
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
|
|
@ -509,3 +519,66 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension PasswordDetailTableViewController {
|
||||
private func requestYubiKeyPIN(completion: @escaping (String) -> Void) {
|
||||
let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert)
|
||||
alert.addAction(
|
||||
UIAlertAction.cancel { _ in
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
)
|
||||
alert.addAction(
|
||||
UIAlertAction.ok { _ in
|
||||
let pin = alert.textFields?.first?.text ?? ""
|
||||
completion(pin)
|
||||
}
|
||||
)
|
||||
alert.addTextField { textField in
|
||||
textField.isSecureTextEntry = true
|
||||
}
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func handleError(error: AppError) {
|
||||
switch error {
|
||||
case let .yubiKey(yubiKeyError):
|
||||
let errorMessage = yubiKeyError.localizedDescription
|
||||
if #available(iOS 13.0, *) {
|
||||
YubiKitManager.shared.stopNFCConnection(withErrorMessage: errorMessage)
|
||||
DispatchQueue.main.async {
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.presentFailureAlert(message: errorMessage) { _ in
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
DispatchQueue.main.async {
|
||||
self.presentFailureAlert(message: error.localizedDescription) { _ in
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCancellation(_: Error) {
|
||||
DispatchQueue.main.async {
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func decryptThenShowPasswordYubiKey() {
|
||||
guard let passwordEntity = passwordEntity else {
|
||||
handleError(error: AppError.other(message: "PasswordDoesNotExist"))
|
||||
return
|
||||
}
|
||||
Pass.yubiKeyDecrypt(passwordEntity: passwordEntity, requestPIN: requestYubiKeyPIN, errorHandler: handleError, cancellation: handleCancellation) { password in
|
||||
self.password = password
|
||||
self.showPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ extension PasswordNavigationViewController {
|
|||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
|
||||
if identifier == "showPasswordDetail" {
|
||||
guard PGPAgent.shared.isPrepared else {
|
||||
guard Defaults.isYubiKeyEnabled || PGPAgent.shared.isPrepared else {
|
||||
Utils.alert(title: "CannotShowPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self)
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,12 +87,16 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
|
||||
private func setPGPKeyTableViewCellDetailText() {
|
||||
var label = "NotSet".localize()
|
||||
|
||||
let keyID = (try? PGPAgent.shared.getShortKeyID()) ?? []
|
||||
if keyID.count == 1 {
|
||||
label = keyID.first ?? ""
|
||||
} else if keyID.count > 1 {
|
||||
label = "Multiple"
|
||||
}
|
||||
if Defaults.isYubiKeyEnabled {
|
||||
label += "+YubiKey"
|
||||
}
|
||||
pgpKeyTableViewCell.detailTextLabel?.text = label
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +184,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
)
|
||||
}
|
||||
|
||||
optionMenu.addAction(
|
||||
UIAlertAction(title: Defaults.isYubiKeyEnabled ? "✓ YubiKey" : "YubiKey", style: .default) { _ in
|
||||
Defaults.isYubiKeyEnabled.toggle()
|
||||
self.setPGPKeyTableViewCellDetailText()
|
||||
}
|
||||
)
|
||||
|
||||
if Defaults.pgpKeySource != nil {
|
||||
optionMenu.addAction(
|
||||
UIAlertAction(title: "RemovePgpKeys".localize(), style: .destructive) { _ in
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@
|
|||
#define Objective_CBridgingHeader_h
|
||||
|
||||
@import ObjectiveGit;
|
||||
#import <YubiKit.h>
|
||||
|
||||
#endif /* Objective_CBridgingHeader_h */
|
||||
|
|
|
|||
|
|
@ -2,6 +2,21 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
|
||||
<array>
|
||||
<string>A000000527471117</string>
|
||||
<string>A0000006472F0001</string>
|
||||
<string>A0000005272101</string>
|
||||
<string>A000000308</string>
|
||||
<string>D27600012401</string>
|
||||
<string>A000000527200101</string>
|
||||
</array>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
<string>The application needs access to NFC reading to communicate with your YubiKey.</string>
|
||||
<key>UISupportedExternalAccessoryProtocols</key>
|
||||
<array>
|
||||
<string>com.yubico.ylp</string>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,28 @@
|
|||
// Copyright © 2021 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import CryptoTokenKit
|
||||
import Gopenpgp
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
import YubiKit
|
||||
|
||||
func decryptPassword(in controller: UIViewController, with passwordPath: String, using keyID: String? = nil, completion: @escaping ((Password) -> Void)) {
|
||||
func decryptPassword(
|
||||
in controller: UIViewController,
|
||||
with passwordPath: String,
|
||||
using keyID: String? = nil,
|
||||
completion: @escaping ((Password) -> Void)
|
||||
) {
|
||||
// YubiKey is not supported in extension
|
||||
if Defaults.isYubiKeyEnabled {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Error", message: "YubiKey is not supported in extension, please use the Pass app instead.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.ok())
|
||||
controller.present(alert, animated: true)
|
||||
}
|
||||
return
|
||||
}
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
do {
|
||||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: controller)
|
||||
|
|
@ -37,3 +54,144 @@ func decryptPassword(in controller: UIViewController, with passwordPath: String,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias RequestPINAction = (@escaping (String) -> Void) -> Void
|
||||
|
||||
let symmetricKeyIDNameDict: [UInt8: String] = [
|
||||
2: "3des",
|
||||
3: "cast5",
|
||||
7: "aes128",
|
||||
8: "aes192",
|
||||
9: "aes256",
|
||||
]
|
||||
|
||||
private func isEncryptKeyAlgoRSA(_ applicationRelatedData: Data) -> Bool {
|
||||
let tlv = TKBERTLVRecord.sequenceOfRecords(from: applicationRelatedData)!
|
||||
// 0x73: Discretionary data objects
|
||||
for record in TKBERTLVRecord.sequenceOfRecords(from: tlv.first!.value)! where record.tag == 0x73 {
|
||||
// 0xC2: Algorithm attributes decryption, 0x01: RSA
|
||||
for record2 in TKBERTLVRecord.sequenceOfRecords(from: record.value)! where record2.tag == 0xC2 && record2.value.first! == 0x01 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
public func yubiKeyDecrypt(
|
||||
passwordEntity: PasswordEntity,
|
||||
requestPIN: @escaping RequestPINAction,
|
||||
errorHandler: @escaping ((AppError) -> Void),
|
||||
cancellation: @escaping ((_ error: Error) -> Void),
|
||||
completion: @escaping ((Password) -> Void)
|
||||
) {
|
||||
let encryptedDataPath = PasswordStore.shared.storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||
|
||||
guard let encryptedData = try? Data(contentsOf: encryptedDataPath) else {
|
||||
errorHandler(AppError.other(message: "PasswordDoesNotExist".localize()))
|
||||
return
|
||||
}
|
||||
|
||||
// swiftlint:disable closure_body_length
|
||||
requestPIN { pin in
|
||||
// swiftlint:disable closure_body_length
|
||||
passKit.YubiKeyConnection.shared.connection(cancellation: cancellation) { connection in
|
||||
guard let smartCard = connection.smartCardInterface else {
|
||||
errorHandler(AppError.yubiKey(.connection(message: "Failed to get smart card interface.")))
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Select OpenPGP application
|
||||
let selectOpenPGPAPDU = YubiKeyAPDU.selectOpenPGPApplication()
|
||||
smartCard.selectApplication(selectOpenPGPAPDU) { _, error in
|
||||
guard error == nil else {
|
||||
errorHandler(AppError.yubiKey(.selectApplication(message: "Failed to select application.")))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Verify PIN
|
||||
let verifyApdu = YubiKeyAPDU.verify(password: pin)
|
||||
smartCard.executeCommand(verifyApdu) { _, error in
|
||||
guard error == nil else {
|
||||
errorHandler(AppError.yubiKey(.verify(message: "Failed to verify PIN.")))
|
||||
return
|
||||
}
|
||||
|
||||
let applicationRelatedDataApdu = YubiKeyAPDU.get_application_related_data()
|
||||
smartCard.executeCommand(applicationRelatedDataApdu) { data, _ in
|
||||
guard let data = data else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to get application related data.")))
|
||||
return
|
||||
}
|
||||
|
||||
if !isEncryptKeyAlgoRSA(data) {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Encryption key algorithm is not supported. Supported algorithm: RSA.")))
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Decipher
|
||||
let ciphertext = encryptedData
|
||||
var error: NSError?
|
||||
let message = CryptoNewPGPMessage(ciphertext)
|
||||
guard let mpi1 = Gopenpgp.HelperPassGetEncryptedMPI1(message, &error) else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to get encrypted MPI.")))
|
||||
return
|
||||
}
|
||||
|
||||
let decipherApdu = YubiKeyAPDU.decipher(data: mpi1)
|
||||
smartCard.executeCommand(decipherApdu) { data, error in
|
||||
guard let data = data else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to execute decipher.")))
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
YubiKitManager.shared.stopNFCConnection()
|
||||
}
|
||||
guard let algoByte = data.first, let algo = symmetricKeyIDNameDict[algoByte] else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to new session key.")))
|
||||
return
|
||||
}
|
||||
guard let session_key = Gopenpgp.CryptoNewSessionKeyFromToken(data[1 ..< data.count - 2], algo) else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to new session key.")))
|
||||
return
|
||||
}
|
||||
|
||||
var error: NSError?
|
||||
let message = CryptoNewPGPMessage(ciphertext)
|
||||
|
||||
guard let plaintext = Gopenpgp.HelperPassDecryptWithSessionKey(message, session_key, &error)?.data else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to decrypt with session key.")))
|
||||
return
|
||||
}
|
||||
|
||||
guard let plaintext_str = String(data: plaintext, encoding: .utf8) else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to convert plaintext to string.")))
|
||||
return
|
||||
}
|
||||
|
||||
guard let password = try? Password(name: passwordEntity.getName(), url: passwordEntity.getURL(), plainText: plaintext_str) else {
|
||||
errorHandler(AppError.yubiKey(.decipher(message: "Failed to construct password.")))
|
||||
return
|
||||
}
|
||||
|
||||
completion(password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
struct HexEncodingOptions: OptionSet {
|
||||
let rawValue: Int
|
||||
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
func hexEncodedString(options: HexEncodingOptions = []) -> String {
|
||||
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
|
||||
return map { String(format: format, $0) }.joined()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
<dict>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.nfc.readersession.formats</key>
|
||||
<array>
|
||||
<string>NDEF</string>
|
||||
<string>TAG</string>
|
||||
</array>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
<dict>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.nfc.readersession.formats</key>
|
||||
<array>
|
||||
<string>NDEF</string>
|
||||
<string>TAG</string>
|
||||
</array>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
|
|
|
|||
|
|
@ -14,4 +14,10 @@ public extension UIAlertController {
|
|||
alert.addAction(UIAlertAction.cancel())
|
||||
return alert
|
||||
}
|
||||
|
||||
class func showErrorAlert(title: String, message: String, completion: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.ok(handler: completion))
|
||||
return alert
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum AppError: Error, Equatable {
|
||||
case repositoryNotSet
|
||||
case repositoryRemoteBranchNotFound(branchName: String)
|
||||
|
|
@ -18,13 +20,31 @@ public enum AppError: Error, Equatable {
|
|||
case gitPushNotSuccessful
|
||||
case pgpPublicKeyNotFound(keyID: String)
|
||||
case pgpPrivateKeyNotFound(keyID: String)
|
||||
case yubiKey(YubiKeyError)
|
||||
case passwordFileNotFound(path: String)
|
||||
case keyExpiredOrIncompatible
|
||||
case wrongPassphrase
|
||||
case wrongPasswordFilename
|
||||
case decryption
|
||||
case encryption
|
||||
case encoding
|
||||
case unknown
|
||||
case other(message: String)
|
||||
}
|
||||
|
||||
public enum YubiKeyError: Error, Equatable {
|
||||
case connection(message: String)
|
||||
case selectApplication(message: String)
|
||||
case verify(message: String)
|
||||
case decipher(message: String)
|
||||
}
|
||||
|
||||
extension YubiKeyError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case let .connection(message), let .decipher(message), let .selectApplication(message), let .verify(message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppError: LocalizedError {
|
||||
|
|
@ -36,6 +56,8 @@ extension AppError: LocalizedError {
|
|||
return localizationKey.localize(name)
|
||||
case let .pgpPrivateKeyNotFound(keyID), let .pgpPublicKeyNotFound(keyID):
|
||||
return localizationKey.localize(keyID)
|
||||
case let .other(message):
|
||||
return message.localize()
|
||||
default:
|
||||
return localizationKey.localize()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public extension DefaultsKeys {
|
|||
var pgpKeySource: DefaultsKey<KeySource?> { .init("pgpKeySource") }
|
||||
var pgpPublicKeyURL: DefaultsKey<URL?> { .init("pgpPublicKeyURL") }
|
||||
var pgpPrivateKeyURL: DefaultsKey<URL?> { .init("pgpPrivateKeyURL") }
|
||||
var isYubiKeyEnabled: DefaultsKey<Bool> { .init("isYubiKeyEnabled", defaultValue: false) }
|
||||
|
||||
// Keep them for legacy reasons.
|
||||
var pgpPublicKeyArmor: DefaultsKey<String?> { .init("pgpPublicKeyArmor") }
|
||||
|
|
|
|||
54
passKit/Helpers/YubiKeyAPDU.swift
Normal file
54
passKit/Helpers/YubiKeyAPDU.swift
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// YubiKeyAPDU.swift
|
||||
// passKit
|
||||
//
|
||||
// Copyright © 2022 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import YubiKit
|
||||
|
||||
public enum YubiKeyAPDU {
|
||||
public static func selectOpenPGPApplication() -> YKFSelectApplicationAPDU {
|
||||
let selectOpenPGPAPDU = YKFSelectApplicationAPDU(data: Data([0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]))!
|
||||
return selectOpenPGPAPDU
|
||||
}
|
||||
|
||||
public static func verify(password: String) -> YKFAPDU {
|
||||
let pw1: [UInt8] = Array(password.utf8)
|
||||
var apdu: [UInt8] = []
|
||||
apdu += [0x00] // CLA
|
||||
apdu += [0x20] // INS: VERIFY
|
||||
apdu += [0x00] // P1
|
||||
apdu += [0x82] // P2: PW1
|
||||
apdu += withUnsafeBytes(of: UInt8(pw1.count).bigEndian, Array.init)
|
||||
apdu += pw1
|
||||
let verifyApdu = YKFAPDU(data: Data(apdu))!
|
||||
return verifyApdu
|
||||
}
|
||||
|
||||
public static func decipher(data: Data) -> YKFAPDU {
|
||||
var apdu: [UInt8] = []
|
||||
apdu += [0x00] // CLA
|
||||
apdu += [0x2A, 0x80, 0x86] // INS, P1, P2: PSO.DECIPHER
|
||||
// Lc, An extended Lc field consists of three bytes:
|
||||
// one byte set to '00' followed by two bytes not set to '0000' (1 to 65535 dec.).
|
||||
apdu += [0x00] + withUnsafeBytes(of: UInt16(data.count + 1).bigEndian, Array.init)
|
||||
// Padding indicator byte (00) for RSA or (02) for AES followed by cryptogram Cipher DO 'A6' for ECDH
|
||||
apdu += [0x00]
|
||||
apdu += data
|
||||
apdu += [0x02, 0x00]
|
||||
let decipherApdu = YKFAPDU(data: Data(apdu))!
|
||||
|
||||
return decipherApdu
|
||||
}
|
||||
|
||||
public static func get_application_related_data() -> YKFAPDU {
|
||||
var apdu: [UInt8] = []
|
||||
apdu += [0x00] // CLA
|
||||
apdu += [0xCA] // INS: GET DATA
|
||||
apdu += [0x00]
|
||||
apdu += [0x6E] // P2: application related data
|
||||
apdu += [0x00]
|
||||
return YKFAPDU(data: Data(apdu))!
|
||||
}
|
||||
}
|
||||
63
passKit/Helpers/YubiKeyConnection.swift
Normal file
63
passKit/Helpers/YubiKeyConnection.swift
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// YubiKeyConnection.swift
|
||||
// passKit
|
||||
//
|
||||
// Copyright © 2022 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import YubiKit
|
||||
|
||||
public class YubiKeyConnection: NSObject {
|
||||
public static let shared = YubiKeyConnection()
|
||||
|
||||
var accessoryConnection: YKFAccessoryConnection?
|
||||
var nfcConnection: YKFNFCConnection?
|
||||
var connectionCallback: ((_ connection: YKFConnectionProtocol) -> Void)?
|
||||
var cancellationCallback: ((_ error: Error) -> Void)?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
YubiKitManager.shared.delegate = self
|
||||
YubiKitManager.shared.startAccessoryConnection()
|
||||
}
|
||||
|
||||
public func connection(cancellation: @escaping (_ error: Error) -> Void, completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {
|
||||
if let connection = accessoryConnection {
|
||||
completion(connection)
|
||||
} else {
|
||||
connectionCallback = completion
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
YubiKitManager.shared.startNFCConnection()
|
||||
}
|
||||
}
|
||||
cancellationCallback = cancellation
|
||||
}
|
||||
}
|
||||
|
||||
extension YubiKeyConnection: YKFManagerDelegate {
|
||||
public func didConnectNFC(_ connection: YKFNFCConnection) {
|
||||
nfcConnection = connection
|
||||
if let callback = connectionCallback {
|
||||
callback(connection)
|
||||
}
|
||||
}
|
||||
|
||||
public func didDisconnectNFC(_: YKFNFCConnection, error _: Error?) {
|
||||
nfcConnection = nil
|
||||
}
|
||||
|
||||
public func didConnectAccessory(_ connection: YKFAccessoryConnection) {
|
||||
accessoryConnection = connection
|
||||
}
|
||||
|
||||
public func didDisconnectAccessory(_: YKFAccessoryConnection, error _: Error?) {
|
||||
accessoryConnection = nil
|
||||
}
|
||||
|
||||
public func didFailConnectingNFC(_ error: Error) {
|
||||
if let callback = cancellationCallback {
|
||||
callback(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ public extension PasswordEntity {
|
|||
if let path = getPath().stringByAddingPercentEncodingForRFC3986(), let url = URL(string: path) {
|
||||
return url
|
||||
}
|
||||
throw AppError.unknown
|
||||
throw AppError.other(message: "cannot decode URL")
|
||||
}
|
||||
|
||||
// XXX: define some getters to get core data, we need to consider
|
||||
|
|
|
|||
55
passKit/Protocols/AlertPresenting.swift
Normal file
55
passKit/Protocols/AlertPresenting.swift
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// AlertPresenting.swift
|
||||
// pass
|
||||
//
|
||||
// Copyright © 2022 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public typealias AlertAction = (UIAlertAction) -> Void
|
||||
|
||||
public protocol AlertPresenting {
|
||||
func presentAlert(title: String, message: String)
|
||||
func presentFailureAlert(title: String?, message: String, action: AlertAction?)
|
||||
func presentAlertWithAction(title: String, message: String, action: AlertAction?)
|
||||
}
|
||||
|
||||
public extension AlertPresenting where Self: UIViewController {
|
||||
func presentAlert(title: String, message: String) {
|
||||
presentAlert(
|
||||
title: title,
|
||||
message: message,
|
||||
actions: [UIAlertAction(title: "OK", style: .cancel, handler: nil)]
|
||||
)
|
||||
}
|
||||
|
||||
// swiftlint:disable function_default_parameter_at_end
|
||||
func presentFailureAlert(title: String? = nil, message: String, action: AlertAction? = nil) {
|
||||
let title = title ?? "Error"
|
||||
presentAlert(
|
||||
title: title,
|
||||
message: message,
|
||||
actions: [UIAlertAction(title: "OK", style: .cancel, handler: action)]
|
||||
)
|
||||
}
|
||||
|
||||
func presentAlertWithAction(title: String, message: String, action: AlertAction?) {
|
||||
presentAlert(
|
||||
title: title,
|
||||
message: message,
|
||||
actions: [
|
||||
UIAlertAction(title: "Yes", style: .default, handler: action),
|
||||
UIAlertAction(title: "No", style: .cancel, handler: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
private func presentAlert(title: String, message: String, actions: [UIAlertAction] = []) {
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
actions.forEach { action in
|
||||
alertController.addAction(action)
|
||||
}
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
set -euox pipefail
|
||||
|
||||
GOPENPGP_VERSION="v2.1.10"
|
||||
GOPENPGP_VERSION="passforios"
|
||||
|
||||
export GOPATH="$(pwd)/go"
|
||||
export PATH="$PATH:$GOPATH/bin"
|
||||
|
|
@ -18,13 +18,11 @@ go env -w GO111MODULE=auto
|
|||
go get golang.org/x/mobile/cmd/gomobile
|
||||
gomobile init
|
||||
|
||||
git clone --depth 1 --branch "$GOPENPGP_VERSION" https://github.com/ProtonMail/gopenpgp.git "$GOPENPGP_PATH"
|
||||
|
||||
git apply patch/gnu-dummy.patch --directory "$GOPENPGP_PATH"
|
||||
git clone --depth 1 --branch "$GOPENPGP_VERSION" https://github.com/mssun/gopenpgp.git "$GOPENPGP_PATH"
|
||||
|
||||
sed -i '' 's/build android/echo "Skipping Android build."/g' "$GOPENPGP_PATH/build.sh"
|
||||
|
||||
(cd "$GOPENPGP_PATH" && ./build.sh)
|
||||
|
||||
cp -R "$GOPENPGP_PATH/dist/Gopenpgp.xcframework" "$OUTPUT_PATH"
|
||||
cp -r "$GOPENPGP_PATH/dist/Gopenpgp.xcframework" "$OUTPUT_PATH"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue