Sesame-iOS/Sesame/KeyManagement.swift
2022-01-29 18:59:42 +01:00

119 lines
3.3 KiB
Swift

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