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
51 changes: 47 additions & 4 deletions lib/devicetrust/enroll/enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,26 @@ import (
"runtime"

"github.com/gravitational/trace"
"golang.org/x/exp/slices"

devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/devicetrust"
)

// RunCeremony performs the client-side device enrollment ceremony.
func RunCeremony(ctx context.Context, devicesClient devicepb.DeviceTrustServiceClient, enrollToken string) (*devicepb.Device, error) {
// Start by checking the OSType, this lets us exit early with a nicer message
// for non-supported OSes.
if getOSType() != devicepb.OSType_OS_TYPE_MACOS {
return nil, trace.BadParameter("device enrollment not supported for current OS (%v)", runtime.GOOS)
osType := getOSType()
if !slices.Contains([]devicepb.OSType{
devicepb.OSType_OS_TYPE_MACOS,
devicepb.OSType_OS_TYPE_WINDOWS,
}, osType) {
return nil, trace.BadParameter(
"device enrollment not supported for current OS (%s)",
types.ResourceOSTypeToString(osType),
)
}

init, err := enrollInit()
Expand Down Expand Up @@ -57,10 +66,21 @@ func RunCeremony(ctx context.Context, devicesClient devicepb.DeviceTrustServiceC
// Unimplemented errors are not expected to happen after this point.

// 2. Challenge.
// Only macOS is supported, see the guard at the beginning of the method.
if err := enrollDeviceMacOS(stream, resp); err != nil {
switch osType {
case devicepb.OSType_OS_TYPE_MACOS:
err = enrollDeviceMacOS(stream, resp)
// err handled below
case devicepb.OSType_OS_TYPE_WINDOWS:
err = enrollDeviceTPM(stream, resp)
// err handled below
default:
// This should be caught by the OSType guard at start of function.
panic("no enrollment function provided for os")
}
if err != nil {
return nil, trace.Wrap(err)
}

resp, err = stream.Recv()
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -93,6 +113,29 @@ func enrollDeviceMacOS(stream devicepb.DeviceTrustService_EnrollDeviceClient, re
return trace.Wrap(err)
}

func enrollDeviceTPM(stream devicepb.DeviceTrustService_EnrollDeviceClient, resp *devicepb.EnrollDeviceResponse) error {
challenge := resp.GetTpmChallenge()
switch {
case challenge == nil:
return trace.BadParameter("unexpected challenge payload from server: %T", resp.Payload)
case challenge.EncryptedCredential == nil:
return trace.BadParameter("missing encrypted_credential in challenge from server")
case len(challenge.AttestationNonce) == 0:
return trace.BadParameter("missing attestation_nonce in challenge from server")
}

challengeResponse, err := solveTPMEnrollChallenge(challenge)
if err != nil {
return trace.Wrap(err)
}
err = stream.Send(&devicepb.EnrollDeviceRequest{
Payload: &devicepb.EnrollDeviceRequest_TpmChallengeResponse{
TpmChallengeResponse: challengeResponse,
},
})
return trace.Wrap(err)
}

func getDeviceOSType() devicepb.OSType {
switch runtime.GOOS {
case "darwin":
Expand Down
48 changes: 43 additions & 5 deletions lib/devicetrust/enroll/enroll_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"os"
"testing"

"github.com/gravitational/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -37,25 +38,61 @@ func TestRunCeremony(t *testing.T) {

macOSDev1, err := testenv.NewFakeMacOSDevice()
require.NoError(t, err, "NewFakeMacOSDevice failed")
windowsDev1 := testenv.NewFakeWindowsDevice()

tests := []struct {
name string
dev fakeDevice
name string
dev fakeDevice
assertErr func(t *testing.T, err error)
assertGotDevice func(t *testing.T, device *devicepb.Device)
}{
{
name: "macOS device",
name: "macOS device succeeds",
dev: macOSDev1,
assertErr: func(t *testing.T, err error) {
assert.NoError(t, err, "RunCeremony returned an error")
},
assertGotDevice: func(t *testing.T, d *devicepb.Device) {
assert.NotNil(t, d, "RunCeremony returned nil device")
},
},
{
name: "windows device succeeds",
dev: windowsDev1,
assertErr: func(t *testing.T, err error) {
assert.NoError(t, err, "RunCeremony returned an error")
},
assertGotDevice: func(t *testing.T, d *devicepb.Device) {
require.NotNil(t, d, "RunCeremony returned nil device")
require.NotNil(t, d.Credential, "device credential is nil")
assert.Equal(t, windowsDev1.CredentialID, d.Credential.Id, "device credential mismatch")
},
},
{
name: "linux device fails",
dev: testenv.NewFakeLinuxDevice(),
assertErr: func(t *testing.T, err error) {
require.Error(t, err)
assert.True(
t, trace.IsBadParameter(err), "RunCeremony did not return a BadParameter error",
)
assert.ErrorContains(t, err, "linux", "RunCeremony error mismatch")
},
assertGotDevice: func(t *testing.T, d *devicepb.Device) {
assert.Nil(t, d, "RunCeremony returned an unexpected, non-nil device")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
*enroll.GetOSType = test.dev.GetOSType
*enroll.EnrollInit = test.dev.EnrollDeviceInit
*enroll.SignChallenge = test.dev.SignChallenge
*enroll.SolveTPMEnrollChallenge = test.dev.SolveTPMEnrollChallenge

got, err := enroll.RunCeremony(ctx, devices, "faketoken")
require.NoError(t, err, "RunCeremony failed")
assert.NotNil(t, got, "RunCeremony returned nil device")
test.assertErr(t, err)
test.assertGotDevice(t, got)
})
}
}
Expand Down Expand Up @@ -85,4 +122,5 @@ type fakeDevice interface {
EnrollDeviceInit() (*devicepb.EnrollDeviceInit, error)
GetOSType() devicepb.OSType
SignChallenge(chal []byte) (sig []byte, err error)
SolveTPMEnrollChallenge(challenge *devicepb.TPMEnrollChallenge) (*devicepb.TPMEnrollChallengeResponse, error)
}
9 changes: 5 additions & 4 deletions lib/devicetrust/enroll/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
package enroll

var (
CollectDeviceData = &collectDeviceData
EnrollInit = &enrollInit
GetOSType = &getOSType
SignChallenge = &signChallenge
CollectDeviceData = &collectDeviceData
EnrollInit = &enrollInit
GetOSType = &getOSType
SignChallenge = &signChallenge
SolveTPMEnrollChallenge = &solveTPMEnrollChallenge
)
9 changes: 5 additions & 4 deletions lib/devicetrust/enroll/native_shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import "github.com/gravitational/teleport/lib/devicetrust/native"

// vars below are used to fake OSes and switch implementations for tests.
var (
collectDeviceData = native.CollectDeviceData
enrollInit = native.EnrollDeviceInit
getOSType = getDeviceOSType
signChallenge = native.SignChallenge
collectDeviceData = native.CollectDeviceData
enrollInit = native.EnrollDeviceInit
getOSType = getDeviceOSType
signChallenge = native.SignChallenge
solveTPMEnrollChallenge = native.SolveTPMEnrollChallenge
)
9 changes: 8 additions & 1 deletion lib/devicetrust/native/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

package native

import devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
import (
devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
)

// EnrollDeviceInit creates the initial enrollment data for the device.
// This includes fetching or creating a device credential, collecting device
Expand All @@ -39,3 +41,8 @@ func SignChallenge(chal []byte) (sig []byte, err error) {
func GetDeviceCredential() (*devicepb.DeviceCredential, error) {
return getDeviceCredential()
}

// SolveTPMEnrollChallenge completes a TPM enrollment challenge.
func SolveTPMEnrollChallenge(challenge *devicepb.TPMEnrollChallenge) (*devicepb.TPMEnrollChallengeResponse, error) {
return solveTPMEnrollChallenge(challenge)
}
12 changes: 9 additions & 3 deletions lib/devicetrust/native/device_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ func collectDeviceData() (*devicepb.DeviceCollectedData, error) {
return nil, trace.Wrap(statusErrorFromC(res))
}

sn := C.GoString(dd.serial_number)
return &devicepb.DeviceCollectedData{
CollectTime: timestamppb.Now(),
OsType: devicepb.OSType_OS_TYPE_MACOS,
SerialNumber: C.GoString(dd.serial_number),
CollectTime: timestamppb.Now(),
OsType: devicepb.OSType_OS_TYPE_MACOS,
SerialNumber: sn,
SystemSerialNumber: sn,
}, nil
}

Expand Down Expand Up @@ -142,3 +144,7 @@ func getDeviceCredential() (*devicepb.DeviceCredential, error) {
func statusErrorFromC(res C.int32_t) error {
return &statusError{status: int32(res)}
}

func solveTPMEnrollChallenge(challenge *devicepb.TPMEnrollChallenge) (*devicepb.TPMEnrollChallengeResponse, error) {
return nil, trace.BadParameter("called solveTPMEnrollChallenge on darwin")
}
Loading