Geoffrey
Geoffrey

Reputation: 11353

How do I generate the SSH agent sign response

I am writing an internal management system that also needs to implement a SSH agent. I have so far implemented the SSH_AGENTC_REQUEST_IDENTITIES and SSH_AGENT_IDENTITIES_ANSWER correctly which causes SSH to then send the SSH_AGENTC_SIGN_REQUEST as per https://tools.ietf.org/id/draft-miller-ssh-agent-00.html

The issue I have is figuring out how to sign the request, at this point I only need to support ssh-rsa. I have attemped to implement this as per https://www.rfc-editor.org/rfc/rfc4253 but I can't seem to generate a reply that will authenticate.

From what I understand of this specification the entire data section of the SSH_AGENTC_SIGN_REQUEST message needs to be signed using RSASSA-PKCS1-v1_5

Here is the code that receives the request

if msgType == SSH_AGENTC_SIGN_REQUEST:
  blobLen = struct.unpack('!I', sock.recv(4))[0]
  blob    = sock.recv(blobLen)
  dataLen = struct.unpack('!I', sock.recv(4))[0]
  data    = sock.recv(dataLen)
  flags   = struct.unpack('!I', sock.recv(4))[0]

  print("blob: %s\ndata: %s\n flags: %d" % (blob, data, flags))

  if base64.b64decode(server["public_key"]) != blob:
    sock.sendall(struct.pack('!IB', 1, SSH_AGENT_FAILURE))
    continue

  signed = rpc.root.signSSHData(server_id, data)
  if not signed:
    sock.sendall(struct.pack('!IB', 1, SSH_AGENT_FAILURE))
    continue

  buff = bytearray()
  buff.extend(struct.pack('!B', SSH_AGENT_SIGN_RESPONSE))
  packBytes(buff, bytes("ssh-rsa", "UTF-8"))
  packBytes(buff, signed)
  head = struct.pack('!I', len(buff))

  sock.sendall(head);
  sock.sendall(buff);

Here is the source of rpc.root.signSSHData:

  def exposed_signSSHData(self, server_id, data):
    query = "SELECT private_key FROM servers WHERE deleted = 0 AND id = %s LIMIT 1"
    cursor = self.core.getDBC().cursor()
    cursor.execute(query, (server_id, ))
    row    = cursor.fetchone();
    if row is None:
      cursor.close()
      return False

    key    = self.core.decryptData(row[0])
    key    = RSA.importKey(key);
    h      = SHA.new(data)
    signer = PKCS1_v1_5.new(key)
    return signer.sign(h)

Update 1: I believe I may have figured it out, seems that the data is a SSH_MSG_USERAUTH_REQUEST as defined in RFC4252. I will update my application and see how it goes.

Upvotes: 1

Views: 417

Answers (1)

Geoffrey
Geoffrey

Reputation: 11353

I found the solution, the reply was formatted incorrectly, below is the corrected code:

if msgType == SSH_AGENTC_SIGN_REQUEST:
  blobLen = struct.unpack('!I', sock.recv(4))[0]
  blob    = sock.recv(blobLen)
  dataLen = struct.unpack('!I', sock.recv(4))[0]
  data    = sock.recv(dataLen)
  flags   = struct.unpack('!I', sock.recv(4))[0]

  if base64.b64decode(server["public_key"]) != blob:
    sock.sendall(struct.pack('!IB', 1, SSH_AGENT_FAILURE))
    continue

  sig = rpc.root.signSSHData(server_id, data);
  if not sig:
    sock.sendall(struct.pack('!IB', 1, SSH_AGENT_FAILURE))
    continue

  signature = bytearray()
  packBytes(signature, bytes("ssh-rsa", "UTF-8"))
  packBytes(signature, sig)

  buff = bytearray()
  buff.extend(struct.pack('!B', SSH_AGENT_SIGN_RESPONSE))
  packBytes(buff, signature)
  head = struct.pack('!I', len(buff))

  sock.sendall(head);
  sock.sendall(buff);
  continue

Upvotes: 1

Related Questions