FossilOrigin-Name: 32913793c8ee46edae2797f5e1063ed0a034aa77da6bcad037899286a53dc5c9
198 lines
7.5 KiB
Swift
198 lines
7.5 KiB
Swift
/*
|
|
* Copyright (c) 2016 - 2024 Jonathan Schleifer <js@nil.im>
|
|
*
|
|
* https://fl.nil.im/cryptopassphrase
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
import UIKit
|
|
import ObjFW
|
|
import ObjFWBridge
|
|
|
|
class ShowDetailsController: UITableViewController, UITextFieldDelegate {
|
|
@IBOutlet var nameField: UITextField?
|
|
@IBOutlet var lengthField: UITextField?
|
|
@IBOutlet var legacySwitch: UISwitch?
|
|
@IBOutlet var keyFileField: UITextField?
|
|
@IBOutlet var passphraseField: UITextField?
|
|
public var mainViewController: MainViewController?
|
|
|
|
private var name: String = ""
|
|
private var length: UInt = 0
|
|
private var isLegacy: Bool = false
|
|
private var keyFile: String? = nil
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
guard let mainViewController = self.mainViewController else { return }
|
|
guard let tableView = mainViewController.tableView else { return }
|
|
let siteStorage = mainViewController.siteStorage
|
|
guard let indexPath = tableView.indexPathForSelectedRow else { return }
|
|
|
|
name = mainViewController.sites[indexPath.row]
|
|
length = siteStorage.length(forSite: name)
|
|
isLegacy = siteStorage.isLegacy(site: name)
|
|
keyFile = siteStorage.keyFile(forSite: name)
|
|
|
|
nameField?.text = name
|
|
lengthField?.text = "\(length)"
|
|
legacySwitch?.isOn = isLegacy
|
|
keyFileField?.text = keyFile
|
|
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
}
|
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
textField.resignFirstResponder()
|
|
return false
|
|
}
|
|
|
|
static private func clearNSMutableString(_ string: NSMutableString) {
|
|
/*
|
|
* NSMutableString does not offer a way to zero the string.
|
|
* This is in the hope that setting a single character at an index just
|
|
* replaces that character in memory, and thus allows us to zero the
|
|
* password.
|
|
*/
|
|
for i in 0..<string.length {
|
|
string.replaceCharacters(in: NSRange(location: i, length: 1),
|
|
with: " ")
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView,
|
|
didSelectRowAt indexPath: IndexPath) {
|
|
passphraseField?.resignFirstResponder()
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
if indexPath.section == 3 {
|
|
switch indexPath.row {
|
|
case 0:
|
|
self.generateAndCopy()
|
|
case 1:
|
|
self.generateAndShow()
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
private func generateAndCopy() {
|
|
self.generateWithCallback { (password: NSMutableString) in
|
|
let pasteboard = UIPasteboard.general
|
|
pasteboard.string = password as String
|
|
|
|
ShowDetailsController.clearNSMutableString(password)
|
|
|
|
let message = "The password has been copied into the clipboard."
|
|
let alert = UIAlertController(title: "Password Generated",
|
|
message: message,
|
|
preferredStyle: .alert)
|
|
let action = UIAlertAction(title: "OK", style: .default) {
|
|
(UIAlertAction) in
|
|
self.navigationController?.popViewController(animated: true)
|
|
}
|
|
alert.addAction(action)
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|
|
|
|
private func generateAndShow() {
|
|
self.generateWithCallback { (password: NSMutableString) in
|
|
let alert = UIAlertController(title: "Generated Passphrase",
|
|
message: password as String,
|
|
preferredStyle: .alert)
|
|
let action = UIAlertAction(title: "OK", style: .default) {
|
|
(UIAlertAction) in
|
|
self.navigationController?.popViewController(animated: true)
|
|
}
|
|
alert.addAction(action)
|
|
|
|
self.present(alert, animated: true) {
|
|
ShowDetailsController.clearNSMutableString(password)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func generateWithCallback(
|
|
_ block: @escaping (_: NSMutableString) -> ()
|
|
) {
|
|
let generator: PasswordGenerator = isLegacy ?
|
|
LegacyPasswordGenerator() : NewPasswordGenerator()
|
|
generator.site = name.ofObject
|
|
generator.length = size_t(length)
|
|
|
|
if let keyFile = keyFile {
|
|
guard let documentDirectory = NSSearchPathForDirectoriesInDomains(
|
|
.documentDirectory, .userDomainMask, true).first
|
|
else {
|
|
print("Could not get key files: No documents directory")
|
|
return
|
|
}
|
|
|
|
let keyFilePath = documentDirectory.ofObject.appendingPathComponent(
|
|
keyFile.ofObject)
|
|
generator.keyFile = OFMutableData(contentsOfFile: keyFilePath)
|
|
}
|
|
|
|
let passphraseText = (passphraseField?.text ?? "") as NSString
|
|
let passphraseLen = passphraseText.lengthOfBytes(
|
|
using: String.Encoding.utf8.rawValue) + 1
|
|
let passphrase = OFSecureData(count: passphraseLen,
|
|
allowsSwappableMemory: false)
|
|
memcpy(passphrase.mutableItems, passphraseText.utf8String!,
|
|
passphraseLen)
|
|
generator.passphrase = passphrase
|
|
|
|
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
|
let activityController = mainStoryboard.instantiateViewController(
|
|
withIdentifier: "activityIndicator")
|
|
navigationController?.view.addSubview(activityController.view)
|
|
|
|
DispatchQueue.global(qos: .default).async {
|
|
generator.derivePassword()
|
|
|
|
let password = NSMutableString(
|
|
bytes: generator.output.items!,
|
|
length: generator.length,
|
|
encoding: String.Encoding.utf8.rawValue)!
|
|
|
|
DispatchQueue.main.sync {
|
|
activityController.view.isHidden = true
|
|
block(password)
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func remove(_ sender: Any?) {
|
|
let message = "Do you want to remove this site?"
|
|
let alert = UIAlertController(title: "Remove Site?",
|
|
message: message,
|
|
preferredStyle: .alert)
|
|
alert.addAction(
|
|
UIAlertAction(title: "No", style: .cancel, handler: nil))
|
|
let yesAction = UIAlertAction(title: "Yes", style: .destructive) {
|
|
(UIAlertAction) in
|
|
self.mainViewController?.siteStorage.removeSite(self.name)
|
|
self.mainViewController?.reset()
|
|
|
|
self.navigationController?.popViewController(animated: true)
|
|
}
|
|
alert.addAction(yesAction)
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|