You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUtilitiesKit/Database/Models/Identity.swift

183 lines
6.2 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
public struct Identity: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "identity" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case variant
case data
}
public enum Variant: String, Codable, CaseIterable, DatabaseValueConvertible {
case seed
case ed25519SecretKey
case ed25519PublicKey
case x25519PrivateKey
case x25519PublicKey
}
public var id: Variant { variant }
let variant: Variant
let data: Data
// MARK: - Initialization
public init(
variant: Variant,
data: Data
) {
self.variant = variant
self.data = data
}
}
// MARK: - GRDB Interactions
public extension Identity {
static func generate(from seed: Data) throws -> (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) {
guard (seed.count == 16) else { throw CryptoError.invalidSeed }
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Session/Media Viewing & Editing/PhotoCapture.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Notifications/AppNotifications.swift # Session/Onboarding/RestoreVC.swift # Session/Shared/SessionTableViewController.swift # Session/Shared/SessionTableViewModel.swift # SessionMessagingKit/Calls/WebRTCSession.swift # SessionMessagingKit/Database/Models/Attachment.swift # SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift # SessionMessagingKit/File Server/FileServerAPI.swift # SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift # SessionMessagingKit/Open Groups/OpenGroupAPI.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionSnodeKit/SnodeAPI.swift # SessionUtilitiesKit/Database/Models/Identity.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
let padding = Data(repeating: 0, count: 16)
guard
let ed25519KeyPair: KeyPair = Singleton.crypto.generate(
.ed25519KeyPair(seed: Array(seed + padding))
),
let x25519PublicKey: [UInt8] = Singleton.crypto.generate(
.x25519(ed25519Pubkey: ed25519KeyPair.publicKey)
),
let x25519SecretKey: [UInt8] = Singleton.crypto.generate(
.x25519(ed25519Seckey: ed25519KeyPair.secretKey)
)
else {
throw GeneralError.keyGenerationFailed
}
return (
ed25519KeyPair: KeyPair(
publicKey: ed25519KeyPair.publicKey,
secretKey: ed25519KeyPair.secretKey
),
x25519KeyPair: KeyPair(
publicKey: x25519PublicKey,
secretKey: x25519SecretKey
)
)
}
static func store(_ db: Database, seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) throws {
try Identity(variant: .seed, data: seed).save(db)
try Identity(variant: .ed25519SecretKey, data: Data(ed25519KeyPair.secretKey)).save(db)
try Identity(variant: .ed25519PublicKey, data: Data(ed25519KeyPair.publicKey)).save(db)
try Identity(variant: .x25519PrivateKey, data: Data(x25519KeyPair.secretKey)).save(db)
try Identity(variant: .x25519PublicKey, data: Data(x25519KeyPair.publicKey)).save(db)
}
static func userExists(_ db: Database? = nil) -> Bool {
return (fetchUserKeyPair(db) != nil)
}
static func fetchUserPublicKey(_ db: Database? = nil) -> Data? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserPublicKey(db) }
}
return try? Identity.fetchOne(db, id: .x25519PublicKey)?.data
}
static func fetchUserPrivateKey(_ db: Database? = nil) -> Data? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserPrivateKey(db) }
}
return try? Identity.fetchOne(db, id: .x25519PrivateKey)?.data
}
static func fetchUserKeyPair(_ db: Database? = nil) -> KeyPair? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserKeyPair(db) }
}
guard
let publicKey: Data = fetchUserPublicKey(db),
let privateKey: Data = fetchUserPrivateKey(db)
else { return nil }
return KeyPair(
publicKey: publicKey.bytes,
secretKey: privateKey.bytes
)
}
static func fetchUserEd25519KeyPair(_ db: Database? = nil) -> KeyPair? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchUserEd25519KeyPair(db) }
}
guard
let publicKey: Data = try? Identity.fetchOne(db, id: .ed25519PublicKey)?.data,
let secretKey: Data = try? Identity.fetchOne(db, id: .ed25519SecretKey)?.data
else { return nil }
return KeyPair(
publicKey: publicKey.bytes,
secretKey: secretKey.bytes
)
}
static func fetchHexEncodedSeed(_ db: Database? = nil) -> String? {
guard let db: Database = db else {
return Storage.shared.read { db in fetchHexEncodedSeed(db) }
}
guard let data: Data = try? Identity.fetchOne(db, id: .seed)?.data else {
return nil
}
return data.toHexString()
}
static func mnemonic() throws -> String {
let dbIsValid: Bool = Storage.shared.isValid
let dbIsSuspended: Bool = Storage.shared.isSuspended
if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() {
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
}
guard let legacyPrivateKey: String = Identity.fetchUserPrivateKey()?.toHexString() else {
let hasStoredPublicKey: Bool = (Identity.fetchUserPublicKey() != nil)
let hasStoredEdKeyPair: Bool = (Identity.fetchUserEd25519KeyPair() != nil)
let dbStates: [String] = [
"dbIsValid: \(dbIsValid)",
"dbIsSuspended: \(dbIsSuspended)",
"storedSeed: false",
"userPublicKey: \(hasStoredPublicKey)",
"userPrivateKey: false",
"userEdKeyPair: \(hasStoredEdKeyPair)"
]
SNLog("Failed to retrieve keys for mnemonic generation (\(dbStates.joined(separator: ", ")))")
throw StorageError.objectNotFound
}
// Legacy account
return Mnemonic.encode(hexEncodedString: legacyPrivateKey)
}
}
// MARK: - Convenience
public extension Notification.Name {
static let registrationStateDidChange = Notification.Name("registrationStateDidChange")
}
public extension Identity {
static func didRegister() {
NotificationCenter.default.post(name: .registrationStateDidChange, object: nil, userInfo: nil)
}
}