diff --git a/passKitTests/Fixtures/password-store-empty.git/HEAD b/passKitTests/Fixtures/password-store-empty.git/HEAD new file mode 100644 index 0000000..b870d82 --- /dev/null +++ b/passKitTests/Fixtures/password-store-empty.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/passKitTests/Fixtures/password-store-empty.git/config b/passKitTests/Fixtures/password-store-empty.git/config new file mode 100644 index 0000000..e6da231 --- /dev/null +++ b/passKitTests/Fixtures/password-store-empty.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/passKitTests/Fixtures/password-store-empty.git/description b/passKitTests/Fixtures/password-store-empty.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/passKitTests/Fixtures/password-store-empty.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/passKitTests/Fixtures/password-store.git/info/exclude b/passKitTests/Fixtures/password-store-empty.git/info/exclude similarity index 100% rename from passKitTests/Fixtures/password-store.git/info/exclude rename to passKitTests/Fixtures/password-store-empty.git/info/exclude diff --git a/passKitTests/Fixtures/password-store-empty.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/passKitTests/Fixtures/password-store-empty.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 0000000..adf6411 Binary files /dev/null and b/passKitTests/Fixtures/password-store-empty.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/passKitTests/Fixtures/password-store-empty.git/objects/4e/b23a2d659dcaa6fbc01ada57aed6d1fbeb0520 b/passKitTests/Fixtures/password-store-empty.git/objects/4e/b23a2d659dcaa6fbc01ada57aed6d1fbeb0520 new file mode 100644 index 0000000..c1ae0b0 Binary files /dev/null and b/passKitTests/Fixtures/password-store-empty.git/objects/4e/b23a2d659dcaa6fbc01ada57aed6d1fbeb0520 differ diff --git a/passKitTests/Fixtures/password-store-empty.git/objects/50/96ac11d1376ea9b22ddedac1130f45ec618d11 b/passKitTests/Fixtures/password-store-empty.git/objects/50/96ac11d1376ea9b22ddedac1130f45ec618d11 new file mode 100644 index 0000000..5f9c0af Binary files /dev/null and b/passKitTests/Fixtures/password-store-empty.git/objects/50/96ac11d1376ea9b22ddedac1130f45ec618d11 differ diff --git a/passKitTests/Fixtures/password-store-empty.git/objects/ae/a863facd4acba3b4862e3f42847da1000e486a b/passKitTests/Fixtures/password-store-empty.git/objects/ae/a863facd4acba3b4862e3f42847da1000e486a new file mode 100644 index 0000000..7dbbc42 Binary files /dev/null and b/passKitTests/Fixtures/password-store-empty.git/objects/ae/a863facd4acba3b4862e3f42847da1000e486a differ diff --git a/passKitTests/Fixtures/password-store-empty.git/objects/f0/95bb4897e4cd58faadfe4d4f678fb697be3ffd b/passKitTests/Fixtures/password-store-empty.git/objects/f0/95bb4897e4cd58faadfe4d4f678fb697be3ffd new file mode 100644 index 0000000..3e561dd --- /dev/null +++ b/passKitTests/Fixtures/password-store-empty.git/objects/f0/95bb4897e4cd58faadfe4d4f678fb697be3ffd @@ -0,0 +1,3 @@ +xM +0@a9 \zi:@BzzK}eN*3Sv48J=Eo؎D҈k"EMnKNRy,_pr_9p;qETھ +&rɒi@ \ No newline at end of file diff --git a/passKitTests/Fixtures/password-store-empty.git/packed-refs b/passKitTests/Fixtures/password-store-empty.git/packed-refs new file mode 100644 index 0000000..432aeb4 --- /dev/null +++ b/passKitTests/Fixtures/password-store-empty.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +f095bb4897e4cd58faadfe4d4f678fb697be3ffd refs/heads/main diff --git a/passKitTests/Fixtures/password-store.git/FETCH_HEAD b/passKitTests/Fixtures/password-store-with-gpgid.git/FETCH_HEAD similarity index 100% rename from passKitTests/Fixtures/password-store.git/FETCH_HEAD rename to passKitTests/Fixtures/password-store-with-gpgid.git/FETCH_HEAD diff --git a/passKitTests/Fixtures/password-store.git/HEAD b/passKitTests/Fixtures/password-store-with-gpgid.git/HEAD similarity index 100% rename from passKitTests/Fixtures/password-store.git/HEAD rename to passKitTests/Fixtures/password-store-with-gpgid.git/HEAD diff --git a/passKitTests/Fixtures/password-store.git/config b/passKitTests/Fixtures/password-store-with-gpgid.git/config similarity index 100% rename from passKitTests/Fixtures/password-store.git/config rename to passKitTests/Fixtures/password-store-with-gpgid.git/config diff --git a/passKitTests/Fixtures/password-store-with-gpgid.git/description b/passKitTests/Fixtures/password-store-with-gpgid.git/description new file mode 100644 index 0000000..5536ff7 --- /dev/null +++ b/passKitTests/Fixtures/password-store-with-gpgid.git/description @@ -0,0 +1 @@ +Example password store repository for passforios tests with .gpg-id files. diff --git a/passKitTests/Fixtures/password-store-with-gpgid.git/info/exclude b/passKitTests/Fixtures/password-store-with-gpgid.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/passKitTests/Fixtures/password-store-with-gpgid.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx b/passKitTests/Fixtures/password-store-with-gpgid.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx similarity index 100% rename from passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx rename to passKitTests/Fixtures/password-store-with-gpgid.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.idx diff --git a/passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.pack b/passKitTests/Fixtures/password-store-with-gpgid.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.pack similarity index 100% rename from passKitTests/Fixtures/password-store.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.pack rename to passKitTests/Fixtures/password-store-with-gpgid.git/objects/pack/pack-6a8dbb253e7642cc425de97363624aab04882615.pack diff --git a/passKitTests/Fixtures/password-store.git/packed-refs b/passKitTests/Fixtures/password-store-with-gpgid.git/packed-refs similarity index 100% rename from passKitTests/Fixtures/password-store.git/packed-refs rename to passKitTests/Fixtures/password-store-with-gpgid.git/packed-refs diff --git a/passKitTests/Fixtures/password-store.git/refs/remotes/origin/master b/passKitTests/Fixtures/password-store-with-gpgid.git/refs/remotes/origin/master similarity index 100% rename from passKitTests/Fixtures/password-store.git/refs/remotes/origin/master rename to passKitTests/Fixtures/password-store-with-gpgid.git/refs/remotes/origin/master diff --git a/passKitTests/Fixtures/password-store.git/description b/passKitTests/Fixtures/password-store.git/description deleted file mode 100644 index 9258d15..0000000 --- a/passKitTests/Fixtures/password-store.git/description +++ /dev/null @@ -1 +0,0 @@ -Example password store repository for passforios tests. diff --git a/passKitTests/Models/PasswordStoreTest.swift b/passKitTests/Models/PasswordStoreTest.swift index 5e12555..205ab13 100644 --- a/passKitTests/Models/PasswordStoreTest.swift +++ b/passKitTests/Models/PasswordStoreTest.swift @@ -13,7 +13,6 @@ import XCTest @testable import passKit final class PasswordStoreTest: XCTestCase { - private lazy var remoteRepoURL: URL = Bundle(for: type(of: self)).resourceURL!.appendingPathComponent("Fixtures/password-store.git") private let localRepoURL: URL = Globals.sharedContainerURL.appendingPathComponent("Library/password-store-test/") private var passwordStore: PasswordStore! = nil @@ -25,10 +24,12 @@ final class PasswordStoreTest: XCTestCase { override func tearDown() { passwordStore.erase() passwordStore = nil + + Defaults.removeAll() } func testInitPasswordEntityCoreData() throws { - try cloneRepository() + try cloneRepository(.withGPGID) XCTAssertEqual(passwordStore.numberOfPasswords, 4) @@ -50,7 +51,7 @@ final class PasswordStoreTest: XCTestCase { } func testEraseStoreData() throws { - try cloneRepository() + try cloneRepository(.withGPGID) XCTAssertTrue(FileManager.default.fileExists(atPath: localRepoURL.path)) XCTAssertGreaterThan(passwordStore.numberOfPasswords, 0) XCTAssertNotNil(passwordStore.gitRepository) @@ -63,8 +64,8 @@ final class PasswordStoreTest: XCTestCase { } func testErase() throws { - try cloneRepository() - try importPGPKeys() + try cloneRepository(.withGPGID) + try importSinglePGPKey() Defaults.gitSignatureName = "Test User" PasscodeLock.shared.save(passcode: "1234") @@ -84,7 +85,7 @@ final class PasswordStoreTest: XCTestCase { } func testFetchPasswordEntityCoreDataByParent() throws { - try cloneRepository() + try cloneRepository(.withGPGID) let rootChildren = passwordStore.fetchPasswordEntityCoreData(parent: nil) XCTAssertGreaterThan(rootChildren.count, 0) @@ -99,7 +100,7 @@ final class PasswordStoreTest: XCTestCase { } func testFetchPasswordEntityCoreDataWithDir() throws { - try cloneRepository() + try cloneRepository(.withGPGID) let allPasswords = passwordStore.fetchPasswordEntityCoreData(withDir: false) XCTAssertEqual(allPasswords.count, 4) @@ -108,14 +109,21 @@ final class PasswordStoreTest: XCTestCase { } } + func testEncryptSaveDecryptMultiline() throws { + try cloneRepository(.empty) + try importSinglePGPKey() + + let password = Password(name: "test", path: "test.gpg", plainText: "foobar\nwith\nmultiple\nlines") + _ = try passwordStore.add(password: password) + let decryptedPassword = try decrypt(path: "test.gpg") + XCTAssertEqual(decryptedPassword.plainText, "foobar\nwith\nmultiple\nlines") + } + func testCloneAndDecryptMultiKeys() throws { - try cloneRepository() - try importPGPKeys() + try cloneRepository(.withGPGID) + try importMultiplePGPKeys() Defaults.isEnableGPGIDOn = true - defer { - Defaults.isEnableGPGIDOn = false - } [ ("work/github.com", "4712286271220DB299883EA7062E678DA1024DAE"), @@ -139,21 +147,51 @@ final class PasswordStoreTest: XCTestCase { // MARK: - Helpers - private func cloneRepository() throws { - try passwordStore.cloneRepository(remoteRepoURL: remoteRepoURL, branchName: "master") + private enum RemoteRepo { + case empty + case withGPGID + + var url: URL { + switch self { + case .empty: + Bundle(for: PasswordStoreTest.self).resourceURL!.appendingPathComponent("Fixtures/password-store-empty.git") + case .withGPGID: + Bundle(for: PasswordStoreTest.self).resourceURL!.appendingPathComponent("Fixtures/password-store-with-gpgid.git") + } + } + + var branchName: String { + switch self { + case .empty: + "main" + case .withGPGID: + "master" + } + } + } + + private func cloneRepository(_ remote: RemoteRepo) throws { + try passwordStore.cloneRepository(remoteRepoURL: remote.url, branchName: remote.branchName) expectation(for: NSPredicate { _, _ in FileManager.default.fileExists(atPath: self.localRepoURL.path) }, evaluatedWith: nil) waitForExpectations(timeout: 3, handler: nil) } - private func importPGPKeys() throws { + private func importSinglePGPKey() throws { + let keychain = AppKeychain.shared + try KeyFileManager(keyType: PGPKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: RSA4096.publicKey) + try KeyFileManager(keyType: PGPKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: RSA4096.privateKey) + try PGPAgent.shared.initKeys() + } + + private func importMultiplePGPKeys() throws { let keychain = AppKeychain.shared try KeyFileManager(keyType: PGPKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: RSA2048_RSA4096.publicKeys) try KeyFileManager(keyType: PGPKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: RSA2048_RSA4096.privateKeys) try PGPAgent.shared.initKeys() } - private func decrypt(path: String) throws -> Password { + private func decrypt(path: String, keyID: String? = nil) throws -> Password { let entity = passwordStore.fetchPasswordEntity(with: path)! - return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase) + return try passwordStore.decrypt(passwordEntity: entity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase) } }