metatext/Shared/Services/SecretsService.swift

124 lines
3.3 KiB
Swift
Raw Normal View History

// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
protocol SecretsStorable {
var dataStoredInSecrets: Data { get }
static func fromDataStoredInSecrets(_ data: Data) throws -> Self
}
enum SecretsStorableError: Error {
case conversionFromDataStoredInSecrets(Data)
}
2020-08-09 02:52:41 +00:00
struct SecretsService {
let identityID: UUID
2020-08-12 09:01:21 +00:00
private let keychainServiceType: KeychainService.Type
2020-08-12 09:01:21 +00:00
init(identityID: UUID, keychainServiceType: KeychainService.Type) {
2020-08-09 02:52:41 +00:00
self.identityID = identityID
2020-08-12 07:24:39 +00:00
self.keychainServiceType = keychainServiceType
}
}
2020-08-09 02:52:41 +00:00
extension SecretsService {
enum Item: String, CaseIterable {
2020-08-12 07:24:39 +00:00
case clientID
case clientSecret
case accessToken
case pushKey
case pushAuth
}
}
2020-08-09 02:52:41 +00:00
extension SecretsService {
func set(_ data: SecretsStorable, forItem item: Item) throws {
2020-08-12 07:24:39 +00:00
try keychainServiceType.setGenericPassword(
data: data.dataStoredInSecrets,
forAccount: key(item: item),
service: Self.keychainServiceName)
}
2020-08-09 02:52:41 +00:00
func item<T: SecretsStorable>(_ item: Item) throws -> T? {
2020-08-12 07:24:39 +00:00
guard let data = try keychainServiceType.getGenericPassword(
account: key(item: item),
service: Self.keychainServiceName) else {
2020-08-09 01:29:05 +00:00
return nil
}
return try T.fromDataStoredInSecrets(data)
}
2020-08-09 02:52:41 +00:00
func deleteAllItems() throws {
for item in SecretsService.Item.allCases {
2020-08-12 07:24:39 +00:00
try keychainServiceType.deleteGenericPassword(
account: key(item: item),
service: Self.keychainServiceName)
2020-08-09 02:52:41 +00:00
}
}
2020-08-12 07:24:39 +00:00
func generatePushKeyAndReturnPublicKey() throws -> Data {
try keychainServiceType.generateKeyAndReturnPublicKey(
applicationTag: key(item: .pushKey),
attributes: PushKey.attributes)
2020-08-12 07:24:39 +00:00
}
func getPushKey() throws -> Data? {
try keychainServiceType.getPrivateKey(
applicationTag: key(item: .pushKey),
attributes: PushKey.attributes)
2020-08-12 07:24:39 +00:00
}
func generatePushAuth() throws -> Data {
var bytes = [UInt8](repeating: 0, count: PushKey.authLength)
2020-08-12 07:24:39 +00:00
_ = SecRandomCopyBytes(kSecRandomDefault, PushKey.authLength, &bytes)
2020-08-12 07:24:39 +00:00
let pushAuth = Data(bytes)
try set(pushAuth, forItem: .pushAuth)
return pushAuth
}
func getPushAuth() throws -> Data? {
try item(.pushAuth)
}
}
2020-08-09 02:52:41 +00:00
private extension SecretsService {
2020-08-12 07:24:39 +00:00
static let keychainServiceName = "com.metabolist.metatext"
2020-08-09 02:52:41 +00:00
func key(item: Item) -> String {
identityID.uuidString + "." + item.rawValue
}
}
extension Data: SecretsStorable {
var dataStoredInSecrets: Data { self }
static func fromDataStoredInSecrets(_ data: Data) throws -> Data {
data
}
}
extension String: SecretsStorable {
var dataStoredInSecrets: Data { Data(utf8) }
static func fromDataStoredInSecrets(_ data: Data) throws -> String {
guard let string = String(data: data, encoding: .utf8) else {
throw SecretsStorableError.conversionFromDataStoredInSecrets(data)
}
return string
}
}
struct PushKey {
static let authLength = 16
static let sizeInBits = 256
static let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: sizeInBits]
}