mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-05 03:33:24 +08:00
259 lines
7.4 KiB
Go
259 lines
7.4 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package native
|
|
|
|
// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=11.0
|
|
// #cgo LDFLAGS: -framework CoreFoundation -framework Foundation -framework IOKit -framework Security
|
|
// #include <stdint.h>
|
|
// #include <stdlib.h>
|
|
// #include "device_darwin.h"
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"log/slog"
|
|
"os/exec"
|
|
"os/user"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gravitational/trace"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
|
|
"github.com/gravitational/teleport/lib/darwin"
|
|
)
|
|
|
|
func enrollDeviceInit() (*devicepb.EnrollDeviceInit, error) {
|
|
cred, err := deviceKeyGetOrCreate()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
cd, err := CollectDeviceData(CollectedDataAlwaysEscalate)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "collecting device data")
|
|
}
|
|
|
|
return &devicepb.EnrollDeviceInit{
|
|
CredentialId: cred.Id,
|
|
DeviceData: cd,
|
|
Macos: &devicepb.MacOSEnrollPayload{
|
|
PublicKeyDer: cred.PublicKeyDer,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func deviceKeyGetOrCreate() (*devicepb.DeviceCredential, error) {
|
|
newID := uuid.NewString()
|
|
newIDC := C.CString(newID)
|
|
defer C.free(unsafe.Pointer(newIDC))
|
|
|
|
var pubKeyC C.PublicKey
|
|
defer func() {
|
|
C.free(unsafe.Pointer(pubKeyC.id))
|
|
C.free(unsafe.Pointer(pubKeyC.pub_key))
|
|
}()
|
|
|
|
if res := C.DeviceKeyGetOrCreate(newIDC, &pubKeyC); res != 0 {
|
|
return nil, trace.Wrap(statusErrorFromC(res))
|
|
}
|
|
|
|
id := C.GoString(pubKeyC.id)
|
|
pubKeyRaw := C.GoBytes(unsafe.Pointer(pubKeyC.pub_key), C.int(pubKeyC.pub_key_len))
|
|
return pubKeyToCredential(id, pubKeyRaw)
|
|
}
|
|
|
|
func pubKeyToCredential(id string, pubKeyRaw []byte) (*devicepb.DeviceCredential, error) {
|
|
ecPubKey, err := darwin.ECDSAPublicKeyFromRaw(pubKeyRaw)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
pubKeyDER, err := x509.MarshalPKIXPublicKey(ecPubKey)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &devicepb.DeviceCredential{
|
|
Id: id,
|
|
PublicKeyDer: pubKeyDER,
|
|
}, nil
|
|
}
|
|
|
|
func collectDeviceData(_ CollectDataMode) (*devicepb.DeviceCollectedData, error) {
|
|
var dd C.DeviceData
|
|
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() {
|
|
defer wg.Done()
|
|
out, err := spec.fn()
|
|
if err != nil {
|
|
slog.WarnContext(context.Background(), "Device Trust: Failed to get device details", "details", spec.desc, "error", err)
|
|
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,
|
|
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) {
|
|
slog.DebugContext(context.Background(), "Device Trust: Jamf binary not found", "binary_path", 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 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{
|
|
data: (*C.uint8_t)(C.CBytes(h[:])),
|
|
data_len: (C.size_t)(len(h)),
|
|
}
|
|
defer func() { C.free(unsafe.Pointer(digC.data)) }()
|
|
|
|
var sigC C.Signature
|
|
defer func() { C.free(unsafe.Pointer(sigC.data)) }()
|
|
|
|
if res := C.DeviceKeySign(digC, &sigC); res != 0 {
|
|
return nil, trace.Wrap(statusErrorFromC(res))
|
|
}
|
|
|
|
sig = C.GoBytes(unsafe.Pointer(sigC.data), C.int(sigC.data_len))
|
|
return sig, err
|
|
}
|
|
|
|
func getDeviceCredential() (*devicepb.DeviceCredential, error) {
|
|
var pubKeyC C.PublicKey
|
|
defer func() {
|
|
C.free(unsafe.Pointer(pubKeyC.id))
|
|
C.free(unsafe.Pointer(pubKeyC.pub_key))
|
|
}()
|
|
|
|
if res := C.DeviceKeyGet(&pubKeyC); res != 0 {
|
|
return nil, trace.Wrap(statusErrorFromC(res))
|
|
}
|
|
|
|
id := C.GoString(pubKeyC.id)
|
|
pubKeyRaw := C.GoBytes(unsafe.Pointer(pubKeyC.pub_key), C.int(pubKeyC.pub_key_len))
|
|
return pubKeyToCredential(id, pubKeyRaw)
|
|
}
|
|
|
|
func statusErrorFromC(res C.int32_t) error {
|
|
return &statusError{status: int32(res)}
|
|
}
|
|
|
|
func solveTPMEnrollChallenge(_ *devicepb.TPMEnrollChallenge, _ bool) (*devicepb.TPMEnrollChallengeResponse, error) {
|
|
return nil, trace.BadParameter("called solveTPMEnrollChallenge on darwin")
|
|
}
|
|
|
|
func solveTPMAuthnDeviceChallenge(_ *devicepb.TPMAuthenticateDeviceChallenge) (*devicepb.TPMAuthenticateDeviceChallengeResponse, error) {
|
|
return nil, trace.BadParameter("called solveTPMAuthnDeviceChallenge on darwin")
|
|
}
|
|
|
|
func handleTPMActivateCredential(_, _ string) error {
|
|
return trace.BadParameter("called handleTPMActivateCredential on darwin")
|
|
}
|