Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Commit

Permalink
Merge branch 'master' into exclusive-access
Browse files Browse the repository at this point in the history
  • Loading branch information
mastahyeti authored Aug 11, 2017
2 parents 4519b2b + 5cd865a commit 9a1bd5d
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 36 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
![](https://user-images.githubusercontent.com/1144197/28190263-470a80d2-67e7-11e7-81e6-17895d70bf75.png)

Soft U2F is a software U2F authenticator for OS X. It emulates a hardware U2F HID device and performs cryptographic operations using the OS X Keychain. This tool works with Google Chrome and Opera's built-in U2F implementations as well as with the U2F extensions for OS X Safari and Firefox.
Soft U2F is a software U2F authenticator for OS X. It emulates a hardware U2F HID device and performs cryptographic operations using the OS X Keychain. This tool works with Google Chrome and Opera's built-in U2F implementations as well as with the U2F extensions for [OS X Safari](https://github.com/Safari-FIDO-U2F/Safari-FIDO-U2F) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/).

We take the security of this project seriously. Report any security vulnerabilities to the [GitHub Bug Bounty Program](https://hackerone.com/github).

Expand Down Expand Up @@ -52,6 +52,12 @@ Delete the kernel extension
$ sudo rm -rf /Library/Extensions/softu2f.kext
```

Tell macOS to forget about the installation

```
$ sudo pkgutil --forget com.GitHub.SoftU2F
```

Done

## Security considerations
Expand Down
8 changes: 8 additions & 0 deletions SoftU2F.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
F738F5871E4A3C09005680A2 /* DataReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F738F5851E4A3C09005680A2 /* DataReaderTests.swift */; };
F738F5881E4A3C09005680A2 /* DataWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F738F5861E4A3C09005680A2 /* DataWriterTests.swift */; };
F738F58A1E4A3C21005680A2 /* TestUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F738F5891E4A3C21005680A2 /* TestUtil.swift */; };
F78BEF5F1F3E654A0005B3D5 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78BEF5E1F3E654A0005B3D5 /* Settings.swift */; };
F78BEF611F3E65510005B3D5 /* CLI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78BEF601F3E65510005B3D5 /* CLI.swift */; };
F7ABD9BE1E80603D00768FEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7ABD9BD1E80603D00768FEC /* Assets.xcassets */; };
F7B5DBAD1E4A5CED00E5ABD4 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B5DBAC1E4A5CED00E5ABD4 /* Command.swift */; };
F7B5DBAF1E4A815700E5ABD4 /* RawConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B5DBAE1E4A815700E5ABD4 /* RawConvertible.swift */; };
Expand Down Expand Up @@ -241,6 +243,8 @@
F738F5851E4A3C09005680A2 /* DataReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DataReaderTests.swift; path = DataTests/DataReaderTests.swift; sourceTree = "<group>"; };
F738F5861E4A3C09005680A2 /* DataWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DataWriterTests.swift; path = DataTests/DataWriterTests.swift; sourceTree = "<group>"; };
F738F5891E4A3C21005680A2 /* TestUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtil.swift; sourceTree = "<group>"; };
F78BEF5E1F3E654A0005B3D5 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
F78BEF601F3E65510005B3D5 /* CLI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLI.swift; sourceTree = "<group>"; };
F7ABD9BD1E80603D00768FEC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
F7B5DBAC1E4A5CED00E5ABD4 /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
F7B5DBAE1E4A815700E5ABD4 /* RawConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawConvertible.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -374,6 +378,8 @@
51213EC51E3916EB005454E0 /* U2FHID.swift */,
51B289E41E39903F00AD90CC /* U2FAuthenticator.swift */,
51FE30EC1E403DA000BAE824 /* U2FRegistration.swift */,
F78BEF601F3E65510005B3D5 /* CLI.swift */,
F78BEF5E1F3E654A0005B3D5 /* Settings.swift */,
518537BF1E380E4600600911 /* SoftU2F-Bridging-Header.h */,
51F090181E37E8C600F03AD3 /* Info.plist */,
);
Expand Down Expand Up @@ -841,12 +847,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F78BEF611F3E65510005B3D5 /* CLI.swift in Sources */,
5190B6121E3BFE3D00E6FE06 /* UserPresence.swift in Sources */,
51FE30F11E410B3D00BAE824 /* Utils.swift in Sources */,
5119862E1E3C1519006A3BBB /* KnownFacets.swift in Sources */,
51F090101E37E8C600F03AD3 /* AppDelegate.swift in Sources */,
51213EC61E3916EB005454E0 /* U2FHID.swift in Sources */,
51E214601E3823E7005B2864 /* SHA256.swift in Sources */,
F78BEF5F1F3E654A0005B3D5 /* Settings.swift in Sources */,
51B289E51E39903F00AD90CC /* U2FAuthenticator.swift in Sources */,
514F3D821E43C833008FA513 /* Keychain.swift in Sources */,
51FE30ED1E403DA000BAE824 /* U2FRegistration.swift in Sources */,
Expand Down
13 changes: 9 additions & 4 deletions SoftU2FTool/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
if !U2FAuthenticator.start() {
if CLI(CommandLine.arguments).run() {
quit()
} else if !U2FAuthenticator.start(){
print("Error starting authenticator")
NSApplication.shared().terminate(self)
quit()
}

}

func applicationWillTerminate(_ aNotification: Notification) {
if U2FAuthenticator.shared != nil && !U2FAuthenticator.stop() {
if U2FAuthenticator.running && !U2FAuthenticator.stop() {
print("Error stopping authenticator")
}
}
Expand All @@ -29,4 +30,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// with our notification.
NSApplication.shared().hide(nil)
}

private func quit() {
NSApplication.shared().terminate(self)
}
}
102 changes: 102 additions & 0 deletions SoftU2FTool/CLI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//
// CLI.swift
// SoftU2F
//
// Created by Ben Toews on 8/1/17.
//

import Foundation

// Command line flags
fileprivate let listFlag = "--list"
fileprivate let deleteAllFlag = "--delete-all"
fileprivate let showTouchidFlag = "--show-touchid"
fileprivate let enableTouchidFlag = "--enable-touchid"
fileprivate let disableTouchidFlag = "--disable-touchid"

class CLI {
private let args: [String]

init(_ arguments: [String]) {
args = arguments
}

func run() -> Bool {
if args.contains(listFlag) {
listRegistrations()
return true
} else if args.contains(deleteAllFlag) {
deleteAll()
return true
} else if args.contains(showTouchidFlag) {
showTouchid()
return true
} else if args.contains(enableTouchidFlag) {
enableTouchid()
return true
} else if args.contains(disableTouchidFlag) {
disableTouchid()
return true
}

return false
}

private func listRegistrations() {
print("The following is a list of U2F registrations stored in your keychain. Each key contains several fields:")
print(" - Key handle: This is the key handle that we registered with a website. For Soft U2F, the key handle is simply a hash of the public key.")
print(" - Application parameter: This is the sha256 of the app-id of the site.")
print(" - Known facet: For some sites we know the application parameter → site name mapping.")
print(" - Counter: How many times this registration has been used.")
print("")

U2FRegistration.all.forEach { reg in
print("Key handle: ", reg.keyHandle.base64EncodedString())
print("Application parameter: ", reg.applicationParameter.base64EncodedString())

if let kf = KnownFacets[reg.applicationParameter] {
print("Known facet: ", kf)
} else {
print("Known facet: N/A")
}

print("Counter: ", reg.counter)
print("")
}
}

private func deleteAll() {
guard let initialCount = U2FRegistration.count else {
print("Error getting registration count from keychain.")
return
}

if !U2FRegistration.deleteAll() {
print("Error deleting registrations from keychain.")
return
}

print("Deleted ", initialCount, " registrations")
}

private func showTouchid() {
if Settings.touchidDisabled {
print("TouchID is disabled")
} else {
print("TouchID is enabled")
}
}

private func enableTouchid() {
if Settings.enableTouchid() {
print("TouchID is now enabled")
} else {
print("Error enabling TouchID. Does your system support it?")
}
}

private func disableTouchid() {
Settings.disableTouchid()
print("TouchID is now disabled")
}
}
64 changes: 64 additions & 0 deletions SoftU2FTool/KeyPair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,63 @@

import Foundation

fileprivate class tmpKeyPair {
var label: String
var applicationLabel: Data
var publicKey: SecKey?
var privateKey: SecKey?

var keyPair: KeyPair? {
guard let pub = publicKey else { return nil }
guard let priv = privateKey else { return nil }

return KeyPair(label: label, appLabel: applicationLabel, publicKey: pub, privateKey: priv)
}

init(label l: String, applicationLabel al: Data) {
label = l
applicationLabel = al
}
}

class KeyPair {
/// Get all KeyPairs with the given label.
static func all(label: String) -> [KeyPair] {
let secKeys = Keychain.getSecKeys(attrLabel: label as CFString)
var tmpKeyPairs: [tmpKeyPair] = []
var keyPairs: [KeyPair] = []

secKeys.forEach { secKey in
guard let appLabel: CFData = Keychain.getSecKeyAttr(key: secKey, attr: kSecAttrApplicationLabel) else { return }

let applicationLabel = appLabel as Data
var tmpKP: tmpKeyPair

if let kp = tmpKeyPairs.first(where: { $0.applicationLabel == applicationLabel }) {
tmpKP = kp
} else {
tmpKP = tmpKeyPair.init(label: label, applicationLabel: applicationLabel)
tmpKeyPairs.append(tmpKP)
}

guard let klass: CFString = Keychain.getSecKeyAttr(key: secKey, attr: kSecAttrKeyClass) else { return }

if klass == kSecAttrKeyClassPublic {
tmpKP.publicKey = secKey
} else {
tmpKP.privateKey = secKey
}
}

tmpKeyPairs.forEach { tmpKP in
guard let kp = tmpKP.keyPair else { return }

keyPairs.append(kp)
}

return keyPairs
}

// The number of key pairs (keys/2) in the keychain.
static func count(label: String) -> Int? {
guard let c = Keychain.count(attrLabel: label as CFString) else { return nil }
Expand Down Expand Up @@ -75,6 +131,14 @@ class KeyPair {
privateKey = priv
}

// Initialize a key pair with all the necessary data.
init(label l: String, appLabel al: Data, publicKey pub: SecKey, privateKey priv: SecKey) {
label = l
applicationLabel = al
publicKey = pub
privateKey = priv
}

// Delete this key pair.
func delete() -> Bool {
return Keychain.delete(
Expand Down
31 changes: 29 additions & 2 deletions SoftU2FTool/Keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ class Keychain {
}

guard let opaqueResult = dict[name as String] else {
print("Missing key in dictionary")
return nil
}

Expand Down Expand Up @@ -152,6 +151,34 @@ class Keychain {
return opaqueResult as! CFData as Data
}

// Lookup all keys with the given label.
static func getSecKeys(attrLabel: CFString) -> [SecKey] {
let query = makeCFDictionary(
(kSecClass, kSecClassKey),
(kSecAttrKeyType, kSecAttrKeyTypeEC),
(kSecAttrLabel, attrLabel),
(kSecReturnRef, kCFBooleanTrue),
(kSecMatchLimit, 1000 as CFNumber)
)

var optionalOpaqueResult: CFTypeRef? = nil
let err = SecItemCopyMatching(query, &optionalOpaqueResult)

if err != errSecSuccess {
print("Error from keychain: \(err)")
return []
}

guard let opaqueResult = optionalOpaqueResult else {
print("Unexpected nil returned from keychain")
return []
}

let result = opaqueResult as! [SecKey]

return result
}

static func getSecKey(attrAppLabel: CFData, keyClass: CFString) -> SecKey? {
// Lookup public key.
let query = makeCFDictionary(
Expand Down Expand Up @@ -223,7 +250,7 @@ class Keychain {
var err: Unmanaged<CFError>? = nil
defer { err?.release() }

let sig = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &err) as? Data
let sig = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, &err) as Data?

if err != nil {
print("Error creating signature: \(err!.takeUnretainedValue().localizedDescription)")
Expand Down
3 changes: 2 additions & 1 deletion SoftU2FTool/KnownFacets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ let KnownFacets: [Data: String] = [
SHA256.digest("https://www.dropbox.com/u2f-app-id.json"): "https://dropbox.com",
SHA256.digest("https://www.gstatic.com/securitykey/origins.json"): "https://google.com",
SHA256.digest("https://vault.bitwarden.com/app-id.json"): "https://vault.bitwarden.com",
SHA256.digest("https://keepersecurity.com"): "https://keepersecurity.com"
SHA256.digest("https://keepersecurity.com"): "https://keepersecurity.com",
SHA256.digest("https://api-9dcf9b83.duosecurity.com"): "https://api-9dcf9b83.duosecurity.com"
]
38 changes: 38 additions & 0 deletions SoftU2FTool/Settings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Settings.swift
// SoftU2F
//
// Created by Ben Toews on 8/2/17.
//

import Foundation
import LocalAuthentication

class Settings {
private static let touchidDisabledKey = "touchidDisabled"

static var touchidDisabled: Bool {
return touchidAvailable && UserDefaults.standard.bool(forKey: touchidDisabledKey)
}

static func enableTouchid() -> Bool {
if touchidAvailable {
UserDefaults.standard.set(false, forKey: touchidDisabledKey)
return true
} else {
return false
}
}

static func disableTouchid() {
UserDefaults.standard.set(true, forKey: touchidDisabledKey)
}

private static var touchidAvailable: Bool {
if #available(OSX 10.12.2, *) {
return LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
} else {
return false
}
}
}
Loading

0 comments on commit 9a1bd5d

Please sign in to comment.