Skip to content

Commit

Permalink
make prefs alphabetical and support for automatically rotating the ke…
Browse files Browse the repository at this point in the history
…y in the keychain if its missing
  • Loading branch information
wesw-stripe committed Jan 29, 2025
1 parent 6aa672a commit 9559c63
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 37 deletions.
21 changes: 10 additions & 11 deletions Crypt/Mechanisms/Check.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ class Check: CryptMechanism {
let removePlist = getPref(key: .RemovePlist) as! Bool
os_log("RemovePlist Preferences is set to %{public}@", log: Check.log, type: .default, String(describing: removePlist))

// Check to see if our recovery key exists at the OutputPath Preference.
let recoveryKeyExists: Bool = checkFileExists(path: filepath)
let genKey = getManagedPref(key: .GenerateNewKey)
// check if t
let generateKey = genKey.0 as! Bool
let forcedKey = genKey.1!
let useKeychain = getPref(key: .StoreRecoveryKeyInKeychain) as! Bool

// Check to see if we have a recovery key somewhere
let recoveryKeyExists: Bool = hasRecoveryKey(path: filepath, useKeychain: useKeychain)
let generateKey = getPref(key: .GenerateNewKey) as! Bool
let alreadyGeneratedKey = getPref(key: .RotatedKey) as! Bool

if (!recoveryKeyExists && !removePlist && rotateKey) || generateKey {
if forcedKey {
os_log("WARNING!!!!!! GenerateNewKey is set to True, but it's a Managed Preference, you probably don't want to do this. Please change to a non Managed value.", log: Check.log, type: .error)
if alreadyGeneratedKey {
os_log("We've already generated a new key. If you wish to generate another key please remove RotatedKey from preferences...", log: Check.log, type: .default)
allowLogin()
return
}
Expand All @@ -92,10 +92,9 @@ class Check: CryptMechanism {
}

if generateKey {
os_log("We've rotated the key and GenerateNewKey was True, setting to False to avoid multiple generations", log: Check.log, type: .default)
// delete from root if set there.
CFPreferencesSetAppValue("GenerateNewKey" as CFString, nil, bundleid as CFString)
os_log("We've rotated the key and GenerateNewKey was True, setting to RotatedKey to avoid multiple generations", log: Check.log, type: .default)
// set to false for house keeping, setPref will also sync to disk
_ = setPref(key: .RotatedKey, value: true)
}
allowLogin()
return
Expand Down
22 changes: 19 additions & 3 deletions Filevault.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import Foundation
import os.log


// check authrestart capability
func checkAuthRestart() -> Bool {
let outPipe = Pipe.init()
Expand Down Expand Up @@ -202,6 +201,21 @@ func checkFileExists(path: String) -> Bool {
}
}

func hasRecoveryKey(path: String, useKeychain: Bool) -> Bool {
if !useKeychain {
return checkFileExists(path: path)
}

let label = "com.grahamgilbert.crypt.recovery"
guard let recoveryKey = getPasswordFromKeychain(label: label) else {
os_log("Recovery Key NOT found in keychain...", log: filevaultLog, type: .default)
return false
}

os_log("Recovery Key found in keychain...", log: filevaultLog, type: .default)
return true
}

/// Processes the output from a FileVault operation and handles the storage of recovery information
///
/// This function takes the output data from a FileVault operation and either stores the recovery key
Expand Down Expand Up @@ -245,15 +259,17 @@ private func handleFileVaultOutput(outputData: Data, filepath: String) throws ->
let systemKeychainPath = "/Library/Keychains/System.keychain"
var read_apps = getPref(key: .AppsAllowedToReadKey) as! [String]
var change_apps = getPref(key: .AppsAllowedToChangeKey) as! [String]
// we need to insert empty strings into the arrays which will add the Authorization Framwork paths into lists so we can read and write the key later on.
read_apps.insert("", at: 0)
change_apps.insert("", at: 0)
let invisible = getPref(key: .InvisibleInKeychain) as! Bool
let label: String = cryptBundleID + ".recovery"
let label: String = "com.grahamgilbert.crypt.recovery"
guard syncRecoveryKeyToKeychain(label: label, recoveryKey: recoveryKey, keychain: systemKeychainPath, apps: read_apps, owners: change_apps, makeInvisible: invisible) else {
os_log("Error: Failed to sync recovery key to keychain.", log: filevaultLog, type: .error)
return false
}
// We should clear the LastEscrow pref value so we queue up a sync at first run.

// We should clear the LastEscrow pref value so we queue up a sync at first run of checkin.
_ = setPref(key: .LastEscrow, value: Date(timeIntervalSince1970: 0))
return true
}
Expand Down
16 changes: 10 additions & 6 deletions Keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func syncRecoveryKeyToKeychain(label: String, recoveryKey: String, keychain: Str
var needToDeleteAndReaddKey: Bool = false

// get the current info value so we can check if it is up to date.
if let storedRecoveryKey = getPasswordFromKeychain(keyChain: secKeychain, label: label) {
if let storedRecoveryKey = getPasswordFromKeychain(label: label, keyChain: secKeychain) {
needToSyncRecoveryKey = false
needToDeleteAndReaddKey = true

Expand Down Expand Up @@ -272,8 +272,8 @@ func syncRecoveryKeyToKeychain(label: String, recoveryKey: String, keychain: Str
/// both the attributes and data of the matched item.
///
/// - Parameters:
/// - keyChain: The `SecKeychain` instance where the password item is stored.
/// - label: A `String` representing the label of the password item to search for in the keychain.
/// - keyChain: Optional `SecKeychain` instance for a keychain where the password item is stored.
/// - Returns: An optional `String` containing the password if found and properly decoded, or `nil` if the item is
/// not found or an error occurs.
///
Expand All @@ -285,15 +285,19 @@ func syncRecoveryKeyToKeychain(label: String, recoveryKey: String, keychain: Str
/// - Throws:
/// The function does not throw Swift-level errors but logs any issues encountered using `os_log`, which includes
/// scenarios where the password item cannot be located or read from the keychain.
func getPasswordFromKeychain(keyChain: SecKeychain, label: String) -> String? {
let query: [CFString: Any] = [
func getPasswordFromKeychain(label: String, keyChain: SecKeychain? = nil) -> String? {
var query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecReturnAttributes: true,
kSecReturnData: true,
kSecAttrLabel: label,
kSecMatchSearchList: [keyChain]
kSecAttrLabel: label
]

// Only add kSecMatchSearchList if keyChain is provided
if let keyChain = keyChain {
query[kSecMatchSearchList] = [keyChain]
}

var item: CFTypeRef?

os_log("Looking for password in keychain for label: [%{public}@].", log: keychainLog, type: .default, label)
Expand Down
37 changes: 20 additions & 17 deletions Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,41 @@ import os.log
let cryptBundleID = "com.grahamgilbert.crypt"

enum Preference: String {
case OutputPath
case RotateUsedKey
case RemovePlist
case SkipUsers
case ValidateKey
case GenerateNewKey
case StoreRecoveryKeyInKeychain
case AppsAllowedToReadKey
case AppsAllowedToChangeKey
case ServerURL
case AppsAllowedToReadKey
case GenerateNewKey
case InvisibleInKeychain
case KeychainUIPromptDescription
case LastEscrow
case OutputPath
case RemovePlist
case RotatedKey
case RotateUsedKey
case ServerURL
case SkipUsers
case StoreRecoveryKeyInKeychain
case ValidateKey

// Default preferences as a computed property
static var defaultPreferences: [Preference: Any] {
return [
.AppsAllowedToChangeKey: [],
.AppsAllowedToReadKey: ["/Library/Crypt/checkin"],
.GenerateNewKey: false,
.InvisibleInKeychain: false,
.KeychainUIPromptDescription: "Crypt FileVault Recovery Key",
.LastEscrow: Date(timeIntervalSince1970: 0),
.OutputPath: "/var/root/crypt_output.plist",
.RotateUsedKey: true,
.RemovePlist: true,
.RotateUsedKey: true,
.RotatedKey: false,
.SkipUsers: [],
.ValidateKey: true,
.GenerateNewKey: false,
.StoreRecoveryKeyInKeychain: true,
.AppsAllowedToReadKey: ["/Library/Crypt/checkin"],
.AppsAllowedToChangeKey: [],
.InvisibleInKeychain: false,
.KeychainUIPromptDescription: "Crypt FileVault Recovery Key",
.LastEscrow: Date(timeIntervalSince1970: 0)
.ValidateKey: true
]
}
}

/**
Retrieves a preference value.

Expand Down

0 comments on commit 9559c63

Please sign in to comment.