Scan QR Code to import SSH private key

This commit is contained in:
Yishi Lin 2017-06-07 02:12:54 +08:00
parent a31f5b797d
commit 3cde0d954c
6 changed files with 175 additions and 25 deletions

View file

@ -648,7 +648,7 @@
</objects>
<point key="canvasLocation" x="4954" y="-1027"/>
</scene>
<!--Scan QR Codes-->
<!--Scan OTP QR Codes-->
<scene sceneID="AuR-rQ-G3V">
<objects>
<viewController id="A9p-Qb-WmU" customClass="OTPScannerController" customModule="pass" customModuleProvider="target" sceneMemberID="viewController">
@ -678,7 +678,7 @@
<constraint firstAttribute="trailingMargin" secondItem="lOI-p4-BGb" secondAttribute="trailing" constant="30" id="qNb-4K-GGl"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Scan QR Codes" id="Hlb-5I-bfE">
<navigationItem key="navigationItem" title="Scan OTP QR Codes" id="Hlb-5I-bfE">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="KBZ-N2-OGE">
<connections>
<segue destination="0gD-ix-2NF" kind="unwind" unwindAction="cancelOTPScannerWithSegue:" id="nZe-B6-MNt"/>
@ -1028,7 +1028,7 @@ Phone Support PIN #: 84719</string>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Encrypt in ASCII-Armored" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Jwg-mt-woS">
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@ -1045,18 +1045,18 @@ Phone Support PIN #: 84719</string>
<rect key="frame" x="0.0" y="155" width="414" height="44"/>
<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="376" height="43.666666666666664"/>
<rect key="frame" x="0.0" y="0.0" width="381" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Git Signature" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="87a-xY-AbR">
<rect key="frame" x="20.000000000000007" y="11.999999999999998" width="99.666666666666671" height="20.333333333333332"/>
<rect key="frame" x="15.000000000000007" y="11.999999999999998" width="99.666666666666671" height="20.333333333333332"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Not Set" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2qr-d7-0SK">
<rect key="frame" x="318.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
<rect key="frame" x="321.33333333333331" y="11.999999999999998" width="57.666666666666664" height="20.333333333333332"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
@ -1080,7 +1080,7 @@ Phone Support PIN #: 84719</string>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Discard All Local Changes" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zrl-v3-fxg">
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.50196081400000003" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -1097,7 +1097,7 @@ Phone Support PIN #: 84719</string>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Erase All Password Store Data" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="K2K-Bx-g7Z">
<rect key="frame" x="20" y="0.0" width="379" height="43.666666666666664"/>
<rect key="frame" x="15" y="0.0" width="384" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.50196081400000003" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -1447,7 +1447,7 @@ Cgo
<scene sceneID="zWR-BT-dXv">
<objects>
<tableViewController id="WgM-cY-mig" customClass="GitSSHKeyArmorSettingTableViewController" customModule="pass" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" allowsSelection="NO" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="1" id="e2Q-qC-LUk">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="1" id="e2Q-qC-LUk">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
@ -1522,6 +1522,14 @@ Cgo
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="jXO-n0-Mvx">
<rect key="frame" x="0.0" y="404" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="jXO-n0-Mvx" id="AIL-mq-u7n">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
@ -1539,6 +1547,8 @@ Cgo
</navigationItem>
<connections>
<outlet property="armorPrivateKeyTextView" destination="23o-MP-wQY" id="ybK-Ba-vJt"/>
<outlet property="scanPrivateKeyCell" destination="jXO-n0-Mvx" id="Zdb-wm-7Ls"/>
<segue destination="lLV-kB-JkG" kind="show" identifier="showSSHScannerSegue" id="orz-gu-cTp"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="qJO-AN-K9p" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -1670,7 +1680,7 @@ Cgo
</objects>
<point key="canvasLocation" x="4967" y="5333"/>
</scene>
<!--Scanner-->
<!--Scan PGP Keys-->
<scene sceneID="7j1-Qg-pUZ">
<objects>
<viewController id="LZE-gF-IcM" customClass="QRScannerController" customModule="pass" customModuleProvider="target" sceneMemberID="viewController">
@ -1700,7 +1710,7 @@ Cgo
<constraint firstItem="53A-gx-tky" firstAttribute="top" secondItem="U8O-Md-w8e" secondAttribute="bottom" constant="80" id="tzL-K0-lE7"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Scanner" id="JIs-3z-Tmr">
<navigationItem key="navigationItem" title="Scan PGP Keys" id="JIs-3z-Tmr">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="yma-o8-xRu">
<connections>
<segue destination="0F8-Zv-c2C" kind="unwind" unwindAction="cancelPGPScannerWithSegue:" id="q14-fu-3N4"/>
@ -1716,6 +1726,52 @@ Cgo
</objects>
<point key="canvasLocation" x="6122" y="4302"/>
</scene>
<!--Scan SSH Keys-->
<scene sceneID="kmR-CX-Yi7">
<objects>
<viewController id="lLV-kB-JkG" customClass="QRScannerController" customModule="pass" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="NSI-Fp-tX4"/>
<viewControllerLayoutGuide type="bottom" id="QZv-Im-xZk"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="GYE-sh-yvu">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="scanner output" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fsL-pq-A5q">
<rect key="frame" x="50" y="567" width="314" height="45"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.5" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="IPT-kM-arz"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="QZv-Im-xZk" firstAttribute="top" secondItem="fsL-pq-A5q" secondAttribute="bottom" constant="80" id="76J-7g-eVO"/>
<constraint firstItem="fsL-pq-A5q" firstAttribute="leading" secondItem="GYE-sh-yvu" secondAttribute="leadingMargin" constant="30" id="IOo-JD-FMG"/>
<constraint firstAttribute="trailingMargin" secondItem="fsL-pq-A5q" secondAttribute="trailing" constant="30" id="ymC-G4-bdA"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Scan SSH Keys" id="bov-FI-Hkg">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="JhH-TO-YkO">
<connections>
<segue destination="J2S-Mr-s10" kind="unwind" unwindAction="cancelSSHScannerWithSegue:" id="Thj-zj-TEX"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="scannerOutput" destination="fsL-pq-A5q" id="lSv-2S-qVd"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="s1p-jn-4PV" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="J2S-Mr-s10" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="7133" y="2895"/>
</scene>
</scenes>
<resources>
<image name="Lock" width="25" height="25"/>

