Skip to content

Commit

Permalink
added checks for valid password and keychain unlock
Browse files Browse the repository at this point in the history
  • Loading branch information
twocanoes committed Jun 3, 2022
1 parent 8ec30da commit ab84789
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 3 deletions.
66 changes: 66 additions & 0 deletions XCreds/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,73 @@ class AppDelegate: NSObject, NSApplicationDelegate {


func applicationDidFinishLaunching(_ aNotification: Notification) {
NotificationCenter.default.addObserver(forName: Notification.Name("TCSTokensUpdated"), object: nil, queue: nil) { notification in
// ScheduleManager.shared.startCredentialCheck()

let alertController = NSAlert()
alertController.messageText = "Local Password"
alertController.addButton(withTitle: "OK")
alertController.addButton(withTitle: "Cancel")

let localPassword = NSSecureTextField(frame: CGRect(x: 0, y: 0, width: 200, height: 24))
localPassword.becomeFirstResponder()

alertController.accessoryView = localPassword
alertController.runModal()

}
mainMenu.statusBarItem.menu = mainMenu.mainMenu
ScheduleManager.shared.startCredentialCheck()
var password:String?
while (true){
let alertController = NSAlert()

alertController.messageText = "Please enter your local password"
alertController.addButton(withTitle: "OK")
alertController.addButton(withTitle: "Cancel")

let localPassword = NSSecureTextField(frame: CGRect(x: 0, y: 0, width: 200, height: 24))

alertController.accessoryView = localPassword
localPassword.becomeFirstResponder()

let response = alertController.runModal()


if response == .alertSecondButtonReturn {
break
}
let isPasswordValid = PasswordUtils.verifyCurrentUserPassword(password: localPassword.stringValue)
if isPasswordValid==true {
password=localPassword.stringValue
break
}
}

if let password = password {

NotifyManager.shared.sendMessage(message: "valid password")
}
else {
NotifyManager.shared.sendMessage(message: "cancelled")
}


do {

let isPasswordValid = PasswordUtils.verifyCurrentUserPassword(password: "asdfasfd")

if isPasswordValid {
print("good password")
}
else {
print("bad password")

}
}



}

func applicationWillTerminate(_ aNotification: Notification) {
Expand Down
36 changes: 36 additions & 0 deletions XCreds/NotifyManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// NotifyManager.swift
// XCreds
//
// Created by Timothy Perfitt on 6/3/22.
//

import Cocoa
import UserNotifications

class NotifyManager {

static let shared = NotifyManager()


init() {
let center = UNUserNotificationCenter.current()

center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
}

}
func sendMessage(message:String) {
let content = UNMutableNotificationContent()
content.title = message

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)

// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

// add our notification request
UNUserNotificationCenter.current().add(request)

}
}
131 changes: 131 additions & 0 deletions XCreds/PasswordUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// PasswordUtils.swift
// XCreds
//
// Created by Timothy Perfitt on 6/3/22.
//

import Cocoa
import SystemConfiguration
import SecurityFoundation
import OpenDirectory

enum PasswordError: Error, CustomStringConvertible {
case itemNotFound(String)
case invalidParamater(String)
case invalidResult(String)
case unknownError(String)

var description: String {
switch self {
case .itemNotFound(let message): return message
case .invalidParamater(let message): return message
case .invalidResult(let message): return message
case .unknownError(let message): return message
}
}
}

