From b8a04fdf5795cc64b9ac4de849a6d08c63bb19a9 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sat, 29 Jan 2022 18:59:42 +0100 Subject: [PATCH] Initial version --- Sesame.xcodeproj/project.pbxproj | 372 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 30473 bytes .../xcschemes/xcschememanagement.plist | 14 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 98 +++++ Sesame/Assets.xcassets/Contents.json | 6 + Sesame/Client.swift | 105 +++++ Sesame/ClientState.swift | 137 +++++++ Sesame/ContentView.swift | 164 ++++++++ Sesame/KeyManagement.swift | 118 ++++++ .../Preview Assets.xcassets/Contents.json | 6 + Sesame/Response.swift | 80 ++++ Sesame/SesameApp.swift | 17 + Sesame/ShareSheet.swift | 21 + Sesame/SymmetricKey+Extensions.swift | 21 + 17 files changed, 1185 insertions(+) create mode 100644 Sesame.xcodeproj/project.pbxproj create mode 100644 Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Sesame.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 Sesame.xcodeproj/xcuserdata/imac.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 Sesame/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Sesame/Assets.xcassets/Contents.json create mode 100644 Sesame/Client.swift create mode 100644 Sesame/ClientState.swift create mode 100644 Sesame/ContentView.swift create mode 100644 Sesame/KeyManagement.swift create mode 100644 Sesame/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 Sesame/Response.swift create mode 100644 Sesame/SesameApp.swift create mode 100644 Sesame/ShareSheet.swift create mode 100644 Sesame/SymmetricKey+Extensions.swift 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 0000000000000000000000000000000000000000..902307e0a98a7792950cb4e6777471fce4b8f2f9 GIT binary patch literal 30473 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 GiT?+pA~Ng% literal 0 HcmV?d00001 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: ", ") + + "}," + } +}