Initial version

This commit is contained in:
Christoph Hagen 2022-01-29 18:59:42 +01:00
parent bf6061a6d0
commit b8a04fdf57
17 changed files with 1185 additions and 0 deletions

View 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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

105
Sesame/Client.swift Normal file
View 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
View 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
View 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
View 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)
}
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

80
Sesame/Response.swift Normal file
View 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
View 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
View 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
}
}

View 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: ", ") +
"},"
}
}