class PasswordUtils: NSObject {

static let currentConsoleUserName: String = NSUserName()
static let uid: String = String(getuid())

class func getCurrentConsoleUserRecord() -> ODRecord? {
// Get ODRecords where record name is equal to the Current Console User's username
let session = ODSession.default()
var records = [ODRecord]()
do {
//let node = try ODNode.init(session: session, type: UInt32(kODNodeTypeAuthentication))
let node = try ODNode.init(session: session, type: UInt32(kODNodeTypeLocalNodes))
let query = try ODQuery.init(node: node, forRecordTypes: kODRecordTypeUsers, attribute: kODAttributeTypeRecordName, matchType: UInt32(kODMatchEqualTo), queryValues: currentConsoleUserName, returnAttributes: kODAttributeTypeNativeOnly, maximumResults: 0)
records = try query.resultsAllowingPartial(false) as! [ODRecord]
} catch {

}


// We may have gotten multiple ODRecords that match username,
// So make sure it also matches the UID.
if ( records != nil ) {
for case let record in records {
let attribute = "dsAttrTypeStandard:UniqueID"
if let odUid = try? String(describing: record.values(forAttribute: attribute)[0]) {
if ( odUid == uid) {
return record
}
}
}
}
return nil
}


class func verifyCurrentUserPassword(password:String) -> Bool {
let currentUser = PasswordUtils.getCurrentConsoleUserRecord()

do {
try currentUser?.verifyPassword(password)
}
catch {
return false

}
return true
}

class func verifyKeychainPassword(password: String) throws -> Bool {
var getDefaultKeychain: OSStatus
var myDefaultKeychain: SecKeychain?
var err: OSStatus

// get the user's default keychain. (Typically login.keychain)
getDefaultKeychain = SecKeychainCopyDefault(&myDefaultKeychain)
if ( getDefaultKeychain == errSecNoDefaultKeychain ) {
throw PasswordError.itemNotFound("Could not find Default Keychain")
}
var oldPasswordMutable = password

err = SecKeychainUnlock(myDefaultKeychain, UInt32(oldPasswordMutable.count), &oldPasswordMutable, true)
if err != noErr {
return false
}
return true
}
static func changeKeychainPassword(_ oldPassword: String, newPassword1: String, newPassword2: String) throws {
if (newPassword1 != newPassword2) {
throw PasswordError.invalidParamater("New passwords do not match.")
}

var getDefaultKeychain: OSStatus
var myDefaultKeychain: SecKeychain?
var err: OSStatus

// get the user's default keychain. (Typically login.keychain)
getDefaultKeychain = SecKeychainCopyDefault(&myDefaultKeychain)
if ( getDefaultKeychain == errSecNoDefaultKeychain ) {
throw PasswordError.itemNotFound("Could not find Default Keychain")
}

// Test if the keychain password is correct by trying to unlock it.

var oldPasswordMutable = oldPassword

err = SecKeychainUnlock(myDefaultKeychain, UInt32(oldPasswordMutable.count), &oldPasswordMutable, true)

if err != noErr {
throw PasswordError.invalidResult("Error unlocking default keychain.")
}

err = SecKeychainChangePassword(myDefaultKeychain, UInt32(oldPassword.count), oldPassword, UInt32(newPassword1.count), newPassword1)

if (err == noErr) {
return
} else if ( err == errSecAuthFailed ) {
return
} else {
// If we got any other error, we don't know if the password is good or not because we probably couldn't find the keychain.
throw PasswordError.unknownError("Unknown error: " + err.description)
}
}
}
39 changes: 39 additions & 0 deletions XCreds/ScheduleManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// ScheduleManager.swift
// XCreds
//
// Created by Timothy Perfitt on 6/3/22.
//

import Cocoa

class ScheduleManager {

static let shared=ScheduleManager()

var timer:Timer?
func startCredentialCheck() {

if let timer = timer, timer.isValid==true {
return
}

timer=Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { timer in
if TokenManager.shared.getNewAccessToken() {
NotifyManager.shared.sendMessage(message: "Azure password unchanged")
} else {
timer.invalidate()
NotifyManager.shared.sendMessage(message: "Azure password changed or not set")
mainMenu.webView = WebViewController()
mainMenu.webView?.window!.forceToFrontAndFocus(nil)
mainMenu.webView?.run()
}
})
}
func stopCredentialCheck() {
if let timer = timer, timer.isValid==true {
timer.invalidate()

}
}
}
16 changes: 16 additions & 0 deletions XCreds/SecurityPrivateAPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// SecurityPrivateAPI.h
// NoMAD
//
// Created by Phillip Boushy on 4/26/16.
// Copyright © 2016 Trusource Labs. All rights reserved.
//

#ifndef SecurityPrivateAPI_h
#define SecurityPrivateAPI_h

// So we can use SecKeychainChangePassword() in NoMADUser
#import <Security/Security.h>
extern OSStatus SecKeychainChangePassword(SecKeychainRef keychainRef, UInt32 oldPasswordLength, const void* oldPassword, UInt32 newPasswordLength, const void* newPassword);

#endif /* SecurityPrivateAPI_h */
5 changes: 4 additions & 1 deletion XCreds/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ extension WebViewController: OIDCLiteDelegate {
UserDefaults.standard.set(tokens.accessToken, forKey: PrefKeys.accessToken.rawValue)
UserDefaults.standard.set(tokens.idToken, forKey: PrefKeys.idToken.rawValue)
UserDefaults.standard.set(tokens.refreshToken, forKey: PrefKeys.refreshToken.rawValue)
1


RunLoop.main.perform {
self.window?.close()
NotificationCenter.default.post(name: Notification.Name("TCSTokensUpdated"), object: self)

}
}
}
13 changes: 13 additions & 0 deletions XCreds/XCreds-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// XCreds-Bridging-Header.h
// XCreds
//
// Created by Timothy Perfitt on 6/3/22.
//

#ifndef XCreds_Bridging_Header_h
#define XCreds_Bridging_Header_h
#import "SecurityPrivateAPI.h"


#endif /* XCreds_Bridging_Header_h */
Loading

0 comments on commit ab84789

Please sign in to comment.