diff --git a/Cartfile b/Cartfile index dba1439..60924ca 100644 --- a/Cartfile +++ b/Cartfile @@ -1,6 +1,9 @@ github "SVProgressHUD/SVProgressHUD" github "radex/SwiftyUserDefaults" github "libgit2/objective-git" -github "bitserf/FavIcon" +# github "zahlz/SwiftPasscodeLock" "master" +github "yishilin14/SwiftPasscodeLock" "app-extension-support" +github "mssun/FavIcon" "master" github "kishikawakatsumi/KeychainAccess" github "mattrubin/OneTimePassword" +github "jpsim/Yams" diff --git a/Podfile b/Podfile index a95879a..aa80ddc 100644 --- a/Podfile +++ b/Podfile @@ -2,7 +2,7 @@ platform :ios, '10.2' use_frameworks! target 'passKit' do - pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git', :tag => '0.10.0-beta2' + pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git', :tag => '0.10.0-beta3' target 'pass' do inherit! :search_paths end diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 008af7e..2a9d6b7 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 18F19A67B0C07F13C17169E0 /* Pods_pass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A5620D17DF5E86B61761D0E /* Pods_pass.framework */; }; 23B82F0228254275DBA609E7 /* Pods_passExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B975797E0F0B7476CADD6A7D /* Pods_passExtension.framework */; }; + 3012B06D2039D6E400BE1793 /* Yams.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3012B06C2039D6E400BE1793 /* Yams.framework */; }; 61326CDA7A73757FB68DCB04 /* Pods_passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB3F5541E51ADC8C6B56642 /* Pods_passKit.framework */; }; A20691F41F2A3D0E0096483D /* SecurePasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */; }; A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */; }; @@ -157,6 +158,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3012B06C2039D6E400BE1793 /* Yams.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Yams.framework; path = Carthage/Build/iOS/Yams.framework; sourceTree = ""; }; 31C3033E8868D05B2C55C8B1 /* Pods-passExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.debug.xcconfig"; sourceTree = ""; }; 3A5620D17DF5E86B61761D0E /* Pods_pass.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_pass.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 666769E0B255666D02945C15 /* Pods-passKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-passKitTests/Pods-passKitTests.release.xcconfig"; sourceTree = ""; }; @@ -317,6 +319,7 @@ A260758D1EEC6F34005DB03E /* passKit.framework in Frameworks */, DCC408C71E307DBB00F29B0E /* SVProgressHUD.framework in Frameworks */, 18F19A67B0C07F13C17169E0 /* Pods_pass.framework in Frameworks */, + 3012B06D2039D6E400BE1793 /* Yams.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -551,6 +554,7 @@ DC917BED1E2F38C4000FDF54 /* Frameworks */ = { isa = PBXGroup; children = ( + 3012B06C2039D6E400BE1793 /* Yams.framework */, A2A61C101EEF8E3500CFE063 /* libPods-passKit.a */, A2A61C0C1EEF8DFE00CFE063 /* libPods-passExtension.a */, A2227D541EEE5E78002A69A9 /* libObjectivePGP.a */, @@ -997,6 +1001,7 @@ "$(SRCROOT)/Carthage/Build/iOS/KeychainAccess.framework", "$(SRCROOT)/Carthage/Build/iOS/OneTimePassword.framework", "$(SRCROOT)/Carthage/Build/iOS/Base32.framework", + "$(SRCROOT)/Carthage/Build/iOS/Yams.framework", ); name = "Run Script"; outputPaths = ( @@ -1450,6 +1455,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2"; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1501,6 +1507,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2"; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; diff --git a/pass/Controllers/OpenSourceComponentsTableViewController.swift b/pass/Controllers/OpenSourceComponentsTableViewController.swift index b67fb40..b220434 100644 --- a/pass/Controllers/OpenSourceComponentsTableViewController.swift +++ b/pass/Controllers/OpenSourceComponentsTableViewController.swift @@ -35,6 +35,9 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController { ["SVProgressHUD", "https://github.com/SVProgressHUD/SVProgressHUD", "https://github.com/SVProgressHUD/SVProgressHUD/blob/master/LICENSE.txt"], + ["Yams", + "https://github.com/jpsim/Yams", + "https://github.com/jpsim/Yams/blob/master/LICENSE"], ] override func viewDidLoad() { diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index 7ed2ed6..e03eb48 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -28,7 +28,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl private var navigationItemTitle: String? private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()} - private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""] + private var sectionFooterTitles = ["", "", "Use YAML format for additional fields.", ""] private let nameSection = 0 private let passwordSection = 1 private let additionsSection = 2 diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index cae9fee..d610e84 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -10,6 +10,7 @@ import Foundation import SwiftyUserDefaults import OneTimePassword import Base32 +import Yams struct AdditionField { var title: String @@ -24,6 +25,8 @@ enum PasswordChange: Int { public class Password { public static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"] + private static let OTPAUTH = "otpauth" + private static let OTPAUTH_URL_START = "\(OTPAUTH)://" public var name = "" public var url: URL? @@ -84,29 +87,34 @@ public class Password { additions.removeAll() // split the plain text - let plainTextSplit = plainText.split(maxSplits: 1, omittingEmptySubsequences: false) { + let plainTextSplit = self.plainText.split(omittingEmptySubsequences: true) { $0 == "\n" || $0 == "\r\n" }.map(String.init) // get password password = plainTextSplit.first ?? "" - // get additonal fields - if plainTextSplit.count == 2 { - var unknownIndex = 0 - plainTextSplit[1].enumerateLines() { line, _ in - if !line.isEmpty { - var (key, value) = Password.getKeyValuePair(from: line) - if key == nil { - unknownIndex += 1 - key = "unknown \(unknownIndex)" - } - self.additions.append((key!, value)) - } - } - } + // get remaining lines + let additionalLines = plainTextSplit[1...] - // check whether the first line of the plainText looks like an otp entry + // separate normal lines (no otp tokens) + let normalAdditionalLines = additionalLines.filter { + !$0.hasPrefix(Password.OTPAUTH_URL_START) + }.joined(separator: "\n") + + // try to interpret the text format as YAML first + do { + try getAdditionalFields(fromYaml: normalAdditionalLines) + } + catch { + getAdditionalFields(fromPlainText: normalAdditionalLines) + } + + // get and append otp tokens + let otpAdditionalLines = additionalLines.filter { $0.hasPrefix(Password.OTPAUTH_URL_START) } + otpAdditionalLines.forEach { self.additions.append((Password.OTPAUTH, $0)) } + + // check whether the first line looks like an otp entry let (key, value) = Password.getKeyValuePair(from: self.password) if Password.otpKeywords.contains(key ?? "") { firstLineIsOTPField = true @@ -118,6 +126,26 @@ public class Password { // construct the otp token self.updateOtpToken() } + + private func getAdditionalFields(fromYaml: String) throws { + guard !fromYaml.isEmpty else { return } + let yamlFile = try Yams.load(yaml: fromYaml) as! [String: Any] + additions.append(contentsOf: yamlFile.map { ($0, String(describing: $1)) }) + } + + private func getAdditionalFields(fromPlainText: String) { + var unknownIndex = 0 + fromPlainText.enumerateLines() { line, _ in + if !line.isEmpty { + var (key, value) = Password.getKeyValuePair(from: line) + if key == nil { + unknownIndex += 1 + key = "unknown \(unknownIndex)" + } + self.additions.append((key!, value)) + } + } + } public func getFilteredAdditions() -> [(String, String)] { var filteredAdditions = [(String, String)]() @@ -153,8 +181,8 @@ public class Password { // no ": " found, or empty on both sides of ": " value = line // otpauth special case - if value.hasPrefix("otpauth://") { - key = "otpauth" + if value.hasPrefix(Password.OTPAUTH_URL_START) { + key = Password.OTPAUTH } } else { if !items[0].isEmpty { @@ -221,9 +249,9 @@ public class Password { self.otpToken = nil // get otpauth, if we are able to generate a token, return - if var otpauthString = getAdditionValue(withKey: "otpauth") { - if !otpauthString.hasPrefix("otpauth:") { - otpauthString = "otpauth:\(otpauthString)" + if var otpauthString = getAdditionValue(withKey: Password.OTPAUTH) { + if !otpauthString.hasPrefix("\(Password.OTPAUTH):") { + otpauthString = "\(Password.OTPAUTH):\(otpauthString)" } if let otpauthUrl = URL(string: otpauthString), let token = Token(url: otpauthUrl) { @@ -347,7 +375,7 @@ public class Password { let (key, _) = Password.getKeyValuePair(from: line) if !Password.otpKeywords.contains(key ?? "") { lines.append(line) - } else if key == "otpauth" && newOtpauth != nil { + } else if key == Password.OTPAUTH && newOtpauth != nil { lines.append(newOtpauth!) // set to nil to prevent duplication newOtpauth = nil