diff --git a/Sesame.xcodeproj/project.pbxproj b/Sesame.xcodeproj/project.pbxproj new file mode 100644 index 0000000..39061e4 --- /dev/null +++ b/Sesame.xcodeproj/project.pbxproj @@ -0,0 +1,372 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B6279F48C100D6E650 /* SesameApp.swift */; }; + 884A45B9279F48C100D6E650 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45B8279F48C100D6E650 /* ContentView.swift */; }; + 884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 884A45BA279F48C300D6E650 /* Assets.xcassets */; }; + 884A45BE279F48C300D6E650 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 884A45BD279F48C300D6E650 /* Preview Assets.xcassets */; }; + 884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */; }; + 884A45C727A429EF00D6E650 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45C627A429EF00D6E650 /* ShareSheet.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 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CE27A5402D00D6E650 /* Response.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 884A45B3279F48C100D6E650 /* Sesame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sesame.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 884A45B6279F48C100D6E650 /* SesameApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SesameApp.swift; sourceTree = ""; }; + 884A45B8279F48C100D6E650 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 884A45BA279F48C300D6E650 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 884A45BD279F48C300D6E650 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = ""; }; + 884A45C627A429EF00D6E650 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; + 884A45C827A43D7900D6E650 /* ClientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientState.swift; sourceTree = ""; }; + 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 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 884A45B0279F48C100D6E650 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 884A45AA279F48C100D6E650 = { + isa = PBXGroup; + children = ( + 884A45B5279F48C100D6E650 /* Sesame */, + 884A45B4279F48C100D6E650 /* Products */, + ); + sourceTree = ""; + }; + 884A45B4279F48C100D6E650 /* Products */ = { + isa = PBXGroup; + children = ( + 884A45B3279F48C100D6E650 /* Sesame.app */, + ); + name = Products; + sourceTree = ""; + }; + 884A45B5279F48C100D6E650 /* Sesame */ = { + isa = PBXGroup; + children = ( + 884A45B6279F48C100D6E650 /* SesameApp.swift */, + 884A45B8279F48C100D6E650 /* ContentView.swift */, + 884A45CC27A465F500D6E650 /* Client.swift */, + 884A45CE27A5402D00D6E650 /* Response.swift */, + 884A45C827A43D7900D6E650 /* ClientState.swift */, + 884A45C627A429EF00D6E650 /* ShareSheet.swift */, + 884A45C4279F4BBE00D6E650 /* KeyManagement.swift */, + 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */, + 884A45BA279F48C300D6E650 /* Assets.xcassets */, + 884A45BC279F48C300D6E650 /* Preview Content */, + ); + path = Sesame; + sourceTree = ""; + }; + 884A45BC279F48C300D6E650 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 884A45BD279F48C300D6E650 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 884A45B2279F48C100D6E650 /* Sesame */ = { + isa = PBXNativeTarget; + buildConfigurationList = 884A45C1279F48C300D6E650 /* Build configuration list for PBXNativeTarget "Sesame" */; + buildPhases = ( + 884A45AF279F48C100D6E650 /* Sources */, + 884A45B0279F48C100D6E650 /* Frameworks */, + 884A45B1279F48C100D6E650 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sesame; + productName = Sesame; + productReference = 884A45B3279F48C100D6E650 /* Sesame.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 884A45AB279F48C100D6E650 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1320; + TargetAttributes = { + 884A45B2279F48C100D6E650 = { + CreatedOnToolsVersion = 13.2.1; + }; + }; + }; + buildConfigurationList = 884A45AE279F48C100D6E650 /* Build configuration list for PBXProject "Sesame" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 884A45AA279F48C100D6E650; + productRefGroup = 884A45B4279F48C100D6E650 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 884A45B2279F48C100D6E650 /* Sesame */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 884A45B1279F48C100D6E650 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 884A45BE279F48C300D6E650 /* Preview Assets.xcassets in Resources */, + 884A45BB279F48C300D6E650 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 884A45AF279F48C100D6E650 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 884A45CF27A5402D00D6E650 /* Response.swift in Sources */, + 884A45B9279F48C100D6E650 /* ContentView.swift in Sources */, + 884A45CD27A465F500D6E650 /* Client.swift in Sources */, + 884A45CB27A464C000D6E650 /* SymmetricKey+Extensions.swift in Sources */, + 884A45C927A43D7900D6E650 /* ClientState.swift in Sources */, + 884A45B7279F48C100D6E650 /* SesameApp.swift in Sources */, + 884A45C5279F4BBE00D6E650 /* KeyManagement.swift in Sources */, + 884A45C727A429EF00D6E650 /* ShareSheet.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 884A45BF279F48C300D6E650 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 884A45C0279F48C300D6E650 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 884A45C2279F48C300D6E650 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sesame/Preview Content\""; + DEVELOPMENT_TEAM = H8WR4M6QQ4; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.Sesame; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 884A45C3279F48C300D6E650 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sesame/Preview Content\""; + DEVELOPMENT_TEAM = H8WR4M6QQ4; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = de.christophhagen.Sesame; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 884A45AE279F48C100D6E650 /* Build configuration list for PBXProject "Sesame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 884A45BF279F48C300D6E650 /* Debug */, + 884A45C0279F48C300D6E650 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 884A45C1279F48C300D6E650 /* Build configuration list for PBXNativeTarget "Sesame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 884A45C2279F48C300D6E650 /* Debug */, + 884A45C3279F48C300D6E650 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 884A45AB279F48C100D6E650 /* Project object */; +} diff --git a/Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Sesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Sesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Sesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate b/Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..902307e Binary files /dev/null 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 new file mode 100644 index 0000000..5ac8256 --- /dev/null +++ b/Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Sesame.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Sesame/Assets.xcassets/AccentColor.colorset/Contents.json b/Sesame/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Sesame/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "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" + }, + { + "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" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame/Assets.xcassets/Contents.json b/Sesame/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sesame/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sesame/Client.swift b/Sesame/Client.swift new file mode 100644 index 0000000..7e51653 --- /dev/null +++ b/Sesame/Client.swift @@ -0,0 +1,105 @@ +import Foundation +import CryptoKit + +struct Client { + + let server: URL + + private let delegate = NeverCacheDelegate() + + init(server: URL) { + self.server = server + } + + private enum RequestReponse: Error { + case requestFailed + case unknownResponse + case success(UInt8) + } + + func deviceStatus() async throws -> ClientState { + let url = server.appendingPathComponent("status") + let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) + let response = await integerReponse(to: request) + switch response { + case .requestFailed: + return .statusRequestFailed + case .unknownResponse: + return .unknownDeviceStatus + case .success(let int): + switch int { + case 0: + return .deviceDisconnected + case 1: + return .deviceConnected + default: + print("Unexpected device status '\(int)'") + return .unknownDeviceStatus + } + } + } + + func keyResponse(key: SymmetricKey, id: Int) async throws -> ClientState { + let url = server.appendingPathComponent("key/\(id)") + var request = URLRequest(url: url) + request.httpBody = key.data + request.httpMethod = "POST" + let response = await integerReponse(to: request) + switch response { + case .requestFailed: + return .statusRequestFailed + case .unknownResponse: + return .unknownDeviceStatus + case .success(let int): + guard let status = KeyResult(rawValue: int) else { + print("Invalid key response: \(int)") + return .unknownDeviceStatus + } + return ClientState(keyResult: status) + } + } + + private func fulfill(_ request: URLRequest) async -> Result { + do { + let (data, response) = try await URLSession.shared.data(for: request) + guard let code = (response as? HTTPURLResponse)?.statusCode else { + print("No response from server") + return .failure(.requestFailed) + } + guard code == 200 else { + print("Invalid server response \(code)") + return .failure(.requestFailed) + } + return .success(data) + } catch { + print("Request failed: \(error)") + return .failure(.requestFailed) + } + } + + private func integerReponse(to request: URLRequest) async -> RequestReponse { + let response = await fulfill(request) + switch response { + case .failure(let cause): + return cause + case .success(let data): + guard let string = String(data: data, encoding: .utf8) else { + print("Unexpected device status data: \([UInt8](data))") + return .unknownResponse + } + guard let int = UInt8(string) else { + print("Unexpected device status '\(string)'") + return .unknownResponse + } + return .success(int) + } + } + +} + +class NeverCacheDelegate: NSObject, NSURLConnectionDataDelegate { + + func connection(_ connection: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? { + return nil + } +} diff --git a/Sesame/ClientState.swift b/Sesame/ClientState.swift new file mode 100644 index 0000000..a25b25d --- /dev/null +++ b/Sesame/ClientState.swift @@ -0,0 +1,137 @@ +import Foundation +import SwiftUI + +enum ClientState { + + /// The initial state after app launch + case initial + + /// There are no keys stored locally on the client. New keys must be generated before use. + case noKeysAvailable + + /// New keys have been generated and can now be transmitted to the device. + case newKeysGenerated + + /// The device status could not be determined + case statusRequestFailed + + /// The status received from the server could not be decoded + case unknownDeviceStatus + + /// The remote device is not connected (no socket opened) + case deviceDisconnected + + /// The device is connected and ready to receive a key + case deviceConnected + + /// The key is being transmitted and a response is awaited + case waitingForResponse + + /// The transmitted key was rejected (multiple possible reasons) + case keyRejected + + /// Internal errors with the implementation + case internalError + + /// The configuration of the devices doesn't match + case configurationError + + /// The device responded that the opening action was started + case openSesame + + /// All keys have been used + case allKeysUsed + + var canSendKey: Bool { + switch self { + case .deviceConnected, .openSesame, .keyRejected: + return true + default: + return false + } + } + + init(keyResult: KeyResult) { + switch keyResult { + case .textReceived, .unexpectedSocketEvent, .unknownDeviceError: + self = .unknownDeviceStatus + case .invalidPayloadSize, .invalidKeyIndex, .invalidKey: + self = .configurationError + case .keyAlreadyUsed, .keyWasSkipped: + self = .keyRejected + case .keyAccepted: + self = .openSesame + case .noBodyData, .corruptkeyData: + self = .internalError + case .deviceNotConnected, .deviceTimedOut: + self = .deviceDisconnected + } + } + + var description: String { + switch self { + case .initial: + return "Checking state..." + case .noKeysAvailable: + return "No keys found" + case .newKeysGenerated: + return "New keys generated" + case .deviceDisconnected: + return "Device not connected" + case .statusRequestFailed: + return "Unable to get device status" + case .unknownDeviceStatus: + return "Unknown device status" + case .deviceConnected: + return "Device connected" + case .waitingForResponse: + return "Waiting for response" + case .internalError: + return "An internal error occured" + case .configurationError: + return "Configuration error" + + case .allKeysUsed: + return "No fresh keys available" + case .keyRejected: + return "The key was rejected" + case .openSesame: + return "Unlocked" + } + } + + var openButtonText: String { + switch self { + case .initial, .statusRequestFailed, .unknownDeviceStatus, .deviceDisconnected, .newKeysGenerated, .configurationError, .internalError: + return "Connect" + case .allKeysUsed, .noKeysAvailable: + return "Disabled" + case .deviceConnected, .keyRejected, .openSesame: + return "Unlock" + case .waitingForResponse: + return "Unlocking..." + } + } + + var openButtonColor: Color { + switch self { + case .initial, .newKeysGenerated, .statusRequestFailed, .waitingForResponse: + return .yellow + case .noKeysAvailable, .allKeysUsed, .deviceDisconnected, .unknownDeviceStatus, .keyRejected, .configurationError, .internalError: + return .red + case .deviceConnected, .openSesame: + return .green + } + } + + var openActionIsEnabled: Bool { + switch self { + case .allKeysUsed, .noKeysAvailable, .waitingForResponse: + return false + default: + return true + } + } + + +} diff --git a/Sesame/ContentView.swift b/Sesame/ContentView.swift new file mode 100644 index 0000000..2396a3a --- /dev/null +++ b/Sesame/ContentView.swift @@ -0,0 +1,164 @@ +import SwiftUI +import CryptoKit + +let keyManager = try! KeyManagement() +let server = Client(server: URL(string: "https://christophhagen.de/sesame/")!) + +struct ContentView: View { + + @State var state: ClientState = .initial + + var canShareKey = false + + @State var showNewKeyWarning = false + + @State var showKeyGenerationFailedWarning = false + + @State var showShareSheetForNewKeys = false + + @State var activeRequestCount = 0 + + var isPerformingRequests: Bool { + activeRequestCount > 0 + } + + var keyText: String { + let totalKeys = keyManager.numberOfKeys + guard totalKeys > 0 else { + return "No keys available" + } + let unusedKeys = keyManager.unusedKeyCount + guard unusedKeys > 0 else { + return "All keys used" + } + return "\(totalKeys - unusedKeys) / \(totalKeys) keys used" + } + + private let buttonWidth: CGFloat = 200 + + private let topButtonHeight: CGFloat = 60 + + var body: some View { + VStack(spacing: 20) { + Text(keyText) + Button("Generate new keys", action: { + showNewKeyWarning = true + print("Key regeneration requested") + }) + .padding() + .frame(width: buttonWidth, height: topButtonHeight) + .background(.blue) + .foregroundColor(.white) + .cornerRadius(topButtonHeight / 2) + Button("Share one-time key", action: shareKey) + .padding() + .frame(width: buttonWidth, height: topButtonHeight) + .background(.mint) + .foregroundColor(.white) + .cornerRadius(topButtonHeight / 2) + .disabled(!canShareKey) + + Spacer() + HStack { + if isPerformingRequests { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + Text(state.description) + .padding() + } + Button(state.openButtonText, action: mainButtonPressed) + .frame(width: buttonWidth, height: 80, alignment: .center) + .background(state.openButtonColor) + .cornerRadius(100) + .foregroundColor(.white) + .font(.title2) + .disabled(!state.openActionIsEnabled) + } + .padding(20) + .onAppear { + checkInitialDeviceStatus() + }.alert(isPresented: $showKeyGenerationFailedWarning) { + Alert(title: Text("The keys could not be generated"), + message: Text("All previous keys will be deleted and the lock will be blocked. Are you sure?"), + dismissButton: .default(Text("Okay"))) + }.shareSheet(isPresented: $showShareSheetForNewKeys, items: [keyManager.exportFile]) + .alert(isPresented: $showNewKeyWarning) { + Alert(title: Text("Generate new keys"), + message: Text("All previous keys will be deleted and the lock will be blocked. Are you sure?"), + primaryButton: .destructive(Text("Generate"), action: regenerateKeys), + secondaryButton: .cancel()) + } + } + + func mainButtonPressed() { + print("Main button pressed") + if state.canSendKey { + sendKey() + } else { + checkInitialDeviceStatus() + } + } + + func sendKey() { + guard let key = keyManager.useNextKey() else { + state = .allKeysUsed + return + } + state = .waitingForResponse + activeRequestCount += 1 + print("Sending key \(key.id)") + Task { + let newState = try await server.keyResponse(key: key.key, id: key.id) + activeRequestCount -= 1 + state = newState + } + } + + func checkInitialDeviceStatus() { + print("Checking device status") + Task { + do { + activeRequestCount += 1 + let newState = try await server.deviceStatus() + activeRequestCount -= 1 + print("Device status: \(newState)") + switch newState { + case .noKeysAvailable, .allKeysUsed: + return + default: + state = newState + } + } catch { + print("Failed to get device status: \(error)") + state = .statusRequestFailed + } + } + } + + func regenerateKeys() { + print("Regenerate keys") + do { + try keyManager.regenerateKeys() + state = .newKeysGenerated + showKeyGenerationFailedWarning = false + showShareSheetForNewKeys = true + checkInitialDeviceStatus() + } catch { + state = .noKeysAvailable + showKeyGenerationFailedWarning = true + showShareSheetForNewKeys = false + } + } + + func shareKey() { + + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .previewDevice("iPhone 8") + } +} diff --git a/Sesame/KeyManagement.swift b/Sesame/KeyManagement.swift new file mode 100644 index 0000000..111b11f --- /dev/null +++ b/Sesame/KeyManagement.swift @@ -0,0 +1,118 @@ +import Foundation +import CryptoKit +import SwiftUI + +final class KeyManagement { + + static let securityKeySize: SymmetricKeySize = .bits128 + + enum KeyError: Error { + /// Keys which are already in use can't be exported + case exportAttemptOfUsedKeys + } + + static var documentsDirectory: URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + + private let keyFile = KeyManagement.documentsDirectory.appendingPathComponent("keys") + + let exportFile = KeyManagement.documentsDirectory.appendingPathComponent("export.cpp") + + private var keys: [(key: SymmetricKey, used: Bool)] { + didSet { + do { + try saveKeys() + } catch { + print("Failed to save changed keys: \(error)") + } + } + } + + var numberOfKeys: Int { + keys.count + } + + var hasUsedKeys: Bool { + keys.contains { $0.used } + } + + var hasUnusedKeys: Bool { + unusedKeyCount > 0 + } + + var unusedKeyCount: Int { + guard let id = nextKeyId else { + return 0 + } + return keys.count - id + 1 + } + + var usedKeyCount: Int { + nextKeyId ?? keys.count + } + + var lastKeyId: Int? { + keys.lastIndex { $0.used } + } + + var nextKeyId: Int? { + let index = lastKeyId ?? -1 + 1 + guard index < keys.count else { + return nil + } + return index + } + + init() throws { + guard FileManager.default.fileExists(atPath: keyFile.path) else { + self.keys = [] + return + } + let content = try String(contentsOf: keyFile) + self.keys = content.components(separatedBy: "\n") + .enumerated().compactMap { (index, line) -> (SymmetricKey, Bool)? in + let parts = line.components(separatedBy: ":") + guard parts.count == 2 else { + return nil + } + let keyData = Data(base64Encoded: parts[0])! + return (SymmetricKey(data: keyData), parts[1] != "0") + } + print("\(unusedKeyCount) / \(keys.count) keys remaining") + } + + func useNextKey() -> (key: SymmetricKey, id: Int)? { + guard let index = nextKeyId else { + return nil + } + let key = keys[index].key + keys[index].used = true + return (key, index) + } + + func regenerateKeys(count: Int = 100) throws { + self.keys = Self.generateKeys(count: count) + .map { ($0, false) } + let keyString = keys.map { $0.key.codeString }.joined(separator: "\n") + try keyString.write(to: exportFile, atomically: false, encoding: .utf8) + } + + private func saveKeys() throws { + let content = keys.map { key, used -> String in + let keyString = key.withUnsafeBytes { + return Data(Array($0)).base64EncodedString() + } + return keyString + ":" + (used ? "1" : "0") + }.joined(separator: "\n") + try content.write(to: keyFile, atomically: true, encoding: .utf8) + print("Keys saved") + } + + static func generateKeys(count: Int = 100) -> [SymmetricKey] { + (0.., items: [Any]) -> some View { + guard isPresented.wrappedValue else { return self } + let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) + let presentedViewController = UIApplication.keyWindow?.rootViewController?.presentedViewController ?? UIApplication.keyWindow?.rootViewController + activityViewController.completionWithItemsHandler = { _, _, _, _ in isPresented.wrappedValue = false } + presentedViewController?.present(activityViewController, animated: true) + return self + } + +} diff --git a/Sesame/SymmetricKey+Extensions.swift b/Sesame/SymmetricKey+Extensions.swift new file mode 100644 index 0000000..d977084 --- /dev/null +++ b/Sesame/SymmetricKey+Extensions.swift @@ -0,0 +1,21 @@ +import Foundation +import CryptoKit + +extension SymmetricKey { + + var data: Data { + withUnsafeBytes { Data(Array($0)) } + } + + var base64: String { + data.base64EncodedString() + } + + var codeString: String { + " {" + + withUnsafeBytes { + return Data(Array($0)) + }.map(String.init).joined(separator: ", ") + + "}," + } +}