Subject: ssh-agent protocol

From: [email-masked] (Niels Möller)
Date: 21 Jan 2002 11:15:45 +0100

Hi,

Markus asked me to post this information to the list. It's an
inofficial description of the ssh-agent protocol, written by Balazs
Scheidler.

Back then, I expected the SSH folks to publish an official spec on
ssh-agent "soon", but now two years have passed and this is still the
best information on ssh-agent I have seen.

I have some concerns about the security in ssh-agent, and I think
improvements are possible, if one also creates a new better publickey
userauth methods that signs some more information. But before doing
anything about that, we first need to understand the details of the
current ssh-agent mechanism.

Regards,
/Niels

: Date: Fri, 5 Nov 1999 20:29:59 +0100
: From: Balazs Scheidler <[email-masked]>
: To: Niels Moller <[email-masked]>
: Subject: ssh-agent protocol
: Message-ID: <[email-masked]>

[ ... deletia ...]

: SSH-AGENT requests and replies
: 
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_REQUEST_VERSION(1)
: 
: reply:
: byte	SSH_AGENT_VERSION_RESPONSE(103)
: UINT32	version
: 
: The version field contains 2 for ssh-agent2, and presumably 1 for
: ssh-agent1.
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_ADD_KEY(202)
: string	private
: string	public
: string	description
: UINT8[]	constraint data
: 
: constraint data is an encoded part in the form attribute,value, where
: attribute can be one of the following:
: 
: #define SSH_AGENT_CONSTRAINT_OLD_TIMEOUT            1
: #define SSH_AGENT_CONSTRAINT_OLD_USE_LIMIT          2
: #define SSH_AGENT_CONSTRAINT_OLD_FORWARDING_STEPS   3
: #define SSH_AGENT_CONSTRAINT_OLD_FORWARDING_PATH    4
: #define SSH_AGENT_CONSTRAINT_OLD_COMPAT             5
: #define SSH_AGENT_CONSTRAINT_OLD_STATUS             6
: #define SSH_AGENT_CONSTRAINT_TIMEOUT                50
: #define SSH_AGENT_CONSTRAINT_USE_LIMIT              51
: #define SSH_AGENT_CONSTRAINT_FORWARDING_STEPS       52
: #define SSH_AGENT_CONSTRAINT_FORWARDING_PATH        100
: #define SSH_AGENT_CONSTRAINT_COMPAT                 150
: #define SSH_AGENT_CONSTRAINT_STATUS                 53
: 
: Different constraint codes may involve different encoding structures.
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_DELETE_ALL_KEYS
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_LIST_KEYS(204)
: 
: reply
: byte	SSH_AGENT_KEY_LIST(104)
: UINT32	number of keys (=n)
: string	certificates    ] one pair for each key
: string	description     ]
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_PRIVATE_KEY_OP(205)
: string	op-name
: string	public keyblob
: op-name dependent parameters
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: 
: possible operations:
: 
: 1) "sign" ask the agent to sign some data
: 
: parameters:
: string	blob to sign
: 
: 2) "hash-and-sign" ask the agent to hash & sign some data
: 
: parameters:
: string	blob to sign
: 
: 3) "decrypt" ask the agent to decrypt the given blob
: 
: parameters:
: string	blob to decrypt
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_FORWARDING_NOTICE (206)
: string	forwarding host name
: string	???
: UINT32	port
: 
: reply:
: no reply
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_DELETE_KEY (207)
: string	public blob
: string	description
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_LOCK (208)
: string	password
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_UNLOCK (209)
: string	password
: 
: reply:
: byte	SSH_AGENT_SUCCESS or SSH_AGENT_ERROR_FAILURE
: ----------------------------------------------------------------------
: request:
: byte	SSH_AGENT_PING
: arbitrary data
: 
: reply:
: byte	SSH_AGENT_ALIVE
: arbitrary data returned
: ----------------------------------------------------------------------

---

From: Markus Friedl <[email-masked]>
Date: Mon, 21 Jan 2002 12:04:14 +0100

Since there was no documentation about a protocol v2 agent
protocol, OpenSSH uses a quick hack and private extensions to the
protocol used by the ssh-1.2.x implementations. The protocol v1
agent protocol works as documented below.

