Files
teleport/lib/devicetrust/authn/authn.go
T
Alan Parra 4e07b4dfa3 Implement client-side device authentication (#19377)
Implement the client-side device authentication ceremony.

Device authentication requires a previously registered and enrolled device. In
exchange for solving a challenge, the ceremony allows the user to augment their
certificates with device extensions, which are necessary to clear device-aware
authentication.

Wiring to `tsh` to be done in a follow-up PR.

gravitational/teleport.e#514
2022-12-16 14:24:56 +00:00

112 lines
3.3 KiB
Go

// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package authn
import (
"context"
"github.com/gravitational/trace"
devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
"github.com/gravitational/teleport/lib/devicetrust/native"
)
// vars below are used to swap native methods for fakes in tests.
var (
getDeviceCredential = native.GetDeviceCredential
collectDeviceData = native.CollectDeviceData
signChallenge = native.SignChallenge
)
// RunCeremony performs the client-side device authentication ceremony.
//
// Device authentication requires a previously registered and enrolled device
// (see the lib/devicetrust/enroll package).
//
// The outcome of the authentication ceremony is a pair of user certificates
// augmented with device extensions.
func RunCeremony(ctx context.Context, devicesClient devicepb.DeviceTrustServiceClient, certs *devicepb.UserCertificates) (*devicepb.UserCertificates, error) {
switch {
case devicesClient == nil:
return nil, trace.BadParameter("devicesClient required")
case certs == nil:
return nil, trace.BadParameter("certs required")
}
stream, err := devicesClient.AuthenticateDevice(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
// 1. Init.
cred, err := getDeviceCredential()
if err != nil {
return nil, trace.Wrap(err)
}
cd, err := collectDeviceData()
if err != nil {
return nil, trace.Wrap(err)
}
if err := stream.Send(&devicepb.AuthenticateDeviceRequest{
Payload: &devicepb.AuthenticateDeviceRequest_Init{
Init: &devicepb.AuthenticateDeviceInit{
UserCertificates: &devicepb.UserCertificates{
// Forward only the SSH certificate, the TLS identity is part of the
// connection.
SshAuthorizedKey: certs.SshAuthorizedKey,
},
CredentialId: cred.Id,
DeviceData: cd,
},
},
}); err != nil {
return nil, trace.Wrap(err)
}
resp, err := stream.Recv()
if err != nil {
return nil, trace.Wrap(err)
}
// 2. Challenge.
chalResp := resp.GetChallenge()
if chalResp == nil {
return nil, trace.BadParameter("unexpected payload from server, expected AuthenticateDeviceChallenge: %T", resp.Payload)
}
sig, err := signChallenge(chalResp.Challenge)
if err != nil {
return nil, trace.Wrap(err)
}
if err := stream.Send(&devicepb.AuthenticateDeviceRequest{
Payload: &devicepb.AuthenticateDeviceRequest_ChallengeResponse{
ChallengeResponse: &devicepb.AuthenticateDeviceChallengeResponse{
Signature: sig,
},
},
}); err != nil {
return nil, trace.Wrap(err)
}
resp, err = stream.Recv()
if err != nil {
return nil, trace.Wrap(err)
}
// 3. User certificates.
newCerts := resp.GetUserCertificates()
if newCerts == nil {
return nil, trace.BadParameter("unexpected payload from server, expected UserCertificates: %T", resp.Payload)
}
return newCerts, nil
}