From 31506a00c0e5c20334c4cbb85c7896afd68dc1e1 Mon Sep 17 00:00:00 2001 From: Rebecca Mahany-Horton Date: Thu, 12 Sep 2024 14:52:21 -0400 Subject: [PATCH] Proof-of-concept for Windows Hello desktop triggers Windows Hello Fix timeout, small refactor Tidy up names, add documentation Retrieve key credential status Retrieve pubkey Get attestation Proof-of-concept for Windows Hello --- cmd/launcher/desktop.go | 3 + ee/presencedetection/hello_other.go | 13 + ee/presencedetection/hello_windows.go | 430 ++++++++++++++++++++++++++ go.mod | 8 +- go.sum | 10 +- 5 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 ee/presencedetection/hello_other.go create mode 100644 ee/presencedetection/hello_windows.go diff --git a/cmd/launcher/desktop.go b/cmd/launcher/desktop.go index a397fadb0..ceddacc18 100644 --- a/cmd/launcher/desktop.go +++ b/cmd/launcher/desktop.go @@ -19,6 +19,7 @@ import ( "github.com/kolide/launcher/ee/desktop/user/notify" userserver "github.com/kolide/launcher/ee/desktop/user/server" "github.com/kolide/launcher/ee/desktop/user/universallink" + "github.com/kolide/launcher/ee/presencedetection" "github.com/kolide/launcher/pkg/authedclient" "github.com/kolide/launcher/pkg/log/multislogger" "github.com/kolide/launcher/pkg/rungroup" @@ -182,6 +183,8 @@ func runDesktop(_ *multislogger.MultiSlogger, args []string) error { } }() + go presencedetection.WindowsHello(context.TODO(), slogger.With("component", "windows_hello")) + // blocks until shutdown called m.Init() diff --git a/ee/presencedetection/hello_other.go b/ee/presencedetection/hello_other.go new file mode 100644 index 000000000..1846bf8d5 --- /dev/null +++ b/ee/presencedetection/hello_other.go @@ -0,0 +1,13 @@ +//go:build !windows +// +build !windows + +package presencedetection + +import ( + "context" + "log/slog" +) + +func WindowsHello(_ context.Context, _ *slog.Logger) { + return +} diff --git a/ee/presencedetection/hello_windows.go b/ee/presencedetection/hello_windows.go new file mode 100644 index 000000000..68abdf75f --- /dev/null +++ b/ee/presencedetection/hello_windows.go @@ -0,0 +1,430 @@ +//go:build windows +// +build windows + +package presencedetection + +import ( + "context" + "errors" + "fmt" + "log/slog" + "syscall" + "time" + "unsafe" + + ole "github.com/go-ole/go-ole" + "github.com/kolide/kit/ulid" + "github.com/saltosystems/winrt-go" + "github.com/saltosystems/winrt-go/windows/foundation" + "github.com/saltosystems/winrt-go/windows/storage/streams" +) + +// GUIDs retrieved from: +// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/winrt/windows.security.credentials.idl +var ( + keyCredentialManagerGuid = ole.NewGUID("6AAC468B-0EF1-4CE0-8290-4106DA6A63B5") + keyCredentialRetrievalResultGuid = ole.NewGUID("58CD7703-8D87-4249-9B58-F6598CC9644E") + keyCredentialGuid = ole.NewGUID("9585EF8D-457B-4847-B11A-FA960BBDB138") + keyCredentialAttestationResultGuid = ole.NewGUID("78AAB3A1-A3C1-4103-B6CC-472C44171CBB") +) + +// Signatures were generated following the guidance in +// https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system#guid-generation-for-parameterized-types. +// The GUIDs themselves came from the same source as above (windows.security.credentials.idl). +// The GUIDs must be lowercase in the parameterized types. +const ( + keyCredentialRetrievalResultSignature = "rc(Windows.Security.Credentials.KeyCredentialRetrievalResult;{58cd7703-8d87-4249-9b58-f6598cc9644e})" + keyCredentialAttestationResultSignature = "rc(Windows.Security.Credentials.KeyCredentialAttestationResult;{78aab3a1-a3c1-4103-b6cc-472c44171cbb})" + booleanSignature = "b1" +) + +// KeyCredentialManager is defined here: +// https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredentialmanager?view=winrt-26100 +type KeyCredentialManager struct { + ole.IInspectable +} + +func (v *KeyCredentialManager) VTable() *KeyCredentialManagerVTable { + return (*KeyCredentialManagerVTable)(unsafe.Pointer(v.RawVTable)) +} + +type KeyCredentialManagerVTable struct { + ole.IInspectableVtbl + IsSupportedAsync uintptr + RenewAttestationAsync uintptr + RequestCreateAsync uintptr + OpenAsync uintptr + DeleteAsync uintptr +} + +// KeyCredentialRetrievalResult is defined here: +// https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredentialretrievalresult?view=winrt-26100 +type KeyCredentialRetrievalResult struct { + ole.IInspectable +} + +func (v *KeyCredentialRetrievalResult) VTable() *KeyCredentialRetrievalResultVTable { + return (*KeyCredentialRetrievalResultVTable)(unsafe.Pointer(v.RawVTable)) +} + +type KeyCredentialRetrievalResultVTable struct { + ole.IInspectableVtbl + GetCredential uintptr + GetStatus uintptr +} + +// KeyCredential is defined here: +// https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredential?view=winrt-26100 +type KeyCredential struct { + ole.IInspectable +} + +func (v *KeyCredential) VTable() *KeyCredentialVTable { + return (*KeyCredentialVTable)(unsafe.Pointer(v.RawVTable)) +} + +type KeyCredentialVTable struct { + ole.IInspectableVtbl + GetName uintptr + RetrievePublicKeyWithDefaultBlobType uintptr + RetrievePublicKeyWithBlobType uintptr + RequestSignAsync uintptr + GetAttestationAsync uintptr +} + +type KeyCredentialAttestationResult struct { + ole.IInspectable +} + +func (v *KeyCredentialAttestationResult) VTable() *KeyCredentialAttestationResultVTable { + return (*KeyCredentialAttestationResultVTable)(unsafe.Pointer(v.RawVTable)) +} + +type KeyCredentialAttestationResultVTable struct { + ole.IInspectableVtbl + GetCertificateChainBuffer uintptr + GetAttestationBuffer uintptr + GetStatus uintptr +} + +// windowsHello is a proof-of-concept that exercises prompting the user via Hello. +// TODO RM: +// * the syscalls panic easily; we will probably need to wrap this in a recovery routine +// * for readability, we should refactor individual calls into functions hanging off the appropriate structs above +func WindowsHello(ctx context.Context, slogger *slog.Logger) { + if err := ole.RoInitialize(1); err != nil { + slogger.Log(ctx, slog.LevelError, "could not initialize", "err", err) + return + } + + // Get access to the KeyCredentialManager + factory, err := ole.RoGetActivationFactory("Windows.Security.Credentials.KeyCredentialManager", ole.IID_IInspectable) + if err != nil { + slogger.Log(ctx, slog.LevelError, "could not get activation factory for KeyCredentialManager", "err", err) + return + } + defer factory.Release() + managerObj, err := factory.QueryInterface(keyCredentialManagerGuid) + if err != nil { + slogger.Log(ctx, slog.LevelError, "could not get KeyCredentialManager from factory", "err", err) + return + } + defer managerObj.Release() + keyCredentialManager := (*KeyCredentialManager)(unsafe.Pointer(managerObj)) + + // Check to see if Hello is an option + isHelloSupported, err := isSupported(keyCredentialManager) + if err != nil { + slogger.Log(ctx, slog.LevelError, "could not determine whether Hello is supported", "err", err) + return + } + if !isHelloSupported { + slogger.Log(ctx, slog.LevelError, "Hello is not supported") + return + } + + // Create a credential that will be tied to the current user and this application + credentialName := ulid.New() + pubkeyBytes, keyCredentialObj, err := requestCreate(keyCredentialManager, credentialName) + defer func() { + if keyCredentialObj != nil { + keyCredentialObj.Release() + } + }() + if err != nil { + slogger.Log(ctx, slog.LevelError, "could not create credential", "err", err) + return + } + slogger.Log(ctx, slog.LevelInfo, "created credential for user", "credential_name", credentialName, "pubkey_len", len(pubkeyBytes)) + + credential := (*KeyCredential)(unsafe.Pointer(keyCredentialObj)) + attestationBytes, err := getAttestationAsync(credential) + if err != nil { + slogger.Log(ctx, slog.LevelError, "could not get attestation from credential", "err", err) + return + } + + slogger.Log(ctx, slog.LevelInfo, "got attestation", "attestation_len", len(attestationBytes)) +} + +// isSupported calls Windows.Security.Credentials.KeyCredentialManager.IsSupportedAsync. +// It determines whether the current device and user is capable of provisioning a key credential. +// See: https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredentialmanager.issupportedasync?view=winrt-26100 +func isSupported(keyCredentialManager *KeyCredentialManager) (bool, error) { + var isSupportedAsyncOperation *foundation.IAsyncOperation + ret, _, _ := syscall.SyscallN( + keyCredentialManager.VTable().IsSupportedAsync, + 0, // Because this is a static function, we don't pass in a reference to `this` + uintptr(unsafe.Pointer(&isSupportedAsyncOperation)), // Windows.Foundation.IAsyncOperation + ) + if ret != 0 { + return false, fmt.Errorf("calling IsSupportedAsync: %w", ole.NewError(ret)) + } + + // IsSupportedAsync returns Windows.Foundation.IAsyncOperation + iid := winrt.ParameterizedInstanceGUID(foundation.GUIDAsyncOperationCompletedHandler, booleanSignature) + statusChan := make(chan foundation.AsyncStatus) + handler := foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) { + statusChan <- asyncStatus + }) + defer handler.Release() + isSupportedAsyncOperation.SetCompleted(handler) + + select { + case operationStatus := <-statusChan: + if operationStatus != foundation.AsyncStatusCompleted { + return false, fmt.Errorf("IsSupportedAsync operation did not complete: status %d", operationStatus) + } + case <-time.After(5 * time.Second): + return false, errors.New("timed out waiting for IsSupportedAsync operation to complete") + } + + res, err := isSupportedAsyncOperation.GetResults() + if err != nil { + return false, fmt.Errorf("getting results of IsSupportedAsync: %w", err) + } + + return uintptr(res) > 0, nil +} + +// requestCreate calls Windows.Security.Credentials.KeyCredentialManager.RequestCreateAsync. +// It creates a new key credential for the current user and application. +// See: https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredentialmanager.requestcreateasync?view=winrt-26100 +func requestCreate(keyCredentialManager *KeyCredentialManager, credentialName string) ([]byte, *ole.IDispatch, error) { + credentialNameHString, err := ole.NewHString(credentialName) + if err != nil { + return nil, nil, fmt.Errorf("creating credential name hstring: %w", err) + } + defer ole.DeleteHString(credentialNameHString) + + var requestCreateAsyncOperation *foundation.IAsyncOperation + requestCreateReturn, _, _ := syscall.SyscallN( + keyCredentialManager.VTable().RequestCreateAsync, + 0, // Because this is a static function, we don't pass in a reference to `this` + uintptr(unsafe.Pointer(&credentialNameHString)), // The name of the key credential to create + 0, // KeyCredentialCreationOption -- 0 indicates to replace any existing key credentials, 1 indicates to fail if a key credential already exists + uintptr(unsafe.Pointer(&requestCreateAsyncOperation)), // Windows.Foundation.IAsyncOperation + ) + if requestCreateReturn != 0 { + return nil, nil, fmt.Errorf("calling RequestCreateAsync: %w", ole.NewError(requestCreateReturn)) + } + + // RequestCreateAsync returns Windows.Foundation.IAsyncOperation + iid := winrt.ParameterizedInstanceGUID(foundation.GUIDAsyncOperationCompletedHandler, keyCredentialRetrievalResultSignature) + statusChan := make(chan foundation.AsyncStatus) + handler := foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) { + statusChan <- asyncStatus + }) + defer handler.Release() + requestCreateAsyncOperation.SetCompleted(handler) + + select { + case operationStatus := <-statusChan: + if operationStatus != foundation.AsyncStatusCompleted { + return nil, nil, fmt.Errorf("RequestCreateAsync operation did not complete: status %d", operationStatus) + } + case <-time.After(1 * time.Minute): + return nil, nil, errors.New("timed out waiting for RequestCreateAsync operation to complete") + } + + // Retrieve the results from the async operation + resPtr, err := requestCreateAsyncOperation.GetResults() + if err != nil { + return nil, nil, fmt.Errorf("getting results of RequestCreateAsync: %w", err) + } + + if uintptr(resPtr) == 0x0 { + return nil, nil, errors.New("no response to RequestCreateAsync") + } + + resultObj, err := (*ole.IUnknown)(resPtr).QueryInterface(keyCredentialRetrievalResultGuid) + if err != nil { + return nil, nil, fmt.Errorf("could not get KeyCredentialRetrievalResult from result of RequestCreateAsync: %w", err) + } + defer resultObj.Release() + result := (*KeyCredentialRetrievalResult)(unsafe.Pointer(resultObj)) + + // Now, retrieve the KeyCredential from the KeyCredentialRetrievalResult + var credentialPointer unsafe.Pointer + getCredentialReturn, _, _ := syscall.SyscallN( + result.VTable().GetCredential, + uintptr(unsafe.Pointer(result)), // Since we're retrieving an object property, we need a reference to `this` + uintptr(unsafe.Pointer(&credentialPointer)), + ) + if getCredentialReturn != 0 { + return nil, nil, fmt.Errorf("calling GetCredential on KeyCredentialRetrievalResult: %w", ole.NewError(getCredentialReturn)) + } + + keyCredentialObj, err := (*ole.IUnknown)(credentialPointer).QueryInterface(keyCredentialGuid) + if err != nil { + return nil, nil, fmt.Errorf("could not get KeyCredential from KeyCredentialRetrievalResult: %w", err) + } + defer keyCredentialObj.Release() + credential := (*KeyCredential)(unsafe.Pointer(keyCredentialObj)) + + // All right, things are going swimmingly. Let's retrieve the public key. + var pubkeyBufferPointer unsafe.Pointer + retrievePubKeyReturn, _, _ := syscall.SyscallN( + credential.VTable().RetrievePublicKeyWithDefaultBlobType, + uintptr(unsafe.Pointer(credential)), // Not a static method, so we need a reference to `this` + uintptr(unsafe.Pointer(&pubkeyBufferPointer)), + ) + if retrievePubKeyReturn != 0 { + return nil, nil, fmt.Errorf("calling RetrievePublicKey on KeyCredential: %w", ole.NewError(retrievePubKeyReturn)) + } + + pubkeyBufferObj, err := (*ole.IUnknown)(pubkeyBufferPointer).QueryInterface(ole.NewGUID(streams.GUIDIBuffer)) + if err != nil { + return nil, nil, fmt.Errorf("could not get buffer from result of RetrievePublicKey: %w", err) + } + defer pubkeyBufferObj.Release() + pubkeyBuffer := (*streams.IBuffer)(unsafe.Pointer(pubkeyBufferObj)) + + pubkeyBufferLen, err := pubkeyBuffer.GetLength() + if err != nil { + return nil, nil, fmt.Errorf("could not get length of pubkey buffer: %w", err) + } + pubkeyReader, err := streams.DataReaderFromBuffer(pubkeyBuffer) + if err != nil { + return nil, nil, fmt.Errorf("could not create data reader for pubkey buffer: %w", err) + } + pubkeyBytes, err := pubkeyReader.ReadBytes(pubkeyBufferLen) + if err != nil { + return nil, nil, fmt.Errorf("reading from pubkey buffer: %w", err) + } + + return pubkeyBytes, keyCredentialObj, nil + +} + +// getAttestationAsync calls Windows.Security.Credentials.KeyCredential.GetAttestationAsync. +// It gets an attestation for a key credential. +// See: https://learn.microsoft.com/en-us/uwp/api/windows.security.credentials.keycredential.getattestationasync?view=winrt-26100 +func getAttestationAsync(credential *KeyCredential) ([]byte, error) { + // Now it's time to get the attestation. This is another async operation. + var getAttestationAsyncOperation *foundation.IAsyncOperation + getAttestationReturn, _, _ := syscall.SyscallN( + credential.VTable().GetAttestationAsync, + uintptr(unsafe.Pointer(credential)), // Not a static method, so we need a reference to `this` + uintptr(unsafe.Pointer(&getAttestationAsyncOperation)), // Windows.Foundation.IAsyncOperation + ) + if getAttestationReturn != 0 { + return nil, fmt.Errorf("calling GetAttestationAsync: %w", ole.NewError(getAttestationReturn)) + } + + // GetAttestationAsync returns Windows.Foundation.IAsyncOperation + attestionResultIid := winrt.ParameterizedInstanceGUID(foundation.GUIDAsyncOperationCompletedHandler, keyCredentialAttestationResultSignature) + attestationStatusChan := make(chan foundation.AsyncStatus) + attestationHandler := foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(attestionResultIid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) { + attestationStatusChan <- asyncStatus + }) + defer attestationHandler.Release() + getAttestationAsyncOperation.SetCompleted(attestationHandler) + + select { + case operationStatus := <-attestationStatusChan: + if operationStatus != foundation.AsyncStatusCompleted { + return nil, fmt.Errorf("GetAttestationAsync operation did not complete: status %d", operationStatus) + } + case <-time.After(1 * time.Minute): + return nil, errors.New("timed out waiting for GetAttestationAsync operation to complete") + } + + // Retrieve the results from the async attestation operation + attestationResPtr, err := getAttestationAsyncOperation.GetResults() + if err != nil { + return nil, fmt.Errorf("getting results of GetAttestationAsync: %w", err) + } + + if uintptr(attestationResPtr) == 0x0 { + return nil, errors.New("no response to GetAttestationAsync") + } + + attestationResultObj, err := (*ole.IUnknown)(attestationResPtr).QueryInterface(keyCredentialAttestationResultGuid) + if err != nil { + return nil, fmt.Errorf("could not get KeyCredentialAttestationResult from result of GetAttestationAsync: %w", err) + } + defer attestationResultObj.Release() + attestationResult := (*KeyCredentialAttestationResult)(unsafe.Pointer(attestationResultObj)) + + // From here, we can retrieve both the attestation (via GetAttestationBuffer) and the certificate chain (via GetCertificateChainBuffer). + // Both of these operations should look identical to our IBuffer usage above, so I'm just going to grab the attestation here + // for now and fill in the certificate chain if we happen to need it later. + var attestationBufferPointer unsafe.Pointer + getAttestationBufferReturn, _, _ := syscall.SyscallN( + attestationResult.VTable().GetAttestationBuffer, + uintptr(unsafe.Pointer(attestationResult)), // Not a static method, so we need a reference to `this` + uintptr(unsafe.Pointer(&attestationBufferPointer)), + ) + if getAttestationBufferReturn != 0 { + return nil, fmt.Errorf("calling GetAttestationBuffer on KeyCredentialAttestationResult: %w", ole.NewError(getAttestationBufferReturn)) + } + + attestationBufferObj, err := (*ole.IUnknown)(attestationBufferPointer).QueryInterface(ole.NewGUID(streams.GUIDIBuffer)) + if err != nil { + return nil, fmt.Errorf("could not get buffer from result of GetAttestationBuffer: %w", err) + } + defer attestationBufferObj.Release() + attestationBuffer := (*streams.IBuffer)(unsafe.Pointer(attestationBufferObj)) + + attestationBufferLen, err := attestationBuffer.GetLength() + if err != nil { + return nil, fmt.Errorf("could not get length of attestation buffer: %w", err) + } + attestationReader, err := streams.DataReaderFromBuffer(attestationBuffer) + if err != nil { + return nil, fmt.Errorf("could not create data reader for attestation buffer: %w", err) + } + attestationBytes, err := attestationReader.ReadBytes(attestationBufferLen) + if err != nil { + return nil, fmt.Errorf("reading from attestation buffer: %w", err) + } + + return attestationBytes, nil +} + +// waitForAsyncOperation should allow us to abstract away the details of waiting for an async operation, +// but right now it only works for IsSupportedAsync; it results in an error 3 being returned from RequestCreateAsync. +// TODO RM -- fix. +func waitForAsyncOperation(signature string, timeout time.Duration, asyncOperation *foundation.IAsyncOperation) (unsafe.Pointer, error) { + statusChan := make(chan foundation.AsyncStatus) + + iid := winrt.ParameterizedInstanceGUID(foundation.GUIDAsyncOperationCompletedHandler, signature) + handler := foundation.NewAsyncOperationCompletedHandler(ole.NewGUID(iid), func(instance *foundation.AsyncOperationCompletedHandler, asyncInfo *foundation.IAsyncOperation, asyncStatus foundation.AsyncStatus) { + statusChan <- asyncStatus + }) + defer handler.Release() + + asyncOperation.SetCompleted(handler) + + select { + case operationStatus := <-statusChan: + if operationStatus != foundation.AsyncStatusCompleted { + return nil, fmt.Errorf("async operation did not complete: status %d", operationStatus) + } + case <-time.After(timeout): + return nil, errors.New("timed out waiting for operation to complete") + } + + return asyncOperation.GetResults() +} diff --git a/go.mod b/go.mod index 487b7d14d..1ae5473e1 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/mixer/clock v0.0.0-20170901150240-b08e6b4da7ea github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9 - github.com/peterbourgon/ff/v3 v3.0.0 + github.com/peterbourgon/ff/v3 v3.1.2 github.com/pkg/errors v0.9.1 github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 @@ -49,9 +49,11 @@ require ( github.com/apache/thrift v0.16.0 github.com/golang-jwt/jwt/v5 v5.0.0 github.com/golang-migrate/migrate/v4 v4.16.2 + github.com/golang/snappy v0.0.4 github.com/kolide/goleveldb v0.0.0-20240514204455-8d30cd4d31c6 github.com/kolide/systray v0.0.0-20240530130728-8265cd4e35db github.com/kolide/toast v1.0.2 + github.com/saltosystems/winrt-go v0.0.0-20240510082706-db61b37f5877 github.com/shirou/gopsutil/v3 v3.23.3 github.com/spf13/pflag v1.0.5 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 @@ -63,7 +65,6 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/golang/snappy v0.0.4 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -92,13 +93,12 @@ require ( github.com/BurntSushi/toml v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-tpm v0.3.3 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 048672642..31f4e6664 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,9 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -182,7 +183,6 @@ github.com/kolide/systray v0.0.0-20240530130728-8265cd4e35db/go.mod h1:FwK9yUmU3 github.com/kolide/toast v1.0.2 h1:BQlIfO3wbKIEWfF0c8v4UkdhSIZYnSWaKkZl+Yarptk= github.com/kolide/toast v1.0.2/go.mod h1:OguLiOUf57YSEuZqjfk4uP4KdT0QOblGoySOI8F1I0Y= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -231,8 +231,8 @@ github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9 h1:+7IDjPDpcEwV github.com/osquery/osquery-go v0.0.0-20231006172600-d6f325f636a9/go.mod h1:mLJRc1Go8uP32LRALGvWj2lVJ+hDYyIfxDzVa+C5Yo8= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= -github.com/peterbourgon/ff/v3 v3.0.0 h1:eQzEmNahuOjQXfuegsKQTSTDbf4dNvr/eNLrmJhiH7M= -github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0= +github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM= +github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -258,6 +258,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/saltosystems/winrt-go v0.0.0-20240510082706-db61b37f5877 h1:h+mGFGCgqpe2xqFpYtXSqDg3uJ1nYugFb5VQhTHvyL4= +github.com/saltosystems/winrt-go v0.0.0-20240510082706-db61b37f5877/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ=