The parseDSAKey function constructed a *dsa.PrivateKey directly from the add-identity request without validating the key parameters. Unlike DSA certificates, whose parameters are checked by the ssh package when the certificate's public key is parsed, raw DSA keys added to the agent were not validated at all. Align the raw DSA key parsing with the validation already performed by the main ssh package. Fixes golang/go#79725 Change-Id: I537cc2175d35c19848c90c68739cf94ba7b50e10 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795422 Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Junyang Shao <shaojunyang@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
643 lines
16 KiB
Go
643 lines
16 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package agent
|
|
|
|
import (
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// server wraps an Agent and uses it to implement the agent side of
|
|
// the SSH-agent, wire protocol.
|
|
type server struct {
|
|
agent Agent
|
|
}
|
|
|
|
func (s *server) processRequestBytes(reqData []byte) []byte {
|
|
rep, err := s.processRequest(reqData)
|
|
if err != nil {
|
|
if err != errLocked {
|
|
// TODO(hanwen): provide better logging interface?
|
|
log.Printf("agent %d: %v", reqData[0], err)
|
|
}
|
|
return []byte{agentFailure}
|
|
}
|
|
|
|
if rep == nil {
|
|
return []byte{agentSuccess}
|
|
}
|
|
|
|
return ssh.Marshal(rep)
|
|
}
|
|
|
|
func marshalKey(k *Key) []byte {
|
|
var record struct {
|
|
Blob []byte
|
|
Comment string
|
|
}
|
|
record.Blob = k.Marshal()
|
|
record.Comment = k.Comment
|
|
|
|
return ssh.Marshal(&record)
|
|
}
|
|
|
|
// See [PROTOCOL.agent], section 2.5.1.
|
|
const agentV1IdentitiesAnswer = 2
|
|
|
|
type agentV1IdentityMsg struct {
|
|
Numkeys uint32 `sshtype:"2"`
|
|
}
|
|
|
|
type agentRemoveIdentityMsg struct {
|
|
KeyBlob []byte `sshtype:"18"`
|
|
}
|
|
|
|
type agentLockMsg struct {
|
|
Passphrase []byte `sshtype:"22"`
|
|
}
|
|
|
|
type agentUnlockMsg struct {
|
|
Passphrase []byte `sshtype:"23"`
|
|
}
|
|
|
|
func (s *server) processRequest(data []byte) (interface{}, error) {
|
|
switch data[0] {
|
|
case agentRequestV1Identities:
|
|
return &agentV1IdentityMsg{0}, nil
|
|
|
|
case agentRemoveAllV1Identities:
|
|
return nil, nil
|
|
|
|
case agentRemoveIdentity:
|
|
var req agentRemoveIdentityMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var wk wireKey
|
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
|
|
|
case agentRemoveAllIdentities:
|
|
return nil, s.agent.RemoveAll()
|
|
|
|
case agentLock:
|
|
var req agentLockMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, s.agent.Lock(req.Passphrase)
|
|
|
|
case agentUnlock:
|
|
var req agentUnlockMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, s.agent.Unlock(req.Passphrase)
|
|
|
|
case agentSignRequest:
|
|
var req signRequestAgentMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var wk wireKey
|
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k := &Key{
|
|
Format: wk.Format,
|
|
Blob: req.KeyBlob,
|
|
}
|
|
|
|
var sig *ssh.Signature
|
|
var err error
|
|
if extendedAgent, ok := s.agent.(ExtendedAgent); ok {
|
|
sig, err = extendedAgent.SignWithFlags(k, req.Data, SignatureFlags(req.Flags))
|
|
} else {
|
|
sig, err = s.agent.Sign(k, req.Data)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
|
|
|
case agentRequestIdentities:
|
|
keys, err := s.agent.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rep := identitiesAnswerAgentMsg{
|
|
NumKeys: uint32(len(keys)),
|
|
}
|
|
for _, k := range keys {
|
|
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
|
}
|
|
return rep, nil
|
|
|
|
case agentAddIDConstrained, agentAddIdentity:
|
|
return nil, s.insertIdentity(data)
|
|
|
|
case agentExtension:
|
|
// Return a stub object where the whole contents of the response gets marshaled.
|
|
var responseStub struct {
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if extendedAgent, ok := s.agent.(ExtendedAgent); !ok {
|
|
// If this agent doesn't implement extensions, [PROTOCOL.agent] section 4.7
|
|
// requires that we return a standard SSH_AGENT_FAILURE message.
|
|
responseStub.Rest = []byte{agentFailure}
|
|
} else {
|
|
var req extensionAgentMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
res, err := extendedAgent.Extension(req.ExtensionType, req.Contents)
|
|
if err != nil {
|
|
// If agent extensions are unsupported, return a standard SSH_AGENT_FAILURE
|
|
// message as required by [PROTOCOL.agent] section 4.7.
|
|
if err == ErrExtensionUnsupported {
|
|
responseStub.Rest = []byte{agentFailure}
|
|
} else {
|
|
// As the result of any other error processing an extension request,
|
|
// [PROTOCOL.agent] section 4.7 requires that we return a
|
|
// SSH_AGENT_EXTENSION_FAILURE code.
|
|
responseStub.Rest = []byte{agentExtensionFailure}
|
|
}
|
|
} else {
|
|
if len(res) == 0 {
|
|
return nil, nil
|
|
}
|
|
responseStub.Rest = res
|
|
}
|
|
}
|
|
|
|
return responseStub, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
|
}
|
|
|
|
func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) {
|
|
for len(constraints) != 0 {
|
|
switch constraints[0] {
|
|
case agentConstrainLifetime:
|
|
if len(constraints) < 5 {
|
|
return 0, false, nil, io.ErrUnexpectedEOF
|
|
}
|
|
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
|
constraints = constraints[5:]
|
|
case agentConstrainConfirm:
|
|
confirmBeforeUse = true
|
|
constraints = constraints[1:]
|
|
case agentConstrainExtension, agentConstrainExtensionV00:
|
|
var msg constrainExtensionAgentMsg
|
|
if err = ssh.Unmarshal(constraints, &msg); err != nil {
|
|
return 0, false, nil, err
|
|
}
|
|
extensions = append(extensions, ConstraintExtension{
|
|
ExtensionName: msg.ExtensionName,
|
|
ExtensionDetails: msg.ExtensionDetails,
|
|
})
|
|
constraints = msg.Rest
|
|
default:
|
|
return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
|
lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key.LifetimeSecs = lifetimeSecs
|
|
key.ConfirmBeforeUse = confirmBeforeUse
|
|
key.ConstraintExtensions = constraintExtensions
|
|
return nil
|
|
}
|
|
|
|
// checkRSAKeyParams enforces the same bounds as parseRSA in the ssh
|
|
// package, and additionally caps the prime factors. Without this,
|
|
// the rsa.PrivateKey built from an Add request would call Precompute()
|
|
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
|
|
// |p| and can consume excessive CPU on oversized keys.
|
|
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
|
|
if N.BitLen() > 8192 {
|
|
return errors.New("agent: RSA modulus too large")
|
|
}
|
|
if P.BitLen() > 4096 || Q.BitLen() > 4096 {
|
|
return errors.New("agent: RSA prime too large")
|
|
}
|
|
if E.BitLen() > 24 {
|
|
return errors.New("agent: RSA public exponent too large")
|
|
}
|
|
e := E.Int64()
|
|
if e < 3 || e&1 == 0 {
|
|
return errors.New("agent: incorrect RSA public exponent")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseRSAKey(req []byte) (*AddedKey, error) {
|
|
var k rsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkRSAKeyParams(k.N, k.E, k.P, k.Q); err != nil {
|
|
return nil, err
|
|
}
|
|
priv := &rsa.PrivateKey{
|
|
PublicKey: rsa.PublicKey{
|
|
E: int(k.E.Int64()),
|
|
N: k.N,
|
|
},
|
|
D: k.D,
|
|
Primes: []*big.Int{k.P, k.Q},
|
|
}
|
|
priv.Precompute()
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|
var k ed25519KeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
|
}
|
|
priv := ed25519.PrivateKey(k.Priv)
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func checkDSAParams(param *dsa.Parameters) error {
|
|
// SSH specifies FIPS 186-2, which only provided a single size
|
|
// (1024 bits) DSA key. FIPS 186-3 allows for larger key
|
|
// sizes, which would confuse SSH.
|
|
if l := param.P.BitLen(); l != 1024 {
|
|
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
|
}
|
|
|
|
// FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
|
|
// this to prevent DoS attacks where an attacker sends a huge Q which makes
|
|
// verification slow.
|
|
if l := param.Q.BitLen(); l != 160 {
|
|
return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
|
|
}
|
|
|
|
// The generator G is an element of the group, so it must be strictly less
|
|
// than the modulus P.
|
|
if param.G.Cmp(param.P) >= 0 {
|
|
return errors.New("ssh: DSA generator larger than modulus")
|
|
}
|
|
|
|
// G must be positive.
|
|
if param.G.Sign() <= 0 {
|
|
return errors.New("ssh: DSA generator must be positive")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseDSAKey(req []byte) (*AddedKey, error) {
|
|
var k dsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
params := dsa.Parameters{
|
|
P: k.P,
|
|
Q: k.Q,
|
|
G: k.G,
|
|
}
|
|
if err := checkDSAParams(¶ms); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The public value Y must be a non-zero element of the group, i.e. strictly
|
|
// between 0 and P, to prevent a maliciously oversized Y from slowing
|
|
// signature operations.
|
|
if k.Y.Sign() <= 0 || k.Y.Cmp(k.P) >= 0 {
|
|
return nil, errors.New("agent: DSA public value Y out of range")
|
|
}
|
|
|
|
priv := &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: params,
|
|
Y: k.Y,
|
|
},
|
|
X: k.X,
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
|
|
priv = &ecdsa.PrivateKey{
|
|
D: privScalar,
|
|
}
|
|
|
|
switch curveName {
|
|
case "nistp256":
|
|
priv.Curve = elliptic.P256()
|
|
case "nistp384":
|
|
priv.Curve = elliptic.P384()
|
|
case "nistp521":
|
|
priv.Curve = elliptic.P521()
|
|
default:
|
|
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
|
|
}
|
|
|
|
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
|
|
if priv.X == nil || priv.Y == nil {
|
|
return nil, errors.New("agent: point not on curve")
|
|
}
|
|
|
|
return priv, nil
|
|
}
|
|
|
|
func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|
var k ed25519CertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(k.Priv) != ed25519.PrivateKeySize {
|
|
return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
|
|
}
|
|
priv := ed25519.PrivateKey(k.Priv)
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad ED25519 certificate")
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseECDSAKey(req []byte) (*AddedKey, error) {
|
|
var k ecdsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseRSACert(req []byte) (*AddedKey, error) {
|
|
var k rsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad RSA certificate")
|
|
}
|
|
|
|
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
|
|
var rsaPub struct {
|
|
Name string
|
|
E *big.Int
|
|
N *big.Int
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
|
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
}
|
|
|
|
if err := checkRSAKeyParams(rsaPub.N, rsaPub.E, k.P, k.Q); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv := rsa.PrivateKey{
|
|
PublicKey: rsa.PublicKey{
|
|
E: int(rsaPub.E.Int64()),
|
|
N: rsaPub.N,
|
|
},
|
|
D: k.D,
|
|
Primes: []*big.Int{k.Q, k.P},
|
|
}
|
|
priv.Precompute()
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseDSACert(req []byte) (*AddedKey, error) {
|
|
var k dsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad DSA certificate")
|
|
}
|
|
|
|
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
|
|
var w struct {
|
|
Name string
|
|
P, Q, G, Y *big.Int
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
|
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
}
|
|
|
|
priv := &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: w.P,
|
|
Q: w.Q,
|
|
G: w.G,
|
|
},
|
|
Y: w.Y,
|
|
},
|
|
X: k.X,
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseECDSACert(req []byte) (*AddedKey, error) {
|
|
var k ecdsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad ECDSA certificate")
|
|
}
|
|
|
|
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
|
|
var ecdsaPub struct {
|
|
Name string
|
|
ID string
|
|
Key []byte
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func (s *server) insertIdentity(req []byte) error {
|
|
var record struct {
|
|
Type string `sshtype:"17|25"`
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := ssh.Unmarshal(req, &record); err != nil {
|
|
return err
|
|
}
|
|
|
|
var addedKey *AddedKey
|
|
var err error
|
|
|
|
switch record.Type {
|
|
case ssh.KeyAlgoRSA:
|
|
addedKey, err = parseRSAKey(req)
|
|
case ssh.InsecureKeyAlgoDSA:
|
|
addedKey, err = parseDSAKey(req)
|
|
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
|
addedKey, err = parseECDSAKey(req)
|
|
case ssh.KeyAlgoED25519:
|
|
addedKey, err = parseEd25519Key(req)
|
|
case ssh.CertAlgoRSAv01:
|
|
addedKey, err = parseRSACert(req)
|
|
case ssh.InsecureCertAlgoDSAv01:
|
|
addedKey, err = parseDSACert(req)
|
|
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
|
addedKey, err = parseECDSACert(req)
|
|
case ssh.CertAlgoED25519v01:
|
|
addedKey, err = parseEd25519Cert(req)
|
|
default:
|
|
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.agent.Add(*addedKey)
|
|
}
|
|
|
|
// ServeAgent serves the agent protocol on the given connection. It
|
|
// returns when an I/O error occurs.
|
|
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
|
s := &server{agent}
|
|
|
|
var length [4]byte
|
|
for {
|
|
if _, err := io.ReadFull(c, length[:]); err != nil {
|
|
return err
|
|
}
|
|
l := binary.BigEndian.Uint32(length[:])
|
|
if l == 0 {
|
|
return fmt.Errorf("agent: request size is 0")
|
|
}
|
|
if l > maxAgentResponseBytes {
|
|
// We also cap requests.
|
|
return fmt.Errorf("agent: request too large: %d", l)
|
|
}
|
|
|
|
req := make([]byte, l)
|
|
if _, err := io.ReadFull(c, req); err != nil {
|
|
return err
|
|
}
|
|
|
|
repData := s.processRequestBytes(req)
|
|
if len(repData) > maxAgentResponseBytes {
|
|
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
|
}
|
|
|
|
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
|
if _, err := c.Write(length[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := c.Write(repData); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|