Initial version
This commit is contained in:
parent
bf6061a6d0
commit
b8a04fdf57
372
Sesame.xcodeproj/project.pbxproj
Normal file
372
Sesame.xcodeproj/project.pbxproj
Normal file
@ -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 = "<group>"; };
|
||||||
|
884A45B8279F48C100D6E650 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
884A45BA279F48C300D6E650 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
884A45BD279F48C300D6E650 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
|
884A45C4279F4BBE00D6E650 /* KeyManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagement.swift; sourceTree = "<group>"; };
|
||||||
|
884A45C627A429EF00D6E650 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||||
|
884A45C827A43D7900D6E650 /* ClientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientState.swift; sourceTree = "<group>"; };
|
||||||
|
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SymmetricKey+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
884A45CC27A465F500D6E650 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
|
||||||
|
884A45CE27A5402D00D6E650 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
||||||
|
/* 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 = "<group>";
|
||||||
|
};
|
||||||
|
884A45B4279F48C100D6E650 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
884A45B3279F48C100D6E650 /* Sesame.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
884A45BC279F48C300D6E650 /* Preview Content */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
884A45BD279F48C300D6E650 /* Preview Assets.xcassets */,
|
||||||
|
);
|
||||||
|
path = "Preview Content";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* 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 */;
|
||||||
|
}
|
7
Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
Sesame.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
Sesame.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>Sesame.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
11
Sesame/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
11
Sesame/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
98
Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
98
Sesame/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
6
Sesame/Assets.xcassets/Contents.json
Normal file
6
Sesame/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
105
Sesame/Client.swift
Normal file
105
Sesame/Client.swift
Normal file
@ -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<Data, RequestReponse> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
137
Sesame/ClientState.swift
Normal file
137
Sesame/ClientState.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
164
Sesame/ContentView.swift
Normal file
164
Sesame/ContentView.swift
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
118
Sesame/KeyManagement.swift
Normal file
118
Sesame/KeyManagement.swift
Normal file
@ -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..<count).map { _ in
|
||||||
|
SymmetricKey(size: securityKeySize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
80
Sesame/Response.swift
Normal file
80
Sesame/Response.swift
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
A result from sending a key to the device.
|
||||||
|
*/
|
||||||
|
enum KeyResult: UInt8 {
|
||||||
|
|
||||||
|
/// Text content was received, although binary data was expected
|
||||||
|
case textReceived = 1
|
||||||
|
|
||||||
|
/// A socket event on the device was unexpected (not binary data)
|
||||||
|
case unexpectedSocketEvent = 2
|
||||||
|
|
||||||
|
/// The size of the payload (key id + key data, or just key) was invalid
|
||||||
|
case invalidPayloadSize = 3
|
||||||
|
|
||||||
|
/// The index of the key was out of bounds
|
||||||
|
case invalidKeyIndex = 4
|
||||||
|
|
||||||
|
/// The transmitted key data did not match the expected key
|
||||||
|
case invalidKey = 5
|
||||||
|
|
||||||
|
/// The key has been previously used and is no longer valid
|
||||||
|
case keyAlreadyUsed = 6
|
||||||
|
|
||||||
|
/// A later key has been used, invalidating this key (to prevent replay attacks after blocked communication)
|
||||||
|
case keyWasSkipped = 7
|
||||||
|
|
||||||
|
/// The key was accepted by the device, and the door will be opened
|
||||||
|
case keyAccepted = 8
|
||||||
|
|
||||||
|
/// The device produced an unknown error
|
||||||
|
case unknownDeviceError = 9
|
||||||
|
|
||||||
|
/// The request did not contain body data with the key
|
||||||
|
case noBodyData = 10
|
||||||
|
|
||||||
|
/// The body data could not be read
|
||||||
|
case corruptkeyData = 11
|
||||||
|
|
||||||
|
/// The device is not connected
|
||||||
|
case deviceNotConnected = 12
|
||||||
|
|
||||||
|
/// The device did not respond within the timeout
|
||||||
|
case deviceTimedOut = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
extension KeyResult: CustomStringConvertible {
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidKeyIndex:
|
||||||
|
return "Invalid key id (too large)"
|
||||||
|
case .noBodyData:
|
||||||
|
return "No body data included in the request"
|
||||||
|
case .invalidPayloadSize:
|
||||||
|
return "Invalid key size"
|
||||||
|
case .corruptkeyData:
|
||||||
|
return "Key data corrupted"
|
||||||
|
case .deviceNotConnected:
|
||||||
|
return "Device not connected"
|
||||||
|
case .textReceived:
|
||||||
|
return "The device received unexpected text"
|
||||||
|
case .unexpectedSocketEvent:
|
||||||
|
return "Unexpected socket event for the device"
|
||||||
|
case .invalidKey:
|
||||||
|
return "The transmitted key was not correct"
|
||||||
|
case .keyAlreadyUsed:
|
||||||
|
return "The transmitted key was already used"
|
||||||
|
case .keyWasSkipped:
|
||||||
|
return "A newer key was already used"
|
||||||
|
case .keyAccepted:
|
||||||
|
return "Key successfully sent"
|
||||||
|
case .unknownDeviceError:
|
||||||
|
return "The device experienced an unknown error"
|
||||||
|
case .deviceTimedOut:
|
||||||
|
return "The device did not respond"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
Sesame/SesameApp.swift
Normal file
17
Sesame/SesameApp.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// SesameApp.swift
|
||||||
|
// Sesame
|
||||||
|
//
|
||||||
|
// Created by iMac on 24.01.22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct SesameApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Sesame/ShareSheet.swift
Normal file
21
Sesame/ShareSheet.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension UIApplication {
|
||||||
|
|
||||||
|
static let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first
|
||||||
|
static let keyWindowScene = shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
func shareSheet(isPresented: Binding<Bool>, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
Sesame/SymmetricKey+Extensions.swift
Normal file
21
Sesame/SymmetricKey+Extensions.swift
Normal file
@ -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: ", ") +
|
||||||
|
"},"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user