Skip to content
Closed
9 changes: 2 additions & 7 deletions api/client/proxy/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
transportv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1"
"github.com/gravitational/teleport/api/metadata"
"github.com/gravitational/teleport/api/utils/grpc/interceptors"
"github.com/gravitational/teleport/api/utils/keys"
)

// ClientConfig contains configuration needed for a Client
Expand Down Expand Up @@ -124,7 +125,7 @@ func (c *ClientConfig) CheckAndSetDefaults(ctx context.Context) error {
// before initiating the gRPC dial.
// This approach works because the connection is cached for a few seconds,
// allowing subsequent calls without requiring additional user action.
if priv, ok := cert.PrivateKey.(hardwareKeyWarmer); ok {
if priv, ok := cert.PrivateKey.(*keys.PrivateKey); ok {
err := priv.WarmupHardwareKey(ctx)
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -454,9 +455,3 @@ func (c *Client) Ping(ctx context.Context) error {
_, _ = c.transport.ClusterDetails(ctx)
return nil
}

// hardwareKeyWarmer performs a bogus call to the hardware key,
// to proactively prompt the user for a PIN/touch (if needed).
type hardwareKeyWarmer interface {
WarmupHardwareKey(ctx context.Context) error
}
3 changes: 2 additions & 1 deletion api/client/webclient/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
)

const (
Expand Down Expand Up @@ -528,7 +529,7 @@ type AuthenticationSettings struct {
// PrivateKeyPolicy contains the cluster-wide private key policy.
PrivateKeyPolicy keys.PrivateKeyPolicy `json:"private_key_policy"`
// PIVSlot specifies a specific PIV slot to use with hardware key support.
PIVSlot keys.PIVSlot `json:"piv_slot"`
PIVSlot hardwarekey.PIVSlotKeyString `json:"piv_slot"`
// DeviceTrust holds cluster-wide device trust settings.
DeviceTrust DeviceTrustSettings `json:"device_trust,omitempty"`
// HasMessageOfTheDay is a flag indicating that the cluster has MOTD
Expand Down
3 changes: 2 additions & 1 deletion api/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils/keypaths"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
"github.com/gravitational/teleport/api/utils/sshutils"
)

Expand Down Expand Up @@ -107,7 +108,7 @@ type Profile struct {
PrivateKeyPolicy keys.PrivateKeyPolicy `yaml:"private_key_policy"`

// PIVSlot is a specific piv slot that Teleport clients should use for hardware key support.
PIVSlot keys.PIVSlot `yaml:"piv_slot"`
PIVSlot hardwarekey.PIVSlotKeyString `yaml:"piv_slot"`

// MissingClusterDetails means this profile was created with limited cluster details.
// Missing cluster details should be loaded into the profile by pinging the proxy.
Expand Down
4 changes: 2 additions & 2 deletions api/testhelpers/mtls/mtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func generateCA(t *testing.T) (*keys.PrivateKey, *x509.Certificate) {

caPub, caPriv, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
caKey, err := keys.NewPrivateKey(caPriv, nil)
caKey, err := keys.NewPrivateKey(caPriv)
require.NoError(t, err)

// Create a self signed certificate.
Expand Down Expand Up @@ -97,7 +97,7 @@ func generateChildTLSConfigFromCA(t *testing.T, caKey *keys.PrivateKey, caCert *
pub, priv, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)

key, err := keys.NewPrivateKey(priv, nil)
key, err := keys.NewPrivateKey(priv)
require.NoError(t, err)

// Create a certificate signed by the CA.
Expand Down
9 changes: 5 additions & 4 deletions api/types/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
"github.com/gravitational/teleport/api/utils/tlsutils"
)

Expand Down Expand Up @@ -134,7 +135,7 @@ type AuthPreference interface {
// GetHardwareKey returns the hardware key settings configured for the cluster.
GetHardwareKey() (*HardwareKey, error)
// GetPIVSlot returns the configured piv slot for the cluster.
GetPIVSlot() keys.PIVSlot
GetPIVSlot() hardwarekey.PIVSlotKeyString
// GetHardwareKeySerialNumberValidation returns the cluster's hardware key
// serial number validation settings.
GetHardwareKeySerialNumberValidation() (*HardwareKeySerialNumberValidation, error)
Expand Down Expand Up @@ -491,9 +492,9 @@ func (c *AuthPreferenceV2) GetHardwareKey() (*HardwareKey, error) {
}

// GetPIVSlot returns the configured piv slot for the cluster.
func (c *AuthPreferenceV2) GetPIVSlot() keys.PIVSlot {
func (c *AuthPreferenceV2) GetPIVSlot() hardwarekey.PIVSlotKeyString {
if hk, err := c.GetHardwareKey(); err == nil {
return keys.PIVSlot(hk.PIVSlot)
return hardwarekey.PIVSlotKeyString(hk.PIVSlot)
}
return ""
}
Expand Down Expand Up @@ -840,7 +841,7 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
}

if hk, err := c.GetHardwareKey(); err == nil && hk.PIVSlot != "" {
if err := keys.PIVSlot(hk.PIVSlot).Validate(); err != nil {
if err := hardwarekey.PIVSlotKeyString(hk.PIVSlot).Validate(); err != nil {
return trace.Wrap(err)
}
}
Expand Down
50 changes: 50 additions & 0 deletions api/utils/keys/hardwarekey/attestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2025 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 hardwarekey

import (
"bytes"

"github.com/gogo/protobuf/jsonpb"
"github.com/gravitational/trace"

attestationv1 "github.com/gravitational/teleport/api/gen/proto/go/attestation/v1"
)

// AttestationStatement is an attestation statement for a hardware private key
// that supports json marshaling through the standard json/encoding package.
type AttestationStatement attestationv1.AttestationStatement

// ToProto converts this AttestationStatement to its protobuf form.
func (ar *AttestationStatement) ToProto() *attestationv1.AttestationStatement {
return (*attestationv1.AttestationStatement)(ar)
}

// AttestationStatementFromProto converts an AttestationStatement from its protobuf form.
func AttestationStatementFromProto(att *attestationv1.AttestationStatement) *AttestationStatement {
return (*AttestationStatement)(att)
}

// MarshalJSON implements custom protobuf json marshaling.
func (ar *AttestationStatement) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
err := (&jsonpb.Marshaler{}).Marshal(buf, ar.ToProto())
return buf.Bytes(), trace.Wrap(err)
}

// UnmarshalJSON implements custom protobuf json unmarshaling.
func (ar *AttestationStatement) UnmarshalJSON(buf []byte) error {
return jsonpb.Unmarshal(bytes.NewReader(buf), ar.ToProto())
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build piv && !pivtest

// Copyright 2024 Gravitational, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -14,22 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package keys
package hardwarekey

import (
"context"
"fmt"
"os"

"github.com/go-piv/piv-go/piv"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/utils/prompt"
)

type cliPrompt struct{}
var (
// defaultPIN for the PIV applet. The PIN is used to change the Management Key,
// and slots can optionally require it to perform signing operations.
defaultPIN = "123456"
// defaultPUK for the PIV applet. The PUK is only used to reset the PIN when
// the card's PIN retries have been exhausted.
defaultPUK = "12345678"
)

type CLIPrompt struct{}

func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement) (string, error) {
func (c *CLIPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement, _ ContextualKeyInfo) (string, error) {
message := "Enter your YubiKey PIV PIN"
if requirement == PINOptional {
message = "Enter your YubiKey PIV PIN [blank to use default PIN]"
Expand All @@ -38,12 +44,12 @@ func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement
return password, trace.Wrap(err)
}

func (c *cliPrompt) Touch(_ context.Context) error {
func (c *CLIPrompt) Touch(_ context.Context, _ ContextualKeyInfo) error {
_, err := fmt.Fprintln(os.Stderr, "Tap your YubiKey")
return trace.Wrap(err)
}

func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
func (c *CLIPrompt) ChangePIN(ctx context.Context, _ ContextualKeyInfo) (*PINAndPUK, error) {
var pinAndPUK = &PINAndPUK{}
for {
fmt.Fprintf(os.Stderr, "Please set a new 6-8 character PIN.\n")
Expand All @@ -61,8 +67,8 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
continue
}

if newPIN == piv.DefaultPIN {
fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", piv.DefaultPIN)
if newPIN == defaultPIN {
fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", defaultPIN)
continue
}

Expand All @@ -82,8 +88,8 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
pinAndPUK.PUK = puk

switch puk {
case piv.DefaultPUK:
fmt.Fprintf(os.Stderr, "The default PUK %q is not supported.\n", piv.DefaultPUK)
case defaultPUK:
fmt.Fprintf(os.Stderr, "The default PUK %q is not supported.\n", defaultPUK)
fallthrough
case "":
for {
Expand All @@ -102,8 +108,8 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
continue
}

if newPUK == piv.DefaultPUK {
fmt.Fprintf(os.Stderr, "The default PUK %q is not supported.\n", piv.DefaultPUK)
if newPUK == defaultPUK {
fmt.Fprintf(os.Stderr, "The default PUK %q is not supported.\n", defaultPUK)
continue
}

Expand All @@ -120,11 +126,7 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
return pinAndPUK, nil
}

func (c *cliPrompt) ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error) {
func (c *CLIPrompt) ConfirmSlotOverwrite(ctx context.Context, message string, _ ContextualKeyInfo) (bool, error) {
confirmation, err := prompt.Confirmation(ctx, os.Stderr, prompt.Stdin(), message)
return confirmation, trace.Wrap(err)
}

func isPINLengthValid(pin string) bool {
return len(pin) >= 6 && len(pin) <= 8
}
Loading
Loading