diff --git a/Sesame-Watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Sesame-Watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..49c81cd --- /dev/null +++ b/Sesame-Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame-Watch Watch App/Assets.xcassets/Contents.json b/Sesame-Watch Watch App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sesame-Watch Watch App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame-Watch Watch App/ContentView.swift b/Sesame-Watch Watch App/ContentView.swift new file mode 100644 index 0000000..455ee26 --- /dev/null +++ b/Sesame-Watch Watch App/ContentView.swift @@ -0,0 +1,150 @@ +import SwiftUI +import CryptoKit + +struct ContentView: View { + + @AppStorage("server") + var serverPath: String = "https://christophhagen.de/sesame/" + + @AppStorage("localIP") + var localAddress: String = "192.168.178.104/" + + @AppStorage("counter") + var nextMessageCounter: Int = 0 + + @AppStorage("compensate") + var isCompensatingDaylightTime: Bool = false + + @AppStorage("local") + private var useLocalConnection = false + + @AppStorage("deviceId") + private var deviceId: Int = 0 + + @EnvironmentObject + var keyManager: KeyManagement + + @State + var state: ClientState = .noKeyAvailable + + @State + private var hasActiveRequest = false + + let server = Client() + + var buttonBackground: Color { + state.allowsAction ? + .white.opacity(0.2) : + .black.opacity(0.2) + } + + let buttonBorderWidth: CGFloat = 3 + + var buttonColor: Color { + state.allowsAction ? .white : .gray + } + + private let sidePaddingRatio: CGFloat = 0.05 + private let buttonSizeRatio: CGFloat = 0.9 + + private let smallButtonHeight: CGFloat = 50 + + private let smallButtonWidth: CGFloat = 120 + + private let smallButtonBorderWidth: CGFloat = 1 + + var compensationTime: UInt32 { + isCompensatingDaylightTime ? 3600 : 0 + } + + var isPerformingRequests: Bool { + hasActiveRequest || + state == .waitingForResponse + } + + var body: some View { + VStack(alignment: .center) { + Spacer() + GeometryReader { geo in + HStack(alignment: .center) { + Spacer() + let buttonWidth = min(geo.size.width, geo.size.height) + Text(state.actionText) + .frame(width: buttonWidth, height: buttonWidth) + .background(buttonBackground) + .cornerRadius(buttonWidth / 2) + .overlay(RoundedRectangle(cornerRadius: buttonWidth / 2) + .stroke(lineWidth: buttonBorderWidth).foregroundColor(buttonColor)) + .foregroundColor(buttonColor) + .font(.title) + .disabled(!state.allowsAction) + .onTapGesture(perform: mainButtonPressed) + Spacer() + } + } + Spacer() + } + .background(state.color) + .animation(.easeInOut, value: state.color) + } + + func mainButtonPressed() { + guard let key = keyManager.get(.remoteKey), + let token = keyManager.get(.authToken)?.data, + let deviceId = UInt8(exactly: deviceId) else { + return + } + let count = UInt32(nextMessageCounter) + let sentTime = Date() + // Add time to compensate that the device is using daylight savings time + let content = Message.Content( + time: sentTime.timestamp + compensationTime, + id: count, + device: deviceId) + let message = content.authenticate(using: key) + let historyItem = HistoryItem(sent: message.content, date: sentTime, local: useLocalConnection) + state = .waitingForResponse + print("Sending message \(count)") + Task { + let (newState, responseMessage) = await send(message, authToken: token) + let receivedTime = Date.now + //responseTime = receivedTime + state = newState + let finishedItem = historyItem.didReceive(response: newState, date: receivedTime, message: responseMessage?.content) + guard let key = keyManager.get(.deviceKey) else { + save(historyItem: finishedItem.notAuthenticated()) + return + } + guard let responseMessage else { + save(historyItem: finishedItem) + return + } + guard responseMessage.isValid(using: key) else { + save(historyItem: finishedItem.invalidated()) + return + } + + nextMessageCounter = Int(responseMessage.content.id) + save(historyItem: finishedItem) + } + } + + private func send(_ message: Message, authToken: Data) async -> (state: ClientState, response: Message?) { + if useLocalConnection { + return await server.sendMessageOverLocalNetwork(message, server: localAddress) + } else { + return await server.send(message, server: serverPath, authToken: authToken) + } + } + + private func save(historyItem: HistoryItem) { + + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .environmentObject(KeyManagement()) + } +} diff --git a/Sesame-Watch Watch App/Date+Extensions.swift b/Sesame-Watch Watch App/Date+Extensions.swift new file mode 100644 index 0000000..188a45f --- /dev/null +++ b/Sesame-Watch Watch App/Date+Extensions.swift @@ -0,0 +1,12 @@ +import Foundation + +extension Date { + + var timestamp: UInt32 { + UInt32(timeIntervalSince1970.rounded()) + } + + init(timestamp: UInt32) { + self.init(timeIntervalSince1970: TimeInterval(timestamp)) + } +} diff --git a/Sesame-Watch Watch App/HistoryView.swift b/Sesame-Watch Watch App/HistoryView.swift new file mode 100644 index 0000000..4037310 --- /dev/null +++ b/Sesame-Watch Watch App/HistoryView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct HistoryView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct HistoryView_Previews: PreviewProvider { + static var previews: some View { + HistoryView() + } +} diff --git a/Sesame-Watch Watch App/KeyManagement.swift b/Sesame-Watch Watch App/KeyManagement.swift new file mode 100644 index 0000000..8558a1f --- /dev/null +++ b/Sesame-Watch Watch App/KeyManagement.swift @@ -0,0 +1,121 @@ +import Foundation +import CryptoKit +import SwiftUI + +private let localKey: [UInt8] = [ + 0x98, 0x36, 0x91, 0x09, 0x29, 0xa0, 0x54, 0x44, + 0x03, 0x0c, 0xa5, 0xb4, 0x20, 0x16, 0x10, 0x0d, + 0xaf, 0x41, 0x9b, 0x26, 0x4f, 0x75, 0xa4, 0x61, + 0xed, 0x15, 0x0c, 0xb3, 0x06, 0x39, 0x92, 0x59] + + +private let remoteKey: [UInt8] = [ + 0xfa, 0x23, 0xf6, 0x98, 0xea, 0x87, 0x23, 0xa0, + 0xa0, 0xbe, 0x9a, 0xdb, 0x31, 0x28, 0xcb, 0x7d, + 0xd3, 0xa5, 0x7b, 0xf0, 0xc0, 0xeb, 0x45, 0x65, + 0x4d, 0x94, 0x50, 0x1a, 0x2f, 0x6f, 0xeb, 0x70] + +private let authToken: [UInt8] = { + let s = "Y6QzDK5DaFK1w2oEX5OkzoC0nTqP8w5IxpvWAR1mpro=" + let t = Data(base64Encoded: s.data(using: .utf8)!)! + return Array(t) +}() + +extension KeyManagement { + + enum KeyType: String, Identifiable, CaseIterable { + + case deviceKey = "sesame-device" + case remoteKey = "sesame-remote" + case authToken = "sesame-remote-auth" + + var id: String { + rawValue + } + + var displayName: String { + switch self { + case .deviceKey: + return "Device Key" + case .remoteKey: + return "Remote Key" + case .authToken: + return "Authentication Token" + } + } + + var keyLength: SymmetricKeySize { + .bits256 + } + + var usesHashing: Bool { + switch self { + case .authToken: + return true + default: + return false + } + } + } +} + +extension KeyManagement.KeyType: CustomStringConvertible { + + var description: String { + displayName + } +} + +final class KeyManagement: ObservableObject { + + + @Published + private(set) var hasRemoteKey = true + + @Published + private(set) var hasDeviceKey = true + + @Published + private(set) var hasAuthToken = true + + var hasAllKeys: Bool { + hasRemoteKey && hasDeviceKey && hasAuthToken + } + + init() {} + + func has(_ type: KeyType) -> Bool { + switch type { + case .deviceKey: + return hasDeviceKey + case .remoteKey: + return hasRemoteKey + case .authToken: + return hasAuthToken + } + } + + func get(_ type: KeyType) -> SymmetricKey? { + let bytes: [UInt8] = get(type) + return SymmetricKey(data: bytes) + } + + private func get(_ type: KeyType) -> [UInt8] { + switch type { + case .deviceKey: + return remoteKey + case .remoteKey: + return localKey + case .authToken: + return authToken + } + } + + func delete(_ type: KeyType) { + + } + + func generate(_ type: KeyType) { + + } +} diff --git a/Sesame-Watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json b/Sesame-Watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sesame-Watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame-Watch Watch App/Sesame_WatchApp.swift b/Sesame-Watch Watch App/Sesame_WatchApp.swift new file mode 100644 index 0000000..849c66d --- /dev/null +++ b/Sesame-Watch Watch App/Sesame_WatchApp.swift @@ -0,0 +1,19 @@ +import SwiftUI + +@main +struct Sesame_Watch_Watch_AppApp: App { + + let keyManagement = KeyManagement() + + var body: some Scene { + WindowGroup { + TabView { + ContentView() + .environmentObject(keyManagement) + SettingsView() + HistoryView() + } + .tabViewStyle(PageTabViewStyle()) + } + } +} diff --git a/Sesame-Watch Watch App/SettingsView.swift b/Sesame-Watch Watch App/SettingsView.swift new file mode 100644 index 0000000..2f084af --- /dev/null +++ b/Sesame-Watch Watch App/SettingsView.swift @@ -0,0 +1,19 @@ +import SwiftUI + +struct SettingsView: View { + var body: some View { + ScrollView { + VStack { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } + } + .navigationTitle("Settings") + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView() + .previewDevice("Apple Watch Series 7 - 41mm") + } +} diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj index 995eafd..b17d741 100644 --- a/Sesame.xcodeproj/project.pbxproj +++ b/Sesame.xcodeproj/project.pbxproj @@ -14,10 +14,31 @@ 884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */; }; 884A45C927A43D7900D6E650 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; }; 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; }; - 884A45CD27A465F500D6E650 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; }; 884A45CF27A5402D00D6E650 /* MessageResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CE27A5402D00D6E650 /* MessageResult.swift */; }; 8864664F29E5684C004FE2BE /* CBORCoding in Frameworks */ = {isa = PBXBuildFile; productRef = 8864664E29E5684C004FE2BE /* CBORCoding */; }; 8864665229E5939C004FE2BE /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 8864665129E5939C004FE2BE /* SFSafeSymbols */; }; + 888362342A80F3F90032BBB2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888362332A80F3F90032BBB2 /* SettingsView.swift */; }; + 888362362A80F4420032BBB2 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888362352A80F4420032BBB2 /* HistoryView.swift */; }; + 88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */; }; + 88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197B329EDC9BC00BF1D19 /* ContentView.swift */; }; + 88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */; }; + 88E197B929EDC9BD00BF1D19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */; }; + 88E197C229EDCB0900BF1D19 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */; }; + 88E197C429EDCC8900BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; }; + 88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CC27A465F500D6E650 /* Client.swift */; }; + 88E197C829EDCCCE00BF1D19 /* ClientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C827A43D7900D6E650 /* ClientState.swift */; }; + 88E197C929EDCCE100BF1D19 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77827FF95E00011CFD2 /* Message.swift */; }; + 88E197CC29EDCD4900BF1D19 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = 88E197CB29EDCD4900BF1D19 /* NIOCore */; }; + 88E197CE29EDCD7500BF1D19 /* CBORCoding in Frameworks */ = {isa = PBXBuildFile; productRef = 88E197CD29EDCD7500BF1D19 /* CBORCoding */; }; + 88E197D029EDCD7D00BF1D19 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 88E197CF29EDCD7D00BF1D19 /* SFSafeSymbols */; }; + 88E197D129EDCE5F00BF1D19 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */; }; + 88E197D229EDCE6600BF1D19 /* RouteAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DA2806FE8900769EF6 /* RouteAPI.swift */; }; + 88E197D329EDCE6E00BF1D19 /* MessageResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CE27A5402D00D6E650 /* MessageResult.swift */; }; + 88E197D429EDCE7600BF1D19 /* UInt32+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Extensions.swift */; }; + 88E197D529EDCE8800BF1D19 /* ServerMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1F7281E769F00769EF6 /* ServerMessage.swift */; }; + 88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */; }; + 88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; }; + 88E197D929EDD14D00BF1D19 /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28DED34281EB17600259690 /* HistoryItem.swift */; }; E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77127FDCCC00011CFD2 /* Data+Extensions.swift */; }; E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24EE77327FF95920011CFD2 /* DeviceResponse.swift */; }; E24EE77727FF95C00011CFD2 /* NIOCore in Frameworks */ = {isa = PBXBuildFile; productRef = E24EE77627FF95C00011CFD2 /* NIOCore */; }; @@ -44,6 +65,15 @@ 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SymmetricKey+Extensions.swift"; sourceTree = ""; }; 884A45CC27A465F500D6E650 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; 884A45CE27A5402D00D6E650 /* MessageResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageResult.swift; sourceTree = ""; }; + 888362332A80F3F90032BBB2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 888362352A80F4420032BBB2 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; + 88E197AC29EDC9BC00BF1D19 /* Sesame-Watch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Sesame-Watch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sesame_WatchApp.swift; sourceTree = ""; }; + 88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = ""; }; + 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; 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 = ""; }; @@ -70,6 +100,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 88E197A929EDC9BC00BF1D19 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88E197D029EDCD7D00BF1D19 /* SFSafeSymbols in Frameworks */, + 88E197CE29EDCD7500BF1D19 /* CBORCoding in Frameworks */, + 88E197CC29EDCD4900BF1D19 /* NIOCore in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -77,7 +117,9 @@ isa = PBXGroup; children = ( 884A45B5279F48C100D6E650 /* Sesame */, + 88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */, 884A45B4279F48C100D6E650 /* Products */, + 88E197CA29EDCD4900BF1D19 /* Frameworks */, ); sourceTree = ""; }; @@ -85,6 +127,7 @@ isa = PBXGroup; children = ( 884A45B3279F48C100D6E650 /* Sesame.app */, + 88E197AC29EDC9BC00BF1D19 /* Sesame-Watch Watch App.app */, ); name = Products; sourceTree = ""; @@ -102,8 +145,8 @@ E28DED36281EC7FB00259690 /* HistoryManager.swift */, E28DED2C281E840B00259690 /* SettingsView.swift */, E28DED2E281E8A0500259690 /* SingleKeyView.swift */, - 884A45CC27A465F500D6E650 /* Client.swift */, 884A45C827A43D7900D6E650 /* ClientState.swift */, + 884A45CC27A465F500D6E650 /* Client.swift */, 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */, 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */, 884A45BA279F48C300D6E650 /* Assets.xcassets */, @@ -120,6 +163,36 @@ path = "Preview Content"; sourceTree = ""; }; + 88E197B029EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = { + isa = PBXGroup; + children = ( + 88E197B129EDC9BC00BF1D19 /* Sesame_WatchApp.swift */, + 88E197B329EDC9BC00BF1D19 /* ContentView.swift */, + 888362332A80F3F90032BBB2 /* SettingsView.swift */, + 888362352A80F4420032BBB2 /* HistoryView.swift */, + 88E197C129EDCB0900BF1D19 /* KeyManagement.swift */, + 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */, + 88E197B729EDC9BD00BF1D19 /* Preview Content */, + 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */, + ); + path = "Sesame-Watch Watch App"; + sourceTree = ""; + }; + 88E197B729EDC9BD00BF1D19 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 88E197B829EDC9BD00BF1D19 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 88E197CA29EDCD4900BF1D19 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; E2C5C1D92806FE4A00769EF6 /* API */ = { isa = PBXGroup; children = ( @@ -159,6 +232,28 @@ productReference = 884A45B3279F48C100D6E650 /* Sesame.app */; productType = "com.apple.product-type.application"; }; + 88E197AB29EDC9BC00BF1D19 /* Sesame-Watch Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88E197BF29EDC9BD00BF1D19 /* Build configuration list for PBXNativeTarget "Sesame-Watch Watch App" */; + buildPhases = ( + 88E197A829EDC9BC00BF1D19 /* Sources */, + 88E197A929EDC9BC00BF1D19 /* Frameworks */, + 88E197AA29EDC9BC00BF1D19 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Sesame-Watch Watch App"; + packageProductDependencies = ( + 88E197CB29EDCD4900BF1D19 /* NIOCore */, + 88E197CD29EDCD7500BF1D19 /* CBORCoding */, + 88E197CF29EDCD7D00BF1D19 /* SFSafeSymbols */, + ); + productName = "Sesame-Watch Watch App"; + productReference = 88E197AC29EDC9BC00BF1D19 /* Sesame-Watch Watch App.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -166,13 +261,16 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1320; + LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 1320; TargetAttributes = { 884A45B2279F48C100D6E650 = { CreatedOnToolsVersion = 13.2.1; LastSwiftMigration = 1430; }; + 88E197AB29EDC9BC00BF1D19 = { + CreatedOnToolsVersion = 14.3; + }; }; }; buildConfigurationList = 884A45AE279F48C100D6E650 /* Build configuration list for PBXProject "Sesame" */; @@ -194,6 +292,7 @@ projectRoot = ""; targets = ( 884A45B2279F48C100D6E650 /* Sesame */, + 88E197AB29EDC9BC00BF1D19 /* Sesame-Watch Watch App */, ); }; /* End PBXProject section */ @@ -208,6 +307,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 88E197AA29EDC9BC00BF1D19 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88E197B929EDC9BD00BF1D19 /* Preview Assets.xcassets in Resources */, + 88E197B629EDC9BD00BF1D19 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -221,7 +329,6 @@ E28DED37281EC7FB00259690 /* HistoryManager.swift in Sources */, E2C5C1DB2806FE8900769EF6 /* RouteAPI.swift in Sources */, E2C5C1DD281B3AC400769EF6 /* UInt32+Extensions.swift in Sources */, - 884A45CD27A465F500D6E650 /* Client.swift in Sources */, E24EE77227FDCCC00011CFD2 /* Data+Extensions.swift in Sources */, E24EE77427FF95920011CFD2 /* DeviceResponse.swift in Sources */, 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */, @@ -232,11 +339,35 @@ E28DED33281EB15B00259690 /* HistoryListItem.swift in Sources */, E28DED2D281E840B00259690 /* SettingsView.swift in Sources */, 884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */, + 88E197C429EDCC8900BF1D19 /* Client.swift in Sources */, 884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */, E2C5C1F8281E769F00769EF6 /* ServerMessage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 88E197A829EDC9BC00BF1D19 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 888362342A80F3F90032BBB2 /* SettingsView.swift in Sources */, + 88E197B429EDC9BC00BF1D19 /* ContentView.swift in Sources */, + 888362362A80F4420032BBB2 /* HistoryView.swift in Sources */, + 88E197D329EDCE6E00BF1D19 /* MessageResult.swift in Sources */, + 88E197D529EDCE8800BF1D19 /* ServerMessage.swift in Sources */, + 88E197D129EDCE5F00BF1D19 /* Data+Extensions.swift in Sources */, + 88E197D229EDCE6600BF1D19 /* RouteAPI.swift in Sources */, + 88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */, + 88E197C829EDCCCE00BF1D19 /* ClientState.swift in Sources */, + 88E197B229EDC9BC00BF1D19 /* Sesame_WatchApp.swift in Sources */, + 88E197C929EDCCE100BF1D19 /* Message.swift in Sources */, + 88E197D929EDD14D00BF1D19 /* HistoryItem.swift in Sources */, + 88E197C729EDCCBD00BF1D19 /* Client.swift in Sources */, + 88E197D429EDCE7600BF1D19 /* UInt32+Extensions.swift in Sources */, + 88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */, + 88E197C229EDCB0900BF1D19 /* KeyManagement.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ @@ -423,6 +554,68 @@ }; name = Release; }; + 88E197BD29EDC9BD00BF1D19 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sesame-Watch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = H8WR4M6QQ4; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Sesame-Watch"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "de.christophhagen.Sesame-Watch.watchkitapp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 9.4; + }; + name = Debug; + }; + 88E197BE29EDC9BD00BF1D19 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sesame-Watch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = H8WR4M6QQ4; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Sesame-Watch"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "de.christophhagen.Sesame-Watch.watchkitapp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 9.4; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -444,6 +637,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 88E197BF29EDC9BD00BF1D19 /* Build configuration list for PBXNativeTarget "Sesame-Watch Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88E197BD29EDC9BD00BF1D19 /* Debug */, + 88E197BE29EDC9BD00BF1D19 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -484,6 +686,21 @@ package = 8864665029E5939C004FE2BE /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; productName = SFSafeSymbols; }; + 88E197CB29EDCD4900BF1D19 /* NIOCore */ = { + isa = XCSwiftPackageProductDependency; + package = E24EE77527FF95C00011CFD2 /* XCRemoteSwiftPackageReference "swift-nio" */; + productName = NIOCore; + }; + 88E197CD29EDCD7500BF1D19 /* CBORCoding */ = { + isa = XCSwiftPackageProductDependency; + package = 8864664D29E5684C004FE2BE /* XCRemoteSwiftPackageReference "CBORCoding" */; + productName = CBORCoding; + }; + 88E197CF29EDCD7D00BF1D19 /* SFSafeSymbols */ = { + isa = XCSwiftPackageProductDependency; + package = 8864665029E5939C004FE2BE /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; + productName = SFSafeSymbols; + }; E24EE77627FF95C00011CFD2 /* NIOCore */ = { isa = XCSwiftPackageProductDependency; package = E24EE77527FF95C00011CFD2 /* XCRemoteSwiftPackageReference "swift-nio" */; diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate index 902307e..baf532c 100644 Binary files a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate and b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist b/Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist index 5ac8256..c304408 100644 --- a/Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,11 +4,16 @@ SchemeUserState - Sesame.xcscheme_^#shared#^_ + Sesame-Watch Watch App.xcscheme_^#shared#^_ orderHint 0 + Sesame.xcscheme_^#shared#^_ + + orderHint + 1 + diff --git a/Sesame/SymmetricKey+Extensions.swift b/Sesame/SymmetricKey+Extensions.swift index 2066a91..1e517eb 100644 --- a/Sesame/SymmetricKey+Extensions.swift +++ b/Sesame/SymmetricKey+Extensions.swift @@ -46,3 +46,26 @@ extension String { return results.map { String($0) } } } + +let protocolSalt = "CryptoKit Playgrounds Putting It Together".data(using: .utf8)! + +/// Generates an ephemeral key agreement key and performs key agreement to get the shared secret and derive the symmetric encryption key. +func encrypt(_ data: Data, to theirEncryptionKey: Curve25519.KeyAgreement.PublicKey, signedBy ourSigningKey: Curve25519.Signing.PrivateKey) throws -> + (ephemeralPublicKeyData: Data, ciphertext: Data, signature: Data) { + let ephemeralKey = Curve25519.KeyAgreement.PrivateKey() + let ephemeralPublicKey = ephemeralKey.publicKey.rawRepresentation + + let sharedSecret = try ephemeralKey.sharedSecretFromKeyAgreement(with: theirEncryptionKey) + + let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, + salt: protocolSalt, + sharedInfo: ephemeralPublicKey + + theirEncryptionKey.rawRepresentation + + ourSigningKey.publicKey.rawRepresentation, + outputByteCount: 32) + + let ciphertext = try ChaChaPoly.seal(data, using: symmetricKey).combined + let signature = try ourSigningKey.signature(for: ciphertext + ephemeralPublicKey + theirEncryptionKey.rawRepresentation) + + return (ephemeralPublicKey, ciphertext, signature) +}