Refactor YubiKey decryptor (#663)
- Add YKFSmartCardInterface extension to simplify smart card related calls - Use async/await to rewrite callback closures - Update YubiKeyConnection - Better error handling
This commit is contained in:
parent
fc35805565
commit
a410c9480a
9 changed files with 344 additions and 320 deletions
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
||||
<device id="retina6_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
|
@ -81,11 +81,11 @@
|
|||
<rect key="frame" x="0.0" y="18" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="55g-T3-9ak" id="dKn-cO-EJa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="331.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="General" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="dOt-Rj-vWD">
|
||||
<rect key="frame" x="8" y="0.0" width="355.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="8" y="0.0" width="315.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
<rect key="frame" x="0.0" y="61.666667938232422" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2rc-ZW-XKd" id="CpT-zb-QEP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="331.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Password Repository" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="gWn-ib-STb">
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="Myq-fV-riz">
|
||||
<rect key="frame" x="306.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<rect key="frame" x="266.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
|
|
@ -128,18 +128,18 @@
|
|||
<rect key="frame" x="0.0" y="105.33333587646484" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1ze-MS-Xbj" id="W7U-oL-hOh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="359.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="PGP Key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="RR9-xr-9ko">
|
||||
<rect key="frame" x="8" y="11.999999999999998" width="65.666666666666671" height="20.333333333333332"/>
|
||||
<rect key="frame" x="20" y="11.999999999999998" width="65.666666666666671" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="7lc-Vh-G9W">
|
||||
<rect key="frame" x="306.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<rect key="frame" x="294.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
|
|
@ -156,7 +156,7 @@
|
|||
<rect key="frame" x="0.0" y="205.00000381469729" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="6Y0-mj-qhA" id="qlv-tQ-Xmc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="331.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Passcode Lock" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="RaZ-6t-0CU">
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Off" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="HXb-ZX-HUv">
|
||||
<rect key="frame" x="339.33333333333337" y="11.999999999999998" width="24.333333333333332" height="20.333333333333332"/>
|
||||
<rect key="frame" x="299.33333333333337" y="11.999999999999998" width="24.333333333333332" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
|
|
@ -184,11 +184,11 @@
|
|||
<rect key="frame" x="0.0" y="284.66667175292969" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="tQN-gu-iRe" id="Xs0-LN-r43">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="331.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Advanced" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="MKj-d0-8q3">
|
||||
<rect key="frame" x="8" y="0.0" width="355.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="8" y="0.0" width="315.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -208,11 +208,11 @@
|
|||
<rect key="frame" x="0.0" y="364.33333969116211" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="osS-xk-WRP" id="G6j-ij-rNr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="359.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="About" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="oqz-Hr-RAl">
|
||||
<rect key="frame" x="8" y="0.0" width="355.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="20" y="0.0" width="331.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
|
|
@ -402,14 +402,14 @@
|
|||
<tableViewSection headerTitle="Authentication Method" id="h0N-tI-shZ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="KrP-nb-haa">
|
||||
<rect key="frame" x="0.0" y="356.33332633972168" width="390" height="43"/>
|
||||
<rect key="frame" x="0.0" y="356.33332633972168" width="390" height="43.333332061767578"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KrP-nb-haa" id="1uB-oE-kfI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="43.333332061767578"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LfQ-Af-j2O">
|
||||
<rect key="frame" x="74" y="11" width="256" height="21"/>
|
||||
<rect key="frame" x="74" y="10.999999999999998" width="256" height="21.333333333333329"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
|
@ -434,14 +434,14 @@
|
|||
<inset key="separatorInset" minX="62" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" accessoryType="detailButton" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="Qmt-bo-CuJ">
|
||||
<rect key="frame" x="0.0" y="399.33332633972168" width="390" height="43"/>
|
||||
<rect key="frame" x="0.0" y="399.66665840148926" width="390" height="43.333332061767578"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Qmt-bo-CuJ" id="p3u-8b-h3U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="43.333332061767578"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SSH Key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ezz-76-a53">
|
||||
<rect key="frame" x="74" y="11" width="223" height="21"/>
|
||||
<rect key="frame" x="74" y="10.999999999999998" width="223" height="21.333333333333329"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
|
@ -840,11 +840,11 @@
|
|||
<viewControllerLayoutGuide type="bottom" id="7JD-uM-IyS"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="5YZ-6i-LST">
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="787"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="834"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3dt-Ph-4As">
|
||||
<rect key="frame" x="16" y="76" width="358" height="669"/>
|
||||
<rect key="frame" x="16" y="76" width="358" height="750"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment>
|
||||
|
|
@ -1106,18 +1106,18 @@ Secret Question 1: What is your childhood best friend's most bizarre superhero f
|
|||
<rect key="frame" x="0.0" y="154.99999809265137" width="390" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SVj-jD-qPT" id="HaO-5w-qZt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="359.66666666666669" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="371.66666666666669" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Git Signature" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="87a-xY-AbR">
|
||||
<rect key="frame" x="20" y="11.999999999999998" width="99" height="20.333333333333332"/>
|
||||
<rect key="frame" x="8" y="11.999999999999998" width="99" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="2qr-d7-0SK">
|
||||
<rect key="frame" x="294.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<rect key="frame" x="306.33333333333337" y="11.999999999999998" width="57.333333333333336" height="20.333333333333332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
case name, main, addition, misc
|
||||
}
|
||||
|
||||
private var passwordYubiKeyDecryptor = PasswordYubiKeyDecryptor()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||
|
|
@ -559,12 +561,11 @@ extension PasswordDetailTableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func requestYubiKeyPIN(completion: @escaping (String) -> Void, cancellation: @escaping () -> Void) {
|
||||
private func requestYubiKeyPIN(completion: @escaping (String) -> Void) {
|
||||
let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert)
|
||||
alert.addAction(
|
||||
UIAlertAction.cancel { _ in
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
cancellation()
|
||||
}
|
||||
)
|
||||
alert.addAction(
|
||||
|
|
@ -580,25 +581,10 @@ extension PasswordDetailTableViewController {
|
|||
}
|
||||
|
||||
private func handleError(error: AppError) {
|
||||
switch error {
|
||||
case let .yubiKey(yubiKeyError):
|
||||
let errorMessage = yubiKeyError.localizedDescription
|
||||
YubiKitManager.shared.stopNFCConnection(withErrorMessage: errorMessage)
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async {
|
||||
self.presentFailureAlert(message: error.localizedDescription) { _ in
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
default:
|
||||
DispatchQueue.main.async {
|
||||
self.presentFailureAlert(message: error.localizedDescription) { _ in
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCancellation() {
|
||||
DispatchQueue.main.async {
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -607,9 +593,30 @@ extension PasswordDetailTableViewController {
|
|||
handleError(error: AppError.other(message: "PasswordDoesNotExist"))
|
||||
return
|
||||
}
|
||||
yubiKeyDecrypt(passwordEntity: passwordEntity, requestPIN: requestYubiKeyPIN, errorHandler: handleError, cancellation: handleCancellation) { password in
|
||||
self.password = password
|
||||
self.showPassword()
|
||||
let encryptedDataPath = PasswordStore.shared.storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||
|
||||
guard let encryptedData = try? Data(contentsOf: encryptedDataPath) else {
|
||||
handleError(error: AppError.other(message: "PasswordDoesNotExist"))
|
||||
return
|
||||
}
|
||||
requestYubiKeyPIN { [self] pin in
|
||||
Task {
|
||||
do {
|
||||
let decryptedData = try await passwordYubiKeyDecryptor.yubiKeyDecrypt(encryptedData: encryptedData, pin: pin)
|
||||
guard let decryptedDataString = String(data: decryptedData, encoding: .utf8) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to convert plaintext to string."))
|
||||
}
|
||||
guard let password = try? Password(name: passwordEntity.getName(), url: passwordEntity.getURL(), plainText: decryptedDataString) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to construct password."))
|
||||
}
|
||||
self.password = password
|
||||
self.showPassword()
|
||||
} catch let error as AppError {
|
||||
handleError(error: error)
|
||||
} catch {
|
||||
handleError(error: AppError.yubiKey(.other(message: error.localizedDescription)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,176 +55,61 @@ func decryptPassword(
|
|||
}
|
||||
}
|
||||
|
||||
public typealias RequestPINAction = (@escaping (String) -> Void, @escaping () -> Void) -> Void
|
||||
class PasswordYubiKeyDecryptor {
|
||||
let yubiKeyConnection = YubiKeyConnection()
|
||||
|
||||
let symmetricKeyIDNameDict: [UInt8: String] = [
|
||||
2: "3des",
|
||||
3: "cast5",
|
||||
7: "aes128",
|
||||
8: "aes192",
|
||||
9: "aes256",
|
||||
]
|
||||
|
||||
private func isEncryptKeyAlgoRSA(_ applicationRelatedData: Data) -> Bool {
|
||||
let tlv = TKBERTLVRecord.sequenceOfRecords(from: applicationRelatedData)!
|
||||
// 0x73: Discretionary data objects
|
||||
for record in TKBERTLVRecord.sequenceOfRecords(from: tlv.first!.value)! where record.tag == 0x73 {
|
||||
// 0xC2: Algorithm attributes decryption, 0x01: RSA
|
||||
for record2 in TKBERTLVRecord.sequenceOfRecords(from: record.value)! where record2.tag == 0xC2 && record2.value.first! == 0x01 {
|
||||
return true
|
||||
}
|
||||
func didDisconnect(completion: @escaping (_ connection: YKFConnectionProtocol?, _ error: Error?) -> Void) {
|
||||
yubiKeyConnection.didDisconnect(handler: completion)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func getCapabilities(_ applicationRelatedData: Data) -> (Bool, Bool) {
|
||||
let tlv = TKBERTLVRecord.sequenceOfRecords(from: applicationRelatedData)!
|
||||
// 0x5f52: Historical Bytes
|
||||
for record in TKBERTLVRecord.sequenceOfRecords(from: tlv.first!.value)! where record.tag == 0x5F52 {
|
||||
let historical = record.value
|
||||
if historical.count < 4 {
|
||||
// log_error ("warning: historical bytes are too short\n");
|
||||
return (false, false)
|
||||
}
|
||||
|
||||
if historical[0] != 0 {
|
||||
// log_error ("warning: bad category indicator in historical bytes\n");
|
||||
return (false, false)
|
||||
}
|
||||
|
||||
let dos = historical[1 ..< historical.endIndex - 3]
|
||||
for record2 in TKCompactTLVRecord.sequenceOfRecords(from: dos)! where record2.tag == 7 && record2.value.count == 3 {
|
||||
let cmd_chaining = (record2.value[2] & 0x80) != 0
|
||||
let ext_lc_le = (record2.value[2] & 0x40) != 0
|
||||
return (cmd_chaining, ext_lc_le)
|
||||
private func handleError(error: Error, forConnection connection: YKFConnectionProtocol) -> Error {
|
||||
if (connection as? YKFNFCConnection) != nil {
|
||||
YubiKitManager.shared.stopNFCConnection(withErrorMessage: error.localizedDescription)
|
||||
return error // Dont pass on the error since we display it in the NFC modal
|
||||
}
|
||||
return error
|
||||
}
|
||||
return (false, false)
|
||||
}
|
||||
|
||||
public func yubiKeyDecrypt(
|
||||
passwordEntity: PasswordEntity,
|
||||
requestPIN: @escaping RequestPINAction,
|
||||
errorHandler: @escaping ((AppError) -> Void),
|
||||
cancellation: @escaping (() -> Void),
|
||||
completion: @escaping ((Password) -> Void)
|
||||
) {
|
||||
Task {
|
||||
func yubiKeyDecrypt(encryptedData: Data, pin: String) async throws -> Data {
|
||||
let connection = await yubiKeyConnection.startConnection()
|
||||
|
||||
do {
|
||||
let encryptedDataPath = PasswordStore.shared.storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||
|
||||
guard let encryptedData = try? Data(contentsOf: encryptedDataPath) else {
|
||||
errorHandler(AppError.other(message: "PasswordDoesNotExist".localize()))
|
||||
return
|
||||
}
|
||||
|
||||
guard let pin = await readPin(requestPIN: requestPIN) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let connection = try? await getConnection() else {
|
||||
cancellation()
|
||||
return
|
||||
}
|
||||
|
||||
guard let smartCard = connection.smartCardInterface else {
|
||||
throw AppError.yubiKey(.connection(message: "Failed to get smart card interface."))
|
||||
}
|
||||
|
||||
try await selectOpenPGPApplication(smartCard: smartCard)
|
||||
|
||||
try await verifyPin(smartCard: smartCard, pin: pin)
|
||||
|
||||
guard let applicationRelatedData = try await getApplicationRelatedData(smartCard: smartCard) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to get application related data."))
|
||||
do {
|
||||
try await smartCard.selectOpenPGPApplication()
|
||||
} catch {
|
||||
throw AppError.yubiKey(.connection(message: "Failed to select OpenPGP application"))
|
||||
}
|
||||
|
||||
if !isEncryptKeyAlgoRSA(applicationRelatedData) {
|
||||
throw AppError.yubiKey(.decipher(message: "Encryption key algorithm is not supported. Supported algorithm: RSA."))
|
||||
do {
|
||||
try await smartCard.verify(password: pin)
|
||||
} catch {
|
||||
throw AppError.yubiKey(.connection(message: "Failed to verify PIN"))
|
||||
}
|
||||
|
||||
let (cmd_chaining, _) = getCapabilities(applicationRelatedData)
|
||||
|
||||
let deciphered = try await decipher(smartCard: smartCard, ciphertext: encryptedData, chained: cmd_chaining)
|
||||
|
||||
YubiKitManager.shared.stopNFCConnection()
|
||||
|
||||
let plaintext = try decryptPassword(deciphered: deciphered, ciphertext: encryptedData)
|
||||
guard let password = try? Password(name: passwordEntity.getName(), url: passwordEntity.getURL(), plainText: plaintext) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to construct password."))
|
||||
guard let deciphered = try? await smartCard.decipher(ciphertext: encryptedData) else {
|
||||
throw AppError.yubiKey(.connection(message: "Failed to dicipher data"))
|
||||
}
|
||||
|
||||
completion(password)
|
||||
} catch let error as AppError {
|
||||
errorHandler(error)
|
||||
let decryptedData = try decryptData(deciphered: deciphered, ciphertext: encryptedData)
|
||||
if (connection as? YKFNFCConnection) != nil {
|
||||
YubiKitManager.shared.stopNFCConnection()
|
||||
}
|
||||
return decryptedData
|
||||
} catch {
|
||||
errorHandler(AppError.other(message: String(describing: error)))
|
||||
throw handleError(error: error, forConnection: connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readPin(requestPIN: @escaping RequestPINAction) async -> String? {
|
||||
await withCheckedContinuation { (continuation: CheckedContinuation<String?, Never>) in
|
||||
DispatchQueue.main.async {
|
||||
requestPIN({ pin in continuation.resume(returning: pin) }, { continuation.resume(returning: nil) })
|
||||
}
|
||||
}
|
||||
}
|
||||
private func decryptData(deciphered: Data, ciphertext: Data) throws -> Data {
|
||||
let symmetricKeyIDNameDict: [UInt8: String] = [
|
||||
2: "3des",
|
||||
3: "cast5",
|
||||
7: "aes128",
|
||||
8: "aes192",
|
||||
9: "aes256",
|
||||
]
|
||||
|
||||
func getConnection() async throws -> YKFConnectionProtocol? {
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<YKFConnectionProtocol?, Error>) in
|
||||
passKit.YubiKeyConnection.shared.connection(cancellation: { error in
|
||||
continuation.resume(throwing: error)
|
||||
}, completion: { connection in
|
||||
continuation.resume(returning: connection)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selectOpenPGPApplication(smartCard: YKFSmartCardInterface) async throws {
|
||||
if await withCheckedContinuation({ (continuation: CheckedContinuation<Error?, Never>) in
|
||||
smartCard.selectApplication(YubiKeyAPDU.selectOpenPGPApplication()) { _, error in
|
||||
continuation.resume(returning: error)
|
||||
}
|
||||
}) != nil {
|
||||
throw AppError.yubiKey(.selectApplication(message: "Failed to select application."))
|
||||
}
|
||||
}
|
||||
|
||||
func getApplicationRelatedData(smartCard: YKFSmartCardInterface) async throws -> Data? {
|
||||
try await executeCommandAsync(smartCard: smartCard, apdu: YubiKeyAPDU.get_application_related_data())
|
||||
}
|
||||
|
||||
func verifyPin(smartCard: YKFSmartCardInterface, pin: String) async throws {
|
||||
if await withCheckedContinuation({ (continuation: CheckedContinuation<Error?, Never>) in
|
||||
smartCard.executeCommand(YubiKeyAPDU.verify(password: pin)) { _, error in
|
||||
continuation.resume(returning: error)
|
||||
}}) != nil {
|
||||
throw AppError.yubiKey(.selectApplication(message: "Failed to verify PIN."))
|
||||
}
|
||||
}
|
||||
|
||||
func decipher(smartCard: YKFSmartCardInterface, ciphertext: Data, chained: Bool) async throws -> Data {
|
||||
var error: NSError?
|
||||
let message = createPGPMessage(from: ciphertext)
|
||||
guard let mpi1 = Gopenpgp.HelperPassGetEncryptedMPI1(message, &error) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to get encrypted MPI."))
|
||||
}
|
||||
|
||||
let apdus = chained ? YubiKeyAPDU.decipherChained(data: mpi1) : YubiKeyAPDU.decipherExtended(data: mpi1)
|
||||
|
||||
for (idx, apdu) in apdus.enumerated() {
|
||||
let data = try await executeCommandAsync(smartCard: smartCard, apdu: apdu)
|
||||
// the last response must have the data
|
||||
if idx == apdus.endIndex - 1, let data {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
throw AppError.yubiKey(.verify(message: "Failed to execute decipher."))
|
||||
}
|
||||
|
||||
func decryptPassword(deciphered: Data, ciphertext: Data) throws -> String {
|
||||
let message = createPGPMessage(from: ciphertext)
|
||||
|
||||
guard let algoByte = deciphered.first, let algo = symmetricKeyIDNameDict[algoByte] else {
|
||||
|
|
@ -240,33 +125,5 @@ func decryptPassword(deciphered: Data, ciphertext: Data) throws -> String {
|
|||
throw AppError.yubiKey(.decipher(message: "Failed to decrypt with session key: \(String(describing: error))"))
|
||||
}
|
||||
|
||||
guard let plaintext_str = String(data: plaintext, encoding: .utf8) else {
|
||||
throw AppError.yubiKey(.decipher(message: "Failed to convert plaintext to string."))
|
||||
}
|
||||
|
||||
return plaintext_str
|
||||
}
|
||||
|
||||
func executeCommandAsync(smartCard: YKFSmartCardInterface, apdu: YKFAPDU) async throws -> Data? {
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data?, Error>) in
|
||||
smartCard.executeCommand(apdu) { data, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
continuation.resume(returning: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
struct HexEncodingOptions: OptionSet {
|
||||
let rawValue: Int
|
||||
static let upperCase = Self(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
func hexEncodedString(options: HexEncodingOptions = []) -> String {
|
||||
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
|
||||
return map { String(format: format, $0) }.joined()
|
||||
}
|
||||
return plaintext
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue