diff --git a/api/utils/keys/hardwarekey/cliprompt.go b/api/utils/keys/hardwarekey/cliprompt.go index 1a21e5bcc9674..64c6dfe2cae5e 100644 --- a/api/utils/keys/hardwarekey/cliprompt.go +++ b/api/utils/keys/hardwarekey/cliprompt.go @@ -58,7 +58,7 @@ func NewCLIPrompt(w io.Writer, r prompt.StdinReader) *cliPrompt { // AskPIN prompts the user for a PIN. If the requirement is [PINOptional], // the prompt will offer the default PIN as a default value. -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]" @@ -68,7 +68,7 @@ func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement } // Touch prompts the user to touch the hardware key. -func (c *cliPrompt) Touch(_ context.Context) error { +func (c *cliPrompt) Touch(_ context.Context, _ ContextualKeyInfo) error { _, err := fmt.Fprintln(c.writer, "Tap your YubiKey") return trace.Wrap(err) } @@ -77,7 +77,7 @@ func (c *cliPrompt) Touch(_ context.Context) error { // If the provided PUK is the default value, it will ask for a new PUK as well. // If an invalid PIN or PUK is provided, the user will be re-prompted until a // valid value is provided. -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(c.writer, "Please set a new 6-8 character PIN.\n") @@ -155,7 +155,7 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) { } // ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden. -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, c.writer, c.reader, message) return confirmation, trace.Wrap(err) } diff --git a/api/utils/keys/hardwarekey/cliprompt_test.go b/api/utils/keys/hardwarekey/cliprompt_test.go index 4c8351ac557c4..e17b5f646a674 100644 --- a/api/utils/keys/hardwarekey/cliprompt_test.go +++ b/api/utils/keys/hardwarekey/cliprompt_test.go @@ -153,7 +153,7 @@ func TestChangePIN(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() - PINAndPUK, err := prompt.ChangePIN(ctx) + PINAndPUK, err := prompt.ChangePIN(ctx, hardwarekey.ContextualKeyInfo{}) require.ErrorIs(t, err, tc.expectError) require.Equal(t, tc.expectPINAndPUK, PINAndPUK) }) diff --git a/api/utils/keys/hardwarekey/hardwarekey.go b/api/utils/keys/hardwarekey/hardwarekey.go index 922fc8f73715f..de446c52ba3cd 100644 --- a/api/utils/keys/hardwarekey/hardwarekey.go +++ b/api/utils/keys/hardwarekey/hardwarekey.go @@ -33,7 +33,7 @@ type Service interface { NewPrivateKey(ctx context.Context, config PrivateKeyConfig) (*Signer, error) // Sign performs a cryptographic signature using the specified hardware // private key and provided signature parameters. - Sign(ctx context.Context, ref *PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) + Sign(ctx context.Context, ref *PrivateKeyRef, keyInfo ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) // TODO(Joerger): DELETE IN v19.0.0 // GetFullKeyRef gets the full [PrivateKeyRef] for an existing hardware private // key in the given slot of the hardware key with the given serial number. @@ -44,13 +44,17 @@ type Service interface { type Signer struct { service Service Ref *PrivateKeyRef + KeyInfo ContextualKeyInfo } // NewSigner returns a [Signer] for the given service and ref. -func NewSigner(s Service, ref *PrivateKeyRef) *Signer { +// keyInfo is an optional argument to supply additional contextual info +// used to add additional context to prompts, e.g. ProxyHost. +func NewSigner(s Service, ref *PrivateKeyRef, keyInfo ContextualKeyInfo) *Signer { return &Signer{ service: s, Ref: ref, + KeyInfo: keyInfo, } } @@ -61,7 +65,7 @@ func (h *Signer) Public() crypto.PublicKey { // Sign implements [crypto.Signer]. func (h *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return h.service.Sign(context.TODO(), h.Ref, rand, digest, opts) + return h.service.Sign(context.TODO(), h.Ref, h.KeyInfo, rand, digest, opts) } // GetAttestation returns the hardware private key attestation details. @@ -88,7 +92,7 @@ func (h *Signer) WarmupHardwareKey(ctx context.Context) error { // We don't actually need to hash the digest, just make it match the hash size. digest := make([]byte, hash.Size()) - _, err := h.service.Sign(ctx, h.Ref, rand.Reader, digest, hash) + _, err := h.service.Sign(ctx, h.Ref, h.KeyInfo, rand.Reader, digest, hash) return trace.Wrap(err, "failed to perform warmup signature with hardware private key") } @@ -98,13 +102,13 @@ func EncodeSigner(p *Signer) ([]byte, error) { } // DecodeSigner decodes an encoded hardware key signer for the given service. -func DecodeSigner(encodedKey []byte, s Service) (*Signer, error) { +func DecodeSigner(encodedKey []byte, s Service, keyInfo ContextualKeyInfo) (*Signer, error) { ref, err := decodeKeyRef(encodedKey, s) if err != nil { return nil, trace.Wrap(err) } - return NewSigner(s, ref), nil + return NewSigner(s, ref, keyInfo), nil } // PrivateKeyRef references a specific hardware private key. @@ -230,4 +234,16 @@ type PrivateKeyConfig struct { // - touch & pin -> 9d // - touch & !pin -> 9e CustomSlot PIVSlotKeyString + // ContextualKeyInfo contains additional info to associate with the key. + ContextualKeyInfo ContextualKeyInfo +} + +// ContextualKeyInfo contains contextual information associated with a hardware [PrivateKey]. +type ContextualKeyInfo struct { + // ProxyHost is the root proxy hostname that the key is associated with. + ProxyHost string + // Username is a Teleport username that the key is associated with. + Username string + // ClusterName is a Teleport cluster name that the key is associated with. + ClusterName string } diff --git a/api/utils/keys/hardwarekey/hardwarekey_test.go b/api/utils/keys/hardwarekey/hardwarekey_test.go index c4b29c4282a64..0e71a3bb16529 100644 --- a/api/utils/keys/hardwarekey/hardwarekey_test.go +++ b/api/utils/keys/hardwarekey/hardwarekey_test.go @@ -42,16 +42,23 @@ func TestPrivateKey_EncodeDecode(t *testing.T) { ctx := context.Background() s := hardwarekey.NewMockHardwareKeyService(nil /*prompt*/) + + contextualKeyInfo := hardwarekey.ContextualKeyInfo{ + ProxyHost: "billy.io", + Username: "Billy@billy.io", + ClusterName: "billy.io", + } + hwSigner, err := s.NewPrivateKey(ctx, hardwarekey.PrivateKeyConfig{ - Policy: hardwarekey.PromptPolicyNone, + Policy: hardwarekey.PromptPolicyNone, + ContextualKeyInfo: contextualKeyInfo, }) require.NoError(t, err) - priv := hardwarekey.NewSigner(s, hwSigner.Ref) - encoded, err := hardwarekey.EncodeSigner(priv) + encoded, err := hardwarekey.EncodeSigner(hwSigner) require.NoError(t, err) - decodedSigner, err := hardwarekey.DecodeSigner(encoded, s) + decodedSigner, err := hardwarekey.DecodeSigner(encoded, s, contextualKeyInfo) require.NoError(t, err) require.Equal(t, hwSigner, decodedSigner) } @@ -74,7 +81,7 @@ func TestPrivateKey_DecodePartialKeyRef(t *testing.T) { }) require.NoError(t, err) - decodedSigner, err := hardwarekey.DecodeSigner(partialKeyRefJSON, s) + decodedSigner, err := hardwarekey.DecodeSigner(partialKeyRefJSON, s, hardwarekey.ContextualKeyInfo{}) require.NoError(t, err) require.Equal(t, hwSigner, decodedSigner) } diff --git a/api/utils/keys/hardwarekey/prompt.go b/api/utils/keys/hardwarekey/prompt.go index 991a38c03097f..7dd288d8d9e8a 100644 --- a/api/utils/keys/hardwarekey/prompt.go +++ b/api/utils/keys/hardwarekey/prompt.go @@ -43,17 +43,17 @@ type PromptPolicy struct { type Prompt interface { // AskPIN prompts the user for a PIN. // The requirement tells if the PIN is required or optional. - AskPIN(ctx context.Context, requirement PINPromptRequirement) (string, error) + AskPIN(ctx context.Context, requirement PINPromptRequirement, keyInfo ContextualKeyInfo) (string, error) // Touch prompts the user to touch the hardware key. - Touch(ctx context.Context) error + Touch(ctx context.Context, keyInfo ContextualKeyInfo) error // ChangePIN asks for a new PIN. // If the PUK has a default value, it should ask for the new value for it. // It is up to the implementer how the validation is handled. // For example, CLI prompt can ask for a valid PIN/PUK in a loop, a GUI // prompt can use the frontend validation. - ChangePIN(ctx context.Context) (*PINAndPUK, error) + ChangePIN(ctx context.Context, keyInfo ContextualKeyInfo) (*PINAndPUK, error) // ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden. - ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error) + ConfirmSlotOverwrite(ctx context.Context, message string, keyInfo ContextualKeyInfo) (bool, error) } // PINPromptRequirement specifies whether a PIN is required. diff --git a/api/utils/keys/hardwarekey/service_mock.go b/api/utils/keys/hardwarekey/service_mock.go index a4e07fbfbfcda..ea9a3268c18bb 100644 --- a/api/utils/keys/hardwarekey/service_mock.go +++ b/api/utils/keys/hardwarekey/service_mock.go @@ -82,11 +82,11 @@ func (s *MockHardwareKeyService) NewPrivateKey(ctx context.Context, config Priva } if priv, ok := s.fakeHardwarePrivateKeys[keySlot]; ok { - return NewSigner(s, priv.ref), nil + return NewSigner(s, priv.ref, config.ContextualKeyInfo), nil } // generating a new key with PIN/touch requirements requires the corresponding prompt. - if err := s.tryPrompt(ctx, config.Policy); err != nil { + if err := s.tryPrompt(ctx, config.Policy, config.ContextualKeyInfo); err != nil { return nil, trace.Wrap(err) } @@ -115,12 +115,12 @@ func (s *MockHardwareKeyService) NewPrivateKey(ctx context.Context, config Priva ref: ref, } - return NewSigner(s, ref), nil + return NewSigner(s, ref, config.ContextualKeyInfo), nil } // Sign performs a cryptographic signature using the specified hardware // private key and provided signature parameters. -func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { +func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, keyInfo ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { s.fakeHardwarePrivateKeysMux.Lock() defer s.fakeHardwarePrivateKeysMux.Unlock() @@ -132,14 +132,14 @@ func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, r return nil, trace.NotFound("key not found in slot %d", ref.SlotKey) } - if err := s.tryPrompt(ctx, ref.Policy); err != nil { + if err := s.tryPrompt(ctx, ref.Policy, keyInfo); err != nil { return nil, trace.Wrap(err) } return priv.Sign(rand, digest, opts) } -func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPolicy) error { +func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPolicy, keyInfo ContextualKeyInfo) error { s.promptMu.Lock() defer s.promptMu.Unlock() @@ -150,14 +150,14 @@ func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPol if policy.PINRequired { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() - if _, err := s.prompt.AskPIN(ctx, PINRequired); err != nil { + if _, err := s.prompt.AskPIN(ctx, PINRequired, keyInfo); err != nil { return trace.Wrap(err, "failed to handle pin prompt") } // We don't actually check the PIN for the current tests, any input is sufficient to unblock the prompt. } if policy.TouchRequired { - if err := s.prompt.Touch(ctx); err != nil { + if err := s.prompt.Touch(ctx, keyInfo); err != nil { return trace.Wrap(err) } select { diff --git a/api/utils/keys/piv/service.go b/api/utils/keys/piv/service.go index 419c26fe85bd7..efbdb174cc25e 100644 --- a/api/utils/keys/piv/service.go +++ b/api/utils/keys/piv/service.go @@ -101,7 +101,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P // If PIN is required, check that PIN and PUK are not the defaults. if config.Policy.PINRequired { - if err := s.checkOrSetPIN(ctx, y); err != nil { + if err := s.checkOrSetPIN(ctx, y, config.ContextualKeyInfo); err != nil { return nil, trace.Wrap(err) } } @@ -111,7 +111,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P if err != nil { return nil, trace.Wrap(err) } - return hardwarekey.NewSigner(s, ref), nil + return hardwarekey.NewSigner(s, ref, config.ContextualKeyInfo), nil } // If a custom slot was not specified, check for a key in the @@ -127,7 +127,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P // Unknown cert found, this slot could be in use by a non-teleport client. // Prompt the user before we overwrite the slot. case len(cert.Subject.Organization) == 0 || cert.Subject.Organization[0] != certOrgName: - if err := s.promptOverwriteSlot(ctx, nonTeleportCertificateMessage(pivSlot, cert)); err != nil { + if err := s.promptOverwriteSlot(ctx, nonTeleportCertificateMessage(pivSlot, cert), config.ContextualKeyInfo); err != nil { return nil, trace.Wrap(err) } return generatePrivateKey() @@ -146,25 +146,25 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P case config.Policy.TouchRequired && !keyRef.Policy.TouchRequired: msg := fmt.Sprintf("private key in YubiKey PIV slot %q does not require touch.", pivSlot) - if err := s.promptOverwriteSlot(ctx, msg); err != nil { + if err := s.promptOverwriteSlot(ctx, msg, config.ContextualKeyInfo); err != nil { return nil, trace.Wrap(err) } return generatePrivateKey() case config.Policy.PINRequired && !keyRef.Policy.PINRequired: msg := fmt.Sprintf("private key in YubiKey PIV slot %q does not require PIN", pivSlot) - if err := s.promptOverwriteSlot(ctx, msg); err != nil { + if err := s.promptOverwriteSlot(ctx, msg, config.ContextualKeyInfo); err != nil { return nil, trace.Wrap(err) } return generatePrivateKey() } - return hardwarekey.NewSigner(s, keyRef), nil + return hardwarekey.NewSigner(s, keyRef, config.ContextualKeyInfo), nil } // Sign performs a cryptographic signature using the specified hardware // private key and provided signature parameters. -func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { +func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, keyInfo hardwarekey.ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { y, err := s.getYubiKey(ref.SerialNumber) if err != nil { return nil, trace.Wrap(err) @@ -173,7 +173,7 @@ func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRe promptMux.Lock() defer promptMux.Unlock() - return y.sign(ctx, ref, s.prompt, rand, digest, opts) + return y.sign(ctx, ref, keyInfo, s.prompt, rand, digest, opts) } // TODO(Joerger): Re-attesting the key every time we decode a hardware key signer is very resource @@ -246,11 +246,11 @@ func (s *YubiKeyService) getYubiKey(serialNumber uint32) (*YubiKey, error) { // checkOrSetPIN prompts the user for PIN and verifies it with the YubiKey. // If the user provides the default PIN, they will be prompted to set a // non-default PIN and PUK before continuing. -func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error { +func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey, keyInfo hardwarekey.ContextualKeyInfo) error { promptMux.Lock() defer promptMux.Unlock() - pin, err := s.prompt.AskPIN(ctx, hardwarekey.PINOptional) + pin, err := s.prompt.AskPIN(ctx, hardwarekey.PINOptional, keyInfo) if err != nil { return trace.Wrap(err) } @@ -260,7 +260,7 @@ func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error { fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", piv.DefaultPIN) fallthrough case "": - pin, err = y.setPINAndPUKFromDefault(ctx, s.prompt) + pin, err = y.setPINAndPUKFromDefault(ctx, s.prompt, keyInfo) if err != nil { return trace.Wrap(err) } @@ -269,12 +269,12 @@ func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error { return trace.Wrap(y.verifyPIN(pin)) } -func (s *YubiKeyService) promptOverwriteSlot(ctx context.Context, msg string) error { +func (s *YubiKeyService) promptOverwriteSlot(ctx context.Context, msg string, keyInfo hardwarekey.ContextualKeyInfo) error { promptMux.Lock() defer promptMux.Unlock() promptQuestion := fmt.Sprintf("%v\nWould you like to overwrite this slot's private key and certificate?", msg) - if confirmed, confirmErr := s.prompt.ConfirmSlotOverwrite(ctx, promptQuestion); confirmErr != nil { + if confirmed, confirmErr := s.prompt.ConfirmSlotOverwrite(ctx, promptQuestion, keyInfo); confirmErr != nil { return trace.Wrap(confirmErr) } else if !confirmed { return trace.Wrap(trace.CompareFailed(msg), "user declined to overwrite slot") diff --git a/api/utils/keys/piv/service_unavailable.go b/api/utils/keys/piv/service_unavailable.go index 8cdbeb428f213..24918a072690c 100644 --- a/api/utils/keys/piv/service_unavailable.go +++ b/api/utils/keys/piv/service_unavailable.go @@ -41,7 +41,7 @@ func (s *unavailableYubiKeyPIVService) NewPrivateKey(_ context.Context, _ hardwa // Sign performs a cryptographic signature using the specified hardware // private key and provided signature parameters. -func (s *unavailableYubiKeyPIVService) Sign(_ context.Context, _ *hardwarekey.PrivateKeyRef, _ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) { +func (s *unavailableYubiKeyPIVService) Sign(_ context.Context, _ *hardwarekey.PrivateKeyRef, _ hardwarekey.ContextualKeyInfo, _ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) { return nil, trace.Wrap(errPIVUnavailable) } diff --git a/api/utils/keys/piv/yubikey.go b/api/utils/keys/piv/yubikey.go index df1d0685d9f67..bee6f3f86e66e 100644 --- a/api/utils/keys/piv/yubikey.go +++ b/api/utils/keys/piv/yubikey.go @@ -140,7 +140,7 @@ const ( signTouchPromptDelay = time.Millisecond * 200 ) -func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prompt hardwarekey.Prompt, rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { +func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, keyInfo hardwarekey.ContextualKeyInfo, prompt hardwarekey.Prompt, rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { ctx, cancel := context.WithCancelCause(ctx) defer cancel(nil) @@ -163,7 +163,7 @@ func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prom select { case <-touchPromptDelayTimer.C: // Prompt for touch after a delay, in case the function succeeds without touch due to a cached touch. - err := prompt.Touch(ctx) + err := prompt.Touch(ctx, keyInfo) if err != nil { // Cancel the entire function when an error occurs. // This is typically used for aborting the prompt. @@ -185,7 +185,7 @@ func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prom defer touchPromptDelayTimer.Reset(signTouchPromptDelay) } } - pass, err := prompt.AskPIN(ctx, hardwarekey.PINRequired) + pass, err := prompt.AskPIN(ctx, hardwarekey.PINRequired, keyInfo) return pass, trace.Wrap(err) } @@ -399,8 +399,8 @@ func (y *YubiKey) SetPIN(oldPin, newPin string) error { return trace.Wrap(err) } -func (y *YubiKey) setPINAndPUKFromDefault(ctx context.Context, prompt hardwarekey.Prompt) (string, error) { - pinAndPUK, err := prompt.ChangePIN(ctx) +func (y *YubiKey) setPINAndPUKFromDefault(ctx context.Context, prompt hardwarekey.Prompt, keyInfo hardwarekey.ContextualKeyInfo) (string, error) { + pinAndPUK, err := prompt.ChangePIN(ctx, keyInfo) if err != nil { return "", trace.Wrap(err) } diff --git a/api/utils/keys/privatekey.go b/api/utils/keys/privatekey.go index 1188e83cb7b1f..e2129b8cd44ce 100644 --- a/api/utils/keys/privatekey.go +++ b/api/utils/keys/privatekey.go @@ -253,6 +253,8 @@ func LoadPrivateKey(keyFile string) (*PrivateKey, error) { type ParsePrivateKeyOptions struct { // HardwareKeyService is the hardware key service to use with parsed hardware private keys. HardwareKeyService hardwarekey.Service + // ContextualKeyInfo is contextual information associated with the key. + ContextualKeyInfo hardwarekey.ContextualKeyInfo } // ParsePrivateKeyOpt applies configuration options. @@ -265,6 +267,13 @@ func WithHardwareKeyService(hwKeyService hardwarekey.Service) ParsePrivateKeyOpt } } +// WithContextualKeyInfo adds contextual key info to the parsed private key. +func WithContextualKeyInfo(info hardwarekey.ContextualKeyInfo) ParsePrivateKeyOpt { + return func(o *ParsePrivateKeyOptions) { + o.ContextualKeyInfo = info + } +} + // ParsePrivateKey returns the PrivateKey for the given key PEM block. // Allows passing a custom hardware key prompt. func ParsePrivateKey(keyPEM []byte, opts ...ParsePrivateKeyOpt) (*PrivateKey, error) { @@ -296,7 +305,7 @@ func ParsePrivateKey(keyPEM []byte, opts ...ParsePrivateKeyOpt) (*PrivateKey, er hwks = piv.NewYubiKeyService(nil /*prompt*/) } - hwSigner, err := hardwarekey.DecodeSigner(block.Bytes, hwks) + hwSigner, err := hardwarekey.DecodeSigner(block.Bytes, hwks, appliedOpts.ContextualKeyInfo) if err != nil { return nil, trace.Wrap(err, "failed to parse hardware key signer") } diff --git a/api/utils/keys/privatekey_test.go b/api/utils/keys/privatekey_test.go index 3a0fbbe30693f..4be10d55e6d51 100644 --- a/api/utils/keys/privatekey_test.go +++ b/api/utils/keys/privatekey_test.go @@ -49,8 +49,15 @@ func TestMarshalAndParseKey(t *testing.T) { _, edKey, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) + contextualKeyInfo := hardwarekey.ContextualKeyInfo{ + ProxyHost: "billy.io", + Username: "Billy@billy.io", + ClusterName: "billy.io", + } s := hardwarekey.NewMockHardwareKeyService(nil /*prompt*/) - hwPriv, err := s.NewPrivateKey(context.TODO(), hardwarekey.PrivateKeyConfig{}) + hwPriv, err := s.NewPrivateKey(context.TODO(), hardwarekey.PrivateKeyConfig{ + ContextualKeyInfo: contextualKeyInfo, + }) require.NoError(t, err) for keyType, key := range map[string]crypto.Signer{ @@ -62,7 +69,7 @@ func TestMarshalAndParseKey(t *testing.T) { t.Run(keyType, func(t *testing.T) { keyPEM, err := keys.MarshalPrivateKey(key) require.NoError(t, err) - gotKey, err := keys.ParsePrivateKey(keyPEM, keys.WithHardwareKeyService(s)) + gotKey, err := keys.ParsePrivateKey(keyPEM, keys.WithHardwareKeyService(s), keys.WithContextualKeyInfo(contextualKeyInfo)) require.NoError(t, err) assert.Empty(t, cmp.Diff(key, gotKey.Signer, cmpopts.IgnoreUnexported(hardwarekey.Signer{})), "parsed private key is not equal to the original") diff --git a/integration/proxy/teleterm_test.go b/integration/proxy/teleterm_test.go index 0d3d622501e7d..4deec70fe6986 100644 --- a/integration/proxy/teleterm_test.go +++ b/integration/proxy/teleterm_test.go @@ -40,7 +40,6 @@ import ( "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/api/utils/keys/hardwarekey" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" "github.com/gravitational/teleport/integration/appaccess" dbhelpers "github.com/gravitational/teleport/integration/db" @@ -248,9 +247,6 @@ func testGatewayCertRenewal(ctx context.Context, t *testing.T, params gatewayCer // db cert has expired. Clock: fakeClock, WebauthnLogin: webauthnLogin, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -882,9 +878,6 @@ func testTeletermAppGatewayTargetPortValidation(t *testing.T, pack *appaccess.Pa storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) daemonService, err := daemon.New(daemon.Config{ diff --git a/integration/teleterm_test.go b/integration/teleterm_test.go index 0c376350d964c..25c25507829fc 100644 --- a/integration/teleterm_test.go +++ b/integration/teleterm_test.go @@ -43,7 +43,6 @@ import ( "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils" - "github.com/gravitational/teleport/api/utils/keys/hardwarekey" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" dbhelpers "github.com/gravitational/teleport/integration/db" "github.com/gravitational/teleport/integration/helpers" @@ -258,9 +257,6 @@ func testAddingRootCluster(t *testing.T, pack *dbhelpers.DatabasePack, creds *he storage, err := clusters.NewStorage(clusters.Config{ Dir: t.TempDir(), InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -293,9 +289,6 @@ func testListRootClustersReturnsLoggedInUser(t *testing.T, pack *dbhelpers.Datab storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -378,9 +371,6 @@ func testGetClusterReturnsPropertiesFromAuthServer(t *testing.T, pack *dbhelpers storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -433,9 +423,6 @@ func testHeadlessWatcher(t *testing.T, pack *dbhelpers.DatabasePack, creds *help storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -504,9 +491,6 @@ func testClientCache(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers. Dir: tc.KeysDir, Clock: storageFakeClock, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -766,9 +750,6 @@ func testCreateConnectMyComputerRole(t *testing.T, pack *dbhelpers.DatabasePack) storage, err := clusters.NewStorage(clusters.Config{ Dir: t.TempDir(), InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -885,9 +866,6 @@ func testCreateConnectMyComputerToken(t *testing.T, pack *dbhelpers.DatabasePack InsecureSkipVerify: tc.InsecureSkipVerify, Clock: fakeClock, WebauthnLogin: webauthnLogin, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -948,9 +926,6 @@ func testWaitForConnectMyComputerNodeJoin(t *testing.T, pack *dbhelpers.Database storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -1035,9 +1010,6 @@ func testDeleteConnectMyComputerNode(t *testing.T, pack *dbhelpers.DatabasePack) storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -1265,9 +1237,6 @@ func testListDatabaseUsers(t *testing.T, pack *dbhelpers.DatabasePack) { storage, err := clusters.NewStorage(clusters.Config{ Dir: tc.KeysDir, InsecureSkipVerify: tc.InsecureSkipVerify, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) diff --git a/lib/client/api.go b/lib/client/api.go index 60b91e4fdfb25..f333f3e047cf0 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -4007,6 +4007,11 @@ func (tc *TeleportClient) GetNewLoginKeyRing(ctx context.Context) (keyRing *KeyR priv, err := tc.ClientStore.NewHardwarePrivateKey(ctx, hardwarekey.PrivateKeyConfig{ Policy: tc.PrivateKeyPolicy.GetPromptPolicy(), CustomSlot: tc.PIVSlot, + ContextualKeyInfo: hardwarekey.ContextualKeyInfo{ + ProxyHost: tc.WebProxyHost(), + Username: tc.Username, + ClusterName: tc.SiteName, + }, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/client/client_store_test.go b/lib/client/client_store_test.go index 77ddd7abdca22..62cab08f1d8b0 100644 --- a/lib/client/client_store_test.go +++ b/lib/client/client_store_test.go @@ -192,7 +192,10 @@ func TestClientStore(t *testing.T) { // create a test software and hardware key. idx := KeyRingIndex{"test.proxy.com", "test-user", "root"} softKeyRing := a.makeSignedKeyRing(t, idx, false) - hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{}) + keyInfo := idx.contextualKeyInfo() + hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{ + ContextualKeyInfo: keyInfo, + }) require.NoError(t, err) hardKeyRing := NewKeyRing(hwPriv, hwPriv) hardKeyRing.KeyRingIndex = idx @@ -230,6 +233,22 @@ func TestClientStore(t *testing.T) { require.NoError(t, err) assertEqualKeyRings(t, keyRing, retrievedKeyRing) + // Get the key, now without cluster name. It should retrieve the key without certs + // and without a cluster name in the KeyRingIndex or ContextualKeyInfo (hardware keys). + retrievedKeyRing, err = clientStore.GetKeyRing(KeyRingIndex{idx.ProxyHost, idx.Username, ""}) + require.NoError(t, err) + expectKeyRing = keyRing.Copy() + expectKeyRing.ClusterName = "" + expectKeyRing.Cert = nil + expectKeyRing.DBTLSCredentials = make(map[string]TLSCredential) + if hwPriv, ok := expectKeyRing.TLSPrivateKey.Signer.(*hardwarekey.Signer); ok { + hwPriv.KeyInfo.ClusterName = "" + } + if hwPriv, ok := expectKeyRing.SSHPrivateKey.Signer.(*hardwarekey.Signer); ok { + hwPriv.KeyInfo.ClusterName = "" + } + assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing) + var profileDir string if fs, ok := clientStore.KeyStore.(*FSKeyStore); ok { profileDir = fs.KeyDir diff --git a/lib/client/interfaces.go b/lib/client/interfaces.go index f2e207b1717ed..2e0d6256aab57 100644 --- a/lib/client/interfaces.go +++ b/lib/client/interfaces.go @@ -40,6 +40,7 @@ import ( "github.com/gravitational/teleport/api/constants" apiutils "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/sshutils" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/cryptosuites" @@ -90,6 +91,14 @@ func (idx KeyRingIndex) LogValue() slog.Value { ) } +func (idx KeyRingIndex) contextualKeyInfo() hardwarekey.ContextualKeyInfo { + return hardwarekey.ContextualKeyInfo{ + ProxyHost: idx.ProxyHost, + Username: idx.Username, + ClusterName: idx.ClusterName, + } +} + // TLSCredential holds a signed TLS certificate and matching private key. type TLSCredential struct { // PrivateKey is the private key of the credential. diff --git a/lib/client/keystore.go b/lib/client/keystore.go index 7973d4b02c528..d055dffabd6de 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -540,7 +540,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt return nil, trace.Wrap(err, "no session keys for %+v", idx) } - tlsCred, err := readTLSCredential(fs.userTLSKeyPath(idx), fs.tlsCertPath(idx), keys.WithHardwareKeyService(hwks)) + tlsCred, err := readTLSCredential(fs.userTLSKeyPath(idx), fs.tlsCertPath(idx), keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(idx.contextualKeyInfo())) if err != nil { if trace.IsNotFound(err) { if _, statErr := os.Stat(fs.tlsCertPathLegacy(idx)); statErr == nil { @@ -551,7 +551,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt return nil, trace.Wrap(err) } - sshPriv, err := keys.LoadKeyPair(fs.userSSHKeyPath(idx), fs.publicKeyPath(idx), keys.WithHardwareKeyService(hwks)) + sshPriv, err := keys.LoadKeyPair(fs.userSSHKeyPath(idx), fs.publicKeyPath(idx), keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(idx.contextualKeyInfo())) if err != nil { return nil, trace.ConvertSystemError(err) } @@ -574,7 +574,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt } func (fs *FSKeyStore) updateKeyRingWithCerts(o CertOption, hwks hardwarekey.Service, keyRing *KeyRing) error { - return o.updateKeyRing(fs.KeyDir, keyRing.KeyRingIndex, keyRing, keys.WithHardwareKeyService(hwks)) + return o.updateKeyRing(fs.KeyDir, keyRing.KeyRingIndex, keyRing, keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(keyRing.contextualKeyInfo())) } // GetSSHCertificates gets all certificates signed for the given user and proxy. diff --git a/lib/client/keystore_test.go b/lib/client/keystore_test.go index dc8bc57665720..b891fa9859041 100644 --- a/lib/client/keystore_test.go +++ b/lib/client/keystore_test.go @@ -61,7 +61,9 @@ func TestKeyStore(t *testing.T) { // create a test software and hardware key. idx := KeyRingIndex{"test.proxy.com", "test-user", "root"} softKeyRing := s.makeSignedKeyRing(t, idx, false) - hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{}) + hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{ + ContextualKeyInfo: idx.contextualKeyInfo(), + }) require.NoError(t, err) hardKeyRing := NewKeyRing(hwPriv, hwPriv) hardKeyRing.KeyRingIndex = idx @@ -98,11 +100,18 @@ func TestKeyStore(t *testing.T) { expectKeyRing.DBTLSCredentials = make(map[string]TLSCredential) assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing) - // check for the key, now without cluster name + // Get the key, now without cluster name. It should retrieve the key without certs + // and without a cluster name in the KeyRingIndex or ContextualKeyInfo (hardware keys). retrievedKeyRing, err = keyStore.GetKeyRing(KeyRingIndex{idx.ProxyHost, idx.Username, ""}, hwks) require.NoError(t, err) expectKeyRing.ClusterName = "" expectKeyRing.Cert = nil + if hwPriv, ok := expectKeyRing.TLSPrivateKey.Signer.(*hardwarekey.Signer); ok { + hwPriv.KeyInfo.ClusterName = "" + } + if hwPriv, ok := expectKeyRing.SSHPrivateKey.Signer.(*hardwarekey.Signer); ok { + hwPriv.KeyInfo.ClusterName = "" + } assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing) // delete the key diff --git a/lib/teleterm/clusters/config.go b/lib/teleterm/clusters/config.go index 6c5b5ee02e4e4..d1410b0eb9890 100644 --- a/lib/teleterm/clusters/config.go +++ b/lib/teleterm/clusters/config.go @@ -27,7 +27,6 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/utils/keys/hardwarekey" "github.com/gravitational/teleport/lib/client" - "github.com/gravitational/teleport/lib/teleterm/api/uri" ) // Config is the cluster service config @@ -47,7 +46,7 @@ type Config struct { AddKeysToAgent string // CustomHardwareKeyPrompt is a custom hardware key prompt to use when asking // for a hardware key PIN, touch, etc. - HardwareKeyPromptConstructor func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt + CustomHardwareKeyPrompt hardwarekey.Prompt } // CheckAndSetDefaults checks the configuration for its validity and sets default values if needed @@ -56,10 +55,6 @@ func (c *Config) CheckAndSetDefaults() error { return trace.BadParameter("missing working directory") } - if c.HardwareKeyPromptConstructor == nil { - return trace.BadParameter("missing hardware key prompt constructor") - } - if c.Clock == nil { c.Clock = clockwork.NewRealClock() } diff --git a/lib/teleterm/clusters/storage.go b/lib/teleterm/clusters/storage.go index a546e08163469..25f0796875778 100644 --- a/lib/teleterm/clusters/storage.go +++ b/lib/teleterm/clusters/storage.go @@ -286,11 +286,7 @@ func (s *Storage) makeDefaultClientConfig(rootClusterURI uri.ResourceURI) *clien cfg.InsecureSkipVerify = s.InsecureSkipVerify cfg.AddKeysToAgent = s.AddKeysToAgent cfg.WebauthnLogin = s.WebauthnLogin - // TODO(Joerger): Remove the rootClusterURI dependency from the teleterm prompt so that - // the storage service can share a single hardware key service+prompt. This allows the - // process to properly share PIV connections, prevents duplicate prompts, and enables - // PIN caching across clusters (if both clusters allow PIN caching). - cfg.CustomHardwareKeyPrompt = s.HardwareKeyPromptConstructor(rootClusterURI) + cfg.CustomHardwareKeyPrompt = s.CustomHardwareKeyPrompt cfg.DTAuthnRunCeremony = dtauthn.NewCeremony().Run cfg.DTAutoEnroll = dtenroll.AutoEnroll return cfg diff --git a/lib/teleterm/daemon/daemon_test.go b/lib/teleterm/daemon/daemon_test.go index e9f9daffdc728..725b224574aa9 100644 --- a/lib/teleterm/daemon/daemon_test.go +++ b/lib/teleterm/daemon/daemon_test.go @@ -40,7 +40,6 @@ import ( "google.golang.org/grpc/status" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/api/utils/keys/hardwarekey" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/client/clientcache" @@ -348,9 +347,6 @@ func TestUpdateTshdEventsServerAddress(t *testing.T) { storage, err := clusters.NewStorage(clusters.Config{ Dir: homeDir, InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -385,9 +381,6 @@ func TestUpdateTshdEventsServerAddress_CredsErr(t *testing.T) { storage, err := clusters.NewStorage(clusters.Config{ Dir: homeDir, InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -489,9 +482,6 @@ func TestRetryWithRelogin(t *testing.T) { storage, err := clusters.NewStorage(clusters.Config{ Dir: t.TempDir(), InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) @@ -545,9 +535,6 @@ func TestConcurrentHeadlessAuthPrompts(t *testing.T) { storage, err := clusters.NewStorage(clusters.Config{ Dir: t.TempDir(), InsecureSkipVerify: true, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return nil - }, }) require.NoError(t, err) diff --git a/lib/teleterm/daemon/hardwarekeyprompt.go b/lib/teleterm/daemon/hardwarekeyprompt.go index 6a70b25f7659e..202ed86c1318c 100644 --- a/lib/teleterm/daemon/hardwarekeyprompt.go +++ b/lib/teleterm/daemon/hardwarekeyprompt.go @@ -25,11 +25,9 @@ import ( "github.com/gravitational/teleport/api/utils/keys/hardwarekey" api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" - "github.com/gravitational/teleport/lib/teleterm/api/uri" ) -// NewHardwareKeyPromptConstructor returns a new hardware key prompt constructor -// for this service and the given root cluster URI. +// NewHardwareKeyPrompt returns a new hardware key prompt. // // TODO(gzdunek): Improve multi-cluster and multi-hardware keys support. // The code in yubikey.go doesn't really support using multiple hardware keys (like one per cluster): @@ -46,19 +44,18 @@ import ( // Because the code in yubikey.go assumes you use a single key, we don't have any mutex here. // (unlike other modals triggered by tshd). // We don't expect receiving prompts from different hardware keys. -func (s *Service) NewHardwareKeyPromptConstructor(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return &hardwareKeyPrompter{s: s, rootClusterURI: rootClusterURI} +func (s *Service) NewHardwareKeyPrompt() hardwarekey.Prompt { + return &hardwareKeyPrompter{s: s} } type hardwareKeyPrompter struct { - s *Service - rootClusterURI uri.ResourceURI + s *Service } // Touch prompts the user to touch the hardware key. -func (h *hardwareKeyPrompter) Touch(ctx context.Context) error { +func (h *hardwareKeyPrompter) Touch(ctx context.Context, keyInfo hardwarekey.ContextualKeyInfo) error { _, err := h.s.tshdEventsClient.PromptHardwareKeyTouch(ctx, &api.PromptHardwareKeyTouchRequest{ - RootClusterUri: h.rootClusterURI.String(), + RootClusterUri: keyInfo.ProxyHost, }) if err != nil { return trace.Wrap(err) @@ -67,9 +64,9 @@ func (h *hardwareKeyPrompter) Touch(ctx context.Context) error { } // AskPIN prompts the user for a PIN. -func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwarekey.PINPromptRequirement) (string, error) { +func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwarekey.PINPromptRequirement, keyInfo hardwarekey.ContextualKeyInfo) (string, error) { res, err := h.s.tshdEventsClient.PromptHardwareKeyPIN(ctx, &api.PromptHardwareKeyPINRequest{ - RootClusterUri: h.rootClusterURI.String(), + RootClusterUri: keyInfo.ProxyHost, PinOptional: requirement == hardwarekey.PINOptional, }) if err != nil { @@ -81,9 +78,9 @@ func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwareke // ChangePIN asks for a new PIN. // The Electron app prompt must handle default values for PIN and PUK, // preventing the user from submitting empty/default values. -func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context) (*hardwarekey.PINAndPUK, error) { +func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context, keyInfo hardwarekey.ContextualKeyInfo) (*hardwarekey.PINAndPUK, error) { res, err := h.s.tshdEventsClient.PromptHardwareKeyPINChange(ctx, &api.PromptHardwareKeyPINChangeRequest{ - RootClusterUri: h.rootClusterURI.String(), + RootClusterUri: keyInfo.ProxyHost, }) if err != nil { return nil, trace.Wrap(err) @@ -96,9 +93,9 @@ func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context) (*hardwarekey.PINAn } // ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden. -func (h *hardwareKeyPrompter) ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error) { +func (h *hardwareKeyPrompter) ConfirmSlotOverwrite(ctx context.Context, message string, keyInfo hardwarekey.ContextualKeyInfo) (bool, error) { res, err := h.s.tshdEventsClient.ConfirmHardwareKeySlotOverwrite(ctx, &api.ConfirmHardwareKeySlotOverwriteRequest{ - RootClusterUri: h.rootClusterURI.String(), + RootClusterUri: keyInfo.ProxyHost, Message: message, }) if err != nil { diff --git a/lib/teleterm/teleterm.go b/lib/teleterm/teleterm.go index 8c6b2a897f3e5..e721285906a45 100644 --- a/lib/teleterm/teleterm.go +++ b/lib/teleterm/teleterm.go @@ -32,8 +32,6 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/gravitational/teleport/api/utils/keys/hardwarekey" - "github.com/gravitational/teleport/lib/teleterm/api/uri" "github.com/gravitational/teleport/lib/teleterm/apiserver" "github.com/gravitational/teleport/lib/teleterm/clusteridcache" "github.com/gravitational/teleport/lib/teleterm/clusters" @@ -42,7 +40,6 @@ import ( // Serve starts daemon service func Serve(ctx context.Context, cfg Config) error { - var hardwareKeyPromptConstructor func(clusterURI uri.ResourceURI) hardwarekey.Prompt if err := cfg.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } @@ -59,9 +56,6 @@ func Serve(ctx context.Context, cfg Config) error { Clock: clock, InsecureSkipVerify: cfg.InsecureSkipVerify, AddKeysToAgent: cfg.AddKeysToAgent, - HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt { - return hardwareKeyPromptConstructor(rootClusterURI) - }, }) if err != nil { return trace.Wrap(err) @@ -83,7 +77,8 @@ func Serve(ctx context.Context, cfg Config) error { // TODO(gzdunek): Move tshdEventsClient out of daemonService so that we can // construct the prompt before creating Storage. - hardwareKeyPromptConstructor = daemonService.NewHardwareKeyPromptConstructor + storage.CustomHardwareKeyPrompt = daemonService.NewHardwareKeyPrompt() + apiServer, err := apiserver.New(apiserver.Config{ HostAddr: cfg.Addr, InsecureSkipVerify: cfg.InsecureSkipVerify,