1// Copyright 2012 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package agent implements the ssh-agent protocol, and provides both 6// a client and a server. The client can talk to a standard ssh-agent 7// that uses UNIX sockets, and one could implement an alternative 8// ssh-agent process using the sample server. 9// 10// References: 11// [PROTOCOL.agent]: https://tools.ietf.org/html/draft-miller-ssh-agent-00 12package agent // import "golang.org/x/crypto/ssh/agent" 13 14import ( 15 "bytes" 16 "crypto/dsa" 17 "crypto/ecdsa" 18 "crypto/elliptic" 19 "crypto/rsa" 20 "encoding/base64" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "io" 25 "math/big" 26 "sync" 27 28 "crypto" 29 "golang.org/x/crypto/ed25519" 30 "golang.org/x/crypto/ssh" 31) 32 33// SignatureFlags represent additional flags that can be passed to the signature 34// requests an defined in [PROTOCOL.agent] section 4.5.1. 35type SignatureFlags uint32 36 37// SignatureFlag values as defined in [PROTOCOL.agent] section 5.3. 38const ( 39 SignatureFlagReserved SignatureFlags = 1 << iota 40 SignatureFlagRsaSha256 41 SignatureFlagRsaSha512 42) 43 44// Agent represents the capabilities of an ssh-agent. 45type Agent interface { 46 // List returns the identities known to the agent. 47 List() ([]*Key, error) 48 49 // Sign has the agent sign the data using a protocol 2 key as defined 50 // in [PROTOCOL.agent] section 2.6.2. 51 Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) 52 53 // Add adds a private key to the agent. 54 Add(key AddedKey) error 55 56 // Remove removes all identities with the given public key. 57 Remove(key ssh.PublicKey) error 58 59 // RemoveAll removes all identities. 60 RemoveAll() error 61 62 // Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. 63 Lock(passphrase []byte) error 64 65 // Unlock undoes the effect of Lock 66 Unlock(passphrase []byte) error 67 68 // Signers returns signers for all the known keys. 69 Signers() ([]ssh.Signer, error) 70} 71 72type ExtendedAgent interface { 73 Agent 74 75 // SignWithFlags signs like Sign, but allows for additional flags to be sent/received 76 SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) 77 78 // Extension processes a custom extension request. Standard-compliant agents are not 79 // required to support any extensions, but this method allows agents to implement 80 // vendor-specific methods or add experimental features. See [PROTOCOL.agent] section 4.7. 81 // If agent extensions are unsupported entirely this method MUST return an 82 // ErrExtensionUnsupported error. Similarly, if just the specific extensionType in 83 // the request is unsupported by the agent then ErrExtensionUnsupported MUST be 84 // returned. 85 // 86 // In the case of success, since [PROTOCOL.agent] section 4.7 specifies that the contents 87 // of the response are unspecified (including the type of the message), the complete 88 // response will be returned as a []byte slice, including the "type" byte of the message. 89 Extension(extensionType string, contents []byte) ([]byte, error) 90} 91 92// ConstraintExtension describes an optional constraint defined by users. 93type ConstraintExtension struct { 94 // ExtensionName consist of a UTF-8 string suffixed by the 95 // implementation domain following the naming scheme defined 96 // in Section 4.2 of [RFC4251], e.g. "foo@example.com". 97 ExtensionName string 98 // ExtensionDetails contains the actual content of the extended 99 // constraint. 100 ExtensionDetails []byte 101} 102 103// AddedKey describes an SSH key to be added to an Agent. 104type AddedKey struct { 105 // PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or 106 // *ecdsa.PrivateKey, which will be inserted into the agent. 107 PrivateKey interface{} 108 // Certificate, if not nil, is communicated to the agent and will be 109 // stored with the key. 110 Certificate *ssh.Certificate 111 // Comment is an optional, free-form string. 112 Comment string 113 // LifetimeSecs, if not zero, is the number of seconds that the 114 // agent will store the key for. 115 LifetimeSecs uint32 116 // ConfirmBeforeUse, if true, requests that the agent confirm with the 117 // user before each use of this key. 118 ConfirmBeforeUse bool 119 // ConstraintExtensions are the experimental or private-use constraints 120 // defined by users. 121 ConstraintExtensions []ConstraintExtension 122} 123 124// See [PROTOCOL.agent], section 3. 125const ( 126 agentRequestV1Identities = 1 127 agentRemoveAllV1Identities = 9 128 129 // 3.2 Requests from client to agent for protocol 2 key operations 130 agentAddIdentity = 17 131 agentRemoveIdentity = 18 132 agentRemoveAllIdentities = 19 133 agentAddIDConstrained = 25 134 135 // 3.3 Key-type independent requests from client to agent 136 agentAddSmartcardKey = 20 137 agentRemoveSmartcardKey = 21 138 agentLock = 22 139 agentUnlock = 23 140 agentAddSmartcardKeyConstrained = 26 141 142 // 3.7 Key constraint identifiers 143 agentConstrainLifetime = 1 144 agentConstrainConfirm = 2 145 agentConstrainExtension = 3 146) 147 148// maxAgentResponseBytes is the maximum agent reply size that is accepted. This 149// is a sanity check, not a limit in the spec. 150const maxAgentResponseBytes = 16 << 20 151 152// Agent messages: 153// These structures mirror the wire format of the corresponding ssh agent 154// messages found in [PROTOCOL.agent]. 155 156// 3.4 Generic replies from agent to client 157const agentFailure = 5 158 159type failureAgentMsg struct{} 160 161const agentSuccess = 6 162 163type successAgentMsg struct{} 164 165// See [PROTOCOL.agent], section 2.5.2. 166const agentRequestIdentities = 11 167 168type requestIdentitiesAgentMsg struct{} 169 170// See [PROTOCOL.agent], section 2.5.2. 171const agentIdentitiesAnswer = 12 172 173type identitiesAnswerAgentMsg struct { 174 NumKeys uint32 `sshtype:"12"` 175 Keys []byte `ssh:"rest"` 176} 177 178// See [PROTOCOL.agent], section 2.6.2. 179const agentSignRequest = 13 180 181type signRequestAgentMsg struct { 182 KeyBlob []byte `sshtype:"13"` 183 Data []byte 184 Flags uint32 185} 186 187// See [PROTOCOL.agent], section 2.6.2. 188 189// 3.6 Replies from agent to client for protocol 2 key operations 190const agentSignResponse = 14 191 192type signResponseAgentMsg struct { 193 SigBlob []byte `sshtype:"14"` 194} 195 196type publicKey struct { 197 Format string 198 Rest []byte `ssh:"rest"` 199} 200 201// 3.7 Key constraint identifiers 202type constrainLifetimeAgentMsg struct { 203 LifetimeSecs uint32 `sshtype:"1"` 204} 205 206type constrainExtensionAgentMsg struct { 207 ExtensionName string `sshtype:"3"` 208 ExtensionDetails []byte 209 210 // Rest is a field used for parsing, not part of message 211 Rest []byte `ssh:"rest"` 212} 213 214// See [PROTOCOL.agent], section 4.7 215const agentExtension = 27 216const agentExtensionFailure = 28 217 218// ErrExtensionUnsupported indicates that an extension defined in 219// [PROTOCOL.agent] section 4.7 is unsupported by the agent. Specifically this 220// error indicates that the agent returned a standard SSH_AGENT_FAILURE message 221// as the result of a SSH_AGENTC_EXTENSION request. Note that the protocol 222// specification (and therefore this error) does not distinguish between a 223// specific extension being unsupported and extensions being unsupported entirely. 224var ErrExtensionUnsupported = errors.New("agent: extension unsupported") 225 226type extensionAgentMsg struct { 227 ExtensionType string `sshtype:"27"` 228 Contents []byte 229} 230 231// Key represents a protocol 2 public key as defined in 232// [PROTOCOL.agent], section 2.5.2. 233type Key struct { 234 Format string 235 Blob []byte 236 Comment string 237} 238 239func clientErr(err error) error { 240 return fmt.Errorf("agent: client error: %v", err) 241} 242 243// String returns the storage form of an agent key with the format, base64 244// encoded serialized key, and the comment if it is not empty. 245func (k *Key) String() string { 246 s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) 247 248 if k.Comment != "" { 249 s += " " + k.Comment 250 } 251 252 return s 253} 254 255// Type returns the public key type. 256func (k *Key) Type() string { 257 return k.Format 258} 259 260// Marshal returns key blob to satisfy the ssh.PublicKey interface. 261func (k *Key) Marshal() []byte { 262 return k.Blob 263} 264 265// Verify satisfies the ssh.PublicKey interface. 266func (k *Key) Verify(data []byte, sig *ssh.Signature) error { 267 pubKey, err := ssh.ParsePublicKey(k.Blob) 268 if err != nil { 269 return fmt.Errorf("agent: bad public key: %v", err) 270 } 271 return pubKey.Verify(data, sig) 272} 273 274type wireKey struct { 275 Format string 276 Rest []byte `ssh:"rest"` 277} 278 279func parseKey(in []byte) (out *Key, rest []byte, err error) { 280 var record struct { 281 Blob []byte 282 Comment string 283 Rest []byte `ssh:"rest"` 284 } 285 286 if err := ssh.Unmarshal(in, &record); err != nil { 287 return nil, nil, err 288 } 289 290 var wk wireKey 291 if err := ssh.Unmarshal(record.Blob, &wk); err != nil { 292 return nil, nil, err 293 } 294 295 return &Key{ 296 Format: wk.Format, 297 Blob: record.Blob, 298 Comment: record.Comment, 299 }, record.Rest, nil 300} 301 302// client is a client for an ssh-agent process. 303type client struct { 304 // conn is typically a *net.UnixConn 305 conn io.ReadWriter 306 // mu is used to prevent concurrent access to the agent 307 mu sync.Mutex 308} 309 310// NewClient returns an Agent that talks to an ssh-agent process over 311// the given connection. 312func NewClient(rw io.ReadWriter) ExtendedAgent { 313 return &client{conn: rw} 314} 315 316// call sends an RPC to the agent. On success, the reply is 317// unmarshaled into reply and replyType is set to the first byte of 318// the reply, which contains the type of the message. 319func (c *client) call(req []byte) (reply interface{}, err error) { 320 buf, err := c.callRaw(req) 321 if err != nil { 322 return nil, err 323 } 324 reply, err = unmarshal(buf) 325 if err != nil { 326 return nil, clientErr(err) 327 } 328 return reply, nil 329} 330 331// callRaw sends an RPC to the agent. On success, the raw 332// bytes of the response are returned; no unmarshalling is 333// performed on the response. 334func (c *client) callRaw(req []byte) (reply []byte, err error) { 335 c.mu.Lock() 336 defer c.mu.Unlock() 337 338 msg := make([]byte, 4+len(req)) 339 binary.BigEndian.PutUint32(msg, uint32(len(req))) 340 copy(msg[4:], req) 341 if _, err = c.conn.Write(msg); err != nil { 342 return nil, clientErr(err) 343 } 344 345 var respSizeBuf [4]byte 346 if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { 347 return nil, clientErr(err) 348 } 349 respSize := binary.BigEndian.Uint32(respSizeBuf[:]) 350 if respSize > maxAgentResponseBytes { 351 return nil, clientErr(errors.New("response too large")) 352 } 353 354 buf := make([]byte, respSize) 355 if _, err = io.ReadFull(c.conn, buf); err != nil { 356 return nil, clientErr(err) 357 } 358 return buf, nil 359} 360 361func (c *client) simpleCall(req []byte) error { 362 resp, err := c.call(req) 363 if err != nil { 364 return err 365 } 366 if _, ok := resp.(*successAgentMsg); ok { 367 return nil 368 } 369 return errors.New("agent: failure") 370} 371 372func (c *client) RemoveAll() error { 373 return c.simpleCall([]byte{agentRemoveAllIdentities}) 374} 375 376func (c *client) Remove(key ssh.PublicKey) error { 377 req := ssh.Marshal(&agentRemoveIdentityMsg{ 378 KeyBlob: key.Marshal(), 379 }) 380 return c.simpleCall(req) 381} 382 383func (c *client) Lock(passphrase []byte) error { 384 req := ssh.Marshal(&agentLockMsg{ 385 Passphrase: passphrase, 386 }) 387 return c.simpleCall(req) 388} 389 390func (c *client) Unlock(passphrase []byte) error { 391 req := ssh.Marshal(&agentUnlockMsg{ 392 Passphrase: passphrase, 393 }) 394 return c.simpleCall(req) 395} 396 397// List returns the identities known to the agent. 398func (c *client) List() ([]*Key, error) { 399 // see [PROTOCOL.agent] section 2.5.2. 400 req := []byte{agentRequestIdentities} 401 402 msg, err := c.call(req) 403 if err != nil { 404 return nil, err 405 } 406 407 switch msg := msg.(type) { 408 case *identitiesAnswerAgentMsg: 409 if msg.NumKeys > maxAgentResponseBytes/8 { 410 return nil, errors.New("agent: too many keys in agent reply") 411 } 412 keys := make([]*Key, msg.NumKeys) 413 data := msg.Keys 414 for i := uint32(0); i < msg.NumKeys; i++ { 415 var key *Key 416 var err error 417 if key, data, err = parseKey(data); err != nil { 418 return nil, err 419 } 420 keys[i] = key 421 } 422 return keys, nil 423 case *failureAgentMsg: 424 return nil, errors.New("agent: failed to list keys") 425 } 426 panic("unreachable") 427} 428 429// Sign has the agent sign the data using a protocol 2 key as defined 430// in [PROTOCOL.agent] section 2.6.2. 431func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { 432 return c.SignWithFlags(key, data, 0) 433} 434 435func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) { 436 req := ssh.Marshal(signRequestAgentMsg{ 437 KeyBlob: key.Marshal(), 438 Data: data, 439 Flags: uint32(flags), 440 }) 441 442 msg, err := c.call(req) 443 if err != nil { 444 return nil, err 445 } 446 447 switch msg := msg.(type) { 448 case *signResponseAgentMsg: 449 var sig ssh.Signature 450 if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { 451 return nil, err 452 } 453 454 return &sig, nil 455 case *failureAgentMsg: 456 return nil, errors.New("agent: failed to sign challenge") 457 } 458 panic("unreachable") 459} 460 461// unmarshal parses an agent message in packet, returning the parsed 462// form and the message type of packet. 463func unmarshal(packet []byte) (interface{}, error) { 464 if len(packet) < 1 { 465 return nil, errors.New("agent: empty packet") 466 } 467 var msg interface{} 468 switch packet[0] { 469 case agentFailure: 470 return new(failureAgentMsg), nil 471 case agentSuccess: 472 return new(successAgentMsg), nil 473 case agentIdentitiesAnswer: 474 msg = new(identitiesAnswerAgentMsg) 475 case agentSignResponse: 476 msg = new(signResponseAgentMsg) 477 case agentV1IdentitiesAnswer: 478 msg = new(agentV1IdentityMsg) 479 default: 480 return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) 481 } 482 if err := ssh.Unmarshal(packet, msg); err != nil { 483 return nil, err 484 } 485 return msg, nil 486} 487 488type rsaKeyMsg struct { 489 Type string `sshtype:"17|25"` 490 N *big.Int 491 E *big.Int 492 D *big.Int 493 Iqmp *big.Int // IQMP = Inverse Q Mod P 494 P *big.Int 495 Q *big.Int 496 Comments string 497 Constraints []byte `ssh:"rest"` 498} 499 500type dsaKeyMsg struct { 501 Type string `sshtype:"17|25"` 502 P *big.Int 503 Q *big.Int 504 G *big.Int 505 Y *big.Int 506 X *big.Int 507 Comments string 508 Constraints []byte `ssh:"rest"` 509} 510 511type ecdsaKeyMsg struct { 512 Type string `sshtype:"17|25"` 513 Curve string 514 KeyBytes []byte 515 D *big.Int 516 Comments string 517 Constraints []byte `ssh:"rest"` 518} 519 520type ed25519KeyMsg struct { 521 Type string `sshtype:"17|25"` 522 Pub []byte 523 Priv []byte 524 Comments string 525 Constraints []byte `ssh:"rest"` 526} 527 528// Insert adds a private key to the agent. 529func (c *client) insertKey(s interface{}, comment string, constraints []byte) error { 530 var req []byte 531 switch k := s.(type) { 532 case *rsa.PrivateKey: 533 if len(k.Primes) != 2 { 534 return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) 535 } 536 k.Precompute() 537 req = ssh.Marshal(rsaKeyMsg{ 538 Type: ssh.KeyAlgoRSA, 539 N: k.N, 540 E: big.NewInt(int64(k.E)), 541 D: k.D, 542 Iqmp: k.Precomputed.Qinv, 543 P: k.Primes[0], 544 Q: k.Primes[1], 545 Comments: comment, 546 Constraints: constraints, 547 }) 548 case *dsa.PrivateKey: 549 req = ssh.Marshal(dsaKeyMsg{ 550 Type: ssh.KeyAlgoDSA, 551 P: k.P, 552 Q: k.Q, 553 G: k.G, 554 Y: k.Y, 555 X: k.X, 556 Comments: comment, 557 Constraints: constraints, 558 }) 559 case *ecdsa.PrivateKey: 560 nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) 561 req = ssh.Marshal(ecdsaKeyMsg{ 562 Type: "ecdsa-sha2-" + nistID, 563 Curve: nistID, 564 KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y), 565 D: k.D, 566 Comments: comment, 567 Constraints: constraints, 568 }) 569 case *ed25519.PrivateKey: 570 req = ssh.Marshal(ed25519KeyMsg{ 571 Type: ssh.KeyAlgoED25519, 572 Pub: []byte(*k)[32:], 573 Priv: []byte(*k), 574 Comments: comment, 575 Constraints: constraints, 576 }) 577 default: 578 return fmt.Errorf("agent: unsupported key type %T", s) 579 } 580 581 // if constraints are present then the message type needs to be changed. 582 if len(constraints) != 0 { 583 req[0] = agentAddIDConstrained 584 } 585 586 resp, err := c.call(req) 587 if err != nil { 588 return err 589 } 590 if _, ok := resp.(*successAgentMsg); ok { 591 return nil 592 } 593 return errors.New("agent: failure") 594} 595 596type rsaCertMsg struct { 597 Type string `sshtype:"17|25"` 598 CertBytes []byte 599 D *big.Int 600 Iqmp *big.Int // IQMP = Inverse Q Mod P 601 P *big.Int 602 Q *big.Int 603 Comments string 604 Constraints []byte `ssh:"rest"` 605} 606 607type dsaCertMsg struct { 608 Type string `sshtype:"17|25"` 609 CertBytes []byte 610 X *big.Int 611 Comments string 612 Constraints []byte `ssh:"rest"` 613} 614 615type ecdsaCertMsg struct { 616 Type string `sshtype:"17|25"` 617 CertBytes []byte 618 D *big.Int 619 Comments string 620 Constraints []byte `ssh:"rest"` 621} 622 623type ed25519CertMsg struct { 624 Type string `sshtype:"17|25"` 625 CertBytes []byte 626 Pub []byte 627 Priv []byte 628 Comments string 629 Constraints []byte `ssh:"rest"` 630} 631 632// Add adds a private key to the agent. If a certificate is given, 633// that certificate is added instead as public key. 634func (c *client) Add(key AddedKey) error { 635 var constraints []byte 636 637 if secs := key.LifetimeSecs; secs != 0 { 638 constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...) 639 } 640 641 if key.ConfirmBeforeUse { 642 constraints = append(constraints, agentConstrainConfirm) 643 } 644 645 cert := key.Certificate 646 if cert == nil { 647 return c.insertKey(key.PrivateKey, key.Comment, constraints) 648 } 649 return c.insertCert(key.PrivateKey, cert, key.Comment, constraints) 650} 651 652func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error { 653 var req []byte 654 switch k := s.(type) { 655 case *rsa.PrivateKey: 656 if len(k.Primes) != 2 { 657 return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) 658 } 659 k.Precompute() 660 req = ssh.Marshal(rsaCertMsg{ 661 Type: cert.Type(), 662 CertBytes: cert.Marshal(), 663 D: k.D, 664 Iqmp: k.Precomputed.Qinv, 665 P: k.Primes[0], 666 Q: k.Primes[1], 667 Comments: comment, 668 Constraints: constraints, 669 }) 670 case *dsa.PrivateKey: 671 req = ssh.Marshal(dsaCertMsg{ 672 Type: cert.Type(), 673 CertBytes: cert.Marshal(), 674 X: k.X, 675 Comments: comment, 676 Constraints: constraints, 677 }) 678 case *ecdsa.PrivateKey: 679 req = ssh.Marshal(ecdsaCertMsg{ 680 Type: cert.Type(), 681 CertBytes: cert.Marshal(), 682 D: k.D, 683 Comments: comment, 684 Constraints: constraints, 685 }) 686 case *ed25519.PrivateKey: 687 req = ssh.Marshal(ed25519CertMsg{ 688 Type: cert.Type(), 689 CertBytes: cert.Marshal(), 690 Pub: []byte(*k)[32:], 691 Priv: []byte(*k), 692 Comments: comment, 693 Constraints: constraints, 694 }) 695 default: 696 return fmt.Errorf("agent: unsupported key type %T", s) 697 } 698 699 // if constraints are present then the message type needs to be changed. 700 if len(constraints) != 0 { 701 req[0] = agentAddIDConstrained 702 } 703 704 signer, err := ssh.NewSignerFromKey(s) 705 if err != nil { 706 return err 707 } 708 if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { 709 return errors.New("agent: signer and cert have different public key") 710 } 711 712 resp, err := c.call(req) 713 if err != nil { 714 return err 715 } 716 if _, ok := resp.(*successAgentMsg); ok { 717 return nil 718 } 719 return errors.New("agent: failure") 720} 721 722// Signers provides a callback for client authentication. 723func (c *client) Signers() ([]ssh.Signer, error) { 724 keys, err := c.List() 725 if err != nil { 726 return nil, err 727 } 728 729 var result []ssh.Signer 730 for _, k := range keys { 731 result = append(result, &agentKeyringSigner{c, k}) 732 } 733 return result, nil 734} 735 736type agentKeyringSigner struct { 737 agent *client 738 pub ssh.PublicKey 739} 740 741func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { 742 return s.pub 743} 744 745func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { 746 // The agent has its own entropy source, so the rand argument is ignored. 747 return s.agent.Sign(s.pub, data) 748} 749 750func (s *agentKeyringSigner) SignWithOpts(rand io.Reader, data []byte, opts crypto.SignerOpts) (*ssh.Signature, error) { 751 var flags SignatureFlags 752 if opts != nil { 753 switch opts.HashFunc() { 754 case crypto.SHA256: 755 flags = SignatureFlagRsaSha256 756 case crypto.SHA512: 757 flags = SignatureFlagRsaSha512 758 } 759 } 760 return s.agent.SignWithFlags(s.pub, data, flags) 761} 762 763// Calls an extension method. It is up to the agent implementation as to whether or not 764// any particular extension is supported and may always return an error. Because the 765// type of the response is up to the implementation, this returns the bytes of the 766// response and does not attempt any type of unmarshalling. 767func (c *client) Extension(extensionType string, contents []byte) ([]byte, error) { 768 req := ssh.Marshal(extensionAgentMsg{ 769 ExtensionType: extensionType, 770 Contents: contents, 771 }) 772 buf, err := c.callRaw(req) 773 if err != nil { 774 return nil, err 775 } 776 if len(buf) == 0 { 777 return nil, errors.New("agent: failure; empty response") 778 } 779 // [PROTOCOL.agent] section 4.7 indicates that an SSH_AGENT_FAILURE message 780 // represents an agent that does not support the extension 781 if buf[0] == agentFailure { 782 return nil, ErrExtensionUnsupported 783 } 784 if buf[0] == agentExtensionFailure { 785 return nil, errors.New("agent: generic extension failure") 786 } 787 788 return buf, nil 789} 790