From 543b4e29ce8551dc7570dae6fd9835e1ce21e906 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Fri, 23 Feb 2024 12:00:47 -0800 Subject: [PATCH 1/4] Flag for checking whether the mechs are present --- Crypt/Info.plist | 2 +- README.md | 22 ++- cmd/BUILD.bazel | 2 +- cmd/main.go | 13 +- pkg/{postinstall => authmechs}/BUILD.bazel | 14 +- .../authemechs.go} | 48 +++++- .../authmechs_test.go} | 160 +++++++++++++++++- 7 files changed, 242 insertions(+), 19 deletions(-) rename pkg/{postinstall => authmechs}/BUILD.bazel (57%) rename pkg/{postinstall/postinstall.go => authmechs/authemechs.go} (74%) rename pkg/{postinstall/postinstall_test.go => authmechs/authmechs_test.go} (50%) diff --git a/Crypt/Info.plist b/Crypt/Info.plist index 53a2e91..5a7a958 100644 --- a/Crypt/Info.plist +++ b/Crypt/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 257 + 259 NSHumanReadableCopyright Copyright © 2024 The Crypt Project. All rights reserved. NSPrincipalClass diff --git a/README.md b/README.md index 6ffabf1..a5da665 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ **WARNING:** As this has the potential for stopping users from logging in, extensive testing should take place before deploying into production. -Crypt is an authorization plugin that will enforce FileVault 2, and then submit it to an instance of [Crypt Server](https://github.com/grahamgilbert/crypt-server). Crypt supports macOS 11 and 12. For versions below 11.0, please use version 4.0.0. For versions below 10.12 please use version 2 and below. - -Version 3.0.0 now supports 10.12 and above, previous macOS version support has been deprecated! +Crypt is an authorization plugin that will enforce FileVault 2, and then submit it to an instance of [Crypt Server](https://github.com/grahamgilbert/crypt-server). Crypt supports macOS 13 and above.For versions below 13.0, please use version 4.1.0. For versions below 11.0, please use version 4.0.0. For versions below 10.12 please use version 2 and below. When using Crypt with macOS 10.15 and higher, you will also need to deploy a PPC TCC profile via user approved MDM to allow Crypt to enable FileVault. [An example can be found here.](https://github.com/grahamgilbert/crypt/blob/master/ppctcc_example.mobileconfig) @@ -26,6 +24,14 @@ The `ServerURL` preference sets your Crypt Server. Crypt will not enforce FileVa $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt ServerURL "https://crypt.example.com" ``` +### ManageAuthMechs + +By default, Crypt will ensure the Authenication Mechaniss are set up correctly. If you want to disable this, you can set the `ManageAuthMechs` preference to `FALSE`. + +```bash +$ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt ManageAuthMechs -bool FALSE +``` + ### SkipUsers The `SkipUsers` preference allows you to define an array of users that will not be forced to enable FileVault. @@ -54,7 +60,7 @@ For macOS 10.15 and above, you may want to use the `ROTATE_VIEWED_SECRETS` key i ### ValidateKey -Crypt2 can validate the recovery key if it is stored on disk. If the key fails validation, the plist is removed so it can be regenerated on next login. This is set to `TRUE` by default. +Crypt can validate the recovery key if it is stored on disk. If the key fails validation, the plist is removed so it can be regenerated on next login. This is set to `TRUE` by default. ```bash $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt ValidateKey -bool FALSE @@ -62,7 +68,7 @@ $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt ValidateKey - ### OutputPath -As of version 3.0.0 you can now define a new location for where the recovery key is written to. Default for this is `'/var/root/crypt_output.plist'`. +You can define a new location for where the recovery key is written to. Default for this is `'/var/root/crypt_output.plist'`. ```bash $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt OutputPath "/path/to/different/location" @@ -70,7 +76,7 @@ $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt OutputPath "/ ### KeyEscrowInterval -As of version 3.0.0 you can now define the time interval in Hours for how often Crypt tries to re-escrow the key, after the first successful escrow. Default for this is `1` hour. +You can define the time interval in Hours for how often Crypt tries to re-escrow the key, after the first successful escrow. Default for this is `1` hour. ```bash $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt KeyEscrowInterval -int 2 @@ -86,7 +92,7 @@ $ sudo defaults write /Library/Preferences/com.grahamgilbert.crypt AdditionalCur ### PostRunCommand -(Introduced in version 3.2.0) This is a command that is run after Crypt has detected an error condition with a stored key that cannot be resolved silently - either it has failed validation or the server has instructed the client to rotate the key. These cannot be resolved silently on APFS volumes, so the user will need to log in again. If you have a tool that can enforce a logout or a reboot, you can run it here. This preference can either be a string if your command has no spaces, or an array if there are spaces in the command. +This is a command that is run after Crypt has detected an error condition with a stored key that cannot be resolved silently - either it has failed validation or the server has instructed the client to rotate the key. These cannot be resolved silently on APFS volumes, so the user will need to log in again. If you have a tool that can enforce a logout or a reboot, you can run it here. This preference can either be a string if your command has no spaces, or an array if there are spaces in the command. ## Uninstalling @@ -102,4 +108,4 @@ You will need to configure Xcode 9.3 (requires 10.13.2 or later) to sign the bun ## Credits -Crypt 2 couldn't have been written without the help of [Tom Burgin](https://github.com/tburgin) - he is responsible for all of the good code in this project. The bad bits are mine. +Crypt couldn't have been written without the help of [Tom Burgin](https://github.com/tburgin) - he is responsible for all of the good code in this project. The bad bits are mine. diff --git a/cmd/BUILD.bazel b/cmd/BUILD.bazel index b33e33b..7baf50b 100644 --- a/cmd/BUILD.bazel +++ b/cmd/BUILD.bazel @@ -6,8 +6,8 @@ go_library( importpath = "github.com/grahamgilbert/crypt/cmd", visibility = ["//visibility:private"], deps = [ + "//pkg/authmechs:postinstall", "//pkg/checkin", - "//pkg/postinstall", "//pkg/pref", "//pkg/utils", ], diff --git a/cmd/main.go b/cmd/main.go index 9b8a15a..11b33c1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,8 +6,8 @@ import ( "log" "os" + "github.com/grahamgilbert/crypt/pkg/authmechs" "github.com/grahamgilbert/crypt/pkg/checkin" - "github.com/grahamgilbert/crypt/pkg/postinstall" "github.com/grahamgilbert/crypt/pkg/pref" "github.com/grahamgilbert/crypt/pkg/utils" ) @@ -18,6 +18,7 @@ func main() { install := flag.Bool("install", false, "Install the AuthDB mechanisms") uninstall := flag.Bool("uninstall", false, "Uninstall the AuthDB mechanisms") + checkMechs := flag.Bool("check-auth-mechs", false, "Check the AuthDB mechanisms. Returns 0 if all are present, 1 if not.") versionFlag := flag.Bool("version", false, "print the version") flag.Parse() @@ -28,13 +29,19 @@ func main() { os.Exit(0) } if *install { - err := postinstall.Run(r, true) + err := authmechs.Run(r, true) if err != nil { log.Println(err) os.Exit(1) } } else if *uninstall { - err := postinstall.Run(r, false) + err := authmechs.Run(r, false) + if err != nil { + log.Println(err) + os.Exit(1) + } + } else if *checkMechs { + err := authmechs.Check(r) if err != nil { log.Println(err) os.Exit(1) diff --git a/pkg/postinstall/BUILD.bazel b/pkg/authmechs/BUILD.bazel similarity index 57% rename from pkg/postinstall/BUILD.bazel rename to pkg/authmechs/BUILD.bazel index 0677ece..00b32b3 100644 --- a/pkg/postinstall/BUILD.bazel +++ b/pkg/authmechs/BUILD.bazel @@ -2,8 +2,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "postinstall", - srcs = ["postinstall.go"], - importpath = "github.com/grahamgilbert/crypt/pkg/postinstall", + srcs = ["authemechs.go"], + importpath = "github.com/grahamgilbert/crypt/pkg/authmechs", visibility = ["//visibility:public"], deps = [ "//pkg/utils", @@ -17,3 +17,13 @@ go_test( embed = [":postinstall"], deps = ["@com_github_stretchr_testify//assert"], ) + +go_test( + name = "authmechs_test", + srcs = ["authmechs_test.go"], + embed = [":authmechs"], + deps = [ + "//pkg/utils", + "@com_github_stretchr_testify//assert", + ], +) diff --git a/pkg/postinstall/postinstall.go b/pkg/authmechs/authemechs.go similarity index 74% rename from pkg/postinstall/postinstall.go rename to pkg/authmechs/authemechs.go index 85803a0..070ef2a 100644 --- a/pkg/postinstall/postinstall.go +++ b/pkg/authmechs/authemechs.go @@ -1,8 +1,10 @@ -package postinstall +package authmechs import ( "errors" + "fmt" "os" + "reflect" "github.com/grahamgilbert/crypt/pkg/utils" "github.com/groob/plist" @@ -37,6 +39,19 @@ func removeMechsInDB(db AuthDB, mechList []string) AuthDB { return db } +func checkMechsInDB(db AuthDB, mechList []string, indexMech string, indexOffset int) bool { + insertIndex := indexOf(db.Mechanisms, indexMech) - len(mechList) // start from the position before the indexMech + + // Check if the position is valid + if insertIndex < 0 || insertIndex+len(mechList) > len(db.Mechanisms) { + fmt.Println("Invalid index") + return false + } + + // Compare the corresponding elements of the two slices + return reflect.DeepEqual(db.Mechanisms[insertIndex:insertIndex+len(mechList)], mechList) +} + func setMechsInDB(db AuthDB, mechList []string, indexMech string, indexOffset int, add bool) AuthDB { db = removeMechsInDB(db, mechList) @@ -75,14 +90,23 @@ func indexOf(slice []string, item string) int { return -1 } -func editAuthDB(r utils.Runner, add bool) error { +func getAuthDb(r utils.Runner) (AuthDB, error) { securityConsoleOut, err := r.Runner.RunCmd("/usr/bin/security", "authorizationdb", "read", "system.login.console") if err != nil { - return err + return AuthDB{}, err } var d AuthDB err = plist.Unmarshal(securityConsoleOut, &d) + if err != nil { + return AuthDB{}, err + } + + return d, nil +} + +func editAuthDB(r utils.Runner, add bool) error { + d, err := getAuthDb(r) if err != nil { return err } @@ -109,6 +133,24 @@ func checkRoot() error { return nil } +func Check(r utils.Runner) error { + err := checkRoot() + if err != nil { + return err + } + + d, err := getAuthDb(r) + if err != nil { + return err + } + + if !checkMechsInDB(d, fv2Mechs, fv2IndexMech, fv2IndexOffset) { + return errors.New("mechanisms are not set correctly") + } + + return nil +} + func Run(r utils.Runner, add bool) error { err := checkRoot() if err != nil { diff --git a/pkg/postinstall/postinstall_test.go b/pkg/authmechs/authmechs_test.go similarity index 50% rename from pkg/postinstall/postinstall_test.go rename to pkg/authmechs/authmechs_test.go index ff29059..9439b0a 100644 --- a/pkg/postinstall/postinstall_test.go +++ b/pkg/authmechs/authmechs_test.go @@ -1,8 +1,9 @@ -package postinstall +package authmechs import ( "testing" + "github.com/grahamgilbert/crypt/pkg/utils" "github.com/stretchr/testify/assert" ) @@ -187,3 +188,160 @@ func TestInsertMechsAtPosition(t *testing.T) { }) } } + +func TestGetAuthDB(t *testing.T) { + tests := []struct { + name string + want AuthDB + runner utils.MockCmdRunner + }{ + { + name: "Test with Crypt config", + want: AuthDB{ + Class: "evaluate-mechanisms", + Comment: "Login mechanism based rule. Not for general use, yet.", + Created: 730353220.36463201, + Mechanisms: []string{ + "builtin:prelogin", + "builtin:policy-banner", + "loginwindow:login", + "builtin:login-begin", + "builtin:reset-password,privileged", + "loginwindow:FDESupport,privileged", + "builtin:forward-login,privileged", + "builtin:auto-login,privileged", + "builtin:authenticate,privileged", + "PKINITMechanism:auth,privileged", + "builtin:login-success", + "loginwindow:success", + "HomeDirMechanism:login,privileged", + "HomeDirMechanism:status", + "MCXMechanism:login", + "CryptoTokenKit:login", + "Crypt:Check,privileged", + "Crypt:CryptGUI", + "Crypt:Enablement,privileged", + "loginwindow:done", + }, + Modified: 730407814.24742103, + Shared: true, + Tries: 10000, + Version: 11, + }, + runner: utils.MockCmdRunner{ + Output: ` + + + + class + evaluate-mechanisms + comment + Login mechanism based rule. Not for general use, yet. + created + 730353220.36463201 + mechanisms + + builtin:prelogin + builtin:policy-banner + loginwindow:login + builtin:login-begin + builtin:reset-password,privileged + loginwindow:FDESupport,privileged + builtin:forward-login,privileged + builtin:auto-login,privileged + builtin:authenticate,privileged + PKINITMechanism:auth,privileged + builtin:login-success + loginwindow:success + HomeDirMechanism:login,privileged + HomeDirMechanism:status + MCXMechanism:login + CryptoTokenKit:login + Crypt:Check,privileged + Crypt:CryptGUI + Crypt:Enablement,privileged + loginwindow:done + + modified + 730407814.24742103 + shared + + tries + 10000 + version + 11 + + `, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + runner := &tt.runner + r := utils.Runner{Runner: runner} + got, err := getAuthDb(r) + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestCheckMechsInDB(t *testing.T) { + tests := []struct { + name string + db AuthDB + mechList []string + indexMech string + indexOffset int + expected bool + }{ + { + name: "Test Case 1", // The case when the sequence is present before the indexMech + db: AuthDB{Mechanisms: []string{"mech2", "mech3", "mech1"}}, + mechList: []string{"mech2", "mech3"}, + indexMech: "mech1", + indexOffset: 0, + expected: true, + }, + { + name: "Test Case 2", // The case when the sequence is present after the indexMech + db: AuthDB{Mechanisms: []string{"mech1", "mech2", "mech3"}}, + mechList: []string{"mech2", "mech3"}, + indexMech: "mech1", + indexOffset: 0, + expected: false, + }, + { + name: "Test Case 3", + db: AuthDB{Mechanisms: []string{"mech1", "mech2", "mech3"}}, + mechList: []string{"mech4", "mech5"}, + indexMech: "mech3", + indexOffset: 0, + expected: false, + }, + { + name: "Test Case 4", // The case when the sequence is not present before the indexMech + db: AuthDB{Mechanisms: []string{"mech3", "mech1", "mech2"}}, + mechList: []string{"mech2", "mech3"}, + indexMech: "mech1", + indexOffset: 0, + expected: false, + }, + { + name: "Test Case 5", // The case when the sequence is present, but not in the correct order + db: AuthDB{Mechanisms: []string{"mech3", "mech2", "mech1"}}, + mechList: []string{"mech2", "mech3"}, + indexMech: "mech1", + indexOffset: 0, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := checkMechsInDB(tt.db, tt.mechList, tt.indexMech, tt.indexOffset) + assert.Equal(t, tt.expected, result) + }) + } +} From c8aea72207edd127911465261d3ff9ff5ebed537 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Fri, 23 Feb 2024 13:42:23 -0800 Subject: [PATCH 2/4] Check for the auth mechs by default --- Crypt/Info.plist | 2 +- pkg/authmechs/authemechs.go | 16 ++++++++++++++-- pkg/checkin/BUILD.bazel | 1 + pkg/checkin/escrow.go | 13 +++++++++++++ pkg/pref/pref.go | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Crypt/Info.plist b/Crypt/Info.plist index 5a7a958..9b1a7f1 100644 --- a/Crypt/Info.plist +++ b/Crypt/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 259 + 260 NSHumanReadableCopyright Copyright © 2024 The Crypt Project. All rights reserved. NSPrincipalClass diff --git a/pkg/authmechs/authemechs.go b/pkg/authmechs/authemechs.go index 070ef2a..ff3790c 100644 --- a/pkg/authmechs/authemechs.go +++ b/pkg/authmechs/authemechs.go @@ -3,6 +3,7 @@ package authmechs import ( "errors" "fmt" + "log" "os" "reflect" @@ -156,10 +157,21 @@ func Run(r utils.Runner, add bool) error { if err != nil { return err } - err = editAuthDB(r, add) + + return editAuthDB(r, add) +} + +func Ensure(r utils.Runner) error { + d, err := getAuthDb(r) if err != nil { return err } - return nil + if checkMechsInDB(d, fv2Mechs, fv2IndexMech, fv2IndexOffset) { + return nil + } + + log.Println("Mechanisms are not set correctly, adding to AuthDB") + + return editAuthDB(r, true) } diff --git a/pkg/checkin/BUILD.bazel b/pkg/checkin/BUILD.bazel index 73ee8a4..4c9e10a 100644 --- a/pkg/checkin/BUILD.bazel +++ b/pkg/checkin/BUILD.bazel @@ -6,6 +6,7 @@ go_library( importpath = "github.com/grahamgilbert/crypt/pkg/checkin", visibility = ["//visibility:public"], deps = [ + "//pkg/authmechs:postinstall", "//pkg/pref", "//pkg/utils", "@com_github_groob_plist//:plist", diff --git a/pkg/checkin/escrow.go b/pkg/checkin/escrow.go index e001290..55ab370 100644 --- a/pkg/checkin/escrow.go +++ b/pkg/checkin/escrow.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/grahamgilbert/crypt/pkg/authmechs" "github.com/grahamgilbert/crypt/pkg/pref" "github.com/grahamgilbert/crypt/pkg/utils" "github.com/groob/plist" @@ -47,6 +48,18 @@ func RunEscrow(r utils.Runner, p pref.PrefInterface) error { return errors.Wrap(err, "failed to get remove plist preference") } + manageAuthMechs, err := p.GetBool("ManageAuthMechs") + if err != nil { + return errors.Wrap(err, "failed to get manage auth mechs preference") + } + + if manageAuthMechs { + err := authmechs.Ensure(r) + if err != nil { + return errors.Wrap(err, "failed to ensure auth mechs") + } + } + if rotateUsedKey && validateKey && !removePlist { err := rotateInvalidKey(plistPath, r, p) if err != nil { diff --git a/pkg/pref/pref.go b/pkg/pref/pref.go index 9afdb84..b51e168 100644 --- a/pkg/pref/pref.go +++ b/pkg/pref/pref.go @@ -46,6 +46,7 @@ var defaultPrefs = map[string]interface{}{ "ValidateKey": true, "KeyEscrowInterval": 1, "AdditionalCurlOpts": []string{}, + "ManageAuthMechs": true, } func (p *Pref) Get(prefName string) (interface{}, error) { From 8fba91e1cf1f898f191fe46c832542d0cab4374a Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Fri, 23 Feb 2024 13:56:52 -0800 Subject: [PATCH 3/4] Cleanup version --- Crypt/Info.plist | 2 +- Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Crypt/Info.plist b/Crypt/Info.plist index 9b1a7f1..af7f77f 100644 --- a/Crypt/Info.plist +++ b/Crypt/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 260 + 261 NSHumanReadableCopyright Copyright © 2024 The Crypt Project. All rights reserved. NSPrincipalClass diff --git a/Makefile b/Makefile index a19a95f..69737df 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,8 @@ build_binary: # bazel build --platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64 //:cmd:crypt-amd # bazel build --platforms=@io_bazel_rules_go//go/toolchain:darwin_arm //cmd:crypt-arm # tools/bazel_to_builddir.sh - CGO_ENABLED=1 CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ GOOS=darwin GOARCH=arm64 go build -ldflags "-X main.version=${PACKAGE_VERSION}" -o build/checkin.arm64 cmd/main.go - CGO_ENABLED=1 CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ GOOS=darwin GOARCH=amd64 go build -ldflags "-X main.version=${PACKAGE_VERSION}" -o build/checkin.amd64 cmd/main.go + CGO_ENABLED=1 CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ GOOS=darwin GOARCH=arm64 go build -ldflags "-X main.version=${BUNDLE_VERSION}" -o build/checkin.arm64 cmd/main.go + CGO_ENABLED=1 CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ GOOS=darwin GOARCH=amd64 go build -ldflags "-X main.version=${BUNDLE_VERSION}" -o build/checkin.amd64 cmd/main.go /usr/bin/lipo -create -output build/checkin build/checkin.arm64 build/checkin.amd64 /bin/rm build/checkin.arm64 /bin/rm build/checkin.amd64 From f7caf66a87c99eaed2d10e2577159088117a5969 Mon Sep 17 00:00:00 2001 From: Graham Gilbert Date: Sun, 25 Feb 2024 19:40:47 -0800 Subject: [PATCH 4/4] fix --- Crypt/Info.plist | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Crypt/Info.plist b/Crypt/Info.plist index af7f77f..0573f6f 100644 --- a/Crypt/Info.plist +++ b/Crypt/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 261 + 262 NSHumanReadableCopyright Copyright © 2024 The Crypt Project. All rights reserved. NSPrincipalClass diff --git a/README.md b/README.md index a5da665..fa7d9fd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **WARNING:** As this has the potential for stopping users from logging in, extensive testing should take place before deploying into production. -Crypt is an authorization plugin that will enforce FileVault 2, and then submit it to an instance of [Crypt Server](https://github.com/grahamgilbert/crypt-server). Crypt supports macOS 13 and above.For versions below 13.0, please use version 4.1.0. For versions below 11.0, please use version 4.0.0. For versions below 10.12 please use version 2 and below. +Crypt is an authorization plugin that will enforce FileVault 2, and then submit it to an instance of [Crypt Server](https://github.com/grahamgilbert/crypt-server). Crypt supports macOS 13 and above. For versions below 13.0, please use version 4.1.0. For versions below 11.0, please use version 4.0.0. For versions below 10.12 please use version 2 and below. When using Crypt with macOS 10.15 and higher, you will also need to deploy a PPC TCC profile via user approved MDM to allow Crypt to enable FileVault. [An example can be found here.](https://github.com/grahamgilbert/crypt/blob/master/ppctcc_example.mobileconfig)