Skip to content

Commit

Permalink
Merge pull request #14 from johannes-schliephake/develop
Browse files Browse the repository at this point in the history
v1.1
  • Loading branch information
johannes-schliephake authored Feb 14, 2021
2 parents 5f723d2 + f7cf2f5 commit 8c9e825
Show file tree
Hide file tree
Showing 78 changed files with 503 additions and 124 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions App/Assets.xcassets/AppIcon-debug.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "1024@1x.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
9 changes: 7 additions & 2 deletions App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -34,9 +34,14 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand Down
2 changes: 1 addition & 1 deletion App/PasswordsApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SwiftUI
@main
struct PasswordsApp: App {

@StateObject private var autoFillController = ProcessInfo.processInfo.environment["TEST"] == "true" ? AutoFillController.mock : AutoFillController()
@StateObject private var autoFillController = Configuration.isTestEnvironment ? AutoFillController.mock : AutoFillController()

// MARK: Views

Expand Down
40 changes: 30 additions & 10 deletions Passwords.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Passwords.xcodeproj/xcshareddata/xcschemes/Passwords.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
ReferencedContainer = "container:Passwords.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "TEST"
value = "true"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
9 changes: 7 additions & 2 deletions Provider/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
Expand Down
16 changes: 16 additions & 0 deletions Shared/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation


struct Configuration {

static let shortVersionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
static let appService = Bundle.main.object(forInfoDictionaryKey: "AppService") as! String
static let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
static let appKeychain = Bundle.main.object(forInfoDictionaryKey: "AppKeychain") as! String
static let clientName = "\(Bundle.main.infoDictionary?["CFBundleName"] as! String) (iOS)"
static let isTestEnvironment = ProcessInfo.processInfo.environment["TEST"] == "true"
static let userDefaults = UserDefaults(suiteName: Configuration.appGroup)!

private init() {}

}
121 changes: 121 additions & 0 deletions Shared/Controllers/Global/AuthenticationChallengeController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import CryptoKit
import WebKit
import Combine


final class AuthenticationChallengeController: NSObject, ObservableObject {

static let `default` = AuthenticationChallengeController()

@Published var certificateConfirmationRequests = [CertificateConfirmationRequest]()

private var acceptedCertificateHash: String? {
didSet {
guard let acceptedCertificateHash = acceptedCertificateHash else {
Keychain.default.remove(key: "acceptedCertificateHash")
return
}
Keychain.default.store(key: "acceptedCertificateHash", value: acceptedCertificateHash)
}
}
private var subscriptions = Set<AnyCancellable>()

override private init() {
super.init()

acceptedCertificateHash = Keychain.default.load(key: "acceptedCertificateHash")
CredentialsController.default.$credentials.sink(receiveValue: clearAcceptedCertificateHash).store(in: &subscriptions)
}

func clearAcceptedCertificateHash(credentials: Credentials? = nil) {
guard credentials == nil else {
return
}
acceptedCertificateHash = nil
}

func accept(certificateHash: String) {
acceptedCertificateHash = certificateHash

let acceptedCertificateConfirmationRequests = certificateConfirmationRequests.filter { $0.hash == certificateHash }
certificateConfirmationRequests.removeAll { acceptedCertificateConfirmationRequests.contains($0) }
acceptedCertificateConfirmationRequests.forEach { $0.accept() }
}

func deny(certificateHash: String) {
CredentialsController.default.logout()

let deniedCertificateConfirmationRequests = certificateConfirmationRequests.filter { $0.hash == certificateHash }
certificateConfirmationRequests.removeAll { deniedCertificateConfirmationRequests.contains($0) }
deniedCertificateConfirmationRequests.forEach { $0.deny() }
}

private func handler(didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
/// Check certificate and calculate SHA-256 if invalid
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.performDefaultHandling, nil)
return
}
if SecTrustEvaluateWithError(serverTrust, nil) {
completionHandler(.performDefaultHandling, nil)
return
}
guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.performDefaultHandling, nil)
return
}
let certificateData = SecCertificateCopyData(certificate) as Data
let certificateHash = SHA256.hash(data: certificateData).map { String(format: "%02X", $0) }.joined(separator: ":")

/// Check certificate hash against accepted hash
if certificateHash == acceptedCertificateHash {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
return
}

/// Add data needed for certificate confirmation
let certificateConfirmationRequest = CertificateConfirmationRequest(hash: certificateHash, accept: {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}, deny: {
completionHandler(.performDefaultHandling, nil)
})
certificateConfirmationRequests.append(certificateConfirmationRequest)
}

}


