Skip to content

Instantly share code, notes, and snippets.

@laeroah
Created August 25, 2017 04:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save laeroah/aeabc9bfd7e96a8ab5e6b20d979e575d to your computer and use it in GitHub Desktop.
Save laeroah/aeabc9bfd7e96a8ab5e6b20d979e575d to your computer and use it in GitHub Desktop.
A sample `AFEncryptionDelegate` implementation. You can refer to this gist when you need to create a encryption extension for AppFriends.
//
// EncryptionManager.swift
// HacknocraftBaseProject
//
// Created by HAO WANG on 7/31/17.
// Copyright © 2017 Hacknocraft. All rights reserved.
//
import Foundation
import VirgilSDK
import RxSwift
import AppFriendsCore
import AppFriendsUI
struct VirgilConstants {
static let ChatUser = "SecureChatUser"
static let PrivateKeyStorage = "AppFriendsPrivateKeyStorage"
}
class EncryptionManager: NSObject {
let virgil: VSSVirgilApi?
let disposeBag = DisposeBag()
var virgilCards: [String: VSSVirgilCard] = [String: VSSVirgilCard]()
static let sharedInstance = EncryptionManager()
override init() {
// initialize Virgil
if let virgilToken = EnvConfig.envConfigValue(forKey: "virgilToken") {
virgil = VSSVirgilApi(token: virgilToken)
} else {
virgil = nil
}
super.init()
}
// MARK: - Virgil Cards
func obtainCardForCurrentUser(identity: String, password: String? = nil) -> Observable<Void> {
// try find and create a virgil card for the current user
return searchForExistingCardCurrentUser(identity: identity, password: password)
.flatMap({ (card) -> Observable<Void> in
if let existingCard = card {
do {
_ = try self.virgil?.keys.loadKey(withName: identity, password: password)
return self.publishAndVerifyCard(userID: identity, card: existingCard)
} catch {
return Observable.create({ (observer) -> Disposable in
observer.onError(error)
return Disposables.create()
})
}
} else {
return self.createCardForCurrentUser(identity: identity, password: password)
.flatMap({ (card) in
self.publishAndVerifyCard(userID: identity, card: card)
})
}
})
}
func createCardForCurrentUser(identity: String, password: String? = nil) -> Observable<VSSVirgilCard> {
return Observable.create({ (observer) -> Disposable in
guard let virgil = self.virgil else {
// virgil not initialized
let errorReason = NSLocalizedString("virgil not initialized",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
observer.onError(err)
return Disposables.create()
}
var currentUserKey: VSSVirgilKey?
do {
// try find the key first from keychain
currentUserKey = try virgil.keys
.loadKey(withName: identity,
password: password)
} catch { }
if currentUserKey == nil {
// key not found, create a new one
currentUserKey = virgil.keys.generateKey()
do {
try currentUserKey?.store(withName: identity, password: password)
} catch {
observer.onError(error)
return Disposables.create()
}
}
if let userKey = currentUserKey {
do {
let identityInfo = virgil.identities
.createUserIdentity(withValue: identity,
type: VirgilConstants.ChatUser)
// create a Virgil Card
let card = try virgil.cards
.createCard(with: identityInfo, ownerKey:userKey)
observer.onNext(card)
} catch {
observer.onError(error)
}
} else {
observer.onCompleted()
}
return Disposables.create()
})
}
func searchForExistingCardCurrentUser(identity: String,
password: String? = nil) -> Observable<VSSVirgilCard?> {
return Observable.create({ (observer) -> Disposable in
if let card = self.virgilCards[identity] {
observer.onNext(card)
return Disposables.create()
}
guard let virgil = self.virgil else {
// virgil not initialized
let errorReason = NSLocalizedString("virgil not initialized",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
observer.onError(err)
return Disposables.create()
}
virgil.cards.searchCards(withIdentities: [identity]) { cards, error in
if let candidates = cards, candidates.count > 0 {
self.virgilCards[identity] = candidates[0]
observer.onNext(candidates[0])
} else if let err = error {
observer.onError(err)
} else {
observer.onNext(nil)
}
}
return Disposables.create()
})
}
func searchForCards(identities: [String],
password: String? = nil) -> Observable<[VSSVirgilCard]> {
return Observable.create({ (observer) -> Disposable in
var cards = [VSSVirgilCard]()
for identity in identities {
if let card = self.virgilCards[identity] {
cards.append(card)
}
}
if cards.count == identities.count {
// all the cards were found locally, no need to search
observer.onNext(cards)
return Disposables.create()
}
guard let virgil = self.virgil else {
// virgil not initialized
let errorReason = NSLocalizedString("virgil not initialized",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
observer.onError(err)
return Disposables.create()
}
virgil.cards.searchCards(withIdentities: identities) { cards, error in
if let candidates = cards, candidates.count > 0 {
for card in candidates {
self.virgilCards[card.identity] = card
}
observer.onNext(candidates)
} else if let err = error {
observer.onError(err)
} else {
let errorReason = NSLocalizedString("virgil cards not found",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
observer.onError(err)
}
}
return Disposables.create()
})
}
func publishAndVerifyCard(userID id: String, card: VSSVirgilCard) -> Observable<Void> {
return Observable.create({ (observer) -> Disposable in
let path = "/users/\(id)/virgil_card"
HCSDKCore.sharedInstance.startRequest(httpMethod: "POST",
path: path,
parameters: ["content": card.exportData() as NSString],
completion: { (_, error) in
if let err = error {
observer.onError(err)
} else {
observer.onNext()
}
})
return Disposables.create()
})
}
// MARK: - Encrypt and Decrypt
func encrypt(texts: [String],
forUsers userIDs: [String],
completion: ((_ encryptedTexts: [String]?, _ error: Error?) -> Void)?) {
guard let virgil = self.virgil else {
// virgil not initialized
let errorReason = NSLocalizedString("virgil not initialized",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
completion?(nil, err)
return
}
guard let currentUserID = AFSession.currentUserID() else {
let errorReason = NSLocalizedString("user not logged in",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .notLoggedIn)
completion?(nil, err)
return
}
do {
// try find the key first from keychain
let currentUserKey = try virgil.keys.loadKey(withName: currentUserID,
password: nil)
// always encrypt with the current user's card
var usersToSearch = [String]()
usersToSearch.append(contentsOf: userIDs)
if !usersToSearch.contains(currentUserID) {
usersToSearch.append(currentUserID)
}
searchForCards(identities: usersToSearch)
.subscribe(onNext: { (cards) in
// encrypt the buffer using found Virgil Cards
do {
var encryptedTexts = [String]()
for text in texts {
let ciphertext = try currentUserKey.signThenEncrypt(text, for: cards)
.base64EncodedString()
encryptedTexts.append(ciphertext)
}
completion?(encryptedTexts, nil)
} catch {
let errorReason = NSLocalizedString("virgil encryption failed",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .encryptionError)
completion?(nil, err)
}
}, onError: { (error) in
log.error(error.localizedDescription)
completion?(nil, error)
}).addDisposableTo(disposeBag)
} catch {
let errorReason = NSLocalizedString("missing private key",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
completion?(nil, err)
return
}
}
func decrypt(encryptedTexts: [String],
forUsers userIDs: [String],
completion: ((_ encryptedTexts: [String]?, _ error: Error?) -> Void)?) {
guard let virgil = self.virgil else {
// virgil not initialized
let errorReason = NSLocalizedString("virgil not initialized",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
completion?(nil, err)
return
}
guard let currentUserID = AFSession.currentUserID() else {
let errorReason = NSLocalizedString("user not logged in",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .notLoggedIn)
completion?(nil, err)
return
}
do {
// try find the key first from keychain
let currentUserKey = try virgil.keys.loadKey(withName: currentUserID,
password: nil)
searchForCards(identities: userIDs)
.subscribe(onNext: { (cards) in
// encrypt the buffer using found Virgil Cards
do {
var decryptedTexts = [String]()
for encrypted in encryptedTexts {
let decryptedData = try currentUserKey.decryptThenVerify(base64: encrypted,
fromOneOf: cards)
if let decryptedText = String(data: decryptedData, encoding: .utf8) {
decryptedTexts.append(decryptedText)
}
}
completion?(decryptedTexts, nil)
} catch {
completion?(nil, error)
}
}, onError: { (error) in
completion?(nil, error)
}).addDisposableTo(disposeBag)
} catch {
let errorReason = NSLocalizedString("missing private key",
comment: "")
let err = HCError.createError(withDescription: errorReason,
domain: .appfriends,
code: .badParameters)
completion?(nil, err)
return
}
}
}
// MARK: - AFEncryptionDelegate
extension EncryptionManager: AFEncryptionDelegate {
func encrypt(texts: [String], forUser userID: String, completion: (([String]?, Error?) -> Void)?) {
encrypt(texts: texts, forUsers: [userID], completion: completion)
}
func decrypt(encryptedTexts: [String], forUser userID: String, completion: (([String]?, Error?) -> Void)?) {
decrypt(encryptedTexts: encryptedTexts, forUsers: [userID], completion: completion)
}
func encrypt(texts: [String], forDialog dialogID: String, completion: (([String]?, Error?) -> Void)?) {
AFDialog.getDialog(dialogID: dialogID) { (dialog, error) in
if let err = error {
completion?(nil, err)
} else if let chatDialog = dialog {
self.encrypt(texts: texts,
forUsers: chatDialog.memberIDs(),
completion: completion)
} else {
completion?(nil, nil)
}
}
}
func decrypt(encryptedTexts: [String], forDialog dialogID: String, completion: (([String]?, Error?) -> Void)?) {
AFDialog.getDialog(dialogID: dialogID) { (dialog, error) in
if let err = error {
completion?(nil, err)
} else if let chatDialog = dialog {
self.decrypt(encryptedTexts: encryptedTexts,
forUsers: chatDialog.memberIDs(),
completion: completion)
} else {
completion?(nil, nil)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment