Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 100 additions & 5 deletions lib/devicetrust/native/device_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@ package native
import "C"

import (
"bytes"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"
"io/fs"
"os/exec"
"os/user"
"strings"
"sync"
"unsafe"

"github.com/google/uuid"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/timestamppb"

devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
Expand Down Expand Up @@ -91,21 +100,107 @@ func pubKeyToCredential(id string, pubKeyRaw []byte) (*devicepb.DeviceCredential

func collectDeviceData() (*devicepb.DeviceCollectedData, error) {
var dd C.DeviceData
defer func() { C.free(unsafe.Pointer(dd.serial_number)) }()
defer func() {
C.free(unsafe.Pointer(dd.serial_number))
C.free(unsafe.Pointer(dd.model))
C.free(unsafe.Pointer(dd.os_version_string))
}()

if res := C.DeviceCollectData(&dd); res != 0 {
return nil, trace.Wrap(statusErrorFromC(res))
}

osUser, err := user.Current()
if err != nil {
return nil, trace.Wrap(err, "reading current user")
}

// Run exec-ed commands concurrently.
var wg sync.WaitGroup
// Note: We could read the OS build from dd.os_version_string, but this
// requires no string parsing.
var osBuild, jamfVersion, macosEnrollmentProfiles string
for _, spec := range []struct {
fn func() (string, error)
out *string
desc string
}{
{fn: getOSBuild, out: &osBuild, desc: "macOS build"},
{fn: getJamfBinaryVersion, out: &jamfVersion, desc: "Jamf version"},
{fn: getMacosEnrollmentProfiles, out: &macosEnrollmentProfiles, desc: "macOs enrollment profiles"},
} {
spec := spec
wg.Add(1)
go func() {
Comment thread
codingllama marked this conversation as resolved.
defer wg.Done()
out, err := spec.fn()
if err != nil {
log.WithError(err).Warnf("Device Trust: Failed to get %v", spec.desc)
return
}
*spec.out = out
}()
}
wg.Wait()

sn := C.GoString(dd.serial_number)
return &devicepb.DeviceCollectedData{
CollectTime: timestamppb.Now(),
OsType: devicepb.OSType_OS_TYPE_MACOS,
SerialNumber: sn,
SystemSerialNumber: sn,
CollectTime: timestamppb.Now(),
OsType: devicepb.OSType_OS_TYPE_MACOS,
SerialNumber: sn,
ModelIdentifier: C.GoString(dd.model),
OsVersion: fmt.Sprintf("%v.%v.%v", dd.os_major, dd.os_minor, dd.os_patch),
OsBuild: osBuild,
OsUsername: osUser.Username,
JamfBinaryVersion: jamfVersion,
MacosEnrollmentProfiles: macosEnrollmentProfiles,
SystemSerialNumber: sn,
}, nil
}

func getOSBuild() (string, error) {
cmd := exec.Command("/usr/bin/sw_vers", "-buildVersion")
out, err := cmd.Output()
if err != nil {
return "", trace.Wrap(err, "running sw_vers -buildVersion")
}
return string(bytes.TrimSpace(out)), nil
}

func getJamfBinaryVersion() (string, error) {
// See https://learn.jamf.com/bundle/jamf-pro-documentation-current/page/Components_Installed_on_Managed_Computers.html
cmd := exec.Command("/usr/local/bin/jamf", "version")
out, err := cmd.Output()
if err != nil {
// Jamf binary may not exist. This is alright.
pathErr := &fs.PathError{}
if errors.As(err, &pathErr) {
log.Debugf("Device Trust: Jamf binary not found: %q", pathErr.Path)
return "", nil
}

return "", trace.Wrap(err, "running jamf version")
}

// Eg: "version=10.46.1-t1683911857"
s := string(bytes.TrimSpace(out))
tmp := strings.Split(s, "=")
if len(tmp) != 2 {
return "", fmt.Errorf("unexpected jamf version string: %q", s)
}

return string(tmp[1]), nil
}

func getMacosEnrollmentProfiles() (string, error) {
cmd := exec.Command("/usr/bin/profiles", "status", "-type", "enrollment")
out, err := cmd.Output()
if err != nil {
return "", trace.Wrap(err, "running /usr/bin/profiles status -type enrollment")
}
return string(bytes.TrimSpace(out)), nil
}

func signChallenge(chal []byte) (sig []byte, err error) {
h := sha256.Sum256(chal)
digC := C.Digest{
Expand Down
12 changes: 12 additions & 0 deletions lib/devicetrust/native/device_darwin.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,19 @@ int32_t DeviceKeySign(Digest digest, Signature *sigOut);

// DeviceData contains collected data for the device in use.
typedef struct _DeviceData {
// Mac system serial number.
// Example: "C02FP3EXXXXX".
const char *serial_number;
// Mac device model.
// See https://support.apple.com/en-us/HT201608.
// Example: "MacBookPro16,1".
const char *model;
Comment thread
codingllama marked this conversation as resolved.
// OS version "string", as acquired from NSProcessInfo.
// Example: "Version 13.4 (Build 22F66)".
const char *os_version_string;
int64_t os_major;
int64_t os_minor;
int64_t os_patch;
} DeviceData;

// DeviceCollectData collects data for the device in use.
Expand Down
55 changes: 47 additions & 8 deletions lib/devicetrust/native/device_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,33 @@ int32_t DeviceKeySign(Digest digest, Signature *sigOut) {
return res;
}

// Duplicate a CFString or CFData `ref` as a C string.
const char *refToCString(CFTypeRef ref) {
NSData *data = NULL; // managed by ARC.
NSString *str = NULL; // managed by ARC.
CFTypeID id;

if (!ref) {
return NULL;
}

id = CFGetTypeID(ref);
if (id == CFStringGetTypeID()) {
str = (__bridge NSString *)ref;
} else if (id == CFDataGetTypeID()) {
data = (__bridge NSData *)ref;
str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
} else {
return NULL;
}

return strdup([str UTF8String]);
}

int32_t DeviceCollectData(DeviceData *out) {
CFStringRef cfSerialNumber = NULL; // managed via bridge
NSString *serialNumber = NULL; // managed by ARC
CFMutableDictionaryRef cfIODict = NULL; // manually released
NSProcessInfo *info = NULL; // managed by ARC
NSOperatingSystemVersion osVersion;
int32_t res = 0;

io_service_t platformExpert = IOServiceGetMatchingService(
Expand All @@ -249,17 +273,32 @@ int32_t DeviceCollectData(DeviceData *out) {
goto end;
}

cfSerialNumber = IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault,
0 /* options */);
if (!cfSerialNumber) {
// For a quick reference, see `ioreg -c IOPlatformExpertDevice -d 2`.
IORegistryEntryCreateCFProperties(platformExpert, &cfIODict,
kCFAllocatorDefault, 0 /* options */);
if (!cfIODict) {
res = kErrIORegistryEntryFailed;
goto end;
}
serialNumber = (__bridge_transfer NSString *)cfSerialNumber;
out->serial_number = strdup([serialNumber UTF8String]);

// Serial number and model from IORegistry.
out->serial_number = refToCString(
CFDictionaryGetValue(cfIODict, CFSTR(kIOPlatformSerialNumberKey)));
out->model = refToCString(CFDictionaryGetValue(cfIODict, CFSTR("model")));

// OS version numbers.
info = [NSProcessInfo processInfo];
osVersion = [info operatingSystemVersion];
out->os_version_string =
strdup([[info operatingSystemVersionString] UTF8String]);
out->os_major = osVersion.majorVersion;
out->os_minor = osVersion.minorVersion;
out->os_patch = osVersion.patchVersion;

end:
if (cfIODict) {
CFRelease(cfIODict);
}
if (platformExpert) {
IOObjectRelease(platformExpert);
}
Expand Down