#define SSH2_AGENTC_REQUEST_IDENTITIES          11
#define SSH2_AGENT_IDENTITIES_ANSWER            12
#define SSH2_AGENTC_SIGN_REQUEST                13
#define SSH2_AGENT_SIGN_RESPONSE                14
#define SSH2_AGENTC_ADD_IDENTITY                17
#define SSH2_AGENTC_REMOVE_IDENTITY             18
#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES       19
#define SSH_AGENTC_ADD_SMARTCARD_KEY            20
#define SSH_AGENTC_REMOVE_SMARTCARD_KEY         21

SSH2_AGENTC_REQUEST_IDENTITIES

SSH2_AGENT_IDENTITIES_ANSWER
	uint32		n
	string		publickey1
	string		comment1
	...
	string		publickeyn
	string		commentn

SSH2_AGENTC_SIGN_REQUEST
	string		pubkey
	string		data
	uint32		flags

SSH2_AGENT_SIGN_RESPONSE
	string		signature_blob

SSH2_AGENTC_ADD_IDENTITY
	string		type	(ssh-rsa or ssh-dss)
	payload depends on keytype,	[XXX should be wrapped in a string]
	for ssh-rsa
		mpint	n
		mpint	e
		mpint	d
		mpint	iqmp
		mpint	p
		mpint	q
	for ssh-dss
		mpint	p
		mpint	q
		mpint	g
		mpint	pub_key
		mpint	priv_key
	string		comment

SSH2_AGENTC_REMOVE_IDENTITY
	string		publickey1

SSH2_AGENTC_REMOVE_ALL_IDENTITIES

SSH_AGENTC_ADD_SMARTCARD_KEY
	string		cardid

SSH_AGENTC_REMOVE_SMARTCARD_KEY
	string		cardid

a public key:
	string		pubkey
is always encoded as:
     string    "ssh-rsa"
     mpint     e
     mpint     n
or
     string    "ssh-dss"
     mpint     p
     mpint     q
     mpint     g
     mpint     y
so the complete public key info is wrapped in a string.

---

see http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/RFC.nroff?rev=1.2

Ylonen  
Internet-Draft  SSH (Secure Shell) Remote Login Protocol 15 November 1995

The Authentication Agent Protocol

The authentication agent is a program that can be used to hold RSA
authentication keys for the user (in future, it might hold data for
other authentication types as well).  An authorized program can send
requests to the agent to generate a proper response to an RSA challenge.
How the connection is made to the agent (or its representative) inside a
host and how access control is done inside a host is implementation-
dependent; however, how it is forwarded and how one interacts with it is
specified in this protocol.  The connection to the agent is normally
automatically forwarded over the secure channel.

A program that wishes to use the agent first opens a connection to its
local representative (typically, the agent itself or an SSH server).  It
then writes a request to the connection, and waits for response.  It is
recommended that at least five minutes of timeout are provided waiting
for the agent to respond to an authentication challenge (this gives suf
ficient time for the user to cut-and-paste the challenge to a separate
machine, perform the computation there, and cut-and-paste the result
back if so desired).

Messages sent to and by the agent are in the following format:

4 bytes     Length, msb first.  Does not include length itself.
1 byte      Packet type.  The value 255 is reserved for future extensions.
data        Any data, depending on packet type.  Encoding as in the ssh packet
	protocol.

The following message types are currently defined:

1 SSH_AGENTC_REQUEST_RSA_IDENTITIES

     (no arguments)

     Requests the agent to send a list of all RSA keys for which it can
     answer a challenge.

2 SSH_AGENT_RSA_IDENTITIES_ANSWER

     32-bit int       howmany
     howmany times:
     32-bit int       bits
     mp-int           public exponent
     mp-int           public modulus
     string           comment

     The agent sends this message in response to the to
     SSH_AGENTC_REQUEST_RSA_IDENTITIES.  The answer lists all RSA keys
     for which the agent can answer a challenge.  The comment field is
     intended to help identify each key; it may be printed by an appli
     cation to indicate which key is being used.  If the agent is not
     holding any keys, howmany will be zero.

3 SSH_AGENTC_RSA_CHALLENGE

     32-bit int   bits
     mp-int       public exponent
     mp-int       public modulus
     mp-int       challenge
     16 bytes     session_id
     32-bit int   response_type

     Requests RSA decryption of random challenge to authenticate the
     other side.  The challenge will be decrypted with the RSA private
     key corresponding to the given public key.

     The decrypted challenge must contain a zero in the highest (par
     tial) byte, 2 in the next byte, followed by non-zero random bytes,
     a zero byte, and then the real challenge value in the lowermost
     bytes.  The real challenge must be 32 8-bit bytes (256 bits).

     Response_type indicates the format of the response to be returned.
     Currently the only supported value is 1, which means to compute MD5
     of the real challenge plus session id, and return the resulting 16
     bytes in a SSH_AGENT_RSA_RESPONSE message.

4 SSH_AGENT_RSA_RESPONSE

     16 bytes   MD5 of decrypted challenge

     Answers an RSA authentication challenge.  The response is 16 bytes:
     the MD5 checksum of the 32-byte challenge.

5 SSH_AGENT_FAILURE

     (no arguments)

     This message is sent whenever the agent fails to answer a request
     properly.  For example, if the agent cannot answer a challenge
     (e.g., no longer has the proper key), it can respond with this.
     The agent also responds with this message if it receives a message
     it does not recognize.

6 SSH_AGENT_SUCCESS

     (no arguments)

     This message is sent by the agent as a response to certain requests
     that do not otherwise cause a message be sent.  Currently, this is
     only sent in response to SSH_AGENTC_ADD_RSA_IDENTITY and
     SSH_AGENTC_REMOVE_RSA_IDENTITY.

7 SSH_AGENTC_ADD_RSA_IDENTITY

     32-bit int   bits
     mp-int       public modulus
     mp-int       public exponent
     mp-int       private exponent
     mp-int       multiplicative inverse of p mod q
     mp-int       p
     mp-int       q
     string       comment

     Registers an RSA key with the agent.  After this request, the agent
     can use this RSA key to answer requests.  The agent responds with
     SSH_AGENT_SUCCESS or SSH_AGENT_FAILURE.

8 SSH_AGENT_REMOVE_RSA_IDENTITY

     32-bit int   bits
     mp-int       public exponent
     mp-int       public modulus

     Removes an RSA key from the agent.  The agent will no longer accept
     challenges for this key and will not list it as a supported iden
     tity.  The agent responds with SSH_AGENT_SUCCESS or SSH_AGENT_FAIL
     URE.

If the agent receives a message that it does not understand, it responds
with SSH_AGENT_FAILURE.  This permits compatible future extensions.

It is possible that several clients have a connection open to the
authentication agent simultaneously.  Each client will use a separate
connection (thus, any SSH connection can have multiple agent connections
active simultaneously).

---

From: Simon Tatham <[email-masked]>
Date: Mon, 21 Jan 2002 11:40:41 +0000

Niels Möller <[email-masked]> wrote:
> inofficial description of the ssh-agent protocol
[...]

I'm not sure this is quite right. I tried making my own observations
of the ssh-agent2 protocol once, and came up with slightly different
conclusions.

In ssh-agent1, an empty message-type-1 is not a version request at
all: it's SSH_AGENTC_REQUEST_RSA_IDENTITIES, and the response from
ssh-agent1 is a list of public keys.

If I recall my own analysis correctly, the ssh-agent2 version
request is a message-type-1 which _does_ contain something. Then you
have some options:

 - if the agent responds to a version request by returning an
   ssh-agent1 list of keys, you know it doesn't understand the
   agent2 protocol at all.

 - if it returns SSH_AGENT_VERSION_RESPONSE then you know it can act
   as an agent2; you can then send it an _empty_ message-type-1 and
   see what it says to that. If it responds to _that_ with an
   ssh-agent1 list of keys then it's able to act as a two-in-one
   agent1 and agent2. (A double agent? :-)

IIRC the snag is that earlier ssh2 agent clients do expect an empty
message-type-1 to provoke an agent2 version response, so to
interoperate with these older clients you would want to be able to
disable ssh-agent1 behaviour in a decently configurable agent.

It's nice to see that the OpenSSH agent2 message numbers are
completely disjoint from the ssh.com ones, though; at least there'll
be no problem with running both of those agent protocols in
parallel!

Cheers,
Simon
-- 
Simon Tatham         "The difference between theory and practice is
<[email-masked]>    that, in theory, there is no difference."