ssh: make the public key cache a 1-entry FIFO cache · golang/crypto@b4f1988 (original) (raw)

2 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -149,15 +149,21 @@ func (s *ServerConfig) AddHostKey(key Signer) {
149 149 }
150 150
151 151 // cachedPubKey contains the results of querying whether a public key is
152 -// acceptable for a user.
152 +// acceptable for a user. This is a FIFO cache.
153 153 type cachedPubKey struct {
154 154 user string
155 155 pubKeyData []byte
156 156 result error
157 157 perms *Permissions
158 158 }
159 159
160 -const maxCachedPubKeys = 16
160 +// maxCachedPubKeys is the number of cache entries we store.
161 +//
162 +// Due to consistent misuse of the PublicKeyCallback API, we have reduced this
163 +// to 1, such that the only key in the cache is the most recently seen one. This
164 +// forces the behavior that the last call to PublicKeyCallback will always be
165 +// with the key that is used for authentication.
166 +const maxCachedPubKeys = 1
161 167
162 168 // pubKeyCache caches tests for public keys. Since SSH clients
163 169 // will query whether a public key is acceptable before attempting to
@@ -179,9 +185,10 @@ func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
179 185
180 186 // add adds the given tuple to the cache.
181 187 func (c *pubKeyCache) add(candidate cachedPubKey) {
182 -if len(c.keys) < maxCachedPubKeys {
183 -c.keys = append(c.keys, candidate)
188 +if len(c.keys) >= maxCachedPubKeys {
189 +c.keys = c.keys[1:]
184 190 }
191 +c.keys = append(c.keys, candidate)
185 192 }
186 193
187 194 // ServerConn is an authenticated SSH connection, as seen from the
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
5 5 package ssh
6 6
7 7 import (
8 +"bytes"
8 9 "errors"
9 10 "fmt"
10 11 "io"
@@ -299,6 +300,54 @@ func TestBannerError(t *testing.T) {
299 300 }
300 301 }
301 302
303 +func TestPublicKeyCallbackLastSeen(t *testing.T) {
304 +var lastSeenKey PublicKey
305 +
306 +c1, c2, err := netPipe()
307 +if err != nil {
308 +t.Fatalf("netPipe: %v", err)
309 + }
310 +defer c1.Close()
311 +defer c2.Close()
312 +serverConf := &ServerConfig{
313 +PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
314 +lastSeenKey = key
315 +fmt.Printf("seen %#v\n", key)
316 +if _, ok := key.(*dsaPublicKey); !ok {
317 +return nil, errors.New("nope")
318 + }
319 +return nil, nil
320 + },
321 + }
322 +serverConf.AddHostKey(testSigners["ecdsap256"])
323 +
324 +done := make(chan struct{})
325 +go func() {
326 +defer close(done)
327 +NewServerConn(c1, serverConf)
328 + }()
329 +
330 +clientConf := ClientConfig{
331 +User: "user",
332 +Auth: []AuthMethod{
333 +PublicKeys(testSigners["rsa"], testSigners["dsa"], testSigners["ed25519"]),
334 + },
335 +HostKeyCallback: InsecureIgnoreHostKey(),
336 + }
337 +
338 +_, _, _, err = NewClientConn(c2, "", &clientConf)
339 +if err != nil {
340 +t.Fatal(err)
341 + }
342 +<-done
343 +
344 +expectedPublicKey := testSigners["dsa"].PublicKey().Marshal()
345 +lastSeenMarshalled := lastSeenKey.Marshal()
346 +if !bytes.Equal(lastSeenMarshalled, expectedPublicKey) {
347 +t.Errorf("unexpected key: got %#v, want %#v", lastSeenKey, testSigners["dsa"].PublicKey())
348 + }
349 +}
350 +
302 351 type markerConn struct {
303 352 closed uint32
304 353 used uint32