diff --git a/Gemfile.lock b/Gemfile.lock index 247f8a0..26cba89 100644 --- a/Gemfile.lock +++ b/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 diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 09fe3ed..0139052 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -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 = ""; }; 9A1EF0B524C50EE00074FEAC /* passBetaExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaExtension.entitlements; sourceTree = ""; }; 9A1EF0B624C50FEA0074FEAC /* passBetaShortcuts.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passBetaShortcuts.entitlements; sourceTree = ""; }; + 9A2C7D832783FF5200BD9AF3 /* YubiKeyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YubiKeyConnection.swift; sourceTree = ""; }; + 9A2C7D8A2784139200BD9AF3 /* YubiKeyAPDU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YubiKeyAPDU.swift; sourceTree = ""; }; 9A55C184259E8C5600FA8FD9 /* PasswordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordsViewController.swift; sourceTree = ""; }; 9A5865EF25AA944B006719C2 /* SearchPassword.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SearchPassword.storyboard; sourceTree = ""; }; 9A58665025AADB76006719C2 /* CredentialProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProvider.swift; sourceTree = ""; }; + 9A5C6EEF2786C8710003F340 /* AlertPresenting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenting.swift; sourceTree = ""; }; 9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertActionExtension.swift; sourceTree = ""; }; 9A74D2DF277D2F8C00F7BC44 /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtension.swift; sourceTree = ""; }; 9A8F9EBC259EA4C50027CE15 /* PasswordsTableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordsTableDataSource.swift; sourceTree = ""; }; @@ -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 = ""; }; + 9A5C6EF32786C9C00003F340 /* Protocols */ = { + isa = PBXGroup; + children = ( + 9A5C6EEF2786C8710003F340 /* AlertPresenting.swift */, + ); + path = Protocols; + sourceTree = ""; + }; 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" */; diff --git a/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 39a1af9..253dc27 100644 --- a/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/pass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" + } } ] }, diff --git a/pass/AppDelegate.swift b/pass/AppDelegate.swift index 4412ada..a65ca6e 100644 --- a/pass/AppDelegate.swift +++ b/pass/AppDelegate.swift @@ -35,6 +35,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } UNUserNotificationCenter.current().delegate = NotificationCenterDispatcher.shared + #if !targetEnvironment(simulator) + _ = passKit.YubiKeyConnection.shared + #endif return true } diff --git a/pass/Controllers/PGPKeyArmorImportTableViewController.swift b/pass/Controllers/PGPKeyArmorImportTableViewController.swift index ac40769..fef56a3 100644 --- a/pass/Controllers/PGPKeyArmorImportTableViewController.swift +++ b/pass/Controllers/PGPKeyArmorImportTableViewController.swift @@ -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 } diff --git a/pass/Controllers/PGPKeyFIleImportTableViewController.swift b/pass/Controllers/PGPKeyFIleImportTableViewController.swift index 6a6322a..58dd834 100644 --- a/pass/Controllers/PGPKeyFIleImportTableViewController.swift +++ b/pass/Controllers/PGPKeyFIleImportTableViewController.swift @@ -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 diff --git a/pass/Controllers/PGPKeyURLImportTableViewController.swift b/pass/Controllers/PGPKeyURLImportTableViewController.swift index 9f5e4f3..30c3dec 100644 --- a/pass/Controllers/PGPKeyURLImportTableViewController.swift +++ b/pass/Controllers/PGPKeyURLImportTableViewController.swift @@ -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 } } diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index d526499..cd1e4d4 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -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() + } + } +} diff --git a/pass/Controllers/PasswordNavigationViewController.swift b/pass/Controllers/PasswordNavigationViewController.swift index 60dc7d4..7ac2839 100644 --- a/pass/Controllers/PasswordNavigationViewController.swift +++ b/pass/Controllers/PasswordNavigationViewController.swift @@ -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 } diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index 5463a2a..1fe9aee 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -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 diff --git a/pass/Helpers/Objective-CBridgingHeader.h b/pass/Helpers/Objective-CBridgingHeader.h index ff807e5..3a71b73 100644 --- a/pass/Helpers/Objective-CBridgingHeader.h +++ b/pass/Helpers/Objective-CBridgingHeader.h @@ -10,5 +10,6 @@ #define Objective_CBridgingHeader_h @import ObjectiveGit; +#import #endif /* Objective_CBridgingHeader_h */ diff --git a/pass/Info.plist b/pass/Info.plist index 7faefec..f5a7106 100644 --- a/pass/Info.plist +++ b/pass/Info.plist @@ -2,6 +2,21 @@ + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + A000000527471117 + A0000006472F0001 + A0000005272101 + A000000308 + D27600012401 + A000000527200101 + + NFCReaderUsageDescription + The application needs access to NFC reading to communicate with your YubiKey. + UISupportedExternalAccessoryProtocols + + com.yubico.ylp + CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/pass/Services/PasswordDecryptor.swift b/pass/Services/PasswordDecryptor.swift index 4a9c6a1..0b6d067 100644 --- a/pass/Services/PasswordDecryptor.swift +++ b/pass/Services/PasswordDecryptor.swift @@ -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() + } +} diff --git a/pass/pass.entitlements b/pass/pass.entitlements index 1444b8a..d915341 100644 --- a/pass/pass.entitlements +++ b/pass/pass.entitlements @@ -4,6 +4,11 @@ com.apple.developer.authentication-services.autofill-credential-provider + com.apple.developer.nfc.readersession.formats + + NDEF + TAG + com.apple.developer.siri com.apple.security.application-groups diff --git a/pass/passBeta.entitlements b/pass/passBeta.entitlements index 5e2efc7..e34057c 100644 --- a/pass/passBeta.entitlements +++ b/pass/passBeta.entitlements @@ -4,6 +4,11 @@ com.apple.developer.authentication-services.autofill-credential-provider + com.apple.developer.nfc.readersession.formats + + NDEF + TAG + com.apple.developer.siri com.apple.security.application-groups diff --git a/passKit/Extensions/UIAlertControllerExtension.swift b/passKit/Extensions/UIAlertControllerExtension.swift index 962c707..a3617ff 100644 --- a/passKit/Extensions/UIAlertControllerExtension.swift +++ b/passKit/Extensions/UIAlertControllerExtension.swift @@ -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 + } } diff --git a/passKit/Helpers/AppError.swift b/passKit/Helpers/AppError.swift index 08dbace..a8a4ca1 100644 --- a/passKit/Helpers/AppError.swift +++ b/passKit/Helpers/AppError.swift @@ -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() } diff --git a/passKit/Helpers/DefaultsKeys.swift b/passKit/Helpers/DefaultsKeys.swift index a0bbf81..a96b206 100644 --- a/passKit/Helpers/DefaultsKeys.swift +++ b/passKit/Helpers/DefaultsKeys.swift @@ -26,6 +26,7 @@ public extension DefaultsKeys { var pgpKeySource: DefaultsKey { .init("pgpKeySource") } var pgpPublicKeyURL: DefaultsKey { .init("pgpPublicKeyURL") } var pgpPrivateKeyURL: DefaultsKey { .init("pgpPrivateKeyURL") } + var isYubiKeyEnabled: DefaultsKey { .init("isYubiKeyEnabled", defaultValue: false) } // Keep them for legacy reasons. var pgpPublicKeyArmor: DefaultsKey { .init("pgpPublicKeyArmor") } diff --git a/passKit/Helpers/YubiKeyAPDU.swift b/passKit/Helpers/YubiKeyAPDU.swift new file mode 100644 index 0000000..7082111 --- /dev/null +++ b/passKit/Helpers/YubiKeyAPDU.swift @@ -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))! + } +} diff --git a/passKit/Helpers/YubiKeyConnection.swift b/passKit/Helpers/YubiKeyConnection.swift new file mode 100644 index 0000000..fe75940 --- /dev/null +++ b/passKit/Helpers/YubiKeyConnection.swift @@ -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) + } + } +} diff --git a/passKit/Models/PasswordEntity.swift b/passKit/Models/PasswordEntity.swift index 84792b8..bbeb861 100644 --- a/passKit/Models/PasswordEntity.swift +++ b/passKit/Models/PasswordEntity.swift @@ -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 diff --git a/passKit/Protocols/AlertPresenting.swift b/passKit/Protocols/AlertPresenting.swift new file mode 100644 index 0000000..ec745da --- /dev/null +++ b/passKit/Protocols/AlertPresenting.swift @@ -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) + } +} diff --git a/scripts/gopenpgp_build.sh b/scripts/gopenpgp_build.sh index f1997e3..f1bac2a 100755 --- a/scripts/gopenpgp_build.sh +++ b/scripts/gopenpgp_build.sh @@ -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"