Initial implementation of using YubiKey for decryption (#533)

This commit is contained in:
Mingshen Sun 2022-01-09 21:38:39 -08:00 committed by GitHub
parent 13804b79e6
commit 955e50c3d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 606 additions and 118 deletions

View file

@ -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

View file

@ -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" */;

View file

@ -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"
}
}
]
},

View file

@ -35,6 +35,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
UNUserNotificationCenter.current().delegate = NotificationCenterDispatcher.shared
#if !targetEnvironment(simulator)
_ = passKit.YubiKeyConnection.shared
#endif
return true
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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()
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -10,5 +10,6 @@
#define Objective_CBridgingHeader_h
@import ObjectiveGit;
#import <YubiKit.h>
#endif /* Objective_CBridgingHeader_h */

View file

@ -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>

View file

@ -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()
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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
}
}

View file

@ -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()
}

View file

@ -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") }

View 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))!
}
}

View 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)
}
}
}

View file

@ -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

View 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)
}
}

View file

@ -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"