213 lines
7.8 KiB
Swift
213 lines
7.8 KiB
Swift
//
|
|
// EncryptedStorageDemo.swift
|
|
// SecureStorgageSample
|
|
//
|
|
// Demonstrates encrypted file storage with LocalData package.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LocalData
|
|
|
|
struct EncryptedStorageDemo: View {
|
|
@State private var logEntry = ""
|
|
@State private var storedLogs: [String] = []
|
|
@State private var statusMessage = ""
|
|
@State private var isLoading = false
|
|
@State private var iterations = 10_000
|
|
@State private var useExternalKeyProvider = false
|
|
@FocusState private var isFieldFocused: Bool
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section {
|
|
Text("Encrypted file storage uses AES-256-GCM encryption with PBKDF2 key derivation. You can also switch to an external key provider that derives keys via HKDF. Data is encrypted before being written to disk with complete file protection.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Section("Add Log Entry") {
|
|
TextField("Enter log message", text: $logEntry, axis: .vertical)
|
|
.lineLimit(3, reservesSpace: true)
|
|
.focused($isFieldFocused)
|
|
|
|
Toggle("Use External Key Provider", isOn: $useExternalKeyProvider)
|
|
|
|
if !useExternalKeyProvider {
|
|
Stepper("PBKDF2 Iterations: \(iterations)", value: $iterations, in: 1000...100000, step: 1000)
|
|
.font(.caption)
|
|
|
|
Text("Higher iterations = more secure but slower")
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
Text("Key material is resolved from a registered provider.")
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
Section("Actions") {
|
|
Button(action: addLogEntry) {
|
|
HStack {
|
|
Image(systemName: "plus.circle.fill")
|
|
Text("Add Encrypted Log Entry")
|
|
}
|
|
}
|
|
.disabled(logEntry.isEmpty || isLoading)
|
|
|
|
Button(action: loadLogs) {
|
|
HStack {
|
|
Image(systemName: "lock.open.fill")
|
|
Text("Decrypt and Load Logs")
|
|
}
|
|
}
|
|
.disabled(isLoading)
|
|
|
|
Button(action: clearLogs) {
|
|
HStack {
|
|
Image(systemName: "trash")
|
|
Text("Clear All Logs")
|
|
}
|
|
}
|
|
.foregroundStyle(.red)
|
|
.disabled(isLoading)
|
|
}
|
|
|
|
if !storedLogs.isEmpty {
|
|
Section("Decrypted Logs (\(storedLogs.count))") {
|
|
ForEach(Array(storedLogs.enumerated()), id: \.offset) { index, log in
|
|
HStack {
|
|
Text("\(index + 1).")
|
|
.foregroundStyle(.secondary)
|
|
Text(log)
|
|
}
|
|
.font(.system(.caption, design: .monospaced))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !statusMessage.isEmpty {
|
|
Section {
|
|
Text(statusMessage)
|
|
.font(.caption)
|
|
.foregroundStyle(statusMessage.contains("Error") ? .red : .green)
|
|
}
|
|
}
|
|
|
|
Section("Encryption Details") {
|
|
LabeledContent("Algorithm", value: useExternalKeyProvider ? "ChaCha20-Poly1305" : "AES-256-GCM")
|
|
LabeledContent("Key Derivation", value: useExternalKeyProvider ? "HKDF-SHA256" : "PBKDF2-SHA256")
|
|
if !useExternalKeyProvider {
|
|
LabeledContent("Iterations", value: "\(iterations)")
|
|
} else {
|
|
LabeledContent("Key Source", value: "External Provider")
|
|
}
|
|
LabeledContent("File Protection", value: "Complete")
|
|
}
|
|
|
|
Section("Key Configuration") {
|
|
LabeledContent("Domain", value: "Encrypted File System")
|
|
LabeledContent("Directory", value: "Caches")
|
|
LabeledContent("Security", value: useExternalKeyProvider ? "External Key Provider" : "AES-256 Encrypted")
|
|
LabeledContent("Platform", value: "Phone Only")
|
|
}
|
|
}
|
|
.navigationTitle("Encrypted Storage")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
Spacer()
|
|
Button("Done") {
|
|
isFieldFocused = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addLogEntry() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
if useExternalKeyProvider {
|
|
let key = StorageKeys.ExternalSessionLogsKey()
|
|
let logs = try await updatedLogs(for: key)
|
|
try await StorageRouter.shared.set(logs, for: key)
|
|
storedLogs = logs
|
|
} else {
|
|
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
|
let logs = try await updatedLogs(for: key)
|
|
try await StorageRouter.shared.set(logs, for: key)
|
|
storedLogs = logs
|
|
}
|
|
|
|
logEntry = ""
|
|
statusMessage = "✓ Entry encrypted and saved"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func loadLogs() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
if useExternalKeyProvider {
|
|
let key = StorageKeys.ExternalSessionLogsKey()
|
|
storedLogs = try await StorageRouter.shared.get(key)
|
|
} else {
|
|
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
|
storedLogs = try await StorageRouter.shared.get(key)
|
|
}
|
|
statusMessage = "✓ Decrypted \(storedLogs.count) log entries"
|
|
} catch StorageError.notFound {
|
|
storedLogs = []
|
|
statusMessage = "No encrypted logs found"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func clearLogs() {
|
|
isLoading = true
|
|
Task {
|
|
do {
|
|
if useExternalKeyProvider {
|
|
let key = StorageKeys.ExternalSessionLogsKey()
|
|
try await StorageRouter.shared.remove(key)
|
|
} else {
|
|
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
|
try await StorageRouter.shared.remove(key)
|
|
}
|
|
storedLogs = []
|
|
statusMessage = "✓ Encrypted logs cleared"
|
|
} catch {
|
|
statusMessage = "Error: \(error.localizedDescription)"
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
private func updatedLogs<Key: StorageKey>(for key: Key) async throws -> [String] where Key.Value == [String] {
|
|
var logs: [String]
|
|
do {
|
|
logs = try await StorageRouter.shared.get(key)
|
|
} catch StorageError.notFound {
|
|
logs = []
|
|
}
|
|
|
|
let timestamp = Date().formatted(date: .abbreviated, time: .standard)
|
|
logs.append("[\(timestamp)] \(logEntry)")
|
|
return logs
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
EncryptedStorageDemo()
|
|
}
|
|
}
|