Files
teleport/lib/devicetrust/assert/assert.go
T
rosstimothy 02a758b23a [2] Migration to the Opaque API (#67384)
* Convert existing non-gogo codegen to the Hybrid API

Contributes to https://github.com/gravitational/teleport/issues/66776.

All existing protos explicitly set to API_OPEN have been change to
API_HBYRID. The new codegen was performed via make grpc. There are no
other functional changes to the code to start consuming the Hybrid API
those will come later. The intent is to get all Hybrid codegen in and
backported to ease the transition.

* Initial migration to the Opaque API

Contributes to https://github.com/gravitational/teleport/issues/66776.

All of the changes here are mechanical conversions generated from
`open2opaque rewrite -levels=green ./...`. There will be a follow up
to this in teleport.e which does the same. Once all changes have been
merged the process will be repeated with -levels=yellow followed by
-levels=red.


See https://protobuf.dev/reference/go/opaque-migration/ for more
details.

* [2] Migration to the Opaque API

Contributes to #66776.

All of the changes here are mechanical conversions generated from
open2opaque rewrite -levels=yellow./.... There will be a follow up
to this in teleport.e which does the same. Once all changes have been
merged the process will be repeated with -levels=red.

See https://protobuf.dev/reference/go/opaque-migration/ for more details.
2026-06-08 13:21:41 +00:00

176 lines
5.7 KiB
Go

// Teleport
// Copyright (C) 2024 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 assert
import (
"context"
"github.com/gravitational/trace"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
"github.com/gravitational/teleport/lib/devicetrust/authn"
authntypes "github.com/gravitational/teleport/lib/devicetrust/authn/types"
)
// AssertDeviceClientStream is the client-side device assertion stream.
type AssertDeviceClientStream interface {
Send(*devicepb.AssertDeviceRequest) error
Recv() (*devicepb.AssertDeviceResponse, error)
}
// Ceremony implements the client-side assertion ceremony.
//
// See [devicepb.AssertDeviceRequest] for details.
type Ceremony struct {
// newAuthnCeremony defaults to authn.NewCeremony.
newAuthnCeremony func() *authn.Ceremony
}
// CeremonyOpt is a creation option for [Ceremony].
type CeremonyOpt func(*Ceremony)
// WithNewAuthnCeremonyFunc overrides the default authn.Ceremony constructor,
// allowing callers to change the underlying assert implementation.
//
// Useful for testing. Avoid for production code.
func WithNewAuthnCeremonyFunc(f func() *authn.Ceremony) CeremonyOpt {
return func(c *Ceremony) {
c.newAuthnCeremony = f
}
}
// NewCeremony creates a new [Ceremony], binding all native device trust
// methods.
func NewCeremony(opts ...CeremonyOpt) (*Ceremony, error) {
c := &Ceremony{
newAuthnCeremony: authn.NewCeremony,
}
for _, opt := range opts {
opt(c)
}
return c, nil
}
// Run runs the client-side device assertion ceremony.
// Requests and responses are consumed from the stream until the device is
// asserted or authentication fails.
func (c *Ceremony) Run(ctx context.Context, stream AssertDeviceClientStream) error {
newAuthn := c.newAuthnCeremony
if newAuthn == nil {
newAuthn = authn.NewCeremony
}
devices := &devicesClientAdapter{
stream: stream,
}
// Implement Assertion in terms of Authentication, so we borrow both the
// Secure Enclave and TPM branches from it.
// TODO(codingllama): Refactor so we don't need so many adapters?
_, err := newAuthn().Run(ctx, &authntypes.CeremonyRunParams{
DevicesClient: devices,
Certs: &devicepb.UserCertificates{}, // required but not used.
})
return trace.Wrap(err)
}
type devicesClientAdapter struct {
devicepb.DeviceTrustServiceClient
stream AssertDeviceClientStream
}
func (d *devicesClientAdapter) AuthenticateDevice(ctx context.Context, opts ...grpc.CallOption) (devicepb.DeviceTrustService_AuthenticateDeviceClient, error) {
return &authnStreamAdapter{
ctx: ctx,
stream: d.stream,
}, nil
}
// authnStreamAdapter adapts an [AssertDeviceClientStream] to a
// [devicepb.DeviceTrustService_AuthenticateDeviceClient] stream. This allows
// the assertion ceremony to borrow the [authn.Ceremony] logic for itself.
type authnStreamAdapter struct {
devicepb.DeviceTrustService_AuthenticateDeviceClient
ctx context.Context
stream AssertDeviceClientStream
}
func (s *authnStreamAdapter) CloseSend() error {
return nil
}
func (s *authnStreamAdapter) Context() context.Context {
return s.ctx
}
func (s *authnStreamAdapter) Recv() (*devicepb.AuthenticateDeviceResponse, error) {
resp, err := s.stream.Recv()
if err != nil {
return nil, trace.Wrap(err)
}
if resp == nil || !resp.HasPayload() {
return nil, trace.BadParameter("assertion response payload required")
}
switch resp.WhichPayload() {
case devicepb.AssertDeviceResponse_Challenge_case:
return devicepb.AuthenticateDeviceResponse_builder{
Challenge: proto.ValueOrDefault(resp.GetChallenge()),
}.Build(), nil
case devicepb.AssertDeviceResponse_TpmChallenge_case:
return devicepb.AuthenticateDeviceResponse_builder{
TpmChallenge: proto.ValueOrDefault(resp.GetTpmChallenge()),
}.Build(), nil
case devicepb.AssertDeviceResponse_DeviceAsserted_case:
// Pass an empty UserCertificates to signify success.
return devicepb.AuthenticateDeviceResponse_builder{
UserCertificates: &devicepb.UserCertificates{},
}.Build(), nil
default:
return nil, trace.BadParameter("unexpected assertion response payload: %T", resp.Payload)
}
}
func (s *authnStreamAdapter) Send(authnReq *devicepb.AuthenticateDeviceRequest) error {
if authnReq == nil || !authnReq.HasPayload() {
return trace.BadParameter("authenticate request payload required")
}
req := &devicepb.AssertDeviceRequest{}
switch authnReq.WhichPayload() {
case devicepb.AuthenticateDeviceRequest_Init_case:
init := authnReq.GetInit()
req.SetInit(devicepb.AssertDeviceInit_builder{
CredentialId: init.GetCredentialId(),
DeviceData: init.GetDeviceData(),
}.Build())
case devicepb.AuthenticateDeviceRequest_ChallengeResponse_case:
req.SetChallengeResponse(proto.ValueOrDefault(authnReq.GetChallengeResponse()))
case devicepb.AuthenticateDeviceRequest_TpmChallengeResponse_case:
req.SetTpmChallengeResponse(proto.ValueOrDefault(authnReq.GetTpmChallengeResponse()))
default:
return trace.BadParameter("unexpected authenticate request payload: %T", authnReq.Payload)
}
return trace.Wrap(s.stream.Send(req))
}