extension AuthenticationChallengeController {

struct CertificateConfirmationRequest: Identifiable, Equatable {

let id = UUID()
let hash: String
let accept: () -> Void
let deny: () -> Void

static func ==(lhs: CertificateConfirmationRequest, rhs: CertificateConfirmationRequest) -> Bool {
lhs.id == rhs.id
}

}

}


extension AuthenticationChallengeController: URLSessionDelegate {

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
handler(didReceive: challenge, completionHandler: completionHandler)
}

}


extension AuthenticationChallengeController: WKNavigationDelegate {

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
handler(didReceive: challenge, completionHandler: completionHandler)
}

}
22 changes: 10 additions & 12 deletions Shared/Controllers/Global/CredentialsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,21 @@ final class CredentialsController: ObservableObject {
@Published var credentials: Credentials? {
didSet {
guard let credentials = credentials else {
keychain.remove(key: "server")
keychain.remove(key: "user")
keychain.remove(key: "password")
Keychain.default.remove(key: "server")
Keychain.default.remove(key: "user")
Keychain.default.remove(key: "password")
return
}
keychain.store(key: "server", value: credentials.server)
keychain.store(key: "user", value: credentials.user)
keychain.store(key: "password", value: credentials.password)
Keychain.default.store(key: "server", value: credentials.server)
Keychain.default.store(key: "user", value: credentials.user)
Keychain.default.store(key: "password", value: credentials.password)
}
}

private let keychain = Keychain(service: Bundle.main.object(forInfoDictionaryKey: "AppService") as! String, accessGroup: Bundle.main.object(forInfoDictionaryKey: "AppKeychain") as! String)

init() {
guard let server = keychain.load(key: "server"),
let user = keychain.load(key: "user"),
let password = keychain.load(key: "password") else {
private init() {
guard let server = Keychain.default.load(key: "server"),
let user = Keychain.default.load(key: "user"),
let password = Keychain.default.load(key: "password") else {
return
}
credentials = Credentials(server: server, user: user, password: password)
Expand Down
11 changes: 8 additions & 3 deletions Shared/Controllers/Local/EditPasswordController.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import CryptoKit


final class EditPasswordController: ObservableObject {
Expand All @@ -7,19 +8,19 @@ final class EditPasswordController: ObservableObject {
private let addPassword: () -> Void
private let updatePassword: () -> Void

@AppStorage("generatorNumbers", store: UserDefaults(suiteName: (Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String))!) var generatorNumbers = true {
@AppStorage("generatorNumbers", store: Configuration.userDefaults) var generatorNumbers = true {
willSet {
/// Extend @AppStorage behaviour to be more similar to @Published
objectWillChange.send()
}
}
@AppStorage("generatorSpecial", store: UserDefaults(suiteName: (Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String))!) var generatorSpecial = true {
@AppStorage("generatorSpecial", store: Configuration.userDefaults) var generatorSpecial = true {
willSet {
/// Extend @AppStorage behaviour to be more similar to @Published
objectWillChange.send()
}
}
@AppStorage("generatorLength", store: UserDefaults(suiteName: (Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String))!) var generatorLength = 36.0 {
@AppStorage("generatorLength", store: Configuration.userDefaults) var generatorLength = 36.0 {
willSet {
/// Extend @AppStorage behaviour to be more similar to @Published
objectWillChange.send()
Expand All @@ -31,6 +32,7 @@ final class EditPasswordController: ObservableObject {
@Published var passwordUrl: String
@Published var passwordNotes: String
@Published var showErrorAlert = false
@Published var showProgressView = false

init(password: Password, addPassword: @escaping () -> Void, updatePassword: @escaping () -> Void) {
self.password = password
Expand All @@ -49,8 +51,10 @@ final class EditPasswordController: ObservableObject {
return
}

showProgressView = true
PasswordServiceRequest(credentials: credentials, numbers: generatorNumbers, special: generatorSpecial).send {
[weak self] password in
self?.showProgressView = false
guard let password = password,
let generatorLength = self?.generatorLength else {
self?.showErrorAlert = true
Expand All @@ -66,6 +70,7 @@ final class EditPasswordController: ObservableObject {
}
if password.password != passwordPassword {
password.edited = Date()
password.hash = Insecure.SHA1.hash(data: passwordPassword.data(using: .utf8)!).map { String(format: "%02x", $0) }.joined()
}
password.updated = Date()

Expand Down
Loading

0 comments on commit 8c9e825

Please sign in to comment.