View file

@ -57,6 +57,7 @@ struct GitCredential {
attempts += 1
lastPassword = newPassword
credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: newPassword!)
print(privateKeyFile)
}
return credential
}

View file

@ -9,17 +9,77 @@
import UIKit
import SwiftyUserDefaults
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var gitSSHPrivateKeyPassphrase: String?
let passwordStore = PasswordStore.shared
private var recentPastedText = ""
class ScannedSSHKey {
static let maxNumberOfGif = 100
var numberOfSegments = 0
var previousSegment = ""
var key = ""
var message = ""
var hasStarted = false
var isDone = false
func reset() {
numberOfSegments = 0
previousSegment = ""
key = ""
message = "Looking for the starting frame."
hasStarted = false
isDone = false
}
func addSegment(segment: String) {
// skip duplicated segments
guard segment != previousSegment else {
return
}
previousSegment = segment
// check whether we have found the first block
if hasStarted == false {
hasStarted = segment.contains("-----BEGIN")
}
guard hasStarted == true else {
return
}
// check the number of segments
numberOfSegments = numberOfSegments + 1
guard numberOfSegments <= ScannedSSHKey.maxNumberOfGif else {
key = "Too many QR codes"
return
}
// update full text and check whether we are done
key.append(segment)
if let index1 = key.range(of: "-----END")?.lowerBound,
let _ = key.substring(from: index1).range(of: "KEY-----")?.lowerBound {
isDone = true
}
// update message
message = "\(numberOfSegments) scanned QR codes."
}
}
var scanned = ScannedSSHKey()
override func viewDidLoad() {
super.viewDidLoad()
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
armorPrivateKeyTextView.delegate = self
scanPrivateKeyCell?.textLabel?.text = "Scan Private Key QR Codes"
scanPrivateKeyCell?.textLabel?.textColor = Globals.blue
scanPrivateKeyCell?.selectionStyle = .default
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
}
@IBAction func doneButtonTapped(_ sender: Any) {
@ -46,4 +106,46 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
}
return true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == scanPrivateKeyCell {
scanned.reset()
self.performSegue(withIdentifier: "showSSHScannerSegue", sender: self)
}
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
scanned.addSegment(segment: line)
if scanned.isDone {
return (accept: true, message: "Done")
} else {
return (accept: false, message: scanned.message)
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
armorPrivateKeyTextView.text = scanned.key
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSSHScannerSegue" {
if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? QRScannerController {
viewController.delegate = self
}
} else if let viewController = segue.destination as? QRScannerController {
viewController.delegate = self
}
}
}
@IBAction private func cancelSSHScanner(segue: UIStoryboardSegue) {
}
}

View file

@ -120,8 +120,7 @@ class GitServerSettingTableViewController: UITableViewController {
}
private func gitSSHKeyExists() -> Bool {
return FileManager.default.fileExists(atPath: Globals.gitSSHPublicKeyPath) &&
FileManager.default.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
return FileManager.default.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
}
func showSSHKeyActionSheet() {

View file

@ -15,9 +15,7 @@ class Globals {
static let pgpPublicKeyPath = "\(documentPath)/gpg_key.pub"
static let pgpPrivateKeyPath = "\(documentPath)/gpg_key"
static let gitSSHPublicKeyPath = "\(documentPath)/ssh_key.pub"
static let gitSSHPrivateKeyPath = "\(documentPath)/ssh_key"
static let gitSSHPublicKeyURL = URL(fileURLWithPath: gitSSHPublicKeyPath)
static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath)
static let repositoryPath = "\(libraryPath)/password-store"

View file

@ -104,14 +104,10 @@ class PasswordStore {
}
public func initGitSSHKey(with armorKey: String, _ keyType: SSHKeyType) throws {
var keyPath = ""
switch keyType {
case .public:
keyPath = Globals.gitSSHPublicKeyPath
case .secret:
keyPath = Globals.gitSSHPrivateKeyPath
guard keyType == .secret else {
return
}
let keyPath = Globals.gitSSHPrivateKeyPath
try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii)
}
@ -692,7 +688,6 @@ class PasswordStore {
Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath)
Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath)
Utils.removeFileIfExists(atPath: Globals.gitSSHPublicKeyPath)
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
Utils.removeAllKeychain()
@ -805,7 +800,6 @@ class PasswordStore {
}
func removeGitSSHKeys() {
Utils.removeFileIfExists(atPath: Globals.gitSSHPublicKeyPath)
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
Defaults.remove(.gitSSHPrivateKeyArmor)
Defaults.remove(.gitSSHPrivateKeyURL)