From 2a8833ff206ffb94b9b4b13c17d1e74fed857544 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 1 May 2022 14:07:43 +0200 Subject: [PATCH] Use remote authentication, add key display screen --- Sesame.xcodeproj/project.pbxproj | 14 +- .../UserInterfaceState.xcuserstate | Bin 71271 -> 95155 bytes Sesame/{ => API}/Data+Extensions.swift | 0 Sesame/API/DeviceResponse.swift | 5 + Sesame/API/Message.swift | 4 + Sesame/API/MessageResult.swift | 5 + Sesame/API/ServerMessage.swift | 51 +++++ Sesame/Client.swift | 57 +---- Sesame/ClientState.swift | 11 +- Sesame/ContentView.swift | 84 +++++--- Sesame/KeyManagement.swift | 197 ++++++++++++------ Sesame/KeyView.swift | 25 +++ Sesame/SingleKeyView.swift | 54 +++++ Sesame/SymmetricKey+Extensions.swift | 20 ++ 14 files changed, 378 insertions(+), 149 deletions(-) rename Sesame/{ => API}/Data+Extensions.swift (100%) create mode 100644 Sesame/API/ServerMessage.swift create mode 100644 Sesame/KeyView.swift create mode 100644 Sesame/SingleKeyView.swift diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj index 3081a47..b3132e3 100644 --- a/Sesame.xcodeproj/project.pbxproj +++ b/Sesame.xcodeproj/project.pbxproj @@ -20,8 +20,11 @@ E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; }; E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; }; E24EE77927FF95E00011CFD2 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; }; + E28DED2D281E840B00259690 /* KeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED2C281E840B00259690 /* KeyView.swift */; }; + E28DED2F281E8A0500259690 /* SingleKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED2E281E8A0500259690 /* SingleKeyView.swift */; }; E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; }; E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; }; + E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,8 +41,11 @@ E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; E24EE77327FF95920011CFD2 /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = ""; }; E24EE77827FF95E00011CFD2 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + E28DED2C281E840B00259690 /* KeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyView.swift; sourceTree = ""; }; + E28DED2E281E8A0500259690 /* SingleKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleKeyView.swift; sourceTree = ""; }; E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPI.swift; sourceTree = ""; }; E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extensions.swift"; sourceTree = ""; }; + E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerMessage.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -76,11 +82,12 @@ E2C5C1D92806FE4A00769EF6 /* API */, 884A45B6279F48C100D6E650 /* SesameApp.swift */, 884A45B8279F48C100D6E650 /* ContentView.swift */, + E28DED2C281E840B00259690 /* KeyView.swift */, + E28DED2E281E8A0500259690 /* SingleKeyView.swift */, 884A45CC27A465F500D6E650 /* Client.swift */, 884A45C827A43D7900D6E650 /* ClientState.swift */, 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */, 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */, - E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */, 884A45BA279F48C300D6E650 /* Assets.xcassets */, 884A45BC279F48C300D6E650 /* Preview Content */, ); @@ -98,10 +105,12 @@ E2C5C1D92806FE4A00769EF6 /* API */ = { isa = PBXGroup; children = ( + E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */, E24EE77327FF95920011CFD2 /* DeviceResponse.swift */, E24EE77827FF95E00011CFD2 /* Message.swift */, 884A45CE27A5402D00D6E650 /* MessageResult.swift */, E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */, + E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */, E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */, ); path = API; @@ -185,6 +194,7 @@ files = ( 884A45CF27A5402D00D6E650 /* MessageResult.swift in Sources */, 884A45B9279F48C100D6E650 /* ContentView.swift in Sources */, + E28DED2F281E8A0500259690 /* SingleKeyView.swift in Sources */, E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */, E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */, 884A45CD27A465F500D6E650 /* Client.swift in Sources */, @@ -193,8 +203,10 @@ 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */, E24EE77927FF95E00011CFD2 /* Message.swift in Sources */, 884A45C927A43D7900D6E650 /* ClientState.swift in Sources */, + E28DED2D281E840B00259690 /* KeyView.swift in Sources */, 884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */, 884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */, + E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index dff6be4f0f20814a9ff667c23e3ddcdcebfcf3df..a4bb03d6d7475f9e6adcd73cb3edfb3d6def9544 100644 GIT binary patch literal 95155 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 literal 71271 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 AL;wH) diff --git a/Sesame/Data+Extensions.swift b/Sesame/API/Data+Extensions.swift similarity index 100% rename from Sesame/Data+Extensions.swift rename to Sesame/API/Data+Extensions.swift diff --git a/Sesame/API/DeviceResponse.swift b/Sesame/API/DeviceResponse.swift index 10e3749..b5157bc 100644 --- a/Sesame/API/DeviceResponse.swift +++ b/Sesame/API/DeviceResponse.swift @@ -16,6 +16,11 @@ struct DeviceResponse { .init(event: .deviceNotConnected) } + /// Shorthand property for a connected event. + static var deviceConnected: DeviceResponse { + .init(event: .deviceConnected) + } + /// Shorthand property for an unexpected socket event. static var unexpectedSocketEvent: DeviceResponse { .init(event: .unexpectedSocketEvent) diff --git a/Sesame/API/Message.swift b/Sesame/API/Message.swift index 4bed14a..b29024c 100644 --- a/Sesame/API/Message.swift +++ b/Sesame/API/Message.swift @@ -101,6 +101,10 @@ extension Message { mac + content.encoded } + var bytes: [UInt8] { + Array(encoded) + } + /** Create a message from received bytes. - Parameter data: The sequence of bytes diff --git a/Sesame/API/MessageResult.swift b/Sesame/API/MessageResult.swift index 384332f..ecf41af 100644 --- a/Sesame/API/MessageResult.swift +++ b/Sesame/API/MessageResult.swift @@ -38,6 +38,9 @@ enum MessageResult: UInt8 { /// Another message is being processed by the device case operationInProgress = 14 + + /// The device is connected + case deviceConnected = 15 } extension MessageResult: CustomStringConvertible { @@ -66,6 +69,8 @@ extension MessageResult: CustomStringConvertible { return "The device did not respond" case .operationInProgress: return "Another operation is in progress" + case .deviceConnected: + return "The device is connected" } } } diff --git a/Sesame/API/ServerMessage.swift b/Sesame/API/ServerMessage.swift new file mode 100644 index 0000000..1e4dfb6 --- /dev/null +++ b/Sesame/API/ServerMessage.swift @@ -0,0 +1,51 @@ +import Foundation +import NIOCore + +#if canImport(CryptoKit) +import CryptoKit +#else +import Crypto +#endif + +struct ServerMessage { + + static let authTokenSize = SHA256.byteCount + + static let length = authTokenSize + Message.length + + let authToken: Data + + let message: Message + + init(authToken: Data, message: Message) { + self.authToken = authToken + self.message = message + } + + /** + Decode a message from a byte buffer. + The buffer must contain at least `ServerMessage.length` bytes, or it will return `nil`. + - Parameter buffer: The buffer containing the bytes. + */ + init?(decodeFrom buffer: ByteBuffer) { + guard let data = buffer.getBytes(at: 0, length: ServerMessage.length) else { + return nil + } + self.authToken = Data(data.prefix(ServerMessage.authTokenSize)) + self.message = Message(decodeFrom: Data(data.dropFirst(ServerMessage.authTokenSize))) + } + + var encoded: Data { + authToken + message.encoded + } + + static func token(from buffer: ByteBuffer) -> Data? { + guard buffer.readableBytes == authTokenSize else { + return nil + } + guard let bytes = buffer.getBytes(at: 0, length: authTokenSize) else { + return nil + } + return Data(bytes) + } +} diff --git a/Sesame/Client.swift b/Sesame/Client.swift index 74106c9..2e706f0 100644 --- a/Sesame/Client.swift +++ b/Sesame/Client.swift @@ -10,41 +10,20 @@ struct Client { init(server: URL) { self.server = server } + + func deviceStatus(authToken: Data) async -> ClientState { + await send(path: .getDeviceStatus, data: authToken).state + } - private enum RequestReponse: Error { - case requestFailed - case unknownResponseData(Data) - case unknownResponseString(String) - case success(UInt8) + func send(_ message: Message, authToken: Data) async -> (state: ClientState, response: Message?) { + let serverMessage = ServerMessage(authToken: authToken, message: message) + return await send(path: .postMessage, data: serverMessage.encoded) } - func deviceStatus() async -> ClientState { - let url = server.appendingPathComponent(RouteAPI.getDeviceStatus.rawValue) - let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) - let response = await integerReponse(to: request) - switch response { - case .requestFailed: - return .deviceNotAvailable(.serverNotReached) - case .unknownResponseData(let data): - return .internalError("Unknown status (\(data.count) bytes)") - case .unknownResponseString(let string): - return .internalError("Unknown status (\(string.prefix(15)))") - case .success(let int): - switch int { - case 0: - return .deviceNotAvailable(.deviceDisconnected) - case 1: - return .ready - default: - return .internalError("Invalid status: \(int)") - } - } - } - - func send(_ message: Message) async throws -> (state: ClientState, response: Message?) { - let url = server.appendingPathComponent(RouteAPI.postMessage.rawValue) + private func send(path: RouteAPI, data: Data) async -> (state: ClientState, response: Message?) { + let url = server.appendingPathComponent(path.rawValue) var request = URLRequest(url: url) - request.httpBody = message.encoded + request.httpBody = data request.httpMethod = "POST" guard let data = await fulfill(request) else { return (.deviceNotAvailable(.serverNotReached), nil) @@ -81,22 +60,6 @@ struct Client { return nil } } - - private func integerReponse(to request: URLRequest) async -> RequestReponse { - guard let data = await fulfill(request) else { - return .requestFailed - } - guard let string = String(data: data, encoding: .utf8) else { - print("Unexpected device status data: \([UInt8](data))") - return .unknownResponseData(data) - } - guard let int = UInt8(string) else { - print("Unexpected device status '\(string)'") - return .unknownResponseString(string) - } - return .success(int) - } - } class NeverCacheDelegate: NSObject, NSURLConnectionDataDelegate { diff --git a/Sesame/ClientState.swift b/Sesame/ClientState.swift index 19dbca3..356be3f 100644 --- a/Sesame/ClientState.swift +++ b/Sesame/ClientState.swift @@ -93,16 +93,13 @@ enum ClientState { self = .deviceNotAvailable(.deviceDisconnected) case .operationInProgress: self = .waitingForResponse + case .deviceConnected: + self = .ready } } var actionText: String { - switch self { - case .noKeyAvailable: - return "Create key" - default: - return "Unlock" - } + "Unlock" } var requiresDescription: Bool { @@ -137,7 +134,7 @@ enum ClientState { var allowsAction: Bool { switch self { - case .requestingStatus, .deviceNotAvailable, .waitingForResponse: + case .requestingStatus, .deviceNotAvailable, .waitingForResponse, .noKeyAvailable: return false default: return true diff --git a/Sesame/ContentView.swift b/Sesame/ContentView.swift index 97fed5a..41dff83 100644 --- a/Sesame/ContentView.swift +++ b/Sesame/ContentView.swift @@ -7,6 +7,9 @@ struct ContentView: View { @AppStorage("counter") var nextMessageCounter: Int = 0 + + @State + var keyManager = KeyManagement() @State var state: ClientState = .noKeyAvailable @@ -20,6 +23,12 @@ struct ContentView: View { @State private var responseTime: Date? = nil + @State + private var showKeySheet = false + + @State + private var showHistorySheet = false + var isPerformingRequests: Bool { hasActiveRequest || state == .waitingForResponse @@ -28,7 +37,7 @@ struct ContentView: View { var buttonBackground: Color { state.allowsAction ? .white.opacity(0.2) : - .gray.opacity(0.2) + .black.opacity(0.2) } let buttonBorderWidth: CGFloat = 3 @@ -39,16 +48,44 @@ struct ContentView: View { private let buttonWidth: CGFloat = 250 + private let smallButtonHeight: CGFloat = 50 + + private let smallButtonWidth: CGFloat = 120 + + private let smallButtonBorderWidth: CGFloat = 1 + var body: some View { GeometryReader { geo in VStack(spacing: 20) { + HStack { + Button("History", action: { showHistorySheet = true }) + .frame(width: smallButtonWidth, + height: smallButtonHeight) + .background(.white.opacity(0.2)) + .cornerRadius(smallButtonHeight / 2) + .overlay(RoundedRectangle(cornerRadius: smallButtonHeight / 2).stroke(lineWidth: smallButtonBorderWidth).foregroundColor(.white)) + .foregroundColor(.white) + .font(.title2) + .padding() + Spacer() + Button("Keys", action: { showKeySheet = true }) + .frame(width: smallButtonWidth, + height: smallButtonHeight) + .background(.white.opacity(0.2)) + .cornerRadius(smallButtonHeight / 2) + .overlay(RoundedRectangle(cornerRadius: smallButtonHeight / 2).stroke(lineWidth: smallButtonBorderWidth).foregroundColor(.white)) + .foregroundColor(.white) + .font(.title2) + .padding() + } Spacer() if state.requiresDescription { Text(state.description) .padding() } Button(state.actionText, action: mainButtonPressed) - .frame(width: buttonWidth, height: buttonWidth, alignment: .center) + .frame(width: buttonWidth, + height: buttonWidth) .background(buttonBackground) .cornerRadius(buttonWidth / 2) .overlay(RoundedRectangle(cornerRadius: buttonWidth / 2).stroke(lineWidth: buttonBorderWidth).foregroundColor(buttonColor)) @@ -57,8 +94,9 @@ struct ContentView: View { .disabled(!state.allowsAction) .padding(.bottom, (geo.size.width-buttonWidth) / 2) } + .background(state.color) .onAppear { - if KeyManagement.hasKey { + if keyManager.hasAllKeys { state = .requestingStatus } startRegularStatusUpdates() @@ -67,20 +105,19 @@ struct ContentView: View { endRegularStatusUpdates() } .frame(width: geo.size.width, height: geo.size.height) - .background(state.color) .animation(.easeInOut, value: state.color) + .sheet(isPresented: $showKeySheet) { + KeyView(keyManager: $keyManager) + } } } func mainButtonPressed() { - guard let key = KeyManagement.key?.remote else { - generateKey() + guard let key = keyManager.get(.remoteKey), + let token = keyManager.get(.authToken)?.data else { return } - sendMessage(using: key) - } - - func sendMessage(using key: SymmetricKey) { + let count = UInt32(nextMessageCounter) let now = Date() let content = Message.Content( @@ -90,7 +127,7 @@ struct ContentView: View { state = .waitingForResponse print("Sending message \(count)") Task { - let (newState, message) = try await server.send(message) + let (newState, message) = await server.send(message, authToken: token) responseTime = now state = newState if let message = message { @@ -100,7 +137,7 @@ struct ContentView: View { } private func processResponse(_ message: Message, sendTime: Date) { - guard let key = KeyManagement.key?.device else { + guard let key = keyManager.get(.deviceKey) else { return } guard message.isValid(using: key) else { @@ -115,9 +152,13 @@ struct ContentView: View { let time1 = deviceTime.timeIntervalSince(sendTime) let time2 = now.timeIntervalSince(deviceTime) if time1 < 0 { - print("Device time behind by at least \(Int(-time1 * 1000)) ms behind") + print("Device time behind by at least \(Int(-time1 * 1000)) ms") + print("Device: \(deviceTime)") + print("Remote: \(now)") } else if time2 < 0 { - print("Device time behind by at least \(Int(-time2 * 1000)) ms ahead") + print("Device time ahead by at least \(Int(-time2 * 1000)) ms") + print("Device: \(deviceTime)") + print("Remote: \(now)") } else { print("Device time synchronized") } @@ -139,13 +180,16 @@ struct ContentView: View { } func checkDeviceStatus(_ timer: Timer) { + guard let authToken = keyManager.get(.authToken) else { + return + } guard !hasActiveRequest else { return } hasActiveRequest = true print("Checking device status") Task { - let newState = await server.deviceStatus() + let newState = await server.deviceStatus(authToken: authToken.data) hasActiveRequest = false switch state { case .noKeyAvailable: @@ -173,16 +217,6 @@ struct ContentView: View { } } } - - func generateKey() { - print("Regenerate key") - KeyManagement.generateNewKeys() - state = .requestingStatus - } - - func shareKey() { - - } } struct ContentView_Previews: PreviewProvider { diff --git a/Sesame/KeyManagement.swift b/Sesame/KeyManagement.swift index 7618a41..c6e8078 100644 --- a/Sesame/KeyManagement.swift +++ b/Sesame/KeyManagement.swift @@ -2,95 +2,154 @@ import Foundation import CryptoKit import SwiftUI -final class KeyManagement { +extension KeyManagement { - static let tag = "com.ch.sesame.key".data(using: .utf8)! + enum KeyType: String, Identifiable, CaseIterable { - private static let label = "sesame" + case deviceKey = "sesame-device" + case remoteKey = "sesame-remote" + case authToken = "sesame-remote-auth" - private static let keyType = kSecAttrKeyTypeEC + var id: String { + rawValue + } - private static let keyClass = kSecAttrKeyClassSymmetric + var displayName: String { + switch self { + case .deviceKey: + return "Device Key" + case .remoteKey: + return "Remote Key" + case .authToken: + return "Authentication Token" + } + } - private static let query: [String: Any] = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrAccount as String: "account", - kSecAttrServer as String: "christophhagen.de", - kSecAttrLabel as String: "sesame"] + var keyLength: SymmetricKeySize { + .bits256 + } + } +} - private static func loadKeys() -> Data? { - var query = query +extension KeyManagement.KeyType: CustomStringConvertible { + + var description: String { + displayName + } +} + +private struct KeyChain { + + private let keyType = kSecAttrKeyTypeEC + + private let keyClass = kSecAttrKeyClassSymmetric + + private let domain: String + + init(domain: String) { + self.domain = domain + } + + private func baseQuery(for type: KeyManagement.KeyType) -> [String : Any] { + [kSecClass as String: kSecClassInternetPassword, + kSecAttrAccount as String: type.rawValue, + kSecAttrServer as String: domain] + } + + func save(_ type: KeyManagement.KeyType, _ key: SymmetricKey) { + var query = baseQuery(for: type) + query[kSecValueData as String] = key.data + let status = SecItemAdd(query as CFDictionary, nil) + guard status == errSecSuccess else { + print("Failed to store \(type): \(status)") + return + } + print("\(type) saved to keychain") + } + + func load(_ type: KeyManagement.KeyType) -> SymmetricKey? { + var query = baseQuery(for: type) query[kSecReturnData as String] = kCFBooleanTrue var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) guard status == errSecSuccess else { - print("Failed to get key: \(status)") + print("Failed to get \(type): \(status)") return nil } let key = item as! CFData - print("Key loaded from keychain") - return key as Data + print("\(type) loaded from keychain") + return SymmetricKey(data: key as Data) } - private static func deleteKeys() { - let status = SecItemDelete(query as CFDictionary) + func delete(_ type: KeyManagement.KeyType) { + let status = SecItemDelete(baseQuery(for: type) as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { - print("Failed to remove key: \(status)") + print("Failed to remove \(type): \(status)") return } - print("Key removed from keychain") + print("\(type) removed from keychain") } - private static func saveKeys(_ data: Data) { - var query = query - query[kSecValueData as String] = data - let status = SecItemAdd(query as CFDictionary, nil) - guard status == errSecSuccess else { - print("Failed to store key: \(status)") - return - } - print("Key saved to keychain") - } - - private static var keyData: Data? = loadKeys() { - didSet { - guard let data = keyData else { - deleteKeys() - return - } - saveKeys(data) - } - } - - static var hasKey: Bool { - key != nil - } - - private(set) static var key: (device: SymmetricKey, remote: SymmetricKey)? { - get { - guard let data = keyData else { - return nil - } - let device = SymmetricKey(data: data.prefix(32)) - let remote = SymmetricKey(data: data.advanced(by: 32)) - return (device, remote) - } - set { - guard let key = newValue else { - keyData = nil - return - } - keyData = key.device.data + key.remote.data - } - } - - static func generateNewKeys() { - let device = SymmetricKey(size: .bits256) - let remote = SymmetricKey(size: .bits256) - key = (device, remote) - print("New keys:") - print("Device: \(device.data.hexEncoded)") - print("Remote: \(remote.data.hexEncoded)") + func has(_ type: KeyManagement.KeyType) -> Bool { + load(type) != nil + } +} + +final class KeyManagement: ObservableObject { + + private let keyChain: KeyChain + + @Published + private(set) var hasRemoteKey = false + + @Published + private(set) var hasDeviceKey = false + + @Published + private(set) var hasAuthToken = false + + var hasAllKeys: Bool { + hasRemoteKey && hasDeviceKey && hasAuthToken + } + + init() { + self.keyChain = KeyChain(domain: "christophhagen.de") + updateKeyStates() + } + + func has(_ type: KeyType) -> Bool { + switch type { + case .deviceKey: + return hasDeviceKey + case .remoteKey: + return hasRemoteKey + case .authToken: + return hasAuthToken + } + } + + func get(_ type: KeyType) -> SymmetricKey? { + keyChain.load(type) + } + + func delete(_ type: KeyType) { + keyChain.delete(type) + updateKeyStates() + } + + func generate(_ type: KeyType) { + let key = SymmetricKey(size: type.keyLength) + if keyChain.has(type) { + keyChain.delete(type) + } + keyChain.save(type, key) + updateKeyStates() + } + + private func updateKeyStates() { + self.hasRemoteKey = keyChain.has(.remoteKey) + self.hasDeviceKey = keyChain.has(.deviceKey) + self.hasAuthToken = keyChain.has(.authToken) } } diff --git a/Sesame/KeyView.swift b/Sesame/KeyView.swift new file mode 100644 index 0000000..170e2cf --- /dev/null +++ b/Sesame/KeyView.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct KeyView: View { + + @Binding + var keyManager: KeyManagement + + var body: some View { + GeometryReader { geo in + VStack(alignment: .leading, spacing: 16) { + ForEach(KeyManagement.KeyType.allCases) { keyType in + SingleKeyView( + keyManager: $keyManager, + type: keyType) + } + }.padding() + } + } +} + +struct KeyView_Previews: PreviewProvider { + static var previews: some View { + KeyView(keyManager: .constant(KeyManagement())) + } +} diff --git a/Sesame/SingleKeyView.swift b/Sesame/SingleKeyView.swift new file mode 100644 index 0000000..4cdd976 --- /dev/null +++ b/Sesame/SingleKeyView.swift @@ -0,0 +1,54 @@ +import SwiftUI + +struct SingleKeyView: View { + + @State + private var needRefresh = false + + @Binding + var keyManager: KeyManagement + + let type: KeyManagement.KeyType + + private var generateText: String { + hasKey ? "Generate" : "Regenerate" + } + + var hasKey: Bool { + keyManager.has(type) + } + + var content: String { + keyManager.get(type)?.displayString ?? "-" + } + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(type.displayName) + .bold() + Text(needRefresh ? content : content) + .font(.system(.body, design: .monospaced)) + HStack() { + Button(generateText) { + keyManager.generate(type) + needRefresh.toggle() + } + .padding() + Button("Copy") { + UIPasteboard.general.string = content + } + .disabled(!hasKey) + .padding() + Spacer() + } + } + } +} + +struct SingleKeyView_Previews: PreviewProvider { + static var previews: some View { + SingleKeyView( + keyManager: .constant(KeyManagement()), + type: .deviceKey) + } +} diff --git a/Sesame/SymmetricKey+Extensions.swift b/Sesame/SymmetricKey+Extensions.swift index d977084..e0f11ee 100644 --- a/Sesame/SymmetricKey+Extensions.swift +++ b/Sesame/SymmetricKey+Extensions.swift @@ -10,6 +10,10 @@ extension SymmetricKey { var base64: String { data.base64EncodedString() } + + var displayString: String { + data.hexEncoded.uppercased().split(by: 4).joined(separator: " ") + } var codeString: String { " {" + @@ -19,3 +23,19 @@ extension SymmetricKey { "}," } } + +extension String { + + func split(by length: Int) -> [String] { + var startIndex = self.startIndex + var results = [Substring]() + + while startIndex < self.endIndex { + let endIndex = self.index(startIndex, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex + results.append(self[startIndex..