From 05bef1b5e93b2566668768a37d22e050578bd0f7 Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Mon, 24 Nov 2025 21:55:31 +0100 Subject: [PATCH] ssh/agent: support smartcard extensions Implements a `SmartcardAgent` interface that allows agents to serve the smartcard add and remove extensions. Fixes: https://github.com/golang/go/issues/16304 Signed-off-by: Morten Linderud --- ssh/agent/client.go | 20 +++++++++++++++++ ssh/agent/server.go | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/ssh/agent/client.go b/ssh/agent/client.go index b357e18b0a..b255ca21b9 100644 --- a/ssh/agent/client.go +++ b/ssh/agent/client.go @@ -89,6 +89,26 @@ type ExtendedAgent interface { Extension(extensionType string, contents []byte) ([]byte, error) } +type AddedSmartcardKey struct { + PIN string + ReaderID string + LifetimeSecs uint32 + ConfirmBeforeUse bool + ConstraintExtensions []ConstraintExtension +} + +type RemovedSmartcardKey struct { + PIN string + ReaderID string +} + +type SmartcardAgent interface { + Agent + + AddSmartcard(key AddedSmartcardKey) error + RemoveSmartcard(key RemovedSmartcardKey) error +} + // ConstraintExtension describes an optional constraint defined by users. type ConstraintExtension struct { // ExtensionName consist of a UTF-8 string suffixed by the diff --git a/ssh/agent/server.go b/ssh/agent/server.go index 4e8ff86b61..028376c0f7 100644 --- a/ssh/agent/server.go +++ b/ssh/agent/server.go @@ -73,6 +73,17 @@ type agentUnlockMsg struct { Passphrase []byte `sshtype:"23"` } +type agentRemoveSmartcardKeyMsg struct { + ReaderID string `sshtype:"21"` + PIN string +} + +type agentAddSmartcardKeyMsg struct { + ReaderID string `sshtype:"20|26"` + PIN string + Constraints []byte `ssh:"rest"` +} + func (s *server) processRequest(data []byte) (interface{}, error) { switch data[0] { case agentRequestV1Identities: @@ -192,7 +203,51 @@ func (s *server) processRequest(data []byte) (interface{}, error) { responseStub.Rest = res } } + return responseStub, nil + case agentAddSmartcardKey, agentAddSmartcardKeyConstrained: + // Return a stub object where the whole contents of the response gets marshaled. + var responseStub struct { + Rest []byte `ssh:"rest"` + } + if scagent, ok := s.agent.(SmartcardAgent); !ok { + responseStub.Rest = []byte{agentFailure} + } else { + var req agentAddSmartcardKeyMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + key := AddedSmartcardKey{ + PIN: req.PIN, + ReaderID: req.ReaderID, + } + lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(req.Constraints) + if err != nil { + return nil, err + } + key.LifetimeSecs = lifetimeSecs + key.ConfirmBeforeUse = confirmBeforeUse + key.ConstraintExtensions = constraintExtensions + return nil, scagent.AddSmartcard(key) + } + return responseStub, nil + case agentRemoveSmartcardKey: + var responseStub struct { + Rest []byte `ssh:"rest"` + } + if scagent, ok := s.agent.(SmartcardAgent); !ok { + responseStub.Rest = []byte{agentFailure} + } else { + var req agentRemoveSmartcardKeyMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + key := RemovedSmartcardKey{ + PIN: req.PIN, + ReaderID: req.ReaderID, + } + return nil, scagent.RemoveSmartcard(key) + } return responseStub, nil }